Skip to content

Commit d469ca7

Browse files
Fix ES bypass regression from #552 (#694)
1 parent 73b3a1a commit d469ca7

File tree

2 files changed

+170
-55
lines changed

2 files changed

+170
-55
lines changed

spec/System/TestDefence_spec.lua

Lines changed: 158 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,16 @@ describe("TestDefence", function()
1919
runCallback("OnFrame")
2020
end
2121

22+
-- a small helper function to calculate damage taken from limited test parameters
23+
local function takenHitFromTypeMaxHit(type, enemyDamageMulti)
24+
return build.calcsTab.calcs.takenHitFromDamage(build.calcsTab.calcsOutput[type.."MaximumHitTaken"] * (enemyDamageMulti or 1), type, build.calcsTab.calcsEnv.player)
25+
end
26+
27+
local function poolsRemainingAfterTypeMaxHit(type, enemyDamageMulti)
28+
local _, takenDamages = takenHitFromTypeMaxHit(type, enemyDamageMulti)
29+
return build.calcsTab.calcs.reducePoolsByDamage(nil, takenDamages, build.calcsTab.calcsEnv.player)
30+
end
31+
2232
it("no armour max hits", function()
2333
build.configTab.input.enemyIsBoss = "None"
2434
build.configTab.input.customMods = ""
@@ -92,12 +102,29 @@ describe("TestDefence", function()
92102
assert.are.equals(3000, build.calcsTab.calcsOutput.ColdMaximumHitTaken)
93103
assert.are.equals(3000, build.calcsTab.calcsOutput.LightningMaximumHitTaken)
94104
assert.are.equals(3000, build.calcsTab.calcsOutput.ChaosMaximumHitTaken)
105+
local poolsRemaining = poolsRemainingAfterTypeMaxHit("Lightning")
106+
assert.are.equals(0, floor(poolsRemaining.Life))
107+
assert.are.equals(0, floor(poolsRemaining.OverkillDamage))
108+
109+
build.configTab.input.customMods = "\z
110+
+200 to all resistances\n\z
111+
+200 to all maximum resistances\n\z
112+
50% reduced damage taken\n\z
113+
50% less damage taken\n\z
114+
Nearby enemies deal 20% less damage\n\z
115+
Gain 100% of life as extra maximum energy shield\n\z
116+
intelligence provides no bonus to energy shield\n\z
117+
"
118+
pob1and2Compat()
119+
assert.are.equals(600, build.calcsTab.calcsOutput.PhysicalMaximumHitTaken)
120+
assert.are.equals(6000, build.calcsTab.calcsOutput.FireMaximumHitTaken)
121+
assert.are.equals(6000, build.calcsTab.calcsOutput.ColdMaximumHitTaken)
122+
assert.are.equals(6000, build.calcsTab.calcsOutput.LightningMaximumHitTaken)
123+
assert.are.equals(4500, build.calcsTab.calcsOutput.ChaosMaximumHitTaken)
124+
assert.are.equals(0, floor(poolsRemaining.EnergyShield))
125+
assert.are.equals(0, floor(poolsRemaining.Life))
126+
assert.are.equals(0, floor(poolsRemaining.OverkillDamage))
95127
end)
96-
97-
-- a small helper function to calculate damage taken from limited test parameters
98-
local function takenHitFromTypeMaxHit(type, enemyDamageMulti)
99-
return build.calcsTab.calcs.takenHitFromDamage(build.calcsTab.calcsOutput[type.."MaximumHitTaken"] * (enemyDamageMulti or 1), type, build.calcsTab.calcsEnv.player)
100-
end
101128

102129
it("armoured max hits", function()
103130
build.configTab.input.enemyIsBoss = "None"
@@ -269,9 +296,9 @@ describe("TestDefence", function()
269296
Chaos Inoculation\n\z
270297
"
271298
pob1and2Compat()
272-
local _, takenDamages = takenHitFromTypeMaxHit("Cold")
273-
local poolsRemaining = build.calcsTab.calcs.reducePoolsByDamage(nil, takenDamages, build.calcsTab.calcsEnv.player)
274-
assert.are.equals(0, round(poolsRemaining.Life))
299+
local poolsRemaining = poolsRemainingAfterTypeMaxHit("Cold")
300+
assert.are.equals(0, floor(poolsRemaining.Life))
301+
assert.are.equals(0, floor(poolsRemaining.OverkillDamage))
275302
end)
276303

277304
it("damage conversion to different size pools", function()
@@ -286,10 +313,10 @@ describe("TestDefence", function()
286313
10% of lightning damage taken as cold damage\n\z
287314
" -- 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)
288315
pob1and2Compat()
289-
local _, takenDamages = takenHitFromTypeMaxHit("Lightning")
290-
local poolsRemaining = build.calcsTab.calcs.reducePoolsByDamage(nil, takenDamages, build.calcsTab.calcsEnv.player)
291-
assert.are.equals(0, round(poolsRemaining.Mana))
292-
assert.are.not_false(poolsRemaining.Life / 100 < 0.1)
316+
local poolsRemaining = poolsRemainingAfterTypeMaxHit("Lightning")
317+
assert.are.equals(0, floor(poolsRemaining.Mana))
318+
assert.are.equals(0, floor(poolsRemaining.Life))
319+
assert.are.equals(0, floor(poolsRemaining.OverkillDamage))
293320

294321
build.configTab.input.customMods = "\z
295322
+140 to maximum life\n\z
@@ -301,10 +328,10 @@ describe("TestDefence", function()
301328
20% of lightning damage taken as cold damage\n\z
302329
" -- This is a case where cold damage drains the whole life pool and lightning damage drains the entire mana pool, leaving nothing
303330
pob1and2Compat()
304-
_, takenDamages = takenHitFromTypeMaxHit("Lightning")
305-
poolsRemaining = build.calcsTab.calcs.reducePoolsByDamage(nil, takenDamages, build.calcsTab.calcsEnv.player)
306-
assert.are.equals(0, round(poolsRemaining.Life))
307-
assert.are.equals(0, round(poolsRemaining.Mana))
331+
poolsRemaining = poolsRemainingAfterTypeMaxHit("Lightning")
332+
assert.are.equals(0, floor(poolsRemaining.Mana))
333+
assert.are.equals(0, floor(poolsRemaining.Life))
334+
assert.are.equals(0, floor(poolsRemaining.OverkillDamage))
308335

309336
build.configTab.input.customMods = "\z
310337
+40 to maximum life\n\z
@@ -316,10 +343,10 @@ describe("TestDefence", function()
316343
20% of lightning damage taken as cold damage\n\z
317344
" -- Any extra mana in this case will not help and be left over after death, since life hits 0 from the cold damage alone
318345
pob1and2Compat()
319-
_, takenDamages = takenHitFromTypeMaxHit("Lightning")
320-
poolsRemaining = build.calcsTab.calcs.reducePoolsByDamage(nil, takenDamages, build.calcsTab.calcsEnv.player)
321-
assert.are.equals(0, round(poolsRemaining.Life))
346+
poolsRemaining = poolsRemainingAfterTypeMaxHit("Lightning")
322347
assert.are.not_false(1000 < round(poolsRemaining.Mana))
348+
assert.are.equals(0, floor(poolsRemaining.Life))
349+
assert.are.equals(0, floor(poolsRemaining.OverkillDamage))
323350

324351
-- conversion into a bigger pool
325352
build.configTab.input.customMods = "\z
@@ -332,10 +359,10 @@ describe("TestDefence", function()
332359
90% of cold damage taken as lightning damage\n\z
333360
" -- With inverted conversion amounts the behaviour of converting into a bigger pool should be exactly the same as converting into a lower one.
334361
pob1and2Compat()
335-
_, takenDamages = takenHitFromTypeMaxHit("Cold")
336-
poolsRemaining = build.calcsTab.calcs.reducePoolsByDamage(nil, takenDamages, build.calcsTab.calcsEnv.player)
337-
assert.are.equals(0, round(poolsRemaining.Mana))
338-
assert.are.not_false(poolsRemaining.Life / 100 < 0.1)
362+
poolsRemaining = poolsRemainingAfterTypeMaxHit("Cold")
363+
assert.are.equals(0, floor(poolsRemaining.Mana))
364+
assert.are.equals(0, floor(poolsRemaining.Life))
365+
assert.are.equals(0, floor(poolsRemaining.OverkillDamage))
339366

340367
build.configTab.input.customMods = "\z
341368
+140 to maximum life\n\z
@@ -347,10 +374,10 @@ describe("TestDefence", function()
347374
80% of cold damage taken as lightning damage\n\z
348375
"
349376
pob1and2Compat()
350-
_, takenDamages = takenHitFromTypeMaxHit("Cold")
351-
poolsRemaining = build.calcsTab.calcs.reducePoolsByDamage(nil, takenDamages, build.calcsTab.calcsEnv.player)
352-
assert.are.equals(0, round(poolsRemaining.Life))
353-
assert.are.equals(0, round(poolsRemaining.Mana))
377+
poolsRemaining = poolsRemainingAfterTypeMaxHit("Cold")
378+
assert.are.equals(0, floor(poolsRemaining.Mana))
379+
assert.are.equals(0, floor(poolsRemaining.Life))
380+
assert.are.equals(0, floor(poolsRemaining.OverkillDamage))
354381

355382
build.configTab.input.customMods = "\z
356383
+40 to maximum life\n\z
@@ -362,10 +389,33 @@ describe("TestDefence", function()
362389
80% of cold damage taken as lightning damage\n\z
363390
"
364391
pob1and2Compat()
365-
_, takenDamages = takenHitFromTypeMaxHit("Cold")
366-
poolsRemaining = build.calcsTab.calcs.reducePoolsByDamage(nil, takenDamages, build.calcsTab.calcsEnv.player)
367-
assert.are.equals(0, round(poolsRemaining.Life))
392+
poolsRemaining = poolsRemainingAfterTypeMaxHit("Cold")
368393
assert.are.not_false(1000 < round(poolsRemaining.Mana))
394+
assert.are.equals(0, floor(poolsRemaining.Life))
395+
assert.are.equals(0, floor(poolsRemaining.OverkillDamage))
396+
397+
build.configTab.input.customMods = "\z
398+
+940 to maximum life\n\z
399+
+950 to mana\n\z
400+
+1000 to energy shield\n\z
401+
+10000 to armour\n\z
402+
+110% to all elemental resistances\n\z
403+
Armour applies to Fire, Cold and Lightning Damage taken from Hits instead of Physical Damage\n\z
404+
100% of Lightning Damage is taken from Mana before Life\n\z
405+
80% of cold damage taken as lightning damage\n\z
406+
50% of fire damage taken as chaos damage\n\z
407+
"
408+
pob1and2Compat()
409+
poolsRemaining = poolsRemainingAfterTypeMaxHit("Cold")
410+
assert.are.equals(0, floor(poolsRemaining.EnergyShield))
411+
assert.are.equals(0, floor(poolsRemaining.Mana))
412+
assert.are.equals(0, floor(poolsRemaining.Life))
413+
assert.are.equals(0, floor(poolsRemaining.OverkillDamage))
414+
poolsRemaining = poolsRemainingAfterTypeMaxHit("Fire")
415+
assert.are.equals(0, floor(poolsRemaining.EnergyShield))
416+
assert.are.equals(1000, floor(poolsRemaining.Mana))
417+
assert.are.equals(0, floor(poolsRemaining.Life))
418+
assert.are.equals(0, floor(poolsRemaining.OverkillDamage))
369419
end)
370420

371421
it("energy shield bypass tests #pet", function()
@@ -378,25 +428,102 @@ describe("TestDefence", function()
378428
+60% to all resistances
379429
]]
380430
pob1and2Compat()
431+
local poolsRemaining = poolsRemainingAfterTypeMaxHit("Chaos")
432+
assert.are.equals(100, round(poolsRemaining.EnergyShield))
433+
assert.are.equals(0, floor(poolsRemaining.Life))
434+
assert.are.equals(0, floor(poolsRemaining.OverkillDamage))
381435
assert.are.equals(200, build.calcsTab.calcsOutput.FireMaximumHitTaken)
382436
assert.are.equals(200, build.calcsTab.calcsOutput.ChaosMaximumHitTaken)
383437

384438
-- Make sure we can't reach over 100% bypass
385439
build.configTab.input.customMods = [[
386440
+40 to maximum life
387441
+100 to energy shield
388-
50% of chaos damage taken does not bypass energy shield
442+
physical damage taken bypasses energy shield
389443
You have no intelligence
390444
+60% to all resistances
391445
]]
392446
pob1and2Compat()
447+
assert.are.equals(100, build.calcsTab.calcsOutput.PhysicalMaximumHitTaken)
393448
assert.are.equals(200, build.calcsTab.calcsOutput.FireMaximumHitTaken)
394449
assert.are.equals(150, build.calcsTab.calcsOutput.ChaosMaximumHitTaken)
450+
poolsRemaining = poolsRemainingAfterTypeMaxHit("Physical")
451+
assert.are.equals(100, floor(poolsRemaining.EnergyShield))
452+
assert.are.equals(0, floor(poolsRemaining.Life))
453+
assert.are.equals(0, floor(poolsRemaining.OverkillDamage))
454+
poolsRemaining = poolsRemainingAfterTypeMaxHit("Chaos")
455+
assert.are.equals(0, floor(poolsRemaining.EnergyShield))
456+
assert.are.equals(0, floor(poolsRemaining.Life))
457+
assert.are.equals(0, floor(poolsRemaining.OverkillDamage))
395458
-- Chaos damage should still bypass
396459
build.configTab.input.customMods = build.configTab.input.customMods .. "\nAll damage taken bypasses energy shield"
397460
build.configTab:BuildModList()
398461
runCallback("OnFrame")
462+
assert.are.equals(100, build.calcsTab.calcsOutput.PhysicalMaximumHitTaken)
399463
assert.are.equals(100, build.calcsTab.calcsOutput.FireMaximumHitTaken)
400464
assert.are.equals(100, build.calcsTab.calcsOutput.ChaosMaximumHitTaken)
465+
poolsRemaining = poolsRemainingAfterTypeMaxHit("Physical")
466+
assert.are.equals(100, floor(poolsRemaining.EnergyShield))
467+
assert.are.equals(0, floor(poolsRemaining.Life))
468+
assert.are.equals(0, floor(poolsRemaining.OverkillDamage))
469+
poolsRemaining = poolsRemainingAfterTypeMaxHit("Chaos")
470+
assert.are.equals(100, round(poolsRemaining.EnergyShield))
471+
assert.are.equals(0, floor(poolsRemaining.Life))
472+
assert.are.equals(0, floor(poolsRemaining.OverkillDamage))
473+
474+
-- Bypass + MoM
475+
build.configTab.input.customMods = [[
476+
+40 to maximum life
477+
+50 to mana
478+
+200 to energy shield
479+
50% of damage taken bypasses energy shield
480+
50% of Lightning Damage is taken from Mana before Life
481+
intelligence provides no bonus to energy shield
482+
+60% to all resistances
483+
]]
484+
pob1and2Compat()
485+
assert.are.equals(400, build.calcsTab.calcsOutput.LightningMaximumHitTaken)
486+
assert.are.equals(200, build.calcsTab.calcsOutput.FireMaximumHitTaken)
487+
assert.are.equals(200, build.calcsTab.calcsOutput.ChaosMaximumHitTaken)
488+
poolsRemaining = poolsRemainingAfterTypeMaxHit("Chaos")
489+
assert.are.equals(0, round(poolsRemaining.EnergyShield))
490+
assert.are.equals(0, floor(poolsRemaining.Life))
491+
assert.are.equals(0, floor(poolsRemaining.OverkillDamage))
492+
poolsRemaining = poolsRemainingAfterTypeMaxHit("Fire")
493+
assert.are.equals(100, round(poolsRemaining.EnergyShield))
494+
assert.are.equals(0, floor(poolsRemaining.Life))
495+
assert.are.equals(0, floor(poolsRemaining.OverkillDamage))
496+
poolsRemaining = poolsRemainingAfterTypeMaxHit("Lightning")
497+
assert.are.equals(0, round(poolsRemaining.EnergyShield))
498+
assert.are.equals(0, floor(poolsRemaining.Mana))
499+
assert.are.equals(0, floor(poolsRemaining.Life))
500+
assert.are.equals(0, floor(poolsRemaining.OverkillDamage))
501+
502+
build.configTab.input.customMods = [[
503+
+40 to maximum life
504+
+150 to mana
505+
+300 to energy shield
506+
50% of damage taken bypasses energy shield
507+
50% of Lightning Damage is taken from Mana before Life
508+
intelligence provides no bonus to energy shield
509+
+60% to all resistances
510+
]]
511+
pob1and2Compat()
512+
assert.are.equals(400, build.calcsTab.calcsOutput.LightningMaximumHitTaken)
513+
assert.are.equals(200, build.calcsTab.calcsOutput.FireMaximumHitTaken)
514+
assert.are.equals(200, build.calcsTab.calcsOutput.ChaosMaximumHitTaken)
515+
poolsRemaining = poolsRemainingAfterTypeMaxHit("Chaos")
516+
assert.are.equals(100, round(poolsRemaining.EnergyShield))
517+
assert.are.equals(0, floor(poolsRemaining.Life))
518+
assert.are.equals(0, floor(poolsRemaining.OverkillDamage))
519+
poolsRemaining = poolsRemainingAfterTypeMaxHit("Fire")
520+
assert.are.equals(200, round(poolsRemaining.EnergyShield))
521+
assert.are.equals(0, floor(poolsRemaining.Life))
522+
assert.are.equals(0, floor(poolsRemaining.OverkillDamage))
523+
poolsRemaining = poolsRemainingAfterTypeMaxHit("Lightning")
524+
assert.are.equals(100, round(poolsRemaining.EnergyShield))
525+
assert.are.equals(100, floor(poolsRemaining.Mana))
526+
assert.are.equals(0, floor(poolsRemaining.Life))
527+
assert.are.equals(0, floor(poolsRemaining.OverkillDamage))
401528
end)
402529
end)

src/Modules/CalcDefence.lua

Lines changed: 12 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -470,33 +470,21 @@ function calcs.reducePoolsByDamage(poolTable, damageTable, actor)
470470
local esDamageTypeMultiplier = damageType == "Chaos" and 2 or 1
471471
local esBypass = output[damageType.."EnergyShieldBypass"] / 100 or 0
472472
local lifeHitPool = calcLifeHitPoolWithLossPrevention(life, output.Life, output.preventedLifeLoss, lifeLossBelowHalfPrevented)
473-
if energyShield > 0 and (not modDB:Flag(nil, "EnergyShieldProtectsMana")) and (esBypass) < 1 then
474-
local esPool = esBypass > 0 and m_min(lifeHitPool / esBypass - lifeHitPool, energyShield) or energyShield
475-
local tempDamage = m_min(damageRemainder * (1 - esBypass), esPool / esDamageTypeMultiplier) * esDamageTypeMultiplier
476-
esPoolRemaining = m_min(esPoolRemaining, esPool - tempDamage)
477-
energyShield = energyShield - tempDamage
473+
local MoMEffect = m_min(output.sharedMindOverMatter + output[damageType.."MindOverMatter"], 100) / 100
474+
local MoMPool = MoMEffect < 1 and m_min(lifeHitPool / (1 - MoMEffect) - lifeHitPool, mana) or mana
475+
if energyShield > 0 and esBypass < 1 then
476+
local MoMEBPool = esBypass > 0 and m_min((MoMPool + lifeHitPool) / esBypass * esDamageTypeMultiplier - (MoMPool + lifeHitPool), energyShield) or energyShield
477+
local tempDamage = m_min(damageRemainder * (1 - esBypass), MoMEBPool / esDamageTypeMultiplier)
478+
esPoolRemaining = m_min(esPoolRemaining, MoMEBPool - tempDamage * esDamageTypeMultiplier)
479+
energyShield = energyShield - tempDamage * esDamageTypeMultiplier
478480
damageRemainder = damageRemainder - tempDamage
479481
end
480-
if (output.sharedMindOverMatter + output[damageType.."MindOverMatter"]) > 0 then
481-
local MoMEffect = m_min(output.sharedMindOverMatter + output[damageType.."MindOverMatter"], 100) / 100
482-
local MoMPool = MoMEffect < 1 and m_min(lifeHitPool / (1 - MoMEffect) - lifeHitPool, mana) or mana
482+
if MoMEffect > 0 and mana > 0 then
483483
local MoMDamage = damageRemainder * MoMEffect
484-
if modDB:Flag(nil, "EnergyShieldProtectsMana") and energyShield > 0 and esBypass < 1 then
485-
local MoMEBPool = esBypass > 0 and m_min(MoMPool / esBypass - MoMPool, energyShield) or energyShield
486-
local tempDamage = m_min(MoMDamage * (1 - esBypass), MoMEBPool / esDamageTypeMultiplier) * esDamageTypeMultiplier
487-
esPoolRemaining = m_min(esPoolRemaining, MoMEBPool - tempDamage)
488-
energyShield = energyShield - tempDamage
489-
MoMDamage = MoMDamage - tempDamage
490-
local tempDamage2 = m_min(MoMDamage, MoMPool)
491-
MoMPoolRemaining = m_min(MoMPoolRemaining, MoMPool - tempDamage2)
492-
mana = mana - tempDamage2
493-
damageRemainder = damageRemainder - tempDamage - tempDamage2
494-
elseif mana > 0 then
495-
local tempDamage = m_min(MoMDamage, MoMPool)
496-
MoMPoolRemaining = m_min(MoMPoolRemaining, MoMPool - tempDamage)
497-
mana = mana - tempDamage
498-
damageRemainder = damageRemainder - tempDamage
499-
end
484+
local tempDamage = m_min(MoMDamage, MoMPool)
485+
MoMPoolRemaining = m_min(MoMPoolRemaining, MoMPool - tempDamage)
486+
mana = mana - tempDamage
487+
damageRemainder = damageRemainder - tempDamage
500488
else
501489
MoMPoolRemaining = 0
502490
end

0 commit comments

Comments
 (0)