Files
florian.berthold a399c1eefb chore: move addon into Pawn/ + add standard .gitignore
Matches the Exiles fork-layout convention (each addon in its own folder).
2026-05-25 10:59:30 +02:00

2991 lines
130 KiB
Lua

-- 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"] = <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