diff --git a/spec/System/TestItemMods_spec.lua b/spec/System/TestItemMods_spec.lua index 6b0206e4d8..daa789c6b3 100644 --- a/spec/System/TestItemMods_spec.lua +++ b/spec/System/TestItemMods_spec.lua @@ -718,6 +718,26 @@ describe("TetsItemMods", function() assert.are.equals(10, build.calcsTab.calcsOutput.PhysicalEnergyShieldRecoup) end) + it("supports Baleful Dominion recoup modifiers", function() + build.configTab.input.customMods = [[ + 15% of Damage taken Recouped as Life + 10% of Physical Damage taken Recouped as Life + Life Recoup also recovers Mana + 50% less recovery from Recoup + ]] + build.configTab:BuildModList() + runCallback("OnFrame") + + assert.are.equals(7.5, build.calcsTab.calcsOutput.LifeRecoup) + assert.are.equals(7.5, build.calcsTab.calcsOutput.ManaRecoup) + assert.are.equals(5, build.calcsTab.calcsOutput.PhysicalLifeRecoup) + assert.are.equals(5, build.calcsTab.calcsOutput.PhysicalManaRecoup) + local lifeRecoupBreakdown = build.calcsTab.calcsEnv.player.breakdown.LifeRecoup + assert.are.equals("7.5% over 4 seconds", lifeRecoupBreakdown[#lifeRecoupBreakdown]) + local physicalLifeRecoupBreakdown = build.calcsTab.calcsEnv.player.breakdown.PhysicalLifeRecoup + assert.are.equals("= 5.0% over 4 seconds", physicalLifeRecoupBreakdown[#physicalLifeRecoupBreakdown]) + end) + it("crafts modifiers from supported bases on rare-like uniques", function() local item = new("Item"):Item([[ Item Class: Helmets diff --git a/src/Data/ModCache.lua b/src/Data/ModCache.lua index 28da845876..b55bfcab80 100755 --- a/src/Data/ModCache.lua +++ b/src/Data/ModCache.lua @@ -5946,7 +5946,7 @@ c["50% less Lightning Resistance"]={{[1]={flags=0,keywordFlags=0,name="Lightning c["50% less Minimum Lightning Damage"]={{[1]={flags=0,keywordFlags=0,name="MinLightningDamage",type="MORE",value=-50}},nil} c["50% less Poison Duration"]={{[1]={flags=0,keywordFlags=0,name="EnemyPoisonDuration",type="MORE",value=-50}},nil} c["50% less maximum Total Life Recovery per Second from Leech"]={{[1]={flags=0,keywordFlags=0,name="MaxLifeLeechRate",type="MORE",value=-50}},nil} -c["50% less recovery from Recoup"]={{[1]={flags=0,keywordFlags=0,name="FlaskLifeRecoveryLowLife",type="MORE",value=-50}}," from Recoup "} +c["50% less recovery from Recoup"]={{[1]={flags=0,keywordFlags=0,name="RecoupRecoveryAmount",type="MORE",value=-50}},nil} c["50% more Accuracy Rating against Marked Enemy"]={{[1]={[1]={actor="enemy",type="ActorCondition",var="Marked"},flags=0,keywordFlags=0,name="AccuracyVsEnemy",type="MORE",value=50}},nil} c["50% more Accuracy Rating at Close Range"]={{[1]={[1]={type="Condition",var="AtCloseRange"},flags=0,keywordFlags=0,name="AccuracyVsEnemy",type="MORE",value=50}},nil} c["50% more Critical Strike Chance while Insane"]={{[1]={[1]={type="Condition",var="Insane"},flags=0,keywordFlags=0,name="CritChance",type="MORE",value=50}},nil} @@ -9746,8 +9746,7 @@ c["Life Leech from Exerted Attacks is instant Non-Exerted Attacks deal no Damage c["Life Leech from Hits with this Weapon is instant"]={{[1]={[1]={type="Condition",var="{Hand}Attack"},flags=0,keywordFlags=0,name="InstantLifeLeech",type="BASE",value=100}},nil} c["Life Leech from Melee Damage is Instant"]={{[1]={flags=256,keywordFlags=0,name="InstantLifeLeech",type="BASE",value=100}},nil} c["Life Recoup Effects instead occur over 3 seconds"]={{[1]={flags=0,keywordFlags=0,name="3SecondLifeRecoup",type="FLAG",value=true}},nil} -c["Life Recoup also recovers Mana"]={nil,"Life Recoup also recovers Mana "} -c["Life Recoup also recovers Mana 50% less recovery from Recoup"]={nil,"Life Recoup also recovers Mana 50% less recovery from Recoup "} +c["Life Recoup also recovers Mana"]={{[1]={flags=0,keywordFlags=0,name="AddLifeRecoupToManaRecoup",type="FLAG",value=true}},nil} c["Life Recovery from Flasks also applies to Energy Shield"]={{[1]={flags=0,keywordFlags=0,name="LifeFlaskAppliesToEnergyShield",type="FLAG",value=true}},nil} c["Life Recovery from Flasks also applies to Energy Shield during Effect"]={{[1]={[1]={type="Condition",var="UsingFlask"},flags=0,keywordFlags=0,name="LifeFlaskAppliesToEnergyShield",type="FLAG",value=true}},nil} c["Life Recovery from Non-Instant Leech is not applied"]={{[1]={flags=0,keywordFlags=0,name="UnaffectedByNonInstantLifeLeech",type="FLAG",value=true}},nil} diff --git a/src/Modules/CalcDefence.lua b/src/Modules/CalcDefence.lua index 036d8e5c05..77a70614a3 100644 --- a/src/Modules/CalcDefence.lua +++ b/src/Modules/CalcDefence.lua @@ -635,6 +635,7 @@ end -- Performs all ingame and related defensive calculations function calcs.defence(env, actor) + ---@type ModDB local modDB = actor.modDB local enemyDB = actor.enemy.modDB local output = actor.output @@ -1387,32 +1388,46 @@ function calcs.defence(env, actor) do output["anyRecoup"] = 0 local recoupTypeList = {"Life", "Mana", "EnergyShield"} + local recoupInc = 1 + modDB:Sum("INC", nil, "RecoupRecoveryAmount") / 100 + local recoupMore = modDB:More(nil, "RecoupRecoveryAmount") + for _, recoupType in ipairs(recoupTypeList) do + for _, recoupType2 in ipairs(recoupTypeList) do + if recoupType ~= recoupType2 then + local addFlag = s_format("Add%sRecoupTo%sRecoup", recoupType, recoupType2) + if modDB:Flag(nil, addFlag) then + local baseRecoup = modDB:Sum("BASE", nil, recoupType .. "Recoup") + -- inherit source from original flag + local flagMod = modDB:Tabulate("FLAG", nil, addFlag)[1].mod + modDB:NewMod(recoupType2 .. "Recoup", "BASE", baseRecoup, flagMod.source) + end + end + end + end for _, recoupType in ipairs(recoupTypeList) do - local baseRecoup = modDB:Sum("BASE", nil, recoupType.."Recoup") if recoupType == "Life" and modDB:Flag(nil, "EnergyShieldRecoupInsteadOfLife") then output.LifeRecoup = 0 local lifeRecoup = modDB:Sum("BASE", nil, "LifeRecoup") modDB:NewMod("EnergyShieldRecoup", "BASE", lifeRecoup, "Life Recoup Conversion") else - output[recoupType.."Recoup"] = baseRecoup * output[recoupType.."RecoveryRateMod"] + local baseRecoup = modDB:Sum("BASE", nil, recoupType .. "Recoup") + output[recoupType .. "Recoup"] = baseRecoup * output[recoupType .. "RecoveryRateMod"] * recoupInc * recoupMore output["anyRecoup"] = output["anyRecoup"] + output[recoupType.."Recoup"] if breakdown then - if output[recoupType.."RecoveryRateMod"] ~= 1 then - breakdown[recoupType.."Recoup"] = { - s_format("%d%% ^8(base)", baseRecoup), - s_format("* %.2f ^8(recovery rate modifier)", output[recoupType.."RecoveryRateMod"]), - s_format("= %.1f%% over %d seconds", output[recoupType.."Recoup"], (modDB:Flag(nil, "3Second"..recoupType.."Recoup") or modDB:Flag(nil, "3SecondRecoup")) and 3 or 4) - } + breakdown[recoupType .. "Recoup"] = { s_format("%d%% ^8(base)", baseRecoup) } + if recoupInc ~= 1 then + t_insert(breakdown[recoupType .. "Recoup"], s_format("x %.2f (increased/reduced)", recoupInc)) + end + if recoupMore ~= 1 then + t_insert(breakdown[recoupType .. "Recoup"], s_format("x %.2f (more/less)", recoupMore)) + end + if output[recoupType .. "RecoveryRateMod"] ~= 1 then + t_insert(breakdown[recoupType .. "Recoup"], s_format("* %.2f ^8(recovery rate modifier)", output[recoupType .. "RecoveryRateMod"])) + t_insert(breakdown[recoupType .. "Recoup"], s_format("= %.1f%% over %d seconds", output[recoupType .. "Recoup"], (modDB:Flag(nil, "3Second" .. recoupType .. "Recoup") or modDB:Flag(nil, "3SecondRecoup")) and 3 or 4)) else - breakdown[recoupType.."Recoup"] = { s_format("%d%% over %d seconds", output[recoupType.."Recoup"], (modDB:Flag(nil, "3Second"..recoupType.."Recoup") or modDB:Flag(nil, "3SecondRecoup")) and 3 or 4) } + table.insert(breakdown[recoupType .. "Recoup"], s_format("%.1f%% over %d seconds", output[recoupType .. "Recoup"], (modDB:Flag(nil, "3Second" .. recoupType .. "Recoup") or modDB:Flag(nil, "3SecondRecoup")) and 3 or 4)) end end end - local addToEnergyShieldFlag = "Add"..recoupType.."RecoupToEnergyShieldRecoup" - if modDB:Flag(nil, addToEnergyShieldFlag) then - local flagMod = modDB:Tabulate("FLAG", nil, addToEnergyShieldFlag)[1].mod - modDB:ReplaceMod("EnergyShieldRecoup", "BASE", baseRecoup, flagMod.source) - end end if modDB:Flag(nil, "UsePowerCharges") and modDB:Flag(nil, "PowerChargesConvertToAbsorptionCharges") then @@ -1425,28 +1440,35 @@ function calcs.defence(env, actor) for _, recoupType in ipairs(recoupTypeList) do for _, damageType in ipairs(dmgTypeList) do local recoup = modDB:Sum("BASE", nil, damageType..recoupType.."Recoup") + local recoupName = damageType..recoupType.."Recoup" if recoupType == "Life" and modDB:Flag(nil, "EnergyShieldRecoupInsteadOfLife") then - output[damageType.."LifeRecoup"] = 0 + output[recoupName] = 0 modDB:NewMod(damageType.."EnergyShieldRecoup", "BASE", recoup, "Life Recoup Conversion") else - output[damageType..recoupType.."Recoup"] = recoup * output[recoupType.."RecoveryRateMod"] - output["anyRecoup"] = output["anyRecoup"] + output[damageType..recoupType.."Recoup"] + output[recoupName] = recoup * output[recoupType.."RecoveryRateMod"] * recoupInc * recoupMore + output["anyRecoup"] = output["anyRecoup"] + output[recoupName] if breakdown then + breakdown[recoupName] = { s_format("%d%% ^8(base)", recoup) } + if recoupInc ~= 1 then + t_insert(breakdown[recoupName], s_format("x %.2f (increased/reduced)", recoupInc)) + end + if recoupMore ~= 1 then + t_insert(breakdown[recoupName], s_format("x %.2f (more/less)", recoupMore)) + end if output[recoupType.."RecoveryRateMod"] ~= 1 then - breakdown[damageType..recoupType.."Recoup"] = { - s_format("%d%% ^8(base)", recoup), - s_format("* %.2f ^8(recovery rate modifier)", output[recoupType.."RecoveryRateMod"]), - s_format("= %.1f%% over %d seconds", output[damageType..recoupType.."Recoup"], (modDB:Flag(nil, "3Second"..recoupType.."Recoup") or modDB:Flag(nil, "3SecondRecoup")) and 3 or 4) - } - else - breakdown[damageType..recoupType.."Recoup"] = { s_format("%d%% over %d seconds", output[damageType..recoupType.."Recoup"], (modDB:Flag(nil, "3Second"..recoupType.."Recoup") or modDB:Flag(nil, "3SecondRecoup")) and 3 or 4) } + t_insert(breakdown[recoupName], s_format("* %.2f ^8(recovery rate modifier)", output[recoupType.."RecoveryRateMod"])) end + t_insert(breakdown[recoupName], s_format("= %.1f%% over %d seconds", output[recoupName], (modDB:Flag(nil, "3Second"..recoupType.."Recoup") or modDB:Flag(nil, "3SecondRecoup")) and 3 or 4)) end end - local addToEnergyShieldFlag = "Add"..recoupType.."RecoupToEnergyShieldRecoup" - if modDB:Flag(nil, addToEnergyShieldFlag) then - local flagMod = modDB:Tabulate("FLAG", nil, addToEnergyShieldFlag)[1].mod - modDB:ReplaceMod(damageType.."EnergyShieldRecoup", "BASE", recoup, flagMod.source) + for _, recoupType2 in ipairs(recoupTypeList) do + if recoupType ~= recoupType2 then + local addFlag = s_format("Add%sRecoupTo%sRecoup", recoupType, recoupType2) + if modDB:Flag(nil, addFlag) then + local flagMod = modDB:Tabulate("FLAG", nil, addFlag)[1].mod + modDB:ReplaceMod(damageType..recoupType2.."Recoup", "BASE", recoup, flagMod.source) + end + end end end end diff --git a/src/Modules/CalcSections.lua b/src/Modules/CalcSections.lua index 599b5974c7..e279c47c3e 100644 --- a/src/Modules/CalcSections.lua +++ b/src/Modules/CalcSections.lua @@ -1521,8 +1521,8 @@ return { { label = "Recovery modifiers", modName = "ManaRecoveryRate" }, }, }, { label = "Recoup", haveOutput = "ManaRecoup", { format = "{1:output:ManaRecoup}%", { breakdown = "ManaRecoup" }, - { label = "Sources", modName = "ManaRecoup" }, - { label = "Recovery modifiers", modName = "ManaRecoveryRate" }, + { label = "Sources", modName = { "ManaRecoup", "AddLifeRecoupToManaRecoup" } }, + { label = "Recovery modifiers", modName = { "ManaRecoveryRate", "RecoupRecoveryAmount" } }, { label = "FasterRecoup", modName = "3SecondRecoup" }, }, }, } } diff --git a/src/Modules/ModParser.lua b/src/Modules/ModParser.lua index 0b582014d4..9c86ae3543 100644 --- a/src/Modules/ModParser.lua +++ b/src/Modules/ModParser.lua @@ -436,6 +436,7 @@ local modNameList = { ["damage taken recouped as energy shield"] = "EnergyShieldRecoup", ["damage taken recouped as mana"] = "ManaRecoup", ["damage taken recouped as life, mana and energy shield"] = { "LifeRecoup", "EnergyShieldRecoup", "ManaRecoup" }, + ["recovery from recoup"] = "RecoupRecoveryAmount", ["missing unreserved life before being hit by an enemy"] = "MissingLifeBeforeEnemyHit", ["missing unreserved mana before being hit by an enemy"] = "MissingManaBeforeEnemyHit", -- Stun/knockback modifiers @@ -4722,6 +4723,7 @@ local specialModList = { ["recoup effects instead occur over 3 seconds"] = { flag("3SecondRecoup") }, ["life recoup effects instead occur over 3 seconds"] = { flag("3SecondLifeRecoup") }, ["recoup energy shield instead of life"] = { flag("EnergyShieldRecoupInsteadOfLife") }, + ["life recoup also recovers mana"] = { flag("AddLifeRecoupToManaRecoup") }, ["damage taken recouped as (%a+) is also recouped as energy shield"] = function(_, type) return { flag("Add"..firstToUpper(type).."RecoupToEnergyShieldRecoup"), } end,