diff --git a/spec/System/TestSkills_spec.lua b/spec/System/TestSkills_spec.lua index 5d1516a561f..6b832ed95f7 100644 --- a/spec/System/TestSkills_spec.lua +++ b/spec/System/TestSkills_spec.lua @@ -150,6 +150,22 @@ describe("TestAttacks", function() assert.True(preAdrenalineMaxStages < build.calcsTab.mainEnv.player.activeSkillList[1].skillModList:Sum("BASE", nil, "Multiplier:BlightMaxStages")) 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") + + -- Unlike PoE 2, PoE 1 penetration can lower resistance below zero: + -- 50% of hits use 0% resistance and 50% use -100% resistance. + assert.are.equals(1.5, build.calcsTab.calcsOutput.FireEffMult) + local breakdownText = table.concat(build.calcsTab.calcsEnv.player.breakdown.FireEffMult, "\n") + assert.is_truthy(breakdownText:match("inverted hit")) + assert.is_truthy(breakdownText:match("weighted average")) + end) it("Test cost efficiency modifiers", function() -- Test Mana Cost Efficiency build.skillsTab:PasteSocketGroup("Ball Lightning 1/0 1\n") @@ -212,4 +228,4 @@ describe("TestAttacks", function() local finalCost = build.calcsTab.mainOutput.ManaCost assert.are.equals(7, round(finalCost)) end) -end) \ No newline at end of file +end) diff --git a/src/Modules/CalcBreakdown.lua b/src/Modules/CalcBreakdown.lua index 6df1f18d867..a264a8dddbf 100644 --- a/src/Modules/CalcBreakdown.lua +++ b/src/Modules/CalcBreakdown.lua @@ -108,20 +108,25 @@ function breakdown.area(base, areaMod, total, incBreakpoint, moreBreakpoint, red return out end -function breakdown.effMult(damageType, resist, pen, taken, mult, takenMore, sourceRes, useRes, invertChance) +function breakdown.effMult(damageType, resist, pen, taken, mult, takenMore, sourceRes, useRes, invertChance, effectiveResist) local out = { } local resistForm = (damageType == "Physical") and "physical damage reduction" or "resistance" local resistLabel = resistForm + effectiveResist = effectiveResist or resist - pen - if invertChance and invertChance ~= 0 then - resistLabel = "average inverted "..resistForm - end 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 = resist - pen + local invertedResist = -resist - pen + 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 @@ -139,7 +144,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 - (resist - pen) / 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 673001eba72..b04e01012f6 100644 --- a/src/Modules/CalcOffence.lua +++ b/src/Modules/CalcOffence.lua @@ -3367,14 +3367,8 @@ function calcs.offence(env, actor, activeSkill) end end local invertChanceEle = m_max(m_min(skillModList:Sum("CHANCE", cfg, "HitsInvertEleResChance"), 1), 0) - if isElemental[damageType] and invertChanceEle > 0 then - -- resist = (1 - invertChanceEle) * resist + invertChanceEle * (-1 * resist) - resist = resist - 2 * invertChanceEle * resist - end local invertChanceChaos = m_max(m_min(skillModList:Sum("CHANCE", cfg, "HitsInvertChaosResChance"), 1), 0) - if damageType == "Chaos" and invertChanceChaos > 0 then - resist = resist - 2 * invertChanceChaos * resist - end + local invertChance = (isElemental[damageType] and invertChanceEle) or (damageType == "Chaos" and invertChanceChaos) 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") @@ -3388,10 +3382,17 @@ function calcs.offence(env, actor, activeSkill) local effMult = (1 + takenInc / 100) * takenMore local useResChance = useThisResist(damageType) local useRes = useResChance > 0 + local effectiveResist = resist if skillModList:Flag(cfg, isElemental[damageType] and "CannotElePenIgnore" or nil) then - effMult = effMult * (1 - resist / 100) + effectiveResist = invertChance > 0 and (resist - 2 * invertChance * resist) or resist + effMult = effMult * (1 - effectiveResist / 100) elseif useRes then - effMult = effMult * (1 - ((resist - pen) * useResChance) / 100) + if invertChance > 0 then + effectiveResist = ((resist - pen) * (1 - invertChance) + (-resist - pen) * invertChance) * useResChance + else + effectiveResist = (resist - pen) * useResChance + end + effMult = effMult * (1 - effectiveResist / 100) end damageTypeHitMin = damageTypeHitMin * effMult damageTypeHitMax = damageTypeHitMax * effMult @@ -3399,13 +3400,12 @@ function calcs.offence(env, actor, activeSkill) if env.mode == "CALCS" then output[damageType.."EffMult"] = effMult end - local invertChance = (isElemental[damageType] and invertChanceEle) or (damageType == "Chaos" and invertChanceChaos) or 0 - 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) - elseif pass == 2 and breakdown and (effMult ~= 1 or sourceRes ~= damageType) then + breakdown[damageType.."EffMult"] = breakdown.effMult(damageType, resist, 0, takenInc, effMult, takenMore, sourceRes, useRes, invertChance, effectiveResist) + elseif pass == 2 and breakdown and (effMult ~= 1 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) + breakdown[damageType.."EffMult"] = breakdown.effMult(damageType, resist, pen, takenInc, effMult, takenMore, sourceRes, useRes, invertChance, effectiveResist) end end if pass == 2 and breakdown then