Skip to content

Commit

Permalink
Add warnings if skill cost exceeds avilable resource. (#5019)
Browse files Browse the repository at this point in the history
* FEAT: Stop disabling aura effects when unreserved mana is negative. Add
a warning for when LifeCost or ManaCost is higher than avilable pool.

* FEAT: Make mana cost warnings show for all enabled active skill gems not
just mainSkill.

* FIX: generalize match condition for cost coloring.

* FIX: Add support for mana cost warning for triggered skills.

* FIX: fix copy paste typo.

* FEAT: implement warnings for energy shield and rage cost. Minor tweaks.

* Apply suggestions from code review

Co-authored-by: QuickStick <[email protected]>

* FIX: Add better handling for eldrich battery. Minor tweaks.

* FIX: fix other costs missing from top level cache table.

* FIX: add compatibility with #5199

Co-authored-by: QuickStick <[email protected]>
  • Loading branch information
Paliak and QuickStick123 authored Dec 9, 2022
1 parent f12d7b2 commit 92afbf2
Show file tree
Hide file tree
Showing 5 changed files with 97 additions and 40 deletions.
64 changes: 48 additions & 16 deletions src/Modules/Build.lua
Original file line number Diff line number Diff line change
Expand Up @@ -318,19 +318,19 @@ function buildMode:Init(dbFileName, buildName, buildXML, convertBuild)
{ stat = "AreaOfEffectRadius", label = "AoE Radius", fmt = "d" },
{ stat = "BrandAttachmentRange", label = "Attachment Range", fmt = "d", flag = "brand" },
{ stat = "BrandTicks", label = "Activations per Brand", fmt = "d", flag = "brand" },
{ stat = "ManaCost", label = "Mana Cost", fmt = "d", color = colorCodes.MANA, compPercent = true, lowerIsBetter = true, condFunc = function(v,o) return o.ManaHasCost end },
{ stat = "ManaPercentCost", label = "Mana Cost", fmt = "d%%", color = colorCodes.MANA, compPercent = true, lowerIsBetter = true, condFunc = function(v,o) return o.ManaPercentHasCost end },
{ stat = "ManaPerSecondCost", label = "Mana Cost per second", fmt = ".2f", color = colorCodes.MANA, compPercent = true, lowerIsBetter = true, condFunc = function(v,o) return o.ManaPerSecondHasCost end },
{ stat = "ManaPercentPerSecondCost", label = "Mana Cost per second", fmt = ".2f%%", color = colorCodes.MANA, compPercent = true, lowerIsBetter = true, condFunc = function(v,o) return o.ManaPercentPerSecondHasCost end },
{ stat = "LifeCost", label = "Life Cost", fmt = "d", color = colorCodes.LIFE, compPercent = true, lowerIsBetter = true, condFunc = function(v,o) return o.LifeHasCost end },
{ stat = "LifePercentCost", label = "Life Cost", fmt = "d%%", color = colorCodes.LIFE, compPercent = true, lowerIsBetter = true, condFunc = function(v,o) return o.LifePercentHasCost end },
{ stat = "LifePerSecondCost", label = "Life Cost per second", fmt = ".2f", color = colorCodes.LIFE, compPercent = true, lowerIsBetter = true, condFunc = function(v,o) return o.LifePerSecondHasCost end },
{ stat = "LifePercentPerSecondCost", label = "Life Cost per second", fmt = ".2f%%", color = colorCodes.LIFE, compPercent = true, lowerIsBetter = true, condFunc = function(v,o) return o.LifePercentPerSecondHasCost end },
{ stat = "ESCost", label = "Energy Shield Cost", fmt = "d", color = colorCodes.ES, compPercent = true, lowerIsBetter = true, condFunc = function(v,o) return o.ESHasCost end },
{ stat = "RageCost", label = "Rage Cost", fmt = "d", color = colorCodes.RAGE, compPercent = true, lowerIsBetter = true, condFunc = function(v,o) return o.RageHasCost end },
{ stat = "RagePerSecondCost", label = "Rage Cost per second", fmt = ".2f", color = colorCodes.RAGE, compPercent = true, lowerIsBetter = true, condFunc = function(v,o) return o.RagePerSecondHasCost end },
{ stat = "ESPerSecondCost", label = "ES Cost per second", fmt = ".2f", color = colorCodes.ES, compPercent = true, lowerIsBetter = true, condFunc = function(v,o) return o.ESPerSecondHasCost end },
{ stat = "ManaCost", label = "Mana Cost", fmt = "d", color = colorCodes.MANA, pool = "ManaUnreserved", compPercent = true, lowerIsBetter = true, condFunc = function(v,o) return o.ManaHasCost end },
{ stat = "ManaPercentCost", label = "Mana Cost", fmt = "d%%", color = colorCodes.MANA, pool = "ManaUnreservedPercent", compPercent = true, lowerIsBetter = true, condFunc = function(v,o) return o.ManaPercentHasCost end },
{ stat = "ManaPerSecondCost", label = "Mana Cost", fmt = ".2f/s", color = colorCodes.MANA, pool = "ManaUnreserved", compPercent = true, lowerIsBetter = true, condFunc = function(v,o) return o.ManaPerSecondHasCost end },
{ stat = "ManaPercentPerSecondCost", label = "Mana Cost", fmt = ".2f%%/s", color = colorCodes.MANA, pool = "ManaUnreservedPercent", compPercent = true, lowerIsBetter = true, condFunc = function(v,o) return o.ManaPercentPerSecondHasCost end },
{ stat = "LifeCost", label = "Life Cost", fmt = "d", color = colorCodes.LIFE, pool = "LifeUnreserved", compPercent = true, lowerIsBetter = true, condFunc = function(v,o) return o.LifeHasCost end },
{ stat = "LifePercentCost", label = "Life Cost", fmt = "d%%", color = colorCodes.LIFE, pool = "LifeUnreservedPercent", compPercent = true, lowerIsBetter = true, condFunc = function(v,o) return o.LifePercentHasCost end },
{ stat = "LifePerSecondCost", label = "Life Cost", fmt = ".2f/s", color = colorCodes.LIFE, pool = "LifeUnreserved", compPercent = true, lowerIsBetter = true, condFunc = function(v,o) return o.LifePerSecondHasCost end },
{ stat = "LifePercentPerSecondCost", label = "Life Cost", fmt = ".2f%%/s", color = colorCodes.LIFE, pool = "LifeUnreservedPercent", compPercent = true, lowerIsBetter = true, condFunc = function(v,o) return o.LifePercentPerSecondHasCost end },
{ stat = "ESCost", label = "Energy Shield Cost", fmt = "d", color = colorCodes.ES, pool = "EnergyShield", compPercent = true, lowerIsBetter = true, condFunc = function(v,o) return o.ESHasCost end },
{ stat = "ESPerSecondCost", label = "ES Cost per second", fmt = ".2f", color = colorCodes.ES, pool = "EnergyShield", compPercent = true, lowerIsBetter = true, condFunc = function(v,o) return o.ESPerSecondHasCost end },
{ stat = "ESPercentPerSecondCost", label = "ES Cost per second", fmt = ".2f%%", color = colorCodes.ES, compPercent = true, lowerIsBetter = true, condFunc = function(v,o) return o.ESPercentPerSecondHasCost end },
{ stat = "RageCost", label = "Rage Cost", fmt = "d", color = colorCodes.RAGE, pool = "Rage", compPercent = true, lowerIsBetter = true, condFunc = function(v,o) return o.RageHasCost end },
{ stat = "RagePerSecondCost", label = "Rage Cost per second", fmt = ".2f", color = colorCodes.RAGE, pool = "Rage", compPercent = true, lowerIsBetter = true, condFunc = function(v,o) return o.RagePerSecondHasCost end },
{ },
{ stat = "Str", label = "Strength", color = colorCodes.STRENGTH, fmt = "d" },
{ stat = "ReqStr", label = "Strength Required", color = colorCodes.STRENGTH, fmt = "d", lowerIsBetter = true, condFunc = function(v,o) return v > o.Str end, warnFunc = function(v) return "You do not meet the Strength requirement" end },
Expand Down Expand Up @@ -1263,13 +1263,14 @@ function buildMode:RefreshSkillSelectControls(controls, mainGroup, suffix)
end
end

function buildMode:FormatStat(statData, statVal, overCapStatVal)
function buildMode:FormatStat(statData, statVal, overCapStatVal, colorOverride)
if type(statVal) == "table" then return "" end
local val = statVal * ((statData.pc or statData.mod) and 100 or 1) - (statData.mod and 100 or 0)
local color = (statVal >= 0 and "^7" or statData.chaosInoc and "^8" or colorCodes.NEGATIVE)
local color = colorOverride or (statVal >= 0 and "^7" or statData.chaosInoc and "^8" or colorCodes.NEGATIVE)
if statData.label == "Unreserved Life" and statVal == 0 then
color = colorCodes.NEGATIVE
end

local valStr = s_format("%"..statData.fmt, val)
valStr:gsub("%.", main.decimalSeparator)
valStr = color .. formatNumSep(valStr)
Expand Down Expand Up @@ -1330,14 +1331,27 @@ function buildMode:AddDisplayStatList(statList, actor)
end
end
elseif not (statData.hideStat) then
-- Change the color of the stat label to red if cost exceeds pool
local output = actor.output
local poolVal = output[statData.pool]
local colorOverride = nil
if statData.stat:match("Cost$") and statVal and poolVal then
if statData.stat == "ManaCost" and output.EnergyShieldProtectsMana then
if statVal > output.ManaUnreserved + output.EnergyShield then
colorOverride = colorCodes.NEGATIVE
end
elseif statVal > poolVal then
colorOverride = colorCodes.NEGATIVE
end
end
t_insert(statBoxList, {
height = 16,
labelColor..statData.label..":",
self:FormatStat(statData, statVal, overCapStatVal),
self:FormatStat(statData, statVal, overCapStatVal, colorOverride),
})
end
end
if statData.warnFunc and statVal and ((statData.condFunc and statData.condFunc(statVal, actor.output)) or not statData.condFunc) then
if statData.warnFunc and statVal and ((statData.condFunc and statData.condFunc(statVal, actor.output)) or not statData.condFunc) then
local v = statData.warnFunc(statVal, actor.output)
if v then
InsertIfNew(self.controls.warnings.lines, v)
Expand All @@ -1352,6 +1366,24 @@ function buildMode:AddDisplayStatList(statList, actor)
end
end
end
for pool, warningFlag in pairs({["Life"] = "LifeCostWarning", ["Mana"] = "ManaCostWarning", ["Rage"] = "RageCostWarning", ["Energy Shield"] = "ESCostWarning"}) do
if actor.output[warningFlag] then
local line = "You do not have enough "..(actor.output.EnergyShieldProtectsMana and pool == "Mana" and "Energy Shield and Mana" or pool).." to use a Selected Skill"
InsertIfNew(self.controls.warnings.lines, line)
end
end
for pool, warningFlag in pairs({["Life"] = "LifePerSecondCostPerSecondWarning", ["Mana"] = "ManaPerSecondCostPerSecondWarning", ["Rage"] = "RagePerSecondCostPerSecondWarning", ["EnergyShield"] = "ESPerSecondCostPerSecondWarning"}) do
if actor.output[warningFlag] then
local line = "You do not have enough ".. pool .." to use a Selected Skill for a second"
InsertIfNew(self.controls.warnings.lines, line)
end
end
for pool, warningFlag in pairs({["Unreserved life"] = "LifePercentCostPercentPerSecondWarning", ["Unreserved life"] = "LifePercentPerSecondCostPercentPerSecondWarning", ["Unreserved Mana"] = "ManaPercentPerSecondCostPercentPerSecondWarning", ["Unreserved Mana"] = "ManaPercentCostPercentPerSecondWarning", ["EnergyShield"] = "ESPercentPerSecondCostPercentPerSecondWarning"}) do
if actor.output[warningFlag] then
local line = "You do not have enough ".. pool .."% to use a Selected Skill"
InsertIfNew(self.controls.warnings.lines, line)
end
end
end

-- Build list of side bar stats
Expand Down
1 change: 0 additions & 1 deletion src/Modules/CalcOffence.lua
Original file line number Diff line number Diff line change
Expand Up @@ -4390,7 +4390,6 @@ function calcs.offence(env, actor, activeSkill)
local uuid = cacheSkillUUID(triggerSkill)
if not GlobalCache.cachedData[calcMode][uuid] then
calcs.buildActiveSkill(env, calcMode, triggerSkill)
env.dontCache = true
end
-- We found a skill and it can crit
if GlobalCache.cachedData[calcMode][uuid] and GlobalCache.cachedData[calcMode][uuid].CritChance and GlobalCache.cachedData[calcMode][uuid].CritChance > 0 then
Expand Down
26 changes: 3 additions & 23 deletions src/Modules/CalcPerform.lua
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ local function findTriggerSkill(env, skill, source, triggerRate, reqManaCost)
local uuid = cacheSkillUUID(skill)
if not GlobalCache.cachedData["CACHE"][uuid] or GlobalCache.noCache then
calcs.buildActiveSkill(env, "CACHE", skill)
env.dontCache = true
end

if GlobalCache.cachedData["CACHE"][uuid] then
Expand Down Expand Up @@ -1818,12 +1817,6 @@ function calcs.perform(env, avoidCache)
local skillModList = activeSkill.skillModList
local skillCfg = activeSkill.skillCfg
for _, buff in ipairs(activeSkill.buffList) do
--Skip adding buff if reservation exceeds maximum
for _, value in ipairs({"Mana", "Life"}) do
if activeSkill.skillData[value.."ReservedBase"] and activeSkill.skillData[value.."ReservedBase"] > env.player.output[value] then
goto disableAura
end
end
if buff.cond and not skillModList:GetCondition(buff.cond, skillCfg) then
-- Nothing!
elseif buff.enemyCond and not enemyDB:GetCondition(buff.enemyCond) then
Expand Down Expand Up @@ -2023,7 +2016,6 @@ function calcs.perform(env, avoidCache)
t_insert(curses, curse)
end
end
::disableAura::
end
if activeSkill.minion and activeSkill.minion.activeSkillList then
local castingMinion = activeSkill.minion
Expand Down Expand Up @@ -2215,13 +2207,6 @@ function calcs.perform(env, avoidCache)
if (activeSkill.buffList[1] and curse.name == activeSkill.buffList[1].name and activeSkill.skillTypes[SkillType.Aura]) then
if modDB:Flag(nil, "SelfAurasOnlyAffectYou") then
skipAddingCurse = true
break
end
for _, value in ipairs({"Mana", "Life"}) do
if activeSkill.skillData[value.."ReservedBase"] and activeSkill.skillData[value.."ReservedBase"] > env.player.output[value] then
skipAddingCurse = true
break
end
end
break
end
Expand Down Expand Up @@ -2391,7 +2376,7 @@ function calcs.perform(env, avoidCache)
activeSkill.skillModList:NewMod("Multiplier:ScorchingRayStageAfterFirst", "BASE", maximum, "Base")
end
end

-- Process Triggered Skill and Set Trigger Conditions
-- Cospri's Malice
if env.player.mainSkill.skillData.triggeredByCospris and not env.player.mainSkill.skillFlags.minion then
Expand Down Expand Up @@ -2503,7 +2488,6 @@ function calcs.perform(env, avoidCache)
-- cache a new copy of this skill that's affected by Mirage Archer
if avoidCache then
usedSkill = env.player.mainSkill
env.dontCache = true
else
if not GlobalCache.cachedData[calcMode][uuid] then
calcs.buildActiveSkill(env, calcMode, env.player.mainSkill, true)
Expand Down Expand Up @@ -2543,7 +2527,6 @@ function calcs.perform(env, avoidCache)
newEnv.player.mainSkill = newSkill
-- mark it so we don't recurse infinitely
newSkill.marked = true
newEnv.dontCache = true
calcs.perform(newEnv)

env.player.mainSkill.infoMessage = tostring(mirageCount) .. " Mirage Archers using " .. usedSkill.activeEffect.grantedEffect.name
Expand Down Expand Up @@ -3032,7 +3015,7 @@ function calcs.perform(env, avoidCache)
end
end
end

-- Fix the configured impale stacks on the enemy
-- If the config is missing (blank), then use the maximum number of stacks
-- If the config is larger than the maximum number of stacks, replace it with the correct maximum
Expand Down Expand Up @@ -3231,8 +3214,5 @@ function calcs.perform(env, avoidCache)
calcs.offence(env, env.minion, env.minion.mainSkill)
end

local uuid = cacheSkillUUID(env.player.mainSkill)
if not env.dontCache then
cacheData(uuid, env)
end
cacheData(cacheSkillUUID(env.player.mainSkill), env)
end
43 changes: 43 additions & 0 deletions src/Modules/Calcs.lua
Original file line number Diff line number Diff line change
Expand Up @@ -405,6 +405,49 @@ function calcs.buildOutput(build, mode)
calcs.perform(env)

local output = env.player.output

for _, skill in ipairs(env.player.activeSkillList) do
local uuid = cacheSkillUUID(skill)
if not GlobalCache.cachedData["CACHE"][uuid] then
calcs.buildActiveSkill(env, "CACHE", skill)
end
if GlobalCache.cachedData["CACHE"][uuid] then
local EB = env.modDB:Flag(nil, "EnergyShieldProtectsMana")
for pool, costResource in pairs({["LifeUnreserved"] = "LifeCost", ["ManaUnreserved"] = "ManaCost", ["Rage"] = "RageCost", ["EnergyShield"] = "ESCost"}) do
local cachedCost = GlobalCache.cachedData["CACHE"][uuid].Env.player.output[costResource]
if cachedCost then
if EB and costResource == "Mana" then --Handling for mana cost warnings with EB allocated
output.EnergyShieldProtectsMana = true
output[costResource.."Warning"] = output[costResource.."Warning"] or (((output[pool] or 0) + (output["EnergyShield"] or 0)) < cachedCost)
else
output[costResource.."Warning"] = output[costResource.."Warning"] or ((output[pool] or 0) < cachedCost) -- defaulting to 0 to avoid crashing
end
end
end
for pool, costResource in pairs({["LifeUnreserved"] = "LifePerSecondCost", ["ManaUnreserved"] = "ManaPerSecondCost", ["Rage"] = "RagePerSecondCost", ["EnergyShield"] = "ESPerSecondCost"}) do
local cachedCost = GlobalCache.cachedData["CACHE"][uuid].Env.player.output[costResource]
if cachedCost then
if EB and costResource == "Mana" then
output.EnergyShieldProtectsMana = true
output[costResource.."PerSecondWarning"] = output[costResource.."PerSecondWarning"] or (((output[pool] or 0) + (output["EnergyShield"] or 0)) < cachedCost)
else
output[costResource.."PerSecondWarning"] = output[costResource.."PerSecondWarning"] or ((output[pool] or 0) < cachedCost)
ConPrintf(costResource.."PerSecondWarning".." "..(output[costResource.."PerSecondWarning"] and "true" or "false"))
end
end
end
for pool, costResource in pairs({["LifeUnreservedPercent"] = "LifePercentCost", ["LifeUnreservedPercent"] = "LifePercentPerSecondCost", ["ManaUnreservedPercent"] = "ManaPercentPerSecondCost", ["ManaUnreservedPercent"] = "ManaPercentCost", ["ESPercentPerSecondCost"] = "ESPercentPerSecondCost"}) do
local cachedCost = GlobalCache.cachedData["CACHE"][uuid].Env.player.output[costResource]
if cachedCost then
if costResource == "ESPercentPerSecondCost" then --Assuming 100% of Es is always available
output["ESPercentPerSecondCostPercentPerSecondWarning"] = output["ESPercentPerSecondCostPercentPerSecondWarning"] or ((output["EnergyShield"] or 0) < cachedCost)
else
output[costResource.."PercentPerSecondWarning"] = output[costResource.."PercentPerSecondWarning"] or ((output[pool] or 0) < cachedCost)
end
end
end
end
end

-- Build output across all skills added to FullDPS skills
GlobalCache.noCache = true
Expand Down
3 changes: 3 additions & 0 deletions src/Modules/Common.lua
Original file line number Diff line number Diff line change
Expand Up @@ -672,6 +672,9 @@ function cacheData(uuid, env)
Name = env.player.mainSkill.activeEffect.grantedEffect.name,
Speed = env.player.output.Speed,
ManaCost = env.player.output.ManaCost,
LifeCost = env.player.output.LifeCost,
ESCost = env.player.output.ESCost,
RageCost = env.player.output.RageCost,
HitChance = env.player.output.HitChance,
PreEffectiveCritChance = env.player.output.PreEffectiveCritChance,
CritChance = env.player.output.CritChance,
Expand Down

0 comments on commit 92afbf2

Please sign in to comment.