diff --git a/spec/System/TestSkills_spec.lua b/spec/System/TestSkills_spec.lua index 69b21f4677..345187f53a 100644 --- a/spec/System/TestSkills_spec.lua +++ b/spec/System/TestSkills_spec.lua @@ -216,6 +216,42 @@ describe("TestAttacks", function() assert.True(math.abs(finalCost - 12) < 0.1) -- floor(12 * 1.5) / 1.5 end) + it("Test flat cost is added before cost efficiency", function() + -- In-game order is ((base cost * multipliers) + flat cost) / (1 + cost efficiency) + build.skillsTab:PasteSocketGroup("Hydrosphere 1/0 1\n") + + -- Hydrosphere 12 base mana cost + build.configTab.input.customMods = "+10 to Total Mana Cost\n50% increased Mana Cost Efficiency" + build.configTab:BuildModList() + runCallback("OnFrame") + + local finalCost = build.calcsTab.mainOutput.ManaCost + -- (12 + 10) / 1.5 = 14.667 + assert.True(math.abs(finalCost - 22 / 1.5) < 0.001) + end) + it("Test flat cost is added before cost efficiency for life costs", function() + build.skillsTab:PasteSocketGroup("Hydrosphere 1/0 1\n") + + -- Convert Hydrosphere's 12 base cost to life, then add +10 flat and 50% efficiency + build.configTab.input.customMods = "Skills Cost Life instead of Mana\n+10 to Total Cost\n50% increased Cost Efficiency" + build.configTab:BuildModList() + runCallback("OnFrame") + + -- (12 + 10) / 1.5 = 14.667 + assert.True(math.abs(build.calcsTab.mainOutput.LifeCost - 22 / 1.5) < 0.001) + end) + + it("Test flat cost is added before cost efficiency for energy shield costs (#10003)", function() + build.skillsTab:PasteSocketGroup("Hydrosphere 1/0 1\n") + + -- Convert Hydrosphere's 12 base cost to ES, then add +10 flat and 50% efficiency + build.configTab.input.customMods = "Skills Cost Energy Shield instead of Mana or Life\n+10 to Total Cost\n50% increased Cost Efficiency" + build.configTab:BuildModList() + runCallback("OnFrame") + + -- (12 + 10) / 1.5 = 14.667 + assert.True(math.abs(build.calcsTab.mainOutput.ESCost - 22 / 1.5) < 0.001) + end) it("Test mana cost efficiency with support gems", function() -- Test interaction between cost efficiency and cost multipliers build.skillsTab:PasteSocketGroup("Contagion 6/0 1\nMagnified Area I 1/0 1") diff --git a/src/Modules/CalcOffence.lua b/src/Modules/CalcOffence.lua index 85ecb9f772..956ac30db3 100644 --- a/src/Modules/CalcOffence.lua +++ b/src/Modules/CalcOffence.lua @@ -1756,7 +1756,7 @@ function calcs.offence(env, actor, activeSkill) moreType = skillModList:More(skillCfg, val.type.."Cost") moreCost = skillModList:More(skillCfg, "Cost") inc = skillModList:Sum("INC", skillCfg, val.type.."Cost", "Cost") - output[costNameRaw] = val.baseCostRaw and m_max(0, m_max(0, (1 + inc / 100) * val.baseCostRaw * moreType * moreCost / costEfficiency) + val.totalCost) + output[costNameRaw] = val.baseCostRaw and m_max(0, m_max(0, (1 + inc / 100) * val.baseCostRaw * moreType * moreCost) + val.totalCost) / costEfficiency if inc < 0 then output[costName] = m_max(0, m_ceil((1 + inc / 100) * output[costName])) else @@ -1772,9 +1772,9 @@ function calcs.offence(env, actor, activeSkill) else output[costName] = m_max(0, m_floor(moreCost * output[costName])) end + output[costName] = m_max(0, output[costName] + val.totalCost) -- Apply cost efficiency (similar to reservation efficiency) output[costName] = m_max(0, output[costName] / costEfficiency) - output[costName] = m_max(0, output[costName] + val.totalCost) if val.type == "Mana" and hybridLifeCost > 0 then -- Life/Mana Mastery output[costName] = m_max(0, m_floor((1 - hybridLifeCost) * output[costName])) output[costNameRaw] = output[costNameRaw] and m_max(0, (1 - hybridLifeCost) * output[costNameRaw]) @@ -1785,10 +1785,10 @@ function calcs.offence(env, actor, activeSkill) output[costName] = m_floor(val.baseCost + val.baseCostNoMult) output[costName] = m_max(0, (1 + inc / 100) * output[costName]) output[costName] = m_max(0, moreType * output[costName]) + output[costName] = m_max(0, output[costName] + val.totalCost) -- Apply cost efficiency for unaffected costs too output[costName] = m_max(0, output[costName] / costEfficiency) - output[costName] = m_max(0, output[costName] + val.totalCost) - output[costNameRaw] = val.baseCostRaw and m_max(0, m_max(0, (1 + inc / 100) * (val.baseCostRaw + val.baseCostNoMult) * moreType / costEfficiency) + val.totalCost) + output[costNameRaw] = val.baseCostRaw and m_max(0, m_max(0, (1 + inc / 100) * (val.baseCostRaw + val.baseCostNoMult) * moreType) + val.totalCost) / costEfficiency end if breakdown and hasCost then breakdown[costName] = { @@ -1815,16 +1815,16 @@ function calcs.offence(env, actor, activeSkill) if moreType ~= 1 then t_insert(breakdown[costName], s_format("x %.2f ^8(more/less "..val.text.." cost)", moreType)) end + if val.totalCost ~= 0 then + t_insert(breakdown[costName], s_format("%+d ^8(total " .. val.text .. " cost)", val.totalCost)) + end if costEfficiency ~= 1 then t_insert(breakdown[costName], s_format("/ %.2f ^8(" .. val.text .. " cost efficiency)", costEfficiency)) end - if val.totalCost ~= 0 then - t_insert(breakdown[costName], s_format("%+d ^8(total "..val.text.." cost)", val.totalCost)) - end if val.type == "Mana" and hybridLifeCost > 0 then t_insert(breakdown[costName], s_format("x %.2f ^8(%d%% paid for with life)", (1-hybridLifeCost), hybridLifeCost*100)) end - t_insert(breakdown[costName], s_format("= %"..(val.upfront and "d" or ".2f")..(val.percent and "%%" or ""), output[costName])) + t_insert(breakdown[costName], s_format("= %" .. (val.upfront and "d" or ".2f") .. (val.percent and "%%" or ""), m_ceil(output[costName]))) end end end diff --git a/src/Modules/CalcSections.lua b/src/Modules/CalcSections.lua index 0dc6da8a03..fcc1f55f16 100644 --- a/src/Modules/CalcSections.lua +++ b/src/Modules/CalcSections.lua @@ -40,6 +40,18 @@ local fireConvert = { "FireDamageConvertToChaos", "ElementalDamageConvertToChaos", "NonChaosDamageConvertToChaos", "FireDamageGainAsChaos", "ElementalDamageGainAsChaos", "NonChaosDamageGainAsChaos" } +local manaCost = { + "ManaCost", "Cost", "ManaCostNoMult", "ManaCostEfficiency", "CostEfficiency" +} +local lifeCost = { + "LifeCost", "Cost", "LifeCostNoMult", "LifeCostEfficiency", "CostEfficiency" +} +local ESCost = { + "ESCost", "Cost", "ESCostNoMult", "ESCostEfficiency", "CostEfficiency" +} +local rageCost = { + "RageCost", "Cost", "RageCostNoMult", "RageCostEfficiency", "CostEfficiency" +} -- format {width, id, group, color, subsection:{default hidden, label, data:{}}} return { @@ -619,20 +631,20 @@ return { { 1, "SkillTypeStats", 1, colorCodes.OFFENCE, {{ defaultCollapsed = false, label = "Skill type-specific Stats", data = { { label = "Gem Level", haveOutput = "GemHasLevel", { format = "{0:output:GemLevel}", { breakdown = "GemLevel" }, { modName = { "GemLevel" }, cfg = "skill" },{ modName = { "GemSupportLevel" }, cfg = "skill" }, { modName = { "GemItemLevel" }, cfg = "skill" }, }, }, { label = "Gem Quality", haveOutput = "GemHasQuality", { format = "{0:output:GemQuality}", { breakdown = "GemQuality" }, { modName = { "GemQuality" }, cfg = "skill" },{ modName = { "GemSupportQuality" }, cfg = "skill" }, { modName = { "GemItemQuality" }, cfg = "skill" }, }, }, - { label = "Mana Cost", color = colorCodes.MANA, haveOutput = "ManaHasCost", { format = "{0:output:ManaCost}", { breakdown = "ManaCost" }, { modName = { "ManaCost", "Cost", "ManaCostNoMult" }, cfg = "skill" }, }, }, - { label = "Mana % Cost", color = colorCodes.MANA, haveOutput = "ManaPercentHasCost", { format = "{0:output:ManaPercentCost}", { breakdown = "ManaPercentCost" }, { modName = { "ManaCost", "Cost", "ManaCostNoMult" }, cfg = "skill" }, }, }, - { label = "Mana per second", color = colorCodes.MANA, haveOutput = "ManaPerSecondHasCost", { format = "{2:output:ManaPerSecondCost}", { breakdown = "ManaPerSecondCost" }, { modName = { "ManaCost", "Cost", "ManaCostNoMult" }, cfg = "skill" }, }, }, - { label = "Mana % per second", color = colorCodes.MANA, haveOutput = "ManaPercentPerSecondHasCost", { format = "{2:output:ManaPercentPerSecondCost}", { breakdown = "ManaPercentPerSecondCost" }, { modName = { "ManaCost", "Cost", "ManaCostNoMult" }, cfg = "skill" }, }, }, - { label = "Life Cost", color = colorCodes.LIFE, haveOutput = "LifeHasCost", { format = "{0:output:LifeCost}", { breakdown = "LifeCost" }, { modName = { "LifeCost", "Cost", "LifeCostNoMult" }, cfg = "skill" }, }, }, - { label = "Life % Cost", color = colorCodes.LIFE, haveOutput = "LifePercentHasCost", { format = "{0:output:LifePercentCost}", { breakdown = "LifePercentCost" }, { modName = { "LifeCost", "Cost", "LifeCostNoMult" }, cfg = "skill" }, }, }, - { label = "Life per second", color = colorCodes.LIFE, haveOutput = "LifePerSecondHasCost", { format = "{2:output:LifePerSecondCost}", { breakdown = "LifePerSecondCost" }, { modName = { "LifeCost", "Cost", "LifeCostNoMult" }, cfg = "skill" }, }, }, - { label = "Life % per second", color = colorCodes.LIFE, haveOutput = "LifePercentPerSecondHasCost", { format = "{2:output:LifePercentPerSecondCost}", { breakdown = "LifePercentPerSecondCost" }, { modName = { "LifeCost", "Cost", "LifeCostNoMult" }, cfg = "skill" }, }, }, - { label = "ES Cost", color = colorCodes.ES, haveOutput = "ESHasCost", { format = "{0:output:ESCost}", { breakdown = "ESCost" }, { modName = { "ESCost", "Cost", "ESCostNoMult" }, cfg = "skill" }, }, }, - { label = "ES per second", color = colorCodes.ES, haveOutput = "ESPerSecondHasCost", { format = "{2:output:ESPerSecondCost}", { breakdown = "ESPerSecondCost" }, { modName = { "ESCost", "Cost", "ESCostNoMult" }, cfg = "skill" }, }, }, - { label = "ES % per second", color = colorCodes.ES, haveOutput = "ESPercentPerSecondHasCost", { format = "{2:output:ESPercentPerSecondCost}", { breakdown = "ESPercentPerSecondCost" }, { modName = { "ESCost", "Cost", "ESCostNoMult" }, cfg = "skill" }, }, }, - { label = "Rage Cost", color = colorCodes.RAGE, haveOutput = "RageHasCost", { format = "{0:output:RageCost}", { breakdown = "RageCost" }, { modName = { "RageCost", "Cost", "RageNoMult" }, cfg = "skill" }, }, }, - { label = "Rage per second", color = colorCodes.RAGE, haveOutput = "RagePerSecondHasCost", { format = "{2:output:RagePerSecondCost}", { breakdown = "RagePerSecondCost" }, { modName = { "RageCost", "Cost", "RageNoMult" }, cfg = "skill" }, }, }, - { label = "Soul Cost", color = colorCodes.RAGE, haveOutput = "SoulHasCost", { format = "{0:output:SoulCost}", { breakdown = "SoulCost" }, { modName = { "SoulCost" }, cfg = "skill" }, }, }, + { label = "Mana Cost", color = colorCodes.MANA, haveOutput = "ManaHasCost", { format = "{0:output:ManaCost}", { breakdown = "ManaCost" }, { modName = manaCost, cfg = "skill" }, }, }, + { label = "Mana % Cost", color = colorCodes.MANA, haveOutput = "ManaPercentHasCost", { format = "{0:output:ManaPercentCost}", { breakdown = "ManaPercentCost" }, { modName = manaCost, cfg = "skill" }, }, }, + { label = "Mana per second", color = colorCodes.MANA, haveOutput = "ManaPerSecondHasCost", { format = "{2:output:ManaPerSecondCost}", { breakdown = "ManaPerSecondCost" }, { modName = manaCost, cfg = "skill" }, }, }, + { label = "Mana % per second", color = colorCodes.MANA, haveOutput = "ManaPercentPerSecondHasCost", { format = "{2:output:ManaPercentPerSecondCost}", { breakdown = "ManaPercentPerSecondCost" }, { modName = manaCost, cfg = "skill" }, }, }, + { label = "Life Cost", color = colorCodes.LIFE, haveOutput = "LifeHasCost", { format = "{0:output:LifeCost}", { breakdown = "LifeCost" }, { modName = lifeCost, cfg = "skill" }, }, }, + { label = "Life % Cost", color = colorCodes.LIFE, haveOutput = "LifePercentHasCost", { format = "{0:output:LifePercentCost}", { breakdown = "LifePercentCost" }, { modName = lifeCost, cfg = "skill" }, }, }, + { label = "Life per second", color = colorCodes.LIFE, haveOutput = "LifePerSecondHasCost", { format = "{2:output:LifePerSecondCost}", { breakdown = "LifePerSecondCost" }, { modName = lifeCost, cfg = "skill" }, }, }, + { label = "Life % per second", color = colorCodes.LIFE, haveOutput = "LifePercentPerSecondHasCost", { format = "{2:output:LifePercentPerSecondCost}", { breakdown = "LifePercentPerSecondCost" }, { modName = lifeCost, cfg = "skill" }, }, }, + { label = "ES Cost", color = colorCodes.ES, haveOutput = "ESHasCost", { format = "{0:output:ESCost}", { breakdown = "ESCost" }, { modName = ESCost, cfg = "skill" }, }, }, + { label = "ES per second", color = colorCodes.ES, haveOutput = "ESPerSecondHasCost", { format = "{2:output:ESPerSecondCost}", { breakdown = "ESPerSecondCost" }, { modName = ESCost, cfg = "skill" }, }, }, + { label = "ES % per second", color = colorCodes.ES, haveOutput = "ESPercentPerSecondHasCost", { format = "{2:output:ESPercentPerSecondCost}", { breakdown = "ESPercentPerSecondCost" }, { modName = ESCost, cfg = "skill" }, }, }, + { label = "Rage Cost", color = colorCodes.RAGE, haveOutput = "RageHasCost", { format = "{0:output:RageCost}", { breakdown = "RageCost" }, { modName = rageCost, cfg = "skill" }, }, }, + { label = "Rage per second", color = colorCodes.RAGE, haveOutput = "RagePerSecondHasCost", { format = "{2:output:RagePerSecondCost}", { breakdown = "RagePerSecondCost" }, { modName = rageCost, cfg = "skill" }, }, }, + { label = "Soul Cost", color = colorCodes.RAGE, haveOutput = "SoulHasCost", { format = "{0:output:SoulCost}", { breakdown = "SoulCost" }, { modName = { "SoulCost", "SoulCostEfficiency" }, cfg = "skill" }, }, }, { label = "Active Minion Limit", haveOutput = "ActiveMinionLimit", { format = "{0:output:ActiveMinionLimit}" } }, { label = "Quantity Multiplier", haveOutput = "QuantityMultiplier", { format = "{0:output:QuantityMultiplier}", { breakdown = "QuantityMultiplier" },