-- SageGearLevelTooltip.lua --[[ /console reloadui ]] ---------------------------------------------------------------------------------------------------- local Class = sage and sage.class and sage.class.Class or error("Need access to sage.class.Class") local Item = sage.item and sage.item.Item or error("Need access to sage.item.Item") local Reforgings = sage.reforge and sage.reforge.Reforgings or error("Need access to sage.item.Item") local function newClass(arg) return Class:NewClass(arg) end ---------------------------------------------------------------------------------------------------- local CachingEvaluator do local class = newClass{name="CachingEvaluator"} function class:_Initialize(instance, delegate) -- self is class self.super:_Initialize(instance) instance.delegate = delegate instance.cache = {} instance.cacheHitCount = 0 instance.cacheMissCount = 0 end function class:Evaluate(itemLink) -- TODO: normalize link first? local cached = self.cache[itemLink] if cached then self.cacheHitCount = self.cacheHitCount + 1 return unpack(cached) end local left, leftLabel, right, rightLabel = self.delegate:Evaluate(itemLink) if left and leftLabel then self.cache[itemLink] = { left, leftLabel, right, rightLabel } self.cacheMissCount = self.cacheMissCount + 1 return left, leftLabel, right, rightLabel end end CachingEvaluator = class end ---------------------------------------------------------------------------------------------------- --[[ A base class for Evaluators which *might* need to be cached. Just adds a :Cache method which wraps self with a CachingEvaluator ]] local CachableEvaluator do local class = newClass{name="CachableEvaluator"} function class:Cache() return CachingEvaluator:New(self) end CachableEvaluator = class end ---------------------------------------------------------------------------------------------------- local ItemLevelIdCommenter do local class = newClass{name="ItemLevelIdCommenter"} function class:Comment(itemLink, tooltip) if not itemLink then return end local _,_,_,level = GetItemInfo(itemLink) local itemId = itemLink:match("item:(%d+):") itemId = itemId and tonumber(itemId) or ">>" .. itemLink:gsub("|", "!") tooltip:AddDoubleLine( "iLevel " .. level, "id " .. itemId, class.r, class.g, class.b, 1, 1, 1) end ItemLevelIdCommenter = class end ---------------------------------------------------------------------------------------------------- local ItemIdEvaluator do local class = newClass{name="ItemIdEvaluator"} local label = {label = "id", r=0.5, g=1, b=0.5} function class:Evaluate(itemLink) if not itemLink then return end local itemId = itemLink:match("item:(%d+):") itemId = itemId and tonumber(itemId) or ">>" .. itemLink:gsub("|", "!") -- local itemId = Item:New(itemLink):ItemString() return itemId, label end ItemIdEvaluator = class end ---------------------------------------------------------------------------------------------------- local ITEM_MOD_NAME_MAP = { EMPTY_SOCKET_RED="RedSocket", EMPTY_SOCKET_BLUE="BlueSocket", EMPTY_SOCKET_YELLOW="YellowSocket", EMPTY_SOCKET_PRISMATIC="PrismaticSocket", ITEM_MOD_FIRE_RESISTANCE_SHORT="FireResist", ITEM_MOD_FROST_RESISTANCE_SHORT="FrostResist", ITEM_MOD_CRIT_RATING_SHORT="Crit", ITEM_MOD_CRIT_RANGED_RATING_SHORT="CritRanged", ITEM_MOD_DODGE_RATING_SHORT="Dodge", ITEM_MOD_EXPERTISE_RATING_SHORT="Expertise", ITEM_MOD_HASTE_RATING_SHORT="Haste", ITEM_MOD_HIT_RATING_SHORT="Hit", ITEM_MOD_MASTERY_RATING_SHORT="Mastery", ITEM_MOD_PARRY_RATING_SHORT="Parry", ITEM_MOD_RESILIENCE_RATING_SHORT="Resilience", ITEM_MOD_ATTACK_POWER_SHORT="AP", ITEM_MOD_DAMAGE_PER_SECOND_SHORT="DPS", ITEM_MOD_AGILITY_SHORT="Agility", ITEM_MOD_INTELLECT_SHORT="Intellect", ITEM_MOD_SPELL_POWER_SHORT="SP", ITEM_MOD_SPIRIT_SHORT="Spirit", ITEM_MOD_STAMINA_SHORT="Stamina", ITEM_MOD_STRENGTH_SHORT="Strength", RESISTANCE0_NAME="Armor", RESISTANCE2_NAME="FireResist", RESISTANCE4_NAME="FrostResist*", -- duplicate? } ---------------------------------------------------------------------------------------------------- local SimplePawnEvaluator do local class = newClass{name="SimplePawnEvaluator", super=CachableEvaluator} function class:_Initialize(instance, label, coefficients) --self is class self.super:_Initialize(instance) instance.coefficients = coefficients instance.label = label end function class:Evaluate(itemLink) if not itemLink then return end local stats = self:_GetItemStats(itemLink) local total = 0 local missedStat, missedStatLabel = nil, nil for k,v in pairs(stats) do local stat = ITEM_MOD_NAME_MAP[k] if not stat then missedStat, missedStatLabel = v, {label=k, r=class.r, g=class.g, b=class.b} end total = total + v * (self.coefficients[stat] or 0) end if total > 0 then return math.floor(0.5 + total), self.label, missedStat, missedStatLabel elseif missedStat then return missedStat, missedStatLabel end end function class:_GetItemStats(itemLink) -- broken out to be overridable return GetItemStats(itemLink) end SimplePawnEvaluator = class end ---------------------------------------------------------------------------------------------------- local AsReforgedPawnEvaluator do local class = newClass{name="AsReforgedPawnEvaluator", super=SimplePawnEvaluator} local reforgings = Reforgings:New() function class:_GetItemStats(itemLink) local reforging = reforgings:Find(itemLink) return Item:New(itemLink):GetItemStats() end AsReforgedPawnEvaluator = class end ---------------------------------------------------------------------------------------------------- local BestReforgedEvaluator do local class = newClass{name="BestReforgedEvaluator", super=CachableEvaluator} local reforgings = Reforgings:New() local reforgingLabel = {label = "", r=0.6, g=0.6, b=0.6} function class:_Initialize(instance, label, coefficients) --self is class self.super:_Initialize(instance) instance.coefficients = coefficients instance.label = label end function class:_GetItemStats(itemLink) local reforging = reforgings:Find(itemLink) return Item:New(itemLink):GetItemStats() end function class:Evaluate(itemLink) if not itemLink then return end itemLink = Item:New(itemLink):WithoutMods():ItemString() local stats = GetItemStats(itemLink) -- baseline w/o reforging local rightValue, rightLabel = nil, nil local reforging = self:_FindOptimalReforging(stats) if reforging then reforging:AdjustStats(stats) rightValue, rightLabel = reforging:Describe(), reforgingLabel end local evaluation = self:_EvalStats(stats) return evaluation, self.label, rightValue, rightLabel end function class:_FindOptimalReforging(stats) local maxDelta, maxReforging = 0, nil for index,reforging in pairs(reforgings:All()) do local from, to = reforging:GetShortNames() local c0 = self.coefficients[ITEM_MOD_NAME_MAP[from]] or 0 local c1 = self.coefficients[ITEM_MOD_NAME_MAP[to]] or 0 if stats[from] and (not stats[to]) and (c1 > c0) then local delta = stats[from] * (c1 - c0) --don't bother to scale by 40% here if delta > maxDelta then maxDelta, maxReforging = delta, reforging end end end return maxReforging end function class:_EvalStats(stats) local total = 0 for k,v in pairs(stats) do local stat = ITEM_MOD_NAME_MAP[k] total = total + v * (self.coefficients[stat] or 0) end return math.floor(0.5 + total) end function class:_GetItemStats(itemLink) -- broken out to be overridable return GetItemStats(itemLink) end BestReforgedEvaluator = class end ---------------------------------------------------------------------------------------------------- local EvaluatorToLineAdderAdaptor do local class = newClass{name="EvaluatorToLineAdderAdaptor"} function class:_Initialize(instance, evaluator) instance.evaluator = evaluator end -- Adds lines for the given item -- @param itemLink a link for the item -- @param tooltip a callback supporting :AddLine and :AddDoubleLine with same args as a GameTooltip -- NOTE: tooltip is not guaranteed to be an actual tooltip, or support other methods function class:Comment(itemLink, tooltip) local left, leftLabel, right, rightLabel = self.evaluator:Evaluate(itemLink) if left and leftLabel and right and rightLabel then tooltip:AddDoubleLine( leftLabel.label .. ": " .. left, rightLabel.label .. ": " .. right, leftLabel.r, leftLabel.g, leftLabel.b, rightLabel.r, rightLabel.g, rightLabel.b) elseif left and leftLabel then tooltip:AddLine(leftLabel.label .. ": " .. left, leftLabel.r, leftLabel.g, leftLabel.b) end end EvaluatorToLineAdderAdaptor = class end ---------------------------------------------------------------------------------------------------- local SageGearLevelTooltipUpdater do local class = newClass{name="TooltipUpdater"} -- class function function class:_Initialize(instance, methodsToHook) -- no call to super needed instance.evals = {} instance.commenters = {} return instance:_HookFrameMethods(methodsToHook) end -- Updates the tooltip with all the comments from the item commenters -- @param tooltip A "tooltip" support :AddLine and :AddDoubleLine *only* function class:UpdateTooltip(tooltip) local _,itemLink = tooltip:GetItem() for _,commenter in ipairs(self.commenters) do commenter:Comment(itemLink, tooltip) end end function class:_HookFrameMethods() -- for tooltipName, methods in pairs(methodsToHook) do -- self:HookTooltip(tooltipName, methods) -- end self:Print("SageGearLevelTooltip loaded.") return self end function class:HookTooltip(tooltipName, methods) local tooltip = _G[tooltipName] local update = function(tooltip, ...) self:UpdateTooltip(tooltip) end for _, method in ipairs(methods) do -- self:Debug((tooltip and tooltipName or "whoops") .. "/" .. type(tooltip) .. "+" .. method .. "/" .. type(method)) hooksecurefunc(tooltip, method, update) end return self; end function class:AddEvaluator(e) return self:AddItemCommenter(EvaluatorToLineAdderAdaptor:New(e)) end -- Adds an item commenter to this tooltip annotator -- @param commenter an ItemCommenter to give comment to this tooltip function class:AddItemCommenter(commenter) local commenters = self.commenters commenters[#commenters + 1] = commenter return self end SageGearLevelTooltipUpdater = class end ---------------------------------------------------------------------------------------------------- --local methodsToHook = { -- GameTooltip = {"SetBagItem", "SetInventoryItem", "SetHyperlink", "SetAuctionItem", -- "SetQuestItem", "SetQuestLogItem"}, -- ItemRefTooltip = {"SetHyperlink", "Show", "Hide"}, -- ShoppingTooltip1 = {"SetHyperlinkCompareItem"}, -- ShoppingTooltip2 = {"SetHyperlinkCompareItem"}} SageGearLevelTooltipUpdater:New(methodsToHook) :HookTooltip("GameTooltip", {"SetBagItem", "SetInventoryItem", "SetHyperlink", "SetAuctionItem", "SetQuestItem", "SetQuestLogItem"}) :HookTooltip("ItemRefTooltip", {"SetHyperlink", "Show", "Hide"}) :HookTooltip("ShoppingTooltip1", {"SetHyperlinkCompareItem"}) :HookTooltip("ShoppingTooltip2", {"SetHyperlinkCompareItem"}) :AddItemCommenter(ItemLevelIdCommenter:New()) --Warlock -- :AddEvaluator(BestReforgedEvaluator:New({label = "Wk.Aff", r=1.0, g=0.2, b=1.0}, -- {Hit=100, Master=100, SP=72, Haste=61, Crit=38, Spirit=34, Intellect=15}) -- :Cache()) -- :AddEvaluator(BestReforgedEvaluator:New({label = "Wk.Dem", r=0.8, g=0.2, b=1.0}, -- {Hit=100, Master=100, Haste=50, SP=45, Crit=31, Spirit=29, Intellect=13}) -- :Cache()) -- Hunter -- :AddEvaluator(BestReforgedEvaluator:New({label = "Hr.Su", r=1.0, g=0.7, b=0.3}, -- {RangedDPS=181, Mastery=100, Hit=100, Agility=76, Crit=42, Intellect=35, Haste=31}) -- :Cache()) -- Worlock -- :AddEvaluator(BestReforgedEvaluator:New({label = "Wk.Aff", r=0.4, g=1.0, b=0.2}, -- {Hit=100, Mastery=100, SP=72, Haste=61, Crit=38, Spirit=34, Intellect=15}) -- :Cache()) -- :AddEvaluator(BestReforgedEvaluator:New({label = "Wk.Demo", r=0.8, g=0.2, b=1.0}, -- {Hit=100, Mastery=100, Haste=50, SP=45, Crit=31, Spirit=29, Intellect=13}) -- :Cache()) -- Warrior :AddEvaluator(BestReforgedEvaluator:New({label = "W.Best", r=0.8, g=0.8, b=1.0}, {Strength=48, Agility=67, Stamina=100, Hit=10, Crit=7, Haste=1, AP=1, Expertise=19, Armor=6, Dodge=90, Parry=67}) :Cache()) -- :AddEvaluator(SimplePawnEvaluator:New({label = "W.Simple", r=0.8, g=0.8, b=1.0}, -- {Strength=48, Agility=67, Stamina=100, Hit=10, Crit=7, Haste=1, AP=1, Expertise=19, Armor=6, Dodge=90, Parry=67}) -- :Cache()) ---- :AddEvaluator(SimplePawnEvaluator:New({label = "M.Simple", r=0.8, g=1, b=0.6}, ---- {Mastery=1}) ---- :Cache()) -- :AddEvaluator(AsReforgedPawnEvaluator:New({label = "W.Reforged", r=0.8, g=0.8, b=1.0}, -- {Strength=48, Agility=67, Stamina=100, Hit=10, Crit=7, Haste=1, AP=1, Expertise=19, Armor=6, Dodge=90, Parry=67}) -- :Cache()) ---- :AddEvaluator(AsReforgedPawnEvaluator:New({label = "M.Reforged", r=0.8, g=1, b=0.6}, ---- {Mastery=1}) ---- :Cache()) -- :AddEvaluator(BestReforgedEvaluator:New({label = "W.Best", r=0.8, g=0.8, b=1.0}, -- {Strength=48, Agility=67, Stamina=100, Hit=10, Crit=7, Haste=1, AP=1, Expertise=19, Armor=6, Dodge=90, Parry=67}) -- :Cache()) ---- :AddEvaluator(BestReforgedEvaluator:New({label = "D.Best", r=0.8, g=1, b=0.6}, ---- {Dodge=1}) ---- :Cache()) ---- :AddEvaluator(BestReforgedEvaluator:New({label = "Hunter.SV", r=1.0, g=0.8, b=0.4}, ---- {RangedDPS=181, Hit=100, Master=100, Agility=76, Crit=42, Intellect=35, Haste=31}) ---- :Cache())