From 2733503114b624808ee19e5e72008961e228be0b Mon Sep 17 00:00:00 2001 From: emptywalls Date: Mon, 13 Jul 2026 12:32:49 +0100 Subject: [PATCH 1/2] Fix an interaction between Rakiatas Flow and sources of "ignore non-negative elemental resistances" Support for "ignore non-negative elemental resistances" was raised as [2] and implemented with [3], however the implementation did not address resistances inversion. As shown in [1], Rakiatas inversion is done before ignoring. [1] https://youtu.be/2d646c8_m0k?t=428 [2] https://github.com/PathOfBuildingCommunity/PathOfBuilding-PoE2/issues/498 [3] https://github.com/PathOfBuildingCommunity/PathOfBuilding-PoE2/pull/690 --- src/Modules/CalcOffence.lua | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/Modules/CalcOffence.lua b/src/Modules/CalcOffence.lua index b6de2bf85d..ee5130f088 100644 --- a/src/Modules/CalcOffence.lua +++ b/src/Modules/CalcOffence.lua @@ -4093,6 +4093,7 @@ function calcs.offence(env, actor, activeSkill) if (damageTypeHitMin ~= 0 or damageTypeHitMax ~= 0) and env.mode_effective then -- Apply enemy resistances and damage taken modifiers local resist = 0 + local resistBeforeIgnoreNonNegative = 0 local pen = 0 local minPen = 0 local sourceRes = damageType @@ -4165,6 +4166,7 @@ function calcs.offence(env, actor, activeSkill) end sourceRes = elementUsed elseif isElemental[damageType] then + resistBeforeIgnoreNonNegative = resist if resist > 0 and modDB:Flag(cfg, "IgnoreNonNegativeEleRes") then resist = 0 end @@ -4194,11 +4196,11 @@ function calcs.offence(env, actor, activeSkill) return resist > minPen and m_max(resist - pen, minPen) or resist end if skillModList:Flag(cfg, isElemental[damageType] and "CannotElePenIgnore" or nil) then - effectiveResist = (isElemental[damageType] and invertChance > 0) and (resist - 2 * invertChance * resist) or resist + effectiveResist = (isElemental[damageType] and invertChance > 0) and (resistBeforeIgnoreNonNegative - 2 * invertChance * resistBeforeIgnoreNonNegative) or resist effMult = effMult * (1 - effectiveResist / 100) elseif useRes then if isElemental[damageType] and invertChance > 0 then - effectiveResist = calcPenResist(resist) * (1 - invertChance) + calcPenResist(-resist) * invertChance + effectiveResist = calcPenResist(resist) * (1 - invertChance) + calcPenResist(-resistBeforeIgnoreNonNegative) * invertChance else effectiveResist = calcPenResist(resist) end From 14ca707165f975c8de77788a1885ad7f05712558 Mon Sep 17 00:00:00 2001 From: LocalIdentity Date: Mon, 31 Aug 2026 07:40:22 +1000 Subject: [PATCH 2/2] Fix implementation Fixes the implementation that had issues in a number of circumstances Fixes the breakdown too when change to invert mods are involved and adds tests --- spec/System/TestSkills_spec.lua | 40 +++++++++++++++++++++++++++++++++ src/Modules/CalcBreakdown.lua | 10 ++++----- src/Modules/CalcOffence.lua | 34 +++++++++++++++------------- 3 files changed, 64 insertions(+), 20 deletions(-) diff --git a/spec/System/TestSkills_spec.lua b/spec/System/TestSkills_spec.lua index 54c449879f..df8f2b7d3a 100644 --- a/spec/System/TestSkills_spec.lua +++ b/spec/System/TestSkills_spec.lua @@ -1146,6 +1146,46 @@ describe("TestSkills", function() assert.truthy(breakdownText:match("weighted average")) end) + it("ignores non-negative elemental resistance after inversion", function() + build.skillsTab:PasteSocketGroup("Fireball 20/0 1") + build.configTab.input.enemyIsBoss = "None" + build.configTab.input.enemyFireResist = -50 + build.configTab.input.conditionEnemyFrozen = true + build.configTab.input.customMods = "Hits have 100% chance to treat Enemy Monster Elemental Resistance values as inverted\nHits ignore non-negative Elemental Resistances of Frozen Enemies" + build.configTab:BuildModList() + runCallback("OnFrame") + + assert.are.equals(1, build.calcsTab.calcsOutput.FireEffMult) + end) + + it("inverts the selected lowest elemental resistance", function() + build.skillsTab:PasteSocketGroup("Fireball 20/0 1") + build.configTab.input.enemyIsBoss = "None" + build.configTab.input.enemyFireResist = 50 + build.configTab.input.enemyColdResist = 20 + build.configTab.input.enemyLightningResist = 30 + build.configTab.input.customMods = "Hits have 100% chance to treat Enemy Monster Elemental Resistance values as inverted\nElemental Damage you Deal with Hits is Resisted by Lowest Elemental Resistance instead" + build.configTab:BuildModList() + runCallback("OnFrame") + + assert.are.equals(1.2, build.calcsTab.calcsOutput.FireEffMult) + end) + + it("shows the resistance used for each inversion outcome", function() + build.skillsTab:PasteSocketGroup("Fireball 20/0 1") + build.configTab.input.enemyIsBoss = "None" + build.configTab.input.enemyFireResist = 50 + build.configTab.input.conditionEnemyFrozen = true + build.configTab.input.customMods = "Hits have 50% chance to treat Enemy Monster Elemental Resistance values as inverted\nHits ignore non-negative Elemental Resistances of Frozen Enemies" + 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("0%% ^8%(non%-inverted hit after penetration%)")) + assert.truthy(breakdownText:match("%-50%% ^8%(inverted hit after penetration%)")) + 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 a2ab6b7738..6f75f18ad7 100644 --- a/src/Modules/CalcBreakdown.lua +++ b/src/Modules/CalcBreakdown.lua @@ -110,7 +110,7 @@ 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, effectiveResist) +function breakdown.effMult(damageType, resist, pen, taken, mult, takenMore, sourceRes, useRes, invertChance, minPen, effectiveResist, normalHitResist, invertedHitResist) local out = { } local resistForm = (damageType == "Physical") and "physical damage reduction" or "resistance" local resistLabel = resistForm @@ -126,11 +126,11 @@ function breakdown.effMult(damageType, resist, pen, taken, mult, takenMore, sour t_insert(out, s_format("Enemy %s: %d%%", resistLabel, resist)) end if invertChance and invertChance ~= 0 and useRes then - local normalResist = calcPenResist(resist) - local invertedResist = calcPenResist(-resist) + normalHitResist = normalHitResist or calcPenResist(resist) + invertedHitResist = invertedHitResist or 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(non-inverted hit after penetration)", normalHitResist)) + t_insert(out, s_format("%g%% ^8(inverted hit after penetration)", invertedHitResist)) 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:") diff --git a/src/Modules/CalcOffence.lua b/src/Modules/CalcOffence.lua index ee5130f088..3c73ec0cd8 100644 --- a/src/Modules/CalcOffence.lua +++ b/src/Modules/CalcOffence.lua @@ -4093,7 +4093,6 @@ function calcs.offence(env, actor, activeSkill) if (damageTypeHitMin ~= 0 or damageTypeHitMax ~= 0) and env.mode_effective then -- Apply enemy resistances and damage taken modifiers local resist = 0 - local resistBeforeIgnoreNonNegative = 0 local pen = 0 local minPen = 0 local sourceRes = damageType @@ -4166,10 +4165,6 @@ function calcs.offence(env, actor, activeSkill) end sourceRes = elementUsed elseif isElemental[damageType] then - resistBeforeIgnoreNonNegative = resist - if resist > 0 and modDB:Flag(cfg, "IgnoreNonNegativeEleRes") then - resist = 0 - end pen = skillModList:Sum("BASE", cfg, damageType.."Penetration", "ElementalPenetration") minPen = skillModList:Sum("BASE", cfg, damageType.."PenetrationMinimum", "ElementalPenetrationMinimum") takenInc = takenInc + enemyDB:Sum("INC", cfg, "ElementalDamageTaken") @@ -4195,14 +4190,22 @@ function calcs.offence(env, actor, activeSkill) 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 - effectiveResist = (isElemental[damageType] and invertChance > 0) and (resistBeforeIgnoreNonNegative - 2 * invertChance * resistBeforeIgnoreNonNegative) or resist - effMult = effMult * (1 - effectiveResist / 100) - elseif useRes then + local cannotElePenIgnore = isElemental[damageType] and skillModList:Flag(cfg, "CannotElePenIgnore") + local ignoreNonNegativeEleRes = isElemental[damageType] and modDB:Flag(cfg, "IgnoreNonNegativeEleRes") + local calcHitResist = function(hitResist) + if not cannotElePenIgnore and ignoreNonNegativeEleRes and hitResist >= 0 then + return 0 + end + return cannotElePenIgnore and hitResist or calcPenResist(hitResist) + end + local normalHitResist = calcHitResist(resist) + local invertedHitResist = calcHitResist(-resist) + local usesResistance = cannotElePenIgnore or useRes + if usesResistance then if isElemental[damageType] and invertChance > 0 then - effectiveResist = calcPenResist(resist) * (1 - invertChance) + calcPenResist(-resistBeforeIgnoreNonNegative) * invertChance + effectiveResist = normalHitResist * (1 - invertChance) + invertedHitResist * invertChance else - effectiveResist = calcPenResist(resist) + effectiveResist = normalHitResist end effMult = effMult * (1 - effectiveResist / 100) end @@ -4212,12 +4215,13 @@ 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 or invertChance > 0) and skillModList:Flag(cfg, isElemental[damageType] and "CannotElePenIgnore" or nil) then + local breakdownResist = invertChance > 0 and resist or normalHitResist + if pass == 2 and breakdown and (effMult ~= 1 or sourceRes ~= damageType or invertChance > 0) and cannotElePenIgnore 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, effectiveResist) + breakdown[damageType.."EffMult"] = breakdown.effMult(damageType, breakdownResist, 0, takenInc, effMult, takenMore, sourceRes, usesResistance, invertChance, minPen, effectiveResist, normalHitResist, invertedHitResist) 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, effectiveResist) + breakdown[damageType.."EffMult"] = breakdown.effMult(damageType, breakdownResist, pen, takenInc, effMult, takenMore, sourceRes, usesResistance, invertChance, minPen, effectiveResist, normalHitResist, invertedHitResist) end end if pass == 2 and breakdown then @@ -6342,4 +6346,4 @@ function calcs.offence(env, actor, activeSkill) output.CullingDPS = output.CombinedDPS * (bestCull - 1) output.ReservationDPS = output.CombinedDPS * (output.ReservationDpsMultiplier - 1) output.CombinedDPS = output.CombinedDPS * bestCull * output.ReservationDpsMultiplier -end \ No newline at end of file +end