From 9a1a87066588a60f05b5029104bcc2d2500bd22e Mon Sep 17 00:00:00 2001 From: EminGul Date: Sun, 15 Mar 2026 13:21:48 -0400 Subject: [PATCH 1/2] Fix: Valako, Storm's Embrace and Tasalio, Cleansing Water Ascendancy nodes applying effects twice due to Foulborn Choir of the Storm --- src/Modules/CalcDefence.lua | 71 +++++++++++++++++++------------------ 1 file changed, 37 insertions(+), 34 deletions(-) diff --git a/src/Modules/CalcDefence.lua b/src/Modules/CalcDefence.lua index 74b9583b07..09ef83a274 100644 --- a/src/Modules/CalcDefence.lua +++ b/src/Modules/CalcDefence.lua @@ -513,47 +513,50 @@ function calcs.resistances(actor) output["PhysicalResist"] = 0 -- Process Resistance conversion mods - for _, resFrom in ipairs(resistTypeList) do - local maxRes - for _, resTo in ipairs(resistTypeList) do - local conversionRate = modDB:Sum("BASE", nil, resFrom.."MaxResConvertTo"..resTo) / 100 - if conversionRate ~= 0 then - if not maxRes then - maxRes = 0 - for _, mod in ipairs(modDB:Tabulate("BASE", nil, resFrom.."ResistMax")) do - if mod.mod.source ~= "Base" then - maxRes = maxRes + mod.value + if not actor.resistConversionApplied then + actor.resistConversionApplied = true + for _, resFrom in ipairs(resistTypeList) do + local maxRes + for _, resTo in ipairs(resistTypeList) do + local conversionRate = modDB:Sum("BASE", nil, resFrom.."MaxResConvertTo"..resTo) / 100 + if conversionRate ~= 0 then + if not maxRes then + maxRes = 0 + for _, mod in ipairs(modDB:Tabulate("BASE", nil, resFrom.."ResistMax")) do + if mod.mod.source ~= "Base" then + maxRes = maxRes + mod.value + end end end - end - if maxRes ~= 0 then - modDB:NewMod(resTo.."ResistMax", "BASE", maxRes * conversionRate, resFrom.." To "..resTo.." Max Resistance Conversion") + if maxRes ~= 0 then + modDB:NewMod(resTo.."ResistMax", "BASE", maxRes * conversionRate, resFrom.." To "..resTo.." Max Resistance Conversion") + end end end end - end - - for _, resFrom in ipairs(resistTypeList) do - local res - for _, resTo in ipairs(resistTypeList) do - local conversionRate = modDB:Sum("BASE", nil, resFrom.."ResConvertTo"..resTo) / 100 - if conversionRate ~= 0 then - if not res then - res = 0 - for _, mod in ipairs(modDB:Tabulate("BASE", nil, resFrom.."Resist")) do - if mod.mod.source ~= "Base" then - res = res + mod.value + + for _, resFrom in ipairs(resistTypeList) do + local res + for _, resTo in ipairs(resistTypeList) do + local conversionRate = modDB:Sum("BASE", nil, resFrom.."ResConvertTo"..resTo) / 100 + if conversionRate ~= 0 then + if not res then + res = 0 + for _, mod in ipairs(modDB:Tabulate("BASE", nil, resFrom.."Resist")) do + if mod.mod.source ~= "Base" then + res = res + mod.value + end end end - end - if res ~= 0 then - modDB:NewMod(resTo.."Resist", "BASE", res * conversionRate, resFrom.." To "..resTo.." Resistance Conversion") - end - for _, mod in ipairs(modDB:Tabulate("INC", nil, resFrom.."Resist")) do - modDB:NewMod(resTo.."Resist", "INC", mod.value * conversionRate, mod.mod.source) - end - for _, mod in ipairs(modDB:Tabulate("MORE", nil, resFrom.."Resist")) do - modDB:NewMod(resTo.."Resist", "MORE", mod.value * conversionRate, mod.mod.source) + if res ~= 0 then + modDB:NewMod(resTo.."Resist", "BASE", res * conversionRate, resFrom.." To "..resTo.." Resistance Conversion") + end + for _, mod in ipairs(modDB:Tabulate("INC", nil, resFrom.."Resist")) do + modDB:NewMod(resTo.."Resist", "INC", mod.value * conversionRate, mod.mod.source) + end + for _, mod in ipairs(modDB:Tabulate("MORE", nil, resFrom.."Resist")) do + modDB:NewMod(resTo.."Resist", "MORE", mod.value * conversionRate, mod.mod.source) + end end end end From 7a8ed99372f1e2f9b59423331d2c0e6f8826eb65 Mon Sep 17 00:00:00 2001 From: LocalIdentity Date: Sat, 18 Apr 2026 06:36:39 +1000 Subject: [PATCH 2/2] Use temp actor instead of changing calcs --- spec/System/TestDefence_spec.lua | 22 +++++++++- src/Modules/CalcDefence.lua | 69 +++++++++++++++----------------- src/Modules/CalcPerform.lua | 12 +++++- 3 files changed, 64 insertions(+), 39 deletions(-) diff --git a/spec/System/TestDefence_spec.lua b/spec/System/TestDefence_spec.lua index 8a0d4daa3f..6dacc2dfdd 100644 --- a/spec/System/TestDefence_spec.lua +++ b/spec/System/TestDefence_spec.lua @@ -358,6 +358,26 @@ describe("TestDefence", function() build.skillsTab.socketGroupList = {} end) + it("foulborn resistance conversion remains stable across recalculation", function() + build.configTab.input.enemyIsBoss = "None" + build.configTab.input.customMods = "\z + +300 to fire resistance\n\z + modifiers to fire resistance also apply to cold and lightning resistances at 50% of their value\n\z + mana is increased by 100% of overcapped lightning resistance\n\z + " + build.configTab:BuildModList() + runCallback("OnFrame") + + assert.are.equals(90, build.calcsTab.calcsOutput.LightningResistTotal) + assert.are.equals(15, build.calcsTab.calcsOutput.LightningResistOverCap) + + build.configTab:BuildModList() + runCallback("OnFrame") + + assert.are.equals(90, build.calcsTab.calcsOutput.LightningResistTotal) + assert.are.equals(15, build.calcsTab.calcsOutput.LightningResistOverCap) + end) + -- fun part it("armoured max hits", function() build.configTab.input.enemyIsBoss = "None" @@ -1177,4 +1197,4 @@ describe("TestDefence", function() assert.are.equals(0, floor(poolsRemaining.Life)) assert.are.equals(0, floor(poolsRemaining.OverkillDamage)) end) -end) \ No newline at end of file +end) diff --git a/src/Modules/CalcDefence.lua b/src/Modules/CalcDefence.lua index 09ef83a274..e693f29ec0 100644 --- a/src/Modules/CalcDefence.lua +++ b/src/Modules/CalcDefence.lua @@ -513,50 +513,47 @@ function calcs.resistances(actor) output["PhysicalResist"] = 0 -- Process Resistance conversion mods - if not actor.resistConversionApplied then - actor.resistConversionApplied = true - for _, resFrom in ipairs(resistTypeList) do - local maxRes - for _, resTo in ipairs(resistTypeList) do - local conversionRate = modDB:Sum("BASE", nil, resFrom.."MaxResConvertTo"..resTo) / 100 - if conversionRate ~= 0 then - if not maxRes then - maxRes = 0 - for _, mod in ipairs(modDB:Tabulate("BASE", nil, resFrom.."ResistMax")) do - if mod.mod.source ~= "Base" then - maxRes = maxRes + mod.value - end + for _, resFrom in ipairs(resistTypeList) do + local maxRes + for _, resTo in ipairs(resistTypeList) do + local conversionRate = modDB:Sum("BASE", nil, resFrom.."MaxResConvertTo"..resTo) / 100 + if conversionRate ~= 0 then + if not maxRes then + maxRes = 0 + for _, mod in ipairs(modDB:Tabulate("BASE", nil, resFrom.."ResistMax")) do + if mod.mod.source ~= "Base" then + maxRes = maxRes + mod.value end end - if maxRes ~= 0 then - modDB:NewMod(resTo.."ResistMax", "BASE", maxRes * conversionRate, resFrom.." To "..resTo.." Max Resistance Conversion") - end + end + if maxRes ~= 0 then + modDB:NewMod(resTo.."ResistMax", "BASE", maxRes * conversionRate, resFrom.." To "..resTo.." Max Resistance Conversion") end end end + end - for _, resFrom in ipairs(resistTypeList) do - local res - for _, resTo in ipairs(resistTypeList) do - local conversionRate = modDB:Sum("BASE", nil, resFrom.."ResConvertTo"..resTo) / 100 - if conversionRate ~= 0 then - if not res then - res = 0 - for _, mod in ipairs(modDB:Tabulate("BASE", nil, resFrom.."Resist")) do - if mod.mod.source ~= "Base" then - res = res + mod.value - end + for _, resFrom in ipairs(resistTypeList) do + local res + for _, resTo in ipairs(resistTypeList) do + local conversionRate = modDB:Sum("BASE", nil, resFrom.."ResConvertTo"..resTo) / 100 + if conversionRate ~= 0 then + if not res then + res = 0 + for _, mod in ipairs(modDB:Tabulate("BASE", nil, resFrom.."Resist")) do + if mod.mod.source ~= "Base" then + res = res + mod.value end end - if res ~= 0 then - modDB:NewMod(resTo.."Resist", "BASE", res * conversionRate, resFrom.." To "..resTo.." Resistance Conversion") - end - for _, mod in ipairs(modDB:Tabulate("INC", nil, resFrom.."Resist")) do - modDB:NewMod(resTo.."Resist", "INC", mod.value * conversionRate, mod.mod.source) - end - for _, mod in ipairs(modDB:Tabulate("MORE", nil, resFrom.."Resist")) do - modDB:NewMod(resTo.."Resist", "MORE", mod.value * conversionRate, mod.mod.source) - end + end + if res ~= 0 then + modDB:NewMod(resTo.."Resist", "BASE", res * conversionRate, resFrom.." To "..resTo.." Resistance Conversion") + end + for _, mod in ipairs(modDB:Tabulate("INC", nil, resFrom.."Resist")) do + modDB:NewMod(resTo.."Resist", "INC", mod.value * conversionRate, mod.mod.source) + end + for _, mod in ipairs(modDB:Tabulate("MORE", nil, resFrom.."Resist")) do + modDB:NewMod(resTo.."Resist", "MORE", mod.value * conversionRate, mod.mod.source) end end end diff --git a/src/Modules/CalcPerform.lua b/src/Modules/CalcPerform.lua index f107ade614..2f1dd52808 100644 --- a/src/Modules/CalcPerform.lua +++ b/src/Modules/CalcPerform.lua @@ -3189,8 +3189,16 @@ function calcs.perform(env, skipEHP) -- Foulborn Choir of the Storm, needs to be after main auras (incase purity of lightning/elements auras) but before extra auras (Radiant Faith) if modDB:Flag(nil, "ManaIncreasedByOvercappedLightningRes") then - -- Calclate resistances for ManaIncreasedByOvercappedLightningRes - calcs.resistances(env.player) + -- Calculate resistances for ManaIncreasedByOvercappedLightningRes without mutating conversion mods on the player ModDB. + local tempResistActor = { + modDB = new("ModDB", modDB), + output = output, + activeSkillList = env.player.activeSkillList, + enemy = env.player.enemy, + } + tempResistActor.player = tempResistActor + tempResistActor.modDB.actor = tempResistActor + calcs.resistances(tempResistActor) -- Set the life/mana reservations again as we now have increased mana from overcapped lightning resistance doActorLifeMana(env.player) doActorLifeManaReservation(env.player, true)