Skip to content

Commit e8edfd2

Browse files
Calculate maximum sustainable trauma stacks (#5049)
* auto boneshatter stacks * add support for other mods and fix multistrike * Cleanup and taruma breakdown * cleanup * Apply suggestions * Switch to phys as probably more fitting * improve breakdown and var names * move somewhere a little more approriate * move it * Add initial attack rate cap. This doesn't behave correctly with multistrike. * handle mutlistrike * Add breakdown * Slightly tidy breakdown * make attack speed cap more generic * Fix self damage breakdown as it needs to be called after damage calculations now.
1 parent 627ee57 commit e8edfd2

File tree

5 files changed

+105
-4
lines changed

5 files changed

+105
-4
lines changed

src/Data/Skills/act_str.lua

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1422,7 +1422,7 @@ skills["Boneshatter"] = {
14221422
area = true,
14231423
},
14241424
},
1425-
preDamageFunc = function(activeSkill, output, breakdown)
1425+
preDotFunc = function(activeSkill, output, breakdown)
14261426
local t_insert = table.insert
14271427
local s_format = string.format
14281428
local ipairs = ipairs
@@ -1499,10 +1499,15 @@ skills["Boneshatter"] = {
14991499
},
15001500
["attack_speed_+%_per_trauma"] = {
15011501
mod("Speed", "INC", nil, ModFlag.Attack, 0, { type = "Multiplier", var = "TraumaStacks" }),
1502+
mod("SpeedPerTrauma", "INC", nil, ModFlag.Attack, 0),
15021503
},
15031504
["trauma_strike_self_damage_per_trauma"] = {
15041505
skill("SelfDamageTakenLife", nil),
15051506
},
1507+
["trauma_base_duration_ms"] = {
1508+
skill("duration", nil),
1509+
div = 1000,
1510+
},
15061511
},
15071512
baseFlags = {
15081513
attack = true,

src/Export/Skills/act_str.txt

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -281,7 +281,7 @@ local skills, mod, flag, skill = ...
281281
area = true,
282282
},
283283
},
284-
preDamageFunc = function(activeSkill, output, breakdown)
284+
preDotFunc = function(activeSkill, output, breakdown)
285285
local t_insert = table.insert
286286
local s_format = string.format
287287
local ipairs = ipairs
@@ -358,10 +358,15 @@ local skills, mod, flag, skill = ...
358358
},
359359
["attack_speed_+%_per_trauma"] = {
360360
mod("Speed", "INC", nil, ModFlag.Attack, 0, { type = "Multiplier", var = "TraumaStacks" }),
361+
mod("SpeedPerTrauma", "INC", nil, ModFlag.Attack, 0),
361362
},
362363
["trauma_strike_self_damage_per_trauma"] = {
363364
skill("SelfDamageTakenLife", nil),
364365
},
366+
["trauma_base_duration_ms"] = {
367+
skill("duration", nil),
368+
div = 1000,
369+
},
365370
},
366371
#baseMod skill("radius", 14, { type = "SkillPart", skillPart = 2 })
367372
#mods

src/Modules/CalcOffence.lua

Lines changed: 91 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1493,6 +1493,7 @@ function calcs.offence(env, actor, activeSkill)
14931493
end
14941494

