From 858e2406582f7643a914e0435598a9eb85d92811 Mon Sep 17 00:00:00 2001 From: LocalIdentity Date: Sat, 13 Jun 2026 04:03:27 +1000 Subject: [PATCH] Fix calculation of inverted Resistances when having a source of Pen The calculation for inverted res was handled incorrectly where it was applying an average reduction to the resistance value and then we were applying pen on top of that We now calculated the average res based on the chance to invert and the impact of pen --- spec/System/TestSkills_spec.lua | 14 ++++++++++++++ src/Modules/CalcBreakdown.lua | 21 +++++++++++++++------ src/Modules/CalcOffence.lua | 28 +++++++++++++++++----------- 3 files changed, 46 insertions(+), 17 deletions(-) diff --git a/spec/System/TestSkills_spec.lua b/spec/System/TestSkills_spec.lua index 21877e1383..6b7d664ee0 100644 --- a/spec/System/TestSkills_spec.lua +++ b/spec/System/TestSkills_spec.lua @@ -798,6 +798,20 @@ describe("TestSkills", function() assert.True(build.calcsTab.mainEnv.enemyDB:Sum("BASE", nil, "FireResist") < fireResistWithoutPotentExposure) end) + it("averages inverted elemental resistance after penetration", function() + build.skillsTab:PasteSocketGroup("Fireball 20/0 1") + build.configTab.input.enemyIsBoss = "None" + build.configTab.input.enemyFireResist = 50 + build.configTab.input.customMods = "Hits have 50% chance to treat Enemy Monster Elemental Resistance values as inverted\nDamage Penetrates 50% of Enemy Fire Resistance" + build.configTab:BuildModList() + runCallback("OnFrame") + + assert.are.equals(1.25, build.calcsTab.calcsOutput.FireEffMult) + local breakdownText = table.concat(build.calcsTab.calcsEnv.player.breakdown.FireEffMult, "\n") + assert.truthy(breakdownText:match("inverted hit")) + assert.truthy(breakdownText:match("weighted average")) + end) + it("Test granted skills with exposure stats make exposure configurable", function() build.skillsTab:PasteSocketGroup("Fireball 20/0 1") local spec = build.spec diff --git a/src/Modules/CalcBreakdown.lua b/src/Modules/CalcBreakdown.lua index c2740d5dcd..a2ab6b7738 100644 --- a/src/Modules/CalcBreakdown.lua +++ b/src/Modules/CalcBreakdown.lua @@ -110,20 +110,29 @@ function breakdown.area(base, areaMod, total, incBreakpoint, moreBreakpoint, red return out end -function breakdown.effMult(damageType, resist, pen, taken, mult, takenMore, sourceRes, useRes, invertChance, minPen) +function breakdown.effMult(damageType, resist, pen, taken, mult, takenMore, sourceRes, useRes, invertChance, minPen, effectiveResist) local out = { } local resistForm = (damageType == "Physical") and "physical damage reduction" or "resistance" local resistLabel = resistForm - - if invertChance and invertChance ~= 0 then - resistLabel = "average inverted "..resistForm + minPen = minPen or 0 + local calcPenResist = function(resist) + return resist > minPen and m_max(resist - pen, minPen) or resist end + effectiveResist = effectiveResist or calcPenResist(resist) + if sourceRes and sourceRes ~= damageType then t_insert(out, s_format("Enemy %s: %d%% ^8(%s)", resistLabel, resist, sourceRes)) elseif resist ~= 0 then t_insert(out, s_format("Enemy %s: %d%%", resistLabel, resist)) end - if pen ~= 0 or not useRes then + if invertChance and invertChance ~= 0 and useRes then + local normalResist = calcPenResist(resist) + local invertedResist = calcPenResist(-resist) + t_insert(out, "Effective resistance:") + t_insert(out, s_format("%g%% ^8(non-inverted hit after penetration)", normalResist)) + t_insert(out, s_format("%g%% ^8(inverted hit after penetration)", invertedResist)) + t_insert(out, s_format("= %g%% ^8(weighted average from %.0f%% inversion chance)", effectiveResist, invertChance * 100)) + elseif pen ~= 0 or not useRes then t_insert(out, "Effective resistance:") t_insert(out, s_format("%d%% ^8(resistance)", resist)) if pen < 0 then @@ -145,7 +154,7 @@ function breakdown.effMult(damageType, resist, pen, taken, mult, takenMore, sour if useRes then breakdown.multiChain(out, { label = "Effective DPS modifier:", - { "%.2f ^8(%s)", 1 - (math.max(resist - pen,0)) / 100, resistForm }, + { "%.2f ^8(%s)", 1 - effectiveResist / 100, resistForm }, { "%.2f ^8(increased/reduced damage taken)", 1 + taken / 100 }, { "%.2f ^8(more/less damage taken)", takenMore }, total = s_format("= %.3f", mult), diff --git a/src/Modules/CalcOffence.lua b/src/Modules/CalcOffence.lua index c314de83f0..a750c61c45 100644 --- a/src/Modules/CalcOffence.lua +++ b/src/Modules/CalcOffence.lua @@ -4157,11 +4157,7 @@ function calcs.offence(env, actor, activeSkill) end end - local invertChance = m_max(m_min(skillModList:Sum("CHANCE", cfg, "HitsInvertEleResChance"), 1), 0) - if isElemental[damageType] and invertChance > 0 then - -- resist = (1 - invertChance) * resist + invertChance * (-1 * resist) - resist = resist - 2 * invertChance * resist - end + local invertChance = isElemental[damageType] and m_max(m_min(skillModList:Sum("CHANCE", cfg, "HitsInvertEleResChance"), 1), 0) or 0 sourceRes = env.modDB:Flag(nil, "Enemy"..sourceRes.."ResistEqualToYours") and "Your "..sourceRes.." Resistance" or (env.partyMembers.modDB:Flag(nil, "Enemy"..sourceRes.."ResistEqualToYours") and "Party Member "..sourceRes.." Resistance" or sourceRes) if skillFlags.projectile then takenInc = takenInc + enemyDB:Sum("INC", nil, "ProjectileDamageTaken") @@ -4174,10 +4170,20 @@ function calcs.offence(env, actor, activeSkill) end local effMult = (1 + takenInc / 100) * takenMore local useRes = useThisResist(damageType) + local effectiveResist = resist + local calcPenResist = function(resist) + return resist > minPen and m_max(resist - pen, minPen) or resist + end if skillModList:Flag(cfg, isElemental[damageType] and "CannotElePenIgnore" or nil) then - effMult = effMult * (1 - resist / 100) + effectiveResist = (isElemental[damageType] and invertChance > 0) and (resist - 2 * invertChance * resist) or resist + effMult = effMult * (1 - effectiveResist / 100) elseif useRes then - effMult = effMult * (1 - (resist > minPen and m_max(resist - pen, minPen) or resist) / 100) + if isElemental[damageType] and invertChance > 0 then + effectiveResist = calcPenResist(resist) * (1 - invertChance) + calcPenResist(-resist) * invertChance + else + effectiveResist = calcPenResist(resist) + end + effMult = effMult * (1 - effectiveResist / 100) end damageTypeHitMin = damageTypeHitMin * effMult damageTypeHitMax = damageTypeHitMax * effMult @@ -4185,12 +4191,12 @@ function calcs.offence(env, actor, activeSkill) if env.mode == "CALCS" then output[damageType.."EffMult"] = effMult end - if pass == 2 and breakdown and (effMult ~= 1 or sourceRes ~= damageType) and skillModList:Flag(cfg, isElemental[damageType] and "CannotElePenIgnore" or nil) then + if pass == 2 and breakdown and (effMult ~= 1 or sourceRes ~= damageType or invertChance > 0) and skillModList:Flag(cfg, isElemental[damageType] and "CannotElePenIgnore" or nil) then t_insert(breakdown[damageType], s_format("x %.3f ^8(effective DPS modifier)", effMult)) - breakdown[damageType.."EffMult"] = breakdown.effMult(damageType, resist, 0, takenInc, effMult, takenMore, sourceRes, useRes, invertChance, minPen) - elseif pass == 2 and breakdown and (effMult ~= 1 or (resist - pen) < minPen or sourceRes ~= damageType) then + breakdown[damageType.."EffMult"] = breakdown.effMult(damageType, resist, 0, takenInc, effMult, takenMore, sourceRes, useRes, invertChance, minPen, effectiveResist) + elseif pass == 2 and breakdown and (effMult ~= 1 or (resist - pen) < minPen or sourceRes ~= damageType or invertChance > 0) then t_insert(breakdown[damageType], s_format("x %.3f ^8(effective DPS modifier)", effMult)) - breakdown[damageType.."EffMult"] = breakdown.effMult(damageType, resist, pen, takenInc, effMult, takenMore, sourceRes, useRes, invertChance, minPen) + breakdown[damageType.."EffMult"] = breakdown.effMult(damageType, resist, pen, takenInc, effMult, takenMore, sourceRes, useRes, invertChance, minPen, effectiveResist) end end if pass == 2 and breakdown then