Skip to content

Commit 195006f

Browse files
Improve EHP accuracy (#8430)
* Improve EHP accuracy * Hunting more corner cases MoM with ES when there is ES bypass was not capping effective ES. MoM with life loss prevention was not accounting for the multiplied life effectiveness when reducing pools. Max hit smoothing (for conversion + armour) now uses pool reduction to effectively home in on a precise max hit. * Hunting more corner cases MoM with ES when there is ES bypass was not capping effective ES. MoM with life loss prevention was not accounting for the multiplied life effectiveness when reducing pools. Max hit smoothing (for conversion + armour) now uses pool reduction to effectively home in on a precise max hit.
1 parent b6b326f commit 195006f

File tree

3 files changed

+256
-225
lines changed

3 files changed

+256
-225
lines changed

spec/System/TestDefence_spec.lua

Lines changed: 160 additions & 155 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ describe("TestDefence", function()
1212
build.configTab.input.enemyIsBoss = "None"
1313
build.configTab:BuildModList()
1414
runCallback("OnFrame")
15-
15+
1616
assert.are.equals(60, build.calcsTab.calcsOutput.PhysicalMaximumHitTaken)
1717
assert.are.equals(38, build.calcsTab.calcsOutput.FireMaximumHitTaken)
1818
assert.are.equals(38, build.calcsTab.calcsOutput.ColdMaximumHitTaken)
@@ -95,73 +95,76 @@ describe("TestDefence", function()
9595
end
9696

9797
it("progenesis and petrified blood", function()
98+
build.configTab.input.enemyIsBoss = "None"
9899
-- Petrified blood
99-
--build.skillsTab:PasteSocketGroup("\z
100-
--Label: 50% petrified\n\z
101-
--Petrified Blood 20/40 Alternate1 1\n\z
102-
--Arrogance 21/200 Alternate1 1\n\z
103-
--") -- 50% petrified effect, when exactly half of the life is reserved, should make the life pool be equivalent to no petrified effect and full life.
104-
--build.skillsTab:ProcessSocketGroup(build.skillsTab.socketGroupList[1])
105-
--build.configTab.input.customMods = "\z
106-
--+200 to all resistances\n\z
107-
--+200 to all maximum resistances\n\z
108-
--50% reduced damage taken\n\z
109-
--50% less damage taken\n\z
110-
--Nearby enemies deal 20% less damage\n\z
111-
--"
112-
--build.configTab:BuildModList()
113-
--runCallback("OnFrame")
114-
--assert.are.equals(300, build.calcsTab.calcsOutput.PhysicalMaximumHitTaken)
115-
--assert.are.equals(3000, build.calcsTab.calcsOutput.FireMaximumHitTaken)
116-
--assert.are.equals(3000, build.calcsTab.calcsOutput.ColdMaximumHitTaken)
117-
--assert.are.equals(3000, build.calcsTab.calcsOutput.LightningMaximumHitTaken)
118-
--assert.are.equals(3000, build.calcsTab.calcsOutput.ChaosMaximumHitTaken)
119-
--build.skillsTab.socketGroupList = {}
120-
--
121-
--build.skillsTab:PasteSocketGroup("\z
122-
--Label: 50% petrified\n\z
123-
--Petrified Blood 20/40 Alternate1 1\n\z
124-
--Arrogance 21/200 Alternate1 1\n\z
125-
--")
126-
--build.skillsTab:ProcessSocketGroup(build.skillsTab.socketGroupList[1])
127-
--build.configTab.input.customMods = "\z
128-
--+200 to all resistances\n\z
129-
--+200 to all maximum resistances\n\z
130-
--50% reduced damage taken\n\z
131-
--50% less damage taken\n\z
132-
--Nearby enemies deal 20% less damage\n\z
133-
--100% less intelligence\n\z
134-
--+60 to maximum energy shield\n\z
135-
--" -- petrified blood should not interact with pools other than life.
136-
--build.configTab:BuildModList()
137-
--runCallback("OnFrame")
138-
--assert.are.equals(600, build.calcsTab.calcsOutput.PhysicalMaximumHitTaken)
139-
--assert.are.equals(6000, build.calcsTab.calcsOutput.FireMaximumHitTaken)
140-
--assert.are.equals(6000, build.calcsTab.calcsOutput.ColdMaximumHitTaken)
141-
--assert.are.equals(6000, build.calcsTab.calcsOutput.LightningMaximumHitTaken)
142-
--assert.are.equals(3000, build.calcsTab.calcsOutput.ChaosMaximumHitTaken)
143-
--build.skillsTab.socketGroupList = {}
144-
--
145-
--build.skillsTab:PasteSocketGroup("\z
146-
--Label: 75% petrified\n\z
147-
--Petrified Blood 20/140 Alternate1 1\n\z
148-
--") -- 75% petrified effect, starting from full life, should make the life pool be equivalent to 0.5 * life (unprotected upper half) and then 4 * 0.5 * life (protected lower half), making it 2.5* bigger in total
149-
--build.skillsTab:ProcessSocketGroup(build.skillsTab.socketGroupList[1])
150-
--build.configTab.input.customMods = "\z
151-
--+200 to all resistances\n\z
152-
--+200 to all maximum resistances\n\z
153-
--50% reduced damage taken\n\z
154-
--50% less damage taken\n\z
155-
--Nearby enemies deal 20% less damage\n\z
156-
--"
157-
--build.configTab:BuildModList()
158-
--runCallback("OnFrame")
159-
--assert.are.equals(750, build.calcsTab.calcsOutput.PhysicalMaximumHitTaken)
160-
--assert.are.equals(7500, build.calcsTab.calcsOutput.FireMaximumHitTaken)
161-
--assert.are.equals(7500, build.calcsTab.calcsOutput.ColdMaximumHitTaken)
162-
--assert.are.equals(7500, build.calcsTab.calcsOutput.LightningMaximumHitTaken)
163-
--assert.are.equals(7500, build.calcsTab.calcsOutput.ChaosMaximumHitTaken)
164-
--build.skillsTab.socketGroupList = {}
100+
build.skillsTab:PasteSocketGroup("\z
101+
Petrified Blood 20/0 Default 1\n\z
102+
Arrogance 21/0 Default 1\n\z
103+
") -- 50% petrified effect, when exactly half of the life is reserved, should make the life pool be equivalent to no petrified effect and full life.
104+
build.skillsTab:ProcessSocketGroup(build.skillsTab.socketGroupList[1])
105+
build.configTab.input.customMods = "\z
106+
+200 to all resistances\n\z
107+
+200 to all maximum resistances\n\z
108+
50% reduced damage taken\n\z
109+
50% less damage taken\n\z
110+
Nearby enemies deal 20% less damage\n\z
111+
40% increased reservation efficiency\n\z
112+
25% increased effect of buffs on you\n\z
113+
"
114+
build.configTab:BuildModList()
115+
runCallback("OnFrame")
116+
assert.are.equals(300, build.calcsTab.calcsOutput.PhysicalMaximumHitTaken)
117+
assert.are.equals(3000, build.calcsTab.calcsOutput.FireMaximumHitTaken)
118+
assert.are.equals(3000, build.calcsTab.calcsOutput.ColdMaximumHitTaken)
119+
assert.are.equals(3000, build.calcsTab.calcsOutput.LightningMaximumHitTaken)
120+
assert.are.equals(3000, build.calcsTab.calcsOutput.ChaosMaximumHitTaken)
121+
build.skillsTab.socketGroupList = {}
122+
123+
build.skillsTab:PasteSocketGroup("\z
124+
Petrified Blood 20/0 Default 1\n\z
125+
Arrogance 21/0 Default 1\n\z
126+
")
127+
build.skillsTab:ProcessSocketGroup(build.skillsTab.socketGroupList[1])
128+
build.configTab.input.customMods = "\z
129+
+200 to all resistances\n\z
130+
+200 to all maximum resistances\n\z
131+
50% reduced damage taken\n\z
132+
50% less damage taken\n\z
133+
Nearby enemies deal 20% less damage\n\z
134+
100% less intelligence\n\z
135+
+60 to maximum energy shield\n\z
136+
40% increased reservation efficiency\n\z
137+
25% increased effect of buffs on you\n\z
138+
" -- petrified blood should not interact with pools other than life.
139+
build.configTab:BuildModList()
140+
runCallback("OnFrame")
141+
assert.are.equals(600, build.calcsTab.calcsOutput.PhysicalMaximumHitTaken)
142+
assert.are.equals(6000, build.calcsTab.calcsOutput.FireMaximumHitTaken)
143+
assert.are.equals(6000, build.calcsTab.calcsOutput.ColdMaximumHitTaken)
144+
assert.are.equals(6000, build.calcsTab.calcsOutput.LightningMaximumHitTaken)
145+
assert.are.equals(3000, build.calcsTab.calcsOutput.ChaosMaximumHitTaken)
146+
build.skillsTab.socketGroupList = {}
147+
148+
build.skillsTab:PasteSocketGroup("\z
149+
Petrified Blood 20/0 Default 1\n\z
150+
") -- 80% petrified effect, starting from full life, should make the life pool be equivalent to 0.5 * life (unprotected upper half) and then 5 * 0.5 * life (protected lower half), making it 3* bigger in total
151+
build.skillsTab:ProcessSocketGroup(build.skillsTab.socketGroupList[1])
152+
build.configTab.input.customMods = "\z
153+
+200 to all resistances\n\z
154+
+200 to all maximum resistances\n\z
155+
50% reduced damage taken\n\z
156+
50% less damage taken\n\z
157+
Nearby enemies deal 20% less damage\n\z
158+
100% increased effect of buffs on you\n\z
159+
"
160+
build.configTab:BuildModList()
161+
runCallback("OnFrame")
162+
assert.are.equals(900, build.calcsTab.calcsOutput.PhysicalMaximumHitTaken)
163+
assert.are.equals(9000, build.calcsTab.calcsOutput.FireMaximumHitTaken)
164+
assert.are.equals(9000, build.calcsTab.calcsOutput.ColdMaximumHitTaken)
165+
assert.are.equals(9000, build.calcsTab.calcsOutput.LightningMaximumHitTaken)
166+
assert.are.equals(9000, build.calcsTab.calcsOutput.ChaosMaximumHitTaken)
167+
build.skillsTab.socketGroupList = {}
165168

166169
-- Progenesis
167170
build.configTab.input.customMods = "\z
@@ -173,7 +176,6 @@ describe("TestDefence", function()
173176
When Hit during effect, 50% of Life loss from Damage taken occurs over 4 seconds instead\n\z
174177
" -- 50% progenesis should just simply double the life pool
175178
build.configTab.input.conditionUsingFlask = true
176-
build.configTab.input.enemyIsBoss = "None"
177179
build.configTab:BuildModList()
178180
runCallback("OnFrame")
179181
assert.are.equals(600, build.calcsTab.calcsOutput.PhysicalMaximumHitTaken)
@@ -201,52 +203,53 @@ describe("TestDefence", function()
201203
assert.are.equals(6000, build.calcsTab.calcsOutput.ChaosMaximumHitTaken)
202204

203205
-- Progenesis + petrified blood
204-
--build.skillsTab:PasteSocketGroup("\z
205-
--Label: 50% petrified\n\z
206-
--Petrified Blood 20/40 Alternate1 1\n\z
207-
--Arrogance 21/200 Alternate1 1\n\z
208-
--")
209-
--build.skillsTab:ProcessSocketGroup(build.skillsTab.socketGroupList[1])
210-
--build.configTab.input.customMods = "\z
211-
--+200 to all resistances\n\z
212-
--+200 to all maximum resistances\n\z
213-
--50% reduced damage taken\n\z
214-
--50% less damage taken\n\z
215-
--Nearby enemies deal 20% less damage\n\z
216-
--When Hit during effect, 50% of Life loss from Damage taken occurs over 4 seconds instead\n\z
217-
--" -- With half of life reserved, both effects are active and multiplicative with each other, making the effective life pool 4 * half life = 2 * life (or same as no petrified, no reserve and 50% progenesis)
218-
--build.configTab.input.conditionUsingFlask = true
219-
--build.configTab:BuildModList()
220-
--runCallback("OnFrame")
221-
--assert.are.equals(600, build.calcsTab.calcsOutput.PhysicalMaximumHitTaken)
222-
--assert.are.equals(6000, build.calcsTab.calcsOutput.FireMaximumHitTaken)
223-
--assert.are.equals(6000, build.calcsTab.calcsOutput.ColdMaximumHitTaken)
224-
--assert.are.equals(6000, build.calcsTab.calcsOutput.LightningMaximumHitTaken)
225-
--assert.are.equals(6000, build.calcsTab.calcsOutput.ChaosMaximumHitTaken)
226-
--build.skillsTab.socketGroupList = {}
227-
--
228-
--build.skillsTab:PasteSocketGroup("\z
229-
--Label: 50% petrified\n\z
230-
--Petrified Blood 20/40 Alternate1 1\n\z
231-
--")
232-
--build.skillsTab:ProcessSocketGroup(build.skillsTab.socketGroupList[1])
233-
--build.configTab.input.customMods = "\z
234-
--+200 to all resistances\n\z
235-
--+200 to all maximum resistances\n\z
236-
--50% reduced damage taken\n\z
237-
--50% less damage taken\n\z
238-
--Nearby enemies deal 20% less damage\n\z
239-
--When Hit during effect, 50% of Life loss from Damage taken occurs over 4 seconds instead\n\z
240-
--" -- With no life reserved, progenesis first doubles the pool of life above low, then both progenesis and petrified quadruple the pool of life below low, so effective pool is 3 * life
241-
--build.configTab.input.conditionUsingFlask = true
242-
--build.configTab:BuildModList()
243-
--runCallback("OnFrame")
244-
--assert.are.equals(900, build.calcsTab.calcsOutput.PhysicalMaximumHitTaken)
245-
--assert.are.equals(9000, build.calcsTab.calcsOutput.FireMaximumHitTaken)
246-
--assert.are.equals(9000, build.calcsTab.calcsOutput.ColdMaximumHitTaken)
247-
--assert.are.equals(9000, build.calcsTab.calcsOutput.LightningMaximumHitTaken)
248-
--assert.are.equals(9000, build.calcsTab.calcsOutput.ChaosMaximumHitTaken)
249-
--build.skillsTab.socketGroupList = {}
206+
build.skillsTab:PasteSocketGroup("\z
207+
Petrified Blood 20/0 Default 1\n\z
208+
Arrogance 21/0 Default 1\n\z
209+
")
210+
build.skillsTab:ProcessSocketGroup(build.skillsTab.socketGroupList[1])
211+
build.configTab.input.customMods = "\z
212+
+200 to all resistances\n\z
213+
+200 to all maximum resistances\n\z
214+
50% reduced damage taken\n\z
215+
50% less damage taken\n\z
216+
Nearby enemies deal 20% less damage\n\z
217+
When Hit during effect, 50% of Life loss from Damage taken occurs over 4 seconds instead\n\z
218+
40% increased reservation efficiency\n\z
219+
25% increased effect of buffs on you\n\z
220+
" -- With half of life reserved, both effects are active and multiplicative with each other, making the effective life pool 4 * half life = 2 * life (or same as no petrified, no reserve and 50% progenesis)
221+
build.configTab.input.conditionUsingFlask = true
222+
build.configTab:BuildModList()
223+
runCallback("OnFrame")
224+
assert.are.equals(600, build.calcsTab.calcsOutput.PhysicalMaximumHitTaken)
225+
assert.are.equals(6000, build.calcsTab.calcsOutput.FireMaximumHitTaken)
226+
assert.are.equals(6000, build.calcsTab.calcsOutput.ColdMaximumHitTaken)
227+
assert.are.equals(6000, build.calcsTab.calcsOutput.LightningMaximumHitTaken)
228+
assert.are.equals(6000, build.calcsTab.calcsOutput.ChaosMaximumHitTaken)
229+
build.skillsTab.socketGroupList = {}
230+
231+
build.skillsTab:PasteSocketGroup("\z
232+
Petrified Blood 20/0 Default 1\n\z
233+
")
234+
build.skillsTab:ProcessSocketGroup(build.skillsTab.socketGroupList[1])
235+
build.configTab.input.customMods = "\z
236+
+200 to all resistances\n\z
237+
+200 to all maximum resistances\n\z
238+
50% reduced damage taken\n\z
239+
50% less damage taken\n\z
240+
Nearby enemies deal 20% less damage\n\z
241+
When Hit during effect, 50% of Life loss from Damage taken occurs over 4 seconds instead\n\z
242+
25% increased effect of buffs on you\n\z
243+
" -- With no life reserved, progenesis first doubles the pool of life above low, then both progenesis and petrified quadruple the pool of life below low, so effective pool is 3 * life
244+
build.configTab.input.conditionUsingFlask = true
245+
build.configTab:BuildModList()
246+
runCallback("OnFrame")
247+
assert.are.equals(900, build.calcsTab.calcsOutput.PhysicalMaximumHitTaken)
248+
assert.are.equals(9000, build.calcsTab.calcsOutput.FireMaximumHitTaken)
249+
assert.are.equals(9000, build.calcsTab.calcsOutput.ColdMaximumHitTaken)
250+
assert.are.equals(9000, build.calcsTab.calcsOutput.LightningMaximumHitTaken)
251+
assert.are.equals(9000, build.calcsTab.calcsOutput.ChaosMaximumHitTaken)
252+
build.skillsTab.socketGroupList = {}
250253

251254
build.skillsTab:PasteSocketGroup("\z
252255
Petrified Blood 20/0 Default 1\n\z
@@ -277,57 +280,57 @@ describe("TestDefence", function()
277280
assert.are.equals(120, poolsRemaining.LifeLossLostOverTime)
278281
assert.are.equals(20, poolsRemaining.LifeBelowHalfLossLostOverTime)
279282

280-
--build.skillsTab:PasteSocketGroup("\z
281-
--Label: 50% petrified\n\z
282-
--Petrified Blood 20/40 Alternate1 1\n\z
283-
--")
284-
--build.skillsTab:ProcessSocketGroup(build.skillsTab.socketGroupList[1])
285-
--build.configTab.input.customMods = "\z
286-
--+1950 to life\n\z
287-
--+2960 to mana\n\z
288-
--+3000 to energy shield\n\z
289-
--100% less attributes\n\z
290-
--100% less mana reserved\n\z
291-
--+60% to all resistances\n\z
292-
--chaos damage does not bypass energy shield\n\z
293-
--mind over matter\n\z
294-
--eldritch battery\n\z
295-
--10% of lightning damage is taken from mana before life\n\z
296-
--chaos damage is taken from mana before life\n\z
297-
--When Hit during effect, 50% of Life loss from Damage taken occurs over 4 seconds instead\n\z
298-
--"
299-
--build.configTab.input.conditionUsingFlask = true
300-
--build.configTab:BuildModList()
301-
--runCallback("OnFrame")
302-
--
303-
--_, takenDamages = takenHitFromTypeMaxHit("Fire")
304-
--poolsRemaining = build.calcsTab.calcs.reducePoolsByDamage(nil, takenDamages, build.calcsTab.calcsEnv.player)
305-
--assert.are.equals(0, poolsRemaining.Life)
306-
--assert.are.equals(0, poolsRemaining.EnergyShield)
307-
--assert.is.not_false(poolsRemaining.Mana > 0)
308-
--
309-
--_, takenDamages = takenHitFromTypeMaxHit("Lightning")
310-
--poolsRemaining = build.calcsTab.calcs.reducePoolsByDamage(nil, takenDamages, build.calcsTab.calcsEnv.player)
311-
--assert.are.equals(0, poolsRemaining.Life)
312-
--assert.are.equals(0, poolsRemaining.EnergyShield)
313-
--assert.are.equals(0, poolsRemaining.Mana)
314-
--
315-
--_, takenDamages = takenHitFromTypeMaxHit("Chaos")
316-
--poolsRemaining = build.calcsTab.calcs.reducePoolsByDamage(nil, takenDamages, build.calcsTab.calcsEnv.player)
317-
--assert.are.equals(0, poolsRemaining.Life)
318-
--assert.are.equals(0, poolsRemaining.EnergyShield)
319-
--assert.are.equals(0, poolsRemaining.Mana)
283+
build.skillsTab:PasteSocketGroup("\z
284+
Petrified Blood 20/0 Default 1\n\z
285+
")
286+
build.skillsTab:ProcessSocketGroup(build.skillsTab.socketGroupList[1])
287+
build.configTab.input.customMods = "\z
288+
+1950 to life\n\z
289+
+2960 to mana\n\z
290+
+3000 to energy shield\n\z
291+
100% less attributes\n\z
292+
100% less mana reserved\n\z
293+
+60% to all resistances\n\z
294+
chaos damage does not bypass energy shield\n\z
295+
mind over matter\n\z
296+
eldritch battery\n\z
297+
10% of lightning damage is taken from mana before life\n\z
298+
chaos damage is taken from mana before life\n\z
299+
When Hit during effect, 50% of Life loss from Damage taken occurs over 4 seconds instead\n\z
300+
25% increased effect of buffs on you\n\z
301+
"
302+
build.configTab.input.conditionUsingFlask = true
303+
build.configTab:BuildModList()
304+
runCallback("OnFrame")
305+
306+
_, takenDamages = takenHitFromTypeMaxHit("Fire")
307+
poolsRemaining = build.calcsTab.calcs.reducePoolsByDamage(nil, takenDamages, build.calcsTab.calcsEnv.player)
308+
assert.are.equals(0, poolsRemaining.Life)
309+
assert.are.equals(0, poolsRemaining.EnergyShield)
310+
assert.is.not_false(poolsRemaining.Mana > 0)
311+
312+
_, takenDamages = takenHitFromTypeMaxHit("Lightning")
313+
poolsRemaining = build.calcsTab.calcs.reducePoolsByDamage(nil, takenDamages, build.calcsTab.calcsEnv.player)
314+
assert.are.equals(0, poolsRemaining.Life)
315+
assert.are.equals(0, poolsRemaining.EnergyShield)
316+
assert.are.equals(0, poolsRemaining.Mana)
317+
318+
_, takenDamages = takenHitFromTypeMaxHit("Chaos")
319+
poolsRemaining = build.calcsTab.calcs.reducePoolsByDamage(nil, takenDamages, build.calcsTab.calcsEnv.player)
320+
assert.are.equals(0, poolsRemaining.Life)
321+
assert.are.equals(0, poolsRemaining.EnergyShield)
322+
assert.are.equals(0, poolsRemaining.Mana)
320323

321324
build.skillsTab.socketGroupList = {}
322325
end)
323326

324327
-- fun part
325328
it("armoured max hits", function()
329+
build.configTab.input.enemyIsBoss = "None"
326330
build.configTab.input.customMods = "\z
327331
+940 to maximum life\n\z
328332
+10000 to armour\n\z
329333
" -- hit of 2000 on 10000 armour results in 50% DR which reduces the damage to 1000 - total HP
330-
build.configTab.input.enemyIsBoss = "None"
331334
build.configTab:BuildModList()
332335
runCallback("OnFrame")
333336
assert.are.equals(1000, takenHitFromTypeMaxHit("Physical"))
@@ -426,6 +429,8 @@ describe("TestDefence", function()
426429
return 0.9 < ratio and ratio < 1.1
427430
end
428431
it("damage conversion max hits", function()
432+
build.configTab.input.enemyIsBoss = "None"
433+
429434
build.configTab.input.customMods = "\z
430435
+940 to maximum life\n\z
431436
+200 to all resistances\n\z

0 commit comments

Comments
 (0)