14951495
local storedMainHandAccuracy = nil
1496+
local storedSustainedTraumaBreakdown = { }
14961497
for _, pass in ipairs(passList) do
14971498
globalOutput, globalBreakdown = output, breakdown
14981499
local source, output, cfg, breakdown = pass.source, pass.output, pass.cfg, pass.breakdown
@@ -1599,11 +1600,95 @@ function calcs.offence(env, actor, activeSkill)
15991600
else
16001601
baseTime = skillData.castTimeOverride or activeSkill.activeEffect.grantedEffect.castTime or 1
16011602
end
1602-
local inc = skillModList:Sum("INC", cfg, "Speed")
16031603
local more = skillModList:More(cfg, "Speed")
1604+
output.Repeats = 1 + (skillModList:Sum("BASE", cfg, "RepeatCount") or 0)
1605+
1606+
--Calculates the max number of trauma stacks you can sustain
1607+
if activeSkill.activeEffect.grantedEffect.name == "Boneshatter" then
1608+
local effectiveAttackRateCap = data.misc.ServerTickRate * output.Repeats
1609+
local duration = calcSkillDuration(activeSkill.skillModList, activeSkill.skillCfg, activeSkill.skillData, env, enemyDB)
1610+
local traumaPerAttack = 1 + m_min(skillModList:Sum("BASE", cfg, "ExtraTrauma"), 100) / 100
1611+
local incAttackSpeedPerTrauma = skillModList:Sum("INC", skillCfg, "SpeedPerTrauma")
1612+
-- compute trauma using an exact form.
1613+
local configTrauma = skillModList:Sum("BASE", skillCfg, "Multiplier:TraumaStacks")
1614+
local inc = skillModList:Sum("INC", cfg, "Speed") - incAttackSpeedPerTrauma * configTrauma -- remove trauma attack speed added by config.
1615+
local attackSpeedBeforeInc = 1 / baseTime * globalOutput.ActionSpeedMod * more
1616+
local incAttackSpeedPerTraumaCap = (effectiveAttackRateCap - attackSpeedBeforeInc * (1 + inc / 100)) / attackSpeedBeforeInc * 100
1617+
local traumaRateBeforeInc = traumaPerAttack * (output.HitChance / 100) * attackSpeedBeforeInc / output.Repeats
1618+
local trauma = traumaRateBeforeInc * (1 + inc / 100) / ( 1 / duration - traumaRateBeforeInc * incAttackSpeedPerTrauma / 100 )
1619+
local traumaBreakdown = trauma
1620+
local invalid = false
1621+
if trauma < 0 or incAttackSpeedPerTrauma * trauma > incAttackSpeedPerTraumaCap then -- invalid long term trauma generation as maximum attack rate is once per tick.
1622+
trauma = traumaPerAttack * (output.HitChance / 100) * effectiveAttackRateCap / output.Repeats * duration
1623+
invalid = true
1624+
end
1625+
skillModList:NewMod("Multiplier:SustainableTraumaStacks", "BASE", trauma, "Maximum Sustainable Trauma Stacks")
1626+
if breakdown then
1627+
storedSustainedTraumaBreakdown = { }
1628+
if incAttackSpeedPerTrauma == 0 then
1629+
breakdown.multiChain(storedSustainedTraumaBreakdown, {
1630+
label = "Attack Speed",
1631+
base = s_format("%.2f ^8(base)", 1 / baseTime),
1632+
{ "%.2f ^8(increased/reduced)", 1 + inc/100 },
1633+
{ "%.2f ^8(more/less)", more },
1634+
{ "%.2f ^8(action speed modifier)", globalOutput.ActionSpeedMod },
1635+
total = s_format("= %.2f ^8attacks per second", attackSpeedBeforeInc * (1 + inc/100))
1636+
})
1637+
breakdown.multiChain(storedSustainedTraumaBreakdown, {
1638+
label = "Trauma",
1639+
base = s_format("%.2f ^8(base)", attackSpeedBeforeInc * (1 + inc/100)),
1640+
{ "%.2f ^8(trauma per attack)", traumaPerAttack },
1641+
{ "%.2f ^8(chance to hit)", (output.HitChance / 100) },
1642+
{ "%.2f ^8(duration)", duration }
1643+
})
1644+
if output.Repeats ~= 1 then
1645+
t_insert(storedSustainedTraumaBreakdown, s_format("/ %.2f ^8(repeats)", output.Repeats))
1646+
end
1647+
else
1648+
breakdown.multiChain(storedSustainedTraumaBreakdown, {
1649+
label = "Attack Speed before increased Attack Speed",
1650+
base = s_format("%.2f ^8(base)", 1 / baseTime),
1651+
{ "%.2f ^8(more/less)", more },
1652+
{ "%.2f ^8(action speed modifier)", globalOutput.ActionSpeedMod },
1653+
total = s_format("= %.2f ^8attacks per second", attackSpeedBeforeInc)
1654+
})
1655+
breakdown.multiChain(storedSustainedTraumaBreakdown, {
1656+
label = "Trauma per second before increased Attack Speed",
1657+
base = s_format("%.2f ^8(base)", attackSpeedBeforeInc),
1658+
{ "%.2f ^8(trauma per attack)", traumaPerAttack },
1659+
{ "%.2f ^8(chance to hit)", (output.HitChance / 100) },
1660+
})
1661+
if output.Repeats ~= 1 then
1662+
t_insert(storedSustainedTraumaBreakdown, s_format("/ %.2f ^8(repeats)", output.Repeats))
1663+
end
1664+
t_insert(storedSustainedTraumaBreakdown, s_format("= %.2f ^8trauma per second", traumaRateBeforeInc))
1665+
t_insert(storedSustainedTraumaBreakdown, "Trauma")
1666+
t_insert(storedSustainedTraumaBreakdown, s_format("%.2f ^8(base)", traumaRateBeforeInc))
1667+
t_insert(storedSustainedTraumaBreakdown, s_format("x %.2f ^8(increased/reduced)", (1 + inc / 100)))
1668+
t_insert(storedSustainedTraumaBreakdown, s_format("/ %.4f ^8(1 / duration - trauma per second * increased attack speed per trauma / 100)", ( 1 / duration - traumaRateBeforeInc * incAttackSpeedPerTrauma / 100 )))
1669+
end
1670+
t_insert(storedSustainedTraumaBreakdown, s_format("= "..(invalid and "^1" or "").."%d ^8trauma", traumaBreakdown))
1671+
if invalid then
1672+
t_insert(storedSustainedTraumaBreakdown, "Attack Speed exceeds cap recalculating")
1673+
breakdown.multiChain(storedSustainedTraumaBreakdown, {
1674+
base = s_format("%.2f ^8(base)", effectiveAttackRateCap),
1675+
{ "%.2f ^8(trauma per attack)", traumaPerAttack },
1676+
{ "%.2f ^8(chance to hit)", (output.HitChance / 100) },
1677+
{ "%.2f ^8(duration)", (duration) },
1678+
})
1679+
if output.Repeats ~= 1 then
1680+
t_insert(storedSustainedTraumaBreakdown, s_format("/ %.2f ^8(repeats)", output.Repeats))
1681+
end
1682+
t_insert(storedSustainedTraumaBreakdown, s_format("= %d ^8trauma", trauma))
1683+
end
1684+
end
1685+
end
1686+
if skillModList:Sum("BASE", skillCfg, "Multiplier:TraumaStacks") == 0 then
1687+
skillModList:NewMod("Multiplier:TraumaStacks", "BASE", skillModList:Sum("BASE", skillCfg, "Multiplier:SustainableTraumaStacks"), "Maximum Sustainable Trauma Stacks")
1688+
end
1689+
local inc = skillModList:Sum("INC", cfg, "Speed")
16041690
output.Speed = 1 / baseTime * round((1 + inc/100) * more, 2)
16051691
output.CastRate = output.Speed
1606-
output.Repeats = 1 + (skillModList:Sum("BASE", cfg, "RepeatCount") or 0)
16071692
if skillFlags.selfCast then
16081693
-- Self-cast skill; apply action speed
16091694
output.Speed = output.Speed * globalOutput.ActionSpeedMod
@@ -1673,6 +1758,10 @@ function calcs.offence(env, actor, activeSkill)
16731758
output.HitSpeed = 1 / output.HitTime
16741759
end
16751760
end
1761+
if breakdown then
1762+
breakdown.SustainableTrauma = storedSustainedTraumaBreakdown
1763+
end
1764+
output.SustainableTrauma = activeSkill.activeEffect.grantedEffect.name == "Boneshatter" and skillModList:Sum("BASE", skillCfg, "Multiplier:SustainableTraumaStacks")
16761765

