From bd0269e9972e5ac4741f911a088f12634035a14f Mon Sep 17 00:00:00 2001 From: Florian Berthold Date: Fri, 8 May 2026 04:03:35 +0200 Subject: [PATCH] vendor: import Pawn 1.3.8 from in-game AddOns dir MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Imported from /srv/add01/wow-ascension/Interface/AddOns/Pawn — the build Ascension's WotLK 3.3.5 client ships. No upstream history rooted: Pawn 1.3.8 (circa 2010) predates the modern Pawn repo at github.com/VgerMods/Pawn (retail-only since 6.x), and Ascension-Addons doesn't carry a Pawn fork. If a Wrath-era Pawn upstream ever surfaces, this can be re-rooted on it the same way coa-bartender / coa-omen were. License: per .toc. --- Bindings.xml | 11 + Gems.lua | 442 +++++ Localization.lua | 822 ++++++++++ Pawn.lua | 2990 ++++++++++++++++++++++++++++++++++ Pawn.toc | 16 + PawnUI.lua | 1996 +++++++++++++++++++++++ PawnUI.xml | 1473 +++++++++++++++++ Readme.htm | 755 +++++++++ Textures/CompareBanner.tga | Bin 0 -> 252559 bytes Textures/CompareBar.tga | Bin 0 -> 52783 bytes Textures/CompareBarLeft.tga | Bin 0 -> 27128 bytes Textures/CompareBarRight.tga | Bin 0 -> 26987 bytes Textures/HorizontalBar.tga | Bin 0 -> 14514 bytes Textures/PawnButton.tga | Bin 0 -> 42477 bytes Textures/PawnLogo.tga | Bin 0 -> 22566 bytes Textures/PawnUIHeader.tga | Bin 0 -> 80183 bytes Textures/Question.tga | Bin 0 -> 2934 bytes Textures/UpgradeArrowBig.tga | Bin 0 -> 17742 bytes Version history.htm | 957 +++++++++++ VgerCore/Readme.htm | 113 ++ VgerCore/VgerCore.css | 83 + VgerCore/VgerCore.lua | 268 +++ Wowhead.lua | 487 ++++++ 23 files changed, 10413 insertions(+) create mode 100644 Bindings.xml create mode 100644 Gems.lua create mode 100644 Localization.lua create mode 100644 Pawn.lua create mode 100644 Pawn.toc create mode 100644 PawnUI.lua create mode 100644 PawnUI.xml create mode 100644 Readme.htm create mode 100644 Textures/CompareBanner.tga create mode 100644 Textures/CompareBar.tga create mode 100644 Textures/CompareBarLeft.tga create mode 100644 Textures/CompareBarRight.tga create mode 100644 Textures/HorizontalBar.tga create mode 100644 Textures/PawnButton.tga create mode 100644 Textures/PawnLogo.tga create mode 100644 Textures/PawnUIHeader.tga create mode 100644 Textures/Question.tga create mode 100644 Textures/UpgradeArrowBig.tga create mode 100644 Version history.htm create mode 100644 VgerCore/Readme.htm create mode 100644 VgerCore/VgerCore.css create mode 100644 VgerCore/VgerCore.lua create mode 100644 Wowhead.lua diff --git a/Bindings.xml b/Bindings.xml new file mode 100644 index 0000000..9021d5e --- /dev/null +++ b/Bindings.xml @@ -0,0 +1,11 @@ + + + PawnUIShow() + + + PawnUI_SetCompareFromHover(1) + + + PawnUI_SetCompareFromHover(2) + + \ No newline at end of file diff --git a/Gems.lua b/Gems.lua new file mode 100644 index 0000000..3f901cd --- /dev/null +++ b/Gems.lua @@ -0,0 +1,442 @@ +-- Pawn by Vger-Azjol-Nerub +-- www.vgermods.com +-- © 2006-2010 Green Eclipse. This mod is released under the Creative Commons Attribution-NonCommercial-NoDerivs 3.0 license. +-- See Readme.htm for more information. +-- +-- Gem information +------------------------------------------------------------ + + +-- Gem table row format: +-- { ItemID, Class, Red, Yellow, Blue, "Stat1" Quantity1, "Stat2", Quantity2 } +-- ItemID: The item ID of this gem. +-- Red: Is this gem red? +-- Yellow: Is this gem yellow? +-- Blue: Is this gem blue? +-- "Stat": The stat that this gem gives. +-- Quantity: How much of the stat that the gem gives. + + +--======================================== +-- Colored level 80 uncommon-quality gems +--======================================== +PawnGemData80Uncommon = +{ + + +------------------------------------------------------------ +-- Red gems +------------------------------------------------------------ + +{ 39900, true, false, false, "Strength", 12 }, -- Bold Bloodstone +{ 39905, true, false, false, "Agility", 12 }, -- Delicate Bloodstone +{ 39906, true, false, false, "Ap", 24 }, -- Bright Bloodstone +{ 39907, true, false, false, "DodgeRating", 12 }, -- Subtle Bloodstone +{ 39908, true, false, false, "ParryRating", 12 }, -- Flashing Bloodstone +{ 39909, true, false, false, "ArmorPenetration", 12 }, -- Fractured Bloodstone +{ 39910, true, false, false, "ExpertiseRating", 12 }, -- Precise Bloodstone +{ 39911, true, false, false, "SpellPower", 14 }, -- Runed Bloodstone + + +------------------------------------------------------------ +-- Orange gems +------------------------------------------------------------ + +{ 39946, true, true, false, "SpellPower", 7, "Intellect", 6 }, -- Luminous Huge Citrine +{ 39947, true, true, false, "Strength", 6, "CritRating", 6 }, -- Inscribed Huge Citrine +{ 39948, true, true, false, "Strength", 6, "HitRating", 6 }, -- Etched Huge Citrine +{ 39949, true, true, false, "Strength", 6, "DefenseRating", 6 }, -- Champion's Huge Citrine +{ 39950, true, true, false, "Strength", 6, "ResilienceRating", 6 }, -- Resplendent Huge Citrine +{ 39951, true, true, false, "Strength", 6, "HasteRating", 6 }, -- Fierce Huge Citrine +{ 39952, true, true, false, "Agility", 6, "CritRating", 6 }, -- Deadly Huge Citrine +{ 39953, true, true, false, "Agility", 6, "HitRating", 6 }, -- Glinting Huge Citrine +{ 39954, true, true, false, "Agility", 6, "ResilienceRating", 6 }, -- Lucent Huge Citrine +{ 39955, true, true, false, "Agility", 6, "HasteRating", 6 }, -- Deft Huge Citrine +{ 39956, true, true, false, "SpellPower", 7, "CritRating", 6 }, -- Potent Huge Citrine +{ 39957, true, true, false, "SpellPower", 7, "HitRating", 6 }, -- Veiled Huge Citrine +{ 39958, true, true, false, "SpellPower", 7, "ResilienceRating", 6 }, -- Durable Huge Citrine +{ 39959, true, true, false, "SpellPower", 7, "HasteRating", 6 }, -- Reckless Huge Citrine +{ 39960, true, true, false, "Ap", 12, "CritRating", 6 }, -- Wicked Huge Citrine +{ 39961, true, true, false, "Ap", 12, "HitRating", 6 }, -- Pristine Huge Citrine +{ 39962, true, true, false, "Ap", 12, "ResilienceRating", 6 }, -- Empowered Huge Citrine +{ 39963, true, true, false, "Ap", 12, "HasteRating", 6 }, -- Stark Huge Citrine +{ 39964, true, true, false, "DodgeRating", 6, "DefenseRating", 6 }, -- Stalwart Huge Citrine +{ 39965, true, true, false, "ParryRating", 6, "DefenseRating", 6 }, -- Glimmering Huge Citrine +{ 39966, true, true, false, "ExpertiseRating", 6, "HitRating", 6 }, -- Accurate Huge Citrine +{ 39967, true, true, false, "ExpertiseRating", 6, "DefenseRating", 6 }, -- Resolute Huge Citrine + + +------------------------------------------------------------ +-- Yellow gems +------------------------------------------------------------ + +{ 39912, false, true, false, "Intellect", 12 }, -- Brilliant Sun Crystal +{ 39914, false, true, false, "CritRating", 12 }, -- Smooth Sun Crystal +{ 39915, false, true, false, "HitRating", 12 }, -- Rigid Sun Crystal +{ 39916, false, true, false, "DefenseRating", 12 }, -- Thick Sun Crystal +{ 39917, false, true, false, "ResilienceRating", 12 }, -- Mystic Sun Crystal +{ 39918, false, true, false, "HasteRating", 12 }, -- Quick Sun Crystal + + +------------------------------------------------------------ +-- Green gems +------------------------------------------------------------ + +{ 39968, false, true, true, "Intellect", 6, "Stamina", 9 }, -- Timeless Dark Jade +{ 39974, false, true, true, "CritRating", 6, "Stamina", 9 }, -- Jagged Dark Jade +{ 39975, false, true, true, "HitRating", 6, "Stamina", 9 }, -- Vivid Dark Jade +{ 39976, false, true, true, "DefenseRating", 6, "Stamina", 9 }, -- Enduring Dark Jade +{ 39977, false, true, true, "ResilienceRating", 6, "Stamina", 9 }, -- Steady Dark Jade +{ 39978, false, true, true, "HasteRating", 6, "Stamina", 9 }, -- Forceful Dark Jade +{ 39979, false, true, true, "Intellect", 6, "Spirit", 6 }, -- Seer's Dark Jade +{ 39980, false, true, true, "CritRating", 6, "Spirit", 6 }, -- Misty Dark Jade +{ 39981, false, true, true, "HitRating", 6, "Spirit", 6 }, -- Shining Dark Jade +{ 39982, false, true, true, "ResilienceRating", 6, "Spirit", 6 }, -- Turbid Dark Jade +{ 39983, false, true, true, "HasteRating", 6, "Spirit", 6 }, -- Intricate Dark Jade +{ 39984, false, true, true, "Intellect", 6, "Mp5", 3 }, -- Dazzling Dark Jade +{ 39985, false, true, true, "CritRating", 6, "Mp5", 3 }, -- Sundered Dark Jade +{ 39986, false, true, true, "HitRating", 6, "Mp5", 3 }, -- Lambent Dark Jade +{ 39988, false, true, true, "ResilienceRating", 6, "Mp5", 3 }, -- Opaque Dark Jade +{ 39989, false, true, true, "HasteRating", 6, "Mp5", 3 }, -- Energized Dark Jade +{ 39990, false, true, true, "CritRating", 6, "SpellPenetration", 8 }, -- Radiant Dark Jade +{ 39991, false, true, true, "HitRating", 6, "SpellPenetration", 8 }, -- Tense Dark Jade +{ 39992, false, true, true, "HasteRating", 6, "SpellPenetration", 8 }, -- Shattered Dark Jade + + +------------------------------------------------------------ +-- Blue gems +------------------------------------------------------------ + +{ 39919, false, false, true, "Stamina", 18 }, -- Solid Chalcedony +{ 39920, false, false, true, "Spirit", 12 }, -- Sparkling Chalcedony +{ 39927, false, false, true, "Mp5", 6 }, -- Lustrous Chalcedony +{ 39932, false, false, true, "SpellPenetration", 15 }, -- Stormy Chalcedony + + +------------------------------------------------------------ +-- Purple gems +------------------------------------------------------------ + +{ 39933, true, false, true, "ArmorPenetration", 6, "Stamina", 9 }, -- Puissant Shadow Crystal +{ 39934, true, false, true, "Strength", 6, "Stamina", 9 }, -- Sovereign Shadow Crystal +{ 39935, true, false, true, "Agility", 6, "Stamina", 9 }, -- Shifting Shadow Crystal +{ 39936, true, false, true, "SpellPower", 7, "Stamina", 9 }, -- Glowing Shadow Crystal +{ 39937, true, false, true, "Ap", 12, "Stamina", 9 }, -- Balanced Shadow Crystal +{ 39938, true, false, true, "DodgeRating", 6, "Stamina", 9 }, -- Regal Shadow Crystal +{ 39939, true, false, true, "ParryRating", 6, "Stamina", 9 }, -- Defender's Shadow Crystal +{ 39940, true, false, true, "ExpertiseRating", 6, "Stamina", 9 }, -- Guardian's Shadow Crystal +{ 39941, true, false, true, "SpellPower", 7, "Spirit", 6 }, -- Purified Shadow Crystal +{ 39942, true, false, true, "Agility", 6, "Mp5", 3 }, -- Tenuous Shadow Crystal +{ 39943, true, false, true, "SpellPower", 7, "Mp5", 3 }, -- Royal Shadow Crystal +{ 39944, true, false, true, "Ap", 12, "Mp5", 3 }, -- Infused Shadow Crystal +{ 39945, true, false, true, "SpellPower", 7, "SpellPenetration", 8 }, -- Mysterious Shadow Crystal + + +} + + +--======================================== +-- Colored level 80 rare-quality gems +--======================================== +PawnGemData80Rare = +{ + + +------------------------------------------------------------ +-- Red gems +------------------------------------------------------------ + +{ 39996, true, false, false, "Strength", 16 }, -- Bold Scarlet Ruby +{ 39997, true, false, false, "Agility", 16 }, -- Delicate Scarlet Ruby +{ 39998, true, false, false, "SpellPower", 19 }, -- Runed Scarlet Ruby +{ 39999, true, false, false, "Ap", 32 }, -- Bright Scarlet Ruby +{ 40000, true, false, false, "DodgeRating", 16 }, -- Subtle Scarlet Ruby +{ 40001, true, false, false, "ParryRating", 16 }, -- Flashing Scarlet Ruby +{ 40002, true, false, false, "ArmorPenetration", 16 }, -- Fractured Scarlet Ruby +{ 40003, true, false, false, "ExpertiseRating", 16 }, -- Precise Scarlet Ruby + + +------------------------------------------------------------ +-- Orange gems +------------------------------------------------------------ + +{ 40037, true, true, false, "Strength", 8, "CritRating", 8 }, -- Inscribed Monarch Topaz +{ 40038, true, true, false, "Strength", 8, "HitRating", 8 }, -- Etched Monarch Topaz +{ 40039, true, true, false, "Strength", 8, "DefenseRating", 8 }, -- Champion's Monarch Topaz +{ 40040, true, true, false, "Strength", 8, "ResilienceRating", 8 }, -- Resplendent Monarch Topaz +{ 40041, true, true, false, "Strength", 8, "HasteRating", 8 }, -- Fierce Monarch Topaz +{ 40043, true, true, false, "Agility", 8, "CritRating", 8 }, -- Deadly Monarch Topaz +{ 40044, true, true, false, "Agility", 8, "HitRating", 8 }, -- Glinting Monarch Topaz +{ 40045, true, true, false, "Agility", 8, "ResilienceRating", 8 }, -- Lucent Monarch Topaz +{ 40046, true, true, false, "Agility", 8, "HasteRating", 8 }, -- Deft Monarch Topaz +{ 40047, true, true, false, "SpellPower", 9, "Intellect", 8 }, -- Luminous Monarch Topaz +{ 40048, true, true, false, "SpellPower", 9, "CritRating", 8 }, -- Potent Monarch Topaz +{ 40049, true, true, false, "SpellPower", 9, "HitRating", 8 }, -- Veiled Monarch Topaz +{ 40050, true, true, false, "SpellPower", 9, "ResilienceRating", 8 }, -- Durable Monarch Topaz +{ 40051, true, true, false, "SpellPower", 9, "HasteRating", 8 }, -- Reckless Monarch Topaz +{ 40052, true, true, false, "Ap", 16, "CritRating", 8 }, -- Wicked Monarch Topaz +{ 40053, true, true, false, "Ap", 16, "HitRating", 8 }, -- Pristine Monarch Topaz +{ 40054, true, true, false, "Ap", 16, "ResilienceRating", 8 }, -- Empowered Monarch Topaz +{ 40055, true, true, false, "Ap", 16, "HasteRating", 8 }, -- Stark Monarch Topaz +{ 40056, true, true, false, "DodgeRating", 8, "DefenseRating", 8 }, -- Stalwart Monarch Topaz +{ 40057, true, true, false, "ParryRating", 8, "DefenseRating", 8 }, -- Glimmering Monarch Topaz +{ 40058, true, true, false, "ExpertiseRating", 8, "HitRating", 8 }, -- Accurate Monarch Topaz +{ 40059, true, true, false, "ExpertiseRating", 8, "DefenseRating", 8 }, -- Resolute Monarch Topaz + + +------------------------------------------------------------ +-- Yellow gems +------------------------------------------------------------ + +{ 40012, false, true, false, "Intellect", 16 }, -- Brilliant Autumn's Glow +{ 40013, false, true, false, "CritRating", 16 }, -- Smooth Autumn's Glow +{ 40014, false, true, false, "HitRating", 16 }, -- Rigid Autumn's Glow +{ 40015, false, true, false, "DefenseRating", 16 }, -- Thick Autumn's Glow +{ 40016, false, true, false, "ResilienceRating", 16 }, -- Mystic Autumn's Glow +{ 40017, false, true, false, "HasteRating", 16 }, -- Quick Autumn's Glow + + +------------------------------------------------------------ +-- Green gems +------------------------------------------------------------ + +{ 40085, false, true, true, "Intellect", 8, "Stamina", 12 }, -- Timeless Forest Emerald +{ 40086, false, true, true, "CritRating", 8, "Stamina", 12 }, -- Jagged Forest Emerald +{ 40088, false, true, true, "HitRating", 8, "Stamina", 12 }, -- Vivid Forest Emerald +{ 40089, false, true, true, "DefenseRating", 8, "Stamina", 12 }, -- Enduring Forest Emerald +{ 40090, false, true, true, "ResilienceRating", 8, "Stamina", 12 }, -- Steady Forest Emerald +{ 40091, false, true, true, "HasteRating", 8, "Stamina", 12 }, -- Forceful Forest Emerald +{ 40092, false, true, true, "Intellect", 8, "Spirit", 8 }, -- Seer's Forest Emerald +{ 40094, false, true, true, "Intellect", 8, "Mp5", 4 }, -- Dazzling Forest Emerald +{ 40095, false, true, true, "CritRating", 8, "Spirit", 8 }, -- Misty Forest Emerald +{ 40096, false, true, true, "CritRating", 8, "Mp5", 4 }, -- Sundered Forest Emerald +{ 40098, false, true, true, "CritRating", 8, "SpellPenetration", 10 }, -- Radiant Forest Emerald +{ 40099, false, true, true, "HitRating", 8, "Spirit", 8 }, -- Shining Forest Emerald +{ 40100, false, true, true, "HitRating", 8, "Mp5", 4 }, -- Lambent Forest Emerald +{ 40101, false, true, true, "HitRating", 8, "SpellPenetration", 10 }, -- Tense Forest Emerald +{ 40102, false, true, true, "ResilienceRating", 8, "Spirit", 8 }, -- Turbid Forest Emerald +{ 40103, false, true, true, "ResilienceRating", 8, "Mp5", 4 }, -- Opaque Forest Emerald +{ 40104, false, true, true, "HasteRating", 8, "Spirit", 8 }, -- Intricate Forest Emerald +{ 40105, false, true, true, "HasteRating", 8, "Mp5", 4 }, -- Energized Forest Emerald +{ 40106, false, true, true, "HasteRating", 8, "SpellPenetration", 10 }, -- Shattered Forest Emerald + + +------------------------------------------------------------ +-- Blue gems +------------------------------------------------------------ + +{ 40008, false, false, true, "Stamina", 24 }, -- Solid Sky Sapphire +{ 40009, false, false, true, "Spirit", 16 }, -- Sparkling Sky Sapphire +{ 40010, false, false, true, "Mp5", 8 }, -- Lustrous Sky Sapphire +{ 40011, false, false, true, "SpellPenetration", 20 }, -- Stormy Sky Sapphire + + +------------------------------------------------------------ +-- Purple gems +------------------------------------------------------------ + +{ 40022, true, false, true, "Strength", 8, "Stamina", 12 }, -- Sovereign Twilight Opal +{ 40023, true, false, true, "Agility", 8, "Stamina", 12 }, -- Shifting Twilight Opal +{ 40024, true, false, true, "Agility", 8, "Mp5", 4 }, -- Tenuous Twilight Opal +{ 40025, true, false, true, "SpellPower", 9, "Stamina", 12 }, -- Glowing Twilight Opal +{ 40026, true, false, true, "SpellPower", 9, "Spirit", 8 }, -- Purified Twilight Opal +{ 40027, true, false, true, "SpellPower", 9, "Mp5", 4 }, -- Royal Twilight Opal +{ 40028, true, false, true, "SpellPower", 9, "SpellPenetration", 10 }, -- Mysterious Twilight Opal +{ 40029, true, false, true, "Ap", 16, "Stamina", 12 }, -- Balanced Twilight Opal +{ 40030, true, false, true, "Ap", 16, "Mp5", 4 }, -- Infused Twilight Opal +{ 40031, true, false, true, "DodgeRating", 8, "Stamina", 12 }, -- Regal Twilight Opal +{ 40032, true, false, true, "ParryRating", 8, "Stamina", 12 }, -- Defender's Twilight Opal +{ 40033, true, false, true, "ArmorPenetration", 8, "Stamina", 12 }, -- Puissant Twilight Opal +{ 40034, true, false, true, "ExpertiseRating", 8, "Stamina", 12 }, -- Guardian's Twilight Opal + + +} + + +--======================================== +-- Colored level 80 epic-quality gems +--======================================== +PawnGemData80Epic = +{ + + +------------------------------------------------------------ +-- Red gems +------------------------------------------------------------ + +{ 40111, true, false, false, "Strength", 20 }, -- Bold Cardinal Ruby +{ 40112, true, false, false, "Agility", 20 }, -- Delicate Cardinal Ruby +{ 40113, true, false, false, "SpellPower", 23 }, -- Runed Cardinal Ruby +{ 40114, true, false, false, "Ap", 40 }, -- Bright Cardinal Ruby +{ 40115, true, false, false, "DodgeRating", 20 }, -- Subtle Cardinal Ruby +{ 40116, true, false, false, "ParryRating", 20 }, -- Flashing Cardinal Ruby +{ 40117, true, false, false, "ArmorPenetration", 20 }, -- Fractured Cardinal Ruby +{ 40118, true, false, false, "ExpertiseRating", 20 }, -- Precise Cardinal Ruby + + +------------------------------------------------------------ +-- Orange gems +------------------------------------------------------------ + +{ 40142, true, true, false, "Strength", 10, "CritRating", 10 }, -- Inscribed Ametrine +{ 40143, true, true, false, "Strength", 10, "HitRating", 10 }, -- Etched Ametrine +{ 40144, true, true, false, "Strength", 10, "DefenseRating", 10 }, -- Champion's Ametrine +{ 40145, true, true, false, "Strength", 10, "ResilienceRating", 10 }, -- Resplendent Ametrine +{ 40146, true, true, false, "Strength", 10, "HasteRating", 10 }, -- Fierce Ametrine +{ 40147, true, true, false, "Agility", 10, "CritRating", 10 }, -- Deadly Ametrine +{ 40148, true, true, false, "Agility", 10, "HitRating", 10 }, -- Glinting Ametrine +{ 40149, true, true, false, "Agility", 10, "ResilienceRating", 10 }, -- Lucent Ametrine +{ 40150, true, true, false, "Agility", 10, "HasteRating", 10 }, -- Deft Ametrine +{ 40151, true, true, false, "SpellPower", 12, "Intellect", 10 }, -- Luminous Ametrine +{ 40152, true, true, false, "SpellPower", 12, "CritRating", 10 }, -- Potent Ametrine +{ 40153, true, true, false, "SpellPower", 12, "HitRating", 10 }, -- Veiled Ametrine +{ 40154, true, true, false, "SpellPower", 12, "ResilienceRating", 10 }, -- Durable Ametrine +{ 40155, true, true, false, "SpellPower", 12, "HasteRating", 10 }, -- Reckless Ametrine +{ 40156, true, true, false, "Ap", 20, "CritRating", 10 }, -- Wicked Ametrine +{ 40157, true, true, false, "Ap", 20, "HitRating", 10 }, -- Pristine Ametrine +{ 40158, true, true, false, "Ap", 20, "ResilienceRating", 10 }, -- Empowered Ametrine +{ 40159, true, true, false, "Ap", 20, "HasteRating", 10 }, -- Stark Ametrine +{ 40160, true, true, false, "DodgeRating", 10, "DefenseRating", 10 }, -- Stalwart Ametrine +{ 40161, true, true, false, "ParryRating", 10, "DefenseRating", 10 }, -- Glimmering Ametrine +{ 40162, true, true, false, "ExpertiseRating", 10, "HitRating", 10 }, -- Accurate Ametrine +{ 40163, true, true, false, "ExpertiseRating", 10, "DefenseRating", 10 }, -- Resolute Ametrine + + +------------------------------------------------------------ +-- Yellow gems +------------------------------------------------------------ + +{ 40123, false, true, false, "Intellect", 20 }, -- Brilliant King's Amber +{ 40124, false, true, false, "CritRating", 20 }, -- Smooth King's Amber +{ 40125, false, true, false, "HitRating", 20 }, -- Rigid King's Amber +{ 40126, false, true, false, "DefenseRating", 20 }, -- Thick King's Amber +{ 40127, false, true, false, "ResilienceRating", 20 }, -- Mystic King's Amber +{ 40128, false, true, false, "HasteRating", 20 }, -- Quick King's Amber + + +------------------------------------------------------------ +-- Green gems +------------------------------------------------------------ + +{ 40164, false, true, true, "Intellect", 10, "Stamina", 15 }, -- Timeless Eye of Zul +{ 40165, false, true, true, "CritRating", 10, "Stamina", 15 }, -- Jagged Eye of Zul +{ 40166, false, true, true, "HitRating", 10, "Stamina", 15 }, -- Vivid Eye of Zul +{ 40167, false, true, true, "DefenseRating", 10, "Stamina", 15 }, -- Enduring Eye of Zul +{ 40168, false, true, true, "ResilienceRating", 10, "Stamina", 15 }, -- Steady Eye of Zul +{ 40169, false, true, true, "HasteRating", 10, "Stamina", 15 }, -- Forceful Eye of Zul +{ 40170, false, true, true, "Intellect", 10, "Spirit", 10 }, -- Seer's Eye of Zul +{ 40171, false, true, true, "CritRating", 10, "Spirit", 10 }, -- Misty Eye of Zul +{ 40172, false, true, true, "HitRating", 10, "Spirit", 10 }, -- Shining Eye of Zul +{ 40173, false, true, true, "ResilienceRating", 10, "Spirit", 10 }, -- Turbid Eye of Zul +{ 40174, false, true, true, "HasteRating", 10, "Spirit", 10 }, -- Intricate Eye of Zul +{ 40175, false, true, true, "Intellect", 10, "Mp5", 5 }, -- Dazzling Eye of Zul +{ 40176, false, true, true, "CritRating", 10, "Mp5", 5 }, -- Sundered Eye of Zul +{ 40177, false, true, true, "HitRating", 10, "Mp5", 5 }, -- Lambent Eye of Zul +{ 40178, false, true, true, "ResilienceRating", 10, "Mp5", 5 }, -- Opaque Eye of Zul +{ 40179, false, true, true, "HasteRating", 10, "Mp5", 5 }, -- Energized Eye of Zul +{ 40180, false, true, true, "CritRating", 10, "SpellPenetration", 13 }, -- Radiant Eye of Zul +{ 40181, false, true, true, "HitRating", 10, "SpellPenetration", 13 }, -- Tense Eye of Zul +{ 40182, false, true, true, "HasteRating", 10, "SpellPenetration", 13 }, -- Shattered Eye of Zul + + +------------------------------------------------------------ +-- Blue gems +------------------------------------------------------------ + +{ 40119, false, false, true, "Stamina", 30 }, -- Solid Majestic Zircon +{ 40120, false, false, true, "Spirit", 20 }, -- Sparkling Majestic Zircon +{ 40121, false, false, true, "Mp5", 10 }, -- Lustrous Majestic Zircon +{ 40122, false, false, true, "SpellPenetration", 25 }, -- Stormy Majestic Zircon + + +------------------------------------------------------------ +-- Purple gems +------------------------------------------------------------ + +{ 40129, true, false, true, "Strength", 10, "Stamina", 15 }, -- Sovereign Dreadstone +{ 40130, true, false, true, "Agility", 10, "Stamina", 15 }, -- Shifting Dreadstone +{ 40131, true, false, true, "Agility", 10, "Mp5", 5 }, -- Tenuous Dreadstone +{ 40132, true, false, true, "SpellPower", 12, "Stamina", 15 }, -- Glowing Dreadstone +{ 40133, true, false, true, "SpellPower", 12, "Spirit", 10 }, -- Purified Dreadstone +{ 40134, true, false, true, "SpellPower", 12, "Mp5", 5 }, -- Royal Dreadstone +{ 40135, true, false, true, "SpellPower", 12, "SpellPenetration", 13 }, -- Mysterious Dreadstone +{ 40136, true, false, true, "Ap", 20, "Stamina", 15 }, -- Balanced Dreadstone +{ 40137, true, false, true, "Ap", 20, "Mp5", 5 }, -- Infused Dreadstone +{ 40138, true, false, true, "DodgeRating", 10, "Stamina", 15 }, -- Regal Dreadstone +{ 40139, true, false, true, "ParryRating", 10, "Stamina", 15 }, -- Defender's Dreadstone +{ 40140, true, false, true, "ArmorPenetration", 10, "Stamina", 15 }, -- Puissant Dreadstone +{ 40141, true, false, true, "ExpertiseRating", 10, "Stamina", 15 }, -- Guardian's Dreadstone + + +} + + +--======================================== +-- Level 80 crafted meta gems +--======================================== +PawnMetaGemData80Rare = +{ + + +------------------------------------------------------------ +-- Meta gems: Earthsiege +------------------------------------------------------------ + +{ 41380, false, false, false, "Stamina", 32 }, -- Austere Earthsiege Diamond (2% Increased Armor Value from Items) +{ 41381, false, false, false, "Ap", 42 }, -- Persistent Earthsiege Diamond (Stun Duration Reduced by 10%) +{ 41382, false, false, false, "SpellPower", 25 }, -- Trenchant Earthsiege Diamond (Stun Duration Reduced by 10%) +{ 41385, false, false, false, "Ap", 42 }, -- Invigorating Earthsiege Diamond (Sometimes Heal on Your Crits) +{ 41389, false, false, false, "CritRating", 21 }, -- Beaming Earthsiege Diamond (+2% Mana) +{ 41395, false, false, false, "SpellPower", 25 }, -- Bracing Earthsiege Diamond (2% Reduced Threat) +{ 41396, false, false, false, "DefenseRating", 21 }, -- Eternal Earthsiege Diamond (+5% Shield Block Value) +{ 41397, false, false, false, "Stamina", 32 }, -- Powerful Earthsiege Diamond (Stun Duration Reduced by 10%) +{ 41398, false, false, false, "Agility", 21 }, -- Relentless Earthsiege Diamond (3% Increased Critical Damage) +{ 41401, false, false, false, "Intellect", 21 }, -- Insightful Earthsiege Diamond (Chance to restore mana on spellcast) + + +------------------------------------------------------------ +-- Meta gems: Skyflare +------------------------------------------------------------ + +{ 41285, false, false, false, "CritRating", 21 }, -- Chaotic Skyflare Diamond (3% Increased Critical Damage) +{ 41307, false, false, false, "CritRating", 25 }, -- Destructive Skyflare Diamond (1% Spell Reflect) +{ 41333, false, false, false, "SpellPower", 25 }, -- Ember Skyflare Diamond (+2% Intellect) +{ 41335, false, false, false, "CritRating", 21 }, -- Enigmatic Skyflare Diamond (Reduces Snare/Root Duration by 10%) +{ 41339, false, false, false, "Ap", 42 }, -- Swift Skyflare Diamond (Minor Run Speed Increase) +{ 41375, false, false, false, "SpellPower", 25 }, -- Tireless Skyflare Diamond (Minor Run Speed Increase) +{ 41376, false, false, false, "Mp5", 11 }, -- Revitalizing Skyflare Diamond (3% Increased Critical Healing Effect) +{ 41377, false, false, false, "Stamina", 32 }, -- Effulgent Skyflare Diamond (Reduce Spell Damage Taken by 2%) +{ 41378, false, false, false, "SpellPower", 25 }, -- Forlorn Skyflare Diamond (Silence Duration Reduced by 10%) +{ 41379, false, false, false, "CritRating", 21 }, -- Impassive Skyflare Diamond (Fear Duration Reduced by 10%) +{ 41400, false, false, false }, -- Thundering Skyflare Diamond (Chance to Increase Melee/Ranged Attack Speed ) + + +} + +--======================================== + +-- The master list of all tables of Pawn gem data + +PawnGemQualityLevels = +{ + { 80, PawnLocal.GemQualityLevel80Uncommon }, + { 81, PawnLocal.GemQualityLevel80Rare }, + { 82, PawnLocal.GemQualityLevel80Epic }, +} +PawnGemQualityTables = +{ + [80] = PawnGemData80Uncommon, + [81] = PawnGemData80Rare, + [82] = PawnGemData80Epic, +} +PawnDefaultGemQualityLevel = 81 + +PawnMetaGemQualityLevels = +{ + { 81, PawnLocal.MetaGemQualityLevel80Rare }, +} +PawnMetaGemQualityTables = +{ + [81] = PawnMetaGemData80Rare, +} +PawnDefaultMetaGemQualityLevel = 81 diff --git a/Localization.lua b/Localization.lua new file mode 100644 index 0000000..27ad585 --- /dev/null +++ b/Localization.lua @@ -0,0 +1,822 @@ +-- Pawn by Vger-Azjol-Nerub +-- www.vgermods.com +-- © 2006-2010 Green Eclipse. This mod is released under the Creative Commons Attribution-NonCommercial-NoDerivs 3.0 license. +-- See Readme.htm for more information. + +-- +-- English resources +------------------------------------------------------------ + + +------------------------------------------------------------ +-- "Constants" +------------------------------------------------------------ + +PawnQuestionTexture = "|TInterface\\AddOns\\Pawn\\Textures\\Question:0|t" -- Texture string that represents a (?). Don't need to localize this. +PawnUINoScale = "(none)" -- The name that shows up in lists of scales if you have no scales + +------------------------------------------------------------ +-- Master table of stats +------------------------------------------------------------ + +-- The master list of all stats that Pawn supports. +-- First column is the friendly translated name of the stat. +-- Second column is the Pawn name of the stat; this can't be translated. +-- Third column is the description of the stat. +-- Fourth column is an optional chunk of text instead of the "1 ___ is worth:" prompt. +-- If only a name is present, the row becomes an uneditable header in the UI and is otherwise ignored. +PawnStats = +{ + {"Base stats"}, + {"Strength", "Strength", "The primary stat, Strength."}, + {"Agility", "Agility", "The primary stat, Agility."}, + {"Stamina", "Stamina", "The primary stat, Stamina."}, + {"Intellect", "Intellect", "The primary stat, Intellect."}, + {"Spirit", "Spirit", "The primary stat, Spirit."}, + + {"Sockets"}, + {"Red socket", "RedSocket", "An empty red socket. Only counts for an item's base value."}, + {"Yellow socket", "YellowSocket", "An empty yellow socket. Only counts for an item's base value."}, + {"Blue socket", "BlueSocket", "An empty blue socket. Only counts for an item's base value."}, + {"Meta: stats", "MetaSocket", "An empty meta socket. Only counts the stat bonus of a meta gem, not the additional effect. The item's value will be the same whether or not the meta gem requirements are met."}, + {"Meta: effect", "MetaSocketEffect", "A meta socket, whether empty or full. Only counts the additional effect of a meta gem, not its stat bonus."}, + + {"Weapon stats"}, + {"DPS", "Dps", "Weapon damage per second. (If you want to value DPS differently for different types of weapons, see the \"Special weapon stats\" section.)"}, + {"Speed", "Speed", "Weapon speed, in seconds per swing. (If you prefer fast weapons, this number should be negative. See also: \"speed baseline\" in the \"Special weapon stats\" section.)"}, + + {"Hybrid ratings"}, + {"Hit rating", "HitRating", "Hit rating. Affects melee attacks, ranged attacks, and spells."}, + {"Crit rating", "CritRating", "Critical strike rating. Affects melee attacks, ranged attacks, and spells."}, + {"Haste rating", "HasteRating", "Haste rating. Affects melee attacks, ranged attacks, and spells."}, + {"Mastery rating", "MasteryRating", VgerCore.Color.Salmon .. "New stat coming in Cataclysm. " .. VgerCore.Color.Reset .. "Improves the unique bonus of the talent tree that you have the most points in."}, + + {"Offensive physical stats"}, + {"Attack power", "Ap", "Attack power. Does not include attack power that you will receive from Strength or Agility, or weapon DPS (for druids)."}, + {"Ranged AP", "Rap", "Ranged attack power."}, + {"Feral AP", "FeralAp", "Attack power that a weapon would grant a druid in feral forms. If you assign a value to this stat, you should not also assign a value to weapon DPS."}, + {"Expertise rating", "ExpertiseRating", "Expertise rating."}, + {"Armor pen.", "ArmorPenetration", "Armor penetration rating causes your attacks to ignore some of your opponent's armor.\n\n" .. VgerCore.Color.Salmon .. "Cataclysm: " .. VgerCore.Color.Reset .. "Items with ArPen will instead have other offensive stats."}, + + {"Spell stats"}, + {"Spell power", "SpellPower", "Spell power, which affects both spell damage and healing."}, + {"Mana per 5", "Mp5", "Mana regeneration per 5 seconds.\n\n" .. VgerCore.Color.Salmon .. "Cataclysm: " .. VgerCore.Color.Reset .. "Items with 1 MP5 will instead have 2 Spirit."}, + {"Spell penetration", "SpellPenetration", "Spell penetration causes your spells to ignore some of your opponent's resistances."}, + + {"Defense stats"}, + {"Armor", "Armor", "Armor, regardless of item type. Classes with abilties that give armor bonuses should assign a value to base and bonus armor instead."}, + {"Armor: base", "BaseArmor", "Base armor value on cloth, leather, mail, and plate. Can be multiplied by abilities such as Thick Hide and Frost Presence.\n\nTank items with bonus armor in green text will have all of their armor count as base armor, as mods cannot determine how much of the armor is bonus armor."}, + {"Armor: bonus", "BonusArmor", "Bonus armor value on weapons, trinkets, and rings. Not affected by abilities and talents that modify armor."}, + {"Block value", "BlockValue", "Block value increases the amount of damage absorbed with each successful shield block.\n\n" .. VgerCore.Color.Salmon .. "Cataclysm: " .. VgerCore.Color.Reset .. "Items with block value will instead have different tanking stats."}, + {"Block rating", "BlockRating", "Block rating increases your chances of blocking with a shield."}, + {"Defense rating", "DefenseRating", "Defense rating.\n\n" .. VgerCore.Color.Salmon .. "Cataclysm: " .. VgerCore.Color.Reset .. "Items with defense will instead have different tanking stats."}, + {"Dodge rating", "DodgeRating", "Dodge rating."}, + {"Parry rating", "ParryRating", "Parry rating."}, + {"Resilience rating", "ResilienceRating", "Resilience rating."}, + + {"Very rare stats"}, + {"Fire spell power", "FireSpellDamage", "Fire-only spell power. This stat does not appear on items that give spell power to all schools."}, + {"Shadow spell power", "ShadowSpellDamage", "Shadow-only spell power. This stat does not appear on items that give spell power to all schools."}, + {"Nature spell power", "NatureSpellDamage", "Nature-only spell power. This stat does not appear on items that give spell power to all schools."}, + {"Arcane spell power", "ArcaneSpellDamage", "Arcane-only spell power. This stat does not appear on items that give spell power to all schools."}, + {"Frost spell power", "FrostSpellDamage", "Frost-only spell power. This stat does not appear on items that give spell power to all schools."}, + {"Holy spell power", "HolySpellDamage", "Holy-only spell power. This stat is quite rare, and does not appear on items that give spell power to all schools."}, + {"All resistances", "AllResist", "All elemental resistances."}, + {"Fire resistance", "FireResist", "Fire resistance. This stat does not appear on items that give all elemental resistances."}, + {"Shadow resistance", "ShadowResist", "Shadow resistance. This stat does not appear on items that give all elemental resistances."}, + {"Nature resistance", "NatureResist", "Nature resistance. This stat does not appear on items that give all elemental resistances."}, + {"Arcane resistance", "ArcaneResist", "Arcane resistance. This stat does not appear on items that give all elemental resistances."}, + {"Frost resistance", "FrostResist", "Frost resistance. This stat does not appear on items that give all elemental resistances."}, + {"Health per 5", "Hp5", "Health regeneration per 5 seconds. Generally only appears on enchantments."}, + {"Health", "Health", "Raw health. Does not include health from Stamina. This generally appears only on enchantments."}, + {"Mana", "Mana", "Raw mana. Does not include mana from Intellect. This generally appears only on enchantments."}, + + {"Weapon types"}, + {"Axe", "IsAxe", "Points to be assigned if the item is an axe (of any kind)."}, + {"Bow", "IsBow", "Points to be assigned if the item is a bow, or a stack of arrows."}, + {"Crossbow", "IsCrossbow", "Points to be assigned if the item is a crossbow."}, + {"Dagger", "IsDagger", "Points to be assigned if the item is a dagger."}, + {"Fist weapon", "IsFist", "Points to be assigned if the item is a fist weapon (of any kind)."}, + {"Gun", "IsGun", "Points to be assigned if the item is a gun, or a stack of bullets."}, + {"Mace", "IsMace", "Points to be assigned if the item is a mace (of any kind)."}, + {"Polearm", "IsPolearm", "Points to be assigned if the item is a polearm."}, + {"Staff", "IsStaff", "Points to be assigned if the item is a staff."}, + {"Sword", "IsSword", "Points to be assigned if the item is a sword."}, + {"Thrown", "IsThrown", "Points to be assigned if the item is a thrown weapon."}, + {"Wand", "IsWand", "Points to be assigned if the item is a wand."}, + + {"Armor types"}, + {"Cloth", "IsCloth", "Points to be assigned if the item is cloth."}, + {"Leather", "IsLeather", "Points to be assigned if the item is leather."}, + {"Mail", "IsMail", "Points to be assigned if the item is mail."}, + {"Plate", "IsPlate", "Points to be assigned if the item is plate."}, + {"Shield", "IsShield", "Points to be assigned if the item is a shield."}, + + {"Special weapon stats"}, + {"Minimum damage", "MinDamage", "Weapon minimum damage."}, + {"Maximum damage", "MaxDamage", "Weapon maximum damage."}, + {"Melee: DPS", "MeleeDps", "Weapon damage per second, only for melee weapons."}, + {"Melee: min damage", "MeleeMinDamage", "Weapon minimum damage, only for melee weapons."}, + {"Melee: max damage", "MeleeMaxDamage", "Weapon maximum damage, only for melee weapons."}, + {"Melee: speed", "MeleeSpeed", "Weapon speed, only for melee weapons."}, + {"Ranged: DPS", "RangedDps", "Weapon damage per second, only for ranged weapons."}, + {"Ranged: min damage", "RangedMinDamage", "Weapon minimum damage, only for ranged weapons."}, + {"Ranged: max damage", "RangedMaxDamage", "Weapon maximum damage, only for ranged weapons."}, + {"Ranged: speed", "RangedSped", "Weapon speed, only for ranged weapons."}, + {"MH: DPS", "MainHandDps", "Weapon damage per second, only for main hand weapons."}, + {"MH: min damage", "MainHandMinDamage", "Weapon minimum damage, only for main hand weapons."}, + {"MH: max damage", "MainHandMaxDamage", "Weapon maximum damage, only for main hand weapons."}, + {"MH: speed", "MainHandSpeed", "Weapon speed, only for main hand weapons."}, + {"OH: DPS", "OffHandDps", "Weapon damage per second, only for off-hand weapons."}, + {"OH: min damage", "OffHandMinDamage", "Weapon minimum damage, only for off-hand weapons."}, + {"OH: max damage", "OffHandMaxDamage", "Weapon maximum damage, only for off-hand weapons."}, + {"OH: speed", "OffHandSpeed", "Weapon speed, only for off-hand weapons."}, + {"1H: DPS", "OneHandDps", "Weapon damage per second, only for weapons marked One Hand, not including Main Hand or Off Hand weapons."}, + {"1H: min damage", "OneHandMinDamage", "Weapon minimum damage, only for weapons marked One Hand, not including Main Hand or Off Hand weapons."}, + {"1H: max damage", "OneHandMaxDamage", "Weapon maximum damage, only for weapons marked One Hand, not including Main Hand or Off Hand weapons."}, + {"1H: speed", "OneHandSpeed", "Weapon speed, only for weapons marked One Hand, not including Main Hand or Off Hand weapons."}, + {"2H: DPS", "TwoHandDps", "Weapon damage per second, only for two-handed weapons."}, + {"2H: min damage", "TwoHandMinDamage", "Weapon minimum damage, only for two-handed weapons."}, + {"2H: max damage", "TwoHandMaxDamage", "Weapon maximum damage, only for two-handed weapons."}, + {"2H: speed", "TwoHandSpeed", "Weapon speed, only for two-handed weapons."}, + {"Speed baseline", "SpeedBaseline", "Not an actual stat, per se. This number is subtracted from the Speed stat before multiplying it by the scale value.", "|cffffffffSpeed baseline|r is:"}, +} + + +------------------------------------------------------------ +-- UI strings +------------------------------------------------------------ + +-- Translation note: All of the strings ending in _Text should be translated; those will show up in the UI. The strings ending +-- in _Tooltip are only used in tooltips, and can be safely left out. If you don't want to translate them right now, delete those +-- lines or set them to nil, and Pawn won't show tooltips for those UI elements. + + +-- Configuration UI +PawnUIFrame_CloseButton_Text = "Close" +PawnUIHeaders = -- (%s is the name of the current scale) +{ + "Manage your Pawn scales", -- Scale tab + "Scale values for %s", -- Values tab + "Compare items using %s", -- Compare tab + "Gems for %s", -- Gems tab + "Adjust Pawn options", -- Options tab + "About Pawn", -- About tab + "Welcome to Pawn!", -- Getting Started tab +} + +-- Configuration UI, Scale selector +PawnUIFrame_ScaleSelector_Header_Text = "Select a scale:" + +-- Configuration UI, Scale tab (this is a new tab; the old Scales tab is now the Values tab) +PawnUIFrame_ScalesTab_Text = "Scale" + +PawnUIFrame_ScalesWelcomeLabel_Text = "Scales are sets of stats and values that are used to assign point values to items. You can customize your own or use scale values that others have created." + +PawnUIFrame_ShowScaleCheck_Label_Text = "Show scale in tooltips" +PawnUIFrame_ShowScaleCheck_Tooltip = "When this option is checked, values for this scale will show up in item tooltips for this character. Each scale can show up for one of your characters, multiple characters, or no characters at all." +PawnUIFrame_RenameScaleButton_Text = "Rename" +PawnUIFrame_RenameScaleButton_Tooltip = "Rename this scale." +PawnUIFrame_DeleteScaleButton_Text = "Delete" +PawnUIFrame_DeleteScaleButton_Tooltip = "Delete this scale.\n\nThis command cannot be undone!" +PawnUIFrame_ScaleColorSwatch_Label_Text = "Change color" +PawnUIFrame_ScaleColorSwatch_Tooltip = "Change the color that this scale's name and value appear in on item tooltips." +PawnUIFrame_ScaleTypeLabel_NormalScaleText = "You can change this scale on the Values tab." +PawnUIFrame_ScaleTypeLabel_ReadOnlyScaleText = "You must make a copy of this scale if you want to customize it." + +PawnUIFrame_ScaleSettingsShareHeader_Text = "Share your scales" + +PawnUIFrame_ImportScaleButton_Text = "Import" +PawnUIFrame_ImportScaleButton_Label_Text = "Add a new scale by pasting a scale tag from the internet." +PawnUIFrame_ExportScaleButton_Text = "Export" +PawnUIFrame_ExportScaleButton_Label_Text = "Share your scale with others on the internet." + +PawnUIFrame_ScaleSettingsNewHeader_Text = "Create a new scale" + +PawnUIFrame_CopyScaleButton_Text = "Copy" +PawnUIFrame_CopyScaleButton_Label_Text = "Create a new scale by making a copy of this one." +PawnUIFrame_NewScaleButton_Text = "Empty" +PawnUIFrame_NewScaleButton_Label_Text = "Create a new scale from scratch." +PawnUIFrame_NewScaleFromDefaultsButton_Text = "Defaults" +PawnUIFrame_NewScaleFromDefaultsButton_Label_Text = "Create a new scale by making a copy of the defaults." + +-- Configuration UI, Values tab (previously the Scales tab) +PawnUIFrame_ValuesTab_Text = "Values" + +PawnUIFrame_ValuesWelcomeLabel_NormalText = "You can customize the values that are assigned to each stat for this scale. To manage your scales and add new ones, use the Scale tab." +PawnUIFrame_ValuesWelcomeLabel_NoScalesText = "You have no scale selected. To get started, go to the Scale tab and start a new scale or paste one from the internet." +PawnUIFrame_ValuesWelcomeLabel_ReadOnlyScaleText = "The scale that you have selected can't be changed. If you'd like to change these values, go to the Scale tab and make a copy of this scale or start a new one." + +PawnUIFrame_ClearValueButton_Text = "Remove" +PawnUIFrame_ClearValueButton_Tooltip = "Remove this stat from the scale." + +PawnUIFrame_ScaleSocketOptionsHeaderLabel_Text = "When calculating a value for this scale:" +PawnUIFrame_ScaleSocketBestRadio_Text = "Automatically handle sockets for me" +PawnUIFrame_ScaleSocketBestRadio_Tooltip = "Pawn will calculate a value for this scale assuming that you would socket the item with the gems that would maximize the value of the item." +PawnUIFrame_ScaleSocketCorrectRadio_Text = "Let me manually pick a socket value" +PawnUIFrame_ScaleSocketCorrectRadio_Tooltip = "Pawn will calculate a value for this scale based on the number you specify." + +PawnUIFrame_NormalizeValuesCheck_Text = "Normalize values (like Wowhead)" +PawnUIFrame_NormalizeValuesCheck_Tooltip = "Enable this option to divide the final calculated value for an item by the sum of all stat values in your scale, like Wowhead and Lootzor do. This helps to even out situations like where one scale has stat values around 1 and another has values around 5. It also helps to keep numbers manageably small.\n\nFor more information on this setting, see the readme file." + +-- Configuration UI, Compare tab +PawnUIFrame_CompareTab_Text = "Compare" + +PawnUIFrame_VersusHeader_Text = "—vs.—" -- Short for "versus." Appears between the names of the two items. +PawnUIFrame_VersusHeader_NoItem = "(no item)" -- Text displayed next to empty item slots. + +PawnUIFrame_CompareMissingItemInfo_TextLeft = "First, pick a scale from the list on the left." +PawnUIFrame_CompareMissingItemInfo_TextRight = "Then, drop an item in this box.\n\nPawn will compare it versus your equipped item." + +PawnUIFrame_CompareSocketBonusHeader_Text = "Socket bonus" -- Heading that appears above the item socket bonuses. + +PawnUIFrame_CompareOtherInfoHeader_Text = "Other" -- Heading that appears above the item's level and the following stats: +PawnUIFrame_CompareAsterisk = "Special effects " .. PawnQuestionTexture +PawnUIFrame_CompareAsterisk_Yes = "Yes" -- Appears on the Compare tab when an item has special effects (?). + +PawnUIFrame_CurrentCompareScaleDropDown_Label_Text = "Comparison scale" +PawnUIFrame_CurrentCompareScaleDropDown_Tooltip = "Select a new scale to use when comparing the two items." + +PawnUIFrame_ClearItemsButton_Label = "Clear" +PawnUIFrame_ClearItemsButton_Tooltip = "Remove both comparison items." + +PawnUIFrame_CompareSwapButton_Text = "< Swap >" +PawnUIFrame_CompareSwapButton_Tooltip = "Swap the item on the left side with the one on the right." + +-- Configuration UI, Gems tab +PawnUIFrame_GemsTab_Text = "Gems" +PawnUIFrame_GemsHeaderLabel_Text = "Choose a scale to have Pawn determine the best gems available according to the values in that scale." + +PawnUIFrame_CurrentGemsScaleDropDown_Label_Text = "Find the best gems for:" +PawnUIFrame_CurrentGemsScaleDropDown_Tooltip = "Select a scale for which to calculate gem values." + +PawnUIFrame_GemQualityDropDown_Label_Text = "Quality:" +PawnUIFrame_GemQualityDropDown_Tooltip = "Select the quality of gems for Pawn to consider." + +PawnUIFrame_FindGemColorHeader_Text = "%s gems" -- Red +PawnUIFrame_FindGemColorHeader_Meta_Text = "Meta gems (ignoring effects)" +PawnUIFrame_FindGemNoGemsHeader_Text = "No gems found." + +-- Configuration UI, Options tab +PawnUIFrame_OptionsTab_Text = "Options" +PawnUIFrame_OptionsHeaderLabel_Text = "Configure Pawn the way you like it. Changes will take effect immediately." + +PawnUIFrame_TooltipOptionsHeaderLabel_Text = "Tooltip options" +PawnUIFrame_ShowItemLevelsCheck_Text = "Show item levels" +PawnUIFrame_ShowItemLevelsCheck_Tooltip = "Enable this option to have Pawn display the item level of every item you come across.\n\nEvery item in World of Warcraft has a hidden level that is used to determine how many stats it can have. In general, an item of the same type (helmet, cloak) and quality (green, blue) and a higher level will have more, or at least better, stats." +PawnUIFrame_ShowItemIDsCheck_Text = "Show item IDs" +PawnUIFrame_ShowItemIDsCheck_Tooltip = "Enable this option to have Pawn display the item ID of every item you come across, as well as the IDs of all enchantments and gems.\n\nEvery item in World of Warcraft has an ID number associated with it. This information is generally only useful to mod authors." +PawnUIFrame_ShowIconsCheck_Text = "Show inventory icons" +PawnUIFrame_ShowIconsCheck_Tooltip = "Enable this option to show inventory icons next to item link windows." +PawnUIFrame_ShowExtraSpaceCheck_Text = "Add a blank line before values" +PawnUIFrame_ShowExtraSpaceCheck_Tooltip = "Keep your item tooltips extra tidy by enabling this option, which adds a blank line before the Pawn values." +PawnUIFrame_AlignRightCheck_Text = "Align values to right edge of tooltip" +PawnUIFrame_AlignRightCheck_Tooltip = "Enable this option to align your Pawn values (as well as item levels and item IDs) to the right edge of the tooltip instead of the left." +PawnUIFrame_AsterisksHeaderLabel_Text = "Show " .. PawnQuestionTexture .. " on special effects:" +PawnUIFrame_AsterisksAutoRadio_Text = "On" +PawnUIFrame_AsterisksAutoRadio_Tooltip = "Show the " .. PawnQuestionTexture .. " icon on items that have special effects (like trinkets)." +PawnUIFrame_AsterisksAutoNoTextRadio_Text = "On, but don't add the warning" +PawnUIFrame_AsterisksAutoNoTextRadio_Tooltip = "Same as On, but don't show the 'Special effects not included' warning message." +PawnUIFrame_AsterisksOffRadio_Text = "Off" +PawnUIFrame_AsterisksOffRadio_Tooltip = "Don't show the " .. PawnQuestionTexture .. " icon or the warning message." + +PawnUIFrame_CalculationOptionsHeaderLabel_Text = "Calculation options" +PawnUIFrame_DigitsBox_Label_Text = "Digits of precision:" +PawnUIFrame_DigitsBox_Tooltip = "Specify how many digits of precision you want in your Pawn values, 0-9. 0 rounds all Pawn values to whole numbers ('25'). 1 is the default ('24.5')." +PawnUIFrame_UnenchantedValuesCheck_Text = "Show base values for items" +PawnUIFrame_UnenchantedValuesCheck_Tooltip = "Enable this option to have Pawn show values for unmodified versions of items, as if they were just dropped or was bought from the vendor.\n\nIf the current value and base value are both visible and not equal, the base value will be shown second, in parentheses." +PawnUIFrame_EnchantedValuesCheck_Text = "Show current values for items" +PawnUIFrame_EnchantedValuesCheck_Tooltip = "Enable this option to have Pawn show values for items exactly as they are, including all enchantments and gems if present. Empty sockets are ignored.\n\nIf the current value and base value are both visible and not equal, the current value will be shown first." +PawnUIFrame_DebugCheck_Text = "Show debug info" +PawnUIFrame_DebugCheck_Tooltip = "If you're not sure how Pawn is calculating the values for a particular item, enable this option to make Pawn spam all sorts of 'useful' data to the chat console whenever you hover over an item. This information includes which stats Pawn thinks the item has, which parts of the item Pawn doesn't understand, and how it took each one into account for each of your scales.\n\nThis option will fill up your chat log quickly, so you'll want to turn it off once you're finished investigating.\n\nShortcuts:\n/pawn debug on\n/pawn debug off" + +PawnUIFrame_OtherOptionsHeaderLabel_Text = "Other options" +PawnUIFrame_ButtonPositionHeaderLabel_Text = "Show the Pawn button:" +PawnUIFrame_ButtonRightRadio_Text = "On the right" +PawnUIFrame_ButtonRightRadio_Tooltip = "Show the Pawn button in the lower-right corner of the Character Info panel." +PawnUIFrame_ButtonLeftRadio_Text = "On the left" +PawnUIFrame_ButtonLeftRadio_Tooltip = "Show the Pawn button in the lower-left corner of the Character Info panel." +PawnUIFrame_ButtonOffRadio_Text = "Hide it" +PawnUIFrame_ButtonOffRadio_Tooltip = "Don't show the Pawn button on the Character Info panel." + +-- Configuration UI, About tab +PawnUIFrame_AboutTab_Text = "About" +PawnUIFrame_AboutHeaderLabel_Text = "by Vger-Azjol-Nerub" +PawnUIFrame_AboutVersionLabel_Text = "Version %s" +PawnUIFrame_AboutTranslationLabel_Text = "Official English version" -- Translators: credit yourself here... "Klingon translation by Stovokor" +PawnUIFrame_ReadmeLabel_Text = "New to Pawn? See the getting started tab for a really basic introduction. You can learn about more advanced features in the readme file that comes with Pawn." +PawnUIFrame_WebsiteLabel_Text = "For other mods by Vger, visit vgermods.com.\n\nWowhead stat weights used with permission. If you have feedback on the scale values, please direct it to the appropriate Wowhead Theorycrafting forum threads." + +-- Configuration UI, Help tab +PawnUIFrame_HelpTab_Text = "Getting started" +PawnUIFrame_GettingStartedLabel_Text = + "Pawn calculates scores for items that let you easily see which one is better for you. These scores show up at the bottom of all your item tooltips.\n\n\n" .. + "Each item will get multiple scores: one for each “scale” that is active for your character. A scale lists the stats that are important to you, and how many points each stat is worth.\n\n\n" .. + "Pawn comes with scales from Wowhead for each class and spec. You can turn scales on and off, create your own by assigning point values to each stat, and even share scales on the internet.\n\n\n" .. + VgerCore.Color.Blue .. "Try out these features once you learn the basics:\n" .. VgerCore.Color.Reset .. + " • Compare the stats of two items by using Pawn's Compare tab.\n" .. + " • Right-click on an item link window to see how it compares to your current item.\n" .. + " • Shift-right-click an item with sockets to have Pawn suggest gems for it.\n" .. + " • Make a copy of one of your scales on the Scale tab, and customize the stat values on the Values tab.\n" .. + " • Find more scales for your class on the internet, or build a custom one with Rawr.\n" .. + " • Check out the readme file to learn more about Pawn's advanced features." + +-- Inventory button +PawnUI_InventoryPawnButton_Tooltip = "Click to show the Pawn UI." +PawnUI_InventoryPawnButton_Subheader = "Totals for all equipped items:" + +-- Socketing button +PawnUI_SocketingPawnButton_Tooltip = "Click to show the Pawn Gems UI." + +-- Item socketing UI +PawnUI_ItemSocketingDescription_Header = "Pawn suggests the following gems:" + +-- Interface Options page +PawnInterfaceOptionsFrame_OptionsHeaderLabel_Text = "Pawn options are found in the Pawn UI." +PawnInterfaceOptionsFrame_OptionsSubHeaderLabel_Text = "Click the Pawn button to go there. You can also open Pawn from your inventory page, or by binding a key to it." + +-- Bindings UI +BINDING_HEADER_PAWN = "Pawn" +BINDING_NAME_PAWN_TOGGLE_UI = "Toggle Pawn UI" -- Show or hide the Pawn UI +PAWN_TOGGLE_UI_DEFAULT_KEY = "P" -- Default key to assign to this command +BINDING_NAME_PAWN_COMPARE_LEFT = "Compare item (left)" -- Set the currently hovered item to be the left-side Compare item +PAWN_COMPARE_LEFT_DEFAULT_KEY = "[" -- Default key to assign to this command +BINDING_NAME_PAWN_COMPARE_RIGHT = "Compare item (right)" -- Set the currently hovered item to be the right-side Compare item +PAWN_COMPARE_RIGHT_DEFAULT_KEY = "]" -- Default key to assign to this command + + +PawnLocal = +{ + + -- General messages + ["NeedNewerVgerCoreMessage"] = "Pawn needs a newer version of VgerCore. Please use the version of VgerCore that came with Pawn.", + + -- Scale management dialog messages + ["NewScaleEnterName"] = "Enter a name for your scale:", + ["NewScaleNoQuotes"] = "A scale can't have \" in its name. Enter a name for your scale:", + ["NewScaleDuplicateName"] = "A scale with that name already exists. Enter a name for your scale:", + + ["CopyScaleEnterName"] = "Enter a name for your new scale, a copy of %s:", -- %s is the name of the existing scale + ["RenameScaleEnterName"] = "Enter a new name for %s:", -- %s is the old name of the scale + ["DeleteScaleConfirmation"] = "Are you sure you want to delete %s? This can't be undone. Type \"%s\" to confirm:", -- First %s is the name of the scale, second %s is DELETE + + ["ImportScaleMessage"] = "Press Ctrl+V to paste a scale tag that you've copied from another source here:", + ["ImportScaleTagErrorMessage"] = "Pawn doesn't understand that scale tag. Did you copy the whole tag? Try copying and pasting again:", + + ["ExportScaleMessage"] = "Press Ctrl+C to copy the following scale tag for |cffffffff%s|r, and then press Ctrl+V to paste it later.", -- %s is name of scale + ["ExportAllScalesMessage"] = "Press Ctrl+C to copy your scale tags, create a file on your computer to save them in for backup, and then press Ctrl+V to paste them.", + + -- Scale selector + ["VisibleScalesHeader"] = "%s's scales", -- %s is name of character + ["HiddenScalesHeader"] = "Other scales", + + -- Configuration UI, Values tab + ["NoStatDescription"] = "Choose a stat from the list on the left.", + ["NoScalesDescription"] = "To begin, import a scale or start a new one.", + ["StatNameText"] = "1 |cffffffff%s|r is worth:", -- |cffffffff%s|r is the name of the stat, in white + + -- Generic string dialogs + ["OKButton"] = "OK", + ["CancelButton"] = "Cancel", + ["CloseButton"] = "Close", + + -- Debug messages + ["EnchantedStatsHeader"] = "(Current value)", + ["UnenchantedStatsHeader"] = "(Base value)", + ["FailedToGetItemLinkMessage"] = " Failed to get item link from tooltip. This may be due to a mod conflict.", + ["FailedToGetUnenchantedItemMessage"] = " Failed to get base item values. This may be due to a mod conflict.", + ["DidntUnderstandMessage"] = " (?) Didn't understand \"%s\".", + ["FoundStatMessage"] = " %d %s", -- 25 Stamina + + ["ValueCalculationMessage"] = " %g %s x %g each = %g", -- 25 Stamina x 1 each = 25 + ["NoValueMessage"] = " %s has no value.", -- Stamina has no value. + ["SocketBonusValueCalculationMessage"] = " -- Socket bonus would be worth:", + ["MissocketWorthwhileMessage"] = " -- But it's better to use only %s gems:", -- Better to use only Red/Blue gems: + ["NormalizationMessage"] = " ---- Normalized by dividing by %g", -- Normalized by dividing by 3.5 + ["TotalValueMessage"] = " ---- Total: %g", -- Total: 25 + + -- Tooltip annotations + ["ItemIDTooltipLine"] = "Item ID", + ["ItemLevelTooltipLine"] = "Item level", + ["AverageItemLevelTooltipLine"] = "Epic gear level", + ["BaseValueWord"] = "base", -- 123.45 (98.76 base) + ["AsteriskTooltipLine"] = "|TInterface\\AddOns\\Pawn\\Textures\\Question:0|t Special effects not included in the value.", + + -- Gem stuff + ["GenericGemName"] = "(Gem %d)", -- (Gem 12345) + ["GenericGemLink"] = "|Hitem:%d|h[Gem %d]|h", -- [Gem 12345] + ["GemColorList1"] = "%d %s", -- 2 Red + ["GemColorList2"] = "%d %s or %s", -- 3 Red or Yellow + ["GemColorList3"] = "%d of any color", -- 1 of any color + + ["GemQualityLevel80Uncommon"] = "Level 80 uncommon", + ["GemQualityLevel80Rare"] = "Level 80 rare", + ["GemQualityLevel80Epic"] = "Level 80 epic", + ["MetaGemQualityLevel80Rare"] = "Level 80 crafted", + ["GemQualityLevel85Uncommon"] = "Level 85 uncommon", + ["GemQualityLevel85Rare"] = "Level 85 rare", + ["GemQualityLevel85Epic"] = "Level 85 epic", + ["MetaGemQualityLevel85Rare"] = "Level 85 crafted", + + -- Slash commands + ["DebugOnCommand"] = "debug on", + ["DebugOffCommand"] = "debug off", + ["BackupCommand"] = "backup", + + ["Usage"] = [[ +Pawn by Vger-Azjol-Nerub +www.vgermods.com + +/pawn -- show or hide the Pawn UI +/pawn debug [ on | off ] -- spam debug messages to the console +/pawn backup -- backup all of your scales to scale tags + +For more information on customizing Pawn, please see the help file (Readme.htm) that comes with the mod. +]], + +} + + +------------------------------------------------------------ +-- Localized scale names +------------------------------------------------------------ + +PawnWowheadScale_Provider = "Wowhead scales" +PawnWowheadScale_WarriorArms = "Warrior: arms" +PawnWowheadScale_WarriorFury = "Warrior: fury" +PawnWowheadScale_WarriorTank = "Warrior: tank" +PawnWowheadScale_PaladinHoly = "Paladin: holy" +PawnWowheadScale_PaladinTank = "Paladin: tank" +PawnWowheadScale_PaladinRetribution = "Paladin: retribution" +PawnWowheadScale_HunterBeastMastery = "Hunter: beast mastery" +PawnWowheadScale_HunterMarksman = "Hunter: marksman" +PawnWowheadScale_HunterSurvival = "Hunter: survival" +PawnWowheadScale_RogueAssassination = "Rogue: assassination" +PawnWowheadScale_RogueCombat = "Rogue: combat" +PawnWowheadScale_RogueSubtlety = "Rogue: subtlety" +PawnWowheadScale_PriestDiscipline = "Priest: discipline" +PawnWowheadScale_PriestHoly = "Priest: holy" +PawnWowheadScale_PriestShadow = "Priest: shadow" +PawnWowheadScale_DeathKnightBloodDps = "DK: blood DPS" +PawnWowheadScale_DeathKnightBloodTank = "DK: blood tank" +PawnWowheadScale_DeathKnightFrostDps = "DK: frost DPS" +PawnWowheadScale_DeathKnightFrostTank = "DK: frost tank" +PawnWowheadScale_DeathKnightUnholyDps = "DK: unholy DPS" +PawnWowheadScale_ShamanElemental = "Shaman: elemental" +PawnWowheadScale_ShamanEnhancement = "Shaman: enhancement" +PawnWowheadScale_ShamanRestoration = "Shaman: restoration" +PawnWowheadScale_MageArcane = "Mage: arcane" +PawnWowheadScale_MageFire = "Mage: fire" +PawnWowheadScale_MageFrost = "Mage: frost" +PawnWowheadScale_WarlockAffliction = "Warlock: affliction" +PawnWowheadScale_WarlockDemonology = "Warlock: demonology" +PawnWowheadScale_WarlockDestruction = "Warlock: destruction" +PawnWowheadScale_DruidBalance = "Druid: balance" +PawnWowheadScale_DruidFeralDps = "Druid: feral cat" +PawnWowheadScale_DruidFeralTank = "Druid: feral bear" +PawnWowheadScale_DruidRestoration = "Druid: restoration" + +------------------------------------------------------------ +-- Tooltip parsing expressions +------------------------------------------------------------ + +-- Turns a game constant into a regular expression. +function PawnGameConstant(Text) + return "^" .. PawnGameConstantUnwrapped(Text) .. "$" +end +function PawnGameConstantUnwrapped(Text) + -- REVIEW: This function seems stupid. + return gsub(gsub(Text, "%%", "%%%%"), "%-", "%%-") +end + +-- These strings indicate that a given line might contain multiple stats, such as complex enchantments +-- (ZG, AQ) and gems. These are sorted in priority order. If a string earlier in the table is present, any +-- string later in the table can be ignored. +PawnSeparators = +{ + ", ", + "/", + " & ", + " and ", +} + +-- This string indicates that whatever stats follow it on the same line is the item's socket bonus. +PawnSocketBonusPrefix = "Socket Bonus: " + +-- Lines that match any of the following patterns will cause all further tooltip parsing to stop. +PawnKillLines = +{ + "^ \n$", -- The blank line before set items before WoW 2.3 + " %(%d+/%d+%)$", -- The (1/8) on set items for all versions of WoW + "^|cff00e0ffDropped By", -- Mod compatibility: MobInfo-2 (should match mifontLightBlue .. MI_TXT_DROPPED_BY) +} + +-- Lines that begin with any of the following strings will not be searched for separator strings. +PawnSeparatorIgnorePrefixes = +{ + '"', -- double quote + "Equip:", + "Use:", + "Chance on hit:", +} + +-- Items that begin with any of the following strings will never be parsed. +PawnIgnoreNames = +{ + "Design:", + "Formula:", + "Manual:", + "Pattern:", + "Plans:", + "Recipe:", + "Schematic:", +} + +-- This is a list of regular expression substitutions that Pawn performs to normalize stat names before running +-- them through the normal gauntlet of expressions. +PawnNormalizationRegexes = +{ + {"^([%w%s%.]+) %+(%d+)$", "+%2 %1"}, -- "Stamina +5" --> "+5 Stamina" + {"^(.-)|r.*", "%1"}, -- For removing meta gem requirements +} + +-- These regular expressions are used to parse item tooltips. +-- The first string is the regular expression to match. Stat values should be denoted with "(%d+)". +-- Subsequent strings follow this pattern: Stat, Number, Source +-- Stat is the name of a statistic. +-- Number is either the amount of that stat to include, or the 1-based index into the matches array produced by the regex. +-- If it's an index, it can also be negative to mean that the stat should be subtracted instead of added. If nil, defaults to 1. +-- Source is either PawnMultipleStatsFixed if Number is the amount of the stat, or PawnMultipleStatsExtract or nil if Number is the matches array index. +-- Note that certain strings don't need to be translated: for example, the game defines +-- ITEM_BIND_ON_PICKUP to be "Binds when picked up" in English, and the correct string +-- in other languages automatically. +PawnMultipleStatsFixed = "_MultipleFixed" +PawnMultipleStatsExtract = "_MultipleExtract" +PawnRegexes = +{ + -- ======================================== + -- Strings that are ignored for compatibility with other mods + -- ======================================== + {"^Used by outfits:"}, -- Mod compatibility: Outfitter + + -- ======================================== + -- Common strings that are ignored (rare ones are at the bottom of the file) + -- ======================================== + {PawnGameConstant(ITEM_QUALITY0_DESC)}, -- Poor + {PawnGameConstant(ITEM_QUALITY1_DESC)}, -- Common + {PawnGameConstant(ITEM_QUALITY2_DESC)}, -- Uncommon + {PawnGameConstant(ITEM_QUALITY3_DESC)}, -- Rare + {PawnGameConstant(ITEM_QUALITY4_DESC)}, -- Epic + {PawnGameConstant(ITEM_QUALITY5_DESC)}, -- Legendary + {PawnGameConstant(ITEM_QUALITY7_DESC)}, -- Heirloom + {PawnGameConstant(ITEM_HEROIC)}, -- Heroic (Thrall's Chestguard of Triumph, level 258 version) + {PawnGameConstant(ITEM_HEROIC_EPIC)}, -- Heroic Epic (Thrall's Chestguard of Triumph, level 258 version, with colorblind mode on) + {"^" .. ITEM_LEVEL}, -- Item Level 200 + {PawnGameConstant(ITEM_UNSELLABLE)}, -- No sell price + {PawnGameConstant(ITEM_SOULBOUND)}, -- Soulbound + {PawnGameConstant(ITEM_BIND_ON_EQUIP)}, -- Binds when equipped + {PawnGameConstant(ITEM_BIND_ON_PICKUP)}, -- Binds when picked up + {PawnGameConstant(ITEM_BIND_ON_USE)}, -- Binds when used + {PawnGameConstant(ITEM_BIND_TO_ACCOUNT)}, -- Binds to account (Polished Spaulders of Valor) + {"^" .. PawnGameConstantUnwrapped(ITEM_UNIQUE)}, -- Unique; leave off the $ for Unique(20) + {"^" .. PawnGameConstantUnwrapped(ITEM_BIND_QUEST)}, -- Leave off the $ for MonkeyQuest mod compatibility + {PawnGameConstant(ITEM_STARTS_QUEST)}, -- This Item Begins a Quest + {PawnGameConstant(ITEM_CONJURED)}, -- Conjured Item + {PawnGameConstant(ITEM_PROSPECTABLE)}, -- Prospectable + {PawnGameConstant(ITEM_MILLABLE)}, -- Millable + {PawnGameConstant(ITEM_DISENCHANT_NOT_DISENCHANTABLE)}, -- Cannot be disenchanted + {"^Will receive"}, -- Appears in the trade window when an item is about to be enchanted ("Will receive +8 Stamina") + {"^Disenchanting requires"}, -- Appears on item tooltips when the Disenchant ability is specified ("Disenchanting requires Enchanting (25)") + {PawnGameConstant(ITEM_ENCHANT_DISCLAIMER)}, -- Item will not be traded! + {"^.+ Charges?$"}, -- Brilliant Mana Oil + {PawnGameConstant(LOCKED)}, -- Locked + {PawnGameConstant(ENCRYPTED)}, -- Encrypted (Floral Foundations) (does not seem to exist in the game yet) + {PawnGameConstant(ITEM_SPELL_KNOWN)}, -- Already Known + {PawnGameConstant(INVTYPE_HEAD)}, -- Head + {PawnGameConstant(INVTYPE_NECK)}, -- Neck + {PawnGameConstant(INVTYPE_SHOULDER)}, -- Shoulder + {PawnGameConstant(INVTYPE_CLOAK), "IsCloth", 1, PawnMultipleStatsFixed}, -- Back (cloaks are cloth even though they don't list it) + {PawnGameConstant(INVTYPE_ROBE)}, -- Chest + {PawnGameConstant(INVTYPE_BODY)}, -- Shirt + {PawnGameConstant(INVTYPE_TABARD)}, -- Tabard + {PawnGameConstant(INVTYPE_WRIST)}, -- Wrist + {PawnGameConstant(INVTYPE_HAND)}, -- Hands + {PawnGameConstant(INVTYPE_WAIST)}, -- Waist + {PawnGameConstant(INVTYPE_FEET)}, -- Feet + {PawnGameConstant(INVTYPE_LEGS)}, -- Legs + {PawnGameConstant(INVTYPE_FINGER)}, -- Finger + {PawnGameConstant(INVTYPE_TRINKET)}, -- Trinket + {PawnGameConstant(MAJOR_GLYPH)}, -- Major Glyph + {PawnGameConstant(MINOR_GLYPH)}, -- Minor Glyph + {"^Totem$"}, + {"^Relic$"}, + {"^Idol$"}, + {"^Libram$"}, + {"^Mount$"}, -- Cenarion War Hippogryph + {"^Classes:"}, + {"^Races:"}, + {"^Requires"}, + {"^Durability"}, + {"^Duration:"}, + {"^Cooldown remaining:"}, + {"<.+>"}, -- Made by, Right-click to read, etc. (No ^$; can be prefixed by a color) + {"^Written by "}, + {"|cff%x%x%x%x%x%xRequires"}, -- Meta gem requirements + {"^%d+ Slot .+$"}, -- Bags of all kinds + {"^.+%(%d+ sec%)$"}, -- Temporary item buff + {"^.+%(%d+ min%)$"}, -- Temporary item buff + {"^Enchantment Requires"}, -- Seen on the enchanter-only ring enchantments when you're not an enchanter, and socketed jewelcrafter-only BoP gems + + -- ======================================== + -- Strings that represent statistics that Pawn cares about + -- ======================================== + {PawnGameConstant(INVTYPE_RANGED), "IsRanged", 1, PawnMultipleStatsFixed}, -- Ranged + {"^Projectile$", "IsRanged", 1, PawnMultipleStatsFixed}, -- Projectile + {PawnGameConstant(INVTYPE_THROWN), "IsRanged", 1, PawnMultipleStatsFixed}, -- Thrown + {PawnGameConstant(INVTYPE_WEAPON), "IsOneHand", 1, PawnMultipleStatsFixed}, -- One-Hand + {PawnGameConstant(INVTYPE_2HWEAPON), "IsTwoHand", 1, PawnMultipleStatsFixed}, -- Two-Hand + {PawnGameConstant(INVTYPE_WEAPONMAINHAND), "IsMainHand", 1, PawnMultipleStatsFixed}, -- Main Hand + {PawnGameConstant(INVTYPE_WEAPONOFFHAND), "IsOffHand", 1, PawnMultipleStatsFixed}, -- Off Hand + {PawnGameConstant(INVTYPE_HOLDABLE)}, -- Held In Off-Hand; no Pawn stat for this + {"^(%d-) %- (%d-) Damage$", "MinDamage", 1, PawnMultipleStatsExtract, "MaxDamage", 2, PawnMultipleStatsExtract}, -- Standard weapon + {"^%+?(%d-) %- (%d-) Fire Damage$", "MinDamage", 1, PawnMultipleStatsExtract, "MaxDamage", 2, PawnMultipleStatsExtract}, -- Wand + {"^%+?(%d-) %- (%d-) Shadow Damage$", "MinDamage", 1, PawnMultipleStatsExtract, "MaxDamage", 2, PawnMultipleStatsExtract}, -- Wand + {"^%+?(%d-) %- (%d-) Nature Damage$", "MinDamage", 1, PawnMultipleStatsExtract, "MaxDamage", 2, PawnMultipleStatsExtract}, -- Wand, Thunderfury + {"^%+?(%d-) %- (%d-) Arcane Damage$", "MinDamage", 1, PawnMultipleStatsExtract, "MaxDamage", 2, PawnMultipleStatsExtract}, -- Wand + {"^%+?(%d-) %- (%d-) Frost Damage$", "MinDamage", 1, PawnMultipleStatsExtract, "MaxDamage", 2, PawnMultipleStatsExtract}, -- Wand + {"^%+?(%d-) %- (%d-) Holy Damage$", "MinDamage", 1, PawnMultipleStatsExtract, "MaxDamage", 2, PawnMultipleStatsExtract}, -- Wand, Ashbringer + {"^%+?(%d-) Weapon Damage$", "MinDamage", 1, PawnMultipleStatsExtract, "MaxDamage", 1, PawnMultipleStatsExtract}, -- Weapon enchantments + {"^Equip: %+?(%d-) Weapon Damage%.$", "MinDamage", 1, PawnMultipleStatsExtract, "MaxDamage", 1, PawnMultipleStatsExtract}, -- Braided Eternium Chain + {"^%+?(%d-) Damage$", "MinDamage", 1, PawnMultipleStatsExtract, "MaxDamage", 1, PawnMultipleStatsExtract}, -- Weapons with no damage range: Crossbow of the Albatross + {"^Scope %(%+(%d-) Damage%)$", "MinDamage", 1, PawnMultipleStatsExtract, "MaxDamage", 1, PawnMultipleStatsExtract}, -- Ranged weapon scopes + {"^%+?(%d+) [Aa]ll [Ss]tats$", "Strength", 1, PawnMultipleStatsExtract, "Agility", 1, PawnMultipleStatsExtract, "Stamina", 1, PawnMultipleStatsExtract, "Intellect", 1, PawnMultipleStatsExtract, "Spirit", 1, PawnMultipleStatsExtract}, + {"^%+?(%d+) to All Stats$", "Strength", 1, PawnMultipleStatsExtract, "Agility", 1, PawnMultipleStatsExtract, "Stamina", 1, PawnMultipleStatsExtract, "Intellect", 1, PawnMultipleStatsExtract, "Spirit", 1, PawnMultipleStatsExtract}, -- Enchanted Pearl, Enchanted Tear + {"^%+?(%-?%d+) Strength$", "Strength"}, + {"^Potency$", "Strength", 20, PawnMultipleStatsFixed}, -- weapon enchantment (untested) + {"^%+?(%-?%d+) Agility$", "Agility"}, + {"^%+?(%-?%d+) Stamina$", "Stamina"}, + {"^%+?(%-?%d+) Intellect$", "Intellect"}, -- negative Intellect: Kreeg's Mug + {"^%+?(%-?%d+) Spirit$", "Spirit"}, + {"^Titanium Weapon Chain$", "HitRating", 28, PawnMultipleStatsFixed}, -- Weapon enchantment; has additional effects + {"^%+?(%d+) Block$", "BlockValue"}, + {"^%+(%d+) Block Value$", "BlockValue"}, -- part of complex warrior helm enchantment + {"^%+(%d+) Shield Block Value$", "BlockValue"}, -- Titanium Plating + {"^Equip: Increases the block value of your shield by (%d+)%.$", "BlockValue"}, + {"^Equip: Increases your block rating by (%d+)%.$", "BlockRating"}, -- Waistband of Wrath + {"^Equip: Increases your shield block rating by (%d+)%.$", "BlockRating"}, -- Warbringer Chestguard + {"^%+?(%d+) Block Rating$", "BlockRating"}, -- Northman's Shield of Blocking + {"^%+?(%d+) Shield Block Rating$", "BlockRating"}, -- enchantment + {"^Equip: Increases defense rating by (%d+)%.$", "DefenseRating"}, -- Bulwark of Kings + {"^Defense Rating %+(%d)%$", "DefenseRating"}, + {"^%+?(%d+) Defense$", "DefenseRating"}, -- compound paladin enchantment + {"^%+?(%d+) Defense Rating$", "DefenseRating"}, -- Thick Amber; Bloodscale Legguards of Defense + {"^%+?(%d+) Dodge Rating$", "DodgeRating"}, -- Arctic Ring of Eluding + {"^Equip: Increases your dodge rating by (%d+)%.$", "DodgeRating"}, -- Frostwolf Insignia Rank 6 + {"^Equip: Increases your parry rating by (%d+)%.$", "ParryRating"}, -- Draconic Avenger + {"^%+?(%d+) Parry Rating$", "ParryRating"}, + {"^%(([%d%.,]+) damage per second%)$"}, -- Ignore this; DPS is calculated manually + {"^Adds ([%d%.,]+) damage per second$", "Dps"}, + {"^Fiery Weapon$", "Dps", 4, PawnMultipleStatsFixed}, -- weapon enchantment, + {"^Equip: Increases your expertise rating by (%d+)%.$", "ExpertiseRating"}, -- Earthwarden + {"^%+?(%d+) Expertise Rating$", "ExpertiseRating"}, -- Guardian's Shadow Crystal + {"^Equip: Improves critical strike rating by (%d+)%.$", "CritRating"}, + {"^Equip: Increases your critical strike rating by (%d+)%.$", "CritRating"}, + {"^%+?(%d+) Crit Rating$", "CritRating"}, -- Mantle of Malorne + {"^%+?(%d+) Critical Rating$", "CritRating"}, -- Enscribed Fire Opal (after normalization) + {"^%+?(%d+) Critical [Ss]trike [Rr]ating%.?$", "CritRating"}, -- One head enchantment is "20 Critical strike rating." with a dot and lowercase + {"^Scope %(%+(%d+) Critical Strike Rating%)$", "CritRating"}, + {"^%+?(%d+) Ranged Critical Strike$", "CritRating"}, -- Heartseeker Scope (untested); Pawn doesn't distinguish between ranged and hybrid crit rating + {"^Equip: Increases your hit rating by (%d+)%.$", "HitRating"}, -- Don Julio's Band + {"^Equip: Improves hit rating by (%d+)%.$", "HitRating"}, + {"^%+?(%d+) Hit Rating$", "HitRating"}, -- 3% hit scope + {"^Surefooted$", "HitRating", 10, PawnMultipleStatsFixed}, -- Enchantment (untested); has additional effects + {"^Accuracy$", "HitRating", 25, PawnMultipleStatsFixed, "CritRating", 25, PawnMultipleStatsFixed}, -- weapon enchantment + {"^Equip: Improves your resilience rating by (%d+)%.$", "ResilienceRating"}, + {"^%+?(%d+) Resilience Rating$", "ResilienceRating"}, + {"^%+?(%d+) Resilience$", "ResilienceRating"}, -- Sublime Mystic Dawnstone + {"^Counterweight %(%+(%d+) Haste Rating%)", "HasteRating"}, + {"^Equip: Improves haste rating by (%d+)%.$", "HasteRating"}, -- Swiftsteel Shoulders + {"^%+?(%d+) Haste Rating$", "HasteRating"}, -- Leggings of the Betrayed + {"^Equip: Increases your mastery rating by (%d+)%.$", "MasteryRating"}, -- Elementium Poleaxe (4.0) + {"^%+?(%d+) Mastery Rating$", "MasteryRating"}, -- Fractured Amberjewel (4.0). + {"^Equip: Increases attack power by (%d+)%.$", "Ap"}, + {"^%+?(%d+) Attack Power$", "Ap"}, + {"^%+?(%d+) Ranged Attack Power$", "Rap"}, + {"^Equip: Increases ranged attack power by (%d+)%.$", "Rap"}, + {"^Equip: Restores (%d+) mana per 5 sec%.$", "Mp5"}, + {"^%+?(%d+) Mana Regen$", "Mp5"}, -- Shoulder enchantment, Scryers? + {"^Mana Regen (%d+) per 5 sec%.$", "Mp5"}, + {"^%+?(%d+) [mM]ana [pP]er 5 [sS]ec%.?$", "Mp5"}, + {"^%+?(%d+) [mM]ana [eE]very 5 [sS]ec%.?$", "Mp5"}, + {"^%+?(%d+) [mM]ana [pP]er 5 [sS]econds$", "Mp5"}, -- Royal Shadow Draenite + {"^%+?(%d+) [mM]ana every 5 [sS]ec%.$", "Mp5"}, + {"^%+?(%d+) [mM]ana every 5 seconds$", "Mp5"}, + {"^%+(%d+) Mana restored per 5 seconds$", "Mp5"}, -- Magister's armor kit + {"^Equip: Restores (%d+) health every 5 sec%.$", "Hp5"}, + {"^Equip: Restores (%d+) health per 5 sec%.$", "Hp5"}, -- Yes, both "every" and "per" are used on items... + {"^%+?(%d+) [hH]ealth [eE]very 5 [sS]ec%.?$", "Hp5"}, -- Aquamarine Signet of Regeneration + {"^%+?(%d+) [hH]ealth [pP]er 5 [sS]ec%.?$", "Hp5"}, -- Anglesite Choker of Regeneration + {"^%+(%d+) Health and Mana every 5 sec$", "Mp5", 1, PawnMultipleStatsExtract, "Hp5", 1, PawnMultipleStatsExtract}, -- Greater Vitality boots enchantment + {"^%+(%d+) Mana$", "Mana"}, -- +150 mana enchantment + {"^%+(%d+) HP$", "Health"}, -- +100 health head/leg enchantment + {"^%+(%d+) Health$", "Health"}, -- +150 health enchantment + {"^(%d+) Armor$", "AutoArmor"}, -- normal armor + {"^%+(%d+) Armor$", "BonusArmor"}, -- cloak armor enchantments + {"^Reinforced %(%+(%d+) Armor%)$", "BonusArmor"}, -- armor kits + {"^Equip: %+(%d+) Armor%.$", "BonusArmor"}, -- paladin Royal Seal of Eldre'Thalas + {"^Equip: Increases spell power by (%d+)%.$", "SpellPower"}, -- Overlaid Chain Spaulders + {"^%+?(%d+) Spell Power$", "SpellPower"}, -- Reckless Monarch Topaz + {"^Equip: Increases armor penetration rating by (%d+)%.$", "ArmorPenetration"}, -- Onslaught Breastplate, Vereesa's Silver Chain Belt + {"^Equip: Increases your armor penetration rating by (%d+)%.$", "ArmorPenetration"}, -- Argent Skeleton Crusher + {"^%+?(%d+) Armor Penetration Rating$", "ArmorPenetration"}, -- Fractured Scarlet Ruby + {"^Equip: Increases your spell penetration by (%d+)%.$", "SpellPenetration"}, -- Frostfire Robe + {"^%+?(%d+) Spell Penetration$", "SpellPenetration"}, -- Radiant Talasite + {"^%+(%d+) Fire Damage$", "FireSpellDamage"}, + {"^%+(%d+) Fire Spell Damage$", "FireSpellDamage"}, + {"^Equip: Increases damage done by Fire spells and effects by up to (%d+)%.$", "FireSpellDamage"}, + {"^Equip: Increases fire spell power by (%d+)%.$", "FireSpellDamage"}, + {"^%+(%d+) Shadow Damage$", "ShadowSpellDamage"}, + {"^%+(%d+) Shadow Spell Damage$", "ShadowSpellDamage"}, + {"^Equip: Increases damage done by Shadow spells and effects by up to (%d+)%.$", "ShadowSpellDamage"}, + {"^Equip: Increases shadow spell power by (%d+)%.$", "FrostSpellDamage"}, -- Frozen Shadoweave Shoulders + {"^%+(%d+) Nature Damage$", "NatureSpellDamage"}, -- Netherstalker Legguards of Nature's Wrath + {"^%+(%d+) Nature Spell Damage$", "NatureSpellDamage"}, + {"^Equip: Increases damage done by Nature spells and effects by up to (%d+)%.$", "NatureSpellDamage"}, + {"^Equip: Increases nature spell power by (%d+)%.$", "NatureSpellDamage"}, + {"^%+(%d+) Arcane Damage$", "ArcaneSpellDamage"}, + {"^%+(%d+) Arcane Spell Damage$", "ArcaneSpellDamage"}, -- Dragon Finger of Arcane Wrath + {"^Equip: Increases damage done by Arcane spells and effects by up to (%d+)%.$", "ArcaneSpellDamage"}, + {"^Equip: Increases arcane spell power by (%d+)%.$", "ArcaneSpellDamage"}, + {"^%+(%d+) Frost Damage$", "FrostSpellDamage"}, + {"^%+(%d+) Frost Spell Damage$", "FrostSpellDamage"}, -- enchantment + {"^Equip: Increases damage done by Frost spells and effects by up to (%d+)%.$", "FrostSpellDamage"}, + {"^Equip: Increases frost spell power by (%d+)%.$", "FrostSpellDamage"}, -- Frozen Shadoweave Shoulders + {"^%+(%d+) Holy Damage$", "HolySpellDamage"}, + {"^%+(%d+) Holy Spell Damage$", "HolySpellDamage"}, + {"^Equip: Increases damage done by Holy spells and effects by up to (%d+)%.$", "HolySpellDamage"}, -- Lightforged Blade + {"^Equip: Increases the damage done by Holy spells and effects by up to (%d+)%.$", "HolySpellDamage"}, -- Drape of the Righteous + {"^Equip: Increases holy spell power by (%d+)%.$", "HolySpellDamage"}, + {"^%+?(%d+) All Resistances$", "AllResist"}, + {"^%+?(%d+) Resist All$", "AllResist"}, -- Prismatic Sphere + {"^%+?(%d+) Fire Resistance$", "FireResist"}, + {"^%+?(%d+) Shadow Resistance$", "ShadowResist"}, + {"^%+?(%d+) Nature Resistance$", "NatureResist"}, + {"^%+?(%d+) Arcane Resistance$", "ArcaneResist"}, + {"^%+?(%d+) Frost Resistance$", "FrostResist"}, + {"^Red Socket$", "RedSocket", 1, PawnMultipleStatsFixed}, + {"^Yellow Socket$", "YellowSocket", 1, PawnMultipleStatsFixed}, + {"^Blue Socket$", "BlueSocket", 1, PawnMultipleStatsFixed}, + {"^Prismatic Socket$", "PrismaticSocket", 1, PawnMultipleStatsFixed}, -- Prismatic / colorless sockets are added by blacksmithing + {"^Meta Socket$", "MetaSocket", 1, PawnMultipleStatsFixed}, + {"^\"Only fits in a meta gem slot%.\"$", "MetaSocketEffect", 1, PawnMultipleStatsFixed}, -- Actual meta gems, not the socket + + -- ======================================== + -- Rare strings that are ignored (common ones are at the top of the file) + -- ======================================== + {'^"'}, -- Flavor text + {"^Increases attack power by (%d+) in Cat, Bear, Dire Bear, and Moonkin forms only%.$"}, -- Shows up on weapons for druids + {"^Alterac Valley$"}, -- Stormpike Soldier's Blood + {"^Blackrock Depths$"}, -- Dark Brewmaiden's Brew + {"^Blade's Edge Mountains$"}, -- Felsworn Gas Mask + {"^Black Temple$"}, -- Naj'entus Spine + {"^Dire Maul$"}, -- Gordok Courtyard Key + {"^Grizzly Hills$"}, -- Element 115 + {"^Hyjal Summit$"}, -- Tears of the Goddess + {"^Icecrown$"}, -- (Argent Tournament dailies) + {"^Karazhan$"}, -- Torment of the Worgen + {"^Old Hillsbrad Foothills$"}, -- Pack of Incendiary Bombs + {"^Serpentshrine Cavern$"}, -- Tainted Core + {"^Shadowmoon Valley$"}, -- Enchanted Illidari Tabard + {"^Stratholme$"}, -- Andonisus, Reaper of Souls + {"^Tempest Keep$"}, -- Cosmic Infuser + {"^The Escape From Durnholde$"}, -- Pack of Incendiary Bombs + {"^The Black Morass$"}, -- Chrono-beacon + {"^Wintergrasp$"}, -- Inflatable Land Mines + {"^Zul'Aman$"}, -- Amani Hex Stick +} + +-- These regexes work exactly the same as PawnRegexes, but they're used to parse the right side of tooltips. +-- Unrecognized stats on the right side are always ignored. +PawnRightHandRegexes = +{ + {"^Speed ([%d%.,]+)$", "Speed"}, + {"^Arrow$", "IsBow", 1, PawnMultipleStatsFixed}, + {"^Axe$", "IsAxe", 1, PawnMultipleStatsFixed}, + {"^Bow$", "IsBow", 1, PawnMultipleStatsFixed}, + {"^Bullet$", "IsGun", 1, PawnMultipleStatsFixed}, + {"^Crossbow$", "IsCrossbow", 1, PawnMultipleStatsFixed}, + {"^Dagger$", "IsDagger", 1, PawnMultipleStatsFixed}, + {"^Fist Weapon$", "IsFist", 1, PawnMultipleStatsFixed}, + {"^Gun$", "IsGun", 1, PawnMultipleStatsFixed}, + {"^Mace$", "IsMace", 1, PawnMultipleStatsFixed}, + {"^Polearm$", "IsPolearm", 1, PawnMultipleStatsFixed}, + {"^Staff$", "IsStaff", 1, PawnMultipleStatsFixed}, + {"^Sword$", "IsSword", 1, PawnMultipleStatsFixed}, + {"^Thrown$", "IsThrown", 1, PawnMultipleStatsFixed}, + {"^Wand$", "IsWand", 1, PawnMultipleStatsFixed}, + {"^Cloth$", "IsCloth", 1, PawnMultipleStatsFixed}, + {"^Leather$", "IsLeather", 1, PawnMultipleStatsFixed}, + {"^Mail$", "IsMail", 1, PawnMultipleStatsFixed}, + {"^Plate$", "IsPlate", 1, PawnMultipleStatsFixed}, + {"^Shield$", "IsShield", 1, PawnMultipleStatsFixed}, +} \ No newline at end of file diff --git a/Pawn.lua b/Pawn.lua new file mode 100644 index 0000000..71bfe31 --- /dev/null +++ b/Pawn.lua @@ -0,0 +1,2990 @@ +-- Pawn by Vger-Azjol-Nerub +-- www.vgermods.com +-- © 2006-2010 Green Eclipse. This mod is released under the Creative Commons Attribution-NonCommercial-NoDerivs 3.0 license. +-- See Readme.htm for more information. + +-- +-- Version 1.3: shared scales and options, revamped UI, Wowhead scales +------------------------------------------------------------ + + +PawnVersion = 1.3 + +-- Pawn requires this version of VgerCore: +local PawnVgerCoreVersionRequired = 1.02 + +-- Caching +-- An item in the cache has the following properties: Name, NumLines, UnknownLines, Stats, SocketBonusStats, UnenchantedStats, UnenchantedSocketBonusStats, Values, Link, PrettyLink, Level, Rarity, ID, Texture, ShouldUseGems +-- (See PawnGetEmptyCachedItem.) +-- An entry in the Values table is an ordered array in the following format: +-- { ScaleName, Value, UnenchantedValue, UseRed, UseYellow, UseBlue } +local PawnItemCache = nil +local PawnItemCacheMaxSize = 50 + +local PawnScaleTotals = { } +-- PawnScaleBestGems["Scale name"] = { +-- ["RedSocket"] = { gem info, gem info }, +-- ["YellowSocket"] = { gem info }, +-- ["BlueSocket"] = { gem info }, +-- ["MetaSocket"] = { gem info }, +-- ["BestGems"] = { ["Value"] = 123.0, ["String"] = "Red/Yellow", ["RedSocket"] = true, ["YellowSocket"] = true, ["BlueSocket"] = false } } +PawnScaleBestGems = { } + +PawnPlayerFullName = nil + +-- Formatting +local PawnEnchantedAnnotationFormat = nil +local PawnUnenchantedAnnotationFormat = nil + +-- Plugin scale providers + +-- PawnScaleProviders["Wowhead"] = { ["Name"] = "Wowhead scales", ["Function"] = } +PawnScaleProviders = { } +local PawnScaleProvidersInitialized = nil + +-- "Constants" +local PawnCurrentScaleVersion = 1 + +local PawnTooltipAnnotation = " " .. PawnQuestionTexture -- (?) texture defined in Localization.lua + +local PawnScaleColorDarkFactor = 0.75 -- the unenchanted color is 75% of the enchanted color + +PawnShowAsterisksNever = 0 +PawnShowAsterisksNonzero = 1 +PawnShowAsterisksAlways = 2 +PawnShowAsterisksNonzeroNoText = 3 + +PawnButtonPositionHidden = 0 +PawnButtonPositionLeft = 1 +PawnButtonPositionRight = 2 + +PawnImportScaleResultSuccess = 1 +PawnImportScaleResultAlreadyExists = 2 +PawnImportScaleResultTagError = 3 + +-- Data used by PawnGetSlotsForItemType. +local PawnItemEquipLocToSlot1 = +{ + INVTYPE_AMMO = 0, + INVTYPE_HEAD = 1, + INVTYPE_NECK = 2, + INVTYPE_SHOULDER = 3, + INVTYPE_BODY = 4, + INVTYPE_CHEST = 5, + INVTYPE_ROBE = 5, + INVTYPE_WAIST = 6, + INVTYPE_LEGS = 7, + INVTYPE_FEET = 8, + INVTYPE_WRIST = 9, + INVTYPE_HAND = 10, + INVTYPE_FINGER = 11, + INVTYPE_TRINKET = 13, + INVTYPE_CLOAK = 15, + INVTYPE_WEAPON = 16, + INVTYPE_SHIELD = 17, + INVTYPE_2HWEAPON = 16, + INVTYPE_WEAPONMAINHAND = 16, + INVTYPE_WEAPONOFFHAND = 17, + INVTYPE_HOLDABLE = 17, + INVTYPE_RANGED = 18, + INVTYPE_THROWN = 18, + INVTYPE_RANGEDRIGHT = 18, + INVTYPE_RELIC = 18, + INVTYPE_TABARD = 19, +} +local PawnItemEquipLocToSlot2 = +{ + INVTYPE_FINGER = 12, + INVTYPE_TRINKET = 14, + INVTYPE_WEAPON = 17, +} + + +------------------------------------------------------------ +-- Pawn events +------------------------------------------------------------ + +-- Called when an event that Pawn cares about is fired. +function PawnOnEvent(Event, arg1, ...) + if Event == "VARIABLES_LOADED" then + PawnInitialize() + elseif Event == "ADDON_LOADED" then + PawnOnAddonLoaded(arg1, ...) + elseif Event == "PLAYER_ENTERING_WORLD" then -- was UPDATE_BINDINGS + PawnSetDefaultKeybindings() + elseif Event == "PLAYER_LOGOUT" then + PawnOnLogout() + end +end + +-- Initializes Pawn after all saved variables have been loaded. +function PawnInitialize() + + -- Check the current version of VgerCore. + if (not VgerCore) or (not VgerCore.Version) or (VgerCore.Version < PawnVgerCoreVersionRequired) then + if DEFAULT_CHAT_FRAME then DEFAULT_CHAT_FRAME:AddMessage("|cfffe8460" .. PawnLocal.NeedNewerVgerCoreMessage) end + message(PawnLocal.NeedNewerVgerCoreMessage) + return + end + + SLASH_PAWN1 = "/pawn" + SlashCmdList["PAWN"] = PawnCommand + + -- Set any unset options to their default values. If the user is a new Pawn user, all options + -- will be set to default values. If upgrading, only missing options will be set to default values. + PawnInitializeOptions() + + -- Now, load any plugins that are ready to be loaded. + PawnInitializePlugins() + + -- Go through the user's scales and check them for errors. + for ScaleName, _ in pairs(PawnCommon.Scales) do + PawnCorrectScaleErrors(ScaleName) + end + + -- Then, recalculate totals. + -- This must be done after checking for errors is completed on all scales because it can trigger other recalculations. + for ScaleName, _ in pairs(PawnCommon.Scales) do + PawnRecalculateScaleTotal(ScaleName) + end + + -- Adjust UI elements. + PawnUI_InventoryPawnButton_Move() + + -- Hook into events. + -- Main game tooltip + hooksecurefunc(GameTooltip, "SetAuctionItem", function(self, ...) PawnUpdateTooltip("GameTooltip", "SetAuctionItem", ...) end) + hooksecurefunc(GameTooltip, "SetAuctionSellItem", function(self, ...) PawnUpdateTooltip("GameTooltip", "SetAuctionSellItem", ...) end) + hooksecurefunc(GameTooltip, "SetBagItem", function(self, ...) PawnUpdateTooltip("GameTooltip", "SetBagItem", ...) end) + hooksecurefunc(GameTooltip, "SetBuybackItem", function(self, ...) PawnUpdateTooltip("GameTooltip", "SetBuybackItem", ...) end) + hooksecurefunc(GameTooltip, "SetExistingSocketGem", function(self, ...) PawnUpdateTooltip("GameTooltip", "SetExistingSocketGem", ...) end) + hooksecurefunc(GameTooltip, "SetGuildBankItem", function(self, ...) PawnUpdateTooltip("GameTooltip", "SetGuildBankItem", ...) end) + hooksecurefunc(GameTooltip, "SetHyperlink", function(self, ...) PawnUpdateTooltip("GameTooltip", "SetHyperlink", ...) end) + hooksecurefunc(GameTooltip, "SetInboxItem", function(self, ...) PawnUpdateTooltip("GameTooltip", "SetInboxItem", ...) end) + hooksecurefunc(GameTooltip, "SetInventoryItem", function(self, ...) PawnUpdateTooltip("GameTooltip", "SetInventoryItem", ...) end) + hooksecurefunc(GameTooltip, "SetLootItem", function(self, ...) PawnUpdateTooltip("GameTooltip", "SetLootItem", ...) end) + hooksecurefunc(GameTooltip, "SetLootRollItem", function(self, ...) PawnUpdateTooltip("GameTooltip", "SetLootRollItem", ...) end) + hooksecurefunc(GameTooltip, "SetMerchantItem", function(self, ...) PawnUpdateTooltip("GameTooltip", "SetMerchantItem", ...) end) + hooksecurefunc(GameTooltip, "SetQuestItem", function(self, ...) PawnUpdateTooltip("GameTooltip", "SetQuestItem", ...) end) + hooksecurefunc(GameTooltip, "SetQuestLogItem", function(self, ...) PawnUpdateTooltip("GameTooltip", "SetQuestLogItem", ...) end) + hooksecurefunc(GameTooltip, "SetSendMailItem", function(self, ...) PawnUpdateTooltip("GameTooltip", "SetSendMailItem", ...) end) + hooksecurefunc(GameTooltip, "SetSocketGem", function(self, ...) PawnUpdateTooltip("GameTooltip", "SetSocketGem", ...) end) + hooksecurefunc(GameTooltip, "SetTradePlayerItem", function(self, ...) PawnUpdateTooltip("GameTooltip", "SetTradePlayerItem", ...) end) + hooksecurefunc(GameTooltip, "SetTradeSkillItem", function(self, ...) PawnUpdateTooltip("GameTooltip", "SetTradeSkillItem", ...) end) + hooksecurefunc(GameTooltip, "SetTradeTargetItem", function(self, ...) PawnUpdateTooltip("GameTooltip", "SetTradeTargetItem", ...) end) + hooksecurefunc(GameTooltip, "SetTrainerService", function(self, ...) PawnUpdateTooltip("GameTooltip", "SetTrainerService", ...) end) + hooksecurefunc(GameTooltip, "Hide", function(self, ...) PawnLastHoveredItem = nil end) + + -- The item link tooltip (only hook it if it's an actual item) + hooksecurefunc(ItemRefTooltip, "SetHyperlink", + function(self, ItemLink, ...) + -- Attach an icon to the tooltip first so that an existing icon can be hidden if the new hyperlink doesn't have one. + PawnAttachIconToTooltip(ItemRefTooltip, false, ItemLink) + if PawnGetHyperlinkType(ItemLink) ~= "item" then return end + PawnUpdateTooltip("ItemRefTooltip", "SetHyperlink", ItemLink, ...) + end) + VgerCore.HookInsecureScript(ItemRefTooltip, "OnEnter", function() _, PawnLastHoveredItem = ItemRefTooltip:GetItem() end) + VgerCore.HookInsecureScript(ItemRefTooltip, "OnLeave", function() PawnLastHoveredItem = nil end) + VgerCore.HookInsecureScript(ItemRefTooltip, "OnMouseUp", + function(object, button) + if button == "RightButton" then + local _, ItemLink = ItemRefTooltip:GetItem() + PawnUI_SetCompareItemAndShow(2, ItemLink) + end + end) + + -- The loot roll window + local LootRollClickHandler = + function(object, button) + if button == "RightButton" then + local ItemLink = GetLootRollItemLink(object:GetParent().rollID) + PawnUI_SetCompareItemAndShow(2, ItemLink) + end + end + GroupLootFrame1IconFrame:SetScript("OnMouseUp", LootRollClickHandler) + GroupLootFrame2IconFrame:SetScript("OnMouseUp", LootRollClickHandler) + GroupLootFrame3IconFrame:SetScript("OnMouseUp", LootRollClickHandler) + GroupLootFrame4IconFrame:SetScript("OnMouseUp", LootRollClickHandler) + + -- The "currently equipped" tooltips (two, in case of rings, trinkets, and dual wielding) + hooksecurefunc(ShoppingTooltip1, "SetHyperlinkCompareItem", function(self, ItemLink, ...) PawnUpdateTooltip("ShoppingTooltip1", "SetHyperlinkCompareItem", ItemLink, ...) PawnAttachIconToTooltip(ShoppingTooltip1, true) end) + hooksecurefunc(ShoppingTooltip2, "SetHyperlinkCompareItem", function(self, ItemLink, ...) PawnUpdateTooltip("ShoppingTooltip2", "SetHyperlinkCompareItem", ItemLink, ...) PawnAttachIconToTooltip(ShoppingTooltip2, true) end) + --if ShoppingTooltip3 then + -- In current builds, this returns the same ItemLink as the original item (the view-as-level parameter hasn't changed). + --hooksecurefunc(ShoppingTooltip3, "SetHyperlinkCompareItem", function(self, ItemLink, ...) PawnUpdateTooltip("ShoppingTooltip3", "SetHyperlinkCompareItem", ItemLink, ...) PawnAttachIconToTooltip(ShoppingTooltip3, true) end) + --end + hooksecurefunc(ShoppingTooltip1, "SetInventoryItem", function(self, ...) PawnUpdateTooltip("ShoppingTooltip1", "SetInventoryItem", ...) PawnAttachIconToTooltip(ShoppingTooltip1, true) end) -- EQCompare compatibility + hooksecurefunc(ShoppingTooltip2, "SetInventoryItem", function(self, ...) PawnUpdateTooltip("ShoppingTooltip2", "SetInventoryItem", ...) PawnAttachIconToTooltip(ShoppingTooltip2, true) end) -- EQCompare compatibility + --if ShoppingTooltip3 then + --hooksecurefunc(ShoppingTooltip3, "SetInventoryItem", function(self, ...) PawnUpdateTooltip("ShoppingTooltip3", "SetInventoryItem", ...) PawnAttachIconToTooltip(ShoppingTooltip3, true) end) -- EQCompare compatibility, assuming EQCompare adds support for the third shopping tooltip + --end + + -- MultiTips compatibility + if MultiTips then + VgerCore.HookInsecureFunction(ItemRefTooltip2, "SetHyperlink", function(self, ItemLink, ...) PawnUpdateTooltip("ItemRefTooltip2", "SetHyperlink", ItemLink, ...) PawnAttachIconToTooltip(ItemRefTooltip2, false, ItemLink) end) + VgerCore.HookInsecureFunction(ItemRefTooltip3, "SetHyperlink", function(self, ItemLink, ...) PawnUpdateTooltip("ItemRefTooltip3", "SetHyperlink", ItemLink, ...) PawnAttachIconToTooltip(ItemRefTooltip3, false, ItemLink) end) + VgerCore.HookInsecureFunction(ItemRefTooltip4, "SetHyperlink", function(self, ItemLink, ...) PawnUpdateTooltip("ItemRefTooltip4", "SetHyperlink", ItemLink, ...) PawnAttachIconToTooltip(ItemRefTooltip4, false, ItemLink) end) + VgerCore.HookInsecureFunction(ItemRefTooltip5, "SetHyperlink", function(self, ItemLink, ...) PawnUpdateTooltip("ItemRefTooltip5", "SetHyperlink", ItemLink, ...) PawnAttachIconToTooltip(ItemRefTooltip5, false, ItemLink) end) + end + + -- EquipCompare compatibility + if ComparisonTooltip1 then + VgerCore.HookInsecureFunction(ComparisonTooltip1, "SetHyperlinkCompareItem", function(self, ItemLink, ...) PawnUpdateTooltip("ComparisonTooltip1", "SetHyperlinkCompareItem", ItemLink, ...) PawnAttachIconToTooltip(ComparisonTooltip1, true) end) + VgerCore.HookInsecureFunction(ComparisonTooltip2, "SetHyperlinkCompareItem", function(self, ItemLink, ...) PawnUpdateTooltip("ComparisonTooltip2", "SetHyperlinkCompareItem", ItemLink, ...) PawnAttachIconToTooltip(ComparisonTooltip2, true) end) + VgerCore.HookInsecureFunction(ComparisonTooltip1, "SetInventoryItem", function(self, ...) PawnUpdateTooltip("ComparisonTooltip1", "SetInventoryItem", ...) PawnAttachIconToTooltip(ComparisonTooltip1, true) end) -- EquipCompare with CharactersViewer + VgerCore.HookInsecureFunction(ComparisonTooltip2, "SetInventoryItem", function(self, ...) PawnUpdateTooltip("ComparisonTooltip2", "SetInventoryItem", ...) PawnAttachIconToTooltip(ComparisonTooltip2, true) end) -- EquipCompare with CharactersViewer + VgerCore.HookInsecureFunction(ComparisonTooltip1, "SetHyperlink", function(self, ItemLink, ...) PawnUpdateTooltip("ComparisonTooltip1", "SetHyperlink", ItemLink, ...) PawnAttachIconToTooltip(ComparisonTooltip1, true) end) -- EquipCompare with Armory + VgerCore.HookInsecureFunction(ComparisonTooltip2, "SetHyperlink", function(self, ItemLink, ...) PawnUpdateTooltip("ComparisonTooltip2", "SetHyperlink", ItemLink, ...) PawnAttachIconToTooltip(ComparisonTooltip2, true) end) -- EquipCompare with Armory + end + + -- Outfitter compatibility + if Outfitter and Outfitter._ExtendedCompareTooltip then + VgerCore.HookInsecureFunction(Outfitter._ExtendedCompareTooltip, "AddShoppingLink", function(self, pTitle, pName, pLink, ...) PawnUpdateTooltip("OutfitterCompareTooltip" .. self.NumTooltipsShown, "SetHyperlink", pLink) end) + end + + -- AtlasLoot Enhanced compatibility + if AtlasLootTooltip then + VgerCore.HookInsecureFunction(AtlasLootTooltip, "SetHyperlink", function(self, ...) PawnUpdateTooltip("AtlasLootTooltip", "SetHyperlink", ...) end) + end + + -- LinkWrangler compatibility + if LinkWrangler then + LinkWrangler.RegisterCallback("Pawn", PawnLinkWranglerOnTooltip, "refresh") + LinkWrangler.RegisterCallback("Pawn", PawnLinkWranglerOnTooltip, "refreshcomp") + end + +end + +function PawnOnLogout() + PawnUnitializePlugins() +end + +function PawnOnAddonLoaded(AddonName) + if AddonName == "Blizzard_InspectUI" then + -- After the inspect UI is loaded, we want to hook it to add the Pawn button. + PawnUI_InspectPawnButton_Attach() + elseif AddonName == "Blizzard_ItemSocketingUI" then + -- After the socketing UI is loaded, it gets a Pawn button too. + PawnUI_SocketingPawnButton_Attach() + end +end + +-- Resets all Pawn options and scales. Used to set the saved variable to a default state. +function PawnResetOptions() + PawnCommon = nil + PawnOptions = nil + PawnInitializeOptions() +end + +-- Sets values for any options that don't have a value set yet. Useful when upgrading. This method can also be +-- called by any code that might run before initialization finishes to ensure that PawnCommon exists and is set up. +function PawnInitializeOptions() + -- If either of the options tables don't exist yet, create them now. + if not PawnCommon then PawnCommon = {} end + if not PawnOptions then PawnOptions = {} end + + -- We need to know the player's full name for some server-specific settings. + PawnPlayerFullName = UnitName("player") .. "-" .. GetRealmName() + -- Save the last known player name to PawnOptions so that we can detect character renames and server + -- transfers in the future. + PawnOptions.LastPlayerFullName = PawnPlayerFullName + + -- Now, migrate all settings over to PawnCommon, and upgrade to the current version from any previous version + -- of Pawn (or none at all). Settings are respected in this order of preference: + -- 1. Global settings in PawnCommon + -- 2. Per-character settings in PawnOptions (used prior to Pawn 1.3) + -- 3. The default values for the settings. + PawnMigrateSetting("Debug", false) + PawnMigrateSetting("Digits", 1) + PawnMigrateSetting("ShowAsterisks", PawnShowAsterisksNonzero) + PawnMigrateSetting("ShowUnenchanted", true) + PawnMigrateSetting("ShowEnchanted", false) + PawnMigrateSetting("ShowItemID", false) + PawnMigrateSetting("AlignNumbersRight", false) + PawnMigrateSetting("ShowSpace", false) + local PawnDefaultPosition = PawnButtonPositionRight + if GetAccountExpansionLevel() >= 3 then PawnDefaultPosition = PawnButtonPositionRight end -- *** Cataclysm beta temp fix: the 4.0 UI puts a different button in this location. + PawnMigrateSetting("ButtonPosition", PawnButtonPositionRight) + PawnMigrateSetting("ShowTooltipIcons", true) + + -- Now, migrate all scales from this character over to PawnCommon. + if not PawnCommon.Scales then PawnCommon.Scales = {} end + if PawnOptions.Scales then + -- Looks like there's one or more scales on this character that need to be migrated. + for ScaleName, Scale in pairs(PawnOptions.Scales) do + if PawnCommon.Scales[ScaleName] then + -- This scale name already exists, so we have to make it unique first. + -- First, try just appending the player name. + -- If that's not good enough, start trying sequential numbers. (Sigh; why do people need + -- to make things so complicated? Did you really need ten characters with the same name + -- and identically named scales on each one?) + ScaleName = ScaleName .. " (" .. UnitName("player") .. ")" + local ScaleNameBase = ScaleName .. " (" + local i = 0 + while PawnCommon.Scales[ScaleName] do + i = i + 1 + ScaleName = ScaleNameBase .. i .. ")" + end + end + + -- We now have a unique name for this scale, so transfer it over to the master scale list. + PawnCommon.Scales[ScaleName] = Scale + Scale.PerCharacterOptions = { } + Scale.PerCharacterOptions[PawnPlayerFullName] = { } + if not Scale.Hidden then + Scale.PerCharacterOptions[PawnPlayerFullName].Visible = true + end + Scale.NormalizationFactor = PawnOptions.NormalizationFactor + Scale.Hidden = nil + end + end + -- Now that migration is complete, remove all migrated scales from the per-character options. + PawnOptions.Scales = nil + + -- These options have been removed or otherwise are no longer useful. + PawnOptions.ShowItemLevel = nil + PawnOptions.ShownGettingStarted = nil + PawnOptions.NormalizationFactor = nil + + -- Finally, this stuff needs to get done after options are changed. + PawnRecreateAnnotationFormats() + +end + +-- If the specified setting does not exist in the common settings list, this function first tries to migrate it from the +-- current character's settings (from Pawn 1.2 or earlier). If it's not there either, it's set to a default value. +function PawnMigrateSetting(SettingName, Default) + if PawnCommon[SettingName] ~= nil then + PawnOptions[SettingName] = nil + return + end + if PawnOptions[SettingName] ~= nil then + PawnCommon[SettingName] = PawnOptions[SettingName] + PawnOptions[SettingName] = nil + return + end + PawnCommon[SettingName] = Default +end + +-- Once per new version of Pawn that adds keybindings, bind the new actions to default keys. +function PawnSetDefaultKeybindings() + -- It's possible that this will happen before the main initialization code, so we need to ensure that the + -- default Pawn options have been set already. Doing this multiple times is harmless. + PawnInitializeOptions() + + if PawnOptions.LastKeybindingsSet == nil then PawnOptions.LastKeybindingsSet = 0 end + local BindingSet = false + + -- Keybindings for opening the Pawn UI and setting comparison items. + if PawnOptions.LastKeybindingsSet < 1 then + BindingSet = PawnSetKeybindingIfAvailable(PAWN_TOGGLE_UI_DEFAULT_KEY, "PAWN_TOGGLE_UI") or BindingSet + BindingSet = PawnSetKeybindingIfAvailable(PAWN_COMPARE_LEFT_DEFAULT_KEY, "PAWN_COMPARE_LEFT") or BindingSet + BindingSet = PawnSetKeybindingIfAvailable(PAWN_COMPARE_RIGHT_DEFAULT_KEY, "PAWN_COMPARE_RIGHT") or BindingSet + end + + -- If any keybindings were changed, save the user's bindings. + if BindingSet then + local CurrentBindingSet = GetCurrentBindingSet() + if CurrentBindingSet == 1 or CurrentBindingSet == 2 then + SaveBindings(CurrentBindingSet) + else + VgerCore.Fail("GetCurrentBindingSet() returned unexpected value: " .. tostring(CurrentBindingSet)) + end + end + + -- Record that we've set those keybindings, so we don't try to set them again in the future, even if + -- the user clears them. + PawnOptions.LastKeybindingsSet = 1 +end + +-- Sets a keybinding to its default value if it's not already assigned to something else. Returns true if anything was changed. +function PawnSetKeybindingIfAvailable(Key, Binding) + -- Is this key already bound? + local ExistingBinding = GetBindingAction(Key) + if not ExistingBinding or ExistingBinding == "" then + -- Bind this key to its default Pawn action. + SetBinding(Key, Binding) + return true + else + -- This key is already bound, so do nothing. + return false + end +end + +-- Returns an empty Pawn scale table. +function PawnGetEmptyScale() + return + { + ["SmartGemSocketing"] = true, + ["GemQualityLevel"] = PawnDefaultGemQualityLevel, + ["SmartMetaGemSocketing"] = true, + ["MetaGemQualityLevel"] = PawnDefaultMetaGemQualityLevel, + ["PerCharacterOptions"] = { }, + ["Values"] = { }, + } +end + +-- Returns the default Pawn scale table. +function PawnGetDefaultScale() + return + { + ["SmartGemSocketing"] = true, + ["GemQualityLevel"] = PawnDefaultGemQualityLevel, + ["SmartMetaGemSocketing"] = true, + ["MetaGemQualityLevel"] = PawnDefaultMetaGemQualityLevel, + ["PerCharacterOptions"] = { }, + ["Values"] = + { + ["Strength"] = 1, + ["Agility"] = 1, + ["Stamina"] = 2/3, + ["Intellect"] = 1, + ["Spirit"] = 1, + ["Armor"] = 0.1, + ["Dps"] = 3.4, + ["ExpertiseRating"] = 1, + ["HitRating"] = 1, + ["CritRating"] = 1, + ["ArmorPenetration"] = 1/7, + ["ResilienceRating"] = 1, + ["HasteRating"] = 1, + ["Ap"] = 0.5, + ["Rap"] = 0.4, + ["Mp5"] = 2, + ["Hp5"] = 2, + ["Mana"] = 1/15, + ["Health"] = 1/15, + ["BlockValue"] = 0.65, + ["BlockRating"] = 1, + ["DefenseRating"] = 1, + ["DodgeRating"] = 1, + ["ParryRating"] = 1, + ["SpellPower"] = 6/7, + ["SpellPenetration"] = 0.8, + ["FireSpellDamage"] = 0.7, + ["ShadowSpellDamage"] = 0.7, + ["NatureSpellDamage"] = 0.7, + ["ArcaneSpellDamage"] = 0.7, + ["FrostSpellDamage"] = 0.7, + ["HolySpellDamage"] = 0.7, + ["AllResist"] = 2.5, + ["FireResist"] = 1, + ["ShadowResist"] = 1, + ["NatureResist"] = 1, + ["ArcaneResist"] = 1, + ["FrostResist"] = 1, + ["MetaSocketEffect"] = 36, + }, + } +end + +-- LinkWrangler compatibility +function PawnLinkWranglerOnTooltip(Tooltip, ItemLink) + if not Tooltip then return end + PawnUpdateTooltip(Tooltip:GetName(), "SetHyperlink", ItemLink) + PawnAttachIconToTooltip(Tooltip, false, ItemLink) +end + +------------------------------------------------------------ +-- Pawn core methods +------------------------------------------------------------ + +-- If debugging is enabled, show a message; otherwise, do nothing. +function PawnDebugMessage(Message) + if PawnCommon.Debug then + VgerCore.Message(Message) + end +end + +-- Processes an Pawn slash command. +function PawnCommand(Command) + if Command == "" then + PawnUIShow() + elseif Command == PawnLocal.DebugOnCommand then + PawnCommon.Debug = true + PawnResetTooltips() + if PawnUIFrame_DebugCheck then PawnUIFrame_DebugCheck:SetChecked(PawnCommon.Debug) end + elseif Command == PawnLocal.DebugOffCommand then + PawnCommon.Debug = false + PawnResetTooltips() + if PawnUIFrame_DebugCheck then PawnUIFrame_DebugCheck:SetChecked(PawnCommon.Debug) end + elseif Command == PawnLocal.BackupCommand then + PawnUIExportAllScales() + else + PawnUsage() + end +end + +-- Displays Pawn usage information. +function PawnUsage() + VgerCore.Message(" ") + VgerCore.MultilineMessage(PawnLocal.Usage) + VgerCore.Message(" ") +end + +-- Returns an empty item for use in the item cache. +function PawnGetEmptyCachedItem(NewItemLink, NewItemName, NewNumLines) + -- Also includes properties set to nil by default: Stats, SocketBonusStats, UnenchantedState, UnenchantedSocketBonusStats, Values, Level, ItemID + return { Name = NewItemName, NumLines = NewNumLines, UnknownLines = {}, Link = NewItemLink } +end + +-- Searches the item cache for an item, and either returns the correct cached item, or nil. +function PawnGetCachedItem(ItemLink, ItemName, NumLines) + -- If the item cache is empty, skip all this... + if (not PawnItemCache) or (#PawnItemCache == 0) then return end + -- If debug mode is on, the cache is disabled. + if PawnCommon.Debug then return end + + -- Otherwise, search the item cache for this item. + for _, CachedItem in pairs(PawnItemCache) do + if NumLines and (NumLines == CachedItem.NumLines) then + if ItemLink and CachedItem.Link then + if ItemLink == CachedItem.Link then return CachedItem end + else + if ItemName == CachedItem.Name then return CachedItem end + end + end + end +end + +-- Adds an item to the item cache, removing existing items if necessary. +function PawnCacheItem(CachedItem) + -- If debug mode is on, the cache is disabled. + if PawnCommon.Debug then return end + + -- Cache it. + if PawnItemCacheMaxSize <= 0 then return end + if not PawnItemCache then PawnItemCache = {} end + tinsert(PawnItemCache, CachedItem) + while #PawnItemCache > PawnItemCacheMaxSize do + tremove(PawnItemCache, 0) + end +end + +-- Clears the item cache. +function PawnClearCache() + PawnItemCache = nil + -- We should also clear out the gem cache when doing this. + PawnClearCacheValuesOnly() +end + +-- Clears only the calculated values for items in the cache, retaining things like stats. +function PawnClearCacheValuesOnly() + local CachedItem + -- First, the main item cache. + if PawnItemCache then + for _, CachedItem in pairs(PawnItemCache) do + CachedItem.Values = nil + end + end + -- Then, the gem cache. + local GemTable + for _, GemTable in pairs(PawnGemQualityTables) do + for _, CachedItem in pairs(GemTable) do + CachedItem[9] = nil + end + end + for _, GemTable in pairs(PawnMetaGemQualityTables) do + for _, CachedItem in pairs(GemTable) do + CachedItem[9] = nil + end + end +end + +-- Performance notes useful to the cache and general item processing: +-- * It's faster to store the size of a table in a separate variable than to use #tablename. +-- * It's faster to use tinsert than tinsert. + +-- Clears all calculated values and causes them to be recalculated the next time tooltips are displayed. The stats +-- will not be re-read next time, however. +function PawnResetTooltips() + -- Clear out the calculated values in the cache, leaving item data. + PawnClearCacheValuesOnly() + -- Then, attempt to reset tooltips where possible. On-hover tooltips don't need to be reset manually, but the + -- item link tooltip does. + PawnResetTooltip("ItemRefTooltip") + PawnResetTooltip("ItemRefTooltip2") -- MultiTips compatibility + PawnResetTooltip("ItemRefTooltip3") -- MultiTips compatibility + PawnResetTooltip("ItemRefTooltip4") -- MultiTips compatibility + PawnResetTooltip("ItemRefTooltip5") -- MultiTips compatibility + PawnResetTooltip("ComparisonTooltip1") -- EquipCompare compatibility + PawnResetTooltip("ComparisonTooltip2") -- EquipCompare compatibility + PawnResetTooltip("AtlasLootTooltip") -- AtlasLoot compatibility +end + +-- Attempts to reset a single tooltip, causing Pawn values to be recalculated. Returns true if successful. +function PawnResetTooltip(TooltipName) + local Tooltip = getglobal(TooltipName) + if not Tooltip or not Tooltip.IsShown or not Tooltip:IsShown() or not Tooltip.GetItem then return end + + local _, ItemLink = Tooltip:GetItem() + if not ItemLink then return end + + Tooltip:SetOwner(UIParent, "ANCHOR_PRESERVE") + Tooltip:SetHyperlink(ItemLink) + Tooltip:Show() + return true +end + +-- Recalculates the total value of all stats in a scale, as well as the socket values if smart gem socketing is enabled. +function PawnRecalculateScaleTotal(ScaleName) + -- Find the appropriate scale. + local ThisScale = PawnCommon.Scales[ScaleName] + local ThisScaleValues + if ThisScale then ThisScaleValues = ThisScale.Values end + if not ThisScaleValues then + -- If the passed-in scale doesn't exist, remove it from our cache and exit. + PawnScaleTotals[ScaleName] = nil + PawnScaleBestGems[ScaleName] = nil + return + end + + -- Calculate the total. When calculating the total value for a scale, ignore sockets. + local Total = 0 + for StatName, Value in pairs(ThisScaleValues) do + if Value and StatName ~= "RedSocket" and StatName ~= "YellowSocket" and StatName ~= "BlueSocket" and StatName ~= "MetaSocket" and StatName ~= "MetaSocketEffect" then + Total = Total + Value + end + end + PawnScaleTotals[ScaleName] = Total + + -- If this scale has smart gem socketing enabled, also recalculate socket values. + -- Even if smart gem socketing is disabled, still calculate gem info, because we will need it elsewhere in the UI. + local BestRed, BestYellow, BestBlue, BestMeta + if not PawnScaleBestGems[ScaleName] then PawnScaleBestGems[ScaleName] = { } end + BestRed, PawnScaleBestGems[ScaleName].RedSocket = PawnFindBestGems(ScaleName, true, false, false) + BestYellow, PawnScaleBestGems[ScaleName].YellowSocket = PawnFindBestGems(ScaleName, false, true, false) + BestBlue, PawnScaleBestGems[ScaleName].BlueSocket = PawnFindBestGems(ScaleName, false, false, true) + BestMeta, PawnScaleBestGems[ScaleName].MetaSocket = PawnFindBestGems(ScaleName, false, false, false, true) + if ThisScale.SmartGemSocketing then + ThisScale.Values.RedSocket = BestRed + ThisScale.Values.YellowSocket = BestYellow + ThisScale.Values.BlueSocket = BestBlue + end + if ThisScale.SmartMetaGemSocketing then + ThisScale.Values.MetaSocket = BestMeta + end + + -- Finally, find which gem colors have the highest raw values. + local BestGemValue = 0 + local BestGemString = "" + local BestGemRed, BestGemYellow, BestGemBlue = false, false, false + if ThisScaleValues.RedSocket and ThisScaleValues.RedSocket > BestGemValue then + BestGemValue = ThisScaleValues.RedSocket + BestGemString = RED_GEM + BestGemRed, BestGemYellow, BestGemBlue = true, false, false + elseif ThisScaleValues.RedSocket == BestGemValue then + BestGemString = BestGemString .. "/" .. RED_GEM + BestGemRed = true + end + if ThisScaleValues.YellowSocket and ThisScaleValues.YellowSocket > BestGemValue then + BestGemValue = ThisScaleValues.YellowSocket + BestGemString = YELLOW_GEM + BestGemRed, BestGemYellow, BestGemBlue = false, true, false + elseif ThisScaleValues.YellowSocket == BestGemValue then + BestGemString = BestGemString .. "/" .. YELLOW_GEM + BestGemYellow = true + end + if ThisScaleValues.BlueSocket and ThisScaleValues.BlueSocket > BestGemValue then + BestGemValue = ThisScaleValues.BlueSocket + BestGemString = BLUE_GEM + BestGemRed, BestGemYellow, BestGemBlue = false, false, true + elseif ThisScaleValues.BlueSocket == BestGemValue then + BestGemString = BestGemString .. "/" .. BLUE_GEM + BestGemBlue = true + end + PawnScaleBestGems[ScaleName].BestGems = + { + ["Value"] = BestGemValue, + ["String"] = BestGemString, + ["RedSocket"] = BestGemRed, + ["YellowSocket"] = BestGemYellow, + ["BlueSocket"] = BestGemBlue, + } + +end + +-- Recreates the tooltip annotation format strings. +function PawnRecreateAnnotationFormats() + PawnUnenchantedAnnotationFormat = "%s%s: %." .. PawnCommon.Digits .. "f" + PawnEnchantedAnnotationFormat = PawnUnenchantedAnnotationFormat .. " %s(%." .. PawnCommon.Digits .. "f " .. PawnLocal.BaseValueWord .. ")" +end + +-- Gets the item data for a specific item link. Retrieves the information from the cache when possible; otherwise, it gets fresh information. +-- Return value type is the same as PawnGetCachedItem. +function PawnGetItemData(ItemLink) + VgerCore.Assert(ItemLink, "ItemLink must be non-null!") + if not ItemLink then return end + + -- Only item links are supported; other links are not. + if PawnGetHyperlinkType(ItemLink) ~= "item" then return end + + -- If we have an item link, we can extract basic data from it from the user's WoW cache (not the Pawn item cache). + -- We get a new, normalized version of ItemLink so that items don't end up in the cache multiple times if they're requested + -- using different styles of links that all point to the same item. + ItemID = PawnGetItemIDFromLink(ItemLink) + local ItemName, NewItemLink, ItemRarity, ItemLevel, _, _, _, _, _, ItemTexture = GetItemInfo(ItemLink) + if NewItemLink then + ItemLink = NewItemLink + else + -- We didn't get a new item link. This is almost certainly because the item is not in the user's local WoW cache. + -- REVIEW: In the future, would it be possible to detect this case, and then poll the tooltip until item information + -- comes back, and THEN parse and annotate it? There's also an OnTooltipSetItem event. + end + + -- Now, with that information, we can look up the item in the Pawn item cache. + local Item = PawnGetCachedItem(nil, ItemName, ItemNumLines) + if Item and Item.Values then + return Item + end + -- If Item is non-null but Item.Values is null, we're not done yet! + + -- If we don't have a cached item at all, that means we have to load a tooltip and parse it. + if not Item then + Item = PawnGetEmptyCachedItem(ItemLink, ItemName, ItemNumLines) + Item.Rarity = ItemRarity + Item.Level = ItemLevel + Item.ID = ItemID + Item.Texture = ItemTexture + if PawnCommon.Debug then + PawnDebugMessage(" ") + PawnDebugMessage("====================") + PawnDebugMessage(ItemLink .. VgerCore.Color.Green .. " (" .. tostring(PawnGetItemIDsForDisplay(ItemLink)) .. VgerCore.Color.Green .. ")") + end + + -- First the enchanted stats. + Item.Stats, Item.SocketBonusStats, Item.UnknownLines, Item.PrettyLink = PawnGetStatsFromTooltipWithMethod("PawnPrivateTooltip", true, "SetHyperlink", Item.Link) + + -- Then, the unenchanted stats. But, we only need to do this if the item is enchanted or socketed. PawnUnenchantItemLink + -- will return nil if the item isn't enchanted, so we can skip that process. + local UnenchantedItemLink = PawnUnenchantItemLink(ItemLink) + if UnenchantedItemLink then + PawnDebugMessage(" ") + PawnDebugMessage(PawnLocal.UnenchantedStatsHeader) + Item.UnenchantedStats, Item.UnenchantedSocketBonusStats = PawnGetStatsForItemLink(UnenchantedItemLink, true) + if not Item.UnenchantedStats then + PawnDebugMessage(PawnLocal.FailedToGetUnenchantedItemMessage) + end + else + -- If there was no unenchanted item link, then it's because the original item was not + -- enchanted. So, the unenchanted item is the enchanted item; copy the stats over. + -- (Don't just copy the reference, because then changes to one stat table would also + -- change the other!) + local TableCopy = {} + if Item.Stats then + for StatName, Value in pairs(Item.Stats) do + TableCopy[StatName] = Value + end + end + Item.UnenchantedStats = TableCopy + TableCopy = {} + if Item.SocketBonusStats then + for StatName, Value in pairs(Item.SocketBonusStats) do + TableCopy[StatName] = Value + end + end + Item.UnenchantedSocketBonusStats = TableCopy + end + + -- MetaSocketEffect is special: if it's present in the unenchanted version of an item it should appear + -- in the enchanted version too, if the enchanted version's socket is full. + if Item.UnenchantedStats and Item.Stats and Item.UnenchantedStats.MetaSocketEffect and not Item.Stats.MetaSocketEffect and not Item.Stats.MetaSocket then + Item.Stats.MetaSocketEffect = Item.UnenchantedStats.MetaSocketEffect + end + + -- Enchanted items should not get points for empty sockets, nor do they get socket bonuses if there are any empty sockets. + if Item.Stats and (Item.Stats.RedSocket or Item.Stats.YellowSocket or Item.Stats.BlueSocket or Item.Stats.MetaSocket) then + Item.SocketBonusStats = {} + Item.Stats.RedSocket = nil + Item.Stats.YellowSocket = nil + Item.Stats.BlueSocket = nil + Item.Stats.MetaSocket = nil + end + + -- Cache this item so we don't have to re-parse next time. + PawnCacheItem(Item) + end + + -- Recalculate the scale values for the item only if necessary. + PawnRecalculateItemValuesIfNecessary(Item) + + return Item +end + +-- Gets the item data for a gem, given a table of gem data from Gems.lua. +-- This function does not add gem details to the Pawn item cache. +-- Return value type is the same as PawnGetCachedItem. +function PawnGetGemData(GemData) + -- If we've already called this function for this gem, keep the stored data. + if GemData[9] then return GemData[9] end + + local ItemID = GemData[1] + local ItemName, ItemLink, ItemRarity, ItemLevel, _, _, _, _, _, ItemTexture = GetItemInfo(ItemID) + if ItemLink == nil or ItemName == nil then + -- If the gem doesn't exist in the user's local cache, we'll have to fake up some info for it. + ItemLink = format(PawnLocal.GenericGemLink, ItemID, ItemID) + ItemName = format(PawnLocal.GenericGemName, ItemID) + end + local Item = PawnGetEmptyCachedItem(ItemLink, ItemName) + Item.ID = ItemID + Item.Rarity = ItemRarity + Item.Level = ItemLevel + Item.Texture = ItemTexture + Item.UnenchantedStats = { } + if GemData[5] then + Item.UnenchantedStats[GemData[5]] = GemData[6] + end + if GemData[7] then + Item.UnenchantedStats[GemData[7]] = GemData[8] + end + PawnRecalculateItemValuesIfNecessary(Item, true) -- Ignore the user's normalization factor when determining these gem values. + + -- Save this value for next time. + GemData[9] = Item + return Item +end + +-- Gets the item data for a specific item. Retrieves the information from the cache when possible; otherwise, gets it from the tooltip specified. +-- Return value type is the same as PawnGetCachedItem. +function PawnGetItemDataFromTooltip(TooltipName, MethodName, Param1, ...) + VgerCore.Assert(TooltipName, "TooltipName must be non-null!") + VgerCore.Assert(MethodName, "MethodName must be non-null!") + if (not TooltipName) or (not MethodName) then return end + + -- First, find the tooltip. + local Tooltip = getglobal(TooltipName) + if not Tooltip then return end + + -- If we have a tooltip, try to get an item link from it. + local ItemLink, ItemID, ItemLevel + if (MethodName == "SetHyperlink") and Param1 then + -- Special case: if the method is SetHyperlink, then we already have an item link. + -- (Normally, GetItem will work, but SetHyperlink is used by some mod compatibility code.) + ItemLink = Param1 + elseif Tooltip.GetItem then + _, ItemLink = Tooltip:GetItem() + end + + -- If we got an item link from the tooltip (or it was passed in), we can go through the simpler and more effective code that specifically + -- uses item links, and skip the rest of this function. + if ItemLink then + return PawnGetItemData(ItemLink) + end + + -- If we made it this far, then we're in the degenerate case where the tooltip doesn't have item information. Let's look for the item's name, + -- and maybe we'll get lucky and find that in our item cache. + local ItemName, ItemNameLineNumber = PawnGetItemNameFromTooltip(TooltipName) + if (not ItemName) or (not ItemNameLineNumber) then return end + local ItemNumLines = Tooltip:NumLines() + local Item = PawnGetCachedItem(nil, ItemName, ItemNumLines) + if Item and Item.Values then + return Item + end + -- If Item is non-null but Item.Values is null, we're not done yet! + + -- Ugh, the tooltip doesn't have item information and this item isn't in the Pawn item cache, so we'll have to try to parse this tooltip. + if not Item then + Item = PawnGetEmptyCachedItem(nil, ItemName, ItemNumLines) + PawnDebugMessage(" ") + PawnDebugMessage("====================") + PawnDebugMessage(VgerCore.Color.Green .. ItemName) + + -- Since we don't have an item link, we have to just read stats from the original tooltip, so we only get enchanted values. + PawnFixStupidTooltipFormatting(TooltipName) + Item.Stats, Item.SocketBonusStats, Item.UnknownLines = PawnGetStatsFromTooltip(TooltipName, true) + PawnDebugMessage(PawnLocal.FailedToGetItemLinkMessage) + + -- Cache this item so we don't have to re-parse next time. + PawnCacheItem(Item) + end + + -- Recalculate the scale values for the item only if necessary. + PawnRecalculateItemValuesIfNecessary(Item) + + return Item +end + +-- Returns the same information as PawnGetItemData, but based on an inventory slot index instead of an item link. +-- If requested, data for the base unenchanted item can be returned instead; otherwise, the actual item is returned. +function PawnGetItemDataForInventorySlot(Slot, Unenchanted, UnitName) + if UnitName == nil then UnitName = "player" end + local ItemLink = GetInventoryItemLink(UnitName, Slot) + if not ItemLink then return end + if Unenchanted then + local UnenchantedItem = PawnUnenchantItemLink(ItemLink) + if UnenchantedItem then ItemLink = UnenchantedItem end + end + return PawnGetItemData(ItemLink) +end + +-- Recalculates the scale values for a cached item if necessary, and returns them. +-- Parameters: Item, NoNormalization +-- Item: The item to update. +-- NoNormalization: If true, ignores the user's normalization factor. +-- Returns: Values +-- Values: The item's table of item values. Only includes enabled scales with nonzero values. +function PawnRecalculateItemValuesIfNecessary(Item, NoNormalization) + -- We now have stats for the item. If values aren't already calculated for the item, calculate those. This happens when we have + -- just retrieved the stats for the item, and also when the item values were cleared from the cache but not the stats. + if not Item.Values then + -- Calculate each of the values for which there are scales. + Item.Values = PawnGetAllItemValues(Item.Stats, Item.SocketBonusStats, Item.UnenchantedStats, Item.UnenchantedSocketBonusStats, true, NoNormalization) + + PawnDebugMessage(" ") + end + + return Item.Values +end + +-- Returns a single scale value (in both its enchanted and unenchanted forms) for a cached item. Returns nil for any values that are not present. +function PawnGetSingleValueFromItem(Item, ScaleName) + if PawnIsScaleVisible(ScaleName) then + -- If they've enabled this scale, its value should already be calculated. + local ValuesTable = PawnRecalculateItemValuesIfNecessary(Item) + if not ValuesTable then return end + + -- The scale values are sorted alphabetically, so we need to go through the list. + local Count = #ValuesTable + for i = 1, Count do + local Value = ValuesTable[i] + if Value[1] == ScaleName then + return Value[2], Value[3] + end + end + + -- If we didn't find a value, it's because this item doesn't have a value for this scale. + return 0, 0 + end + + -- If this scale isn't enabled, just calculate it as a one-off. + local Value, UnenchantedValue + Value = PawnGetItemValue(Item.Stats, Item.SocketBonusStats, ScaleName, false, false) + UnenchantedValue = PawnGetItemValue(Item.UnenchantedStats, Item.UnenchantedSocketBonusStats, ScaleName, false, false) + return Value, UnenchantedValue +end + +-- Updates a specific tooltip. +function PawnUpdateTooltip(TooltipName, MethodName, Param1, ...) + if not PawnCommon.Scales then return end + + -- Get information for the item in this tooltip. This function will use item links and cached data whenever possible. + local Item = PawnGetItemDataFromTooltip(TooltipName, MethodName, Param1, ...) + -- If there's no item data, then something failed, so we can't update this tooltip. + if not Item then return end + + -- If this is the main GameTooltip, remember the item that was hovered over. + -- AtlasLoot compatibility: enable hover comparison for AtlasLoot tooltips too. + if TooltipName == "GameTooltip" or TooltipName == "AtlasLootTooltip" then + PawnLastHoveredItem = Item.Link + end + + -- Now, just update the tooltip with the item data we got from the previous call. + local Tooltip = getglobal(TooltipName) + if not Tooltip then + VgerCore.Fail("Where'd the tooltip go? I seem to have misplaced it.") + return + end + + -- If necessary, add a blank line to the tooltip. + local AddSpace = PawnCommon.ShowSpace + + -- Add the scale values to the tooltip. + if AddSpace and #Item.Values > 0 then Tooltip:AddLine(" ") AddSpace = false end + PawnAddValuesToTooltip(Tooltip, Item.Values) + + -- If there were unrecognized values, annotate those lines. + local Annotated = false + if Item.UnknownLines and (PawnCommon.ShowAsterisks == PawnShowAsterisksAlways) or ((PawnCommon.ShowAsterisks == PawnShowAsterisksNonzero or PawnCommon.ShowAsterisks == PawnShowAsterisksNonzeroNoText) and #Item.Values > 0) then + Annotated = PawnAnnotateTooltipLines(TooltipName, Item.UnknownLines) + end + -- If we annotated the tooltip for unvalued stats, display a message. + if (Annotated and PawnCommon.ShowAsterisks ~= PawnShowAsterisksNonzeroNoText) then + Tooltip:AddLine(PawnLocal.AsteriskTooltipLine, VgerCore.Color.BlueR, VgerCore.Color.BlueG, VgerCore.Color.BlueB) + end + + -- Add the item ID to the tooltip if known. + if PawnCommon.ShowItemID and Item.Link then + local IDs = PawnGetItemIDsForDisplay(Item.Link) + if IDs then + if PawnCommon.AlignNumbersRight then + Tooltip:AddDoubleLine(PawnLocal.ItemIDTooltipLine, IDs, VgerCore.Color.OrangeR, VgerCore.Color.OrangeG, VgerCore.Color.OrangeB, VgerCore.Color.OrangeR, VgerCore.Color.OrangeG, VgerCore.Color.OrangeB) + else + Tooltip:AddLine(PawnLocal.ItemIDTooltipLine .. ": " .. IDs, VgerCore.Color.OrangeR, VgerCore.Color.OrangeG, VgerCore.Color.OrangeB) + end + end + end + + -- Show the updated tooltip. + Tooltip:Show() +end + +-- Returns a sorted list of all scale values for an item (and its unenchanted version, if supplied). +-- Parameters: +-- Item: A table of item values in the format returned by GetStatsFromTooltip. +-- SocketBonus: A table of socket bonus values in the format returned by GetStatsFromTooltip. +-- UnenchantedItem: A table of unenchanted item values in the format returned by GetStatsFromTooltip. +-- UnenchantedItemSocketBonus: A table of unenchanted item socket bonuses in the format returned by GetStatsFromTooltip. +-- DebugMessages: If true, debug messages will be printed. +-- NoNormalization: If true, the user's normalization factor will be ignored. +-- Return value: ItemValues +-- ItemValues: A sorted table of scale values in the following format: { {"Scale 1", 100, 90, ...}, {"\"Provider\":Scale2", 200, 175, ...} }. +-- Values for scales that are not currently enabled are not included. +function PawnGetAllItemValues(Item, SocketBonus, UnenchantedItem, UnenchantedItemSocketBonus, DebugMessages, NoNormalization) + local ItemValues = {} + for ScaleName, Scale in pairs(PawnCommon.Scales) do + local ShowScale = PawnIsScaleVisible(ScaleName) + if ShowScale then -- Skip all disabled scales. PawnGetSingleValueFromItem will calculate them on-demand if necessary. + if ShowScale and DebugMessages then + PawnDebugMessage(" ") + PawnDebugMessage(PawnGetScaleLocalizedName(ScaleName) .. " --------------------") + end + local Value + local UnenchantedValue, UseRed, UseYellow, UseBlue + if UnenchantedItem then + UnenchantedValue, UseRed, UseYellow, UseBlue = PawnGetItemValue(UnenchantedItem, UnenchantedItemSocketBonus, ScaleName, ShowScale and DebugMessages and PawnCommon.ShowUnenchanted, NoNormalization) + end + if Item then + if ShowScale and DebugMessages and PawnCommon.ShowEnchanted and PawnCommon.ShowUnenchanted then + PawnDebugMessage(" ") + PawnDebugMessage(PawnLocal.EnchantedStatsHeader) + end + Value = PawnGetItemValue(Item, SocketBonus, ScaleName, ShowScale and DebugMessages and PawnCommon.ShowEnchanted, NoNormalization) + end + + -- Add these values to the table. + if Value == nil then Value = 0 end + if UnenchantedValue == nil then UnenchantedValue = 0 end + if Value > 0 or UnenchantedValue > 0 then + tinsert(ItemValues, {ScaleName, Value, UnenchantedValue, UseRed, UseYellow, UseBlue, PawnGetScaleLocalizedName(ScaleName)}) + end + end + end + + -- Sort the table, then return it. + sort(ItemValues, PawnItemValueCompare) + return ItemValues +end + +-- Adds an array of item values to a tooltip, handling formatting options. +-- Parameters: Tooltip, ItemValues +-- Tooltip: The tooltip to annotate. (Not a name.) +-- ItemValues: An array of item values to use to annotate the tooltip, in the format returned by PawnGetAllItemValues. +-- OnlyFirstValue: If true, only the first value (the "enchanted" one) is used, regardless of the user's settings. +function PawnAddValuesToTooltip(Tooltip, ItemValues, OnlyFirstValue) + -- First, check input arguments. + if type(Tooltip) ~= "table" then + VgerCore.Fail("Tooltip must be a valid tooltip, not '" .. type(Tooltip) .. "'.") + return + end + if not ItemValues then return end + + -- Loop through all of the item value subtables. + for _, Entry in pairs(ItemValues) do + local ScaleName, Value, UnenchantedValue, LocalizedName = Entry[1], Entry[2], Entry[3], Entry[7] + local Scale = PawnCommon.Scales[ScaleName] + VgerCore.Assert(Scale ~= nil, "Scale name in item value list doesn't exist!") + + if PawnIsScaleVisible(ScaleName) then + -- Ignore values that we don't want to display. + if OnlyFirstValue then + UnenchantedValue = 0 + else + if not PawnCommon.ShowEnchanted then Value = 0 end + if not PawnCommon.ShowUnenchanted then UnenchantedValue = 0 end + end + + local TooltipText = nil + local TextColor = PawnGetScaleColor(ScaleName) + local UnenchantedTextColor = PawnGetScaleColor(ScaleName, true) + + if Value and Value > 0 and UnenchantedValue and UnenchantedValue > 0 and math.abs(Value - UnenchantedValue) >= ((10 ^ -PawnCommon.Digits) / 2) then + TooltipText = format(PawnEnchantedAnnotationFormat, TextColor, LocalizedName, tostring(Value), UnenchantedTextColor, tostring(UnenchantedValue)) + elseif Value and Value > 0 then + TooltipText = format(PawnUnenchantedAnnotationFormat, TextColor, LocalizedName, tostring(Value)) + elseif UnenchantedValue and UnenchantedValue > 0 then + TooltipText = format(PawnUnenchantedAnnotationFormat, TextColor, LocalizedName, tostring(UnenchantedValue)) + end + + -- Add the line to the tooltip. + if TooltipText then + -- This could be optimized a bit, but it's not incredibly necessary. + if PawnCommon.AlignNumbersRight then + local Pos = VgerCore.StringFindReverse(TooltipText, ":") + local Left = strsub(TooltipText, 0, Pos - 1) -- ignore the colon + local Right = strsub(TooltipText, 0, 10) .. strsub(TooltipText, Pos + 3) -- add the color string and ignore the spaces following the colon + Tooltip:AddDoubleLine(Left, Right) + else + Tooltip:AddLine(TooltipText) + end + end + end + end +end + +-- Returns the total scale values of all equipped items. Only counts enchanted values. +-- Parameters: UnitName +-- UnitName: The name of the unit from whom the inventory item should be retrieved. Defaults to "player". +-- Return value: ItemValues, Count, EpicItemLevel +-- ItemValues: Same as PawnGetAllItemValues, or nil if unsuccessful. +-- Count: The number of item values calculated. +-- EpicItemLevel: An average epic-equivalent item level for all equipped items. +function PawnGetInventoryItemValues(UnitName) + local Total = {} + local TotalItemLevel = 0 + local SlotStats + for Slot = 1, 18 do + if Slot ~= 4 then -- Skip slots 0, 4, and 19 (they're not gear). + -- REVIEW: The item level of the ranged slot appears to be ignored for Ulduar vehicle scaling, at least for shamans. + local Item = PawnGetItemDataForInventorySlot(Slot, false, UnitName) + if Item then + ItemValues = PawnGetAllItemValues(Item.Stats, Item.SocketBonusStats) + -- Add the item's level to our running total. If it's a 2H weapon (the off-hand slot is empty), double its value. + local ThisItemLevel = PawnGetEpicEquivalentItemLevel(Item.Level, Item.Rarity) + if Slot == 16 then + local _, _, _, _, _, _, _, _, InvType = GetItemInfo(GetInventoryItemLink(UnitName, Slot)) + if InvType == "INVTYPE_2HWEAPON" then ThisItemLevel = ThisItemLevel * 2 end + end + TotalItemLevel = TotalItemLevel + ThisItemLevel + -- Now, add these values to our running totals. + for _, Entry in pairs(ItemValues) do + local ScaleName, Value = Entry[1], Entry[2] + PawnAddStatToTable(Total, ScaleName, Value) -- (not actually stats, but the function does what we want) + end + end + end + end + -- Once we're done, we need to convert our addition table to one that we can return. + local TotalValues = {} + local Count = 0 + for ScaleName, Value in pairs(Total) do + tinsert(TotalValues, { ScaleName, Value, 0, false, false, false, PawnGetScaleLocalizedName(ScaleName) }) + Count = Count + 1 + end + sort(TotalValues, PawnItemValueCompare) + -- Return our totals. + TotalItemLevel = math.floor(TotalItemLevel / 17 + .05) + return TotalValues, Count, TotalItemLevel +end + +-- Works around annoying inconsistencies in the way that Blizzard formats tooltip text. +-- Enchantments and random item properties ("of the whale") are formatted like this: "|cffffffff+15 Intellect|r\r\n". +-- We correct this here. +function PawnFixStupidTooltipFormatting(TooltipName) + local Tooltip = getglobal(TooltipName) + if not Tooltip then return end + for i = 1, Tooltip:NumLines() do + local LeftLine = getglobal(TooltipName .. "TextLeft" .. i) + local Text = LeftLine:GetText() + local Updated = false + if Text and strsub(Text, 1, 2) ~= "\n" then + -- First, look for a color. + if strsub(Text, 1, 10) == "|cffffffff" then + Text = strsub(Text, 11) + LeftLine:SetTextColor(1, 1, 1) + Updated = true + end + -- Then, look for a trailing \r\n, unless that's all that's left of the string. + if (strlen(Text) > 2) and (strbyte(Text, -1) == 10) then + Text = strsub(Text, 1, -4) + Updated = true + end + -- Then, look for a trailing color restoration flag. + if strsub(Text, -2) == "|r" then + Text = strsub(Text, 1, -3) + Updated = true + end + -- Update the tooltip with the new string. + if Updated then + --VgerCore.Message("Old: [" .. PawnEscapeString(LeftLine:GetText()) .. "]") + LeftLine:SetText(Text) + --VgerCore.Message("New: [" .. PawnEscapeString(Text) .. "]") + end + end + end +end + +-- Calls a method on a tooltip and then returns stats from that tooltip. +-- Parameters: ItemID, DebugMessages +-- TooltipName: The name of the tooltip to use. +-- DebugMessages: If true, debug messages will be shown. +-- Method: The name of the method to call on the tooltip, followed optionally by arguments to that method. +-- Return value: Same as PawnGetStatsFromTooltip, or nil if unsuccessful. +function PawnGetStatsFromTooltipWithMethod(TooltipName, DebugMessages, MethodName, ...) + if not TooltipName or not MethodName then + VgerCore.Fail("PawnGetStatsFromTooltipWithMethod requires a valid tooltip name and method name.") + return + end + local Tooltip = getglobal(TooltipName) + Tooltip:ClearLines() -- Without this, sometimes SetHyperlink seems to fail when called rapidly + local Method = Tooltip[MethodName] + Method(Tooltip, ...) + PawnFixStupidTooltipFormatting(TooltipName) + return PawnGetStatsFromTooltip(TooltipName, DebugMessages) +end + +-- Reads the stats for a given item ID, eventually calling PawnGetStatsFromTooltip. +-- Parameters: ItemID, DebugMessages +-- ItemID: The item ID for which to get stats. +-- DebugMessages: If true, debug messages will be shown. +-- Return value: Same as PawnGetStatsFromTooltip, or nil if unsuccessful. +function PawnGetStatsForItemID(ItemID, DebugMessages) + if not ItemID then + VgerCore.Fail("PawnGetStatsForItemID requires a valid item ID.") + return + end + return PawnGetStatsForItemLink("item:" .. ItemID, DebugMessages) +end + +-- Reads the stats for a given item link, eventually calling PawnGetStatsFromTooltip. +-- Parameters: ItemLink, DebugMessages +-- ItemLink: The item link for which to get stats. +-- DebugMessages: If true, debug messages will be shown. +-- Return value: Same as PawnGetStatsFromTooltip, or nil if unsuccessful. +function PawnGetStatsForItemLink(ItemLink, DebugMessages) + if not ItemLink then + VgerCore.Fail("PawnGetStatsForItemLink requires a valid item link.") + return + end + -- Other types of hyperlinks, such as enchant, quest, or spell are ignored by Pawn. + if PawnGetHyperlinkType(ItemLink) ~= "item" then return end + + PawnPrivateTooltip:ClearLines() -- Without this, sometimes SetHyperlink seems to fail when called rapidly + PawnPrivateTooltip:SetHyperlink(ItemLink) + PawnFixStupidTooltipFormatting("PawnPrivateTooltip") + return PawnGetStatsFromTooltip("PawnPrivateTooltip", DebugMessages) +end + +-- Returns the stats of an equipped item, eventually calling PawnGetStatsFromTooltip. +-- Parameters: Slot +-- Slot: The slot number (0-19). If not looping through all slots, use GetInventorySlotInfo("HeadSlot") to get the number. +-- DebugMessages: If true, debug messages will be shown. +-- UnitName: The name of the unit from whom the inventory item should be retrieved. Defaults to "player". +-- Return value: Same as PawnGetStatsFromTooltip, or nil if unsuccessful. +function PawnGetStatsForInventorySlot(Slot, DebugMessages, UnitName) + if type(Slot) ~= "number" then + VgerCore.Fail("PawnGetStatsForInventorySlot requires a valid slot number. Did you mean to use GetInventorySlotInfo to get a number?") + return + end + if not UnitName then UnitName = "player" end + return PawnGetStatsFromTooltipWithMethod("PawnPrivateTooltip", DebugMessages, "SetInventoryItem", UnitName, Slot) +end + +-- Reads the stats from a tooltip. +-- Returns a table mapping stat name with a quantity of that statistic. +-- For example, ReturnValue["Strength"] = 12. +-- Parameters: TooltipName, DebugMessages +-- TooltipName: The tooltip to read. +-- DebugMessages: If true (default), debug messages will be shown. +-- Return value: Stats, UnknownLines +-- Stats: The table of stats for the item. +-- SocketBonusStats: The table of stats for the item's socket bonus. +-- UnknownLines: A list of lines in the tooltip that were not understood. +-- PrettyLink: A beautified item link, if available. +function PawnGetStatsFromTooltip(TooltipName, DebugMessages) + local Stats, SocketBonusStats, UnknownLines = {}, {}, {} + local HadUnknown = false + local SocketBonusIsValid = false + local Tooltip = getglobal(TooltipName) + if DebugMessages == nil then DebugMessages = true end + + -- Get the item name. It could be on line 2 if the first line is "Currently Equipped". + local ItemName, ItemNameLineNumber = PawnGetItemNameFromTooltip(TooltipName) + if (not ItemName) or (not ItemNameLineNumber) then + --VgerCore.Fail("Failed to find name of item on the hidden tooltip") + return + end + + -- First, check for the ignored item names: for example, any item that starts with "Design:" should + -- be ignored, because it's a jewelcrafting design, not a real item with stats. + for _, ThisName in pairs(PawnIgnoreNames) do + if strsub(ItemName, 1, strlen(ThisName)) == ThisName then + -- This is a known ignored item name; don't return any stats. + return + end + end + + -- Now, read the tooltip for stats. + for i = ItemNameLineNumber + 1, Tooltip:NumLines() do + local LeftLine = getglobal(TooltipName .. "TextLeft" .. i) + local LeftLineText = LeftLine:GetText() + + -- Look for this line in the "kill lines" list. If it's there, we're done. + local IsKillLine = false + -- Dirty, dirty hack for 2.3: check the color of the text; if it's "name of item set" yellow, then treat it as a kill line. + -- Not needed because we look for the (1/8) at the end instead. + --local r, g, b = LeftLine:GetTextColor() + --if (math.abs(r - 1) < .01) and (math.abs(g - .82) < .01) and (b < .01) then + -- IsKillLine = true + --end + if not IsKillLine then + for _, ThisKillLine in pairs(PawnKillLines) do + if strfind(LeftLineText, ThisKillLine) then + -- This is a known ignored kill line; stop now. + IsKillLine = true + break + end + end + end + if IsKillLine then break end + + for Side = 1, 2 do + local CurrentParseText, RegexTable, CurrentDebugMessages, IgnoreErrors + if Side == 1 then + CurrentParseText = LeftLineText + RegexTable = PawnRegexes + CurrentDebugMessages = DebugMessages + IgnoreErrors = false + else + local RightLine = getglobal(TooltipName .. "TextRight" .. i) + CurrentParseText = RightLine:GetText() + if (not CurrentParseText) or (CurrentParseText == "") then break end + RegexTable = PawnRightHandRegexes + CurrentDebugMessages = false + IgnoreErrors = true + end + + local ThisLineIsSocketBonus = false + if Side == 1 and strsub(CurrentParseText, 1, strlen(PawnSocketBonusPrefix)) == PawnSocketBonusPrefix then + -- This line is the socket bonus. + ThisLineIsSocketBonus = true + if LeftLine.GetTextColor then + SocketBonusIsValid = (LeftLine:GetTextColor() == 0) -- green's red component is 0, but grey's red component is .5 + else + PawnDebugMessage(VgerCore.Color.Blue .. "Failed to determine whether socket bonus was valid, so assuming that it is indeed valid.") + SocketBonusIsValid = true + end + CurrentParseText = strsub(CurrentParseText, strlen(PawnSocketBonusPrefix) + 1) + end + + local Understood + if ThisLineIsSocketBonus then + Understood = PawnLookForSingleStat(RegexTable, SocketBonusStats, CurrentParseText, CurrentDebugMessages) + else + Understood = PawnLookForSingleStat(RegexTable, Stats, CurrentParseText, CurrentDebugMessages) + end + + if not Understood then + -- We don't understand this line. Let's see if it's a complex stat. + + -- First, check to see if it starts with any of the ignore prefixes, such as "Use:". + local IgnoreLine = false + for _, ThisPrefix in pairs(PawnSeparatorIgnorePrefixes) do + if strsub(CurrentParseText, 1, strlen(ThisPrefix)) == ThisPrefix then + -- We know that this line doesn't contain a complex stat, so ignore it. + IgnoreLine = true + if CurrentDebugMessages then PawnDebugMessage(VgerCore.Color.Blue .. format(PawnLocal.DidntUnderstandMessage, PawnEscapeString(CurrentParseText))) end + if not Understood and not IgnoreErrors then HadUnknown = true UnknownLines[CurrentParseText] = 1 end + break + end + end + + -- If this line wasn't ignorable, try to break it up. + if not IgnoreLine then + -- We'll assume the entire line was understood for now, but if we find any PART that + -- we don't understand, we'll clear the "understood" flag again. + Understood = true + + local Pos = 1 + local NextPos = 0 + local InnerStatLine = nil + local InnerUnderstood = nil + + while Pos < strlen(CurrentParseText) do + for _, ThisSeparator in pairs(PawnSeparators) do + NextPos = strfind(CurrentParseText, ThisSeparator, Pos, false) + if NextPos then + -- One of the separators was found. Check this string. + InnerStatLine = strsub(CurrentParseText, Pos, NextPos - 1) + if ThisLineIsSocketBonus then + InnerUnderstood = PawnLookForSingleStat(RegexTable, SocketBonusStats, InnerStatLine, CurrentDebugMessages) + else + InnerUnderstood = PawnLookForSingleStat(RegexTable, Stats, InnerStatLine, CurrentDebugMessages) + end + if not InnerUnderstood then + -- We don't understand this line. + Understood = false + if CurrentDebugMessages then PawnDebugMessage(VgerCore.Color.Blue .. format(PawnLocal.DidntUnderstandMessage, PawnEscapeString(InnerStatLine))) end + if not Understood and not IgnoreErrors then HadUnknown = true UnknownLines[InnerStatLine] = 1 end + end + -- Regardless of the outcome, advance to the next position. + Pos = NextPos + strlen(ThisSeparator) + break + end -- (if NextPos...) + -- If we didn't find that separator, continue the for loop to try the next separator. + end -- (for ThisSeparator...) + if (Pos > 1) and (not NextPos) then + -- If there are no more separators left in the string, but we did find one before that, then we have + -- one last string to check: everything after the last separator. + InnerStatLine = strsub(CurrentParseText, Pos) + if ThisLineIsSocketBonus then + InnerUnderstood = PawnLookForSingleStat(RegexTable, SocketBonusStats, InnerStatLine, CurrentDebugMessages) + else + InnerUnderstood = PawnLookForSingleStat(RegexTable, Stats, InnerStatLine, CurrentDebugMessages) + end + if not InnerUnderstood then + -- We don't understand this line. + Understood = false + if CurrentDebugMessages then PawnDebugMessage(VgerCore.Color.Blue .. format(PawnLocal.DidntUnderstandMessage, PawnEscapeString(InnerStatLine))) end + if not Understood and not IgnoreErrors then HadUnknown = true UnknownLines[InnerStatLine] = 1 end + end + break + elseif not NextPos then + -- If there are no more separators in the string and we hadn't found any before that, we're done. + Understood = false + if CurrentDebugMessages then PawnDebugMessage(VgerCore.Color.Blue .. format(PawnLocal.DidntUnderstandMessage, PawnEscapeString(CurrentParseText))) end + if not Understood and not IgnoreErrors then HadUnknown = true UnknownLines[CurrentParseText] = 1 end + break + end + -- Continue on to the next portion of the string. The loop ends when we run out of string. + end -- (while Pos...) + end -- (if not IgnoreLine...) + end + end + end + + -- Before returning, some stats require special handling. + + if Stats["AutoArmor"] then + if Stats["IsCloth"] or Stats["IsLeather"] or Stats["IsMail"] or Stats["IsPlate"] then + -- Cloth, leather, mail, and plate armor is base armor, and can be multiplied by talents. + PawnAddStatToTable(Stats, "BaseArmor", Stats["AutoArmor"]) + else + -- Armor on all other item types (weapons, trinkets, rings) is bonus armor, and not multiplied. + PawnAddStatToTable(Stats, "BonusArmor", Stats["AutoArmor"]) + end + Stats["AutoArmor"] = nil + end + PawnAddStatToTable(Stats, "Armor", Stats["BaseArmor"]) + PawnAddStatToTable(Stats, "Armor", Stats["BonusArmor"]) + + if Stats["IsMainHand"] or Stats["IsOneHand"] or Stats["IsOffHand"] or Stats["IsTwoHand"] or Stats["IsRanged"] then + -- Only perform this conversion if this is an actual weapon. This works around a problem that occurs when you + -- enchant your ring with weapon damage and then Pawn would try to calculate DPS for your ring with no Min/MaxDamage. + local Min = Stats["MinDamage"] + if not Min then Min = 0 end + local Max = Stats["MaxDamage"] + if not Max then Max = 0 end + if (Min > 0 or Max > 0) and Stats["Speed"] then + -- Convert damage to DPS if *either* minimum or maximum damage is present. (A few annoying items + -- like the Brewfest steins have only max damage.) + PawnAddStatToTable(Stats, "Dps", (Min + Max) / Stats["Speed"] / 2) + else + local WeaponStats = 0 + if Stats["MinDamage"] then WeaponStats = WeaponStats + 1 end + if Stats["MaxDamage"] then WeaponStats = WeaponStats + 1 end + if Stats["Speed"] then WeaponStats = WeaponStats + 1 end + VgerCore.Assert(WeaponStats == 0 or WeaponStats == 3, "Weapon with mismatched or missing speed and damage stats was not converted to DPS") + end + end + + if Stats["IsMainHand"] then + PawnAddStatToTable(Stats, "MainHandDps", Stats["Dps"]) + PawnAddStatToTable(Stats, "MainHandSpeed", Stats["Speed"]) + PawnAddStatToTable(Stats, "MainHandMinDamage", Stats["MinDamage"]) + PawnAddStatToTable(Stats, "MainHandMaxDamage", Stats["MaxDamage"]) + PawnAddStatToTable(Stats, "IsMelee", 1) + Stats["IsMainHand"] = nil + end + + if Stats["IsOffHand"] then + PawnAddStatToTable(Stats, "OffHandDps", Stats["Dps"]) + PawnAddStatToTable(Stats, "OffHandSpeed", Stats["Speed"]) + PawnAddStatToTable(Stats, "OffHandMinDamage", Stats["MinDamage"]) + PawnAddStatToTable(Stats, "OffHandMaxDamage", Stats["MaxDamage"]) + PawnAddStatToTable(Stats, "IsMelee", 1) + Stats["IsOffHand"] = nil + end + + if Stats["IsOneHand"] then + PawnAddStatToTable(Stats, "OneHandDps", Stats["Dps"]) + PawnAddStatToTable(Stats, "OneHandSpeed", Stats["Speed"]) + PawnAddStatToTable(Stats, "OneHandMinDamage", Stats["MinDamage"]) + PawnAddStatToTable(Stats, "OneHandMaxDamage", Stats["MaxDamage"]) + PawnAddStatToTable(Stats, "IsMelee", 1) + Stats["IsOneHand"] = nil + end + + if Stats["IsTwoHand"] then + PawnAddStatToTable(Stats, "TwoHandDps", Stats["Dps"]) + PawnAddStatToTable(Stats, "TwoHandSpeed", Stats["Speed"]) + PawnAddStatToTable(Stats, "TwoHandMinDamage", Stats["MinDamage"]) + PawnAddStatToTable(Stats, "TwoHandMaxDamage", Stats["MaxDamage"]) + PawnAddStatToTable(Stats, "IsMelee", 1) + Stats["IsTwoHand"] = nil + end + + if Stats["IsMelee"] and Stats["IsRanged"] then + VgerCore.Fail("Weapon that is both melee and ranged was converted to both Melee* and Ranged* stats") + end + + if Stats["IsMelee"] then + PawnAddStatToTable(Stats, "MeleeDps", Stats["Dps"]) + PawnAddStatToTable(Stats, "MeleeSpeed", Stats["Speed"]) + PawnAddStatToTable(Stats, "MeleeMinDamage", Stats["MinDamage"]) + PawnAddStatToTable(Stats, "MeleeMaxDamage", Stats["MaxDamage"]) + Stats["IsMelee"] = nil + + -- Feral attack power conversion + local FeralAp = PawnGetFeralAp(Stats["Dps"]) + if FeralAp and FeralAp > 0 then PawnAddStatToTable(Stats, "FeralAp", FeralAp) end + end + + if Stats["IsRanged"] then + PawnAddStatToTable(Stats, "RangedDps", Stats["Dps"]) + PawnAddStatToTable(Stats, "RangedSpeed", Stats["Speed"]) + PawnAddStatToTable(Stats, "RangedMinDamage", Stats["MinDamage"]) + PawnAddStatToTable(Stats, "RangedMaxDamage", Stats["MaxDamage"]) + Stats["IsRanged"] = nil + end + + if Stats["MetaSocket"] then + -- For each meta socket, add credit for meta socket effects. + -- Enchanted items will get the benefit of meta sockets on their unenchanted version later. + PawnAddStatToTable(Stats, "MetaSocketEffect", Stats["MetaSocket"]) + end + + -- Now, socket bonuses require special handling. + if SocketBonusIsValid then + -- If the socket bonus is valid (green), then just add those stats directly to the main stats table and be done with it. + PawnAddStatsToTable(Stats, SocketBonusStats) + SocketBonusStats = {} + else + -- If the socket bonus is not valid, then we need to check for sockets. + if Stats["RedSocket"] or Stats["YellowSocket"] or Stats["BlueSocket"] or Stats["MetaSocket"] then + -- There are sockets left, so the player could still meet the requirements. + else + -- There are no sockets left and the socket bonus requirements were not met. Ignore the + -- socket bonus, since the user purposely chose to mis-socket. + SocketBonusStats = {} + end + end + + -- Done! + local _, PrettyLink = Tooltip:GetItem() + if not HadUnknown then UnknownLines = nil end + return Stats, SocketBonusStats, UnknownLines, PrettyLink +end + +-- Looks for a single string in the regex table, and adds it to the stats table if it finds it. +-- Parameters: Stats, ThisString, DebugMessages +-- RegexTable: The regular expression table to look through. +-- Stats: The stats table to modify if anything is found. +-- ThisString: The string to look for. +-- DebugMessages: If true, debug messages will be shown. +-- Return value: Understood +-- Understood: True if the string was understood (even if empty or ignored), otherwise false. +function PawnLookForSingleStat(RegexTable, Stats, ThisString, DebugMessages) + -- First, perform a series of normalizations on the string. For example, "Stamina +5" should + -- be converted to "+5 Stamina" so we don't need two strings for everything. + ThisString = strtrim(ThisString) + for _, Entry in pairs(PawnNormalizationRegexes) do + local Regex, Replacement = unpack(Entry) + local OldString = ThisString + ThisString, Count = gsub(ThisString, Regex, Replacement, 1) + --if Count > 0 then PawnDebugMessage("Normalized string using \"" .. PawnEscapeString(Regex) .. "\" -- was " .. PawnEscapeString(OldString) .. " and is now " .. PawnEscapeString(ThisString)) end + end + + -- Now, look for the string in the main regex table. + local Props, Matches = PawnFindStringInRegexTable(ThisString, RegexTable) + if not Props then + -- We don't understand this. Return false to indicate this, so the caller can handle the case. + return false + else + -- We understand this. It could either be an ignored line like "Soulbound", or an actual stat. + -- The same code handles both cases; just keep going until we find a stat of nil; in the ignored case, we hit this immediately. + local Index = 2 + while true do + local Stat, Number, Source = Props[Index], tonumber(Props[Index + 1]), Props[Index + 2] + if not Stat then break end -- There are no more stats left to process. + if not Number then Number = 1 end + + if Source == PawnMultipleStatsExtract or Source == nil then + -- This is a variable number of a stat, the standard case. + local ExtractedValue = gsub(Matches[math.abs(Number)], ",", ".") + ExtractedValue = tonumber(ExtractedValue) -- replacing commas with dots for the German client + if Number < 0 then ExtractedValue = -ExtractedValue end + if DebugMessages then PawnDebugMessage(format(PawnLocal.FoundStatMessage, ExtractedValue, Stat)) end + PawnAddStatToTable(Stats, Stat, ExtractedValue) + elseif Source == PawnMultipleStatsFixed then + -- This is a fixed number of a stat, such as a socket (1). + if DebugMessages then PawnDebugMessage(format(PawnLocal.FoundStatMessage, Number, Stat)) end + PawnAddStatToTable(Stats, Stat, Number) + else + VgerCore.Fail("Incorrect source value of '" .. Source .. "' for regex: " .. Props[1]) + end + + Index = Index + 3 + end + end + + return true +end + +-- Gets the name of an item given a tooltip name, and the line on which the item appears. +-- Normally this is line 1, but it can be line 2 if the first line is "Currently Equipped". +-- Parameters: TooltipName +-- TooltipName: The name of the tooltip to read. +-- Return value: ItemName, LineNumber +-- ItemName: The name of the item in the tooltip, or nil if the tooltip didn't have one. +-- LineNumber: The line number on which the name was found, or nil if no item was found. +function PawnGetItemNameFromTooltip(TooltipName) + -- First, get the tooltip details. + local TooltipTopLine = getglobal(TooltipName .. "TextLeft1") + if not TooltipTopLine then return end + local ItemName = TooltipTopLine:GetText() + if not ItemName or ItemName == "" then return end + + -- If this is a Currently Equipped tooltip, skip the first line. + if ItemName == CURRENTLY_EQUIPPED then + ItemNameLineNumber = 2 + TooltipTopLine = getglobal(TooltipName .. "TextLeft2") + if not TooltipTopLine then return end + return TooltipTopLine:GetText(), 2 + end + return ItemName, 1 +end + +-- Annotates zero or more lines in a tooltip with the name TooltipName, adding a (?) to the end +-- of each line specified by index in the list Lines. +-- Returns true if any lines were annotated. +function PawnAnnotateTooltipLines(TooltipName, Lines) + if not Lines then return false end + + local Annotated = false + local Tooltip = getglobal(TooltipName) + local LineCount = Tooltip:NumLines() + for i = 2, LineCount do + local LeftLine = getglobal(TooltipName .. "TextLeft" .. i) + if LeftLine then + local LeftLineText = LeftLine:GetText() + if Lines[LeftLineText] then + -- Getting the line text can fail in the following scenario, observable with MobInfo-2: + -- 1. Other mod modifies a tooltip to include unrecognized text. + -- 2. Pawn reads the tooltip, noting those unrecognized lines and remembering them so that they + -- can get marked with (?) later. + -- 3. Something causes the tooltip to be refreshed. For example, picking up the item. All customizations + -- by Pawn and other mods are lost. + -- 4. Pawn re-annotates the tooltip with (?) before the other mod has added the lines that are supposed + -- to get the (?). + -- In this case, we just ignore the problem and leave off the (?), since we can't really come back later. + LeftLine:SetText(LeftLineText .. PawnTooltipAnnotation) + Annotated = true + end + end + end + return Annotated +end + +-- Adds an amount of one stat to a table of stats, increasing the value if +-- it's already there, or adding it if it isn't. +function PawnAddStatToTable(Stats, Stat, Amount) + if not Amount or Amount == 0 then return end + if Stats[Stat] then + Stats[Stat] = Stats[Stat] + Amount + else + Stats[Stat] = Amount + end +end + +-- Adds the contents of one stat table to another. +function PawnAddStatsToTable(Dest, Source) + if not Dest then + VgerCore.Fail("PawnAddStatsToTable requires a destination table!") + return + end + if not Source then return end + for Stat, Quantity in pairs(Source) do + PawnAddStatToTable(Dest, Stat, Quantity) + end +end + +-- Looks for the first regular expression in a given table that matches the given string. +-- Parameters: String, RegexTable +-- String: The string to look for. +-- RegexTable: The table of regular expressions to look through. +-- Return value: Props, Matches +-- Props: The row from the table with a matching regex. +-- Matches: The array of captured matches. +-- Returns nil, nil if no matches were found. +-- Returns {}, {} if the string was ignored. +function PawnFindStringInRegexTable(String, RegexTable) + if (String == nil) or (String == "") or (String == " ") then return {}, {} end + for _, Entry in pairs(RegexTable) do + local StartPos, EndPos, m1, m2, m3, m4, m5 = strfind(String, Entry[1]) + if StartPos then return Entry, { m1, m2, m3, m4, m5 } end + end + return nil, nil +end + +-- Calculates the value of an item. +-- Parameters: Item, SocketBonus, ScaleName, DebugMessages +-- Item: Item stats in the format returned by GetStatsFromTooltip. +-- SocketBonus: Socket bonus stats in the format returned by GetStatsFromTooltip. +-- DebugMessages: If true, debug messages will be shown if appropriate. +-- NoNormalization: If true, the user's normalization factor will be ignored. +-- Returns: Value, ShouldUseRed, ShouldUseYellow, ShouldUseBlue +-- Value: The numeric value of an item based on the given scale values. (example: 21.75) +-- ShouldUseRed: If true, the player should socket this item with red gems. +-- ShouldUseYellow: If true, the player should socket this item with yellow gems. +-- ShouldUseBlue: If true, the player should socket this item with blue gems. +function PawnGetItemValue(Item, SocketBonus, ScaleName, DebugMessages, NoNormalization) + -- If either the item or scale is empty, exit now. + if (not Item) or (not ScaleName) then return end + local ScaleOptions = PawnCommon.Scales[ScaleName] + if not ScaleOptions then return end + ScaleValues = ScaleOptions.Values + if not ScaleValues then return end + + -- Calculate the value. + local Total = 0 + local ThisValue, Stat, Quantity + for Stat, Quantity in pairs(Item) do + ThisValue = ScaleValues[Stat] + -- Colored sockets are considered separately. + if Stat ~= "RedSocket" and Stat ~= "YellowSocket" and Stat ~= "BlueSocket" then + if ThisValue then + -- This stat has a value; add it to the running total. + if ScaleValues.SpeedBaseline and ( + Stat == "Speed" or + Stat == "MeleeSpeed" or + Stat == "MainHandSpeed" or + Stat == "OffHandSpeed" or + Stat == "OneHandSpeed" or + Stat == "TwoHandSpeed" or + Stat == "RangedSpeed" + ) then + -- Speed is a special case; subtract SpeedBaseline from the speed value. + Quantity = Quantity - ScaleValues.SpeedBaseline + end + Total = Total + ThisValue * Quantity + if DebugMessages then PawnDebugMessage(format(PawnLocal.ValueCalculationMessage, Quantity, Stat, ThisValue, Quantity * ThisValue)) end + else + -- This stat doesn't have a value set; display a warning. + if DebugMessages then PawnDebugMessage(VgerCore.Color.Blue .. format(PawnLocal.NoValueMessage, Stat)) end + end + end + end + + -- Decide what to do with socket bonuses. + local BestGemRed, BestGemYellow, BestGemBlue = false, false, false + if SocketBonus then + -- Start by counting the sockets; if there are no sockets, we can quit. + local TotalColoredSockets = 0 + if Item["RedSocket"] then TotalColoredSockets = TotalColoredSockets + Item["RedSocket"] end + if Item["YellowSocket"] then TotalColoredSockets = TotalColoredSockets + Item["YellowSocket"] end + if Item["BlueSocket"] then TotalColoredSockets = TotalColoredSockets + Item["BlueSocket"] end + if TotalColoredSockets > 0 then + -- Find the value of the sockets if they are socketed properly. + if DebugMessages then PawnDebugMessage(PawnLocal.SocketBonusValueCalculationMessage) end + local ProperSocketValue = 0 + Stat = "RedSocket" Quantity = Item[Stat] ThisValue = ScaleValues[Stat] + if Quantity and ThisValue then + ProperSocketValue = ProperSocketValue + Quantity * ThisValue + if DebugMessages then PawnDebugMessage(format(PawnLocal.ValueCalculationMessage, Quantity, Stat, ThisValue, Quantity * ThisValue)) end + end + Stat = "YellowSocket" Quantity = Item[Stat] ThisValue = ScaleValues[Stat] + if Quantity and ThisValue then + ProperSocketValue = ProperSocketValue + Quantity * ThisValue + if DebugMessages then PawnDebugMessage(format(PawnLocal.ValueCalculationMessage, Quantity, Stat, ThisValue, Quantity * ThisValue)) end + end + Stat = "BlueSocket" Quantity = Item[Stat] ThisValue = ScaleValues[Stat] + if Quantity and ThisValue then + ProperSocketValue = ProperSocketValue + Quantity * ThisValue + if DebugMessages then PawnDebugMessage(format(PawnLocal.ValueCalculationMessage, Quantity, Stat, ThisValue, Quantity * ThisValue)) end + end + for Stat, Quantity in pairs(SocketBonus) do + ThisValue = ScaleValues[Stat] + if ThisValue then + ProperSocketValue = ProperSocketValue + ThisValue * Quantity + if DebugMessages then PawnDebugMessage(format(PawnLocal.ValueCalculationMessage, Quantity, Stat, ThisValue, Quantity * ThisValue)) end + end + end + -- Then, find the value of the sockets if they are socketed with the best gem, ignoring the socket bonus. + local BestGemValue = 0 + local BestGemName = "" + local MissocketedValue = 0 + if ScaleOptions.SmartGemSocketing then + BestGemRed, BestGemYellow, BestGemBlue, BestGemValue, BestGemName = PawnGetBestGemColorsForScale(ScaleName) + if BestGemValue and BestGemValue > 0 then MissocketedValue = TotalColoredSockets * BestGemValue end + end + -- So, which one should we use? + if MissocketedValue <= ProperSocketValue then + -- If it's not worthwhile to mis-socket, clear out the best-gem fields. + BestGemRed, BestGemYellow, BestGemBlue = false, false, false + end + if ScaleOptions.SmartGemSocketing and MissocketedValue > ProperSocketValue then + -- It's better to mis-socket and ignore the socket bonus. + if DebugMessages then PawnDebugMessage(format(PawnLocal.MissocketWorthwhileMessage, BestGemName)) end + Total = Total + MissocketedValue + if DebugMessages then PawnDebugMessage(format(PawnLocal.ValueCalculationMessage, TotalColoredSockets, BestGemName, BestGemValue, MissocketedValue)) end + else + -- It's better to socket this item normally. + Total = Total + ProperSocketValue + end + end + end + + -- Perform normalizations on the total if that option is enabled. + if (not NoNormalization) and PawnScaleTotals[ScaleName] then + if ScaleOptions.NormalizationFactor and ScaleOptions.NormalizationFactor > 0 then + Total = ScaleOptions.NormalizationFactor * Total / PawnScaleTotals[ScaleName] + if DebugMessages then PawnDebugMessage(format(PawnLocal.NormalizationMessage, PawnScaleTotals[ScaleName])) end + end + end + + if DebugMessages then PawnDebugMessage(format(PawnLocal.TotalValueMessage, Total)) end + + return Total, BestGemRed, BestGemYellow, BestGemBlue +end + +-- Finds which gem colors are best for a given scale. +-- Returns: BestGemRed, BestGemYellow, BestGemBlue, BestGemValue, BestGemString +function PawnGetBestGemColorsForScale(ScaleName) + local Best = PawnScaleBestGems[ScaleName] + if not Best then + VgerCore.Fail("The best gem colors for this scale should have already been calculated; we don't have any info on it.") + return + end + local BestGems = Best.BestGems + if not BestGems then + VgerCore.Fail("The list of best gems for this scale is missing, so we can't find which colors are best.") + return + end + + return BestGems.RedSocket, BestGems.YellowSocket, BestGems.BlueSocket, BestGems.Value, BestGems.String +end + +-- Given a scale name and a socket color (like RedSocket), return the name of the single best gem of that color, or the name of +-- the color if there's no single best gem. +function PawnGetBestSingleGemForScale(ScaleName, Color) + local GemName + local Gems = PawnScaleBestGems[ScaleName] + if Gems and Gems[Color] and #(Gems[Color]) == 1 then + -- There's exactly one best gem of this color, so return its name. + -- If it's in the Pawn cache, use its name from there. Otherwise, + -- return the color name; that's much more useful than (Gem 1234). + local Item = PawnGetItemData("item:" .. Gems[Color][1].ID) + if Item and Item.Name then + return Item.Name + end + end + + -- Otherwise, return the color name. + if Color == "RedSocket" then + return RED_GEM + elseif Color == "YellowSocket" then + return YELLOW_GEM + elseif Color == "BlueSocket" then + return BLUE_GEM + else + VgerCore.Fail("Improper color value passed to PawnGetBestSingleGemForScale.") + end +end + +-- Returns a string of gems and a number, such as "2 Runed Scarlet Ruby" or "3 Yellow or Blue". +function PawnGetGemListString(GemCount, UseRed, UseYellow, UseBlue, ScaleName) + if UseRed and UseYellow and UseBlue then + return format(PawnLocal.GemColorList3, GemCount) + elseif UseRed and UseYellow and not UseBlue then + return format(PawnLocal.GemColorList2, GemCount, RED_GEM, YELLOW_GEM) + elseif UseYellow and UseBlue and not UseRed then + return format(PawnLocal.GemColorList2, GemCount, YELLOW_GEM, BLUE_GEM) + elseif UseRed and UseBlue and not UseYellow then + return format(PawnLocal.GemColorList2, GemCount, RED_GEM, BLUE_GEM) + elseif UseRed then + return format(PawnLocal.GemColorList1, GemCount, PawnGetBestSingleGemForScale(ScaleName, "RedSocket")) + elseif UseYellow then + return format(PawnLocal.GemColorList1, GemCount, PawnGetBestSingleGemForScale(ScaleName, "YellowSocket")) + elseif UseBlue then + return format(PawnLocal.GemColorList1, GemCount, PawnGetBestSingleGemForScale(ScaleName, "BlueSocket")) + else + return format(PawnLocal.GemColorList3, GemCount) + end +end + +-- Returns the type of hyperlink passed in, or nil if it's not a hyperlink. +-- Possible values include: item, enchant, quest, spell +function PawnGetHyperlinkType(Hyperlink) + -- First, try colored links. + local _, _, LinkType = strfind(Hyperlink, "^|c%x%x%x%x%x%x%x%x|H(.-):") + if not LinkType then + -- Then, try links prepended with |H. (Outfitter does this.) + _, _, LinkType = strfind(Hyperlink, "^|H(.-):") + end + if not LinkType then + -- Then, try raw links. + _, _, LinkType = strfind(Hyperlink, "^(.-):") + end + return LinkType +end + +-- If the item link is of the clickable form, strip off the initial hyperlink portion. +function PawnStripLeftOfItemLink(ItemLink) + local _, _, InnerLink = strfind(ItemLink, "^|%x+|H(.+)") + if InnerLink then return InnerLink else return ItemLink end +end + +-- Extracts the item ID from an ItemLink string and returns it, or nil if unsuccessful. +function PawnGetItemIDFromLink(ItemLink) + local Pos, _, ItemID = strfind(PawnStripLeftOfItemLink(ItemLink), "^item:(%-?%d+):") + if Pos then return ItemID else return ItemLink end +end + +-- Returns a new item link that represents an unenchanted version of the original item link, or +-- nil if unsuccessful or the item is not enchanted. +function PawnUnenchantItemLink(ItemLink) + local TrimmedItemLink = PawnStripLeftOfItemLink(ItemLink) + local Pos, _, ItemID, EnchantID, GemID1, GemID2, GemID3, GemID4, SuffixID, MoreInfo, ViewAtLevel = strfind(TrimmedItemLink, "^item:(%-?%d+):(%d+):(%d+):(%d+):(%d+):(%d+):(%-?%d+):(%-?%d+):(%d+)") + if not Pos then + -- For now, accept item links that don't include ViewAtLevel, for compatibility with sites such as Wowhead. + Pos, _, ItemID, EnchantID, GemID1, GemID2, GemID3, GemID4, SuffixID, MoreInfo = strfind(TrimmedItemLink, "^item:(%-?%d+):(%d+):(%d+):(%d+):(%d+):(%d+):(%-?%d+):(%-?%d+)") + if not Pos then + -- Check for a very simple item link with only an ID. + Pos, _, ItemID = strfind(TrimmedItemLink, "^item:(%-?%d+)") + if Pos then + -- This simple item is not enchanted. Return nil. + return nil + end + end + ViewAtLevel = "0" + end + if Pos then + if EnchantID ~= "0" or GemID1 ~= "0" or GemID2 ~= "0" or GemID3 ~= "0" or GemID4 ~= "0" then + -- This item is enchanted. Return a new link. + return "item:" .. ItemID .. ":0:0:0:0:0:" .. SuffixID .. ":" .. MoreInfo .. ":" .. ViewAtLevel + else + -- This item is not enchanted. Return nil. + return nil + end + else + -- We couldn't parse this item link. Return nil. + VgerCore.Fail("Could not parse the item link: " .. PawnEscapeString(ItemLink)) + return nil + end +end + +-- Returns a nice-looking string that shows the item IDs for an item, its enchantments, and its gems. +function PawnGetItemIDsForDisplay(ItemLink) + local Pos, _, ItemID, EnchantID, GemID1, GemID2, GemID3, GemID4, SuffixID, MoreInfo = strfind(ItemLink, "^|%x+|Hitem:(%-?%d+):(%d+):(%d+):(%d+):(%d+):(%d+):(%-?%d+):(%-?%d+)") + if not Pos then return end + -- Figure out what the LAST enchantment or gem is. + local LastGemSlot = -1 + if EnchantID ~= "0" then LastGemSlot = 0 end + if GemID1 ~= "0" then LastGemSlot = 1 end + if GemID2 ~= "0" then LastGemSlot = 2 end + if GemID3 ~= "0" then LastGemSlot = 3 end + if GemID4 ~= "0" then LastGemSlot = 4 end + -- Then, build a string. + if LastGemSlot >= 0 then + local Display = ItemID .. VgerCore.Color.Silver .. ":" .. EnchantID + if LastGemSlot >= 1 then Display = Display .. ":" .. GemID1 end + if LastGemSlot >= 2 then Display = Display .. ":" .. GemID2 end + if LastGemSlot >= 3 then Display = Display .. ":" .. GemID3 end + if LastGemSlot >= 4 then Display = Display .. ":" .. GemID4 end + return Display + else + -- If there are no enchantments or gems, just return the ID. + return ItemID + end +end + +-- Reads a Pawn scale tag, and breaks it into parts. +-- Parameters: ScaleTag +-- ScaleTag: A Pawn scale tag. Example: '(Pawn:v1:"Healbot":Stamina=1,Intellect=1.24)' +-- Return value: Name, Values; or nil if unsuccessful, or if the version number is too high. +-- Name: The scale name. +-- Values: A table of scale stats and values. Example: {["Stamina"] = 1, ["Intellect"] = 1.24} +function PawnParseScaleTag(ScaleTag) + -- Read the scale and perform basic validation. + local Pos, _, Version, Name, ValuesString = strfind(ScaleTag, "^%s*%(%s*Pawn%s*:%s*v(%d+)%s*:%s*\"([^\"]+)\"%s*:%s*(.+)%s*%)%s*$") + Version = tonumber(Version) + if (not Pos) or (not Version) or (not Name) or (Name == "") or (not ValuesString) or (ValuesString == "") then return end + if Version > PawnCurrentScaleVersion then return end + + -- Now, parse the values string for stat names and values. + local Values = {} + local function SplitStatValuePair(Pair) + local Pos, _, Stat, Value = strfind(Pair, "^%s*([%a%d]+)%s*=%s*(%-?[%d%.]+)%s*,$") + Value = tonumber(Value) + if Pos and Stat and (Stat ~= "") and Value then + Values[Stat] = Value + end + end + gsub(ValuesString .. ",", "[^,]*,", SplitStatValuePair) + + -- Looks like everything worked. + return Name, Values +end + +-- Escapes a string so that it can be more easily printed. +function PawnEscapeString(String) + return gsub(gsub(gsub(String, "\r", "\\r"), "\n", "\\n"), "|", "||") +end + +-- Corrects errors in scales: either human errors, or to correct for bugs in current or past versions of Pawn. +function PawnCorrectScaleErrors(ScaleName) + local ThisScaleOptions = PawnCommon.Scales[ScaleName] + if not ThisScaleOptions then return end + local ThisScale = ThisScaleOptions.Values + if not ThisScale then + ThisScale = { } + ThisScaleOptions.Values = ThisScale + end + + -- Pawn 1.3 adds per-character options to each scale. + if ThisScaleOptions.PerCharacterOptions == nil then ThisScaleOptions.PerCharacterOptions = {} end + if ThisScaleOptions.PerCharacterOptions[PawnPlayerFullName] == nil then ThisScaleOptions.PerCharacterOptions[PawnPlayerFullName] = {} end + + -- Pawn 1.0.1 adds a per-scale setting for smart gem socketing that defaults to on. + -- Pawn 1.2 adds another setting for meta gems that defaults to whatever the colored gem setting was, or on. + if ThisScaleOptions.SmartGemSocketing == nil then ThisScaleOptions.SmartGemSocketing = true end + if ThisScaleOptions.GemQualityLevel == nil then ThisScaleOptions.GemQualityLevel = PawnDefaultGemQualityLevel end + if ThisScaleOptions.SmartMetaGemSocketing == nil then ThisScaleOptions.SmartMetaGemSocketing = ThisScaleOptions.SmartGemSocketing end + if ThisScaleOptions.MetaGemQualityLevel == nil then ThisScaleOptions.MetaGemQualityLevel = PawnDefaultMetaGemQualityLevel end + + -- Some versions of Pawn call resilience rating Resilience and some call it ResilienceRating. + PawnReplaceStat(ThisScale, "Resilience", "ResilienceRating") + + -- Early versions of Pawn 0.7.x had a typo in the configuration UI so that none of the special DPS stats worked. + PawnReplaceStat(ThisScale, "MeleeDPS", "MeleeDps") + PawnReplaceStat(ThisScale, "RangedDPS", "RangedDps") + PawnReplaceStat(ThisScale, "MainHandDPS", "MainHandDps") + PawnReplaceStat(ThisScale, "OffHandDPS", "OffHandDps") + PawnReplaceStat(ThisScale, "OneHandDPS", "OneHandDps") + PawnReplaceStat(ThisScale, "TwoHandDPS", "TwoHandDps") + + -- Remove spell damage and healing stats from the scale, and replace with spell power if it doesn't already have a stat. + if not ThisScale.SpellPower and (ThisScale.SpellDamage or ThisScale.Healing) then + local Healing = ThisScale.Healing + if not Healing then Healing = 0 end + local SpellDamage = ThisScale.SpellDamage + if not SpellDamage then SpellDamage = 0 end + ThisScale.SpellPower = SpellDamage + (13 * Healing / 7) + if ThisScale.SpellDamage and ThisScale.SpellDamage > ThisScale.SpellPower then ThisScale.SpellPower = ThisScale.SpellDamage end + if ThisScale.SpellPower <= 0 then ThisScale.SpellPower = nil end + end + ThisScale.SpellDamage = nil + ThisScale.Healing = nil + + -- Combine melee/ranged/spell hit, crit, and haste ratings into the hybrid stats that work for all. + PawnCombineStats(ThisScale, "HitRating", "SpellHitRating") + PawnCombineStats(ThisScale, "CritRating", "SpellCritRating") + PawnCombineStats(ThisScale, "HasteRating", "SpellHasteRating") + + -- Colorless sockets are no longer valued by Pawn. + ThisScale.ColorlessSocket = nil +end + +-- Replaces one incorrect stat with a correct stat. +function PawnReplaceStat(ThisScale, OldStat, NewStat) + if ThisScale[OldStat] then + if not ThisScale[NewStat] then ThisScale[NewStat] = ThisScale[OldStat] end + ThisScale[OldStat] = nil + end +end + +-- Combines two stats into one. For example, combines HitRating and SpellHitRating, putting the larger of the +-- two values in HitRating. +function PawnCombineStats(ThisScale, PrimaryStat, SecondaryStat) + if ThisScale[SecondaryStat] then + if ThisScale[PrimaryStat] and ThisScale[PrimaryStat] > ThisScale[SecondaryStat] then + -- If the primary stat is larger, do nothing. + else + -- If the secondary stat is larger, increase the value of the primary to the secondary. + ThisScale[PrimaryStat] = ThisScale[SecondaryStat] + end + -- Regardless, clear out the secondary stat afterward. + ThisScale[SecondaryStat] = nil + end +end + +-- Causes the Pawn private tooltip to be shown when next hovering an item. +--function PawnTestShowPrivateTooltip() +-- PawnPrivateTooltip:SetOwner(UIParent, "ANCHOR_TOPRIGHT") +--end + +-- Hides the Pawn private tooltip (normal). +--function PawnTestHidePrivateTooltip() +-- PawnPrivateTooltip:SetOwner(UIParent, "ANCHOR_NONE") +-- PawnPrivateTooltip:Hide() +--end + +-- Depending on the user's current tooltip icon settings, show and hide icons as appropriate. +function PawnToggleTooltipIcons() + PawnAttachIconToTooltip(ItemRefTooltip) + PawnAttachIconToTooltip(ShoppingTooltip1, true) + PawnAttachIconToTooltip(ShoppingTooltip2, true) + + -- MultiTips compatibility + PawnAttachIconToTooltip(ItemRefTooltip2) + PawnAttachIconToTooltip(ItemRefTooltip3) + PawnAttachIconToTooltip(ItemRefTooltip4) + PawnAttachIconToTooltip(ItemRefTooltip5) + + -- EquipCompare compatibility + PawnAttachIconToTooltip(ComparisonTooltip1, true) + PawnAttachIconToTooltip(ComparisonTooltip2, true) +end + +-- If tooltip icons are enabled, attaches an icon to the upper-left corner of a tooltip. Otherwise, hides +-- any icons attached to that tooltip if they exist. +-- Optionally, the caller may include an item link so this function doesn't need to get one. +function PawnAttachIconToTooltip(Tooltip, AttachAbove, ItemLink) + -- If the tooltip doesn't exist, exit now. + if not Tooltip then return end + + -- Find the right texture to use, but skip all this if the user has icons turned off. + local TextureName + if PawnCommon.ShowTooltipIcons then + -- Don't retrieve an item link if one was passed in. + if not ItemLink then + _, ItemLink = Tooltip:GetItem() + end + if ItemLink then + TextureName = GetItemIcon(ItemLink) + end + end + + -- Now, if we don't have a texture to use, or icons are disabled, hide this icon if it's visible + -- and then exit. + local IconFrame = Tooltip.PawnIconFrame + if not TextureName then + if IconFrame then + IconFrame:Hide() + IconFrame.PawnIconTexture = nil + Tooltip.PawnIconFrame = nil + end + return + end + + -- Create the icon's frame if it doesn't already exist. + if not IconFrame then + IconFrame = CreateFrame("Frame", nil, Tooltip) + Tooltip.PawnIconFrame = IconFrame + IconFrame:SetWidth(37) + IconFrame:SetHeight(37) + + local IconTexture = IconFrame:CreateTexture(nil, "BACKGROUND") + IconTexture:SetTexture(TextureName) + IconTexture:SetAllPoints(IconFrame) + IconFrame.PawnIconTexture = IconTexture + else + -- If the icon already existed, then we just need to update the texture. + IconFrame.PawnIconTexture:SetTexture(TextureName) + end + + -- Attach the icon frame and show it. + if AttachAbove then + IconFrame:SetPoint("BOTTOMLEFT", Tooltip, "TOPLEFT", 2, -2) + else + IconFrame:SetPoint("TOPRIGHT", Tooltip, "TOPLEFT", 2, -2) + end + IconFrame:Show() + + return IconFrame +end + +-- Hides any icons on a tooltip, if there are any. +function PawnHideTooltipIcon(TooltipName) + -- Find the tooltip. If it doesn't exist, we can skip out now. + local Tooltip = getglobal(TooltipName) + if not Tooltip then return end + + -- Is there an icon on it? If not, exit. + local IconFrame = Tooltip.PawnIconFrame + if not IconFrame then return end + + -- Hide the icon frame if it's there, and remove the reference to it so it can be garbage-collected. + IconFrame:Hide() + IconFrame.PawnIconTexture = nil + Tooltip.PawnIconFrame = nil +end + +-- Comparer function for use in sort that sorts strings alphabetically, ignoring case, and also ignoring a +-- 10-character color format at the beginning of the string. +function PawnColoredStringCompare(a, b) + return strlower(strsub(a, 11)) < strlower(strsub(b, 11)) +end + +-- Comparer function for use in sort that sorts sub-tables alphabetically by the localized name in the sub-table, ignoring case. +function PawnItemValueCompare(a, b) + return strlower(a[7]) < strlower(b[7]) +end + +-- Returns a string representation of a number to a maximum of one decimal place. If the number passed is nil, nil is returned. +function PawnFormatShortDecimal(Number) + if Number == nil then + return nil + elseif math.abs(Number - floor(Number)) < .0001 then + return tostring(Number) + else + return format("%.1f", Number) + end +end + +-- Takes an ItemEquipLoc and returns one or two slot IDs where that item type can be equipped. +-- Bags are not supported. +function PawnGetSlotsForItemType(ItemEquipLoc) + if (not ItemEquipLoc) or (ItemEquipLoc == "") then return end + return PawnItemEquipLocToSlot1[ItemEquipLoc], PawnItemEquipLocToSlot2[ItemEquipLoc] +end + +-- Takes an item level and a rarity, and returns a roughly equivalent item level if that item were an epic. +-- This formula is based on the fact that when considering the scaling health of Ulduar vehicles, dropping +-- 13 levels on an epic alters the vehicle's health the same as replacing an epic with a blue of the same level. +-- This results in the .935 value; other values are simply assumptions. +function PawnGetEpicEquivalentItemLevel(ItemLevel, Rarity) + if not ItemLevel or ItemLevel <= 1 then return 0 end + if Rarity < 2 or Rarity > 5 then -- Common, poor, or heirloom + return 0 + elseif Rarity == 2 then -- Uncommon + return math.floor(ItemLevel * .87 + .05) + elseif Rarity == 3 then -- Rare + return math.floor(ItemLevel * .935 + .05) + elseif Rarity == 4 then -- Epic + return ItemLevel + elseif Rarity == 5 then -- Legendary + return math.floor(ItemLevel * 1.065 + .05) + end +end + +-- Given a weapon's DPS, returns the amount of feral attack power the weapon would grant a druid. +function PawnGetFeralAp(Dps) + if not Dps then return 0 end + local FeralAp = math.floor((Dps - 54.8) * 14) + if FeralAp < 0 then + return 0 + else + return FeralAp + end +end + +-- Finds the best gems for a particular scale in one or more colors. +-- Parameters: ScaleName, FindRed, FindYellow, FindBlue +-- ScaleName: The name of the scale for which to find gems. +-- FindRed: If true, consider red gems as a possibility. +-- FindYellow: If true, consider yellow gems as a possibility. +-- FindBlue: If true, consider blue gems as a possibility. +-- FindMeta: If true, consider meta gems only. Cannot be used with FindRed/Yellow/Blue. +-- Return value: Value, GemList +-- Value: The value of the best gem or gems for the chosen colors. +-- GemList: A table of gems of that value. Each item in the list is in the standard Pawn item table format, and +-- the list is sorted alphabetically by name. +function PawnFindBestGems(ScaleName, FindRed, FindYellow, FindBlue, FindMeta) + local BestScore = 0 + local BestItems = { } + + if (not FindRed) and (not FindYellow) and (not FindBlue) and (not FindMeta) then + VgerCore.Fail("PawnFindBestGems must be given a color of gem to search for.") + return + elseif (FindRed or FindYellow or FindBlue) and FindMeta then + VgerCore.Fail("PawnFindBestGems cannot find both meta gems and colored gems simultaneously.") + return + end + + -- Go through the list of gems, checking each item that matches one of the find criteria. + local GemTable, GemData, ThisGem + if FindMeta then + GemTable = PawnMetaGemQualityTables[PawnCommon.Scales[ScaleName].MetaGemQualityLevel] + else + GemTable = PawnGemQualityTables[PawnCommon.Scales[ScaleName].GemQualityLevel] + end + if not GemTable then + VgerCore.Fail("Couldn't find gems for this scale because no gem quality level was selected.") + return + end + for _, GemData in pairs(GemTable) do + if FindMeta or (FindRed and GemData[2]) or (FindYellow and GemData[3]) or (FindBlue and GemData[4]) then + -- This gem is of a color we care about, so let's check it out. + ThisGem = PawnGetGemData(GemData) + if ThisGem then + local _, ThisValue = PawnGetSingleValueFromItem(ThisGem, ScaleName) + if ThisValue and ThisValue > BestScore then + -- This gem is better than any we've found so far. + BestScore = ThisValue + wipe(BestItems) + tinsert(BestItems, ThisGem) + elseif ThisValue and ThisValue == BestScore then + -- This gem is tied with the best gems we've found so far. + tinsert(BestItems, ThisGem) + end + else + VgerCore.Fail("Failed to get information about gem " .. GemData[1]) + end + end + end + + -- Now we have a list of the best gems. Sort them alphabetically. + sort(BestItems, PawnItemComparer) + + -- In debug mode, display them. + if PawnCommon.Debug then + local Header = "=== Best " + if FindRed then Header = Header .. "Red " end + if FindYellow then Header = Header .. "Yellow " end + if FindBlue then Header = Header .. "Blue " end + if FindMeta then Header = Header .. "Meta " end + Header = Header .. "gems for " .. PawnGetScaleLocalizedName(ScaleName) .. ": ===" + VgerCore.Message(Header) + for _, ThisGem in pairs(BestItems) do + VgerCore.Message(" " .. ThisGem.Link) + end + VgerCore.Message(" --> Score: " .. tostring(BestScore)) + end + + -- Return the value and list of gems. + return BestScore, BestItems + +end + +-- Refreshes a cached item with new information if available. Currently meant only for refreshing +-- best-gem item data, which often doesn't have a name or texture, with that information. +-- Returns true if it did anything. +function PawnRefreshCachedItem(Item) + if not Item then + VgerCore.Fail("PawnRefreshCachedItem requires an item table.") + return false + end + + -- Request the new information. + local ItemName, _, _, _, _, _, _, _, _, ItemTexture = GetItemInfo(Item.ID) + if not ItemName then + -- The client doesn't have any further information on this item yet, so bail out. + return false + end + + -- Save this new information into the cached item record. + Item.Name = ItemName + Item.Texture = ItemTexture + return true +end + +-- Comparer function for use in sort that sorts items by their name. +function PawnItemComparer(a, b) + if not b then return a end + if not a then return b end + return a.Name < b.Name +end + +------------------------------------------------------------ +-- Pawn API +------------------------------------------------------------ + +-- Resets all custom Pawn scales. +function PawnResetScales() + return PawnResetScalesCore(true, false) +end + +-- Resets all read-only scales from scale providers. +function PawnResetProviderScales() + return PawnResetScalesCore(false, true) +end + +-- Common code for scale resetting functions. +function PawnResetScalesCore(ResetCustomScales, ResetProviderScales) + local ScaleName, Scale + local ScalesToRemove = {} + for ScaleName, Scale in pairs(PawnCommon.Scales) do + if (ResetProviderScales and Scale.Provider) or (ResetCustomScales and ScaleProvider == nil) then tinsert(ScalesToRemove, ScaleName) end + end + for _, ScaleName in pairs(ScalesToRemove) do + PawnCommon.Scales[ScaleName] = nil + end + PawnResetTooltips() + return true +end + +-- Adds a new scale with no values. Returns true if successful. +function PawnAddEmptyScale(ScaleName) + if (not ScaleName) or (ScaleName == "") then + VgerCore.Fail("ScaleName cannot be empty. Usage: PawnAddEmptyScale(\"ScaleName\")") + return false + elseif PawnCommon.Scales[ScaleName] then + VgerCore.Fail("ScaleName cannot be the name of an existing scale, and is case-sensitive.") + return false + end + + PawnCommon.Scales[ScaleName] = PawnGetEmptyScale() + PawnCommon.Scales[ScaleName].PerCharacterOptions[PawnPlayerFullName] = { } + PawnCommon.Scales[ScaleName].PerCharacterOptions[PawnPlayerFullName].Visible = true + PawnRecalculateScaleTotal(ScaleName) + return true +end + +-- Adds a new scale with the default values. Returns true if successful. +function PawnAddDefaultScale(ScaleName) + if (not ScaleName) or (ScaleName == "") then + VgerCore.Fail("ScaleName cannot be empty. Usage: PawnAddDefaultScale(\"ScaleName\")") + return false + elseif PawnCommon.Scales[ScaleName] then + VgerCore.Fail("ScaleName cannot be the name of an existing scale, and is case-sensitive.") + return false + end + + PawnCommon.Scales[ScaleName] = PawnGetDefaultScale() + PawnCommon.Scales[ScaleName].PerCharacterOptions[PawnPlayerFullName] = { } + PawnCommon.Scales[ScaleName].PerCharacterOptions[PawnPlayerFullName].Visible = true + PawnRecalculateScaleTotal(ScaleName) + PawnResetTooltips() + return true +end + +-- Deletes a scale. Returns true if successful. +function PawnDeleteScale(ScaleName) + if (not ScaleName) or (ScaleName == "") then + VgerCore.Fail("ScaleName cannot be empty. Usage: PawnDeleteScale(\"ScaleName\")") + return false + elseif not PawnCommon.Scales[ScaleName] then + VgerCore.Fail("ScaleName must be the name of an existing scale, and is case-sensitive.") + return false + elseif PawnScaleIsReadOnly(ScaleName) then + VgerCore.Fail("ScaleName cannot be the name of a read-only scale.") + return false + end + + PawnCommon.Scales[ScaleName] = nil + PawnRecalculateScaleTotal(ScaleName) + PawnResetTooltips() + return true +end + +-- Renames an existing scale. Returns true if successful. +function PawnRenameScale(OldScaleName, NewScaleName) + if (not OldScaleName) or (OldScaleName == "") or (not NewScaleName) or (NewScaleName == "") then + VgerCore.Fail("OldScaleName and NewScaleName cannot be empty. Usage: PawnRenameScale(\"OldScaleName\", \"NewScaleName\")") + return false + elseif OldScaleName == NewScaleName then + VgerCore.Fail("OldScaleName and NewScaleName cannot be the same.") + return false + elseif not PawnCommon.Scales[OldScaleName] then + VgerCore.Fail("OldScaleName must be the name of an existing scale, and is case-sensitive.") + return false + elseif PawnCommon.Scales[NewScaleName] then + VgerCore.Fail("NewScaleName cannot be the name of an existing scale, and is case-sensitive.") + return false + elseif PawnScaleIsReadOnly(ScaleName) then + VgerCore.Fail("ScaleName cannot be the name of a read-only scale.") + return false + end + + PawnCommon.Scales[NewScaleName] = PawnCommon.Scales[OldScaleName] + PawnCommon.Scales[OldScaleName] = nil + PawnRecalculateScaleTotal(OldScaleName) + PawnRecalculateScaleTotal(NewScaleName) + PawnResetTooltips() + return true +end + +-- Creates a new scale based on an old one. Returns true if successful. +function PawnDuplicateScale(OldScaleName, NewScaleName) + if (not OldScaleName) or (OldScaleName == "") or (not NewScaleName) or (NewScaleName == "") then + VgerCore.Fail("OldScaleName and NewScaleName cannot be empty. Usage: PawnDuplicateScale(\"OldScaleName\", \"NewScaleName\")") + return false + elseif OldScaleName == NewScaleName then + VgerCore.Fail("OldScaleName and NewScaleName cannot be the same.") + return false + elseif not PawnCommon.Scales[OldScaleName] then + VgerCore.Fail("OldScaleName must be the name of an existing scale, and is case-sensitive.") + return false + elseif PawnCommon.Scales[NewScaleName] then + VgerCore.Fail("NewScaleName cannot be the name of an existing scale, and is case-sensitive.") + return false + end + + -- Create the copy. + PawnCommon.Scales[NewScaleName] = {} + PawnCommon.Scales[NewScaleName].Color = PawnCommon.Scales[OldScaleName].Color + PawnCommon.Scales[NewScaleName].SmartGemSocketing = PawnCommon.Scales[OldScaleName].SmartGemSocketing + PawnCommon.Scales[NewScaleName].GemQualityLevel = PawnCommon.Scales[OldScaleName].GemQualityLevel + PawnCommon.Scales[NewScaleName].SmartMetaGemSocketing = PawnCommon.Scales[OldScaleName].SmartMetaGemSocketing + PawnCommon.Scales[NewScaleName].MetaGemQualityLevel = PawnCommon.Scales[OldScaleName].MetaGemQualityLevel + PawnCommon.Scales[NewScaleName].NormalizationFactor = PawnCommon.Scales[OldScaleName].NormalizationFactor + PawnCommon.Scales[NewScaleName].PerCharacterOptions = {} + PawnCommon.Scales[NewScaleName].PerCharacterOptions[PawnPlayerFullName] = {} + PawnCommon.Scales[NewScaleName].PerCharacterOptions[PawnPlayerFullName].Visible = true + PawnCommon.Scales[NewScaleName].Values = {} + local NewScale = PawnCommon.Scales[NewScaleName].Values + for StatName, Value in pairs(PawnCommon.Scales[OldScaleName].Values) do + NewScale[StatName] = Value + end + + -- Do post-copy calculations, and we're done. + PawnRecalculateScaleTotal(NewScaleName) + PawnResetTooltips() + return true +end + +-- Returns the value of one stat in a scale, or nil if unsuccessful. +function PawnGetStatValue(ScaleName, StatName) + if (not ScaleName) or (ScaleName == "") or (not StatName) or (StatName == "") then + VgerCore.Fail("ScaleName and StatName cannot be empty. Usage: x = PawnGetStatValue(\"ScaleName\", \"StatName\")") + return nil + elseif not PawnCommon.Scales[ScaleName] then + VgerCore.Fail("ScaleName must be the name of an existing scale, and is case-sensitive.") + return nil + end + + return PawnCommon.Scales[ScaleName].Values[StatName] +end + +-- Returns true if a particular scale exists, or false if not. +function PawnDoesScaleExist(ScaleName) + if (not ScaleName) or (ScaleName == "") then + VgerCore.Fail("ScaleName cannot be empty. Usage: x = PawnDoesScaleExist(\"ScaleName\")") + return false + end + + if PawnCommon.Scales[ScaleName] then + return true + else + return false + end +end + +-- Returns a table of all stats and their values for a particular scale, or nil if unsuccessful. +-- This returns the actual internal table of stat values, so be careful not to modify it! +function PawnGetAllStatValues(ScaleName) + if (not ScaleName) or (ScaleName == "") then + VgerCore.Fail("ScaleName cannot be empty. Usage: x = PawnGetAllStatValues(\"ScaleName\")") + return nil + elseif not PawnCommon.Scales[ScaleName] then + VgerCore.Fail("ScaleName must be the name of an existing scale, and is case-sensitive.") + return nil + end + + --local TableCopy = {} + --for StatName, Value in pairs(PawnCommon.Scales[ScaleName].Values) do + -- TableCopy[StatName] = Value + --end + --return TableCopy + return PawnCommon.Scales[ScaleName].Values +end + +-- Sets the value of one stat in a scale. Returns true if successful. +-- Use 0 or nil as the Value to remove a stat from the scale. +function PawnSetStatValue(ScaleName, StatName, Value) + if (not ScaleName) or (ScaleName == "") or (not StatName) or (StatName == "") then + VgerCore.Fail("ScaleName and StatName cannot be empty. Usage: PawnSetStatValue(\"ScaleName\", \"StatName\", Value)") + return false + elseif not PawnCommon.Scales[ScaleName] then + VgerCore.Fail("ScaleName must be the name of an existing scale, and is case-sensitive.") + return false + elseif PawnScaleIsReadOnly(ScaleName) then + VgerCore.Fail("ScaleName cannot be the name of a read-only scale.") + return false + end + + if Value == 0 then Value = nil end + PawnCommon.Scales[ScaleName].Values[StatName] = Value + PawnRecalculateScaleTotal(ScaleName) -- also recalculates socket values + PawnResetTooltips() + return true +end + +-- Returns a table of all Pawn scale names. Returns all custom scales not from scale providers, whether visible or not. +-- For more information in one big table, use PawnGetAllScalesEx. This method is provided here for backwards compatibility. +-- DEPRECATED +function PawnGetAllScales() + local TableCopy = {} + local ScaleName, Scale + for ScaleName, Scale in pairs(PawnCommon.Scales) do + if (not Scale.Provider) or (Scale.ProviderActive) then + -- Don't include scales from a provider that isn't active any longer. (Abandoned provider scales) + tinsert(TableCopy, ScaleName) + end + end + sort(TableCopy, VgerCore.CaseInsensitiveComparer) + return TableCopy +end + +-- Return a sorted table of all Pawn scale names and some data about each scale. +-- Each element in the table returned is a table with these values: +-- { Name, LocalizedName, Header, IsVisible } +-- Name: The internal name of the scale. Examples: "My custom scale"; "\"Wowhead\":DruidFeralDps" +-- LocalizedName: The display name of the scale. Examples: "My custom scale"; "Druid feral DPS" +-- Header: The header text to display above this scale. Examples: "Vger's scales"; "Wowhead scales" +-- IsVisible: Whether or not this scale is visible for the current character. Examples: true, true +-- IsProvider: Whether or not this scale comes from a scale provider. Examples: true, false +function PawnGetAllScalesEx() + local TableCopy = {} + local ScaleName, Scale + local ActiveScalesHeader = format(PawnLocal.VisibleScalesHeader, UnitName("player")) + for ScaleName, Scale in pairs(PawnCommon.Scales) do + local IsVisible = PawnIsScaleVisible(ScaleName) + local ScaleData = + { + ["Name"] = ScaleName, + ["LocalizedName"] = Scale.LocalizedName or ScaleName, + ["IsVisible"] = IsVisible, + ["IsProvider"] = Scale.Provider ~= nil + } + if IsVisible then + ScaleData.Header = ActiveScalesHeader + elseif Scale.Provider and Scale.ProviderActive then + ScaleData.Header = PawnScaleProviders[Scale.Provider].Name + else + ScaleData.Header = PawnLocal.HiddenScalesHeader + end + if (not Scale.Provider) or (Scale.ProviderActive) then + -- Don't include scales from a provider that isn't active any longer. (Abandoned provider scales) + tinsert(TableCopy, ScaleData) + --else + -- VgerCore.Message("Not including " .. ScaleName .. " because it seems to be abandoned.") + end + end + sort(TableCopy, PawnGetAllScalesExComparer) + + return TableCopy +end + +-- Sort function used by PawnGetAllScalesEx. Returns true if a should sort before b. +function PawnGetAllScalesExComparer(a, b) + if not b then return a end + if not a then return b end + -- First, if one is visible and the other is not, then sort the visible ones first. + local AVisible = a.IsVisible + local BVisible = b.IsVisible + if AVisible and not BVisible then return true end + if BVisible and not AVisible then return false end + -- They're both the same visibility. Sort custom (non-provider) scales first. + local AProvider = a.IsProvider + local BProvider = b.IsProvider + if AProvider and not BProvider then return false end + if BProvider and not AProvider then return true end + -- If both scales are of the same class, then just sort by display name, case-insensitive. + return strlower(a.LocalizedName) < strlower(b.LocalizedName) +end + +-- Gets the preferred gem quality level for a scale. (See Gems.lua.) +function PawnGetGemQualityLevel(ScaleName) + if (not ScaleName) or (ScaleName == "") then + VgerCore.Fail("ScaleName cannot be empty. Usage: PawnGetGemQualityLevel(\"ScaleName\")") + return false + elseif not PawnCommon.Scales[ScaleName] then + VgerCore.Fail("ScaleName must be the name of an existing scale, and is case-sensitive.") + return false + end + + return PawnCommon.Scales[ScaleName].GemQualityLevel +end + +-- Sets the preferred gem quality level for a scale. (See Gems.lua.) +function PawnSetGemQualityLevel(ScaleName, QualityLevel) + if (not ScaleName) or (ScaleName == "") or (not QualityLevel) or (not PawnGemQualityTables[QualityLevel]) then + VgerCore.Fail("ScaleName and QualityLevel cannot be empty. Usage: PawnSetGemQualityLevel(\"ScaleName\", QualityLevel)") + return false + elseif not PawnCommon.Scales[ScaleName] then + VgerCore.Fail("ScaleName must be the name of an existing scale, and is case-sensitive.") + return false + end + + PawnCommon.Scales[ScaleName].GemQualityLevel = QualityLevel + PawnRecalculateScaleTotal(ScaleName) -- also recalculates socket values + PawnResetTooltips() + return true +end + +-- Creates a Pawn scale tag for a scale. +-- Parameters: ScaleName +-- ScaleName: The name of a Pawn scale. +-- Return value: ScaleTag, or nil if unsuccessful. +-- ScaleTag: A Pawn scale tag. Example: '( Pawn: v1: "Healbot": Stamina=1, Intellect=1.24 )' +function PawnGetScaleTag(ScaleName) + if (not ScaleName) or (ScaleName == "") then + VgerCore.Fail("ScaleName cannot be empty. Usage: PawnGetScaleTag(\"ScaleName\")") + return + elseif not PawnCommon.Scales[ScaleName] then + VgerCore.Fail("ScaleName must be the name of an existing scale, and is case-sensitive.") + return + elseif not PawnCommon.Scales[ScaleName].Values then + return + end + + -- Concatenate the stats. + local ScaleFriendlyName = PawnGetScaleLocalizedName(ScaleName) + local ScaleTag = "( Pawn: v" .. PawnCurrentScaleVersion .. ": \"" .. ScaleFriendlyName .. "\": " + local AddComma = false + local IncludeThis + local SmartGemSocketing = PawnCommon.Scales[ScaleName].SmartGemSocketing + local SmartMetaGemSocketing = PawnCommon.Scales[ScaleName].SmartMetaGemSocketing + for StatName, Value in pairs(PawnCommon.Scales[ScaleName].Values) do + local IncludeThis = (Value and Value ~= 0) + -- If smart gem socketing is enabled, don't include socket stats. + if IncludeThis and SmartGemSocketing and (StatName == "RedSocket" or StatName == "YellowSocket" or StatName == "BlueSocket") then IncludeThis = false end + if IncludeThis and SmartMetaGemSocketing and (StatName == "MetaSocket") then IncludeThis = false end + if IncludeThis then + if AddComma then ScaleTag = ScaleTag .. ", " end + ScaleTag = ScaleTag .. StatName .. "=" .. tostring(Value) + AddComma = true + end + end + -- Add gem quality levels. (Don't include meta gem quality levels right now, since the setting can't even be changed yet.) + if AddComma then ScaleTag = ScaleTag .. ", " end + ScaleTag = ScaleTag .. "GemQualityLevel=" .. tostring(PawnCommon.Scales[ScaleName].GemQualityLevel) + + ScaleTag = ScaleTag .. " )" + return ScaleTag +end + +-- Imports a Pawn scale tag, adding that scale to the current character. +-- Parameters: ScaleTag, Overwrite +-- ScaleTag: A Pawn scale tag to add. Example: '( Pawn: v1: "Healbot": Stamina=1, Intellect=1.24 )' +-- Overwrite: If true, this function will overwrite an existing scale with the same name. +-- Return value: Status, ScaleName +-- Status: One of the PawnImportScaleResult* constants. +-- ScaleName: The name of the Pawn scale specified by ScaleTag, or nil if ScaleTag could not be parsed. +function PawnImportScale(ScaleTag, Overwrite) + local ScaleName, Values = PawnParseScaleTag(ScaleTag) + if not ScaleName then + -- This tag couldn't be parsed. + return PawnImportScaleResultTagError + end + + local AlreadyExists = PawnCommon.Scales[ScaleName] ~= nil + if AlreadyExists and (PawnScaleIsReadOnly(ScaleName) or not Overwrite) then + -- A scale with this name already exists. You can't import a scale with the same name as an existing one, + -- unless you specify Overwrite = true. + return PawnImportScaleResultAlreadyExists, ScaleName + end + + -- Looks like everything's okay. Import the scale. If the scale already exists but Overwrite = true was passed, + -- don't change other options about this scale, such as the color. + if not AlreadyExists then + -- REVIEW: Shouldn't this really use the default new blank scale codepath? + PawnCommon.Scales[ScaleName] = { } + PawnCommon.Scales[ScaleName].PerCharacterOptions = { } + PawnCommon.Scales[ScaleName].PerCharacterOptions[PawnPlayerFullName] = { } + PawnCommon.Scales[ScaleName].PerCharacterOptions[PawnPlayerFullName].Visible = true + end + PawnCommon.Scales[ScaleName].Values = Values + PawnCorrectScaleErrors(ScaleName) + + -- Gem quality levels are included as if they're a stat, but they're not. Move them to a scale setting. (If this property + -- isn't set it will be added later.) + if Values.GemQualityLevel then + PawnCommon.Scales[ScaleName].GemQualityLevel = Values.GemQualityLevel + Values.GemQualityLevel = nil + end + if Values.MetaGemQualityLevel then + PawnCommon.Scales[ScaleName].MetaGemQualityLevel = Values.MetaGemQualityLevel + Values.MetaGemQualityLevel = nil + end + + -- Determine whether to automatically set socket values based on whether or not socket values were specified + -- in the scale. + if not AlreadyExists then + if (not Values.RedSocket) and (not Values.YellowSocket) and (not Values.BlueSocket) then + PawnCommon.Scales[ScaleName].SmartGemSocketing = true + end + if (not Values.MetaSocket) then + PawnCommon.Scales[ScaleName].SmartMetaGemSocketing = true + end + end + + PawnRecalculateScaleTotal(ScaleName) + PawnResetTooltips() + return PawnImportScaleResultSuccess, ScaleName +end + +-- Sets whether or not a scale is visible. If Visible is nil, it will be considered as false. +function PawnSetScaleVisible(ScaleName, Visible) + if (not ScaleName) or (ScaleName == "") then + VgerCore.Fail("ScaleName cannot be empty. Usage: PawnSetScaleVisible(\"ScaleName\", Visible)") + return nil + elseif not PawnCommon.Scales[ScaleName] then + VgerCore.Fail("ScaleName must be the name of an existing scale, and is case-sensitive.") + return nil + end + + local Scale = PawnCommon.Scales[ScaleName] + if Scale.PerCharacterOptions[PawnPlayerFullName].Visible ~= Visible then + Scale.PerCharacterOptions[PawnPlayerFullName].Visible = Visible + PawnResetTooltips() + end + return true +end + +-- Sets true if a given scale is visible in tooltips. +function PawnIsScaleVisible(ScaleName) + if (not ScaleName) or (ScaleName == "") then + VgerCore.Fail("ScaleName cannot be empty. Usage: x = PawnIsScaleVisible(\"ScaleName\")") + return nil + elseif not PawnCommon.Scales[ScaleName] then + VgerCore.Fail("ScaleName must be the name of an existing scale, and is case-sensitive.") + return nil + end + + local Scale = PawnCommon.Scales[ScaleName] + VgerCore.Assert(Scale.PerCharacterOptions ~= nil, "All per-character options for " .. ScaleName .. " were missing.") + VgerCore.Assert(Scale.PerCharacterOptions[PawnPlayerFullName] ~= nil, "Per-character options for this character (" .. PawnPlayerFullName .. ") and scale (" .. ScaleName .. ") were missing.") + return Scale.PerCharacterOptions[PawnPlayerFullName].Visible +end + +-- Gets the color of a scale in hex format. If the scale doesn't specify a color, the default is returned. +-- If Unenchanted is true, then the unenchanted color for the scale is returned. +function PawnGetScaleColor(ScaleName, Unenchanted) + if (not ScaleName) or (ScaleName == "") then + VgerCore.Fail("ScaleName cannot be empty. Usage: rrggbb = PawnGetScaleColor(\"ScaleName\", Unenchanted)") + return nil + end + local Scale = PawnCommon.Scales[ScaleName] + if not Scale then + VgerCore.Fail("ScaleName must be the name of an existing scale, and is case-sensitive.") + return nil + end + + if Unenchanted then + if Scale.UnenchantedColor and strlen(Scale.UnenchantedColor) == 6 then return "|cff" .. Scale.UnenchantedColor end + return VgerCore.Color.DarkBlue + else + if Scale.Color and strlen(Scale.Color) == 6 then return "|cff" .. Scale.Color end + return VgerCore.Color.Blue + end +end + +-- Sets the color of a scale in six-character hex format. The unenchanted color for the scale will also be set +-- to a slightly darker color. +function PawnSetScaleColor(ScaleName, HexColor) + if (not ScaleName) or (ScaleName == "") then + VgerCore.Fail("ScaleName cannot be empty. Usage: rrggbb = PawnGetScaleColor(\"ScaleName\", Unenchanted)") + return nil + end + local Scale = PawnCommon.Scales[ScaleName] + if not Scale then + VgerCore.Fail("ScaleName must be the name of an existing scale, and is case-sensitive.") + return nil + end + if not HexColor or strlen(HexColor) ~= 6 then + VgerCore.Fail("HexColor must be a six-digit hexadecimal color code, such as '66c0ff'.") + return nil + end + + local r, g, b = VgerCore.HexToRGB(HexColor) + Scale.Color = HexColor + Scale.UnenchantedColor = VgerCore.RGBToHex(r * PawnScaleColorDarkFactor, g * PawnScaleColorDarkFactor, b * PawnScaleColorDarkFactor) +end + +-- Returns true if a scale is read-only. +function PawnScaleIsReadOnly(ScaleName) + local Scale = PawnCommon.Scales[ScaleName] + return Scale and Scale.Provider ~= nil +end + +-- Returns the localized name for a scale if it has one. Otherwise, it returns the scale's unlocalized name. +function PawnGetScaleLocalizedName(ScaleName) + local Scale = PawnCommon.Scales[ScaleName] + if Scale and Scale.LocalizedName then + return Scale.LocalizedName + else + return ScaleName + end +end + +-- Uninitialize the plugin infrastructure and clean up our stale data. We'll do this upon logging out or reloading the UI. +function PawnUnitializePlugins() + -- Remove values from all read-only scales from providers so they don't get serialized to SavedVariables unnecessarily. + local ScaleName, Scale + for ScaleName, Scale in pairs(PawnCommon.Scales) do + if Scale.Provider then + Scale.ProviderActive = nil + Scale.Values = nil + Scale.Header = nil + end + end + + -- Clear out the provider data. + PawnScaleProviders = nil +end + +-- Initializes all delay-loaded scale providers. +function PawnInitializePlugins() + -- This only needs to be done once. PawnAddPluginScaleProvider will take care of anything that needs to + -- happen after this is called. + if PawnScaleProvidersInitialized then return end + PawnScaleProvidersInitialized = true + + -- Go through the list of scale providers and call their initialization function. They'll create all of their + -- scales as necessary. + for _, Provider in pairs(PawnScaleProviders) do + if Provider.Function then + -- After we call each provider's initialization function, empty it out so that function can be + -- garbage-collected if necessary. + Provider.Function() + Provider.Function = nil + end + end +end + +-- Registers a plugin scale provider. +-- Arguments: ProviderInternalName, LocalizedName +-- ProviderInternalName: An unlocalized internal name for the scale provider. +-- LocalizedName: The localized name for the scale provider, to show up in the UI. +-- Function: A function to call that adds the scales when it is time. +function PawnAddPluginScaleProvider(ProviderInternalName, LocalizedName, Function) + -- If the scale provider already exists, ignore the second registration. + if PawnScaleProviders[ProviderInternalName] then return end + + if strfind(ProviderInternalName, "\"") then + VgerCore.Fail("Pawn scale providers cannot include double quotes ('\"') in their name.") + return + end + + if PawnScaleProvidersInitialized then + -- If we've already initialized scale providers, just do this one immediately. + PawnScaleProviders[ProviderInternalName] = { ["Name"] = LocalizedName } + Function() + else + -- Otherwise, we'll get to it later. + VgerCore.Assert(Function, "Scale provider \"" .. LocalizedName .. "\" was registered won't initialize properly because no initialization function was specified.") + PawnScaleProviders[ProviderInternalName] = { ["Name"] = LocalizedName, ["Function"] = Function } + end +end + +-- Given a scale provider name and a scale name, returns the full name of a scale from a provider. +function PawnGetProviderScaleName(ProviderInternalName, ScaleInternalName) + return "\"" .. ProviderInternalName .. "\":" .. ScaleInternalName +end + +-- Adds a plugin scale to Pawn. Plugin scales are read-only once added, and are not saved; they must be added on every login. +-- If this plugin scale already exists (it was added this session), it will be overwritten. +function PawnAddPluginScale(ProviderInternalName, ScaleInternalName, LocalizedName, Color, Values, NormalizationFactor) + if not PawnScaleProviders[ProviderInternalName] then + VgerCore.Fail("A scale provider with that name is not registered. Use PawnAddPluginScaleProvider first.") + return + end + + PawnInitializeOptions() + + -- Now, add this new scale to the master list, or if it's already there, update it with the data from the scale provider. + local ScaleFullName = PawnGetProviderScaleName(ProviderInternalName, ScaleInternalName) + local NewScale + if PawnCommon.Scales[ScaleFullName] then + NewScale = PawnCommon.Scales[ScaleFullName] + else + NewScale = PawnGetEmptyScale() + end + NewScale.ProviderActive = true + NewScale.Provider = ProviderInternalName + NewScale.LocalizedName = LocalizedName + NewScale.Header = PawnScaleProviders[ProviderInternalName].Name + NewScale.NormalizationFactor = NormalizationFactor + NewScale.Values = Values + if not NewScale.PerCharacterOptions then NewScale.PerCharacterOptions = {} end + if not NewScale.PerCharacterOptions[PawnPlayerFullName] then NewScale.PerCharacterOptions[PawnPlayerFullName] = {} end + if not PawnCommon.Scales[ScaleFullName] then PawnCommon.Scales[ScaleFullName] = NewScale end + + if not NewScale.Color then PawnSetScaleColor(ScaleFullName, Color) end -- If the user has customized the color, don't overwrite theirs. +end + +-- Shows or hides the Pawn UI. +function PawnUIShow() + if not PawnUIFrame then + VgerCore.Fail("Pawn UI is not loaded!") + return + end + if PawnUIFrame:IsShown() then + PawnUIFrame:Hide() + else + PawnUIFrame:Show() + end +end diff --git a/Pawn.toc b/Pawn.toc new file mode 100644 index 0000000..d63b4a8 --- /dev/null +++ b/Pawn.toc @@ -0,0 +1,16 @@ +## Interface: 30300 +## Title: Pawn +## Version: 1.3.8 +## Notes: Pawn calculates scores for items that let you easily see which one is better for you. +## OptionalDependencies: AtlasLoot, EQCompare, EquipCompare, MultiTips, Outfitter +## SavedVariables: PawnCommon +## SavedVariablesPerCharacter: PawnOptions, PawnWowheadScaleProviderOptions + +VgerCore\VgerCore.lua + +Localization.lua +Gems.lua +Pawn.lua +PawnUI.lua +PawnUI.xml +Wowhead.lua \ No newline at end of file diff --git a/PawnUI.lua b/PawnUI.lua new file mode 100644 index 0000000..ad66dff --- /dev/null +++ b/PawnUI.lua @@ -0,0 +1,1996 @@ +-- Pawn by Vger-Azjol-Nerub +-- www.vgermods.com +-- © 2006-2010 Green Eclipse. This mod is released under the Creative Commons Attribution-NonCommercial-NoDerivs 3.0 license. +-- See Readme.htm for more information. +-- +-- User interface code +------------------------------------------------------------ + + + +------------------------------------------------------------ +-- Globals +------------------------------------------------------------ + +PawnUICurrentScale = nil +PawnUICurrentTabNumber = nil +PawnUICurrentListIndex = 0 +PawnUICurrentStatIndex = 0 + +-- An array with indices 1 and 2 for the left and right compare items, respectively; each one is of the type returned by GetItemData. +local PawnUIComparisonItems = {} +-- An array with indices 1 and 2 for the first and second left side shortcut items. +local PawnUIShortcutItems = {} + +local PawnUITotalScaleLines = 0 +local PawnUITotalComparisonLines = 0 +local PawnUITotalGemLines = 0 + +------------------------------------------------------------ +-- "Constants" +------------------------------------------------------------ + +local PawnUIScaleLineHeight = 16 -- each scale line is 16 pixels tall +local PawnUIScaleSelectorPaddingBottom = 5 -- add 5 pixels of padding to the bottom of the scrolling area + +local PawnUIStatsListHeight = 18 -- the stats list contains 12 items +local PawnUIStatsListItemHeight = 16 -- each item is 16 pixels tall + +local PawnUIComparisonLineHeight = 20 -- each comparison line is 20 pixels tall +local PawnUIComparisonAreaPaddingBottom = 10 -- add 10 pixels of padding to the bottom of the scrolling area + +local PawnUIGemLineHeight = 17 -- each comparison line is 17 pixels tall +local PawnUIGemAreaPaddingBottom = 0 -- add no padding to the bottom of the scrolling area + +local PawnUIFrameNeedsScaleSelector = { true, true, true, true, false, false, false } + + +-- The 1-based indes of the stat headers for gems. +PawnUIStats_RedSocketIndex = 8 +PawnUIStats_YellowSocketIndex = 9 +PawnUIStats_BlueSocketIndex = 10 +PawnUIStats_MetaSocketIndex = 11 +PawnUIStats_MetaSocketEffectIndex = 12 +PawnUIStats_SocketBonusBefore = 13 + + +------------------------------------------------------------ +-- Inventory button +------------------------------------------------------------ + +-- Moves the Pawn inventory sheet button and inspect button to the location specified by the user's current preferences. +function PawnUI_InventoryPawnButton_Move() + if PawnCommon.ButtonPosition == PawnButtonPositionRight then + PawnUI_InventoryPawnButton:ClearAllPoints() + PawnUI_InventoryPawnButton:SetPoint("TOPRIGHT", "CharacterTrinket1Slot", "BOTTOMRIGHT", -1, -8) + PawnUI_InventoryPawnButton:Show() + if PawnUI_InspectPawnButton then + PawnUI_InspectPawnButton:ClearAllPoints() + PawnUI_InspectPawnButton:SetPoint("TOPRIGHT", "InspectTrinket1Slot", "BOTTOMRIGHT", -1, -8) + PawnUI_InspectPawnButton:Show() + end + if PawnUI_SocketingPawnButton then + PawnUI_SocketingPawnButton:ClearAllPoints() + PawnUI_SocketingPawnButton:SetPoint("TOPRIGHT", "ItemSocketingFrame", "TOPRIGHT", -18, -46) + PawnUI_SocketingPawnButton:Show() + end + elseif PawnCommon.ButtonPosition == PawnButtonPositionLeft then + PawnUI_InventoryPawnButton:ClearAllPoints() + PawnUI_InventoryPawnButton:SetPoint("TOPLEFT", "CharacterWristSlot", "BOTTOMLEFT", 1, -8) + PawnUI_InventoryPawnButton:Show() + if PawnUI_InspectPawnButton then + PawnUI_InspectPawnButton:ClearAllPoints() + PawnUI_InspectPawnButton:SetPoint("TOPLEFT", "InspectWristSlot", "BOTTOMLEFT", 1, -8) + PawnUI_InspectPawnButton:Show() + end + else + PawnUI_InventoryPawnButton:Hide() + if PawnUI_InspectPawnButton then + PawnUI_InspectPawnButton:Hide() + end + if PawnUI_SocketingPawnButton then + PawnUI_SocketingPawnButton:Hide() + end + end +end + +function PawnUI_InventoryPawnButton_OnEnter(this) + -- Even if there are no scales, we'll at least display this much. + GameTooltip:ClearLines() + GameTooltip:SetOwner(this, "ANCHOR_BOTTOMRIGHT") + GameTooltip:AddLine("Pawn", 1, 1, 1, 1) + GameTooltip:AddLine(PawnUI_InventoryPawnButton_Tooltip, nil, nil, nil, 1) + + -- If the user has at least one scale and at least one type of value is enabled, calculate a total of all equipped items' values. + PawnUI_AddInventoryTotalsToTooltip(GameTooltip, "player") + + -- Finally, display the tooltip. + GameTooltip:Show() +end + +function PawnUI_InspectPawnButton_OnEnter(this) + -- Even if there are no scales, we'll at least display this much. + GameTooltip:ClearLines() + GameTooltip:SetOwner(this, "ANCHOR_BOTTOMRIGHT") + GameTooltip:AddLine("Pawn", 1, 1, 1, 1) + + -- If the user has at least one scale and at least one type of value is enabled, calculate a total of all equipped items' values. + PawnUI_AddInventoryTotalsToTooltip(GameTooltip, "playertarget") + + -- Finally, display the tooltip. + GameTooltip:Show() +end + +function PawnUI_SocketingPawnButton_OnEnter(this) + GameTooltip:ClearLines() + GameTooltip:SetOwner(this, "ANCHOR_BOTTOMRIGHT") + GameTooltip:AddLine("Pawn", 1, 1, 1, 1) + GameTooltip:AddLine(PawnUI_SocketingPawnButton_Tooltip) + + -- Finally, display the tooltip. + GameTooltip:Show() +end + +function PawnUI_AddInventoryTotalsToTooltip(Tooltip, Unit) + if PawnCommon.ShowUnenchanted or PawnCommon.ShowEnchanted then + -- Get the total stats for all items. + local ItemValues, Count, EpicItemLevel = PawnGetInventoryItemValues(Unit) + if Count > 0 then + Tooltip:AddLine(" ") + Tooltip:AddLine(PawnUI_InventoryPawnButton_Subheader, 1, 1, 1, 1) + PawnAddValuesToTooltip(Tooltip, ItemValues, true) + if PawnCommon.AlignNumbersRight then + Tooltip:AddDoubleLine(PawnLocal.AverageItemLevelTooltipLine, EpicItemLevel, VgerCore.Color.OrangeR, VgerCore.Color.OrangeG, VgerCore.Color.OrangeB, VgerCore.Color.OrangeR, VgerCore.Color.OrangeG, VgerCore.Color.OrangeB) + else + Tooltip:AddLine(PawnLocal.AverageItemLevelTooltipLine .. ": " .. EpicItemLevel, VgerCore.Color.OrangeR, VgerCore.Color.OrangeG, VgerCore.Color.OrangeB) + end + end + end +end + +function PawnUI_InspectPawnButton_Attach() + -- It's possible that this will happen before the main initialization code, so we need to ensure that the + -- default Pawn options have been set already. Doing this multiple times is harmless. + PawnInitializeOptions() + + VgerCore.Assert(InspectPaperDollFrame ~= nil, "InspectPaperDollFrame should be loaded by now!") + CreateFrame("Button", "PawnUI_InspectPawnButton", InspectPaperDollFrame, "PawnUI_InspectPawnButtonTemplate") + PawnUI_InspectPawnButton:SetParent(InspectPaperDollFrame) + PawnUI_InventoryPawnButton_Move() +end + +function PawnUI_SocketingPawnButton_Attach() + -- It's possible that this will happen before the main initialization code, so we need to ensure that the + -- default Pawn options have been set already. Doing this multiple times is harmless. + PawnInitializeOptions() + + -- Attach the socketing button. + VgerCore.Assert(ItemSocketingFrame ~= nil, "ItemSocketingFrame should be loaded by now!") + CreateFrame("Button", "PawnUI_SocketingPawnButton", ItemSocketingFrame, "PawnUI_SocketingPawnButtonTemplate") + PawnUI_SocketingPawnButton:SetParent(ItemSocketingFrame) + PawnUI_InventoryPawnButton_Move() + + -- Hook the item update event. + VgerCore.HookInsecureFunction(ItemSocketingDescription, "SetSocketedItem", PawnUI_OnSocketUpdate) +end + +------------------------------------------------------------ +-- Scale selector events +------------------------------------------------------------ + +function PawnUIFrame_ScaleSelector_Refresh() + -- First, delete the existing scale lines. + for i = 1, PawnUITotalScaleLines do + local LineName = "PawnUIScaleLine" .. i + local Line = getglobal(LineName) + if Line then Line:Hide() end + setglobal(LineName, nil) + end + PawnUITotalScaleLines = 0 + + -- Get a sorted list of scale data and display it all. + local NewSelectedScale, FirstScale, ScaleData, LastHeader + for _, ScaleData in pairs(PawnGetAllScalesEx()) do + local ScaleName = ScaleData.Name + if ScaleName == PawnUICurrentScale then NewSelectedScale = ScaleName end + if not FirstScale then FirstScale = ScaleName end + -- Add the header if necessary. + if ScaleData.Header ~= LastHeader then + LastHeader = ScaleData.Header + PawnUIFrame_ScaleSelector_AddHeaderLine(LastHeader) + end + -- Then, list the scale. + PawnUIFrame_ScaleSelector_AddScaleLine(ScaleName, ScaleData.LocalizedName, ScaleData.IsVisible) + end + + PawnUIScaleSelectorScrollContent:SetHeight(PawnUIScaleLineHeight * PawnUITotalScaleLines + PawnUIScaleSelectorPaddingBottom) + + -- If the scale that they previously selected isn't in the list, or they didn't have a previously-selected + -- scale, just select the first visible one, or the first one if there's no visible scale. + PawnUICurrentScale = NewSelectedScale or FirstScale or PawnUINoScale + PawnUI_HighlightCurrentScale() + + -- Also refresh a few other related UI elements. + PawnUIUpdateHeader() + PawnUIFrame_ShowScaleCheck_Update() +end + +function PawnUIFrame_ScaleSelector_AddHeaderLine(Text) + local Line = PawnUIFrame_ScaleSelector_AddLineCore(Text) + Line:Disable() +end + +function PawnUIFrame_ScaleSelector_AddScaleLine(ScaleName, LocalizedName, IsActive) + local ColoredName + --if IsActive then + -- ColoredName = PawnGetScaleColor(ScaleName) .. ScaleName + --else + ColoredName = LocalizedName + --end + local Line = PawnUIFrame_ScaleSelector_AddLineCore(" " .. ColoredName) + if not IsActive then + Line:SetNormalFontObject("PawnFontSilver") + end + Line.ScaleName = ScaleName +end + +function PawnUIFrame_ScaleSelector_AddLineCore(Text) + PawnUITotalScaleLines = PawnUITotalScaleLines + 1 + local LineName = "PawnUIScaleLine" .. PawnUITotalScaleLines + local Line = CreateFrame("Button", LineName, PawnUIScaleSelectorScrollContent, "PawnUIFrame_ScaleSelector_ItemTemplate") + Line:SetPoint("TOPLEFT", PawnUIScaleSelectorScrollContent, "TOPLEFT", 0, -PawnUIScaleLineHeight * (PawnUITotalScaleLines - 1)) + Line:SetText(Text) + return Line, LineName +end + +function PawnUIFrame_ScaleSelector_OnClick(this) + PawnUI_SelectScale(this.ScaleName) +end + +-- Selects a scale in CurrentScaleDropDown. +function PawnUI_SelectScale(ScaleName) + -- Close popup UI as necessary. + PawnUIStringDialog:Hide() + ColorPickerFrame:Hide() + -- Select the scale. + PawnUICurrentScale = ScaleName + PawnUI_HighlightCurrentScale() + -- After selecting a new scale, update the rest of the UI. + PawnUIFrame_ShowScaleCheck_Update() + PawnUIUpdateHeader() + if PawnUIScalesTabPage:IsVisible() then + PawnUI_ScalesTab_Refresh() + end + if PawnUIValuesTabPage:IsVisible() then + PawnUI_ValuesTab_Refresh() + end + if PawnUICompareTabPage:IsVisible() then + PawnUI_CompareItems() + end + if PawnUIGemsTabPage:IsVisible() then + PawnUI_ShowBestGems() + end +end + +function PawnUI_HighlightCurrentScale() + PawnUIFrame_ScaleSelector_HighlightFrame:ClearAllPoints() + PawnUIFrame_ScaleSelector_HighlightFrame:Hide() + for i = 1, PawnUITotalScaleLines do + local LineName = "PawnUIScaleLine" .. i + local Line = getglobal(LineName) + if Line and Line.ScaleName == PawnUICurrentScale then + PawnUIFrame_ScaleSelector_HighlightFrame:SetPoint("TOPLEFT", "PawnUIScaleLine" .. i, "TOPLEFT", 0, 0) + PawnUIFrame_ScaleSelector_HighlightFrame:Show() + break + end + end +end + +------------------------------------------------------------ +-- Scales tab events +------------------------------------------------------------ + +function PawnUI_ScalesTab_Refresh() + PawnUIFrame_ScaleColorSwatch_Update() + + if PawnUICurrentScale ~= PawnUINoScale then + PawnUIFrame_ScaleNameLabel:SetText(PawnGetScaleColor(PawnUICurrentScale) .. PawnGetScaleLocalizedName(PawnUICurrentScale)) + if PawnScaleIsReadOnly(PawnUICurrentScale) then + PawnUIFrame_ScaleTypeLabel:SetText(PawnUIFrame_ScaleTypeLabel_ReadOnlyScaleText) + PawnUIFrame_RenameScaleButton:Disable() + PawnUIFrame_DeleteScaleButton:Disable() + else + PawnUIFrame_ScaleTypeLabel:SetText(PawnUIFrame_ScaleTypeLabel_NormalScaleText) + PawnUIFrame_RenameScaleButton:Enable() + PawnUIFrame_DeleteScaleButton:Enable() + end + PawnUIFrame_CopyScaleButton:Enable() + PawnUIFrame_ExportScaleButton:Enable() + else + PawnUIFrame_ScaleNameLabel:SetText(PawnUINoScale) + PawnUIFrame_CopyScaleButton:Disable() + PawnUIFrame_RenameScaleButton:Disable() + PawnUIFrame_DeleteScaleButton:Disable() + PawnUIFrame_ExportScaleButton:Disable() + end +end + +------------------------------------------------------------ +-- Values tab events +------------------------------------------------------------ + +function PawnUI_ValuesTab_Refresh() + PawnUIFrame_StatsList_Update() + PawnUIFrame_StatsList_SelectStat(PawnUICurrentStatIndex) + local Scale + if PawnUICurrentScale ~= PawnUINoScale then Scale = PawnCommon.Scales[PawnUICurrentScale] end + + if PawnUICurrentScale == PawnUINoScale then + PawnUIFrame_ValuesWelcomeLabel:SetText(PawnUIFrame_ValuesWelcomeLabel_NoScalesText) + elseif PawnScaleIsReadOnly(PawnUICurrentScale) then + PawnUIFrame_ValuesWelcomeLabel:SetText(PawnUIFrame_ValuesWelcomeLabel_ReadOnlyScaleText) + PawnUIFrame_NormalizeValuesCheck:Disable() + else + PawnUIFrame_ValuesWelcomeLabel:SetText(PawnUIFrame_ValuesWelcomeLabel_NormalText) + PawnUIFrame_NormalizeValuesCheck:Enable() + end + if Scale then + PawnUIFrame_NormalizeValuesCheck:SetChecked(Scale.NormalizationFactor and Scale.NormalizationFactor > 0) + PawnUIFrame_NormalizeValuesCheck:Show() + else + PawnUIFrame_NormalizeValuesCheck:Hide() + end +end + +function PawnUIFrame_ImportScaleButton_OnClick() + PawnUIImportScale() +end + +function PawnUIFrame_NewScaleButton_OnClick() + PawnUIGetString(PawnLocal.NewScaleEnterName, "", PawnUIFrame_NewScale_OnOK) +end + +function PawnUIFrame_NewScale_OnOK(NewScaleName) + -- Does this scale already exist? + if NewScaleName == PawnUINoScale then + PawnUIGetString(PawnLocal.NewScaleEnterName, "", PawnUIFrame_NewScale_OnOK) + return + elseif strfind(NewScaleName, "\"") then + PawnUIGetString(PawnLocal.NewScaleNoQuotes, NewScaleName, PawnUIFrame_NewScale_OnOK) + elseif PawnDoesScaleExist(NewScaleName) then + PawnUIGetString(PawnLocal.NewScaleDuplicateName, NewScaleName, PawnUIFrame_NewScale_OnOK) + return + end + + -- Add and select the scale. + PawnAddEmptyScale(NewScaleName) + PawnUIFrame_ScaleSelector_Refresh() + PawnUI_SelectScale(NewScaleName) + PawnUISwitchToTab(PawnUIValuesTabPage) +end + +function PawnUIFrame_NewScaleFromDefaultsButton_OnClick() + PawnUIGetString(PawnLocal.NewScaleEnterName, "", PawnUIFrame_NewScaleFromDefaults_OnOK) +end + +function PawnUIFrame_NewScaleFromDefaults_OnOK(NewScaleName) + -- Does this scale already exist? + if NewScaleName == PawnUINoScale then + PawnUIGetString(PawnLocal.NewScaleEnterName, "", PawnUIFrame_NewScaleFromDefaults_OnOK) + return + elseif strfind(NewScaleName, "\"") then + PawnUIGetString(PawnLocal.NewScaleNoQuotes, NewScaleName, PawnUIFrame_NewScaleFromDefaults_OnOK) + elseif PawnDoesScaleExist(NewScaleName) then + PawnUIGetString(PawnLocal.NewScaleDuplicateName, NewScaleName, PawnUIFrame_NewScaleFromDefaults_OnOK) + return + end + + -- Add and select the scale. + PawnAddDefaultScale(NewScaleName) + PawnUIFrame_ScaleSelector_Refresh() + PawnUI_SelectScale(NewScaleName) + PawnUISwitchToTab(PawnUIValuesTabPage) +end + +function PawnUIFrame_ExportScaleButton_OnClick() + PawnUIExportScale(PawnUICurrentScale) +end + +function PawnUIFrame_RenameScaleButton_OnClick() + PawnUIGetString(format(PawnLocal.RenameScaleEnterName, PawnUICurrentScale), PawnUICurrentScale, PawnUIFrame_RenameScale_OnOK) +end + +function PawnUIFrame_CopyScaleButton_OnClick() + PawnUIGetString(format(PawnLocal.CopyScaleEnterName, PawnGetScaleLocalizedName(PawnUICurrentScale)), "", PawnUIFrame_CopyScale_OnOK) +end + +-- Shows a dialog where the user can copy a scale tag for a given scale to the clipboard. +-- Immediately returns true if successful, or false if not. +function PawnUIExportScale(ScaleName) + local ScaleTag = PawnGetScaleTag(ScaleName) + if ScaleTag then + PawnUIShowCopyableString(format(PawnLocal.ExportScaleMessage, PawnGetScaleLocalizedName(PawnUICurrentScale)), ScaleTag) + return true + else + return false + end +end + +-- Exports all custom scales as a series of scale tags. +function PawnUIExportAllScales() + local ScaleTags, ScaleName, Scale + ScaleTags = "" + for ScaleName in pairs(PawnCommon.Scales) do + if not PawnScaleIsReadOnly(ScaleName) then ScaleTags = ScaleTags .. PawnGetScaleTag(ScaleName) .. " " end + end + if ScaleTags and ScaleTags ~= "" then + PawnUIShowCopyableString(PawnLocal.ExportAllScalesMessage, ScaleTags) + return true + else + return false + end +end + +-- Shows a dialog where the user can paste a scale tag from the clipboard. +-- Immediately returns. +function PawnUIImportScale() + PawnUIGetString(PawnLocal.ImportScaleMessage, "", PawnUIImportScaleCallback) +end + +-- Callback function for PawnUIImportScale. +function PawnUIImportScaleCallback(ScaleTag) + -- Try to import the scale. If successful, we don't need to do anything else. + local Status, ScaleName = PawnImportScale(ScaleTag, true) -- allow overwriting a scale with the same name + if Status == PawnImportScaleResultSuccess then + if PawnUIFrame_ScaleSelector_Refresh then + -- Select the new scale if the UI is up. + PawnUIFrame_ScaleSelector_Refresh() + PawnUI_SelectScale(ScaleName) + PawnUISwitchToTab(PawnUIValuesTabPage) + end + return + end + + -- If there was a problem, show an error message or reshow the dialog as appropriate. + if Status == PawnImportScaleResultAlreadyExists then + VgerCore.Message(VgerCore.Color.Salmon .. format(PawnLocal.ImportScaleAlreadyExistsMessage, ScaleName)) + return + end + if Status == PawnImportScaleResultTagError then + -- Don't use the tag that was pasted as the default value; it makes it harder to paste. + PawnUIGetString(PawnLocal.ImportScaleTagErrorMessage, "", PawnUIImportScaleCallback) + return + end + + VgerCore.Fail("Unexpected PawnImportScaleResult value: " .. tostring(Status)) +end + +function PawnUIFrame_RenameScale_OnOK(NewScaleName) + -- Did they change anything? + if NewScaleName == PawnUICurrentScale then return end + + -- Does this scale already exist? + if NewScaleName == PawnUINoScale then + PawnUIGetString(format(PawnLocal.RenameScaleEnterName, PawnUICurrentScale), PawnUICurrentScale, PawnUIFrame_RenameScale_OnOK) + return + elseif strfind(NewScaleName, "\"") then + PawnUIGetString(PawnLocal.NewScaleNoQuotes, NewScaleName, PawnUIFrame_RenameScale_OnOK) + elseif PawnDoesScaleExist(NewScaleName) then + PawnUIGetString(PawnLocal.NewScaleDuplicateName, PawnUICurrentScale, PawnUIFrame_RenameScale_OnOK) + return + end + + -- Rename and select the scale. + PawnRenameScale(PawnUICurrentScale, NewScaleName) + PawnUIFrame_ScaleSelector_Refresh() + PawnUI_SelectScale(NewScaleName) +end + +function PawnUIFrame_CopyScale_OnOK(NewScaleName) + -- Does this scale already exist? + if NewScaleName == PawnUINoScale then + PawnUIGetString(PawnLocal.CopyScaleEnterName, "", PawnUIFrame_CopyScale_OnOK) + return + elseif strfind(NewScaleName, "\"") then + PawnUIGetString(PawnLocal.NewScaleNoQuotes, NewScaleName, PawnUIFrame_CopyScale_OnOK) + elseif PawnDoesScaleExist(NewScaleName) then + PawnUIGetString(PawnLocal.NewScaleDuplicateName, NewScaleName, PawnUIFrame_CopyScale_OnOK) + return + end + + -- Create the new scale. + PawnDuplicateScale(PawnUICurrentScale, NewScaleName) + PawnUIFrame_ScaleSelector_Refresh() + PawnUI_SelectScale(NewScaleName) + PawnUISwitchToTab(PawnUIValuesTabPage) +end + +function PawnUIFrame_DeleteScaleButton_OnClick() + if IsShiftKeyDown() then + -- If the user held down the shift key when clicking the Delete button, just do it immediately. + PawnUIFrame_DeleteScaleButton_OnOK(DELETE_ITEM_CONFIRM_STRING) + else + PawnUIGetString(format(PawnLocal.DeleteScaleConfirmation, PawnUICurrentScale, DELETE_ITEM_CONFIRM_STRING), "", PawnUIFrame_DeleteScaleButton_OnOK) + end +end + +function PawnUIFrame_DeleteScaleButton_OnOK(ConfirmationText) + -- If they didn't type "DELETE" (ignoring case), just exit. + if strlower(ConfirmationText) ~= strlower(DELETE_ITEM_CONFIRM_STRING) then return end + + PawnDeleteScale(PawnUICurrentScale) + PawnUICurrentScale = nil + PawnUIFrame_ScaleSelector_Refresh() + PawnUI_ScalesTab_Refresh() +end + +function PawnUIFrame_StatsList_Update() + if not PawnStats then return end + + -- First, update the control and get our new offset. + FauxScrollFrame_Update(PawnUIFrame_StatsList, #PawnStats, PawnUIStatsListHeight, PawnUIStatsListItemHeight) -- list, number of items, number of items visible per page, item height + local Offset = FauxScrollFrame_GetOffset(PawnUIFrame_StatsList) + + -- Then, update the list items as necessary. + local ThisScale + if PawnUICurrentScale ~= PawnUINoScale then ThisScale = PawnGetAllStatValues(PawnUICurrentScale) end + local i + for i = 1, PawnUIStatsListHeight do + local Index = i + Offset + PawnUIFrame_StatsList_UpdateStatItem(i, Index, ThisScale) + end + + -- After the user scrolled, we need to adjust their selection. + PawnUIFrame_StatsList_MoveHighlight() + +end + +-- Updates a single stat in the list based on its index into the PawnStats table. +function PawnUIFrame_StatsList_UpdateStat(Index) + local Offset = FauxScrollFrame_GetOffset(PawnUIFrame_StatsList) + local i = Index - Offset + if i <= 0 or i > PawnUIStatsListHeight then return end + + PawnUIFrame_StatsList_UpdateStatItem(i, Index, PawnGetAllStatValues(PawnUICurrentScale)) +end + +-- Updates a single stat in the list. +function PawnUIFrame_StatsList_UpdateStatItem(i, Index, ThisScale) + local Title = PawnStats[Index][1] + local ThisStat = PawnStats[Index][2] + local Line = getglobal("PawnUIFrame_StatsList_Item" .. i) + + if Index <= #PawnStats then + if not ThisStat then + -- This is a header row. + Line:SetText(Title) + Line:Disable() + elseif ThisScale and ThisScale[ThisStat] then + -- This is a stat that's in the current scale. + Line:SetText(" " .. Title .. " = " .. format("%g", ThisScale[ThisStat])) + Line:SetNormalFontObject("GameFontHighlight") + Line:Enable() + else + -- This is a stat that's not in the current scale. + Line:SetText(" " .. Title) + Line:SetNormalFontObject("PawnFontSilver") + Line:Enable() + end + Line:Show() + else + Line:Hide() + end +end + +-- Adjusts PawnUICurrentListIndex and the position of the highlight based on PawnUICurrentStatIndex. +function PawnUIFrame_StatsList_MoveHighlight() + -- If no stat is selected, just hide the highlight. + if not PawnUICurrentStatIndex or PawnUICurrentStatIndex == 0 then + PawnUICurrentListIndex = 0 + PawnUIFrame_StatsList_HighlightFrame:Hide() + return + end + + -- Otherwise, see if we need to draw a highlight. If the selected stat isn't visible, we shouldn't draw anything. + local Offset = FauxScrollFrame_GetOffset(PawnUIFrame_StatsList) + local i = PawnUICurrentStatIndex - Offset + if i <= 0 or i > PawnUIStatsListHeight then + PawnUICurrentListIndex = 0 + PawnUIFrame_StatsList_HighlightFrame:Hide() + return + end + + -- If we made it this far, then we need to draw a highlight. + PawnUICurrentListIndex = i + PawnUIFrame_StatsList_HighlightFrame:ClearAllPoints() + PawnUIFrame_StatsList_HighlightFrame:SetPoint("TOPLEFT", "PawnUIFrame_StatsList_Item" .. i, "TOPLEFT", 0, 0) + PawnUIFrame_StatsList_HighlightFrame:Show() +end + +-- This is the click handler for list item #i. +function PawnUIFrame_StatsList_OnClick(i) + if not i or i <= 0 or i > PawnUIStatsListHeight then return end + + local Offset = FauxScrollFrame_GetOffset(PawnUIFrame_StatsList) + local Index = i + Offset + + PawnUIFrame_StatsList_SelectStat(Index) +end + +function PawnUIFrame_StatsList_SelectStat(Index) + -- First, make sure that the stat is in the correct range. + if not Index or Index < 0 or Index > #PawnStats then + Index = 0 + end + + -- Then, find out what they've clicked on. + local Title, ThisStat, ThisDescription, ThisPrompt + if Index > 0 then + Title = PawnStats[Index][1] + ThisStat = PawnStats[Index][2] + if ThisStat then + -- This is a stat, not a header row. + else + -- This is a header row, or empty space. + Index = 0 + end + end + PawnUICurrentStatIndex = Index + + -- Show, move, or hide the highlight as appropriate. + PawnUIFrame_StatsList_MoveHighlight() + + -- Finally, change the UI to the right. + local ThisScale + if PawnUICurrentScale ~= PawnUINoScale then ThisScale = PawnGetAllStatValues(PawnUICurrentScale) end + if Index > 0 and ThisScale then + -- They've selected a stat. + ThisDescription = PawnStats[Index][3] + PawnUIFrame_DescriptionLabel:SetText(ThisDescription) + ThisPrompt = PawnStats[Index][4] + if ThisPrompt then + PawnUIFrame_StatNameLabel:SetText(ThisPrompt) + else + PawnUIFrame_StatNameLabel:SetText(format(PawnLocal.StatNameText, Title)) + end + PawnUIFrame_StatNameLabel:Show() + local ThisScaleValue = ThisScale[ThisStat] + local ThisScaleValueUneditable = ThisScaleValue + if not ThisScaleValueUneditable then ThisScaleValueUneditable = "0" end + if not ThisScaleValue or ThisScaleValue == 0 then ThisScaleValue = "" else ThisScaleValue = tostring(ThisScaleValue) end + PawnUIFrame_StatValueBox.SettingValue = (PawnUIFrame_StatValueBox:GetText() ~= ThisScaleValue) + PawnUIFrame_StatValueBox:SetText(ThisScaleValue) + PawnUIFrame_StatValueLabel:SetText(ThisScaleValueUneditable) + PawnUIFrame_ScaleSocketOptionsList_UpdateSelection() + elseif PawnUICurrentScale == PawnUINoScale then + -- They don't have any scales. + PawnUIFrame_DescriptionLabel:SetText(PawnLocal.NoScalesDescription) + PawnUIFrame_StatNameLabel:Hide() + PawnUIFrame_StatValueBox:Hide() + PawnUIFrame_StatValueLabel:Hide() + PawnUIFrame_ClearValueButton:Hide() + PawnUIFrame_ScaleSocketOptionsList:Hide() + else + -- They haven't selected a stat. + PawnUIFrame_DescriptionLabel:SetText(PawnLocal.NoStatDescription) + PawnUIFrame_StatNameLabel:Hide() + PawnUIFrame_StatValueBox:Hide() + PawnUIFrame_StatValueLabel:Hide() + PawnUIFrame_ClearValueButton:Hide() + PawnUIFrame_ScaleSocketOptionsList:Hide() + end + +end + +function PawnUIFrame_StatValueBox_OnTextChanged() + if PawnScaleIsReadOnly(PawnUICurrentScale) then return end + + local NewString = gsub(PawnUIFrame_StatValueBox:GetText(), ",", ".") + local NewValue = tonumber(NewString) + if NewValue == 0 then NewValue = nil end + + if NewValue then + PawnUIFrame_ClearValueButton:Enable() + else + PawnUIFrame_ClearValueButton:Disable() + end + + -- If other code is setting this value, we should ignore this event and not set any values. + if PawnUIFrame_StatValueBox.SettingValue then + PawnUIFrame_StatValueBox.SettingValue = false + return + end + PawnSetStatValue(PawnUICurrentScale, PawnStats[PawnUICurrentStatIndex][2], NewValue) + PawnUIFrame_StatsList_UpdateStat(PawnUICurrentStatIndex) + + -- If the user edited a non-socket value and smart socketing is on, update the sockets too. + -- (The socket values were already updated in PawnSetStatValue.) + if PawnUICurrentStatIndex and + PawnUICurrentStatIndex ~= PawnUIStats_RedSocketIndex and + PawnUICurrentStatIndex ~= PawnUIStats_YellowSocketIndex and + PawnUICurrentStatIndex ~= PawnUIStats_BlueSocketIndex and + PawnUICurrentStatIndex ~= PawnUIStats_MetaSocketIndex and + PawnUICurrentStatIndex ~= PawnUIStats_MetaStatsSocketIndex then + if PawnCommon.Scales[PawnUICurrentScale].SmartGemSocketing then + PawnUIFrame_StatsList_UpdateStat(PawnUIStats_RedSocketIndex) + PawnUIFrame_StatsList_UpdateStat(PawnUIStats_YellowSocketIndex) + PawnUIFrame_StatsList_UpdateStat(PawnUIStats_BlueSocketIndex) + end + if PawnCommon.Scales[PawnUICurrentScale].SmartMetaGemSocketing then + PawnUIFrame_StatsList_UpdateStat(PawnUIStats_MetaSocketIndex) + end + end +end + +function PawnUIFrame_ClearValueButton_OnClick() + PawnUIFrame_StatValueBox:SetText("") +end + +function PawnUIFrame_GetCurrentScaleColor() + local r, g, b + if PawnUICurrentScale and PawnUICurrentScale ~= PawnUINoScale then r, g, b = VgerCore.HexToRGB(PawnCommon.Scales[PawnUICurrentScale].Color) end + if not r then + r, g, b = VgerCore.Color.BlueR, VgerCore.Color.BlueG, VgerCore.Color.BlueB + end + return r, g, b +end + +function PawnUIFrame_ScaleColorSwatch_OnClick() + -- Get the color of the current scale. + local r, g, b = PawnUIFrame_GetCurrentScaleColor() + ColorPickerFrame.func = PawnUIFrame_ScaleColorSwatch_OnChange + ColorPickerFrame.cancelFunc = PawnUIFrame_ScaleColorSwatch_OnCancel + ColorPickerFrame.previousValues = { r, g, b } + ColorPickerFrame.hasOpacity = false + ColorPickerFrame:SetColorRGB(r, g, b) + ColorPickerFrame:SetFrameStrata("HIGH") + ColorPickerFrame:Show() +end + +function PawnUIFrame_ScaleColorSwatch_OnChange() + local r, g, b = ColorPickerFrame:GetColorRGB() + PawnUIFrame_ScaleColorSwatch_SetColor(r, g, b) +end + +function PawnUIFrame_ScaleColorSwatch_OnCancel(rgb) + local r, g, b = unpack(rgb) + PawnUIFrame_ScaleColorSwatch_SetColor(r, g, b) +end + +function PawnUIFrame_ScaleColorSwatch_SetColor(r, g, b) + PawnSetScaleColor(PawnUICurrentScale, VgerCore.RGBToHex(r, g, b)) + PawnUI_ScalesTab_Refresh() + PawnResetTooltips() +end + +function PawnUIFrame_ScaleColorSwatch_Update() + if PawnUICurrentScale ~= PawnUINoScale then + local r, g, b = PawnUIFrame_GetCurrentScaleColor() + PawnUIFrame_ScaleColorSwatch_Color:SetTexture(r, g, b) + PawnUIFrame_ScaleColorSwatch_Label:Show() + PawnUIFrame_ScaleColorSwatch:Show() + else + PawnUIFrame_ScaleColorSwatch_Label:Hide() + PawnUIFrame_ScaleColorSwatch:Hide() + end +end + +function PawnUIFrame_ShowScaleCheck_Update() + if PawnUICurrentScale ~= PawnUINoScale then + PawnUIFrame_ShowScaleCheck:SetChecked(PawnIsScaleVisible(PawnUICurrentScale)) + PawnUIFrame_ShowScaleCheck:Show() + else + PawnUIFrame_ShowScaleCheck:Hide() + end +end + +function PawnUIFrame_ShowScaleCheck_OnClick() + PawnSetScaleVisible(PawnUICurrentScale, PawnUIFrame_ShowScaleCheck:GetChecked()) + PawnUIFrame_ScaleSelector_Refresh() +end + +function PawnUIFrame_ScaleSocketOptionsList_SetSelection(Value) + if PawnUICurrentScale == PawnUINoScale then return end + if not PawnCommon.Scales[PawnUICurrentScale] then return end + if PawnUICurrentStatIndex == PawnUIStats_MetaSocketIndex then + PawnCommon.Scales[PawnUICurrentScale].SmartMetaGemSocketing = Value + else + PawnCommon.Scales[PawnUICurrentScale].SmartGemSocketing = Value + end + PawnUIFrame_ScaleSocketOptionsList_UpdateSelection() + -- Changing the socketing option affects scale values, so we'll have to recalculate everything. + PawnRecalculateScaleTotal(PawnUICurrentScale) + PawnResetTooltips() + PawnUIFrame_StatsList_UpdateStat(PawnUIStats_RedSocketIndex) + PawnUIFrame_StatsList_UpdateStat(PawnUIStats_YellowSocketIndex) + PawnUIFrame_StatsList_UpdateStat(PawnUIStats_BlueSocketIndex) + PawnUIFrame_StatsList_UpdateStat(PawnUIStats_MetaSocketIndex) +end + +function PawnUIFrame_ScaleSocketOptionsList_UpdateSelection() + if PawnUICurrentScale == PawnUINoScale then return end + if not PawnCommon.Scales[PawnUICurrentScale] then return end + + local IsReadOnly = PawnScaleIsReadOnly(PawnUICurrentScale) + local ShowEditingUI = not IsReadOnly + if (not IsReadOnly) and + (PawnUICurrentStatIndex == PawnUIStats_RedSocketIndex or + PawnUICurrentStatIndex == PawnUIStats_YellowSocketIndex or + PawnUICurrentStatIndex == PawnUIStats_BlueSocketIndex or + PawnUICurrentStatIndex == PawnUIStats_MetaSocketIndex) then + local SmartSocketing + if PawnUICurrentStatIndex == PawnUIStats_MetaSocketIndex then + SmartSocketing = PawnCommon.Scales[PawnUICurrentScale].SmartMetaGemSocketing + else + SmartSocketing = PawnCommon.Scales[PawnUICurrentScale].SmartGemSocketing + end + if SmartSocketing then + ShowEditingUI = false + PawnUIFrame_ScaleSocketBestRadio:SetChecked(true) + PawnUIFrame_ScaleSocketCorrectRadio:SetChecked(false) + else + PawnUIFrame_ScaleSocketBestRadio:SetChecked(false) + PawnUIFrame_ScaleSocketCorrectRadio:SetChecked(true) + end + PawnUIFrame_ScaleSocketOptionsList:Show() + else + PawnUIFrame_ScaleSocketOptionsList:Hide() + end + if ShowEditingUI then + PawnUIFrame_StatValueBox:Show() + PawnUIFrame_StatValueLabel:Hide() + PawnUIFrame_ClearValueButton:Show() + else + PawnUIFrame_StatValueBox:Hide() + PawnUIFrame_StatValueLabel:Show() + PawnUIFrame_ClearValueButton:Hide() + end +end + +function PawnUIFrame_NormalizeValuesCheck_OnClick() + if PawnUICurrentScale == PawnUINoScale or PawnScaleIsReadOnly(PawnUICurrentScale) then return end + local Scale = PawnCommon.Scales[PawnUICurrentScale] + + if PawnUIFrame_NormalizeValuesCheck:GetChecked() then + Scale.NormalizationFactor = 1 + else + Scale.NormalizationFactor = nil + end + PawnResetTooltips() +end + +------------------------------------------------------------ +-- Compare tab +------------------------------------------------------------ + +-- Initializes the Compare tab if it hasn't already been initialized. +local PawnUI_CompareTabInitialized +function PawnUI_InitCompareTab() + -- This only needs to be run once. + if PawnUI_CompareTabInitialized then return end + PawnUI_CompareTabInitialized = true + + -- All the Compare tab needs to do here is clear out the comparison items. Initializing the dropdown + -- is actually covered by existing code. + PawnUI_ClearCompareItems() +end + +-- Sets either the left (index 1) or right (index 2) comparison item, using an item link. If the passed item +-- link is nil, that comparison item is instead cleared out. Returns true if an item was actually placed in the +-- slot or cleared from the slot. +function PawnUI_SetCompareItem(Index, ItemLink) + PawnUI_InitCompareTab() + if Index ~= 1 and Index ~= 2 then + VgerCore.Fail("Index must be 1 or 2.") + return + end + + -- Get the item data for this item link; we can't do a comparison without it. + local Item + if ItemLink then + -- If they passed item data instead of an item link, just use that. Otherwise, get item data from the link. + if type(ItemLink) == "table" then + Item = ItemLink + ItemLink = Item.Link + if not ItemLink then + VgerCore.Fail("Second parameter must be an item link or item data from PawnGetItemData.") + return + end + else + -- Unenchant the item link. + local UnenchantedLink = PawnUnenchantItemLink(ItemLink) + if UnenchantedLink then ItemLink = UnenchantedLink end + Item = PawnGetItemData(ItemLink) + VgerCore.Assert(Item, "Failed to get item data while setting an comparison item!") + end + end + local ItemName, ItemRarity, ItemEquipLoc, ItemTexture + local SlotID1, SlotID2 + if ItemLink then + ItemName, _, ItemRarity, _, _, _, _, _, ItemEquipLoc, ItemTexture = GetItemInfo(ItemLink) + SlotID1, SlotID2 = PawnGetSlotsForItemType(ItemEquipLoc) + else + ItemName = PawnUIFrame_VersusHeader_NoItem + ItemRarity = 0 + end + + -- Items that are not equippable cannot be placed in the Compare slots. + if ItemLink and SlotID1 == nil and SlotID2 == nil then return end + + -- Save the item data locally, in case the item is later removed from the main Pawn item cache. + PawnUIComparisonItems[Index] = Item + + -- Now, update the item name and icon. + local Label = getglobal("PawnUICompareItemName" .. Index) + local Texture = getglobal("PawnUICompareItemIconTexture" .. Index) + Label:SetText(ItemName) + -- Workaround: ITEM_QUALITY_COLORS does not have a [7]. :( + if ItemRarity == 7 then ItemRarity = 6 end + local Color = ITEM_QUALITY_COLORS[ItemRarity] + if Color then Label:SetVertexColor(Color.r, Color.g, Color.b) end + Texture:SetTexture(ItemTexture) + + -- If this item is a different type than the existing item, clear out the existing item. + if ItemLink then + local OtherIndex + if Index == 1 then OtherIndex = 2 else OtherIndex = 1 end + if PawnUIComparisonItems[OtherIndex] then + _, _, _, _, _, _, _, _, OtherItemEquipLoc = GetItemInfo(PawnUIComparisonItems[OtherIndex].Link) + local OtherSlotID1, OtherSlotID2 = PawnGetSlotsForItemType(OtherItemEquipLoc) + if not ( + (SlotID1 == nil and SlotID2 == nil and OtherSlotID1 == nil and OtherSlotID2 == nil) or + (SlotID1 and (SlotID1 == OtherSlotID1 or SlotID1 == OtherSlotID2)) or + (SlotID2 and (SlotID2 == OtherSlotID1 or SlotID2 == OtherSlotID2)) + ) then + PawnUI_SetCompareItem(OtherIndex, nil) + end + end + end + + -- Update the item shortcuts. The item shortcuts appear on the left side, but they're based on what's equipped on + -- the right side. + if Index == 2 then + PawnUI_SetShortcutItemForSlot(1, SlotID1) + PawnUI_SetShortcutItemForSlot(2, SlotID2) + end + + -- Finally, either compare the two items, or remove the current comparison, whichever is appropriate. + PawnUI_CompareItems() + + -- Return true to indicate success to the caller. + return true +end + +-- Same as PawnUI_SetCompareItem, but shows the Pawn Compare UI if not already visible. +function PawnUI_SetCompareItemAndShow(Index, ItemLink) + if Index ~= 1 and Index ~= 2 then + VgerCore.Fail("Index must be 1 or 2.") + return + end + if not ItemLink or PawnGetHyperlinkType(ItemLink) ~= "item" then return end + + -- Set this as a compare item. + local Success = PawnUI_SetCompareItem(Index, ItemLink) + if Success then + -- Automatically pick a comparison item when possible. + PawnUI_AutoCompare() + + -- If the Pawn Compare UI is not visible, show it. + PawnUIShowTab(PawnUICompareTabPage) + end + + return Success +end + +-- If there is an item in slot 2 and nothing in slot 1, and the player has an item equipped in the proper slot, automatically +-- compare the slot 2 item with the equipped item. +function PawnUI_AutoCompare() + if PawnUIComparisonItems[2] and not PawnUIComparisonItems[1] and (PawnUIShortcutItems[1] or PawnUIShortcutItems[2]) then + -- Normally, use the first shortcut. But, if the first shortcut is missing or matches the item just compared, use the second + -- shortcut item instead. + local ShortcutToUse = PawnUIShortcutItems[1] + if (not PawnUIShortcutItems[1]) or (PawnUIShortcutItems[2] and (PawnUIShortcutItems[1].Link == PawnUIComparisonItems[2].Link)) then + ShortcutToUse = PawnUIShortcutItems[2] + end + -- Don't bother with an auto-comparison at all if the best item we found was the same item. + if ShortcutToUse.Link ~= PawnUIComparisonItems[2].Link then + PawnUI_SetCompareItem(1, ShortcutToUse) + end + end +end + +-- Tries to set one of the compare items based on what the user is currently hovering over. Meant for keybindings. +function PawnUI_SetCompareFromHover(Index) + PawnUI_SetCompareItemAndShow(Index, PawnLastHoveredItem) +end + +-- Enables or disables one of the "currently equipped" shortcut buttons based on an inventory slot ID. If there is an item in that +-- slot, that item will appear in the shortcut button. If not, or if Slot is nil, that shortcut button will be hidden. +function PawnUI_SetShortcutItemForSlot(ShortcutIndex, Slot) + if ShortcutIndex ~= 1 and ShortcutIndex ~= 2 then + VgerCore.Fail("ShortcutIndex must be 1 or 2.") + return + end + + -- Find the currently equipped inventory item, and save it for later. + local ButtonName = "PawnUICompareItemShortcut" .. ShortcutIndex + local ShortcutButton = getglobal(ButtonName) + local CurrentlyEquippedItem + if Slot then CurrentlyEquippedItem = PawnGetItemDataForInventorySlot(Slot, true) end + PawnUIShortcutItems[ShortcutIndex] = CurrentlyEquippedItem + + -- Now, update the button. + if CurrentlyEquippedItem then + -- There is a currently equipped item to put in this slot; get information about it. + local Texture = getglobal(ButtonName .. "Texture") + local _, _, _, _, _, _, _, _, _, ItemTexture = GetItemInfo(CurrentlyEquippedItem.Link) + Texture:SetTexture(ItemTexture) + ShortcutButton:Show() + else + ShortcutButton:Hide() + end +end + +-- Clears both comparison items and all comparison data. +function PawnUI_ClearCompareItems() + PawnUI_SetCompareItem(1, nil) + PawnUI_SetCompareItem(2, nil) +end + +-- Swaps the left and right comparison items. +function PawnUI_SwapCompareItems() + local Item1, Item2 = PawnUIComparisonItems[1], PawnUIComparisonItems[2] + PlaySound("igMainMenuOptionCheckBoxOn") + -- Set the right item to nil first so that unnecessary comparisons aren't performed. + PawnUI_SetCompareItem(2, nil) + PawnUI_SetCompareItem(1, Item2) + PawnUI_SetCompareItem(2, Item1) +end + +-- Performs an item comparison. If the item in either index 1 or index 2 is currently empty, no +-- item comparison is made and the function silently exits. +function PawnUI_CompareItems() + -- Before doing anything else, clear out the existing comparison data. + PawnUICompareItemScore1:SetText("") + PawnUICompareItemScore2:SetText("") + PawnUICompareItemScoreDifference1:SetText("") + PawnUICompareItemScoreDifference2:SetText("") + PawnUICompareItemScoreHighlight1:Hide() + PawnUICompareItemScoreHighlight2:Hide() + PawnUICompareItemScoreArrow1:Hide() + PawnUICompareItemScoreArrow2:Hide() + PawnUIFrame_CompareSwapButton:Hide() + PawnUI_DeleteComparisonLines() + + -- There must be a scale selected to perform a comparison. + PawnUI_EnsureLoaded() + if (not PawnUICurrentScale) or (PawnUICurrentScale == PawnUINoScale) then return end + + -- There must be two valid comparison items set to perform a comparison. + local Item1, Item2 = PawnUIComparisonItems[1], PawnUIComparisonItems[2] + if Item1 or Item2 then PawnUIFrame_CompareSwapButton:Show() end + if (not Item1) or (not Item2) then return end + + -- We have two comparison items set. Do the compare! + local ItemStats1 = Item1.UnenchantedStats + local ItemSocketBonusStats1 = Item1.UnenchantedSocketBonusStats + local ItemStats2 = Item2.UnenchantedStats + local ItemSocketBonusStats2 = Item2.UnenchantedSocketBonusStats + local ThisScale = PawnCommon.Scales[PawnUICurrentScale] + local ThisScaleValues = ThisScale.Values + + -- For items that have socket bonuses, we actually go through the list twice -- the first loop goes until we get to + -- the place in the list where the socket bonus should be displayed, and then we pause the first loop and go into + -- the second loop. Once the second loop completes, we return to the first loop and finish it. + if (not ItemStats1) or (not ItemStats2) then return end + local CurrentItemStats1, CurrentItemStats2 = ItemStats1, ItemStats2 + local InSocketBonusLoop + local FinishedSocketBonusLoop + + local StatCount = #PawnStats + local LastFoundHeader + local i = 1 + while true do + if i == PawnUIStats_SocketBonusBefore and not FinishedSocketBonusLoop and not InSocketBonusLoop then + -- If we're still in the outer loop, and we've reached the point in the stat list where socket bonuses should be inserted, enter + -- the inner loop. + InSocketBonusLoop = true + i = 1 + CurrentItemStats1, CurrentItemStats2 = ItemSocketBonusStats1, ItemSocketBonusStats2 + LastFoundHeader = PawnUIFrame_CompareSocketBonusHeader_Text + elseif i > StatCount then + if FinishedSocketBonusLoop then + -- We've finished the outer loop, so exit. + break + else + -- We've finished the inner loop, so return to the outer loop. + InSocketBonusLoop = nil + FinishedSocketBonusLoop = true + i = PawnUIStats_SocketBonusBefore + if i > StatCount then break end + CurrentItemStats1, CurrentItemStats2 = ItemStats1, ItemStats2 + LastFoundHeader = nil + end + end + + local ThisStatInfo = PawnStats[i] + VgerCore.Assert(ThisStatInfo, "Failed to find stat info at PawnStats[" .. i .. "]") + local Title, StatName = ThisStatInfo[1], ThisStatInfo[2] + + -- Is this a stat header, or an actual stat? + if StatName then + -- This is a stat name. Is this stat present in the scale AND one of the items? + local StatValue = ThisScaleValues[StatName] + local Stats1, Stats2 = CurrentItemStats1[StatName], CurrentItemStats2[StatName] + if StatValue and (Stats1 or Stats2) then + -- We should show this stat. Do we need to add a header first? + if LastFoundHeader then + PawnUI_AddComparisonHeaderLine(LastFoundHeader) + LastFoundHeader = nil + end + -- Now, add the stat line. + local StatNameAndValue = Title .. " @ " .. format("%g", StatValue) + PawnUI_AddComparisonStatLineNumbers(StatNameAndValue, Stats1, Stats2) + end + else + -- This is a header; remember it. (But, for socket bonuses, ignore all headers.) + if not InSocketBonusLoop then LastFoundHeader = Title end + end + + -- Increment the counter and continue. + i = i + 1 + if i > 1000 then + VgerCore.Fail("Failed to break out of item comparison loop!") + break + end + end + LastFoundHeader = PawnUIFrame_CompareOtherInfoHeader_Text + + -- Add item level information if the user normally has item levels visible. + local Level1, Level2 = Item1.Level, Item2.Level + if not Level1 or Level1 <= 1 then Level1 = nil end + if not Level2 or Level2 <= 1 then Level2 = nil end + if GetCVar("showItemLevel") == "1" and ((Level1 and Level1 > 0) or (Level2 and Level2 > 0)) then + if LastFoundHeader then + PawnUI_AddComparisonHeaderLine(LastFoundHeader) + LastFoundHeader = nil + end + PawnUI_AddComparisonStatLineNumbers(PawnLocal.ItemLevelTooltipLine, Level1, Level2) + end + + -- Add asterisk indicator. + if PawnCommon.ShowAsterisks ~= PawnShowAsterisksNever then + local Asterisk1, Asterisk2 + if Item1.UnknownLines then Asterisk1 = PawnUIFrame_CompareAsterisk_Yes end + if Item2.UnknownLines then Asterisk2 = PawnUIFrame_CompareAsterisk_Yes end + if Asterisk1 or Asterisk2 then + if LastFoundHeader then + PawnUI_AddComparisonHeaderLine(LastFoundHeader) + LastFoundHeader = nil + end + PawnUI_AddComparisonStatLineStrings(PawnUIFrame_CompareAsterisk, Asterisk1, Asterisk2) + end + end + + -- Update the scrolling stat area's height. + PawnUI_RefreshCompareScrollFrame() + + -- Update the total item score row. + local ValueFormat = "%." .. PawnCommon.Digits .. "f" + local r, g, b = VgerCore.HexToRGB(PawnCommon.Scales[PawnUICurrentScale].Color) + if not r then r, g, b = VgerCore.Color.BlueR, VgerCore.Color.BlueG, VgerCore.Color.BlueB end + local _, Value1 = PawnGetSingleValueFromItem(Item1, PawnUICurrentScale) + local _, Value2 = PawnGetSingleValueFromItem(Item2, PawnUICurrentScale) + local Value1String, Value2String + if Value1 then Value1String = format(ValueFormat, Value1) else Value1 = 0 end + if Value2 then Value2String = format(ValueFormat, Value2) else Value2 = 0 end + if Value1 > 0 then + PawnUICompareItemScore1:SetText(Value1String) + PawnUICompareItemScore1:SetVertexColor(r, g, b) + if Value1 > Value2 then + PawnUICompareItemScoreDifference1:SetText("(+" .. format(ValueFormat, Value1 - Value2) .. ")") + PawnUICompareItemScoreHighlight1:Show() + PawnUICompareItemScoreArrow1:Show() + end + end + if Value2 > 0 then + PawnUICompareItemScore2:SetText(Value2String) + PawnUICompareItemScore2:SetVertexColor(r, g, b) + if Value2 > Value1 then + PawnUICompareItemScoreDifference2:SetText("(+" .. format(ValueFormat, Value2 - Value1) .. ")") + PawnUICompareItemScoreHighlight2:Show() + PawnUICompareItemScoreArrow2:Show() + end + end +end + +-- Deletes all comparison stat and header lines. +function PawnUI_DeleteComparisonLines() + for i = 1, PawnUITotalComparisonLines do + local LineName = "PawnUICompareStatLine" .. i + local Line = getglobal(LineName) + if Line then Line:Hide() end + setglobal(LineName, nil) + setglobal(LineName .. "Name", nil) + setglobal(LineName .. "Quantity1", nil) + setglobal(LineName .. "Quantity2", nil) + setglobal(LineName .. "Difference1", nil) + setglobal(LineName .. "Difference2", nil) + end + PawnUITotalComparisonLines = 0 + PawnUI_RefreshCompareScrollFrame() +end + +-- Adds a stat line to the comparison stat area, passing in the strings to use. +function PawnUI_AddComparisonStatLineStrings(StatNameAndValue, Quantity1, Quantity2, Difference1, Difference2) + local Line, LineName = PawnUI_AddComparisonLineCore("PawnUICompareStatLineTemplate") + getglobal(LineName .. "Name"):SetText(StatNameAndValue) + getglobal(LineName .. "Quantity1"):SetText(Quantity1) + getglobal(LineName .. "Quantity2"):SetText(Quantity2) + getglobal(LineName .. "Difference1"):SetText(Difference1) + getglobal(LineName .. "Difference2"):SetText(Difference2) + Line:Show() +end + +-- Adds a stat line to the comparison stat area, passing in the numbers to use. It is acceptable to use nil for either or both +-- of the numbers. Differences are calculated automatically. +function PawnUI_AddComparisonStatLineNumbers(StatNameAndValue, Quantity1, Quantity2) + local QuantityString1 = PawnFormatShortDecimal(Quantity1) + local QuantityString2 = PawnFormatShortDecimal(Quantity2) + local Difference1, Difference2 + if not Quantity1 then Quantity1 = 0 end + if not Quantity2 then Quantity2 = 0 end + if Quantity1 > Quantity2 then + Difference1 = "(+" .. PawnFormatShortDecimal(Quantity1 - Quantity2) .. ")" + elseif Quantity2 > Quantity1 then + Difference2 = "(+" .. PawnFormatShortDecimal(Quantity2 - Quantity1) .. ")" + end + + PawnUI_AddComparisonStatLineStrings(StatNameAndValue, QuantityString1, QuantityString2, Difference1, Difference2) +end + +-- Adds a header line to the comparison stat area. +function PawnUI_AddComparisonHeaderLine(HeaderText) + local Line, LineName = PawnUI_AddComparisonLineCore("PawnUICompareStatLineHeaderTemplate") + local HeaderLabel = getglobal(LineName .. "Name") + HeaderLabel:SetText(HeaderText) + Line:Show() +end + +-- Adds a line to the comparison stat area. +-- Arguments: Template +-- Template: The XML UI template to use when creating the new line. +-- Returns: Line, LineName +-- Line: A reference to the newly added line. +-- LineName: The string name of the newly added line. +function PawnUI_AddComparisonLineCore(Template) + PawnUITotalComparisonLines = PawnUITotalComparisonLines + 1 + local LineName = "PawnUICompareStatLine" .. PawnUITotalComparisonLines + local Line = CreateFrame("Frame", LineName, PawnUICompareScrollContent, Template) + Line:SetPoint("TOPLEFT", PawnUICompareScrollContent, "TOPLEFT", 0, -PawnUIComparisonLineHeight * (PawnUITotalComparisonLines - 1)) + return Line, LineName +end + +-- Updates the height of the comparison stat list scroll area's inner frame. Call this after adding or removing a block of +-- comparison lines to ensure that the scroll area is correct. +function PawnUI_RefreshCompareScrollFrame() + PawnUICompareScrollContent:SetHeight(PawnUIComparisonLineHeight * PawnUITotalComparisonLines + PawnUIComparisonAreaPaddingBottom) + if PawnUITotalComparisonLines > 0 then + PawnUICompareMissingItemInfoFrame:Hide() + PawnUICompareScrollFrame:Show() + else + PawnUICompareScrollFrame:Hide() + PawnUICompareMissingItemInfoFrame:Show() + end +end + +-- Links an item in chat. +function PawnUILinkItemInChat(Item) + if not Item then return end + local EditBox = DEFAULT_CHAT_FRAME.editBox + if EditBox then + if not EditBox:IsShown() then + EditBox:SetText("") + EditBox:Show() + end + EditBox:Insert(Item.Link) + else + VgerCore.Fail("Can't insert item link into chat because the edit box was not found.") + end +end + +-- Called when one of the two upper item slots are clicked. +function PawnUICompareItemIcon_OnClick(Index) + PlaySound("igMainMenuOptionCheckBoxOn") + + -- Are they shift-clicking it to insert the item into chat? + if IsModifiedClick("CHATLINK") then + PawnUILinkItemInChat(PawnUIComparisonItems[Index]) + return + end + + -- Are they dropping an item from their inventory? + local InfoType, Info1, Info2 = GetCursorInfo() + if InfoType == "item" then + ClearCursor() + PawnUI_SetCompareItem(Index, Info2) + if Index == 2 then PawnUI_AutoCompare() end + return + end + + -- Are they dropping an item from a merchant's inventory? + if InfoType == "merchant" then + ClearCursor() + local ItemLink = GetMerchantItemLink(Info1) + if not ItemLink then return end + PawnUI_SetCompareItem(Index, ItemLink) + if Index == 2 then PawnUI_AutoCompare() end + return + end +end + +-- Shows the tooltip for an item comparison slot. +function PawnUICompareItemIcon_TooltipOn(Index) + -- Is there an item set for this slot? + local Item = PawnUIComparisonItems[Index] + if Item then + if Index == 1 then + GameTooltip:SetOwner(PawnUICompareItemIcon1, "ANCHOR_BOTTOMLEFT") + elseif Index == 2 then + GameTooltip:SetOwner(PawnUICompareItemIcon2, "ANCHOR_BOTTOMRIGHT") + end + GameTooltip:SetHyperlink(Item.Link) + end +end + +-- Hides the tooltip for an item comparison slot. +function PawnUICompareItemIcon_TooltipOff() + GameTooltip:Hide() +end + +-- Sets the left item to the item depicted in the "currently equipped" shortcut button. +function PawnUICompareItemShortcut_OnClick(ShortcutIndex, Button) + PlaySound("igMainMenuOptionCheckBoxOn") + + -- Are they shift-clicking it to insert the item into chat? + if IsModifiedClick("CHATLINK") then + PawnUILinkItemInChat(PawnUIShortcutItems[ShortcutIndex]) + return + end + + -- Nope; they want to set the compare item. + local Index = 1 + if Button == "RightButton" then Index = 2 end + PawnUI_SetCompareItem(Index, PawnUIShortcutItems[ShortcutIndex]) +end + +-- Shows the tooltip for the shortcut button. +function PawnUICompareItemShortcut_TooltipOn(ShortcutIndex) + local Item = PawnUIShortcutItems[ShortcutIndex] + if Item then + GameTooltip:SetOwner(getglobal("PawnUICompareItemShortcut" .. ShortcutIndex), "ANCHOR_TOPLEFT") + local UnenchantedLink = PawnUnenchantItemLink(Item.Link) + if not UnenchantedLink then UnenchantedLink = Item.Link end + GameTooltip:SetHyperlink(UnenchantedLink) + end +end + +-- Hides the tooltip for the shortcut button. +function PawnUICompareItemShortcut_TooltipOff() + GameTooltip:Hide() +end + +------------------------------------------------------------ +-- Gems tab +------------------------------------------------------------ + +function PawnUI_InitGemsTab() + -- Each time the gems tab is shown, immediately refresh its contents. + PawnUI_ShowBestGems() +end + +-- When GemQualityDropDown is first shown, initialize it. +local PawnUIFrame_GemQualityDropDown_IsInitialized = false +function PawnUIFrame_GemQualityDropDown_OnShow() + if PawnUIFrame_GemQualityDropDown_IsInitialized then return end + PawnUIFrame_GemQualityDropDown_IsInitialized = true + + UIDropDownMenu_SetWidth(PawnUIFrame_GemQualityDropDown, 140) + PawnUIFrame_GemQualityDropDown_Reset() +end + +-- Resets GemQualityDropDown. +function PawnUIFrame_GemQualityDropDown_Reset() + UIDropDownMenu_Initialize(PawnUIFrame_GemQualityDropDown, PawnUIFrame_GemQualityDropDown_Initialize) +end + +-- Function used by the UIDropDownMenu code to initialize GemQualityDropDown. +function PawnUIFrame_GemQualityDropDown_Initialize() + if PawnUICurrentScale == PawnUINoScale then return end + + + -- Add the item quality levels to the dropdown. + local QualityData + for _, QualityData in pairs(PawnGemQualityLevels) do + UIDropDownMenu_AddButton({ + func = PawnUIFrame_GemQualityDropDown_ItemClicked, + value = QualityData[1], + text = QualityData[2], + }) + end +end + +function PawnUIFrame_GemQualityDropDown_ItemClicked(self) + local QualityLevel = self.value + PawnSetGemQualityLevel(PawnUICurrentScale, QualityLevel) + PawnUI_ShowBestGems() +end + +function PawnUIFrame_GemQualityDropDown_SelectQualityLevel(QualityLevel) + UIDropDownMenu_SetSelectedValue(PawnUIFrame_GemQualityDropDown, QualityLevel) + + -- Painfully stupid: manually update the text on the dropdown to handle the case where the + -- user has just switched scales and the gem quality level needs to be updated. + local QualityData + for _, QualityData in pairs(PawnGemQualityLevels) do + if QualityData[1] == QualityLevel then + UIDropDownMenu_SetText(PawnUIFrame_GemQualityDropDown, QualityData[2]) + return + end + end +end + +function PawnUI_ShowBestGems() + -- Always clear out the existing gems, no matter what happens next. + PawnUI_DeleteGemLines() + if not PawnUICurrentScale or PawnUICurrentScale == PawnUINoScale then return end + + -- Update the gem list for this scale. + PawnUIFrame_GemQualityDropDown_SelectQualityLevel(PawnGetGemQualityLevel(PawnUICurrentScale)) + + -- If no scale is selected, we can't show a gem list. (This is a valid case!) + if not PawnScaleBestGems[PawnUICurrentScale] then + VgerCore.Fail("Failed to build a gem list because no best-gem data was available for this scale.") + return + end + + -- Otherwise, we're good -- show the gem list. + local ShownGems = false + + if #(PawnScaleBestGems[PawnUICurrentScale].RedSocket) > 0 then + PawnUI_AddGemHeaderLine(format(PawnUIFrame_FindGemColorHeader_Text, RED_GEM)) + for _, GemData in pairs(PawnScaleBestGems[PawnUICurrentScale].RedSocket) do + PawnUI_AddGemLine(GemData.Name, GemData.Texture, GemData.ID) + end + ShownGems = true + end + + if #(PawnScaleBestGems[PawnUICurrentScale].YellowSocket) > 0 then + PawnUI_AddGemHeaderLine(format(PawnUIFrame_FindGemColorHeader_Text, YELLOW_GEM)) + for _, GemData in pairs(PawnScaleBestGems[PawnUICurrentScale].YellowSocket) do + PawnUI_AddGemLine(GemData.Name, GemData.Texture, GemData.ID) + end + ShownGems = true + end + + if #(PawnScaleBestGems[PawnUICurrentScale].BlueSocket) > 0 then + PawnUI_AddGemHeaderLine(format(PawnUIFrame_FindGemColorHeader_Text, BLUE_GEM)) + for _, GemData in pairs(PawnScaleBestGems[PawnUICurrentScale].BlueSocket) do + PawnUI_AddGemLine(GemData.Name, GemData.Texture, GemData.ID) + end + ShownGems = true + end + + if #(PawnScaleBestGems[PawnUICurrentScale].MetaSocket) > 0 then + PawnUI_AddGemHeaderLine(PawnUIFrame_FindGemColorHeader_Meta_Text) + for _, GemData in pairs(PawnScaleBestGems[PawnUICurrentScale].MetaSocket) do + PawnUI_AddGemLine(GemData.Name, GemData.Texture, GemData.ID) + end + ShownGems = true + end + + if not ShownGems then + PawnUI_AddGemHeaderLine(PawnUIFrame_FindGemNoGemsHeader_Text) + end + + PawnUI_RefreshGemScrollFrame() +end + +-- Deletes all gem lines. +function PawnUI_DeleteGemLines() + for i = 1, PawnUITotalGemLines do + local LineName = "PawnUIGemLine" .. i + local Line = getglobal(LineName) + if Line then Line:Hide() end + setglobal(LineName, nil) + setglobal(LineName .. "Icon", nil) + setglobal(LineName .. "Name", nil) + setglobal(LineName .. "Highlight", nil) + end + PawnUITotalGemLines = 0 + PawnUI_RefreshGemScrollFrame() +end + +-- Adds a gem line to the gem list area, passing in the string and icon to use. +function PawnUI_AddGemLine(GemName, Icon, ItemID) + local Line, LineName = PawnUI_AddGemLineCore("PawnUIGemLineTemplate") + Line:SetID(ItemID) + + -- Prefer data from the Pawn cache if available. It's more up-to-date if the user + -- has hovered over anything. + local Item = PawnGetItemData("item:" .. ItemID) + if Item and Item.Name then + GemName = Item.Name + Icon = Item.Texture + end + + getglobal(LineName .. "Name"):SetText(GemName) + getglobal(LineName .. "Icon"):SetTexture(Icon) + Line:Show() +end + +-- Adds a header to the gem list area. +function PawnUI_AddGemHeaderLine(Text) + local Line, LineName = PawnUI_AddGemLineCore("PawnUIGemHeaderLineTemplate") + getglobal(LineName .. "Name"):SetText(Text) + Line:Show() +end + +-- Adds a line to the gem list area. +-- Arguments: Template +-- Template: The XML UI template to use when creating the new line. +-- Returns: Line, LineName +-- Line: A reference to the newly added line. +-- LineName: The string name of the newly added line. +function PawnUI_AddGemLineCore(Template) + PawnUITotalGemLines = PawnUITotalGemLines + 1 + local LineName = "PawnUIGemLine" .. PawnUITotalGemLines + local Line = CreateFrame("Button", LineName, PawnUIGemScrollContent, Template) + Line:SetPoint("TOPLEFT", PawnUIGemScrollContent, "TOPLEFT", 0, -PawnUIGemLineHeight * (PawnUITotalGemLines - 1)) + return Line, LineName +end + +-- Updates the height of the gem list scroll area's inner frame. Call this after adding or removing a block of +-- gem lines to ensure that the scroll area is correct. +function PawnUI_RefreshGemScrollFrame() + PawnUIGemScrollContent:SetHeight(PawnUIGemLineHeight * PawnUITotalGemLines + PawnUIGemAreaPaddingBottom) +end + +-- Raised when the user hovers over a gem in the Gems tab. +function PawnUIFrame_GemList_OnEnter(self) + GameTooltip:SetOwner(self, "ANCHOR_LEFT") + GameTooltip:SetHyperlink("item:" .. self:GetID()) + PawnUIFrame_GemList_UpdateInfo(self) +end + +-- Raised when the user stops hovering over a gem in the Gems tab. +function PawnUIFrame_GemList_OnLeave(self) + GameTooltip:Hide() + PawnUIFrame_GemList_UpdateInfo(self) +end + +-- Updates the name and icon for a gem in the gem list if necessary. +function PawnUIFrame_GemList_UpdateInfo(self) + -- If Icon already has a texture set, then we already have item information, so skip this. + local Icon = getglobal(tostring(self:GetName()) .. "Icon") + if Icon and not Icon:GetTexture() then + local Label = getglobal(tostring(self:GetName()) .. "Name") + local Item = PawnGetItemData("item:" .. self:GetID()) + if PawnRefreshCachedItem(Item) then + Label:SetText(Item.Name) + Icon:SetTexture(Item.Texture) + end + end +end + +-- Raised when the user clicks a gem in the Gems tab. +function PawnUIFrame_GemList_OnClick(self) + -- Are they shift-clicking it to insert the item into chat? + if IsModifiedClick("CHATLINK") then + PawnUILinkItemInChat(PawnGetItemData("item:" .. tostring(self:GetID()))) + return + end +end + +------------------------------------------------------------ +-- Options tab +------------------------------------------------------------ + +-- When the Options tab is first shown, set the values of all of the controls based on the user's settings. +function PawnUIOptionsTabPage_OnShow() + -- Tooltip options + PawnUIFrame_ShowItemIDsCheck:SetChecked(PawnCommon.ShowItemID) + PawnUIFrame_ShowIconsCheck:SetChecked(PawnCommon.ShowTooltipIcons) + PawnUIFrame_ShowExtraSpaceCheck:SetChecked(PawnCommon.ShowSpace) + PawnUIFrame_AlignRightCheck:SetChecked(PawnCommon.AlignNumbersRight) + PawnUIFrame_AsterisksList_UpdateSelection() + + -- Calculation options + PawnUIFrame_DigitsBox:SetText(PawnCommon.Digits) + PawnUIFrame_UnenchantedValuesCheck:SetChecked(PawnCommon.ShowUnenchanted) + PawnUIFrame_EnchantedValuesCheck:SetChecked(PawnCommon.ShowEnchanted) + PawnUIFrame_DebugCheck:SetChecked(PawnCommon.Debug) + + -- Other options + PawnUIFrame_ButtonPositionList_UpdateSelection() +end + +function PawnUIFrame_ShowItemIDsCheck_OnClick() + PawnCommon.ShowItemID = PawnUIFrame_ShowItemIDsCheck:GetChecked() ~= nil + PawnResetTooltips() +end + +function PawnUIFrame_ShowIconsCheck_OnClick() + PawnCommon.ShowTooltipIcons = PawnUIFrame_ShowIconsCheck:GetChecked() ~= nil + PawnToggleTooltipIcons() +end + +function PawnUIFrame_ShowExtraSpaceCheck_OnClick() + PawnCommon.ShowSpace = PawnUIFrame_ShowExtraSpaceCheck:GetChecked() ~= nil + PawnResetTooltips() +end + +function PawnUIFrame_AlignRightCheck_OnClick() + PawnCommon.AlignNumbersRight = PawnUIFrame_AlignRightCheck:GetChecked() ~= nil + PawnResetTooltips() +end + +function PawnUIFrame_AsterisksList_SetSelection(Value) + PawnCommon.ShowAsterisks = Value + PawnUIFrame_AsterisksList_UpdateSelection() + PawnResetTooltips() +end + +function PawnUIFrame_AsterisksList_UpdateSelection() + PawnUIFrame_AsterisksAutoRadio:SetChecked(PawnCommon.ShowAsterisks == PawnShowAsterisksNonzero) + PawnUIFrame_AsterisksAutoNoTextRadio:SetChecked(PawnCommon.ShowAsterisks == PawnShowAsterisksNonzeroNoText) + PawnUIFrame_AsterisksOffRadio:SetChecked(PawnCommon.ShowAsterisks == PawnShowAsterisksNever) +end + +function PawnUIFrame_DigitsBox_OnTextChanged() + local Digits = tonumber(PawnUIFrame_DigitsBox:GetText()) + if not Digits then Digits = 0 end + PawnCommon.Digits = Digits + PawnRecreateAnnotationFormats() + PawnResetTooltips() +end + +function PawnUIFrame_UnenchantedValuesCheck_OnClick() + PawnCommon.ShowUnenchanted = PawnUIFrame_UnenchantedValuesCheck:GetChecked() ~= nil + PawnResetTooltips() +end + +function PawnUIFrame_EnchantedValuesCheck_OnClick() + PawnCommon.ShowEnchanted = PawnUIFrame_EnchantedValuesCheck:GetChecked() ~= nil + PawnResetTooltips() +end + +function PawnUIFrame_DebugCheck_OnClick() + PawnCommon.Debug = PawnUIFrame_DebugCheck:GetChecked() ~= nil + PawnResetTooltips() +end + +function PawnUIFrame_ButtonPositionList_SetSelection(Value) + PawnCommon.ButtonPosition = Value + PawnUIFrame_ButtonPositionList_UpdateSelection() + PawnUI_InventoryPawnButton_Move() +end + +function PawnUIFrame_ButtonPositionList_UpdateSelection() + PawnUIFrame_ButtonRightRadio:SetChecked(PawnCommon.ButtonPosition == PawnButtonPositionRight) + PawnUIFrame_ButtonLeftRadio:SetChecked(PawnCommon.ButtonPosition == PawnButtonPositionLeft) + PawnUIFrame_ButtonOffRadio:SetChecked(PawnCommon.ButtonPosition == PawnButtonPositionHidden) +end + +------------------------------------------------------------ +-- About tab methods +------------------------------------------------------------ + +function PawnUIAboutTabPage_OnShow() + local Version = GetAddOnMetadata("Pawn", "Version") + if Version then + PawnUIFrame_AboutVersionLabel:SetText(format(PawnUIFrame_AboutVersionLabel_Text, Version)) + end +end + +------------------------------------------------------------ +-- Item socketing UI +------------------------------------------------------------ + +function PawnUI_OnSocketUpdate() + -- Find out what item it is. + local _, ItemLink = ItemSocketingDescription:GetItem() + local Item = PawnGetItemData(ItemLink) + if not Item or not Item.Values then + VgerCore.Fail("Failed to update the socketing UI because we didn't know what item was in it.") + return + end + if not Item.UnenchantedStats then return end -- Can't do anything interesting if we couldn't get unenchanted item data + + -- Add the annotation lines to the tooltip. + CreateFrame("GameTooltip", "PawnSocketingTooltip", ItemSocketingFrame, "PawnUI_FattyTooltip") + PawnSocketingTooltip:SetOwner(ItemSocketingFrame, "ANCHOR_NONE") + PawnSocketingTooltip:SetPoint("TOPLEFT", ItemSocketingFrame, "BOTTOMLEFT", 10, 30) + PawnSocketingTooltip:SetText("Pawn", 1, 1, 1) + PawnSocketingTooltip:AddLine(PawnUI_ItemSocketingDescription_Header) + + for _, Entry in pairs(Item.Values) do + local ScaleName, UseRed, UseYellow, UseBlue = Entry[1], Entry[4], Entry[5], Entry[6] + if PawnIsScaleVisible(ScaleName) then + local Scale = PawnCommon.Scales[ScaleName] + local ScaleValues = Scale.Values + local ItemStats = Item.UnenchantedStats + local TextColor = VgerCore.Color.Blue + if Scale.Color and strlen(Scale.Color) == 6 then TextColor = "|cff" .. Scale.Color end + + -- Count the number of prismatic sockets. We have to rely on the item socketing UI + -- for this, because unenchanted items don't have prismatic sockets, and enchanted items + -- have gems in those sockets. + local SocketCount = GetNumSockets() + local PrismaticSockets = 0 + for i = 1, SocketCount do + if GetSocketTypes(i) == "Socket" then PrismaticSockets = PrismaticSockets + 1 end + end + + local BestGems = "" + if UseRed or UseYellow or UseBlue then + -- Use all of a single color. + local TotalColoredSockets = 0 + if ItemStats.RedSocket then TotalColoredSockets = TotalColoredSockets + ItemStats.RedSocket end + if ItemStats.YellowSocket then TotalColoredSockets = TotalColoredSockets + ItemStats.YellowSocket end + if ItemStats.BlueSocket then TotalColoredSockets = TotalColoredSockets + ItemStats.BlueSocket end + if PrismaticSockets then TotalColoredSockets = TotalColoredSockets + PrismaticSockets end + BestGems = PawnGetGemListString(TotalColoredSockets, UseRed, UseYellow, UseBlue, ScaleName) + else + -- Use the proper colors. + if PrismaticSockets and PrismaticSockets > 0 then + -- If there are prismatic sockets, we'll try to merge them with other sockets. + UseRed, UseYellow, UseBlue = PawnGetBestGemColorsForScale(ScaleName) + end + if ItemStats.RedSocket then + local RedSockets = ItemStats.RedSocket + if UseRed and not UseYellow and not UseBlue then + RedSockets = RedSockets + PrismaticSockets + PrismaticSockets = 0 + end + if BestGems ~= "" then BestGems = BestGems .. ", " end + BestGems = BestGems .. PawnGetGemListString(RedSockets, true, false, false, ScaleName) + end + if ItemStats.YellowSocket then + local YellowSockets = ItemStats.YellowSocket + if not UseRed and UseYellow and not UseBlue then + YellowSockets = YellowSockets + PrismaticSockets + PrismaticSockets = 0 + end + if BestGems ~= "" then BestGems = BestGems .. ", " end + BestGems = BestGems .. PawnGetGemListString(YellowSockets, false, true, false, ScaleName) + end + if ItemStats.BlueSocket then + local BlueSockets = ItemStats.BlueSocket + if not UseRed and not UseYellow and UseBlue then + BlueSockets = BlueSockets + PrismaticSockets + PrismaticSockets = 0 + end + if BestGems ~= "" then BestGems = BestGems .. ", " end + BestGems = BestGems .. PawnGetGemListString(BlueSockets, false, false, true, ScaleName) + end + if PrismaticSockets and PrismaticSockets > 0 then + -- If the prismatic sockets were merged with another color, this will be skipped. + if BestGems ~= "" then BestGems = BestGems .. ", " end + BestGems = BestGems .. PawnGetGemListString(PrismaticSockets, UseRed, UseYellow, UseBlue, ScaleName) + end + end + if ItemStats.MetaSocket then + if BestGems ~= "" then BestGems = BestGems .. ", " end + BestGems = BestGems .. tostring(ItemStats.MetaSocket) .. " " .. META_GEM + end + local TooltipText = TextColor .. PawnGetScaleLocalizedName(ScaleName) .. ": |r" .. BestGems + PawnSocketingTooltip:AddLine(TooltipText, 1, 1, 1) + end + end + + -- Show our annotations tooltip. + PawnSocketingTooltip:Show() +end + +------------------------------------------------------------ +-- Interface Options +------------------------------------------------------------ + +function PawnInterfaceOptionsFrame_OnLoad() + -- NOTE: If you need anything from PawnCommon in the future, you should call PawnInitializeOptions first. + + -- Register the Interface Options page. + PawnInterfaceOptionsFrame.name = "Pawn" + InterfaceOptions_AddCategory(PawnInterfaceOptionsFrame) + -- Update the version display. + local Version = GetAddOnMetadata("Pawn", "Version") + if Version then + PawnInterfaceOptionsFrame_AboutVersionLabel:SetText(format(PawnUIFrame_AboutVersionLabel_Text, Version)) + end +end + +------------------------------------------------------------ +-- Other Pawn UI methods +------------------------------------------------------------ + +-- Switches to a tab by its Page. +function PawnUISwitchToTab(Tab) + local TabCount = #PawnUITabList + if not Tab then + VgerCore.Fail("You must specify a valid Pawn tab.") + return + end + + -- Hide popup UI. + PawnUIStringDialog:Hide() + ColorPickerFrame:Hide() + + -- Loop through all tab frames, showing all but the current one. + local TabNumber + for i = 1, TabCount do + local ThisTab = PawnUITabList[i] + if ThisTab == Tab then + ThisTab:Show() + TabNumber = i + else + ThisTab:Hide() + end + end + VgerCore.Assert(TabNumber, "Oh noes, we couldn't find that tab.") + PawnUICurrentTabNumber = TabNumber + + -- Then, update the tabstrip itself. + VgerCore.Assert(TabNumber, "Couldn't find the tab to show!") + PanelTemplates_SetTab(PawnUIFrame, TabNumber) + + -- Show/hide the scale selector as appropriate. + if PawnUIFrameNeedsScaleSelector[PawnUICurrentTabNumber] then + PawnUIScaleSelector:Show() + else + PawnUIScaleSelector:Hide() + end + + -- Then, update the header text. + PawnUIUpdateHeader() +end + +function PawnUIUpdateHeader() + if not PawnUIHeaders[PawnUICurrentTabNumber] then return end + local ColoredName + if PawnUICurrentScale and PawnUICurrentScale ~= PawnUINoScale then + ColoredName = PawnGetScaleColor(PawnUICurrentScale) .. PawnGetScaleLocalizedName(PawnUICurrentScale) .. "|r" + else + ColoredName = PawnUINoScale + end + PawnUIHeader:SetText(format(PawnUIHeaders[PawnUICurrentTabNumber], ColoredName)) +end + +-- Switches to a tab and shows the Pawn UI if not already visible. +-- If Toggle is true, close the Pawn UI if it was already visible on that page. +function PawnUIShowTab(Tab, Toggle) + if not PawnUIFrame:IsShown() then + PawnUIShow() + PawnUISwitchToTab(Tab) + elseif not Tab:IsShown() then + PlaySound("igCharacterInfoTab") + PawnUISwitchToTab(Tab) + else + if Toggle then + PawnUIShow() + else + PlaySound("igMainMenuOptionCheckBoxOn") + end + end +end + +-- Makes sure that all first-open initialization has been performed. +function PawnUI_EnsureLoaded() + if not PawnUIOpenedYet then + PawnUIOpenedYet = true + PawnUIFrame_ScaleSelector_Refresh() + PawnUIFrame_ShowScaleCheck_Label:SetText(format(PawnUIFrame_ShowScaleCheck_Label_Text, UnitName("player"))) + if not PawnCommon then + VgerCore.Fail("Pawn UI OnShow handler was called before PawnCommon was initialized.") + PawnUISwitchToTab(PawnUIHelpTabPage) + elseif not PawnCommon.ShownGettingStarted then + PawnCommon.ShownGettingStarted = true + PawnUISwitchToTab(PawnUIHelpTabPage) + else + PawnUISwitchToTab(PawnUIValuesTabPage) + end + end +end + +-- Shows a tooltip for a given control if available. +-- The tooltip used will be the string with the name of the control plus "_Tooltip" on the end. +-- The title of the tooltip will be the text on a control with the same name plus "_Label" on the +-- end if available, or otherwise the actual text on the control if there is any. If the tooltip text +-- OR title is missing, no tooltip is displayed. +function PawnUIFrame_TooltipOn(self) + local TooltipText = getglobal(self:GetName() .. "_Tooltip") + if TooltipText then + local Label + local FontString = getglobal(self:GetName() .. "_Label") + if type(FontString) == "string" then + Label = FontString + elseif FontString then + Label = FontString:GetText() + elseif this.GetText and self:GetText() then + Label = self:GetText() + end + if Label then + GameTooltip:ClearLines() + GameTooltip:SetOwner(this, "ANCHOR_BOTTOMRIGHT") + GameTooltip:AddLine(Label, 1, 1, 1, 1) + GameTooltip:AddLine(TooltipText, nil, nil, nil, 1, 1) + GameTooltip:Show() + end + end +end + +-- Hides the game tooltip. +function PawnUIFrame_TooltipOff() + GameTooltip:Hide() +end + +------------------------------------------------------------ +-- PawnUIStringDialog methods +------------------------------------------------------------ + +-- Shows a dialog containing given prompt text, asking the user for a string. +-- Calls OKCallbackFunction with the typed string as the only input if the user clicked OK. +-- Calls CancelCallbackFunction if the user clicked Cancel. +function PawnUIGetString(Prompt, DefaultValue, OKCallbackFunction, CancelCallbackFunction) + PawnUIGetStringCore(Prompt, DefaultValue, true, OKCallbackFunction, CancelCallbackFunction) +end + +-- Shows a dialog with a copyable string. +-- Calls CallbackFunction when the user closes the dialog. +-- Note: Successfully tested with strings of about 900 characters. +function PawnUIShowCopyableString(Prompt, Value, CallbackFunction) + PawnUIGetStringCore(Prompt, Value, false, CallbackFunction, nil) +end + +-- Core function called by PawnUIGetString. +function PawnUIGetStringCore(Prompt, DefaultValue, Cancelable, OKCallbackFunction, CancelCallbackFunction) + PawnUIStringDialog_PromptText:SetText(Prompt) + PawnUIStringDialog_TextBox:SetText("") -- Causes the insertion point to move to the end on the next SetText + PawnUIStringDialog_TextBox:SetText(DefaultValue) + if Cancelable then + PawnUIStringDialog_OKButton:Show() + PawnUIStringDialog_OKButton:SetText(PawnLocal.OKButton) + PawnUIStringDialog_CancelButton:SetText(PawnLocal.CancelButton) + else + PawnUIStringDialog_OKButton:Hide() + PawnUIStringDialog_CancelButton:SetText(PawnLocal.CloseButton) + end + PawnUIStringDialog.OKCallbackFunction = OKCallbackFunction + PawnUIStringDialog.CancelCallbackFunction = CancelCallbackFunction + PawnUIStringDialog:Show() + PawnUIStringDialog_TextBox:SetFocus() +end + +-- Cancels the string dialog if it's open. +function PawnUIGetStringCancel() + if not PawnUIStringDialog:IsVisible() then return end + PawnUIStringDialog_CancelButton_OnClick() +end + +function PawnUIStringDialog_OKButton_OnClick() + PawnUIStringDialog:Hide() + if PawnUIStringDialog.OKCallbackFunction then PawnUIStringDialog.OKCallbackFunction(PawnUIStringDialog_TextBox:GetText()) end +end + +function PawnUIStringDialog_CancelButton_OnClick() + PawnUIStringDialog:Hide() + if PawnUIStringDialog.CancelCallbackFunction then PawnUIStringDialog.CancelCallbackFunction() end +end + +function PawnUIStringDialog_TextBox_OnTextChanged() + if PawnUIStringDialog_TextBox:GetText() ~= "" then + PawnUIStringDialog_OKButton:Enable() + else + PawnUIStringDialog_OKButton:Disable() + end +end \ No newline at end of file diff --git a/PawnUI.xml b/PawnUI.xml new file mode 100644 index 0000000..c3b5422 --- /dev/null +++ b/PawnUI.xml @@ -0,0 +1,1473 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + PawnUIFrame_TooltipOn(this) + PawnUIFrame_TooltipOff() + + + + + + + + PawnUIFrame_TooltipOn(this) + PawnUIFrame_TooltipOff() + + + + + + + + + + + + + + + + + + + PawnUIFrame_TooltipOn(this) + PawnUIFrame_TooltipOff() + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + this:SetOwner(UIParent, "ANCHOR_NONE") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + PawnUIFrame:StartMoving() + PawnUIFrame:StopMovingOrSizing() + PawnUIFrame:StopMovingOrSizing() + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + PawnUIFrame_ShowScaleCheck_OnClick() + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + PawnUI_ScalesTab_Refresh() + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + FauxScrollFrame_OnVerticalScroll(PawnUIFrame_StatsList, offset, 16, PawnUIFrame_StatsList_Update) + PawnUIFrame_StatsList_Update() + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + PawnUIFrame_ScaleSocketOptionsList_SetSelection(true) + + + + + + + PawnUIFrame_ScaleSocketOptionsList_SetSelection(false) + + + + + + + + + + PawnUIFrame_NormalizeValuesCheck_OnClick() + + + + + + + PawnUI_ValuesTab_Refresh() + PawnUIFrame_StatsList_SelectStat(0) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + PawnUI_InitCompareTab() + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + PawnUI_InitGemsTab() + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + PawnUIFrame_ShowItemIDsCheck_OnClick() + + + + + + + + PawnUIFrame_ShowIconsCheck_OnClick() + + + + + + + + PawnUIFrame_ShowExtraSpaceCheck_OnClick() + + + + + + + + PawnUIFrame_AlignRightCheck_OnClick() + + + + + + + + PawnUIFrame_AsterisksList_SetSelection(PawnShowAsterisksNonzero) + + + + + + + PawnUIFrame_AsterisksList_SetSelection(PawnShowAsterisksNonzeroNoText) + + + + + + + PawnUIFrame_AsterisksList_SetSelection(PawnShowAsterisksNever) + + + + + + + + PawnUIFrame_TooltipOn(this) + PawnUIFrame_TooltipOff() + PawnUIFrame_DigitsBox_OnTextChanged() + + + + + + + + + PawnUIFrame_UnenchantedValuesCheck_OnClick() + + + + + + + + PawnUIFrame_EnchantedValuesCheck_OnClick() + + + + + + + + PawnUIFrame_DebugCheck_OnClick() + + + + + + + + + + PawnUIFrame_ButtonPositionList_SetSelection(PawnButtonPositionRight) + + + + + + + PawnUIFrame_ButtonPositionList_SetSelection(PawnButtonPositionLeft) + + + + + + + PawnUIFrame_ButtonPositionList_SetSelection(PawnButtonPositionHidden) + + + + + + + PawnUIOptionsTabPage_OnShow() + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + PawnUIAboutTabPage_OnShow() + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + this:RegisterEvent("VARIABLES_LOADED") + this:RegisterEvent("ADDON_LOADED") + this:RegisterEvent("PLAYER_ENTERING_WORLD") + this:RegisterEvent("PLAYER_LOGOUT") + PawnUITabList = { PawnUIScalesTabPage, PawnUIValuesTabPage, PawnUICompareTabPage, PawnUIGemsTabPage, PawnUIOptionsTabPage, PawnUIAboutTabPage, PawnUIHelpTabPage } + PanelTemplates_SetNumTabs(PawnUIFrame, #PawnUITabList) + + + PawnOnEvent(event, ...) + + + PlaySound("igCharacterInfoOpen") + PawnUI_EnsureLoaded() + + + PlaySound("igCharacterInfoClose") + + this:StartMoving() + this:StopMovingOrSizing() + this:StopMovingOrSizing() + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + PawnInterfaceOptionsFrame_OnLoad() + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Readme.htm b/Readme.htm new file mode 100644 index 0000000..5c555d6 --- /dev/null +++ b/Readme.htm @@ -0,0 +1,755 @@ + + + +Pawn + + + + +

Pawn

+ +

+ Pawn calculates scores for items that let you easily see which one is better + for you.  + It's completely customizable, and applicable to any class and situation: + for example, it can help you + discover that the ring with a higher item level but one stat you + don't want (such as spirit for shamans), or the ring with the lower item + level but all good stats.  It's that level of customization that makes + it very different from more general mods like GearScore and more specialized + mods like TankPoints.

+

+ Pawn is a mod for hardcore World of Warcraft players who agonize over + stats and itemization.  Use the included Wowhead stat weight presets, + import Pawn "scale tags" posted on forums, or start from scratch and come up + with your own valuation scales.  If you're the kind of person who + plans gear upgrades, + builds Excel spreadsheets, installs Rawr, reads Elitist Jerks... well, Pawn might just be + right up your alley.

+

+ I welcome your feedback—see the Notes section.

+

+ Installing Pawn

+

+ Pawn is installed like pretty much every other World of Warcraft mod on the + planet.  Extract the contents of the zip file to your Add-ons folder, + generally located in one of these locations:

+

+ C:\Users\Public\Games\World of Warcraft\Interface\AddOns
+ C:\Program Files\World of Warcraft\Interface\AddOns
+ C:\Program Files (x86)\World of Warcraft\Interface\AddOns +

+

How Pawn works

+

Pawn works by reading the tooltips for items in-game, and annotating them +with some useful information, based on your personal preferences.  Let's +say that you're a shaman, and someone links the once-popular Molten Core tank shield Drillborer Disk in trade chat.  With Pawn installed and set up, you might +see the following when clicking that link:

+
+
Drillborer Disk
+
Binds when picked up
+
Off Hand     Shield
+
2918 Armor
+
60 Block
+
+10 Stamina
+
Equip: When struck in combat inflicts 3 Arcane damage to the attacker.  + (?)
+
Equip: Increases your block rating by 10.
+
Equip: Increases the block value of your shield by 23.
+
 
+
Healing: 31
+
PvP: 292
+
(?) Special effects were not included in the value.
+
+

There are a couple differences between a Pawn-enhanced item tooltip and the +normal one.  The most obvious is the set of numbers at the bottom.  +I've set up Pawn to calculate two different values for each item I come across: +one for my healing gear set, and one for my PvP gear set.  +When I'm healing in raids, I don't care too much about my armor, or my block +stats.  So, this tank shield isn't very useful to me; it got a rating of 31 +points.  In contrast, in PVP and solo combat, I care a lot more about armor—maybe +someday it will help me manage to get a spell off versus a rogue before I die.  The value of this shield +to me in a PVP situation is considerably higher: 292 points.

+

What are these points?  They're exactly what I like them to be.  +Pawn lets you set up arbitrary valuation "scales" for every item you come across.  +For each scale, you get to assign a point value to each of a wide variety of stats.  Pawn +will then analyze the item for you, and quickly come up with a number score +based on the criteria that you've set up.  Without having to configure +anything, Pawn includes values appropriate for your class from Wowhead, so it's +possible you may never need to configure anything else.

+

Not every possible property of an item can be given a value.  For +example, the Drillborer Disk reflects 3 arcane damage to each enemy who hits the +shield.  This isn't a common property for items in World of Warcraft to +have, and Pawn doesn't know how to value that special effect.  It tells you +this by adding a special (?) icon to that effect on the tooltip, and then adding a helpful message +to the bottom.  When making the decision of whether or not to use Drillborer Disk, you'll need to keep that in mind; if you find another PvP +shield that also gets a rating of about 292 points, then you should choose +Drillborer, because it has an extra effect that wasn't taken into account for the +rating.

+

Let's get started.  First, log into your character, making sure that the +Pawn mod is enabled.  Once you log in, start hovering over items in +your inventory, or click links in the trade channel.  When you hover over things like herbs and ores and quest +items, you'll notice that the tooltip doesn't look any different than it used +to; that's because those items don't have stats.  When you hover over +equipment that you're wearing, though, you should see new lines at the bottom +that list your class and spec.  Without any input from you, Pawn is +assigning a score to every item in your inventory, using stat weights from +Wowhead appropriate for your class.  For example, if you're a shaman, Pawn +will enable elemental, enhancement, and resto PvE scales for your items.  +If you see two gloves with a higher resto score, then the one with the higher +score is most likely the best item for that spec.  The other item might, +however, have a higher score according to the enhancement scale.  Each +scale is independent, because each item is better for different things.

+

For items with gems or enchantments, you may see two numbers.  The +second one is the "base value" for an item, which ignores enchantments and which +gems you have in it.  Use the base values to see if an item is an upgrade +for you.

+

It's quite possible to use Pawn right "out of the box," but you +may want to customize its behavior after you try it out for a while.  +So, without further ado, let's talk about customizing Pawn.

+

Scales

+

Each of your characters has a unique set of options for Pawn, and can have +any number of valuation scales, which often (but not always) represent +different sets of gear or situations you find your character in, or different +talent specs.  A +valuation scale has two things: a name, such as "Pawn value", and a list of +stats and how many points each stat is worth.

+

The first +thing you'll need to do is decide what you'll do with Pawn.  Many +people can be perfectly happy just using the scales from Wowhead that come with +Pawn, and not need to customize a thing.  But, you can customize Pawn to do +much more.  You can make your own personal version of the Wowhead scales +with slightly tweaked stat values, import scale values from Rawr, or even create +a completely new scale:

+ +

Or, maybe, someone has already shared a Pawn scale tag with you, so that you +can use a scale that they created or found themselves.

+

Pawn Scale Tags

+

Scale tags are a handy way that you can share your Pawn scales with other +people, similar to how you can share talent specs with others just by giving +them a link to the WoW talent calculator.  A sample scale tag looks like +this:

+

+ ( Pawn: v1: "Total fire damage": SpellDamage=1, FireSpellDamage=1 )

+

Generally, they're considerably longer than that, but the overall format + is still the same.  A scale tag includes the parentheses ( ) on the + ends and everything in-between.

+

It's possible to use Pawn along with scale tags that other people have +created and never have to do any custom calculations or work yourself.  +Here's how you can use scale tags to share Pawn scales.

+

Adding a Pawn scale that someone shared with you

+

You can easily add Pawn scales that someone else shared with you on a website as a scale +tag to your own copy of Pawn.  Highlight the entire scale tab, including +the parentheses ( ), and then press Ctrl+C to copy it to the clipboard.  +Then, switch to WoW.  To access the Pawn configuration UI, open +your character sheet and inventory (the C key) and click the Pawn button +in the lower-right corner.  Or, type the following slash command:

+

+ /pawn

+

Click the Scale tab on this window, and then click Import.  A window will appear where you can paste the entire scale tag that you got +from someone else.  Press Ctrl+V to paste a scale tag from the clipboard +into this window.  Once you're done, click OK, and that scale will be added +to your copy of Pawn.

+ +

Using Rawr

+

The popular program Rawr can generate highly-customized Pawn scales for you, +ready for import.  Open Rawr, load your character, and then find the +Slot dropdown in the Comparisons tab on the +right.   Click it and select Relative stat values.  +Then, click the Export dropdown in the upper-right and click +Copy Pawn string to clipboard.  You can then use the +normal Import feature to add this scale to Pawn.  (Unfortunately, all +scales that Rawr produces will be called "Rawr", so if you use Rawr for more +than one class or spec, you'll need to rename the scale yourself.)

+

Sharing a Pawn scale with others

+

You can share one of your Pawn scales with +others by exporting it from the Scale tab of the Pawn configuration UI.  From here, choose the scale that you want to export (if you have more than +one) from the Select a scale list, and then click Export.  A window will appear containing your entire scale tag, but most of it will be +scrolled off to the left where you can't see it.  Press Ctrl+C to copy the +scale tag to your clipboard.  Then, switch to your web browser or an +instant message window, or wherever you'd like to share your Pawn scale, and +press Ctrl+V to paste the tag to that window.

+

Finding more Pawn scales

+

You can find more Pawn scales on the unofficial +Pawn Scales Resources Forum.  Or, try the Pawn page at +Curse.  +Or, build your own scale using the Wowhead stat weights as a starting point: +just click Copy on the Scale tab to get +started.

+

Setting up a custom Pawn scale for one of your characters

+

You can customize your Pawn scale in the Pawn configuration UI.  To show +it, click the Pawn button in the lower-right corner of the character inventory +window (the C key), or type the following slash command:

+

+ /pawn

+

Nobody thinks all +statistics are created equal.  Warrior tanks don't care about intellect and +spirit.  Priests don't care about strength.  You can customize Pawn to +only look at the stats that you care about.  Let's do it now.

+

The Pawn configuration UI has everything you need to make changes to your +scale, as well as import scales from other people, export them so you can share +yours with others, and create multiple new scales for different situations.  +Right now, the "Pawn value" scale is selected and ready to be modified.

+

On the left, you see a long list of all of the different item stats that Pawn +understands.  They're grouped into categories—the primary stats like +Stamina and Intellect are at the top, weapon stats are another section, +spell-related state are another, sockets for gems are another, and so on.  +The default Pawn value scale that was created for you has a value for almost +every stat.

+

So, let's make some changes.  Let's start with a new default scale +and delete the stats that we don't care +about.  Go to the Scale tab and click Empty +and give it a name to create a new one.  Now you're on the Values +tab and can customize the numbers.  If you're a melee class, you can get rid of intellect and spirit.  +To do this, click on Intellect in the list on the left (it's near the top).  +When you click on a stat, you see a little description about the stat to the +right (there's not much to say about intellect), and a box where you can type a +new value.  To get rid of intellect, either delete the number from the box, +replace it with 0, or just click Remove.  Then, choose spirit from the list and delete it too.  +You can delete any stats you don't care about, and you can change the value of +any stat in the list.  (If you find yourself removing a lot of stats, you +can also create a new empty scale and start from scratch.  That would +probably be easier than deleting everything individually.  The downside is +that you don't get to see the starting values we suggested for each stat.)

+

Cool.  You don't have to do anything complicated just yet; that should +be fine.  Your changes will take effect immediately; you can hover over new +items or click links in trade chat and you'll see updated values based on your +newly-modified scale.  Once you have thing set up the way you like them, +Pawn will be customized to exactly what you care about in items.

+

If you ever manage to really screw things up, you can click Delete to delete +the scale you're working on, and then click New default to create a new scale +from the defaults.  If you name it "Pawn value" you'll be right back where +you started.

+

Setting up a second Pawn scale

+

You aren't limited to just one scale or a few; you can set up as many +as you like.  To do this, go to the Scale tab on the Pawn configuration UI and click + +Empty to start a new scale with no values for any stat, or Defaults to start a new scale using the defaults as a starting point.  +When you have two different scales, Pawn will show two numbers on each item you +hover over or click in chat.  You can have any number of scales; just +choose the one that you want to work on in the configuration UI before you start +making changes to the stats.

+

Comparing items

+

You can use Pawn to easily compare two items.  Open the Pawn UI and +click the Compare tab.  Then, place an equippable item +from your inventory in the empty box in the upper-right corner.  Once you +do this, Pawn will automatically fill in the slot on the left with whichever +item you currently have equipped in that slot.  (For example, if you put a +cloak in the right slot, Pawn will automatically put your currently equipped +cloak in the left slot.)  In the case of trinkets and rings, you can switch +between both equipped items using buttons in the lower-left corner.

+

The Compare tab shows you a breakdown of the two items by stats, and makes it +easy to tell which item is better by showing the total Pawn value for each item, +and highlighting the item with the higher value.  Only stats in the +currently selected scale appear in the stat breakdown, so if you're viewing two +DPS axes but have a frost mage scale selected, the stat list will be pretty +empty since your frost mage probably doesn't care about agility and expertise.

+

The Compare tab always compares the base versions of items, ignoring +currently socketed gems and enchantments.  (Items with empty sockets will +get points based on the gem that Pawn suggests putting in those sockets.)

+

Comparing an item that just dropped to what you currently have

+

If you're deciding whether to roll or bid on an item, you can't pick it up +and put it in a slot in the Compare tab, but you can still easily compare it to +what you already have.  Just right-click on an item's icon in the roll +window to put it into the Compare tab.  Or, if the item was linked in trade +chat, click on the link to open the item link, and then right-click on the +window (tooltip) that appears.

+

Comparing items in AtlasLoot and other mods without clicking

+

You can also compare items without having to click on them, which is useful +for items you see in mods such as AtlasLoot.  To do this, you'll need to +set up key bindings to Compare left item and Compare +right item in the Key Bindings window.  Pawn will try to bind the +[ and ] (left bracket and right bracket) keys +to those commands if those keys aren't already bound to something else, but you +can customize the key bindings to whatever you want.

+

Once you have key bindings set up, hover over the left item and press the +Compare left item key [, and then hover over the right item and +press the Compare right item key ].  (If the item is +"unsafe" in AtlasLoot, you need to right-click it to make it safe first.)

+ +

Notes

+

Well, hopefully that's enough to get you started.  If you're interested +in customizing Pawn further, check out the Options tab of the Pawn UI, +and rest of this document.

+

Contacting the author

+

I'm interested in knowing what you think of Pawn, and what you use +it for.  Bug reports and suggestions are cool too.  The best way to contact me is on the +Pawn page at Curse, +which I check daily.  You can also contact me through in-game mail: Vger on Azjol-Nerub (US), Horde.  +(Just make sure that you keep a character on +my server and check your mail, or I can't respond!)  Also, check out my +official site, where you +can find links to all of my mods.

+

Reporting bugs

+

When reporting bugs, it's helpful to be as specific as possible.  Does +the problem always happen for you, or just sometimes?  Can you think of any +mods that you're running that might be related?  Does the problem still +occur if you disable all your mods except Pawn?  What item +does it happen on?

+

WoW now hides interface error information from you by default.  Reenabling it +in Interface Options would +be helpful; the error text includes useful information about where the error +occurred. Any information you can provide to help Vger track down the bug is great.

+

Please remember that Pawn is language-specific.  The official English +version of Pawn only works on the English version of World of Warcraft.  +The non-English versions are maintained by other people.

+

Key bindings

+

In addition to the options in the Pawn UI, you can also set a key binding to +open and close the Pawn UI.  Look for it in the list of key bindings under +"Pawn."

+

Making a backup

+

You can back up all of your custom scales.  Just type /pawn +backup in the chat box, and a window will appear.  Press Ctrl+C to +copy its contents to the clipboard.  Then, create or open a file on your +computer where you'd like to save the backup, and press Ctrl+V to paste your +scales to that file.  Save the file, and now you have a backup of all of +your custom scales in case you accidentally delete them, or just want to share +them all with someone else.

+

Note: The scale Import feature only lets you import a single +scale at a time, so to restore your scales from this backup you'll have to copy +and paste them one-by-one.

+

You can also back up your SavedVariables file.  Open your World of +Warcraft folder, and then in that location there is a folder named WTF.  +Open it, and then the folder inside it with your account name, and then the +SavedVariables folder.  Look for the file named "Pawn.lua" and save a copy +of that file to a safe location.

+

The Wowhead scales

+

The Wowhead stat weights are used with permission.  If you have feedback +on the scale values, please direct it to the appropriate +Wowhead +Theorycrafting forum threads.

+

Hiding

+

It's easy to hide any of the Wowhead scales that you don't like from your +tooltips.  Just select a scale from the list and then uncheck Show in +tooltips.

+

If you want to hide all Wowhead scales on all of your characters and have +them not even show up in the list of scales, you can delete the file Wowhead.lua +that comes with Pawn.

+

Resetting

+

It's possible to customize the colors of the Wowhead scales.  If you'd +like to undo any changes you've made to the Wowhead scales, you can execute +these two commands at a chat window:

+

/script PawnResetProviderScales()
+/reload

+

Developers

+

If you have a World of Warcraft mod that you'd like to integrate with Pawn, +please consider getting in touch with me.  I may have suggestions that will +make your life easier.  I've also made it possible for other developers to +create their own "scale providers" that can feed stat weights into Pawn just +like the Wowhead scales.  If you'd like to create your own scale provider, +take a look at Wowhead.lua, and contact me if you have any questions, or +suggestions on ways that Pawn could be improved to work with your mod better.  +(I can't, of course, guarantee that I'll make changes, but I might be able to +help.)

+

Item valuation notes

+

Here are some notes that may help you while you're setting up your Pawn +scales.

+

Gems and socket bonuses

+

+Pawn assumes that you'll fill in any item that has sockets with the gems that will maximize +that item's value, whether it's using the best gems of the correct colors to get the socket bonus, +or gems of all one color and ignoring the socket bonus. By default, Pawn will automatically assign +a value to sockets for you, and will update those values as you change your scale. If you prefer, +however, you can change the values assigned to sockets the same way you can change the values +of any stats. +

+

+If you open the item socketing window, you'll notice that Pawn will add its suggestions on which +gems to use to maximize the value of the item. If you socket the item with exactly those gems, +the value won't change. If you use better gems, the value will go up, and if you use worse gems, +the value will go down. You can see a full listing of which gems Pawn suggests for each of your +scales on the Gems tab of the Pawn UI. +

+

+Socket values in your scales only apply to the base version of an item.  +No points are awarded for empty sockets in the current version of an item.  +(You should gem your items and not be such a scrub!)  So, for socketed +items, the current value for the item will be lower than the base value.  This makes it easy to compare socketed items with non-socketed items +based on their potential stats—just always +compare the base values of the two items. The Compare tab already does that for you. +

+

+Meta gems are also special, since they contain both stats and a secondary effect. You can assign a +value to both parts individually, though by default Pawn will automatically pick a value for the stats +portion of the gem for you. +

+

By default, Pawn assumes that you'll use rare-quality (blue) level 80 gems.  +You can change this for each of your scales individually on the Gems tab.  The following +table shows how many stats the gems of each "tier" have.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Gems at level 70
Gem qualityNumber of base stats
White (vendor)4
Green (crafted)6
Blue (crafted)8
Epic (BoP heroic)9
Epic (raid crafted)10
Epic (BoP JC-only crafted)12
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Gems at level 80
Gem qualityNumber of base stats
Green (crafted)12
Green (perfect crafted)14
Blue (crafted)16
Epic (crafted)20
Epic (BoP JC-only crafted)34
+
+ + + + + + + + + + + + + + + + + + + + +
Gems at level 85
Gem qualityNumber of base stats
Green (crafted)40
Blue (crafted)50
Epic (BoP JC-only crafted)84
+ +

Resistances

+

For resistances, there's an "all resistances" stat and individual resistances.  The +3 All Resistances cloak + enchantment would add three points of "all resistances" to the cloak, but no points + of "fire resistance."  If you're putting together a scale for fire resistance, + give points to both "all resistances" and "fire resistances."

+

Weapon speed

+

Weapon speed can work a little differently than the other stats.  Some +people value weapon speed based on how much faster or slower a weapon is than a +particular speed.  The "speed baseline" stat (which isn't really a stat, +per se) lets you choose this baseline speed, instead of 0, which is the speed +baseline if you don't pick a different one.  For example, to give an item 1 +point for every tenth of a second slower than 2.9 seconds per swing (useful for, +say, enhancement shamans), set speed to 10 (10 = 1 / 0.1) and speed baseline to +2.9.  If you value faster weapons, pick your preferred speed baseline and then set +the value speed to be negative, because higher numbers for speed are bad for +you.

+

Speed baseline shows up in the "special weapon stats" category.

+

Special weapon stats

+

If you want to value different types of weapons differently, don't use the +regular DPS, minimum damage, maximum damage, and speed stats; instead, use the +ones in the "special weapon stats" category at the end of the list.  For +example, if you're a hunter, you might value ranged DPS much higher than melee +DPS, since most of your damage comes from ranged attacks.

+You won't want to use all of the weapon min damage, max damage, and DPS + stats all at once. +

Armor

+

Most classes and specs will want to assign a single value to armor value.  +However, feral druids and death knights have abilities and talents that multiply +their armor by a certain percentage.  These abilities only multiply armor +found on cloth (including cloaks), leather, mail, and plate armor ("base armor"), and not weapons, +trinkets, rings, necklaces, enchantments, and armor kits ("bonus armor").  These classes can value the +two types of armor separately by giving values to the appropriate stats.  +If they do, they should not assign a value to the normal "armor" stat, or armor +will be counted twice.

+

Please note that items that have bonus armor (in green text) will +have the full armor value reported as base armor even though some is considered bonus +armor by the game.  There is currently no way for mods to know how much of that armor +value is base and how much is bonus.

+

Normalizing values (like Wowhead)

+

With the "Normalize values" option disabled (the default), Pawn calculates values by multiplying each stat on +an item by the value of that stat in each of your scales.  If you enable +this option, Pawn will take that number and divide it by the sum of all +of the stat values in each of your scales.  This helps to compensate for +how some scales might use numbers that average out to about 1.0, and others use +numbers in the tens.

+

For example, if your scale were ( Stamina = 1, Intellect = 2, + Crit rating = 1 ), then Wowhead would divide the item's total value by 4.  + An item with 10 Stamina, 10 Intellect, and 20 Crit rating would have a value + of 50 with this option off, and 12.5 with this option on.

+

Special effects (?)

+

It's normal for certain special item effects to be listed with an icon (?).  +You need to decide how important that effect is to you yourself.  For +example, Pawn doesn't have a value for "Equip: Increases the effect that healing +and mana potions have on the wearer by 40%" because only a few items do that.  +You'll need to decide how to adjust that item's value yourself, based on how +much benefit you receive from that special effect.

+

Set bonuses

+

Set bonuses are completely ignored by Pawn, and they won't get the special +effect icon. You'll need +to take them into account when deciding between an item that would give you a set bonus +and an item that would not.

+

Mod support

+

Have a favorite mod that doesn't seem to work with Pawn?  Let me know.  +I may not be able to add support for your favorite, but I might be able to +suggest a replacement, or update Pawn to work better in a future version for +popular mods.

+

Mods that have been tested and work with Pawn

+

This is not a conclusive list.  If any of these mods doesn't seem to be +working with Pawn, please make sure that you have the latest version of both it +and Pawn.

+ +

Release history

+

Version 1.3.8

+ +

Version 1.3.7

+ +

Version 1.3.6

+ +

Version 1.3.5

+ +

Version 1.3.4

+ +

Version 1.3.3

+ +

Version 1.3.2

+ +

Version 1.3.1

+ +

Version 1.3

+

Important!  After upgrading to Pawn + 1.3, your Pawn settings and scales will be upgraded and will no longer be +visible in older versions of Pawn.  If you need to revert to an older version of Pawn, +use the new /pawn backup command to make a backup copy of your scales that you +can save in a file on your computer.

+ +

Older versions

+ +

Known issues and bugs

+

See the version history document for +information about known issues and bugs.

+

Future versions

+

See the version history document for a +list of some of the features I'm considering for future versions of Pawn.

+

The fine print

+

© 2006-2010 Green Eclipse.  This mod is released under the Creative Commons + +Attribution-NonCommercial-NoDerivs 3.0 license.  In short, this means +that you can use it, copy it, and share it, but you can't sell it or distribute +your own altered versions without permission. By using the mod you agree to the terms of the license. For more information, click the link.

+ + diff --git a/Textures/CompareBanner.tga b/Textures/CompareBanner.tga new file mode 100644 index 0000000000000000000000000000000000000000..32879db5a40e27dfd97a4cd42ba7e3645c8d3769 GIT binary patch literal 252559 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 od29#5*1Q(7Cf|jsb2;;v4|=ZE zZI5TX6FSeF{j+v|_0T;C`v z?>%1Edj-#Xx0p)3UGn{R%IWmm zY*i{Rd#cr!y|voQzIy#-f1~kopu79!ApeG%%~!*%*303Zo|hxNy)Q@m`d*Fo_rDq+ z79HBArcx?!4D+ceCCW!kpoSe9km zw&S>t?YfTVxv6%_^IbocPWzd3I-5@CGMRifTgc^#`CKuV3l0kTa3 z%hgJyQLT2@YCW}DvtDnu8;w@1ySu;H9B4KBds;(1J-pEIc3q8yuV( z8k!j%=95=EIXXH!Ha0glHZwLhH8HVia&lpEa;`l+y;Yi--a0e0eRjk4x!J|p*)4N( zJ2uWOZ``=FFu!|#erJ1OVOMo|Aj`p4hkVfJ-0JF?wY9Sc4qP~R z@ZzCEmku4ea`^C-BS$VDJ$mKXv1`YU-Dn>_e!c7D$(!v{r*0-rpT2SW^tCf*Zl67S zYkmE-_4V85&Rsu${#yIOg`3*Ni?=RbeErg;+m|ojyK?3J)vK>xy?XE3wTIWQKe&GV z(Ty7qZ{B=#^X9`_w;r@_-+rjxx%0;BuRVF~wKrdX{f&EfpWM6GzJLGOg9k4jJZL|7 z@buB6_M=BnA3uKa{EauB>uXg~DCj z=UliQ4oA8ok?4zXI9lYGFW@>7iHF0nP>8#H#su5&1Ho}5lHf7naGV29@hdovM%8Fk ziA1DOh#yZzBC@y}oQXsfekvSJGGH`29OfRsK;=XvqDCTSBw|IQeEQL-6N{x{u{6iA zSSAv2_*N`dh(=S9h|fg_ec>?Q3vM|R4v&Vzy`d1l zk~17ngu}z35Wlt+3U!CWJ^YHvWGK|r)z$sOZGR{<9tw?wLQ`E`^>$ZRIo#D%2!#eX z;$A($y{;~YBXN$~Uh(~*;G0}X{@Ngig$Q4}x@uirJl{S}Q45 zo4;KSg`7}`>(wJ(Tjn{hH^fWlig3|NykYrhv=WPzxq6-3b~Kvh)q8x+gd!1x>&&6o z6^+{bgw7A!v6$12#duqyMl`C&v?WRJl4XOhT3nWORW%a{IUa`~r9>jh0pF2B+=VXN@MjnF89??W*Mc`D+y%0N zuUzQ>k-UdonQ%1v{V`897UEnq8h;UuCX&%;G8W?#=q12rG%9f{&>G{R30_^2S1A`_ zF@;yaivg&hG#cd?^lD(3*BOuVqB%C=ao}ji<6bQ0f>eOZl}aQs@pw8O=Qfu}lq9L1 zOjdy`IF83l?L?xc#N)+SEE|pH1ax^Im%hSXAj{QHBnA?RAueN$7e1OujPb&UcnS4b zY%m_@#2l}sHyn$NN29~h=pc`r;;riE>$DS%j>lp%k;qsqHW!U4?pp~~H6$%Z7LL0;3`A}#B4+w{sxaE!M^~3|`fIPn`5?Kz1$Hm{9d%dyP5Z56V>xsn%xGu3+ zD<0==Js$6tBrZ;GGC7b;_9hZ7E_DN|6wQ8JR`%Ce`bX-&(tbv+YS6h~D#;kFgUmKDX)G)LE6 zU3W|~1$-4n_q>d4r<$z+CGo?vFHqSTT}VCW_iS&$u%`yD`Y*<`sX%iUbaWRlBSljXid zqM|6nlGHCreTp*4b&tnuT>3Wv1z;VG?g81c*a1#oh{WTkW3j`L z$Znv?_1ztgMEG;-vDhiNWRaO`pWxms$OhKo@Ro3RCy(3H<}`5LAkJ^*2cyxw{LW}} zAAg8H!QFWLFn>H6<)y48lSdPYeew7*gE1D{5{=F7@IV0hO3@THYJIZgR6!(pJ@iVHj1*s;g?&vN))CUbkhHJg@FJWli&KyO7Ox z8-_1QhU4TdE8X_}VgzLCx}8ep0C(H6+_+^qC{+-SP}Ox^(=^jG19WqJLIEoW2v`4z z>JFL%NJnFwsrb)jj4 zmc{k1b2*dAZqwXg+w+Dor)krYGz7SHeUmJYaGoa-KToFl5b zP1AO&>Y}PHo91CfSrqRlXZFeRE=@bcv)jz_Y(tcGv*}=9X9gN3; z0!W3yw+WDsB$J#3L;Fqhbxpe=%NJz%lBV4>jJq5plV>F9oT}bRB+kZShh+JNBwdNe zPlUq&>jW_8d^o%v9uCk8u9^If0bq_Ik^Rs#(xam>Ksb*IbP4=@Lj1rAV-t??e@#`dDau7zJ}XHl73Ewqc_bd+B}s=8iCsKtML8--`vACMoYM7^hH*mE z4ki+tRCSN*p4GJ7in1Mmo94cB`n0BPx2!!}WKG*70KVC_cNoS-%bGXMSzVvBtPQ3) zW!tdM#_jamHw3{~8kNdap)j0E zb?0(@nM^C4Zsc+;->;=ol!{f?%>i)RPN!1&Os15}RnqA^^%TXF=cT#LX7jlm;4xCE zR5qIpFb=f&2!uxf^$3?MkidL?PvczWC?^A&?J#(73(lc&4w6X(kj$)>R6slG2O$LJ zWtqvtVpd^tVTv&f<{aNJvH;#zbvalu^ ziA+^_?agE|YuiJv%atkXdN0>a*P#RmqZ_txoQfcAMs znMfpf?gbc%ay*&b4_Z|9w4#8G^MHr>J{pDGcY>uvVht)!Bp6WF zRQ0-P-m|QSnszIdV%oi-DA#TKOI>-*B z3b)T;a_T;A`Jj#qa4$ZhfIboufRA$#lQ6j|NdOxmkC;YuClE=hYN1*b1*qFxI0w=; z0K4NbFJ%C`s-|4Gs%dEeZx}hyuBw3uOD1gqFUE6=pDRAq^%_{W?LOBXg3>ju2J8)k zu@8(Jn%1KzO+`Vblq_q~apq7hhB59q^S-~s^B|l}jsuc|>z+#Owe2NMgT@&tV7^b5 zr(73=uVgZ3Q>o*cwg})=l^;BnPG9i6Glp@X!|kSd%ruW%)+x{q%h~q%RO*J~T-Nnd zrg>hL0XfXigOA(xEz7zF?2!Yidd@Je$?{1_I;Ck>WceIur+h@Xz~-uY)p6d0+->_Y zIJd0Fn)Z4kfiyX!>kj~VD)o-0J%HJx(WBw;=2+}RGa!v|x zFLfwC8a)z=9goM)Gd(aX@Z-tk<#_x!a|VBczi!)aG1eGyj`PTI9)fqCA&`&9cWc@? zAkS-&<->3RJbsFo$_qBl(}r=B3*mX^QFEHMCzrd@YCX2B{mJA6sQ0`>w!PP~c6#1E z)7^j-wnSPi_QN2$!2q?Qfa~O znM}d9J@DS&KO`)(0+25jON}sc9b!8`HiAXm1n>?xJ+LWYacsNkSb^|ND}g999CD=K3$*4=U13R zfx720r3T7T*XKYxZ-V3Oa^1b~JW9s*kHYG3lWnhNv*+^pOQ7BN*K@h6nat%(1`0%c zPbtc>=UwvstA@d#IIOBn0het1f~K9Ytc!+m*0L_EDtvy%^InU`cOqrtamvYQP2;ED zLMo{0dBeDEnh^CpSzf37lcaUz1b9QlfW3739i$8zBNhXw0pJbeG03C{?dmcpLg8ri zIg*?S^vtXz-3H^L;^f@6X!H=+hv!8x%3tPyq7o%?Ar?DH1uDz86y+AWC=yu&?ED=? zVTQSCS^S+ls4SkNrd?)e^2Eh7#e4m&TlPp2;q3_SNd z<~Ic3LDSrA+xu*Lw`ned^;Bxbwzu2%Hr@d!$FhL<_BLSe1L~>Ng6q!MHiW&{-Mw0= zEV=GzF1N8(+mX-DW-`2sP0#Dk<;L^*iCk`^Smbs{uzPPd+gquOmP&)=@?b94^!-Y; zI#?{WGMQ?#$s@a=_P)N+Mx(D-tk&zTR;#a2DChG5&SCd}^D~*3Kt0eh0j&85XdM0y z%GH3!xhF8sVSw`v=D8z8I(j>aFi|n+Vw9v%JW@u`I@JK!=FFAgav$tXN2}Ob7`78i-bem;SjUbS#sCKGPbPP~elnPP?)sE&<)oxLtGFqo#_KwY> zD7}U;1<+x3N>a~*)6v|Mz#Xb_9Nv?izKhH=ue&MC@4G>mD&-e(Qtf@xmzygQ0=46HDg&!WbW zA?vmcx?eMl8xSY@1s+8v$70K|*zQE)C^aEcTGQ^xGBCRW0tLEJVIX-e7NcA|3oD00 zb3i_kxB_mI$!kzJ%q`1z0KF(aaYK%ACXu+r5D@Y`P&J4gNkac$i^UEyQONR5Nn%9a z_PiI&A+gv#hMJ;W()C+n+Ch$;kR+r!LhMvBiP%Sw9OK0SbzKjJII$3d3`@(n3x7!6&o!|alTKb1x0c^-8RhDt+{a081-cH5prvSTO8 zG8H{GAY*9S_m|rMJ3x%86EJ}kJA09jaqq*+ob|!PX4-Kp9z~BrpV2(%ILh68>X&9?dn4f>bDUs_2`J^Ga0_J?CL`Bs{?95t76+eaO&Z3O>U#dyq=K zL^3g(c-~XjeJq~GY1e&dS$9xeJat_M{m7Wpj&sBFZkgt~C{d5X>7IwQJI6KgytC-_ zT<$U`cU@q8K-0Dw#`a9+Xt{i@T0NJ^9L!|aa=8Q9?EXwbn4SWVUzEVrBXxHDuSND(1(7><$(JjlD$|Q?EpQS?Wt6TYqgO|WvEsg zE))b=Ohra5}}A4LE1PL7gDMF>GUJd zyYKstkOTnoUklzI{RMqUUb<~mr1&~Y1+$&1K! znm(raCMZUcB$HP`Jp8UG@bVc{3uiinZWvF{G<=uyi~tJQc>G)>vI5M(JMu--?n3K~ zD@{X=JapZ+aE0*DDIyq+sni?U>@(A(TD|6ZuN%fi+rGw}1=JbU5W44~*{|gDRMY3F zP9X&33)7(IG3HU|h&l?NV}-)0Lg8egaNPIzmP*IU<>RPz9D`hL544`mE|tqG_zCEj zY<9Dd>YGa?_(z5mz3KxCF9)zCc>i^syQgd$MGqZbWyUv*%dU%7 zK@uZsu1nG}te1|9PUWX**F`;wO}-b~57Py~jIO|Pl%(_MXL>~Nxu)I2GQo9Jlsmwa zk~A7UjQobU@l`q)r{YAVpkbhI(|kr)14MHiaJ=?dQSO2CWbz6>$Xvkhu`FtZE6f-C z#d!P(x9Rjtq!ikT>Vb(Sm!lbT8JpDe?ow*d9boYC8dIrTOkR$20nKLHr!9-poU$gU z+7Wrsf?+TRLK+nJds3-2L`yDrv|3&7?!ExPp>x;yg{!+_)I8>P8M0N!;c1mH0&DO^WjbX^~Y z({W%#>!=%(%W<{}Hpk4GQ&qJ5_Ec&W>0;ZvBnck|s@#Q0$AQ6J$0+tZ_=oBEWQWjm zx$C*y4c~{#F1c>N=DLn%$6{lChrR=q4&nvx0fnokc^S|%`=irAJeH#n>t`72C!Z#h zhv0NDPxB}orcZ+hc?NwB#Zn$Z>a=#cx@euzAv%h#2JnK3@dtSfm_LZ{2G7trkQS~w zqhkr_j-Fu@aGuK4ve25x;eOYB+c4e|e#~xooyh@G7pDCw{vs*{`$yMr^JIhvgX(^) z>wurC^DM8xG?_GxV=onpl*Y4!#J2K$8$SO@944u7YG_manL z1n&?{c^36TS!$XsUGK(i6y7=(C0zuR-=OFuA#5-rdl2frzeUqH?nk;y5;iS8Riw@w zZBonHg4^i2yCC;$_GmiIJ440FY_S8S4%YD*QJh%C>GZjD8nRroEGjC{jJt3Odrj95 zAzpa5fjcCZN@0#8VUgySJ4OFVuE za*sqv2kb>_U8h$}aT;_`Fa(PR!xUo>62AwXIX3OQ- zYIP&zj{QO>DVIa3Ak9etZtd&CpX>qbcrPHmqtvNM%jMSK;3Tx3&sUHyaC%?gAV@El ztJp7y^)^TkV2)@Jz0v^g0jWO+@;Sbu&p|f{0t5@U9i#(uVJ!wAht!D!a90Fo0`G-N z6h0RxX~%k$q?j;2@OcK7vvZKT>(&gT0MuO<4yM|K?8~4X-A*S7A)~4okH8+=6v&dd zpwC48NvRQtby~|Eh~qL+=rBJrx|%5h7Pu}zom>kpm+Mlm@o*e=nn|;!xecGu^HyD# zV(BE_i{q?$9^gOCyM{;?oPMND2MM1Bk`F|Ts-osEqSO&FnChlUm5SYV86ep9Wnk{Q z*y$YKM90uY0x!P*IF&+7pd=1cktzzR8_vaMq`QQXi0g+rxE1X}T^fndipMSA1);~| znB??F2Qgfb>(r$XcRY?|gRFt;nG$dkF;;FX%3afZ3e1H#N7LMb+|e!_-q&^N&3nSW zAuTgbManVa-*KL#QqPf1uKUFE=v$q|8N1z^PCwCg;CriHe~*4CB{naPVeEM~#4Dx( z*h#fQNs5L6>yk9@c_+HNk$I?^y#nYIKEa&n`)jEbozh+mY3Hq;My;Q5C zRHP`Ffq~iXZXEbly$;|R21|K#xzi;GkF;KLfe#@6x79HG(HzVJAg!FpjTjdl31{Ln~JnaghkYs zqyg|=NG{-~P?BP*FLB_y?|^Y00MCn|u#LMsj0aoRQ%X_ z8;btXfmG_C>(U#d0@zGG08ia8Hei1(A-GMjp-I4w(Sp%PLE7C-lsZr_{rv+Njnt$+fIW~dV4T|k_R-E^fOnzT1Do-C#}SI> z4xkTY4B`a|A8r|^9(~(=gX_7=t3ePdKvckW17FT@uEU9RPMQC89VSOOFh15%DeJCnzfR-?9tBjStcc!5 zqdS1Sh{4GXo{&y~wdV7^q^)@to5l@?m9T|GT0`!UCXr)G-9DKMW- z_v6DL+0inj1iHJY5HiAV9KmvIHphE=Cr~JTYA#nT6aaaDZ|`uaRHKcQ&zG9bUhrNj zm60;=J4I>$`R4%q6+jO{0NhhUej)RN!)AUj#Qbjg}~6k6iBo=s41cIH3ZgJ1bvuzpOm!HgTs}$%18N85M7|6IZ=LS`7G2+hHAZ{` zMz^eeyic$b%@BMA)8u_3787`l6k+s_cx&m2W3>7Hb&>%nb^&oj3{4}n3vN0=nX{lA zuMr(%n%5KsU;diw-WOO$rcl6vp|jEGHZUHEFx#vX`-;aYH@AX0S~{q85vOHRqK$*4 zh!4njNoySh=_FvlE<{edRA`#@&Mkc;=$vAd2f*|&I+!OX#^iuMPcUW_SDjWV(*>0& zzf@5AoBTalraoo#T_m|cYynSHmN_`iQ%A2eSm_ZkcEt!rtTFoO4^xvei6KjF`93d| zYljC(xw3?remuEb979lLIpZpEDnKj!U2uOIC{NaA3?{U*U>S( zy?8dYe4f53tAF~d)n<1$E@KI2z0qh64be<3rPJw=k&z#89tap<4vj=YeEpu{ITg6- z;GHTz9*{SL4%E2=)Pqhc(e^;lV>KdTkTL{@!972Y5NjhKTK!;&+)?0{Rz!X7s zun)1LV5t0|behx17xJWJz(_fwW8fTQB_f7kfM}K;1M*@X#z5b1+ZYGybiQ%cQYqeJ z@J<+jZZ}}Rnoi%aEIbxG8L)E3_wQu04II0n2W3oS+L*$u-i7Z@-y4U*ndKO>icg~oF)>-sYy{W;!hDz zB=~p@u#;{i1mie3@7F{HLfDJY%NjBU8-_L#=4&Q%jfMkdySPB89{7Q)bG%eyGUHm~ z8X{vB(CieaB#=-t&<@dnRBBr`yA!a}GR058TgP`q$26P!8;#x7>JB*F^Js%0df;xL zJ&m^x-tpG+c}RMU;E$af0VK*fXvs72xnaY8I62zsH`;dyFP zI31v4HwtoB6QtGA>J+Yck!fmDh#i3QF4;K(U%H+n>SdZ`7#)M1-jN6eAW;T@xg=!) zItF_b(XP-?!f(ugb|NsE)-()k7_$9Tvq+vEc%5hfHRTKq*Fh%CY!(L*Zy7>lByD0w z(*Qs64h$;FswHUOH!S1>jpmqACom_y( zHlAjz9@q_~cUTZs2PBlHSdE}OoqkieFlZJ6wPa7AI%IVq*2#*);m8)Sj#vldsESai z9}gxLTNOPdv=#lh0!E! zQ)nHu<2H(g8aoj%$QTg~m?i{<06y`Z^cu66Vh~vH_TmC>SB2&O+*}h$5Fm zERE+&m7!_OT-c<@at24xN}wd+RqOg;q2?%DnVY5|g=+P+O67K?LJ#5uwid$B_euBe zO{WRbk>y>cP$JQby$;}WIar2N0A0MZOotJUq53QD?`Kycn78CWVVIX&YC%0m$6iY7=`ABnu0Wdx-CI{~k6~j^u40couNQd1CHzpDu zPfqCd9H}p=QhXRXsCbX01Xz*OVfW7ZE!HDan4)4>Q^(>u9Ck%vh)kR20`AG&Q-mY| z0qW9@XouY?N;}4*X|5Os0X^RAoh(8ktt#9G&K$vWq%K9V%JObzb6r1yhyeto|4=aT z_!i<(u6r9111ll}Sgs+<6q0M)f_daR_B!PsO(hnzA>F||^p0+4^@a!saDET^1v>@{ zk`5|HA+ES6RZru@U>6FmJ|uKI!-S3#y;jUetd#pOIW8oc2HNMOWl`#*Pxw8Hit-vE z8WAGH7{Bj0Pl*VSr{sV|P$GB6sH6x!#7GoMh6xDJ^Fo4tK{iW_fZhQ1Xs}GhGzoN) zJ41vZY_=oyDivfL-G}9J`D&wan{HArcY+oM9y{)rWigR1g7{o+C7nh!ZpTvF`bU%9 z^RU+m4iFF^I53Bf0q;neOa{|@61u(O z3kVr90*H3vywE&}n0&sB$zHG1P-TTbF-P*e*dI2!J-~aA5qJ*J17fF%hqv;5+mz!D z)WJNE{sH;;k1~a8{3+)cA!r&-K`Nwh=k4p?W6D`E!c zQTXg+ThJXUX&S&`?6L(@?^8Gz^ftP1kow(lpK^D>ZoEWEpdY z{@+@$Nbm#sFsCT^?Pr8sWx~hbI0V-T)I;8|H8$I2-v?GW>bn8EEEDIVmLn;PZG8v1 zj`!F}u%l&E6*At7jvy<5(=I^Aq9*7WTw@n*6N!O2h*w0}50hCWr-)%L5(60Wd#F4C zxLD^*1ds0R8?-}#`YKRotp=E96rgQX^**zJ7!kLj_jsJAeh=g`cF4+Sv+p|28&nRc zEF_KNU_;_1o$WN0SXYV{gPtMOhS!dyp|v2wZp>fDBnbx>HBPSUayHA)qW`gPj~9y< zOC@GE97eLeTyC6Qs-rn-r)+j_r+tcUXT?T3O}KqSp|FD@l`c453>~FHVS(Ohy}sCJ zES5?ek?s_!9o?Qz!|a2#8eLP2_kl)(w$UUOBz6q3UNSLczW{r)IgI&8CWZvq;NUn# zD*WDVHhaU>D$0j^Z%-S3{~;?tX&P9L+y+1g?f^b;V(1SAy(MrCtqZgVwJ9hU0|Fus z%owmveTr@|X+j0MMu_yFyM)__#mQFKf_&!qsHcpmR(g0o8(dh($vGD0Kk)&RxsRY(_ zox6J|P$^R}WH8!Uk_6T@4Q3|0x`mVg$ruq{$5*GvL`WBngJvh9#2Mel?W|f;3Pm7 zR{L5qd5*<$_>QO-0Eruel0kl4WR^fNk<@x5pne9O0;stK^SF=jJ$I>EQ9AgM;$f@w zn}9!Mt;q5+Y~S^~SJbBHcM+9;mCe3G0E{QXJi|&;o*+dA(JxBuSZp)5lq9S)<@J#P zVk#s3l}v7>xrW!CNQ`62it^?}KF>%$3DQ$346VRu7olmM$#yDK&s!3U2yoQ*kX?t} z^LY{j@cq_ud3R6GVVIq+lJ8HY)6}EWAidSvS*gtD^OK~%a2*jdsZP(*y-_ zIT*aJ)tUykO*5Nihn5CjJGLV^0V-6CM+_Nqj-5C!c00ZizeMJ0jr`G?+2ycR}o|I;tZ;#sj&g9;iIPPLS4Biuu*L1f`+0CKcD*!BpdfpZpX0ClnE zoT|0fS#-+kbYd}tz`#GuPWXt0)Sb0S6sdHRI;%Bs*po>dO9Jhii3(7sl4D{e9=63= z966K8V8R5ACERu2zm&_}a?#2^<;$CN?7;5t&K!t9+D zQ6lQdD(fv2(yDra_&U8`Rwg0ENaFLlB#EIwrN4@X6YEqMK<-MoM|)?jMvZ$y(4g6&^#JUDguH-1 zO1=Yj+WG)L2=>D9jD{bwU>(N>=>+7#I69nu5n2Xg9ZwP8v5E&t9E>bG5NpItvxa_0 z!Ieq`xoUvEjnB@mD*PgX!Zs_>JJ_dMMc#--)|l>CI@kvcI!slHi$PVN!3&R&K zXU#8DF{a1_(MMHf;S%TRD+TUMn^Ki!Ogkx5X)D3Ei&~ovGqA&qD#fqzhVg`$VbO+gW?skq6jk#U;f)@rUZpNuS;U4E%L(y^NLUa*ra&RzOQk}R zKUjpDNNj-N*Z*s0U7eWM8VK9 zzE9X|C$%buyI7yJzq@;dzEVEFsT>4kHetA9&eUo^pCXjJ`i5GKqzwE|(3gx1tC8^2 zS#?M5ksLdbui`7vf$2$Wl9-3sbW(cpkHJ+BUBAu zE^zNG>SdXdrVV4y;LX4^MA}t#0>sm1g5kw314Ic}rZmQ$4`>_OOuD{==Z=D>AHubO_MXWdiQe=md+ADOhtkTJJX~TXATJg2R&J2e<|Crg_74 zAJ9of%a8}7qeGM(#0yIjHNGPp9)sfX*6FH3!6H0xnG1cE8if(`8Y@siY9hg}q|Lex z;BmuFF)WUE3Q_^`Mf8pDe@OC?q?iCawJ_5TX|6XgBw3Kh0zX`L#uI{u#T&G3kx~d6 z5yRyAP>FMf0_HNg2cc;Knup^lQ3yXO3U(+}{1U5_BxyTv7dzxkkoJWom?ag3@aV2W z0UwK~?=jRPc4Rh-#k(z&VfO(>0+Mn|p+MkqFPbKmBDO@H5#rzEdE@o^&SrCOPY)3? z$c8#~s;6hUUZ>{@$|3X(-QAneG_;sd?+6;)8PWswI#J*rT1@3~AI;RxG9};+=nDnt zKG>bDE~-^*ce0N_o}3JJ~~S6Qo3(I4N|VMhXxCI*|C8Xwdme$pia`XDe{kmA4NlT zkbQY1i9x_(CEow-tjU9vc%#@RhBu96O6(CM%bQq}$4VSc+ox&!=`j)JhStD3P^U^2 zId2kT?0iA33PB$gT6Y=!j_)p@z6p{<#iM3ovF)A20AO#?1FOE)kgNesa4qLWG!=v)AJL=2uJ%7{iPf%iHGon{loDWiZ| z)iB<4T^26gqbJ+3G*L{ZNx1%wNGPxY&32L33A`f&7Yp(jnM_J-6hqZ5UJ^w+>INfd z7DW?}Pm7IW2E}z@T4QW8abhpB(uK}ku=bR#1@OKIXRydxC>r(>V9_4J9pMA2=_rvB zfbO@_U}8aD2kN$sX+G0vEEAL|muaqIO+wsL!gj}%8A+#mS*KJij&uYKi%&6TC{$UI zSE=*^^;)f;MS0jR==XBDoAB2k$sJ>+uMba?JXjSu1KNLpdLU;4H4~s6!TuCvchozu z?;sxA9f)_p4Vi;=s#Fd-X)tgPld~ufpo^7BgvJQbu!IQ-!j@bSoJfQWu>q95C{^kI zfOXbb7agb0Dh_-Zb}0kmY@@;EI4sHo@pRdIf1L0m3sI?6#b#euapn=?RA#phwM?%Rnz0k5E zyMX_6oToyqL6boAnXpp>4NplDk0V%cLb+<_`~h~%QItJxNPu<++MKR1+a*CLw5X|O zm=r2pTf?AfP1_X7&o$iFS(um2?k*PDXMn5>48OBbAooi+hE=MY3AW?B(`sTK&t%9B zVA_z6*+9Kot8J>+i2+ZwDOi1?zLg3K^Z+lwMb-ymhE7wxPNNk=9!sX``!G7aR9L*b z*<@xYR;w)ZC`0X-GpLzbt@Z=nUx0UT&PRZAK0x_%h&;%%W3zYsm^SK#HDvI5&_5M$ zr-Y?uh1c2g5WhY!+W|Bw0Thh5iklIg^I`2!MA87F&p5#Fd@L) zxHqgCoKB~a+lY^f7={{Hmn48b#BwFNOP)u+0;?+^bt+X|XWbG)l0uZbsPdqVO{ksK z8zLox26__hRO+%RJV^XCY~^&C<&{@TrF-;MVlhHC#|RG4S;U{f zdL%d?s#JytYmPP&_!Y~Oq4`r37>6keS;&DM&7{Z%UW6W47;y`=t|;hwqG6W+Iet6Q zTd~R477~#3@$ue8o*j&Dg^)?4cN$D6b6QIx;6-h^6UYN?%ohxHjyp;Qbi?WZpP_(; ziN#K1?;wIGQiacR0Ho(~A0T5A31}Qyv==RfO@#f(^GqhO-|>Jb8rb}a(X)UrgDE5A z8oTKs8v6}{fjQkNRoOR#i4gbJFqX;4uyljPV3IUQ6VLZoknGtkAzwl<1c50%NjhTL z%;C6~N_*Ha1_8tFNSHD`J*!A|7NlW1v!y^bi}GZB={%;4??c*CY@oqQy`!5Z}+z4T!x~YbAg=4b?`2HQ4wcDVUx7z0(f_j zjx>zDfXzc-nnqI4Hw_>@Pn4aYfFSk2VCR4}O878>$cezf^fI0dDCYno=NM{dPXXK* z>QeeitWjdY2IVN8V^AZ9m<40)nXcv5;*C=&K&|`WcUs!U=a}dIX z?iVq9tj`U}B#6X5A|b|P!=<7f1*{k1asDp;*%VbN5Qhg4E+mQAafHqs0+0@>$dJxr z#Ry8_ox#stB)Ne`XNu*`Au@m!Q!J9N9wwg6QnPe=w$2)~LIFnxe{u$lW3jVtgR&Ga zhCF+z#EKkZy3?$%c3qO`2RdSCmaej29D$VK#1K7G_-%MQAcP%MxNm*~!+0bPP2O-+}Np_5^AkGjms` zlLXg^jU_I^+lDEGfS4C)LL%#zfjIjh5vCP6M;LloAnZywJOI!;MJT-_ z(KsC;#NJUVopg-o5dmnt7#^^QI^V~xwp5Z!RGzr(A^~=uLR9$fY?y@Cj*_7h#YIDE z-|D#bB2w8$A%H?ctFT8ZNd@xtqK!ny_b`1hET}6>m`NQmM*545VJN?eNFY@h?$qXJ z6*foHb*4UKHghAtV-_cilt8ga*EO)Z@YYq8>2woyD7*uWGtI)`Kh&X!oBQo}la0{{4Tuzb&@~Oj`Acw(;0Q7TfE<7ep&|vIOSBUT z5FH}~w~9!!06nG$I0d;TqafJ5mraT3mWFX3J6%S~cKHWr(qfzibktsZHq&Ud{ z>QC^E4C57g-ZfEn2fZVbp6EAr5Fy74Eo?ysu90jMW&EZ>fnFOc7V*w$OOP<4TgqaM zzzYy-Ovwh&5?!j-*IKPZNO9nu&u=F6MV~a2q1fC~ud_szRnv4iIWvsSLI|c*VpRqk zCDHk3Y0|{Nz&x!ZR0^rDS`8r{BmyWufp=$(DG2ZHAL;9(Z5p&n%Vnz0zL61@EHMzo zilo3<6ul$h-3bK*#$tePnEU(91LABxP4&rL)*>m2#(S@J1z%|%#lgB^kZJ+BL7LaH zXo`A_05``=5p0`eZ zl_i#y%46IXEJbJy+2Xp`F*gh&SX0ePHPTU_olZKDDH1oA46(K+sO8uchJGn)et;Bq z3y>EJfiM`abwZB1{yG*27xNMq_=@Nsfu5^|@d%@YGfb5ifFvFa6AH+Ei0}sW+(meE z43~2Y@Oez)BiqbZEFm`bs0#t9}h+Q%$e;o^wh&{(-~Jd-(7tzOUP&y~wG+i+~Q zm&<3_MW9q7DsYtAvrt&8)sC0T2UwNV-F>XpI@Z^B0>HDVy07oZ(9rRbkrRD=tNs0} zqoapMM-MfdJMa}pMtE1Z;kOJ9E)Ng$xx^*{0|T39X7>eH6Iy}5EKE8EmXl{6T zZe(O`YHG{$^p=Tb;VBBwMHn&%+o2yk8Z2|G+R%>s6|G~b#gXBW{`;SgcT$q`;Iy!oOcJ|if)!X|9$-PG7eyw(|TD{k3JnHFr#((V5-OaaNjEsC#t39b!-)uD6wc4{{@o}N> zD4&0X?kks{6$)<^i_h|T7T!NB6j(v|R=ZMp*J8sJ(igSb$A!W>Eb;XHr-j0M#o`C- zvBvH!Ea%VV-Y=Iw<9{ZhBE<>ezbQ$jUbR_6lq?p1T&etoDza4iQyTn0H=X_{oBcGM z{+Jfv`x0RQwmu@y%eh?cGmdf7xyvRRqUtJUK3+Q7iA{{HJ+{n61|!^77G z2QQ6}-{8F&AHOm-c5!s{-00}~!oux^g`2ap7w6_K&CZ_Rxbghr;`PPFtMl{gJ9b=N zTDm+lb7<41WApRJwr@YZynJ?f`OMCpr+4f)zPNaF@7~j^tLrN({3jr%cI`UC{}Qsi z%>Na#wr9`5wYB5R%lr21SzTEJv_9rYjk9Ddtz+c;`r#ciSfk^ z!BE~iJ+*to%*xz`)lC}@ZP|Qce&f-Fg%b<&$LBX5+dO}I+t%}o+b=EcxUy^KtzFAE zckgkmBtN(WO%FnM~{WlJ-U;WpcH$HFQ zxbb=D*3Eyvb@TJ~?OVTCxO4j#w{QKAdw2imgZsa``}!~6dh^?d4}N|B-haOF_^XEx zfBpE;Z{K+Q+c!G5kAL&ztuLOpzkT}T%eUV8?X#y}JbU_O``MSz+h4tW@y&}D+Pr|M5qE`1qsWfAYy6KKb~&&p!Rbk3RdukAL)s z&p!R;$3Oo4pZ(;2{mGBN{n3xU`qMx8<|lvp&7b|`oB#4>-~QMC!oipS?Z1BW=YRg~ zU;X7D{^~D(|I@$t=BI!0)nEPPH-Gcj-~R32eETwox%uiO9pPv7SL`JcY|`M-Sk^MCpF=Rg1EUw{7XzyAE|&;Q4F zzxek*{QKwMe*SM?|Hm)B`;T9I`}yZz{qjG){m)u-Pa>u-Pio9}-8>#u+F>#u+No3Fq8;=A@&U;bYH>dWuG_}w?Z{mqxZ`|X!s l{O+qSe)r`UUj(;be(}|pUwrx1mtXU!ufiM#=fC{&{{XO8;{N~u literal 0 HcmV?d00001 diff --git a/Textures/CompareBarRight.tga b/Textures/CompareBarRight.tga new file mode 100644 index 0000000000000000000000000000000000000000..f98eadbc4f91ea6d43cc99f515806d86709bdeca GIT binary patch literal 26987 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 literal 0 HcmV?d00001 diff --git a/Textures/HorizontalBar.tga b/Textures/HorizontalBar.tga new file mode 100644 index 0000000000000000000000000000000000000000..c030279c90d39a4d08627a63b52856fcccc2fc5c GIT binary patch literal 14514 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�eQA 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 literal 0 HcmV?d00001 diff --git a/Textures/PawnUIHeader.tga b/Textures/PawnUIHeader.tga new file mode 100644 index 0000000000000000000000000000000000000000..b9b10fde1a8066056bc4989df45a026a868d23ad GIT binary patch literal 80183 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 dH~seCf7YxWV=VO?y0G$wlo}R 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 literal 0 HcmV?d00001 diff --git a/Version history.htm b/Version history.htm new file mode 100644 index 0000000..e569b7b --- /dev/null +++ b/Version history.htm @@ -0,0 +1,957 @@ + + + +Pawn past and future versions + + + + +

Pawn past and future versions

+ +

+ I got tired of the version history section of the + readme being longer than the rest of it, so I moved parts here.

+

Future versions

+

There are many features that I'd like to add in future versions of Pawn, +besides fixing issues listed in the "known issues" below.

+
    +
  • Now that the Cataclysm beta is in progress, I need to add a way to + select the quality level of meta gems to use between 80 and 85, just like + you can for non-meta gems.
  • +
  • To make Pawn more approachable for new users, I think that by default + I'll remove Pawn values from trinkets (since trinkets have secondary effects + and thus their Pawn values are very rarely accurate).  (I'll add an option to turn values back + on for trinkets, and there's already an option to toggle current item + values.)
  • +
  • I'm investigating how Pawn could eliminate or reduce its dependence on + lots and lots of hand-translated text patterns by using new API features + added in patch 3.2.
      +
    • Specifically, the new API function GetItemStats was added.  + Unfortunately, at this time, it only supports returning stats for the + base version of an item, not the current version, which makes it + insufficient for Pawn.
    • +
    • I could use GetItemStats for base stats only and use the old code + for enchanted stats, but that's starting to get pretty complicated, and + it would likely introduce weird bugs where DPS would be slightly + different between a base item and an enchanted item due to differences + in calculation methods, or old-world DPS enchantments would not work + properly, or things like those.
    • +
    +
  • +
  • The Compare tab should be updated to allow comparisons between two + one-handed items and a two-handed item.
  • +
  • Additional ways you might be able to get to the Compare tab in the + future:
      +
    • I could add little Pawn buttons to item link windows.  I + initially decided not to do this because I thought they'd be obnoxious.
    • +
    • I could add a "compare this item with what I already have" button to + loot roll windows to make it more obvious than just right-clicking the + icon.  Or, I could even just add a little comparison window that + sticks to the side.
    • +
    • In addition to the "currently equipped" buttons in the lower-left, I + could also remember the best 1-2 items you've ever equipped for a given + slot type, and display those there too.  This helps those with + multiple gear sets (for example, shamans with a resto set and an enhance + set).
    • +
    +
  • +
  • Right now, the Compare tab only shows stats that have nonzero values in + your currently-selected scale.  I could add an option to show all stats, though that + would make it a lot harder to use at a glance—weapons have a lot of stats, + including 8-12 stats just for tracking damage and speed, plus any real stats + on the item.
  • +
  • I might change the Compare tab so that it still shows item stats even if + there's only one item.
  • +
  • I could improve the Compare tab to support comparing multiple sets of + items at once.  (For example, an entire gear set versus another + person's entire gear set.)
  • +
  • I still haven't optimized some parts of Pawn for performance.  One + thing that I could do is collect statistics on how often various item stats + are used, and then reorder them in the files so that the most common stats + are checked first, and the rarest stats are checked last.  That might + be difficult and make translation much more challenging, though.
  • +
  • I could add an option to hide values from items you could never equip.  + For example, shamans could have values hidden on swords, sigils, plate + armor, and wands.
  • +
  • There are other things I could annotate tooltips with, but I'm not sure + which things I'll do, since some might conflict with other mods or require a + lot of configuration.
  • +
  • I could make it so that Pawn could generate Wowhead/LootRank URLs from your + scales.
  • +
  • Pawn could keep track of the best item of each type (Helm, Bracers, + Trinket, ...) that you've ever equipped, and then show you how the item + you're hovering over compares to that.  There would be one "best item" + per scale, per item type (two for rings and trinkets).  Or, it could just show a difference between + the hovered item and the equipped item.  This would be useful for + evaluating gear sets that you're not currently wearing—for example, if a DPS + cloak drops but you're in your tanking gear.
  • +
  • One very cool feature would be a way to automatically share Pawn scale + tags with another player through an in-game whisper.  Something like + /pawn share Vger <scale name>.  Or, there could just be a window + where you could type another player's name to see their Pawn scales if they + use Pawn... sort of like a Scalesteal spell.
  • +
  • I'd love to integrate with AtlasLoot's Wish List feature, helping you + find and manage upgrades to your items.
  • +
  • Down the road, I might also like to add a way for people to assign values to + specific item effects and enchantments.  For example, Spellsurge is a + useful weapon enchantment for casters, but Pawn doesn't give it a value.  + Right now, you have to rely on the asterisks and intuition to properly + evaluate Spellsurge versus another weapon enchantment.  I haven't + decided how this would work just yet...  Is it part of a single scale, + or a per-character option?  Does it assign stats (Intellect +30) or + points (Score +30)?  And what's the point of assigning it a value when + you'd generally use unenchanted value in these situations anyway?
  • +
+

Known issues and bugs

+

Check here first if you think you've found a new bug; maybe I already know +about it.

+
    +
  • Cataclysm beta:  The gems suggested by the Gems + tab are based on the stats those gems had before Cataclysm.  For + example, Fractured Cardinal Ruby will still be treated as if it had 20 armor + penetration, even though in 4.0 it has crit instead.  (Based on the + latest stuff on Wowhead, it sounds like maybe existing gems are just getting + converted to different types of gems, so maybe this won't be a problem.)
  • +
  • There's a bug where occasionally some people aren't able to select + scales from the list.
  • +
  • Tradeskill items do not always get Pawn values + right now.
  • +
  • Unverified: wands may only be getting the regular damage stats, and not + the ranged damage stats.
  • +
  • Unverified: Fiery and other weapon damage enchantments are + only counting for the basic DPS stat, and not for the specialized DPS stats + like melee DPS.
  • +
  • Weapon damage is actually stored in the game with more precision than is + displayed.  For example, Gavel of Naaru Blessings says in-game that it + does 16-117 damage, but according to the game files, it does 16.12-116.12 + damage.  This causes Pawn to report its DPS as being slightly different + than the in-game tooltip says.
  • +
  • Item links that you click that aren't already in your game cache show + the message "Retrieving item information" and then when the item appears, there + are no Pawn values until the next time you show that item.  This type of + problem has existed forever, and also affects the Blizzard feedback UI + line, but still I'd like to find a way to solve it.
  • +
+

Ancient release history

+

For recent version history, see the Pawn readme.

+

Version 1.2.5

+
    +
  • What used to be called the enchanted value of an item is now the current + value, and what used to be called the unenchanted value is now the base + value.  The word "base" now also appears on tooltips.  (No functionality has changed; this is just a terminology + change.)
  • +
  • Fixed the error that occurred when hovering over Brewfest steins.
  • +
+

Version 1.2.4

+
    +
  • Fixed an error that occurred when hovering over profession recipes.
  • +
+

Version 1.2.3

+
    +
  • You can now select what quality of gems to use for each of your scales + on the Gems tab: uncommon, rare, or epic.  Each scale can have a + different quality level, so your main spec scale can use epic gems, and your + offspec scale can use rare gems.
  • +
  • By popular request, enchanted values of items now no longer include any + points for sockets on the item, since the enchanted value is meant to + reflect the current state of the item.  This means that before you put + gems in an item, the enchanted value of an item will be lower than the + unenchanted value.
  • +
  • Fixed a bug where occasionally items that weren't enchanted would show + two equal values, such as "123.4 (123.4)".  (This was only a display + issue; the values were being calculated properly.)
  • +
+

Version 1.2.2

+
    +
  • Pawn will now calculate correct socket values if you have the "normalize + values (like Wowhead)" option enabled.
  • +
+

Version 1.2.1

+
    +
  • Fixed the "attempt to perform arithmetic on field '?' (a nil value)" + error that some users were experiencing in Pawn 1.2.
  • +
+

Version 1.2

+
    +
  • Note: Upon installation, this version of Pawn will automatically make adjustments to + each of your +scales' socket values.  To avoid this, you can turn off the option for each + scale to always correctly match gem colors.
  • +
  • By default, Pawn will automatically choose the best rare-quality level + 80 gem of each color and use the value of that gem when calculating item + values.  Most people will no longer ever need to manually set values + for red, yellow, or blue sockets.
  • +
  • There's now a new Gems tab in the Pawn UI.  This tab shows you the + best rare-quality level 80 gems that are available for any of your scales.  + Never again will you wonder which gems to have cut for that new boss drop. +
      +
    • The Gems tab also displays suggested meta gems, but take these with a grain of + salt; Pawn ignores the special effects on the gem.
    • +
    • The Gems tab only considers rare-quality level 80 gems.  I'll + add support for epic gems very soon in an upcoming Pawn update.
    • +
    +
  • +
  • Pawn will now add a tooltip to the item socketing window displaying the + suggested gems to use when socketing that item for each of your scales.  + In addition, the window now has a Pawn button that takes you directly to the + Gems tab for more details.
  • +
  • Meta sockets are now covered by two values instead of one: the old "Meta socket" value + is now "Meta: stats" and covers the normal stat bonuses from meta gems. The + new value is "Meta: effect" and covers the bonus effects from meta gems. Helms with an + empty meta socket will get the values from both added to their totals. Helms with a + filled meta socket will get the values of Meta: effect and the actual stats on the gem added + to their totals. This reflects the fact that stat-for-stat, helms with meta sockets are clearly + better than helms without them because of the added effects that meta gems bring.
  • +
  • Adjusted the value of MP5 in the default scale to correspond with current itemization.
  • +
  • Removed the Pawn feature to show item levels, as it is now built into the game.
  • +
  • Fixed the "Could not parse the item link" error that appeared when + clicking the links that items such as the Titanium Seal of Dalaran and Worn + Troll Dice add to chat.
  • +
  • Corrected the display of socket bonuses on weapons in the Compare tab.
  • +
  • (English) Enchant Boots: Greater Vitality now works again.
  • +
+

Version 1.1.13

+
    +
  • Fixed the "attempt to perform arithmetic on local 'Dps' (a nil value)" + issue introduced by 1.1.12 relating to feral attack power calculations.
  • +
+

Version 1.1.12

+
    +
  • Though it doesn't actually exist as a real weapon stat anymore, druids + can once again assign a value to feral attack power instead of weapon DPS if + they don't like multiplying by 14.
  • +
  • Updated Outfitter support to handle newer versions of Outfitter.  + (If alternate outfit tooltips stop working for you, be sure to download the + latest version of Outfitter too.)
  • +
+

Version 1.1.11

+
    +
  • When "show item levels" is enabled, you can now hover over the Pawn + button on your character sheet or an inspected player's character sheet to + get an estimate of that player's average item level in epic gear.  For + example, an epic gear level of 200 means that the player is in full 10-man + Naxxramas gear; 213 means that the player is in full 25-man Naxxramas gear.
  • +
+

Version 1.1.10

+
    +
  • Added support for Pawn values on Outfitter alternate-outfit tooltips.
  • +
+

Version 1.1.9

+
    +
  • Fixed a bug where scales that used to have a value for colorless sockets + back when Pawn included them as a possible stat would show inaccurate values + if you had normalization turned on.
  • +
  • Fixed a bug where armor penetration gems (like Fractured Scarlet Ruby) + didn't work in the English version.
  • +
  • Added support for Titanium Plating and Enchant Weapon: Accuracy.
  • +
  • Minor updates for colorblind mode in patch 3.1.
  • +
+

Version 1.1.8

+
    +
  • Removed the feral attack power stat entirely since it no longer exists + directly on weapons.  (Feral druids should increase their value for + weapon DPS accordingly.)
  • +
  • Added compatibility with the mod tdItemTip.
  • +
+

Version 1.1.7

+
    +
  • One more potential fix for the error that I worked on in the last + version.
  • +
+

Version 1.1.6

+
    +
  • Fixed an issue where Pawn would in rare situations cause an error when it was + loaded for the first time for a character (either a new character, or a + character new to Pawn).  Whether or not a given person would actually + experience this bug is essentially random.  This is also reported to + have worked around game crashes that a few people were seeing.
  • +
  • Added support for Enchant Boots: Greater Vitality, and the hit rating + effect of Titanium Weapon Chain.
  • +
+

Version 1.1.5

+
    +
  • Feral druids and death knights rejoice!  You can now assign + separate values to base armor (on cloth, cloaks, leather, mail, and plate) and bonus + armor (on weapons, trinkets, necklaces, rings, and enchantments).
      +
    • Please note that pre-Wrath items that had bonus armor (in green + text) will have the full armor value reported as base armor even though + some is bonus armor.  There is currently no way for mods to tell + the difference.
    • +
    +
  • +
  • Updated the default values for the socket stats to more appropriate + values, assuming that you would use level 80 blue gems in them.  Existing + scales will not be changed.
  • +
  • Support for colorless / prismatic sockets added by blacksmithing has + been removed.  Since these sockets never appear on items normally, and + they are simply an interim stage (you'd never socket an item and then decide + not to put a gem in it), I decided it didn't make sense to assign values to + them.
  • +
  • The red text that appears after socketed jewelcrafter-only BoP gems that + says that they require jewelcrafting will no longer appear with an asterisk + (*).
  • +
  • Added support for Enchant Boots: Icewalker.
  • +
  • Worked around a bug that could cause the Compare tab to stop working in + certain situations.
  • +
+

Version 1.1.4

+
    +
  • Fixed a bug where, on the Compare tab, meta sockets would appear under + the "socket bonus" heading instead of the "sockets" heading.
  • +
  • Enchantments that are on enchanters' rings when you are not an enchanter + (the ones that appear in red) will no longer appear with asterisks (*).
  • +
  • Armor penetration rating on new Wrath of the Lich King items will now be + interpreted correctly.
  • +
+

Version 1.1.3

+
    +
  • This is the patch 3.0 version of Pawn, and will not work properly on WoW + 2.4.x.  The stats Spell damage, Healing, Spell hit rating, Spell crit + rating, and Spell haste rating have all been removed.  Spell damage and + Healing have been replaced with Spell power, and the three Spell combat + ratings have been combined into hybrid ratings that work for both casters + and non-casters.  Pawn will update your scales automatically, but you + might want to check the values of those stats to make sure that they are in + line with what you'd expect.
  • +
  • Added stats that allow you to assign different values to different armor + types (such as plate, cloth, etc.).
  • +
+

Version 1.1.2

+
    +
  • Added support for account-bound (gold-quality) items and more new things that appear in + WoW 3.0.
  • +
  • Fixed an error message that could appear when using some item links from + sites like Wowhead.
  • +
  • Armor penetration works once more on WoW 3.0.
  • +
+

Version 1.1.1

+
    +
  • Fixed Link Wrangler compatibility, which was broken in Pawn 1.1.
  • +
  • Items that have at least one recognized stat (normally marked with an + asterisk) will be marked as such in the Compare tab.  (This feature + existed in 1.1 betas but did not appear in the final version.)
  • +
  • Added support for Lake Wintergrasp items and the Scourgestone.
  • +
+

Version 1.1

+
    +
  • This version of Pawn has been tested on both the live realms and on the Wrath of +the Lich King beta, and includes tons of fixes to make Pawn work + correctly on Wrath servers, and to support new stats and types of items that + appear in the expansion content, including new stats, new gems, encrypted + items, colorless sockets, and more.
  • +
  • Pawn now sports a new tab, Compare, which lets you see two items + side-by-side to compare their stats in more detail.  Check it out!
      +
    • You can compare any two items in your inventory or a merchant's + inventory by dropping them in + the slots.
    • +
    • If the left slot is empty and you drop an item in the right slot, + your currently equipped item will automatically be put in the left slot.
    • +
    • In addition to dragging and dropping items, all of the following + shortcuts also work:
        +
      • Clicking either of the "currently equipped" shortcut buttons + that appear in the lower-left corner once there is an item in the + right slot.
      • +
      • Hovering over an item in your inventory, another player's + inventory, a vendor's inventory, an item link window, AtlasLoot, or + other locations, and pressing the "[" or + "]" key.  (This key binding is customizable in the regular key + bindings interface.)
      • +
      • You can also right-click item link windows and item icons in + loot roll windows to immediately compare the item with your + currently equipped item.
      • +
      +
    • +
    +
  • +
  • The first time you run Pawn after upgrading to 1.1, Pawn will + automatically bind keys to its commands if you aren't using those keys for + other things.  By default, "P" will open and close the Pawn UI, and "[" + and "]" are used for item comparisons.  If + you later unbind those keys or bind them to other actions, Pawn won't try to + rebind them.
  • +
  • Fixed a bug where Pawn values didn't show up on item link windows if you + only had unenchanted numbers visible, not enchanted numbers.
  • +
  • Fixed a problem where error text would appear in the chat window when + hovering over recipes in Ackis Recipe List and possibly other similar mods.
  • +
  • German users rejoice!  When entering stat values you can now use a + comma instead of a period as a decimal separator—for example, "0,5" now + works in addition to "0.5".
  • +
  • In this version of Pawn, I've significantly reorganized a lot of the code that reads + and annotates item tooltips.  This, coupled with other changes in Wrath + of the Lich King, is likely to cause some conflicts with other mods.  + If you run into problems with Pawn, please remember to try to reproduce the + same problem with no other item-related mods installed.  I still may be + able to fix the bug if it turns out that the bug is related to another mod, + but I need to know which mod to download and try.
  • +
+

Version 1.0.4

+
    +
  • Fixed a bug that would prevent Pawn from working properly if one or + more of your scales didn't assign a value to the spell damage stat.
  • +
  • Updated the value of the armor penetration and spell penetration stats + in the default Pawn scale to match current itemization.  Existing + scales are not changed.
  • +
  • Pawn will no longer try to assign a default value to spell damage (WoW 2.4) if you use + a scale that contains only spell power (WoW 3.0).
  • +
+

Version 1.0.3

+
    +
  • Added an entry in the Interface Options dialog to launch the Pawn UI, in + case you hide the Pawn button and forget how to get it back.
  • +
  • You can now assign a value to the Wrath of the Lich King "spell power" + stat to your scales.  It uses the same values by default as the "spell + power" stat in Pawn 0.7.4, and your existing scales will be assigned a value + for the stat automatically.  Your scales will still have spell damage + and healing stats in them, but they won't be used for anything.
  • +
  • In Wrath of the Lich King, the stats that currently appear as melee hit + rating, melee crit rating, and melee haste will also apply to spells, and + the spell hit, crit, and haste rating stats are not used.  Existing + scales will not be modified at this time.
  • +
+

Version 1.0.2

+
    +
  • When Armory is installed, added support for showing unenchanted values + on the item comparison tooltips that appear when holding down the Alt key.
  • +
  • Added support for the stat-only components of Surefooted, Cat's + Swiftness, and Boar's Speed.  (The run speed and snare resistance + effects are ignored since there are no Pawn stats for those.)
  • +
  • Fixed a bug where if you clicked a link for an item that had an icon and + then a link for a spell that did not have an icon, the item's icon would not + properly disappear.
  • +
  • Fixed a bug with reading certain items with multiple stats on the same + line that was causing problems with the French translation in progress.
  • +
  • Removed a bunch more asterisks from various items.
  • +
+

Version 1.0.1

+
    +
  • It's now possible to force Pawn to always calculate an item's value + based on the correct color of gems (Pawn 0.8 and older behavior) instead of + maximizing the item's value by potentially using the wrong colors and + ignoring the socket bonus (Pawn 0.9 and later behavior).  To change the + option for one of your scales, choose that scale in the UI and then choose + one of the colored sockets from the stat list.
  • +
  • Added support for socket bonuses (and possible future gems) that give + melee haste rating.
  • +
  • Added shaman healing scales based on the popular Elitist Jerks thread to + the sample scales document that comes with Pawn.
  • +
+

Version 1.0

+
    +
  • Restored compatibility with Link Wrangler, which stopped working in Pawn 0.9.
  • +
  • Added support for showing Pawn values on Link Wrangler's "compare + equipped items" tooltips.
  • +
  • Added support for showing inventory icons next to Link Wrangler + tooltips.
  • +
  • There's now a "getting started" tab to gently remind new users to read + the Readme file.
  • +
  • Takes advantage of new WoW 2.4 functionality to show inventory icons + next to item link windows in a few situations where it couldn't before.
  • +
  • Various other UI and text tweaks.
  • +
+

Version 0.9

+
    +
  • Pawn is now smarter about the way that it values sockets and socket + bonuses, which in some cases will slightly change the values of certain + items, both with and without gems.
      +
    • If you've already filled all of the sockets on an item and you don't + qualify for the socket bonus, Pawn no longer counts the socket bonus + stats, since they're not actually there.
    • +
    • If you can get a better value by socketing the wrong colors of gems + and ignoring the socket bonus, Pawn will now do that when calculating + item values.  (When the "Show debug info" option is enabled, Pawn + will tell you which color gems it used.)
    • +
    +
  • +
  • Pawn now remembers all of the stats from several of the last items + you've viewed so it doesn't have to always recalculate them.  This + should help your framerate when rapidly hovering over many items in + succession, especially when an equip compare mod is also installed.
  • +
  • You now have to type the word "delete" to confirm that you really want + to delete a scale.
  • +
  • You can now make a copy of an existing scale by clicking the new Copy + button.
  • +
  • There is now an option to hide a specific scale from tooltips without + having to delete it (and, most likely, export it and paste it somewhere for + safekeeping).
  • +
  • Pawn will no longer look for stats on quest, spell, and ability links in + WoW 2.4.
  • +
  • Fixed a bunch of UI bugs that occurred when deleting your last scale.
  • +
  • Pawn no longer adds unnecessary asterisks to Black Temple instance-bound items, such as Naj'entus Spine.
  • +
  • Added support for Reckless Noble Topaz and other gems with spell haste + rating.
  • +
+

Version 0.8.2

+
    +
  • The Pawn UI now displays the current Pawn version number on the last + tab.
  • +
  • Pawn now supports addons that modify the Inspect window when it is first + shown instead of immediately upon login, such as Spyglass.
  • +
+

Version 0.8.1

+
    +
  • You can now hover over the Pawn button on the inventory window to get a + grand total of the enchanted values on all of your equipped items.
  • +
  • There is now a disabled Pawn button on the inspect window that you can + hover over to get totals for all of another player's equipped items.
  • +
  • Fixed a bug where all on/off options that defaulted to being on (show + enchanted values, show unenchanted values, and show tooltip icons) would + automatically turn back on after logging out and back in.
  • +
  • Fixed a bug where sometimes disabling enchanted values would cause + unenchanted values to also not be displayed.
  • +
  • Fixed a bug where the word "Projectile" on stacks of arrows would get an + asterisk, and their DPS would not count as ranged DPS.
  • +
  • Fixed a bug where negative values would not be exported in scale tags.  + (Negative stat values are most common with weapon speed.)
  • +
+

Version 0.8

+
    +
  • The Pawn UI now has an Options tab that you can use to set all Pawn + options.
  • +
  • Almost all of the Pawn slash commands are now unnecessary, and have been + removed.  You can still use /pawn to open the Pawn UI, and /pawn debug + is still available as well.
  • +
  • Tooltips have been added to pretty much everything in the Pawn UI.
  • +
  • You can use the new calculation option "Normalize values" to divide all of your scale values by + the sum of all of the numbers in the scale.  This causes Pawn to + generate the same sorts of numbers that Lootzor does.  (See the + appropriate section in the readme file for more details.)
  • +
  • You can now change the text color for your scales individually from the + Pawn UI.  You could, for example, make your healing scale show up in + white and your DPS scale show up in red.
  • +
  • You can now bind a key to the Pawn UI.  Look under "Pawn" in the + standard key bindings list.
  • +
  • Added support for the Fiery Weapon enchantment; Pawn counts it as 4 dps.
  • +
  • Fixed a bug where sometimes tooltip icons would not appear for items + that had never been in your inventory.  (This includes most links in + trade chat.)
  • +
  • Fixed a bug where if you clicked on an item link for an item, and then a + tradeskill recipe (such as [Alchemy: Super Mana Potion]), the icon for the + previous item would stay since the recipe did not have one.
  • +
  • Fixed a bug where if you had set custom colors for your scales, scales + would no longer always be sorted alphabetically by name on item tooltips.
  • +
+

Version 0.7.5

+
    +
  • Pawn now shows inventory icons next to item link and item comparison + tooltips, so when someone links an item, you can also see its inventory + icon.  You can turn this feature off with /pawn icons off.
      +
    • This feature works with the built-in WoW functionality, as well as a + variety of other tooltip mods: EquipCompare, EQCompare, and MultiTips.
    • +
    +
  • +
  • When /pawn ids on is active, Pawn now displays the item IDs of all + applicable enchantments and gems in the tooltip as well.  For example, + if looking at item 123 with no enchantment and gems 45 and 67 socketed, you + would see "Item ID: 123:0:45:67".
  • +
  • You can now have Pawn align item values (and IDs and levels, if those + options are on) along the right side of the tooltip with /pawn align numbers + right.  Some people may find this easier to read; others may find it + more difficult.
  • +
  • A new category of stats has been added, "weapon types," which is + primarily useful for racial abilities and talents.
      +
    • For example, orcs + get a passive 5 expertise bonus when using axes.  So, you might give a + weapon a few bonus points just for being an axe.  At level 70, 5 + expertise costs 78.8 expertise rating, so you might value "being an axe" + as high as 78.8.
    • +
    +
  • +
+

Version 0.7.4

+
    +
  • Pawn plugins for FuBar and Titan Panel are now available as + separate downloads.  You can get them at + Curse.
  • +
  • The old "spell power" stat made sense before patch 2.3, but it was + particularly confusing to healers after that patch, so I've removed it, and + replaced it with easier-to-understand "spell damage".  Now, + the healing stat only + includes healing, and the spell damage stat only includes spell damage.
      +
    • Now, Light's Justice, which says that it increases healing by 382 + and damage by 128, has 382 healing and 128 spell damage, exactly as + you'd expect.
    • +
    • Pawn will automatically upgrade your existing scales and any scale + tags that you import.  Please double-check the value of the spell + damage stat after importing to make sure that it's what you intended, + as in this version Pawn does not read your mind.  + The upgrade process will give correct results for typical cases (DPS + casters who don't care about healing; healers who valued spell power at + least as high as healing, as they should have), but may not give the + results you expect if you had a strange scale (healing but no spell + power).
    • +
    • As always, the default Pawn scale has been adjusted to accommodate + these changes.
    • +
    +
  • +
  • A new slash command /pawn enchanted has been added to let you turn off + the enchanted item values, showing only the unenchanted, unsocketed values + for items.  (You can also use /pawn enchanted off along with /pawn + unenchanted off to temporarily hide all Pawn values.)  This option may + not be compatible with all of your favorite tooltip mods, but it should work + in all cases where you currently get unenchanted item values today, which is + most everywhere.
  • +
  • The Pawn button has a new, unique look, instead of being a regular red + WoW button.
  • +
  • The Pawn button now appears in a more reasonable spot when the character + inventory window has been skinned using a mod such as Skinner.
  • +
  • A new slash command /pawn button has been added to let you move the Pawn + button on the inventory window, or hide it completely.  (If you hide + the button, you'll need to type /pawn to open the Pawn UI, or use the + Pawn plugin for FuBar or Titan Panel.)
  • +
  • Added a new option to the /pawn asterisks slash command, /pawn asterisks + no text.  Using this option is the same as /pawn asterisks auto, except + the "* Pawn gave no value to some stats" warning text is not added to + tooltips of items with at least one (*) unrecognized stat.
  • +
+

Version 0.7.3

+
    +
  • Added a button to the inventory window to show and hide the Pawn + configuration UI.
  • +
  • Pawn now supports Armory and EquipCompare working together with the "/eqc + alt" option to show comparisons with your alt's gear instead of your current + character's.
  • +
  • Added support for the unusual items with negative stats, such as Kreeg's + Mug, which gives -10 Intellect.
  • +
  • Added support for Braided Eternium Chain and other non-weapon items with + weapon damage as an equip bonus.  It shows up as minimum damage and + maximum damage, but not DPS—it's not a weapon, so there's no speed, and + therefore no DPS.
  • +
  • Fixed a problem that would frequently show up when using Pawn with + CowTip, causing an error message to appear in chat when hovering over an + item and then an empty inventory slot.
  • +
  • Fixed a problem where sometimes an open item link window would not be + immediately updated upon making scale changes.
  • +
  • Minor changes to fix some issues when running a German version of Pawn + on the German WoW client.
  • +
  • Updated the samples scales document with newer versions of Emmerald's + feral combat models and ShadowPanther's AEP model, and added scales for + Malan's Elitist Jerks EP for shamans.
  • +
+

Version 0.7.2

+
    +
  • The special weapon DPS stats now work.  I had broken them in either + 0.7 or 0.7.1.  Existing scales will automatically be corrected; you + should not need to re-enter values for those stats.
  • +
+

Version 0.7.1

+
    +
  • If the item link window is open when you make changes to your Pawn + scales, it will now be updated immediately, so you can see how your changes + will affect the value of the item.
  • +
  • Added spell hit rating and spell crit rating to the configuration UI.  + (Oops!)
  • +
  • Corrected a typo in the description of the melee and ranged crit rating + stat.
  • +
+

Version 0.7

+
    +
  • Pawn now has a configuration UI!  To access it, simply type /pawn + in the chat box.  (To see the help information that used to be + available when you typed /pawn, type /pawn help, or click the /pawn commands + button in the configuration UI.)  You should no longer need to manually edit your SavedVariables files + to configure Pawn.
  • +
  • The configuration UI is now the official way to make all changes to + your Pawn scales.  Some of the slash commands that existed in + earlier versions of Pawn are gone, since they're no longer needed: /pawn + list, export, import, delete, rename.
  • +
  • Eliminated that bug where sometimes unenchanted values would + disappear from items after a while.  The problem seemed to be most + commonly linked to use of the world map, but also could occur after zoning + or showing the interface options window.
  • +
  • Scales now appear in alphabetical order on item tooltips.
  • +
  • Pawn now works with tekKompare and LootLink.
  • +
  • Fixed a bug where the value of resilience rating in the default Pawn + value scale was 0 instead of 1 as was intended.  Scale tags that call resilience rating "Resilience" instead of "ResilienceRating" + will now be imported properly.
  • +
  • The value of ArmorPenetration in the default Pawn value scale is now + 0.1, based on the latest information on WoWWiki.
  • +
  • Includes VgerCore 1.0.1.
  • +
+

Version 0.6.3

+
    +
  • Added support for items in the guild bank vault.
  • +
  • In some cases, you can use mods that create fake item tooltips + like AtlasLoot and ItemSync along with mods that mangle item tooltips like + Rating Buster, and Pawn will now still work.  No guarantees that your + favorite combination of mods won't break Pawn, but this should help a lot of + people out.
  • +
  • Finally fixed that bug where items with both durability and an Equip: or + Use: line that Pawn didn't understand would get asterisks on the wrong + lines.  (For example, Stalker's Helm of Second Sight has this problem.)
  • +
  • Added the /pawn space command, which adds a blank space before the item + values it adds to tooltips.
  • +
  • Added a special "stat" called SpeedBaseline.  SpeedBaseline isn't an + actual item property, but rather a number to be subtracted from weapon + speeds before they're multiplied by your scale values.  For example, if + you're an enhancement shaman who wants to give weapons 1 point for every 0.1 + second of speed slower than 2.9, use Speed = 10 (1 / 0.1) and SpeedBaseline + = 2.9.  Weapons slower than 2.9 would instead lose points at the same rate.
  • +
  • Adjusted the value of the FeralAp stat in the Pawn default scale from + 0.5 to 0.4 so that feral items in 2.3 will have roughly the same values as + they did in 2.2 with the old Pawn default scale and so that the Pawn default + scale still roughly mimics item budget formulas.  I do not necessarily + recommend that you change values in your own scales; your items have been + buffed, so it makes sense for their values to go up.
  • +
  • Weapon skill rating stats have been removed from Pawn and the default + Pawn scale, as they've been removed from the game in the patch.  + Existing scales will not be automatically updated to use ExpertiseRating.
  • +
  • Added Pawn values to gems in the socketing UI.
  • +
  • Fixed the amount of healing and spell damage received from Teardrop + Tourmaline and possibly other gems that previously only gave healing.
  • +
  • Added support for the new Chaotic Skyfire Diamond.
  • +
  • Now displays a more readable error message when the embedded mod + VgerCore is missing or failed to load.
  • +
  • Fixed several problems introduced in 2.3:
      +
    • Fixed a problem that appeared in 2.3 due to the new way that set item + tooltips look where Pawn was putting asterisks all over the name of the set, + the items in the set, and the set bonuses.
    • +
    • Fixed a problem that appeared in 2.3 where stats on meta gems weren't + always being counted due to requirements now being displayed on gems even + when socketed.
    • +
    • Fixed a bug that appeared in 2.3 only where certain gems and random item + properties would appear very strange, such as "+45 Stamina|cfff1a1a1a", and + Pawn would not understand them.
    • +
    +
  • +
+

Version 0.6.2

+
    +
  • Fixed a script error that appeared the first time a recipe was clicked + since logging in or resetting your UI.
  • +
  • Pawn now includes VgerCore 1.0 embedded within the mod; you don't need a + separate VgerCore folder in your AddOns folder.
  • +
+

Version 0.6.1

+
    +
  • Supports the 2.2.x live realms and the 2.3 PTRs.  + (Healing-to-spell-damage and expertise require 2.3, of course.)
  • +
  • Support for the healing to spell damage conversion.  Items that say + that they increase healing by 300 and spell damage by 100 will be reported + as Healing = 200, SpellPower = 100.  Recall that SpellPower includes + healing, thus your scale's value for SpellPower should always be equal to or + greater than the value for Healing.
  • +
  • A new value of .255 was picked for the value of Healing in the default + scale, down from .455.  With this value, items that had 1/3 of their + Healing converted to SpellPower (default value .855) in 2.3 will have the exact + same values in 2.3 that they did with the old scale in 2.2 (ignoring rounding errors).  Of + course, you can and should customize your Pawn scales to fit your needs; + generally, healers should not adjust their scales to deal with this + change.
  • +
  • Support for the new ExpertiseRating stat, valued at 1 in the + default "Pawn value" scale, same as the existing weapon skill + stats.  Existing scales will not be adjusted to include this stat.  + If you haven't customized your scales and want to reset them, use /pawn resetscales.
      +
    • Weapon skill ratings are currently still supported so you can use + Pawn 0.6.1 on the live realms too.  They'll be removed in a later + version.
    • +
    +
  • +
  • Support for many more healing enchantments, inscriptions, spellthread, + and so forth.
  • +
  • Craftable items for which you can learn recipes from trainers will now + get Pawn values.
  • +
+

Version 0.6

+
    +
  • Fixed a problem introduced in beta 1 where Pawn would display + an error message in the chat window whenever hovering over a ring with + Enchant Ring - Weapon Might.
  • +
  • Added the ability to customize the colors that your scales will use when + printed.  For example, you could have your DPS value show up in orange, + and your healing value show up in green.  This is an advanced feature + that probably will never get its own UI or slash command.  See the + section titled "Changing the color of a scale" for more information.
  • +
  • Importing a scale tag that has the exact same name as a scale you + already have will update the old scale to match the values in the scale tag.  + All of the values in the old scale will be deleted.  (In previous + versions, Pawn would just fail and warn you that you can't overwrite an + existing scale.  There is no longer a warning.)
  • +
  • Weapon stats have been overhauled!  Old scales will work, but now + advanced users have a variety of new options for assigning values to + weapons.
  • +
  • New stats: MeleeMinDamage, MeleeMaxDamage, MeleeSpeed, MeleeDps, + MainHandMinDamage, MainHandMaxDamage, MainHandSpeed, MainHandDps, + OffHandMinDamage, OffHandMaxDamage, OffHandSpeed, OffHandDps, + OneHandMinDamage, OneHandMaxDamage, OneHandSpeed, OneHandDps, + TwoHandMinDamage, TwoHandMaxDamage, TwoHandSpeed, TwoHandDps, + RangedMinDamage, RangedMaxDamage, RangedSpeed, RangedDps.  Generally, + you'll only use a few of these, and not give values to all of them.
  • +
  • New stats: MinDamage, MaxDamage, Speed.  If you don't care about + which slot or weapon type it is, just use MinDamage, MaxDamage, Speed, + and/or Dps.  Dps is now calculated to full precision (say, 41.333 + instead of 41.3) instead of rounded to one decimal place.
  • +
  • Added support for weapon damage enchantments (Striking and Impact).
  • +
  • Added support for ranged weapon scopes.
  • +
  • Fixed a problem where certain items with suffixes would show unusually low unenchanted + values (their stats would be read as 0). For example, Chimaerascale Legguards of the Bandit + exhibited this problem.
  • +
  • Added support for Black Morass instance-bound items (beacons).
  • +
+

Version 0.5.4

+
    +
  • Added support for haste rating and spell haste rating, which were added as passive + item bonuses on Black Temple equipment. (Iron Counterweight is also supported.) These two + stats (HasteRating, SpellHasteRating) are valued at 1 each on the default Pawn scale; existing + Pawn scales won't be affected. Use /pawn resetscales to delete all of your scales and replace them + with the default one.
  • +
  • Added support for armor penetration ("Your attacks ignore X of your opponent's armor"). + This stat (ArmorPenetration) is not valued in the default Pawn scale.
  • +
  • Added support for Karazhan, Stratholme, and Tempest Keep instance-bound items.
  • +
+

Version 0.5.3

+
    +
  • Added support for the new gold profession recipe links in WoW 2.1.  + (Pawn won't try to read them or put asterisks all over them.)
  • +
+

Version 0.5.2

+
    +
  • Added working support for the Savagery weapon enchantment, and untested support + for Potency, Soulfrost, and Sunfire.
  • +
  • Added support for EquipCompare 2.10.  (This may break compatibility + with older versions of EquipCompare; please use the latest version.)
  • +
+

Version 0.5.1

+
    +
  • Updated for WoW patch 2.1.
  • +
  • Added support for Blizzard's new Currently Equipped functionality + (shift-hover an item).
  • +
  • LinkWrangler support.
  • +
  • Fixed a bug where stats in certain situations (gems with multiple stats + on one gem, socket bonuses, etc.) wouldn't get shown in the /pawn debug on + output.  This did not affect calculation; it was just a debug display error.
  • +
  • Expanded the amount of information Pawn returns in the debug output.
  • +
  • Added support for Enchant Boots: Vitality.
  • +
  • Slightly updated a few of the default "Pawn value" scale's numbers.  + This will not affect existing scales, even if you haven't changed any of the + defaults.  If you want to get the new scale, you can wipe out your + existing scale(s) with /pawn resetscales.
  • +
+

Version 0.5

+
    +
  • Requires VgerCore 0.4. (This is included with the mod. Pawn will warn you if you + are using an older version of VgerCore.)
  • +
  • Pawn can now show unenchanted values for items in addition to the + regular enchanted values, and this is enabled by default.  You can turn + it off with /pawn unenchanted off.  This feature only works for real + tooltips, and not fake tooltips from an item database mod.
  • +
  • Added an option to control the number of digits of precision Pawn uses + to display item values in tooltips.  By default this is 1 ("3.1"), but + you can set it to any number between 0 ("3") and 4 ("3.1416") with /pawn + digits #.
  • +
  • Corrected the value of frost resistance in the default Pawn scale to 1 + (was 4).
  • +
  • Pawn scale tags now have spaces inside of the parentheses by default.  + This is to prevent certain scale tags from turning into emoticons when + posted to certain forums.  (For example, if your scale tag ended in "RedSocket=8)", + the 8) would turn into an emoticon on certain forums.)  Old and current + versions of Pawn will accept the scale with or without the extra spaces.
  • +
  • Lots of work was done to prepare for Pawn translations into other languages.  + I may have made a mistake in this conversion and introduced cases where (*) + shows up where they didn't before; hopefully not.
  • +
+

Version 0.4.1

+
    +
  • Added support for gun, bow, and crossbow skill ratings.
  • +
  • Added support for MultiTips.
  • +
  • Added support for items that list stats as "Stat +X" instead of "+X + Stat", namely the epic gems.  Gems that only provided primary stats, + such as Seer's Chrysoprase, already worked, but others such as Rune Covered + Chrysoprase didn't work until this change.
  • +
+

Version 0.4

+
    +
  • Added scale tags!  Scale tags are chunks of text that you can use + to share Pawn scales with friends, guildmates, and others on forums, similar + to how you can share talent specs today by passing around a link to the WoW + talent calculator.  You can copy a scale tag and then post it on your + guild forums, and then another player can copy that tag and paste it into + their game to automatically add your scale to their copy of Pawn, without + logging out of the game or changing files in Notepad.  To export a + scale tag so you can share it with others, use the /pawn export command.  + To import someone else's scale tag and add it to your own scales, use the + /pawn import command.
  • +
  • Added new slash commands: /pawn list, export, import, delete, and + rename.  See "Slash commands" in Readme.htm for more details.
  • +
  • Added support for weapon skill ratings (including unarmed and feral + combat).  (They have been added to the default Pawn scale, but if you + care about weapon skill ratings you must add them to your custom scales + manually.)
  • +
  • Fixed the /pawn resetscales command so that it would delete all of your + scales before recreating the default one named Pawn value, as was intended.
  • +
+

Version 0.3.1

+
    +
  • Hovering over an enchanted Item X immediately after hovering over an + unenchanted Item X will now recalculate the values for the second item + instead of just displaying the ones for the first item.  This should + work for all pairs of items that differ only by enchantments and gems.
  • +
+

Version 0.3

+
    +
  • To read statistics, Pawn now makes a copy of the tooltip and reads from + that whenever possible, which allows it to read tooltips that have been + mangled by other mods.  Examples of mods that do this are Mendeleev and + Rating Buster.  (Certain mods that use custom tooltips, such as + ItemSync, don't support this; Pawn may still conflict with mods that modify + those custom tooltips.)
  • +
+

Version 0.2.2

+
    +
  • ItemSync support, including using ItemSync with AtlasLoot.  Pawn + will calculate and display values for ItemSync's tooltips.
  • +
  • EQCompare support.  Pawn will calculate and display values for + EQCompare's tooltips.
  • +
  • Item level display should work just about everywhere now, instead of + just in certain places.  (In previous versions, it worked for inventory + items, but not chat item links.  Now it works on both.)
  • +
  • A new option has been added to display the item ID for items you come + across, /pawn ids on.
  • +
  • The default is now to hide the asterisks (*) from items that don't have + any values for any of your scales.  So, for example, Hearthstone and + potions won't display the asterisk.  You can change this behavior using + /pawn asterisks [ on | auto | off ].
  • +
+

Version 0.2.1

+
    +
  • Fixed a problem in 0.2 where Pawn wouldn't work if you'd never used + version 0.1 before.
  • +
+

Version 0.2

+
    +
  • EquipCompare support.  Pawn will calculate and display values for + EquipCompare's tooltips.
  • +
  • AtlasLoot support.  Pawn will calculate and display values for + instance drops when Atlas and AtlasLoot are installed.
  • +
  • Outfitter support.  Pawn will no longer conflict with the lines + that Outfitter adds to tooltips of items used in one or more gear sets.
  • +
  • MonkeyQuest support.  I haven't actually observed it happening in + the past, but this change should prevent MonkeyQuest's item tooltip + annotations from affecting Pawn.
  • +
  • MobInfo-2 support.  Pawn will no longer conflict with the drop rate + information that MobInfo-2 adds to items.  (This problem manifested + itself both as unnecessary asterisks added to tooltips, and also as an + "attempted to concatenate a nil value" error.)
  • +
  • Added APIs to create and change Pawn scales while you're still logged + in.  These will eventually be used to build a configuration UI.  + If you really want to, you can use them from the chat box to change + Pawn scales while logged into the game.
  • +
  • Included a new file "Sample scales.htm" containing (you guessed it) some + sample scales that you can use to get started creating your own. I'll add to this + over time.
  • +
+

Version 0.1

+
    +
  • The first released beta version of Pawn.  The code is stable and + the remaining bugs are mostly just to-do items.  I'll get around to + them eventually.
  • +
  • Not heavily optimized for performance.
  • +
  • No configuration UI.  (Hope you like Notepad!)
  • +
+

The fine print

+

© 2006-2010 Green Eclipse.  This mod is released under the Creative Commons + +Attribution-NonCommercial-NoDerivs 3.0 license.  In short, this means +that you can use it, copy it, and share it, but you can't sell it or distribute +your own altered versions without permission. By using the mod you agree to the terms of the license. For more information, click the link.

+ + diff --git a/VgerCore/Readme.htm b/VgerCore/Readme.htm new file mode 100644 index 0000000..44fd94f --- /dev/null +++ b/VgerCore/Readme.htm @@ -0,0 +1,113 @@ + + + +VgerCore + + + + +

VgerCore 1.0.5

+ +

+ VgerCore contains functionality that is shared by mods written by Vger of + Azjol-Nerub (US).  By default, VgerCore comes with all of the mods that + need it.  You don't need to do anything special to install VgerCore.

+

+ Mods that need VgerCore

+
    +
  • Backdrop
  • +
  • Divisor
  • +
  • Gyro
  • +
  • Hear Kitty
  • +
  • Pawn
  • +
+

Notes

+

I welcome your feedback.  The best way to contact me is through +Curse.  You +can also contact me through in-game mail: Vger on Azjol-Nerub (US), Horde.  +(Just make sure that you keep a character on my server and check your mail, or I +can't respond!)  Also, check out my +official site, where you +can find links to all of my mods.

+

Release history

+

Version 1.0.5

+
    +
  • Minor performance enhancements.
  • +
+

Version 1.0.4

+
    +
  • Updated ExecuteChatCommand for patch 3.3.5.
  • +
+

Version 1.0.3

+
    +
  • Added StringFindReverse.
  • +
+

Version 1.0.2

+
    +
  • Added RGBToHex and HexToRGB.
  • +
+

Version 1.0.1

+
    +
  • Added the CaseInsensitiveComparer function for use with table.sort.
  • +
  • Added more colors.
  • +
+

Version 1.0

+
    +
  • VgerCore is now at version 1.0!  Instead of being a standalone mod, + it's now embedded by default.  If you have a VgerCore folder in + your Interface\AddOns folder, you can now delete it.
  • +
+

Version 0.4.1

+
    +
  • Added static function support to HookInsecureFunction.
  • +
  • HookInsecureFunction now supports hooking functions with more than 10 parameters.
  • +
  • Added HookInsecureScript, which works like the original (Object, FunctionName, Hook) overload of + HookInsecureFunction, except instead of a function name, it takes a script handler name.
  • +
+

Version 0.4

+
    +
  • Added color DarkBlue for Pawn.
  • +
  • Message, BigMessage, Assert, and Fail will now accept non-string + messages (such as booleans and nil).
  • +
  • Added MultilineMessage to print a large message to the console as a + series of individual messages so as to not break scrolling behavior.  + This is useful for mod usage information, for example.
  • +
+

Version 0.3.1

+
    +
  • ExecuteChatCommand (used when running macros) will now no longer try to + execute protected commands such as /cast.  This will prevent WoW from + asking to disable VgerCore simply due to user error.
  • +
+

Version 0.3

+
    +
  • Added new function VgerCore.HookInsecureFunction (analogous to + hooksecurefunc in the WoW APIs) for Pawn.
  • +
+

Version 0.2.1

+
    +
  • Minor update; fully compatible with version 0.2.
  • +
  • Simplified code to take advantage of new functionality in GetMacroInfo.
  • +
  • Used by Pawn.
  • +
+

Version 0.2

+
    +
  • Updated for patch 2.0.
  • +
  • Fixed a typo that might have manifested itself as an error about "PawnMessage."
  • +
  • Removed the optional dependency on CT_RaidAssist.
  • +
  • Used by Hear Kitty.
  • +
+

Version 0.1

+
    +
  • First version publicly released.
  • +
  • Used by Gyro.
  • +
+ +

The fine print

+

© 2006-2010 Green Eclipse.  This mod is released under the Creative Commons + +Attribution-NonCommercial-NoDerivs 3.0 license.  In short, this means +that you can use it, copy it, and share it, but you can't sell it or distribute +your own altered versions without permission. By using the mod you agree to the terms of the license. For more information, click the link.

+ + diff --git a/VgerCore/VgerCore.css b/VgerCore/VgerCore.css new file mode 100644 index 0000000..1b92e78 --- /dev/null +++ b/VgerCore/VgerCore.css @@ -0,0 +1,83 @@ +.codeblock { +background-color: #eaecef; +padding: .5em; +} + +.loot { +background-color: #202020; +border-style: outset; +border-width: 2px; +color: white; +margin-right: auto; +padding: .5em; +width: 25em; +} + +.loot .blue { +color: #1068ff; +} + +.loot .green { +color: lime; +} + +.loot .grey { +color: silver; +} + +.loot .itemname { +font-size: 125%; +margin-top: 0; +} + +.loot .pawnblue { +color: #8ec3e6; +} + +.loot .purple { +color: #6800ff; +} + +.loot .white { +color: white; +} + +.loot div { +margin-top: .1em; +} + +body { +font-family: Segoe UI, Verdana, sans-serif; +font-size: small; +} + +code { +font-family: Consolas, Courier New, monospace; +} + +h1 { +color: #42669e; +} + +h2 { +color: #7ba9d3; +} + +h3 { +color: #5e88b8; +} + +h4 { +color: #42669e; +font-weight: normal; +margin-bottom: -1em; +} + +html { +cursor: default; +} + +.warning { +color: red; +} +} \ No newline at end of file diff --git a/VgerCore/VgerCore.lua b/VgerCore/VgerCore.lua new file mode 100644 index 0000000..315fd15 --- /dev/null +++ b/VgerCore/VgerCore.lua @@ -0,0 +1,268 @@ +-- VgerCore by Vger-Azjol-Nerub +-- www.vgermods.com +-- © 2006-2010 Green Eclipse. This mod is released under the Creative Commons Attribution-NonCommercial-NoDerivs 3.0 license. +-- +-- Version 1.0.5: minor performance enhancements +local VgerCoreThisVersion = 1.05 +-- +-- VgerCore contains functionality that is shared by Vger's mods. +-- It can be used as a standalone add-on, or embedded within other mods. +-- +------------------------------------------------------------ + +local InitializeOrUpgrade = not (VgerCore and VgerCore.Version and VgerCore.Version >= VgerCoreThisVersion); + +-- If the currently loaded version of VgerCore isn't as good as this one, load the new one. +if InitializeOrUpgrade then + +VgerCore = {} +VgerCore.Version = VgerCoreThisVersion + +-- Common colors +VgerCore.Color = {} + +VgerCore.Color.Reset = "|r" + +VgerCore.Color.Blue = "|cff8ec3e6" +VgerCore.Color.BlueR = 142 / 255 +VgerCore.Color.BlueG = 195 / 255 +VgerCore.Color.BlueB = 230 / 255 + +VgerCore.Color.DarkBlue = "|cff6a92ac" +VgerCore.Color.DarkBlueR = 106 / 255 +VgerCore.Color.DarkBlueG = 146 / 255 +VgerCore.Color.DarkBlueB = 172 / 255 + +VgerCore.Color.Green = "|cffb4fe2c" +VgerCore.Color.GreenR = 180 / 255 +VgerCore.Color.GreenG = 255 / 255 +VgerCore.Color.GreenB = 44 / 255 + +VgerCore.Color.Orange = "|cfffecf38" +VgerCore.Color.OrangeR = 255 / 255 +VgerCore.Color.OrangeG = 207 / 255 +VgerCore.Color.OrangeB = 56 / 255 + +VgerCore.Color.Lemon = "|cfffffdd0" +VgerCore.Color.LemonR = 255 / 255 +VgerCore.Color.LemonG = 253 / 255 +VgerCore.Color.LemonB = 208 / 255 + +VgerCore.Color.Salmon = "|cfffe8460" +VgerCore.Color.SalmonR = 255 / 255 +VgerCore.Color.SalmonG = 132 / 255 +VgerCore.Color.SalmonB = 96 / 255 + +VgerCore.Color.Beige = "|cffe0dec8" +VgerCore.Color.BeigeR = 224 / 255 +VgerCore.Color.BeigeG = 222 / 255 +VgerCore.Color.BeigeB = 200 / 255 + +VgerCore.Color.White = "|cffffffff" +VgerCore.Color.WhiteR = 255 / 255 +VgerCore.Color.WhiteG = 255 / 255 +VgerCore.Color.WhiteB = 255 / 255 + +VgerCore.Color.Grey = "|cff909090" +VgerCore.Color.GreyR = 144 / 255 +VgerCore.Color.GreyG = 144 / 255 +VgerCore.Color.GreyB = 144 / 255 + +VgerCore.Color.Silver = "|cffc0c0c0" +VgerCore.Color.SilverR = 192 / 255 +VgerCore.Color.SilverG = 192 / 255 +VgerCore.Color.SilverB = 192 / 255 + +VgerCore.Color.Black= "|cff000000" +VgerCore.Color.BlackR = 0 / 255 +VgerCore.Color.BlackG = 0 / 255 +VgerCore.Color.BlackB = 0 / 255 + +VgerCore.MoneyColor = {} +VgerCore.MoneyColor.Gold = "|cffecda90" +VgerCore.MoneyColor.Silver = "|cffd7d5d8" +VgerCore.MoneyColor.Copper = "|cffe2ad8e" + +-- Common sounds +VgerCore.Sound = {} +VgerCore.Sound.Bell = "Sound\\Interface\\RaidWarning.wav" +VgerCore.Sound.Fanfare = "Sound\\Spells\\NetherwindFocusImpact.wav" + + +-- Displays a standard VgerCore message. +function VgerCore.Message(Text) + if DEFAULT_CHAT_FRAME then + DEFAULT_CHAT_FRAME:AddMessage(VgerCore.Color.Orange .. tostring(Text)) + else + message(VgerCore.Color.Orange .. tostring(Text)) + end +end + +-- Displays a bunch of messages from one string, separated by newlines. +-- Notes: +-- * Colors specified at the beginning of Text will not propagate to subsequent lines of Text. +-- Use the optional Color parameter instead. +-- * Empty lines will be skipped. Add a space to the line if you want it to be printed. +function VgerCore.MultilineMessage(Text, Color) + local Line + local ColorString = Color + if not ColorString then ColorString = "" end + for Line in string.gmatch(Text, "[^\r\n]+") do + VgerCore.Message(ColorString .. Line) + end +end + +-- Displays a large VgerCore message. +function VgerCore.BigMessage(Text) + if UIErrorsFrame then + UIErrorsFrame:AddMessage(tostring(Text), VgerCore.Color.GreenR, VgerCore.Color.GreenG, VgerCore.Color.GreenB, 1.0, 4.0) + end + if DEFAULT_CHAT_FRAME then + DEFAULT_CHAT_FRAME:AddMessage(VgerCore.Color.Green .. tostring(Text)) + end +end + +-- Displays a VgerCore error message if the condition is false. +function VgerCore.Assert(Condition, Message) + -- Possibility: call the assert() function to get a callstack and integrate with mods such as Swatter. + if not Condition then VgerCore.Fail(Message) end +end + +-- Displays a VgerCore error message. +function VgerCore.Fail(Message) + VgerCore.Message(VgerCore.Color.Salmon .. "ERROR: " .. VgerCore.Color.White .. tostring(Message)) +end + +-- Hooks an insecure function. Similar to the base WoW API's hooksecurefunc. The hook function will be run +-- after the original function to be hooked. +-- Valid usage: +-- VgerCore.HookInsecureFunction(Object, FunctionName, Hook) +-- VgerCore.HookInsecureFunction(FunctionName, Hook) +function VgerCore.HookInsecureFunction(arg1, arg2, arg3) + local TypeOfObject = type(arg1) + local OldFunction + if TypeOfObject == "table" then -- Object, FunctionName, Hook + OldFunction = arg1[arg2] + if OldFunction then + arg1[arg2] = VgerCore.CreateHookFunction(OldFunction, arg3) + else + VgerCore.Fail("VgerCore.HookInsecureFunction: could not find member function '" .. arg2 .. "'.") + end + elseif TypeOfObject == "string" then -- FunctionName, Hook + OldFunction = getglobal(arg1) + if OldFunction then + _G = getfenv() + _G[arg1] = VgerCore.CreateHookFunction(OldFunction, arg2) + else + VgerCore.Fail("VgerCore.HookInsecureFunction: could not find function '" .. arg1 .. "'.") + end + else + VgerCore.Fail("VgerCore.HookInsecureFunction argument 1 must be table or string, not " .. TypeOfObject .. ".") + end +end + +-- Hooks an insecure script handler. Works just like HookInsecureFunction(Object, FunctionName, Hook), except that +-- instead of a function name, a script name is passed. +function VgerCore.HookInsecureScript(Object, ScriptName, Hook) + local OldFunction = Object:GetScript(ScriptName) + if OldFunction then + Object:SetScript(ScriptName, VgerCore.CreateHookFunction(OldFunction, Hook)) + else + Object:SetScript(ScriptName, Hook) + end +end + +-- Internal function used by HookInsecureFunction. +function VgerCore.CreateHookFunction(OldFunction, Hook) + return function(...) + local ReturnValue = OldFunction(...) + Hook(...) + return ReturnValue + end +end + +-- Executes a chat command just as if it were typed in the chat window. +-- Returns true if successful, or false if not (primarily if the command is a secure function, such as /cast). +function VgerCore.ExecuteChatCommand(Command) + local EditBox = DEFAULT_CHAT_FRAME.editBox + if not EditBox then return false end + + -- First, make sure that this command is okay. + local _, _, SlashCommand = strfind(Command, "^(/%w+) ") + if SlashCommand then + if IsSecureCmd(SlashCommand) then + VgerCore.Fail(SlashCommand .. " is a secure command and cannot be run automatically.") + return false + end + end + + -- Now, execute the chat command. + local PreviousText = EditBox:GetText() + EditBox:SetText(Command) + ChatEdit_SendText(EditBox) + EditBox:SetText(PreviousText) + return true +end + +-- Runs a macro. +-- Returns true if successful, or false if not. +function VgerCore.RunMacro(MacroName) + -- First, get the text of the macro. + local _, _, Script, _ = GetMacroInfo(MacroName) + if not Script then return false end + + -- Then, execute each line individually. Ignore comments marked with # or -. + local Line + for Line in string.gmatch(Script, "[^\n]+") do + local FirstChar = strsub(Line, 1, 1) + if FirstChar ~= "#" and FirstChar ~= "-" then + VgerCore.ExecuteChatCommand(Line) + end + end + return true +end + +-- Returns true if the user is in a Battleground, or false if not. +function VgerCore.IsInBattleground() + for Battleground = 1, MAX_BATTLEFIELD_QUEUES do + local Status, _, _ = GetBattlefieldStatus(Battleground) + if Status == "active" then return true end + end + return false +end + +-- Comparer function for use in table.sort that sorts strings alphabetically, ignoring case. +function VgerCore.CaseInsensitiveComparer(a, b) + return strlower(a) < strlower(b) +end + +-- Returns a six-digit hex string for three RGB values 0-1. +function VgerCore.RGBToHex(r, g, b) + r = r <= 1 and r >= 0 and r or 0 + g = g <= 1 and g >= 0 and g or 0 + b = b <= 1 and b >= 0 and b or 0 + return format("%02x%02x%02x", r * 255, g * 255, b * 255) +end + +-- Returns RGB values 0-1 for a six-digit hex string, or nil if unsuccessful. +function VgerCore.HexToRGB(hex) + if not hex or strlen(hex) ~= 6 then return end + local r, g, b = strsub(hex, 1, 2), strsub(hex, 3, 4), strsub(hex, 5, 6) + r, g, b = r or 0, g or 0, b or 0 + return tonumber(r, 16) / 255, tonumber(g, 16) / 255, tonumber(b, 16) / 255 +end + +-- Same as strfind, but finds the last occurrence of a substring. The substring to find must be +-- a single character. +function VgerCore.StringFindReverse(str, find) + VgerCore.Assert(strlen(find) == 1, "The substring to find must be a single character.") + local FindByte = strbyte(find) + local StringLength = strlen(str) + local i + for i = StringLength, 1, -1 do + if strbyte(str, i) == FindByte then return i end + end + return nil +end + +end -- if InitializeOrUpgrade diff --git a/Wowhead.lua b/Wowhead.lua new file mode 100644 index 0000000..d6a3559 --- /dev/null +++ b/Wowhead.lua @@ -0,0 +1,487 @@ +-- Pawn by Vger-Azjol-Nerub +-- www.vgermods.com +-- © 2006-2010 Green Eclipse. This mod is released under the Creative Commons Attribution-NonCommercial-NoDerivs 3.0 license. +-- See Readme.htm for more information. +-- +-- Wowhead scales +------------------------------------------------------------ + +local ScaleProviderName = "Wowhead" + +function PawnWowheadScaleProvider_AddScales() + + +------------------------------------------------------------ +-- Warrior +------------------------------------------------------------ + +PawnAddPluginScale( + ScaleProviderName, + "WarriorArms", + PawnWowheadScale_WarriorArms, + "c79c6e", + { + ["Strength"] = 100, ["HitRating"] = 90, ["ExpertiseRating"] = 85, ["CritRating"] = 80, ["Agility"] = 65, ["ArmorPenetration"] = 65, ["HasteRating"] = 50, ["Ap"] = 45, ["Armor"] = 1, ["Stamina"] = .1, ["MetaSocketEffect"] = 3600 + }, + 1 +) + +PawnAddPluginScale( + ScaleProviderName, + "WarriorFury", + PawnWowheadScale_WarriorFury, + "c79c6e", + { + ["ExpertiseRating"] = 100, ["Strength"] = 82, ["CritRating"] = 66, ["Agility"] = 53, ["ArmorPenetration"] = 52, ["HitRating"] = 48, ["HasteRating"] = 36, ["Ap"] = 31, ["Armor"] = 5, ["Stamina"] = .1, ["MetaSocketEffect"] = 3600 + }, + 1 +) + +PawnAddPluginScale( + ScaleProviderName, + "WarriorTank", + PawnWowheadScale_WarriorTank, + "c79c6e", + { + ["Stamina"] = 100, ["DodgeRating"] = 90, ["DefenseRating"] = 86, ["BlockValue"] = 81, ["Agility"] = 67, ["ParryRating"] = 67, ["BlockRating"] = 48, ["Strength"] = 48, ["ExpertiseRating"] = 19, ["HitRating"] = 10, ["ArmorPenetration"] = 10, ["CritRating"] = 7, ["Armor"] = 6, ["HasteRating"] = 1, ["Ap"] = 1, ["MetaSocketEffect"] = 3600 + }, + 1 +) + +------------------------------------------------------------ +-- Paladin +------------------------------------------------------------ + +PawnAddPluginScale( + ScaleProviderName, + "PaladinHoly", + PawnWowheadScale_PaladinHoly, + "f58cba", + { + ["Intellect"] = 100, ["Mp5"] = 88, ["SpellPower"] = 58, ["CritRating"] = 46, ["HasteRating"] = 35, ["Stamina"] = .1, ["MetaSocketEffect"] = 3600 + }, + 1 +) + +PawnAddPluginScale( + ScaleProviderName, + "PaladinTank", + PawnWowheadScale_PaladinTank, + "f58cba", + { + ["Stamina"] = 100, ["Agility"] = 60, ["ExpertiseRating"] = 59, ["DodgeRating"] = 55, ["DefenseRating"] = 45, ["ParryRating"] = 30, ["Strength"] = 16, ["Armor"] = 8, ["BlockRating"] = 7, ["BlockValue"] = 6, ["MetaSocketEffect"] = 3600 + }, + 1 +) + +PawnAddPluginScale( + ScaleProviderName, + "PaladinRetribution", + PawnWowheadScale_PaladinRetribution, + "f58cba", + { + ["MeleeDps"] = 470, ["HitRating"] = 100, ["Strength"] = 80, ["ExpertiseRating"] = 66, ["CritRating"] = 40, ["Ap"] = 34, ["Agility"] = 32, ["HasteRating"] = 30, ["ArmorPenetration"] = 22, ["SpellPower"] = 9, ["Stamina"] = .1, ["MetaSocketEffect"] = 3600 + }, + 1 +) + +------------------------------------------------------------ +-- Hunter +------------------------------------------------------------ + +PawnAddPluginScale( + ScaleProviderName, + "HunterBeastMastery", + PawnWowheadScale_HunterBeastMastery, + "abd473", + { + ["RangedDps"] = 213, ["HitRating"] = 100, ["Agility"] = 58, ["CritRating"] = 40, ["Intellect"] = 37, ["Ap"] = 30, ["ArmorPenetration"] = 28, ["HasteRating"] = 21, ["Stamina"] = .1, ["MetaSocketEffect"] = 3600 + }, + 1 +) + +PawnAddPluginScale( + ScaleProviderName, + "HunterMarksman", + PawnWowheadScale_HunterMarksman, + "abd473", + { + ["RangedDps"] = 379, ["HitRating"] = 100, ["Agility"] = 74, ["CritRating"] = 57, ["ArmorPenetration"] = 40, ["Intellect"] = 39, ["Ap"] = 32, ["HasteRating"] = 24, ["Stamina"] = .1, ["MetaSocketEffect"] = 3600 + }, + 1 +) + +PawnAddPluginScale( + ScaleProviderName, + "HunterSurvival", + PawnWowheadScale_HunterSurvival, + "abd473", + { + ["RangedDps"] = 181, ["HitRating"] = 100, ["Agility"] = 76, ["CritRating"] = 42, ["Intellect"] = 35, ["HasteRating"] = 31, ["Ap"] = 29, ["ArmorPenetration"] = 26, ["Stamina"] = .1, ["MetaSocketEffect"] = 3600 + }, + 1 +) + +------------------------------------------------------------ +-- Rogue +------------------------------------------------------------ + +PawnAddPluginScale( + ScaleProviderName, + "RogueAssassination", + PawnWowheadScale_RogueAssassination, + "fff569", + { + ["MeleeDps"] = 170, ["Agility"] = 100, ["ExpertiseRating"] = 87, ["HitRating"] = 83, ["CritRating"] = 81, ["Ap"] = 65, ["ArmorPenetration"] = 65, ["HasteRating"] = 64, ["Strength"] = 55, ["Stamina"] = .1, ["MetaSocketEffect"] = 3600 + }, + 1 +) + +PawnAddPluginScale( + ScaleProviderName, + "RogueCombat", + PawnWowheadScale_RogueCombat, + "fff569", + { + ["MeleeDps"] = 220, ["ArmorPenetration"] = 100, ["Agility"] = 100, ["ExpertiseRating"] = 82, ["HitRating"] = 80, ["CritRating"] = 75, ["HasteRating"] = 73, ["Strength"] = 55, ["Ap"] = 50, ["Stamina"] = .1, ["MetaSocketEffect"] = 3600 + }, + 1 +) + +PawnAddPluginScale( + ScaleProviderName, + "RogueSubtlety", + PawnWowheadScale_RogueSubtlety, + "fff569", + { + ["MeleeDps"] = 228, ["ExpertiseRating"] = 100, ["Agility"] = 100, ["HitRating"] = 80, ["ArmorPenetration"] = 75, ["CritRating"] = 75, ["HasteRating"] = 75, ["Strength"] = 55, ["Ap"] = 50, ["Stamina"] = .1, ["MetaSocketEffect"] = 3600 + }, + 1 +) + +------------------------------------------------------------ +-- Priest +------------------------------------------------------------ + +PawnAddPluginScale( + ScaleProviderName, + "PriestDiscipline", + PawnWowheadScale_PriestDiscipline, + "ffffff", + { + ["SpellPower"] = 100, ["Mp5"] = 67, ["Intellect"] = 65, ["HasteRating"] = 59, ["CritRating"] = 48, ["Spirit"] = 22, ["Stamina"] = .1, ["MetaSocketEffect"] = 3600 + }, + 1 +) + +PawnAddPluginScale( + ScaleProviderName, + "PriestHoly", + PawnWowheadScale_PriestHoly, + "ffffff", + { + ["Mp5"] = 100, ["Intellect"] = 69, ["SpellPower"] = 60, ["Spirit"] = 52, ["CritRating"] = 38, ["HasteRating"] = 31, ["Stamina"] = .1, ["MetaSocketEffect"] = 3600 + }, + 1 +) + +PawnAddPluginScale( + ScaleProviderName, + "PriestShadow", + PawnWowheadScale_PriestShadow, + "ffffff", + { + ["HitRating"] = 100, ["ShadowSpellDamage"] = 76, ["SpellPower"] = 76, ["CritRating"] = 54, ["HasteRating"] = 50, ["Spirit"] = 16, ["Intellect"] = 16, ["Stamina"] = .1, ["MetaSocketEffect"] = 3600 + }, + 1 +) + +------------------------------------------------------------ +-- DK +------------------------------------------------------------ + +PawnAddPluginScale( + ScaleProviderName, + "DeathKnightBloodDps", + PawnWowheadScale_DeathKnightBloodDps, + "ff4d6b", + { + ["MeleeDps"] = 360, ["ArmorPenetration"] = 100, ["Strength"] = 99, ["HitRating"] = 91, ["ExpertiseRating"] = 90, ["CritRating"] = 57, ["HasteRating"] = 55, ["Ap"] = 36, ["Armor"] = 1, ["Stamina"] = .1, ["MetaSocketEffect"] = 3600 + }, + 1 +) + +PawnAddPluginScale( + ScaleProviderName, + "DeathKnightBloodTank", + PawnWowheadScale_DeathKnightBloodTank, + "ff4d6b", + { + ["MeleeDps"] = 500, ["Stamina"] = 100, ["DefenseRating"] = 90, ["Agility"] = 69, ["DodgeRating"] = 50, ["ParryRating"] = 43, ["ExpertiseRating"] = 38, ["Strength"] = 31, ["ArmorPenetration"] = 26, ["CritRating"] = 22, ["Armor"] = 18, ["HitRating"] = 16, ["HasteRating"] = 16, ["BonusArmor"] = 11, ["Ap"] = 8, ["MetaSocketEffect"] = 3600 + }, + 1 +) + +PawnAddPluginScale( + ScaleProviderName, + "DeathKnightFrostDps", + PawnWowheadScale_DeathKnightFrostDps, + "ff4d6b", + { + ["MeleeDps"] = 337, ["HitRating"] = 100, ["Strength"] = 97, ["ExpertiseRating"] = 81, ["ArmorPenetration"] = 61, ["CritRating"] = 45, ["Ap"] = 35, ["HasteRating"] = 28, ["Armor"] = 1, ["Stamina"] = .1, ["MetaSocketEffect"] = 3600 + }, + 1 +) + +PawnAddPluginScale( + ScaleProviderName, + "DeathKnightFrostTank", + PawnWowheadScale_DeathKnightFrostTank, + "ff4d6b", + { + ["MeleeDps"] = 419, ["ParryRating"] = 100, ["HitRating"] = 97, ["Strength"] = 96, ["DefenseRating"] = 85, ["ExpertiseRating"] = 69, ["DodgeRating"] = 61, ["Agility"] = 61, ["Stamina"] = 61, ["CritRating"] = 49, ["Ap"] = 41, ["ArmorPenetration"] = 31, ["Armor"] = 5, ["MetaSocketEffect"] = 3600 + }, + 1 +) + +PawnAddPluginScale( + ScaleProviderName, + "DeathKnightUnholyDps", + PawnWowheadScale_DeathKnightUnholyDps, + "ff4d6b", + { + ["MeleeDps"] = 209, ["Strength"] = 100, ["HitRating"] = 66, ["ExpertiseRating"] = 51, ["HasteRating"] = 48, ["CritRating"] = 45, ["Ap"] = 34, ["ArmorPenetration"] = 32, ["Armor"] = 1, ["Stamina"] = .1, ["MetaSocketEffect"] = 3600 + }, + 1 +) + +------------------------------------------------------------ +-- Shaman +------------------------------------------------------------ + +PawnAddPluginScale( + ScaleProviderName, + "ShamanElemental", + PawnWowheadScale_ShamanElemental, + "6e95ff", + { + ["HitRating"] = 100, ["SpellPower"] = 60, ["HasteRating"] = 56, ["CritRating"] = 40, ["Intellect"] = 11, ["Stamina"] = .1, ["MetaSocketEffect"] = 3600 + }, + 1 +) + +PawnAddPluginScale( + ScaleProviderName, + "ShamanEnhancement", + PawnWowheadScale_ShamanEnhancement, + "6e95ff", + { + ["MeleeDps"] = 135, ["HitRating"] = 100, ["ExpertiseRating"] = 84, ["Agility"] = 55, ["Intellect"] = 55, ["CritRating"] = 55, ["HasteRating"] = 42, ["Strength"] = 35, ["Ap"] = 32, ["SpellPower"] = 29, ["ArmorPenetration"] = 26, ["Stamina"] = .1, ["MetaSocketEffect"] = 3600 + }, + 1 +) + +PawnAddPluginScale( + ScaleProviderName, + "ShamanRestoration", + PawnWowheadScale_ShamanRestoration, + "6e95ff", + { + ["Mp5"] = 100, ["Intellect"] = 85, ["SpellPower"] = 77, ["CritRating"] = 62, ["HasteRating"] = 35, ["Stamina"] = .1, ["MetaSocketEffect"] = 3600 + }, + 1 +) + +------------------------------------------------------------ +-- Mage +------------------------------------------------------------ + +PawnAddPluginScale( + ScaleProviderName, + "MageArcane", + PawnWowheadScale_MageArcane, + "69ccf0", + { + ["HitRating"] = 100, ["HasteRating"] = 54, ["ArcaneSpellDamage"] = 49, ["SpellPower"] = 49, ["CritRating"] = 37, ["Intellect"] = 34, ["FrostSpellDamage"] = 24, ["FireSpellDamage"] = 24, ["Spirit"] = 14, ["Stamina"] = .1, ["MetaSocketEffect"] = 3600 + }, + 1 +) + +PawnAddPluginScale( + ScaleProviderName, + "MageFire", + PawnWowheadScale_MageFire, + "69ccf0", + { + ["HitRating"] = 100, ["HasteRating"] = 53, ["FireSpellDamage"] = 46, ["SpellPower"] = 46, ["CritRating"] = 43, ["FrostSpellDamage"] = 23, ["ArcaneSpellDamage"] = 23, ["Intellect"] = 13, ["Stamina"] = .1, ["MetaSocketEffect"] = 3600 + }, + 1 +) + +PawnAddPluginScale( + ScaleProviderName, + "MageFrost", + PawnWowheadScale_MageFrost, + "69ccf0", + { + ["HitRating"] = 100, ["HasteRating"] = 42, ["FrostSpellDamage"] = 39, ["SpellPower"] = 39, ["ArcaneSpellDamage"] = 19, ["FireSpellDamage"] = 19, ["CritRating"] = 19, ["Intellect"] = 6, ["Stamina"] = .1, ["MetaSocketEffect"] = 3600 + }, + 1 +) + +------------------------------------------------------------ +-- Warlock +------------------------------------------------------------ + +PawnAddPluginScale( + ScaleProviderName, + "WarlockAffliction", + PawnWowheadScale_WarlockAffliction, + "bca5ff", + { + ["HitRating"] = 100, ["ShadowSpellDamage"] = 72, ["SpellPower"] = 72, ["HasteRating"] = 61, ["CritRating"] = 38, ["FireSpellDamage"] = 36, ["Spirit"] = 34, ["Intellect"] = 15, ["Stamina"] = .1, ["MetaSocketEffect"] = 3600 + }, + 1 +) + +PawnAddPluginScale( + ScaleProviderName, + "WarlockDemonology", + PawnWowheadScale_WarlockDemonology, + "bca5ff", + { + ["HitRating"] = 100, ["HasteRating"] = 50, ["FireSpellDamage"] = 45, ["ShadowSpellDamage"] = 45, ["SpellPower"] = 45, ["CritRating"] = 31, ["Spirit"] = 29, ["Intellect"] = 13, ["Stamina"] = .1, ["MetaSocketEffect"] = 3600 + }, + 1 +) + +PawnAddPluginScale( + ScaleProviderName, + "WarlockDestruction", + PawnWowheadScale_WarlockDestruction, + "bca5ff", + { + ["HitRating"] = 100, ["FireSpellDamage"] = 47, ["SpellPower"] = 47, ["HasteRating"] = 46, ["Spirit"] = 26, ["ShadowSpellDamage"] = 23, ["CritRating"] = 16, ["Intellect"] = 13, ["Stamina"] = .1, ["MetaSocketEffect"] = 3600 + }, + 1 +) + +------------------------------------------------------------ +-- Druid +------------------------------------------------------------ + +PawnAddPluginScale( + ScaleProviderName, + "DruidBalance", + PawnWowheadScale_DruidBalance, + "ff7d0a", + { + ["HitRating"] = 100, ["SpellPower"] = 66, ["HasteRating"] = 54, ["CritRating"] = 43, ["Spirit"] = 22, ["Intellect"] = 22, ["Stamina"] = .1, ["MetaSocketEffect"] = 3600 + }, + 1 +) + +PawnAddPluginScale( + ScaleProviderName, + "DruidFeralDps", + PawnWowheadScale_DruidFeralDps, + "ff7d0a", + { + ["Agility"] = 100, ["ArmorPenetration"] = 90, ["Strength"] = 80, ["CritRating"] = 55, ["ExpertiseRating"] = 50, ["HitRating"] = 50, ["FeralAp"] = 40, ["Ap"] = 40, ["HasteRating"] = 35, ["Stamina"] = .1, ["MetaSocketEffect"] = 3600 + }, + 1 +) + +PawnAddPluginScale( + ScaleProviderName, + "DruidFeralTank", + PawnWowheadScale_DruidFeralTank, + "ff7d0a", + { + ["Agility"] = 100, ["Stamina"] = 75, ["DodgeRating"] = 65, ["DefenseRating"] = 60, ["ExpertiseRating"] = 16, ["Strength"] = 10, ["Armor"] = 10, ["HitRating"] = 8, ["HasteRating"] = 5, ["Ap"] = 4, ["FeralAp"] = 4, ["CritRating"] = 3, ["MetaSocketEffect"] = 3600 + }, + 1 +) + +PawnAddPluginScale( + ScaleProviderName, + "DruidRestoration", + PawnWowheadScale_DruidRestoration, + "ff7d0a", + { + ["SpellPower"] = 100, ["Mp5"] = 73, ["HasteRating"] = 57, ["Intellect"] = 51, ["Spirit"] = 32, ["CritRating"] = 11, ["Stamina"] = .1, ["MetaSocketEffect"] = 3600 + }, + 1 +) + +------------------------------------------------------------ + +-- PawnWowheadScaleProviderOptions.LastAdded keeps track of the last time that we tried to automatically enable scales for this character. +if not PawnWowheadScaleProviderOptions then PawnWowheadScaleProviderOptions = { } end +if not PawnWowheadScaleProviderOptions.LastAdded then PawnWowheadScaleProviderOptions.LastAdded = 0 end + +local _, Class = UnitClass("player") +if PawnWowheadScaleProviderOptions.LastAdded < 1 then + -- Enable round one of scales based on the player's class. + if Class == "WARRIOR" then + PawnSetScaleVisible(PawnGetProviderScaleName(ScaleProviderName, "WarriorFury"), true) + PawnSetScaleVisible(PawnGetProviderScaleName(ScaleProviderName, "WarriorTank"), true) + elseif Class == "PALADIN" then + PawnSetScaleVisible(PawnGetProviderScaleName(ScaleProviderName, "PaladinHoly"), true) + PawnSetScaleVisible(PawnGetProviderScaleName(ScaleProviderName, "PaladinTank"), true) + PawnSetScaleVisible(PawnGetProviderScaleName(ScaleProviderName, "PaladinRetribution"), true) + elseif Class == "HUNTER" then + PawnSetScaleVisible(PawnGetProviderScaleName(ScaleProviderName, "HunterBeastMastery"), true) + PawnSetScaleVisible(PawnGetProviderScaleName(ScaleProviderName, "HunterMarksman"), true) + PawnSetScaleVisible(PawnGetProviderScaleName(ScaleProviderName, "HunterSurvival"), true) + elseif Class == "ROGUE" then + PawnSetScaleVisible(PawnGetProviderScaleName(ScaleProviderName, "RogueAssassination"), true) + PawnSetScaleVisible(PawnGetProviderScaleName(ScaleProviderName, "RogueCombat"), true) + PawnSetScaleVisible(PawnGetProviderScaleName(ScaleProviderName, "RogueSubtlety"), true) + elseif Class == "PRIEST" then + PawnSetScaleVisible(PawnGetProviderScaleName(ScaleProviderName, "PriestDiscipline"), true) + PawnSetScaleVisible(PawnGetProviderScaleName(ScaleProviderName, "PriestHoly"), true) + PawnSetScaleVisible(PawnGetProviderScaleName(ScaleProviderName, "PriestShadow"), true) + elseif Class == "DEATHKNIGHT" then + PawnSetScaleVisible(PawnGetProviderScaleName(ScaleProviderName, "DeathKnightBloodDps"), true) + PawnSetScaleVisible(PawnGetProviderScaleName(ScaleProviderName, "DeathKnightBloodTank"), true) + PawnSetScaleVisible(PawnGetProviderScaleName(ScaleProviderName, "DeathKnightFrostDps"), true) + PawnSetScaleVisible(PawnGetProviderScaleName(ScaleProviderName, "DeathKnightFrostTank"), true) + PawnSetScaleVisible(PawnGetProviderScaleName(ScaleProviderName, "DeathKnightUnholyDps"), true) + elseif Class == "SHAMAN" then + PawnSetScaleVisible(PawnGetProviderScaleName(ScaleProviderName, "ShamanElemental"), true) + PawnSetScaleVisible(PawnGetProviderScaleName(ScaleProviderName, "ShamanEnhancement"), true) + PawnSetScaleVisible(PawnGetProviderScaleName(ScaleProviderName, "ShamanRestoration"), true) + elseif Class == "MAGE" then + PawnSetScaleVisible(PawnGetProviderScaleName(ScaleProviderName, "MageArcane"), true) + PawnSetScaleVisible(PawnGetProviderScaleName(ScaleProviderName, "MageFire"), true) + PawnSetScaleVisible(PawnGetProviderScaleName(ScaleProviderName, "MageFrost"), true) + elseif Class == "WARLOCK" then + PawnSetScaleVisible(PawnGetProviderScaleName(ScaleProviderName, "WarlockAffliction"), true) + PawnSetScaleVisible(PawnGetProviderScaleName(ScaleProviderName, "WarlockDemonology"), true) + PawnSetScaleVisible(PawnGetProviderScaleName(ScaleProviderName, "WarlockDestruction"), true) + elseif Class == "DRUID" then + PawnSetScaleVisible(PawnGetProviderScaleName(ScaleProviderName, "DruidBalance"), true) + PawnSetScaleVisible(PawnGetProviderScaleName(ScaleProviderName, "DruidFeralDps"), true) + PawnSetScaleVisible(PawnGetProviderScaleName(ScaleProviderName, "DruidFeralTank"), true) + PawnSetScaleVisible(PawnGetProviderScaleName(ScaleProviderName, "DruidRestoration"), true) + end +end + +if PawnWowheadScaleProviderOptions.LastAdded < 2 then + if Class == "WARRIOR" then + PawnSetScaleVisible(PawnGetProviderScaleName(ScaleProviderName, "WarriorArms"), true) + end +end + +-- Don't reenable those scales again after the user has disabled them previously. +PawnWowheadScaleProviderOptions.LastAdded = 2 + +-- After this function terminates there's no need for it anymore, so cause it to self-destruct to save memory. +PawnWowheadScaleProvider_AddScales = nil + +end -- PawnWowheadScaleProvider_AddScales + +------------------------------------------------------------ + +PawnAddPluginScaleProvider(ScaleProviderName, PawnWowheadScale_Provider, PawnWowheadScaleProvider_AddScales)