-- SageGearLevelTooltip.lua --[[ /console reloadui ]] ---------------------------------------------------------------------------------------------------- local Class = sage.class.Class local Item = sage.item.Item local Reforgings = sage.reforge.Reforgings 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:Name() return self.delegate:Name() 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 or '??'), "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 print("stat eval") 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 = "Reforge", 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 -- handle sockets local bestStatValue = 0 for i,stat in ipairs({"Stamina", "Strength", "Agility", "Intellect", "Spirit"}) do bestStatValue = math.max(bestStatValue, instance.coefficients[stat] or 0) end local socketValue = bestStatValue * 40 -- cata blue gems instance.coefficients.RedSocket = math.max(socketValue, instance.coefficients.RedSocket or 0) instance.coefficients.BlueSocket = math.max(socketValue, instance.coefficients.BlueSocket or 0) instance.coefficients.YellowSocket = math.max(socketValue, instance.coefficients.YellowSocket or 0) end function class:_GetItemStats(itemLink) local reforging = reforgings:Find(itemLink) return Item:New(itemLink):GetItemStats() end function class:Name() return self.label and self.label.label or "" 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 -- if not high enough for reforging, show base stats local reforging = self:_ItemHighEnoughForForging(itemLink) and self:_FindOptimalReforging(stats) if reforging then reforging:AdjustStats(stats) rightValue, rightLabel = reforging:Describe(), reforgingLabel end local evaluation = self:_EvalStats(stats) if evaluation and (evaluation > 0) then return evaluation, self.label, rightValue, rightLabel end end function class:_ItemHighEnoughForForging(itemLink) _,_,_,itemLevel = GetItemInfo(itemLink) return itemLevel >= 200 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) local leftText = left and leftLabel and (leftLabel.label .. ": " .. left) local rightText = right and rightLabel and (rightLabel.label .. ": " .. right) if leftText and rightText then tooltip:AddDoubleLine(leftText, rightText, leftLabel.r, leftLabel.g, leftLabel.b, rightLabel.r, rightLabel.g, rightLabel.b) elseif leftText 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() local tooltipAdapter = { mustShow = false, AddLine = function(adapter, ...) adapter.mustShow = true; tooltip:AddLine(...) end, AddDoubleLine = function(adapter, ...) adapter.mustShow = true; tooltip:AddDoubleLine(...) end } for _,commenter in ipairs(self.commenters) do commenter:Comment(itemLink, tooltipAdapter) end if tooltipAdapter.mustShow then tooltip:Show() end end function class:_HookFrameMethods() self:Print("SageGearLevelTooltip loaded.") return self end function class:HookTooltipScript(tooltipName) local tooltip = _G[tooltipName] or error("Tooltip not found: " .. tooltipName) local update = function(tooltip, ...) self:UpdateTooltip(tooltip) end tooltip:HookScript("OnTooltipSetItem", update) return self end function class:AddSelfEvaluator(className, e) local playerClassLocal, playerClass = UnitClass('player') if className == playerClass then self:Debug("Installing evaluator for " .. playerClassLocal .. ": " .. (e:Name())) return self:AddEvaluator(e) 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 tooltipUpdater = SageGearLevelTooltipUpdater:New(methodsToHook) :HookTooltipScript("GameTooltip") :HookTooltipScript("ItemRefTooltip") :HookTooltipScript("ShoppingTooltip1") :HookTooltipScript("ShoppingTooltip2") :AddItemCommenter(ItemLevelIdCommenter:New()) local DEFAULT_COLOR = {r=0.5, g=0.5, b=0.5} sage.gear.AllStats(function(spec, class, stats, color) color = color or DEFAULT_COLOR print(spec, class, stats, color) local specInfo = {label = spec, r=color.r, g=color.g, b=color.b} tooltipUpdater:AddSelfEvaluator( class, BestReforgedEvaluator:New(specInfo, stats):Cache()) end)