Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix ES bypass regression #8459

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
190 changes: 165 additions & 25 deletions spec/System/TestDefence_spec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,16 @@ describe("TestDefence", function()
-- newBuild() takes care of resetting everything in setup()
end)

-- a small helper function to calculate damage taken from limited test parameters
local function takenHitFromTypeMaxHit(type, enemyDamageMulti)
return build.calcsTab.calcs.takenHitFromDamage(build.calcsTab.calcsOutput[type.."MaximumHitTaken"] * (enemyDamageMulti or 1), type, build.calcsTab.calcsEnv.player)
end

local function poolsRemainingAfterTypeMaxHit(type, enemyDamageMulti)
local _, takenDamages = takenHitFromTypeMaxHit(type, enemyDamageMulti)
return build.calcsTab.calcs.reducePoolsByDamage(nil, takenDamages, build.calcsTab.calcsEnv.player)
end

-- boring part
it("no armour max hits", function()
build.configTab.input.enemyIsBoss = "None"
Expand Down Expand Up @@ -87,6 +97,30 @@ describe("TestDefence", function()
assert.are.equals(3000, build.calcsTab.calcsOutput.ColdMaximumHitTaken)
assert.are.equals(3000, build.calcsTab.calcsOutput.LightningMaximumHitTaken)
assert.are.equals(3000, build.calcsTab.calcsOutput.ChaosMaximumHitTaken)
local poolsRemaining = poolsRemainingAfterTypeMaxHit("Lightning", 0.8)
assert.are.equals(0, floor(poolsRemaining.Life))
assert.are.equals(0, floor(poolsRemaining.OverkillDamage))

build.configTab.input.customMods = "\z
+200 to all resistances\n\z
+200 to all maximum resistances\n\z
50% reduced damage taken\n\z
50% less damage taken\n\z
Nearby enemies deal 20% less damage\n\z
Gain 100% of life as extra maximum energy shield\n\z
intelligence provides no bonus to energy shield\n\z
"
build.configTab:BuildModList()
runCallback("OnFrame")
assert.are.equals(600, build.calcsTab.calcsOutput.PhysicalMaximumHitTaken)
assert.are.equals(6000, build.calcsTab.calcsOutput.FireMaximumHitTaken)
assert.are.equals(6000, build.calcsTab.calcsOutput.ColdMaximumHitTaken)
assert.are.equals(6000, build.calcsTab.calcsOutput.LightningMaximumHitTaken)
assert.are.equals(3000, build.calcsTab.calcsOutput.ChaosMaximumHitTaken)
poolsRemaining = poolsRemainingAfterTypeMaxHit("Lightning", 0.8)
assert.are.equals(0, floor(poolsRemaining.EnergyShield))
assert.are.equals(0, floor(poolsRemaining.Life))
assert.are.equals(0, floor(poolsRemaining.OverkillDamage))
end)

-- a small helper function to calculate damage taken from limited test parameters
Expand Down Expand Up @@ -507,9 +541,9 @@ describe("TestDefence", function()
"
build.configTab:BuildModList()
runCallback("OnFrame")
local _, takenDamages = takenHitFromTypeMaxHit("Cold")
local poolsRemaining = build.calcsTab.calcs.reducePoolsByDamage(nil, takenDamages, build.calcsTab.calcsEnv.player)
assert.are.equals(0, round(poolsRemaining.Life))
local poolsRemaining = poolsRemainingAfterTypeMaxHit("Cold")
assert.are.equals(0, floor(poolsRemaining.Life))
assert.are.equals(0, floor(poolsRemaining.OverkillDamage))
end)

it("damage conversion to different size pools", function()
Expand All @@ -525,10 +559,10 @@ describe("TestDefence", function()
" -- Small amount of conversion into a smaller pool leads to the higher pool damage type (lightning) draining it's own excess pool (mana), and then joining back on the shared pools (life)
build.configTab:BuildModList()
runCallback("OnFrame")
local _, takenDamages = takenHitFromTypeMaxHit("Lightning")
local poolsRemaining = build.calcsTab.calcs.reducePoolsByDamage(nil, takenDamages, build.calcsTab.calcsEnv.player)
assert.are.equals(0, round(poolsRemaining.Mana))
assert.are.not_false(poolsRemaining.Life / 100 < 0.1)
local poolsRemaining = poolsRemainingAfterTypeMaxHit("Lightning")
assert.are.equals(0, floor(poolsRemaining.Mana))
assert.are.equals(0, floor(poolsRemaining.Life))
assert.are.equals(0, floor(poolsRemaining.OverkillDamage))

build.configTab.input.customMods = "\z
+40 to maximum life\n\z
Expand All @@ -541,10 +575,10 @@ describe("TestDefence", function()
" -- This is a case where cold damage drains the whole life pool and lightning damage drains the entire mana pool, leaving nothing
build.configTab:BuildModList()
runCallback("OnFrame")
_, takenDamages = takenHitFromTypeMaxHit("Lightning")
poolsRemaining = build.calcsTab.calcs.reducePoolsByDamage(nil, takenDamages, build.calcsTab.calcsEnv.player)
assert.are.equals(0, round(poolsRemaining.Life))
assert.are.equals(0, round(poolsRemaining.Mana))
poolsRemaining = poolsRemainingAfterTypeMaxHit("Lightning")
assert.are.equals(0, floor(poolsRemaining.Mana))
assert.are.equals(0, floor(poolsRemaining.Life))
assert.are.equals(0, floor(poolsRemaining.OverkillDamage))

build.configTab.input.customMods = "\z
+40 to maximum life\n\z
Expand All @@ -557,10 +591,10 @@ describe("TestDefence", function()
" -- Any extra mana in this case will not help and be left over after death, since life hits 0 from the cold damage alone
build.configTab:BuildModList()
runCallback("OnFrame")
_, takenDamages = takenHitFromTypeMaxHit("Lightning")
poolsRemaining = build.calcsTab.calcs.reducePoolsByDamage(nil, takenDamages, build.calcsTab.calcsEnv.player)
assert.are.equals(0, round(poolsRemaining.Life))
poolsRemaining = poolsRemainingAfterTypeMaxHit("Lightning")
assert.are.equals(1000, round(poolsRemaining.Mana))
assert.are.equals(0, floor(poolsRemaining.Life))
assert.are.equals(0, floor(poolsRemaining.OverkillDamage))

-- conversion into a bigger pool
build.configTab.input.customMods = "\z
Expand All @@ -574,10 +608,10 @@ describe("TestDefence", function()
" -- With inverted conversion amounts the behaviour of converting into a bigger pool should be exactly the same as converting into a lower one.
build.configTab:BuildModList()
runCallback("OnFrame")
_, takenDamages = takenHitFromTypeMaxHit("Cold")
poolsRemaining = build.calcsTab.calcs.reducePoolsByDamage(nil, takenDamages, build.calcsTab.calcsEnv.player)
assert.are.equals(0, round(poolsRemaining.Mana))
assert.are.not_false(poolsRemaining.Life / 100 < 0.1)
poolsRemaining = poolsRemainingAfterTypeMaxHit("Cold")
assert.are.equals(0, floor(poolsRemaining.Mana))
assert.are.equals(0, floor(poolsRemaining.Life))
assert.are.equals(0, floor(poolsRemaining.OverkillDamage))

build.configTab.input.customMods = "\z
+40 to maximum life\n\z
Expand All @@ -590,10 +624,10 @@ describe("TestDefence", function()
"
build.configTab:BuildModList()
runCallback("OnFrame")
_, takenDamages = takenHitFromTypeMaxHit("Cold")
poolsRemaining = build.calcsTab.calcs.reducePoolsByDamage(nil, takenDamages, build.calcsTab.calcsEnv.player)
assert.are.equals(0, round(poolsRemaining.Life))
assert.are.equals(0, round(poolsRemaining.Mana))
poolsRemaining = poolsRemainingAfterTypeMaxHit("Cold")
assert.are.equals(0, floor(poolsRemaining.Mana))
assert.are.equals(0, floor(poolsRemaining.Life))
assert.are.equals(0, floor(poolsRemaining.OverkillDamage))

build.configTab.input.customMods = "\z
+40 to maximum life\n\z
Expand All @@ -606,10 +640,34 @@ describe("TestDefence", function()
"
build.configTab:BuildModList()
runCallback("OnFrame")
_, takenDamages = takenHitFromTypeMaxHit("Cold")
poolsRemaining = build.calcsTab.calcs.reducePoolsByDamage(nil, takenDamages, build.calcsTab.calcsEnv.player)
assert.are.equals(0, round(poolsRemaining.Life))
poolsRemaining = poolsRemainingAfterTypeMaxHit("Cold")
assert.are.equals(1000, round(poolsRemaining.Mana))
assert.are.equals(0, floor(poolsRemaining.Life))
assert.are.equals(0, floor(poolsRemaining.OverkillDamage))

build.configTab.input.customMods = "\z
+940 to maximum life\n\z
+950 to mana\n\z
+1000 to energy shield\n\z
+10000 to armour\n\z
+110% to all elemental resistances\n\z
Armour applies to Fire, Cold and Lightning Damage taken from Hits instead of Physical Damage\n\z
100% of Lightning Damage is taken from Mana before Life\n\z
80% of cold damage taken as lightning damage\n\z
50% of fire damage taken as chaos damage\n\z
"
build.configTab:BuildModList()
runCallback("OnFrame")
poolsRemaining = poolsRemainingAfterTypeMaxHit("Cold")
assert.are.equals(0, floor(poolsRemaining.EnergyShield))
assert.are.equals(0, floor(poolsRemaining.Mana))
assert.are.equals(0, floor(poolsRemaining.Life))
assert.are.equals(0, floor(poolsRemaining.OverkillDamage))
poolsRemaining = poolsRemainingAfterTypeMaxHit("Fire")
assert.are.not_false(0 < floor(poolsRemaining.EnergyShield))
assert.are.equals(1000, floor(poolsRemaining.Mana))
assert.are.not_false(1 >= floor(poolsRemaining.Life))
assert.are.equals(0, floor(poolsRemaining.OverkillDamage))
end)

it("energy shield bypass tests #pet", function()
Expand All @@ -625,25 +683,40 @@ describe("TestDefence", function()

build.configTab:BuildModList()
runCallback("OnFrame")
local poolsRemaining = poolsRemainingAfterTypeMaxHit("Chaos")
assert.are.equals(100, round(poolsRemaining.EnergyShield))
assert.are.equals(0, floor(poolsRemaining.Life))
assert.are.equals(0, floor(poolsRemaining.OverkillDamage))
assert.are.equals(300, build.calcsTab.calcsOutput.FireMaximumHitTaken)
assert.are.equals(200, build.calcsTab.calcsOutput.ChaosMaximumHitTaken)

-- Negative overrides positive
build.configTab.input.customMods = [[
+40 to maximum life
+100 to energy shield
physical damage taken bypasses energy shield
Chaos damage does not bypass energy shield
You have no intelligence
+60% to all resistances
]]
build.configTab:BuildModList()
runCallback("OnFrame")
assert.are.equals(100, build.calcsTab.calcsOutput.PhysicalMaximumHitTaken)
assert.are.equals(200, build.calcsTab.calcsOutput.FireMaximumHitTaken)
assert.are.equals(200, build.calcsTab.calcsOutput.ChaosMaximumHitTaken)
poolsRemaining = poolsRemainingAfterTypeMaxHit("Physical")
assert.are.equals(100, floor(poolsRemaining.EnergyShield))
assert.are.equals(0, floor(poolsRemaining.Life))
assert.are.equals(0, floor(poolsRemaining.OverkillDamage))
poolsRemaining = poolsRemainingAfterTypeMaxHit("Chaos")
assert.are.equals(0, floor(poolsRemaining.EnergyShield))
assert.are.equals(0, floor(poolsRemaining.Life))
assert.are.equals(0, floor(poolsRemaining.OverkillDamage))
-- Chaos damage should still bypass
build.configTab.input.customMods = build.configTab.input.customMods .. "\nAll damage taken bypasses energy shield"
build.configTab:BuildModList()
runCallback("OnFrame")
assert.are.equals(100, build.calcsTab.calcsOutput.PhysicalMaximumHitTaken)
assert.are.equals(100, build.calcsTab.calcsOutput.FireMaximumHitTaken)
assert.are.equals(100, build.calcsTab.calcsOutput.ChaosMaximumHitTaken)

Expand All @@ -666,5 +739,72 @@ describe("TestDefence", function()
runCallback("OnFrame")
assert.are.equals(100, build.calcsTab.calcsOutput.FireMaximumHitTaken)
assert.are.equals(100, build.calcsTab.calcsOutput.ChaosMaximumHitTaken)
poolsRemaining = poolsRemainingAfterTypeMaxHit("Physical")
assert.are.equals(100, floor(poolsRemaining.EnergyShield))
assert.are.equals(0, floor(poolsRemaining.Life))
assert.are.equals(0, floor(poolsRemaining.OverkillDamage))
poolsRemaining = poolsRemainingAfterTypeMaxHit("Chaos")
assert.are.equals(100, round(poolsRemaining.EnergyShield))
assert.are.equals(0, floor(poolsRemaining.Life))
assert.are.equals(0, floor(poolsRemaining.OverkillDamage))

-- Bypass + MoM
build.configTab.input.customMods = [[
+40 to maximum life
+50 to mana
+200 to energy shield
50% of non-chaos damage taken bypasses energy shield
50% of chaos damage taken does not bypass energy shield
50% of Lightning Damage is taken from Mana before Life
intelligence provides no bonus to energy shield
+60% to all resistances
]]
build.configTab:BuildModList()
runCallback("OnFrame")
assert.are.equals(400, build.calcsTab.calcsOutput.LightningMaximumHitTaken)
assert.are.equals(200, build.calcsTab.calcsOutput.FireMaximumHitTaken)
assert.are.equals(200, build.calcsTab.calcsOutput.ChaosMaximumHitTaken)
poolsRemaining = poolsRemainingAfterTypeMaxHit("Chaos")
assert.are.equals(100, round(poolsRemaining.EnergyShield))
assert.are.equals(0, floor(poolsRemaining.Life))
assert.are.equals(0, floor(poolsRemaining.OverkillDamage))
poolsRemaining = poolsRemainingAfterTypeMaxHit("Fire")
assert.are.equals(100, round(poolsRemaining.EnergyShield))
assert.are.equals(0, floor(poolsRemaining.Life))
assert.are.equals(0, floor(poolsRemaining.OverkillDamage))
poolsRemaining = poolsRemainingAfterTypeMaxHit("Lightning")
assert.are.equals(0, round(poolsRemaining.EnergyShield))
assert.are.equals(0, floor(poolsRemaining.Mana))
assert.are.equals(0, floor(poolsRemaining.Life))
assert.are.equals(0, floor(poolsRemaining.OverkillDamage))

build.configTab.input.customMods = [[
+40 to maximum life
+150 to mana
+300 to energy shield
50% of non-chaos damage taken bypasses energy shield
50% of chaos damage taken does not bypass energy shield
50% of Lightning Damage is taken from Mana before Life
intelligence provides no bonus to energy shield
+60% to all resistances
]]
build.configTab:BuildModList()
runCallback("OnFrame")
assert.are.equals(400, build.calcsTab.calcsOutput.LightningMaximumHitTaken)
assert.are.equals(200, build.calcsTab.calcsOutput.FireMaximumHitTaken)
assert.are.equals(200, build.calcsTab.calcsOutput.ChaosMaximumHitTaken)
poolsRemaining = poolsRemainingAfterTypeMaxHit("Chaos")
assert.are.equals(200, round(poolsRemaining.EnergyShield))
assert.are.equals(0, floor(poolsRemaining.Life))
assert.are.equals(0, floor(poolsRemaining.OverkillDamage))
poolsRemaining = poolsRemainingAfterTypeMaxHit("Fire")
assert.are.equals(200, round(poolsRemaining.EnergyShield))
assert.are.equals(0, floor(poolsRemaining.Life))
assert.are.equals(0, floor(poolsRemaining.OverkillDamage))
poolsRemaining = poolsRemainingAfterTypeMaxHit("Lightning")
assert.are.equals(100, round(poolsRemaining.EnergyShield))
assert.are.equals(100, floor(poolsRemaining.Mana))
assert.are.equals(0, floor(poolsRemaining.Life))
assert.are.equals(0, floor(poolsRemaining.OverkillDamage))
end)
end)
27 changes: 13 additions & 14 deletions src/Modules/CalcDefence.lua
Original file line number Diff line number Diff line change
Expand Up @@ -258,33 +258,32 @@ function calcs.reducePoolsByDamage(poolTable, damageTable, actor)
end
local esBypass = output[damageType.."EnergyShieldBypass"] / 100 or 0
local lifeHitPool = calcLifeHitPoolWithLossPrevention(life, output.Life, output.preventedLifeLoss, lifeLossBelowHalfPrevented)
if energyShield > 0 and (not modDB:Flag(nil, "EnergyShieldProtectsMana")) and (esBypass) < 1 then
local esPool = esBypass > 0 and m_min(lifeHitPool / esBypass - lifeHitPool, energyShield) or energyShield
local MoMEffect = m_min(output.sharedMindOverMatter + output[damageType.."MindOverMatter"], 100) / 100
local MoMPool = MoMEffect < 1 and m_min(lifeHitPool / (1 - MoMEffect) - lifeHitPool, mana) or mana
local lifePlusMoMHitPool = lifeHitPool + MoMPool
if energyShield > 0 and not modDB:Flag(nil, "EnergyShieldProtectsMana") and esBypass < 1 then
local esPool = esBypass > 0 and m_min(lifePlusMoMHitPool / esBypass - lifePlusMoMHitPool, energyShield) or energyShield
local tempDamage = m_min(damageRemainder * (1 - esBypass), esPool)
esPoolRemaining = m_min(esPoolRemaining, esPool - tempDamage)
energyShield = energyShield - tempDamage
damageRemainder = damageRemainder - tempDamage
elseif esBypass == 1 then
esPoolRemaining = 0
end
if (output.sharedMindOverMatter + output[damageType.."MindOverMatter"]) > 0 then
local MoMEffect = m_min(output.sharedMindOverMatter + output[damageType.."MindOverMatter"], 100) / 100
local MoMPool = MoMEffect < 1 and m_min(lifeHitPool / (1 - MoMEffect) - lifeHitPool, mana) or mana
if MoMEffect > 0 and mana > 0 then
local MoMDamage = damageRemainder * MoMEffect
if modDB:Flag(nil, "EnergyShieldProtectsMana") and energyShield > 0 and esBypass < 1 then
local MoMEBPool = esBypass > 0 and m_min(MoMPool / esBypass - MoMPool, energyShield) or energyShield
local tempDamage = m_min(MoMDamage * (1 - esBypass), MoMEBPool)
esPoolRemaining = m_min(esPoolRemaining, MoMEBPool - tempDamage)
energyShield = energyShield - tempDamage
MoMDamage = MoMDamage - tempDamage
local tempDamage2 = m_min(MoMDamage, MoMPool)
MoMPoolRemaining = m_min(MoMPoolRemaining, MoMPool - tempDamage2)
mana = mana - tempDamage2
damageRemainder = damageRemainder - tempDamage - tempDamage2
elseif mana > 0 then
local tempDamage = m_min(MoMDamage, MoMPool)
MoMPoolRemaining = m_min(MoMPoolRemaining, MoMPool - tempDamage)
mana = mana - tempDamage
damageRemainder = damageRemainder - tempDamage
MoMDamage = MoMDamage - tempDamage
end
local tempDamage = m_min(MoMDamage, MoMPool)
MoMPoolRemaining = m_min(MoMPoolRemaining, MoMPool - tempDamage)
mana = mana - tempDamage
damageRemainder = damageRemainder - tempDamage
else
MoMPoolRemaining = 0
end
Expand Down