16771766
if isAttack then
16781767
-- Combine hit chance and attack speed

src/Modules/CalcSections.lua

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -609,6 +609,7 @@ return {
609609
{ label = "Secondary Duration", flag = "duration", haveOutput = "DurationSecondary", { format = "{3:output:DurationSecondary}s", { breakdown = "DurationSecondary" }, }, },
610610
{ label = "Aura Duration", haveOutput = "AuraDuration", { format = "{3:output:AuraDuration}s", { breakdown = "AuraDuration" }, }, },
611611
{ label = "Reserve Duration", haveOutput = "ReserveDuration", { format = "{3:output:ReserveDuration}s", { breakdown = "ReserveDuration" }, }, },
612+
{ label = "Sustainable Trauma", haveOutput = "SustainableTrauma", { format = "{0:output:SustainableTrauma}", { breakdown = "SustainableTrauma" }, { modName = { "SpeedPerTrauma", "ExtraTrauma", "RepeatCount", "Duration", "PrimaryDuration", "SecondaryDuration", "SkillAndDamagingAilmentDuration"}, cfg = "skill" }, }, },
612613
{ label = "Projectile Count", flag = "projectile", { format = "{output:ProjectileCount}", { modName = { "NoAdditionalProjectiles" , "ProjectileCount" }, cfg = "skill" }, }, },
613614
{ label = "Pierce Count", haveOutput = "PierceCount", { format = "{output:PierceCountString}", { modName = { "CannotPierce", "PierceCount", "PierceAllTargets" }, cfg = "skill" }, }, },
614615
{ label = "Fork Count", haveOutput = "ForkCountMax", { format = "{output:ForkCountString}", { modName = { "CannotFork", "ForkCountMax" }, cfg = "skill" }, }, },

src/Modules/ModParser.lua

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3402,6 +3402,7 @@ local specialModList = {
34023402
["added small passive skills have (%d+)%% increased effect"] = function(num) return { mod("JewelData", "LIST", { key = "clusterJewelIncEffect", value = num }) } end,
34033403
["this jewel's socket has (%d+)%% increased effect per allocated passive skill between it and your class' starting location"] = function(num) return { mod("JewelData", "LIST", { key = "jewelIncEffectFromClassStart", value = num }) } end,
34043404
-- Misc
3405+
["boneshatter has (%d+)%% chance to grant %+1 trauma"] = function(num) return { mod("ExtraTrauma", "BASE", num, { type = "SkillName", skillName = "Boneshatter"}) } end,
34053406
["your minimum frenzy, endurance and power charges are equal to your maximum while you are stationary"] = {
34063407
flag("MinimumFrenzyChargesIsMaximumFrenzyCharges", {type = "Condition", var = "Stationary"}),
34073408
flag("MinimumEnduranceChargesIsMaximumEnduranceCharges", {type = "Condition", var = "Stationary"}),

0 commit comments

Comments
 (0)