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 0000000..32879db Binary files /dev/null and b/Textures/CompareBanner.tga differ diff --git a/Textures/CompareBar.tga b/Textures/CompareBar.tga new file mode 100644 index 0000000..91b08d5 Binary files /dev/null and b/Textures/CompareBar.tga differ diff --git a/Textures/CompareBarLeft.tga b/Textures/CompareBarLeft.tga new file mode 100644 index 0000000..cdd63d0 Binary files /dev/null and b/Textures/CompareBarLeft.tga differ diff --git a/Textures/CompareBarRight.tga b/Textures/CompareBarRight.tga new file mode 100644 index 0000000..f98eadb Binary files /dev/null and b/Textures/CompareBarRight.tga differ diff --git a/Textures/HorizontalBar.tga b/Textures/HorizontalBar.tga new file mode 100644 index 0000000..c030279 Binary files /dev/null and b/Textures/HorizontalBar.tga differ diff --git a/Textures/PawnButton.tga b/Textures/PawnButton.tga new file mode 100644 index 0000000..e1d5596 Binary files /dev/null and b/Textures/PawnButton.tga differ diff --git a/Textures/PawnLogo.tga b/Textures/PawnLogo.tga new file mode 100644 index 0000000..4936d80 Binary files /dev/null and b/Textures/PawnLogo.tga differ diff --git a/Textures/PawnUIHeader.tga b/Textures/PawnUIHeader.tga new file mode 100644 index 0000000..b9b10fd Binary files /dev/null and b/Textures/PawnUIHeader.tga differ diff --git a/Textures/Question.tga b/Textures/Question.tga new file mode 100644 index 0000000..b6903f1 Binary files /dev/null and b/Textures/Question.tga differ diff --git a/Textures/UpgradeArrowBig.tga b/Textures/UpgradeArrowBig.tga new file mode 100644 index 0000000..641658d Binary files /dev/null and b/Textures/UpgradeArrowBig.tga differ 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.

+ +

Known issues and bugs

+

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

+ +

Ancient release history

+

For recent version history, see the Pawn readme.

+

Version 1.2.5

+ +

Version 1.2.4

+ +

Version 1.2.3

+ +

Version 1.2.2

+ +

Version 1.2.1

+ +

Version 1.2

+ +

Version 1.1.13

+ +

Version 1.1.12

+ +

Version 1.1.11

+ +

Version 1.1.10

+ +

Version 1.1.9

+ +

Version 1.1.8

+ +

Version 1.1.7

+ +

Version 1.1.6

+ +

Version 1.1.5

+ +

Version 1.1.4

+ +

Version 1.1.3

+ +

Version 1.1.2

+ +

Version 1.1.1

+ +

Version 1.1

+ +

Version 1.0.4

+ +

Version 1.0.3

+ +

Version 1.0.2

+ +

Version 1.0.1

+ +

Version 1.0

+ +

Version 0.9

+ +

Version 0.8.2

+ +

Version 0.8.1

+ +

Version 0.8

+ +

Version 0.7.5

+ +

Version 0.7.4

+ +

Version 0.7.3

+ +

Version 0.7.2

+ +

Version 0.7.1

+ +

Version 0.7

+ +

Version 0.6.3

+ +

Version 0.6.2

+ +

Version 0.6.1

+ +

Version 0.6

+ +

Version 0.5.4

+ +

Version 0.5.3

+ +

Version 0.5.2

+ +

Version 0.5.1

+ +

Version 0.5

+ +

Version 0.4.1

+ +

Version 0.4

+ +

Version 0.3.1

+ +

Version 0.3

+ +

Version 0.2.2

+ +

Version 0.2.1

+ +

Version 0.2

+ +

Version 0.1

+ +

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

+ +

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

+ +

Version 1.0.4

+ +

Version 1.0.3

+ +

Version 1.0.2

+ +

Version 1.0.1

+ +

Version 1.0

+ +

Version 0.4.1

+ +

Version 0.4

+ +

Version 0.3.1

+ +

Version 0.3

+ +

Version 0.2.1

+ +

Version 0.2

+ +

Version 0.1

+ + +

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)