Skip to content

Commit 34d6d09

Browse files
PoB2 #694 backport (#8459)
1 parent 04a393d commit 34d6d09

File tree

2 files changed

+178
-39
lines changed

2 files changed

+178
-39
lines changed

spec/System/TestDefence_spec.lua

Lines changed: 165 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,16 @@ describe("TestDefence", function()
77
-- newBuild() takes care of resetting everything in setup()
88
end)
99

10+
-- a small helper function to calculate damage taken from limited test parameters
11+
local function takenHitFromTypeMaxHit(type, enemyDamageMulti)
12+
return build.calcsTab.calcs.takenHitFromDamage(build.calcsTab.calcsOutput[type.."MaximumHitTaken"] * (enemyDamageMulti or 1), type, build.calcsTab.calcsEnv.player)
13+
end
14+
15+
local function poolsRemainingAfterTypeMaxHit(type, enemyDamageMulti)
16+
local _, takenDamages = takenHitFromTypeMaxHit(type, enemyDamageMulti)
17+
return build.calcsTab.calcs.reducePoolsByDamage(nil, takenDamages, build.calcsTab.calcsEnv.player)
18+
end
19+
1020
-- boring part
1121
it("no armour max hits", function()
1222
build.configTab.input.enemyIsBoss = "None"
@@ -87,6 +97,30 @@ describe("TestDefence", function()
8797
assert.are.equals(3000, build.calcsTab.calcsOutput.ColdMaximumHitTaken)
8898
assert.are.equals(3000, build.calcsTab.calcsOutput.LightningMaximumHitTaken)
8999
assert.are.equals(3000, build.calcsTab.calcsOutput.ChaosMaximumHitTaken)
100+
local poolsRemaining = poolsRemainingAfterTypeMaxHit("Lightning", 0.8)
101+
assert.are.equals(0, floor(poolsRemaining.Life))
102+
assert.are.equals(0, floor(poolsRemaining.OverkillDamage))
103+
104+
build.configTab.input.customMods = "\z
105+
+200 to all resistances\n\z
106+
+200 to all maximum resistances\n\z
107+
50% reduced damage taken\n\z
108+
50% less damage taken\n\z
109+
Nearby enemies deal 20% less damage\n\z
110+
Gain 100% of life as extra maximum energy shield\n\z
111+
intelligence provides no bonus to energy shield\n\z
112+
"
113+
build.configTab:BuildModList()
114+
runCallback("OnFrame")
115+
assert.are.equals(600, build.calcsTab.calcsOutput.PhysicalMaximumHitTaken)
116+
assert.are.equals(6000, build.calcsTab.calcsOutput.FireMaximumHitTaken)
117+
assert.are.equals(6000, build.calcsTab.calcsOutput.ColdMaximumHitTaken)
118+
assert.are.equals(6000, build.calcsTab.calcsOutput.LightningMaximumHitTaken)
119+
assert.are.equals(3000, build.calcsTab.calcsOutput.ChaosMaximumHitTaken)
120+
poolsRemaining = poolsRemainingAfterTypeMaxHit("Lightning", 0.8)
121+
assert.are.equals(0, floor(poolsRemaining.EnergyShield))
122+
assert.are.equals(0, floor(poolsRemaining.Life))
123+
assert.are.equals(0, floor(poolsRemaining.OverkillDamage))
90124
end)
91125

92126
-- a small helper function to calculate damage taken from limited test parameters
@@ -507,9 +541,9 @@ describe("TestDefence", function()
507541
"
508542
build.configTab:BuildModList()
509543
runCallback("OnFrame")
510-
local _, takenDamages = takenHitFromTypeMaxHit("Cold")
511-
local poolsRemaining = build.calcsTab.calcs.reducePoolsByDamage(nil, takenDamages, build.calcsTab.calcsEnv.player)
512-
assert.are.equals(0, round(poolsRemaining.Life))
544+
local poolsRemaining = poolsRemainingAfterTypeMaxHit("Cold")
545+
assert.are.equals(0, floor(poolsRemaining.Life))
546+
assert.are.equals(0, floor(poolsRemaining.OverkillDamage))
513547
end)
514548

515549
it("damage conversion to different size pools", function()
@@ -525,10 +559,10 @@ describe("TestDefence", function()
525559
" -- 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)
526560
build.configTab:BuildModList()
527561
runCallback("OnFrame")
528-
local _, takenDamages = takenHitFromTypeMaxHit("Lightning")
529-
local poolsRemaining = build.calcsTab.calcs.reducePoolsByDamage(nil, takenDamages, build.calcsTab.calcsEnv.player)
530-
assert.are.equals(0, round(poolsRemaining.Mana))
531-
assert.are.not_false(poolsRemaining.Life / 100 < 0.1)
562+
local poolsRemaining = poolsRemainingAfterTypeMaxHit("Lightning")
563+
assert.are.equals(0, floor(poolsRemaining.Mana))
564+
assert.are.equals(0, floor(poolsRemaining.Life))
565+
assert.are.equals(0, floor(poolsRemaining.OverkillDamage))
532566

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

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

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

582616
build.configTab.input.customMods = "\z
583617
+40 to maximum life\n\z
@@ -590,10 +624,10 @@ describe("TestDefence", function()
590624
"
591625
build.configTab:BuildModList()
592626
runCallback("OnFrame")
593-
_, takenDamages = takenHitFromTypeMaxHit("Cold")
594-
poolsRemaining = build.calcsTab.calcs.reducePoolsByDamage(nil, takenDamages, build.calcsTab.calcsEnv.player)
595-
assert.are.equals(0, round(poolsRemaining.Life))
596-
assert.are.equals(0, round(poolsRemaining.Mana))
627+
poolsRemaining = poolsRemainingAfterTypeMaxHit("Cold")
628+
assert.are.equals(0, floor(poolsRemaining.Mana))
629+
assert.are.equals(0, floor(poolsRemaining.Life))
630+
assert.are.equals(0, floor(poolsRemaining.OverkillDamage))
597631

598632
build.configTab.input.customMods = "\z
599633
+40 to maximum life\n\z
@@ -606,10 +640,34 @@ describe("TestDefence", function()
606640
"
607641
build.configTab:BuildModList()
608642
runCallback("OnFrame")
609-
_, takenDamages = takenHitFromTypeMaxHit("Cold")
610-
poolsRemaining = build.calcsTab.calcs.reducePoolsByDamage(nil, takenDamages, build.calcsTab.calcsEnv.player)
611-
assert.are.equals(0, round(poolsRemaining.Life))
643+
poolsRemaining = poolsRemainingAfterTypeMaxHit("Cold")
612644
assert.are.equals(1000, round(poolsRemaining.Mana))
645+
assert.are.equals(0, floor(poolsRemaining.Life))
646+
assert.are.equals(0, floor(poolsRemaining.OverkillDamage))
647+
648+
build.configTab.input.customMods = "\z
649+
+940 to maximum life\n\z
650+
+950 to mana\n\z
651+
+1000 to energy shield\n\z
652+
+10000 to armour\n\z
653+
+110% to all elemental resistances\n\z
654+
Armour applies to Fire, Cold and Lightning Damage taken from Hits instead of Physical Damage\n\z
655+
100% of Lightning Damage is taken from Mana before Life\n\z
656+
80% of cold damage taken as lightning damage\n\z
657+
50% of fire damage taken as chaos damage\n\z
658+
"
659+
build.configTab:BuildModList()
660+
runCallback("OnFrame")
661+
poolsRemaining = poolsRemainingAfterTypeMaxHit("Cold")
662+
assert.are.equals(0, floor(poolsRemaining.EnergyShield))
663+
assert.are.equals(0, floor(poolsRemaining.Mana))
664+
assert.are.equals(0, floor(poolsRemaining.Life))
665+
assert.are.equals(0, floor(poolsRemaining.OverkillDamage))
666+
poolsRemaining = poolsRemainingAfterTypeMaxHit("Fire")
667+
assert.are.not_false(0 < floor(poolsRemaining.EnergyShield))
668+
assert.are.equals(1000, floor(poolsRemaining.Mana))
669+
assert.are.not_false(1 >= floor(poolsRemaining.Life))
670+
assert.are.equals(0, floor(poolsRemaining.OverkillDamage))
613671
end)
614672

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

626684
build.configTab:BuildModList()
627685
runCallback("OnFrame")
686+
local poolsRemaining = poolsRemainingAfterTypeMaxHit("Chaos")
687+
assert.are.equals(100, round(poolsRemaining.EnergyShield))
688+
assert.are.equals(0, floor(poolsRemaining.Life))
689+
assert.are.equals(0, floor(poolsRemaining.OverkillDamage))
628690
assert.are.equals(300, build.calcsTab.calcsOutput.FireMaximumHitTaken)
629691
assert.are.equals(200, build.calcsTab.calcsOutput.ChaosMaximumHitTaken)
630692

631693
-- Negative overrides positive
632694
build.configTab.input.customMods = [[
633695
+40 to maximum life
634696
+100 to energy shield
697+
physical damage taken bypasses energy shield
635698
Chaos damage does not bypass energy shield
636699
You have no intelligence
637700
+60% to all resistances
638701
]]
639702
build.configTab:BuildModList()
640703
runCallback("OnFrame")
704+
assert.are.equals(100, build.calcsTab.calcsOutput.PhysicalMaximumHitTaken)
641705
assert.are.equals(200, build.calcsTab.calcsOutput.FireMaximumHitTaken)
642706
assert.are.equals(200, build.calcsTab.calcsOutput.ChaosMaximumHitTaken)
707+
poolsRemaining = poolsRemainingAfterTypeMaxHit("Physical")
708+
assert.are.equals(100, floor(poolsRemaining.EnergyShield))
709+
assert.are.equals(0, floor(poolsRemaining.Life))
710+
assert.are.equals(0, floor(poolsRemaining.OverkillDamage))
711+
poolsRemaining = poolsRemainingAfterTypeMaxHit("Chaos")
712+
assert.are.equals(0, floor(poolsRemaining.EnergyShield))
713+
assert.are.equals(0, floor(poolsRemaining.Life))
714+
assert.are.equals(0, floor(poolsRemaining.OverkillDamage))
643715
-- Chaos damage should still bypass
644716
build.configTab.input.customMods = build.configTab.input.customMods .. "\nAll damage taken bypasses energy shield"
645717
build.configTab:BuildModList()
646718
runCallback("OnFrame")
719+
assert.are.equals(100, build.calcsTab.calcsOutput.PhysicalMaximumHitTaken)
647720
assert.are.equals(100, build.calcsTab.calcsOutput.FireMaximumHitTaken)
648721
assert.are.equals(100, build.calcsTab.calcsOutput.ChaosMaximumHitTaken)
649722

@@ -666,5 +739,72 @@ describe("TestDefence", function()
666739
runCallback("OnFrame")
667740
assert.are.equals(100, build.calcsTab.calcsOutput.FireMaximumHitTaken)
668741
assert.are.equals(100, build.calcsTab.calcsOutput.ChaosMaximumHitTaken)
742+
poolsRemaining = poolsRemainingAfterTypeMaxHit("Physical")
743+
assert.are.equals(100, floor(poolsRemaining.EnergyShield))
744+
assert.are.equals(0, floor(poolsRemaining.Life))
745+
assert.are.equals(0, floor(poolsRemaining.OverkillDamage))
746+
poolsRemaining = poolsRemainingAfterTypeMaxHit("Chaos")
747+
assert.are.equals(100, round(poolsRemaining.EnergyShield))
748+
assert.are.equals(0, floor(poolsRemaining.Life))
749+
assert.are.equals(0, floor(poolsRemaining.OverkillDamage))
750+
751+
-- Bypass + MoM
752+
build.configTab.input.customMods = [[
753+
+40 to maximum life
754+
+50 to mana
755+
+200 to energy shield
756+
50% of non-chaos damage taken bypasses energy shield
757+
50% of chaos damage taken does not bypass energy shield
758+
50% of Lightning Damage is taken from Mana before Life
759+
intelligence provides no bonus to energy shield
760+
+60% to all resistances
761+
]]
762+
build.configTab:BuildModList()
763+
runCallback("OnFrame")
764+
assert.are.equals(400, build.calcsTab.calcsOutput.LightningMaximumHitTaken)
765+
assert.are.equals(200, build.calcsTab.calcsOutput.FireMaximumHitTaken)
766+
assert.are.equals(200, build.calcsTab.calcsOutput.ChaosMaximumHitTaken)
767+
poolsRemaining = poolsRemainingAfterTypeMaxHit("Chaos")
768+
assert.are.equals(100, round(poolsRemaining.EnergyShield))
769+
assert.are.equals(0, floor(poolsRemaining.Life))
770+
assert.are.equals(0, floor(poolsRemaining.OverkillDamage))
771+
poolsRemaining = poolsRemainingAfterTypeMaxHit("Fire")
772+
assert.are.equals(100, round(poolsRemaining.EnergyShield))
773+
assert.are.equals(0, floor(poolsRemaining.Life))
774+
assert.are.equals(0, floor(poolsRemaining.OverkillDamage))
775+
poolsRemaining = poolsRemainingAfterTypeMaxHit("Lightning")
776+
assert.are.equals(0, round(poolsRemaining.EnergyShield))
777+
assert.are.equals(0, floor(poolsRemaining.Mana))
778+
assert.are.equals(0, floor(poolsRemaining.Life))
779+
assert.are.equals(0, floor(poolsRemaining.OverkillDamage))
780+
781+
build.configTab.input.customMods = [[
782+
+40 to maximum life
783+
+150 to mana
784+
+300 to energy shield
785+
50% of non-chaos damage taken bypasses energy shield
786+
50% of chaos damage taken does not bypass energy shield
787+
50% of Lightning Damage is taken from Mana before Life
788+
intelligence provides no bonus to energy shield
789+
+60% to all resistances
790+
]]
791+
build.configTab:BuildModList()
792+
runCallback("OnFrame")
793+
assert.are.equals(400, build.calcsTab.calcsOutput.LightningMaximumHitTaken)
794+
assert.are.equals(200, build.calcsTab.calcsOutput.FireMaximumHitTaken)
795+
assert.are.equals(200, build.calcsTab.calcsOutput.ChaosMaximumHitTaken)
796+
poolsRemaining = poolsRemainingAfterTypeMaxHit("Chaos")
797+
assert.are.equals(200, round(poolsRemaining.EnergyShield))
798+
assert.are.equals(0, floor(poolsRemaining.Life))
799+
assert.are.equals(0, floor(poolsRemaining.OverkillDamage))
800+
poolsRemaining = poolsRemainingAfterTypeMaxHit("Fire")
801+
assert.are.equals(200, round(poolsRemaining.EnergyShield))
802+
assert.are.equals(0, floor(poolsRemaining.Life))
803+
assert.are.equals(0, floor(poolsRemaining.OverkillDamage))
804+
poolsRemaining = poolsRemainingAfterTypeMaxHit("Lightning")
805+
assert.are.equals(100, round(poolsRemaining.EnergyShield))
806+
assert.are.equals(100, floor(poolsRemaining.Mana))
807+
assert.are.equals(0, floor(poolsRemaining.Life))
808+
assert.are.equals(0, floor(poolsRemaining.OverkillDamage))
669809
end)
670810
end)

src/Modules/CalcDefence.lua

Lines changed: 13 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -258,33 +258,32 @@ function calcs.reducePoolsByDamage(poolTable, damageTable, actor)
258258
end
259259
local esBypass = output[damageType.."EnergyShieldBypass"] / 100 or 0
260260
local lifeHitPool = calcLifeHitPoolWithLossPrevention(life, output.Life, output.preventedLifeLoss, lifeLossBelowHalfPrevented)
261-
if energyShield > 0 and (not modDB:Flag(nil, "EnergyShieldProtectsMana")) and (esBypass) < 1 then
262-
local esPool = esBypass > 0 and m_min(lifeHitPool / esBypass - lifeHitPool, energyShield) or energyShield
261+
local MoMEffect = m_min(output.sharedMindOverMatter + output[damageType.."MindOverMatter"], 100) / 100
262+
local MoMPool = MoMEffect < 1 and m_min(lifeHitPool / (1 - MoMEffect) - lifeHitPool, mana) or mana
263+
local lifePlusMoMHitPool = lifeHitPool + MoMPool
264+
if energyShield > 0 and not modDB:Flag(nil, "EnergyShieldProtectsMana") and esBypass < 1 then
265+
local esPool = esBypass > 0 and m_min(lifePlusMoMHitPool / esBypass - lifePlusMoMHitPool, energyShield) or energyShield
263266
local tempDamage = m_min(damageRemainder * (1 - esBypass), esPool)
264267
esPoolRemaining = m_min(esPoolRemaining, esPool - tempDamage)
265268
energyShield = energyShield - tempDamage
266269
damageRemainder = damageRemainder - tempDamage
270+
elseif esBypass == 1 then
271+
esPoolRemaining = 0
267272
end
268-
if (output.sharedMindOverMatter + output[damageType.."MindOverMatter"]) > 0 then
269-
local MoMEffect = m_min(output.sharedMindOverMatter + output[damageType.."MindOverMatter"], 100) / 100
270-
local MoMPool = MoMEffect < 1 and m_min(lifeHitPool / (1 - MoMEffect) - lifeHitPool, mana) or mana
273+
if MoMEffect > 0 and mana > 0 then
271274
local MoMDamage = damageRemainder * MoMEffect
272275
if modDB:Flag(nil, "EnergyShieldProtectsMana") and energyShield > 0 and esBypass < 1 then
273276
local MoMEBPool = esBypass > 0 and m_min(MoMPool / esBypass - MoMPool, energyShield) or energyShield
274277
local tempDamage = m_min(MoMDamage * (1 - esBypass), MoMEBPool)
275278
esPoolRemaining = m_min(esPoolRemaining, MoMEBPool - tempDamage)
276279
energyShield = energyShield - tempDamage
277-
MoMDamage = MoMDamage - tempDamage
278-
local tempDamage2 = m_min(MoMDamage, MoMPool)
279-
MoMPoolRemaining = m_min(MoMPoolRemaining, MoMPool - tempDamage2)
280-
mana = mana - tempDamage2
281-
damageRemainder = damageRemainder - tempDamage - tempDamage2
282-
elseif mana > 0 then
283-
local tempDamage = m_min(MoMDamage, MoMPool)
284-
MoMPoolRemaining = m_min(MoMPoolRemaining, MoMPool - tempDamage)
285-
mana = mana - tempDamage
286280
damageRemainder = damageRemainder - tempDamage
281+
MoMDamage = MoMDamage - tempDamage
287282
end
283+
local tempDamage = m_min(MoMDamage, MoMPool)
284+
MoMPoolRemaining = m_min(MoMPoolRemaining, MoMPool - tempDamage)
285+
mana = mana - tempDamage
286+
damageRemainder = damageRemainder - tempDamage
288287
else
289288
MoMPoolRemaining = 0
290289
end

0 commit comments

Comments
 (0)