Skip to content

Commit 4b3111a

Browse files
committed
Add support for Kinetic Fusillade
Introduce support for Kinetic Fusillade's sequential fire damage mod. Add support for DPS calculations considering all available projectiles while factoring in duration and attack rate for effective dps calcs. Signed-off-by: Justin Stitt <[email protected]>
1 parent 58aef43 commit 4b3111a

File tree

5 files changed

+120
-1
lines changed

5 files changed

+120
-1
lines changed

src/Data/Skills/act_int.lua

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11142,6 +11142,48 @@ skills["KineticFusillade"] = {
1114211142
},
1114311143
statDescriptionScope = "skill_stat_descriptions",
1114411144
castTime = 1,
11145+
parts = {
11146+
{
11147+
name = "1 Projectile"
11148+
},
11149+
{
11150+
name = "All Projectiles",
11151+
},
11152+
},
11153+
preDamageFunc = function(activeSkill, output)
11154+
if activeSkill.skillPart == 2 then
11155+
activeSkill.skillData.dpsMultiplier = output.ProjectileCount
11156+
11157+
-- Calculate average damage scaling for sequential projectiles
11158+
-- Each projectile does more damage based on how many came before it
11159+
local moreDamagePerProj = activeSkill.skillModList:Sum("MORE", activeSkill.skillCfg, "KineticFusilladeSequentialDamage") or 0
11160+
if moreDamagePerProj ~= 0 and output.ProjectileCount > 1 then
11161+
-- Average multiplier: sum of (0, X, 2X, 3X, ..., (n-1)X) / n
11162+
-- This equals: X * (0 + 1 + 2 + ... + (n-1)) / n = X * n(n-1)/2 / n = X * (n-1)/2
11163+
local avgMoreMult = moreDamagePerProj * (output.ProjectileCount - 1) / 2
11164+
activeSkill.skillModList:NewMod("Damage", "MORE", avgMoreMult, "Skill:KineticFusillade")
11165+
end
11166+
end
11167+
11168+
-- Calculate effective attack rate accounting for delayed projectile firing
11169+
-- Projectiles orbit for base_skill_effect_duration before firing
11170+
-- Recasting resets the timer, so maximum effective rate is 1 / (duration rounded to server ticks)
11171+
local baseDuration = activeSkill.skillData.duration or 0.7
11172+
local actualDuration = output.Duration or baseDuration
11173+
local ticksNeeded = math.ceil(actualDuration / data.misc.ServerTickTime)
11174+
local effectiveDelay = ticksNeeded * data.misc.ServerTickTime
11175+
local maxEffectiveAPS = 1 / effectiveDelay
11176+
11177+
-- Store the maximum effective attack rate for display
11178+
output.KineticFusilladeMaxEffectiveAttackRate = maxEffectiveAPS
11179+
end,
11180+
statMap = {
11181+
["kinetic_fusillade_damage_+%_final_per_projectile_fired"] = {
11182+
mod("KineticFusilladeSequentialDamage", "MORE", nil),
11183+
},
11184+
["quality_display_kinetic_fusillade_is_gem"] = {
11185+
},
11186+
},
1114511187
baseFlags = {
1114611188
attack = true,
1114711189
projectile = true,

src/Export/Skills/act_int.txt

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2265,6 +2265,48 @@ local skills, mod, flag, skill = ...
22652265

22662266
#skill KineticFusillade
22672267
#flags attack projectile area duration
2268+
parts = {
2269+
{
2270+
name = "1 Projectile"
2271+
},
2272+
{
2273+
name = "All Projectiles",
2274+
},
2275+
},
2276+
preDamageFunc = function(activeSkill, output)
2277+
if activeSkill.skillPart == 2 then
2278+
activeSkill.skillData.dpsMultiplier = output.ProjectileCount
2279+
2280+
-- Calculate average damage scaling for sequential projectiles
2281+
-- Each projectile does more damage based on how many came before it
2282+
local moreDamagePerProj = activeSkill.skillModList:Sum("MORE", activeSkill.skillCfg, "KineticFusilladeSequentialDamage") or 0
2283+
if moreDamagePerProj ~= 0 and output.ProjectileCount > 1 then
2284+
-- Average multiplier: sum of (0, X, 2X, 3X, ..., (n-1)X) / n
2285+
-- This equals: X * (0 + 1 + 2 + ... + (n-1)) / n = X * n(n-1)/2 / n = X * (n-1)/2
2286+
local avgMoreMult = moreDamagePerProj * (output.ProjectileCount - 1) / 2
2287+
activeSkill.skillModList:NewMod("Damage", "MORE", avgMoreMult, "Skill:KineticFusillade")
2288+
end
2289+
end
2290+
2291+
-- Calculate effective attack rate accounting for delayed projectile firing
2292+
-- Projectiles orbit for base_skill_effect_duration before firing
2293+
-- Recasting resets the timer, so maximum effective rate is 1 / (duration rounded to server ticks)
2294+
local baseDuration = activeSkill.skillData.duration or 0.7
2295+
local actualDuration = output.Duration or baseDuration
2296+
local ticksNeeded = math.ceil(actualDuration / data.misc.ServerTickTime)
2297+
local effectiveDelay = ticksNeeded * data.misc.ServerTickTime
2298+
local maxEffectiveAPS = 1 / effectiveDelay
2299+
2300+
-- Store the maximum effective attack rate for display
2301+
output.KineticFusilladeMaxEffectiveAttackRate = maxEffectiveAPS
2302+
end,
2303+
statMap = {
2304+
["kinetic_fusillade_damage_+%_final_per_projectile_fired"] = {
2305+
mod("KineticFusilladeSequentialDamage", "MORE", nil),
2306+
},
2307+
["quality_display_kinetic_fusillade_is_gem"] = {
2308+
},
2309+
},
22682310
#mods
22692311

22702312
#skill KineticRain

src/Modules/BuildDisplayStats.lua

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,9 @@ local displayStats = {
6161
{ stat = "WithImpaleDPS", label = "Total DPS inc. Impale", fmt = ".1f", compPercent = true, flag = "impale", flag = "notAverage", condFunc = function(v,o) return v ~= o.TotalDPS and (o.TotalDot or 0) == 0 and (o.IgniteDPS or 0) == 0 and (o.PoisonDPS or 0) == 0 and (o.BleedDPS or 0) == 0 end },
6262
{ stat = "MirageDPS", label = "Total Mirage DPS", fmt = ".1f", compPercent = true, flag = "mirageArcher", condFunc = function(v,o) return v > 0 end },
6363
{ stat = "MirageDPS", label = "Total Wisp DPS", fmt = ".1f", compPercent = true, flag = "wisp", condFunc = function(v,o) return v > 0 end },
64+
{ stat = "KineticFusilladeMaxEffectiveAttackRate", label = "Max Effective Attack Rate", fmt = ".2f", condFunc = function(v,o) return (o.KineticFusilladeMaxEffectiveAttackRate or 0) > 0 end },
65+
{ stat = "KineticFusilladeEffectiveDPS", label = "Effective DPS", fmt = ".1f", compPercent = true, condFunc = function(v,o) return (o.KineticFusilladeEffectiveDPS or 0) > 0 end },
66+
{ stat = "KineticFusilladeWastedDPS", label = "Wasted DPS", fmt = ".1f", condFunc = function(v,o) return (o.KineticFusilladeWastedDPS or 0) > 0 end },
6467
{ stat = "CullingDPS", label = "Culling DPS", fmt = ".1f", compPercent = true, condFunc = function(v,o) return (o.CullingDPS or 0) > 0 end },
6568
{ stat = "ReservationDPS", label = "Reservation DPS", fmt = ".1f", compPercent = true, condFunc = function(v,o) return (o.ReservationDPS or 0) > 0 end },
6669
{ stat = "CombinedDPS", label = "Combined DPS", fmt = ".1f", compPercent = true, flag = "notAverage", condFunc = function(v,o) return v ~= ((o.TotalDPS or 0) + (o.TotalDot or 0)) and v ~= o.WithImpaleDPS and ( o.showTotalDotDPS or ( v ~= o.WithPoisonDPS and v ~= o.WithIgniteDPS and v ~= o.WithBleedDPS ) ) end },
@@ -245,4 +248,4 @@ local extraSaveStats = {
245248
"ActiveMinionLimit",
246249
}
247250

248-
return displayStats, minionDisplayStats, extraSaveStats
251+
return displayStats, minionDisplayStats, extraSaveStats

src/Modules/CalcOffence.lua

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5813,4 +5813,33 @@ function calcs.offence(env, actor, activeSkill)
58135813
output.CullingDPS = output.CombinedDPS * (bestCull - 1)
58145814
output.ReservationDPS = output.CombinedDPS * (output.ReservationDpsMultiplier - 1)
58155815
output.CombinedDPS = output.CombinedDPS * bestCull * output.ReservationDpsMultiplier
5816+
5817+
-- Kinetic Fusillade: Calculate effective DPS accounting for projectile firing delay
5818+
if output.KineticFusilladeMaxEffectiveAttackRate then
5819+
local maxEffectiveAPS = output.KineticFusilladeMaxEffectiveAttackRate
5820+
if output.Speed and output.Speed > maxEffectiveAPS then
5821+
-- Attacking faster than the projectiles can fire wasts some DPS
5822+
local efficiencyRatio = maxEffectiveAPS / output.Speed
5823+
output.KineticFusilladeEffectiveDPS = output.TotalDPS * efficiencyRatio
5824+
output.KineticFusilladeWastedDPS = output.TotalDPS - output.KineticFusilladeEffectiveDPS
5825+
else
5826+
-- attack rate is within effective limits
5827+
output.KineticFusilladeEffectiveDPS = output.TotalDPS
5828+
output.KineticFusilladeWastedDPS = 0
5829+
end
5830+
if breakdown then
5831+
breakdown.KineticFusilladeEffectiveDPS = {}
5832+
if output.Speed then
5833+
t_insert(breakdown.KineticFusilladeEffectiveDPS, s_format("%.2f ^8(current attack rate)", output.Speed))
5834+
end
5835+
t_insert(breakdown.KineticFusilladeEffectiveDPS, s_format("%.2f ^8(max effective attack rate)", maxEffectiveAPS))
5836+
if output.Speed and output.Speed > maxEffectiveAPS then
5837+
t_insert(breakdown.KineticFusilladeEffectiveDPS, s_format("%.2f ^8(efficiency: max/current)", maxEffectiveAPS / output.Speed))
5838+
t_insert(breakdown.KineticFusilladeEffectiveDPS, s_format("%.1f ^8(total DPS)", output.TotalDPS))
5839+
t_insert(breakdown.KineticFusilladeEffectiveDPS, s_format("= %.1f ^8(effective DPS)", output.KineticFusilladeEffectiveDPS))
5840+
else
5841+
t_insert(breakdown.KineticFusilladeEffectiveDPS, "^8Attack rate is within effective limits")
5842+
end
5843+
end
5844+
end
58165845
end

src/Modules/CalcSections.lua

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -345,6 +345,9 @@ return {
345345
{ label = "DPS Multiplier", modName = "DPS" },
346346
}, },
347347
{ label = "Skill DPS", flag = "triggered", { format = "{1:output:TotalDPS}", { breakdown = "TotalDPS" }, { label = "DPS Multiplier", modName = "DPS" }, }, },
348+
{ label = "Max Effective APS", haveOutput = "KineticFusilladeMaxEffectiveAttackRate", { format = "{2:output:KineticFusilladeMaxEffectiveAttackRate}", }, },
349+
{ label = "Effective DPS", haveOutput = "KineticFusilladeEffectiveDPS", { format = "{1:output:KineticFusilladeEffectiveDPS}", { breakdown = "KineticFusilladeEffectiveDPS" }, }, },
350+
{ label = "Wasted DPS", haveOutput = "KineticFusilladeWastedDPS", { format = "{1:output:KineticFusilladeWastedDPS}", }, },
348351
} }
349352
} },
350353
{ 3, "Warcries", 1, colorCodes.OFFENCE, {{ defaultCollapsed = false, label = "Exerting Warcries", data = {

0 commit comments

Comments
 (0)