Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
105 changes: 105 additions & 0 deletions src/Export/Skills/act_int.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2265,6 +2265,111 @@ local skills, mod, flag, skill = ...

#skill KineticFusillade
#flags attack projectile area duration
parts = {
{
name = "All Projectiles",
},
{
name = "1 Projectile"
},
},
preDamageFunc = function(activeSkill, output, breakdown)
local skillData = activeSkill.skillData
local t_insert = table.insert
local s_format = string.format

if activeSkill.skillPart == 1 then
-- Set base dpsMultiplier for projectile count
activeSkill.skillData.dpsMultiplier = output.ProjectileCount

-- Calculate average damage scaling for sequential projectiles
-- Each projectile does more damage based on how many came before it
local moreDamagePerProj = skillData.KineticFusilladeSequentialDamage or 0
if moreDamagePerProj ~= 0 and output.ProjectileCount > 1 then
-- Average multiplier: sum of (0, X, 2X, 3X, ..., (n-1)X) / n
-- This equals: X * (0 + 1 + 2 + ... + (n-1)) / n = X * n(n-1)/2 / n = X * (n-1)/2
local avgMoreMult = moreDamagePerProj * (output.ProjectileCount - 1) / 2
activeSkill.skillModList:NewMod("Damage", "MORE", avgMoreMult, "Skill:KineticFusillade", ModFlag.Hit)

-- Store the average multiplier for display
output.KineticFusilladeAvgMoreMult = avgMoreMult

if breakdown then
local breakdownSequential = {}
t_insert(breakdownSequential, s_format("^8Each projectile deals^7 %d%%^8 more damage per previous projectile", moreDamagePerProj))
t_insert(breakdownSequential, s_format("^8With^7 %d^8 projectiles, damage progression is:^7", output.ProjectileCount))
for i = 1, output.ProjectileCount do
local projMult = moreDamagePerProj * (i - 1)
t_insert(breakdownSequential, s_format(" ^8Projectile %d:^7 %d%%^8 more damage", i, projMult))
end
t_insert(breakdownSequential, s_format("^8Average more multiplier:^7 %.1f%%", avgMoreMult))
breakdown.KineticFusilladeSequentialBreakdown = breakdownSequential
end
end
end
end,
postCritFunc = function(activeSkill, output, breakdown)
local skillData = activeSkill.skillData
local t_insert = table.insert
local s_format = string.format

local baseDelayBetweenProjectiles = 0.05
local projectileCount = 1

if activeSkill.skillPart == 1 then
projectileCount = output.ProjectileCount
end

-- Calculate effective attack rate accounting for delayed projectile firing
-- Projectiles orbit for base_skill_effect_duration before firing
-- Recasting resets the timer, so attacking too fast wastes potential damage
local baseDuration = skillData.duration
local actualDuration = output.Duration or baseDuration
local ticksNeededForInitialDelay = math.ceil(actualDuration / data.misc.ServerTickTime)
local timePerProjectile = baseDelayBetweenProjectiles * output.DurationMod
local timeForAllProjectiles = timePerProjectile * projectileCount
local effectiveDelay = ticksNeededForInitialDelay * data.misc.ServerTickTime + math.ceil(timeForAllProjectiles / data.misc.ServerTickTime) * data.misc.ServerTickTime
local maxEffectiveAPS = 1 / effectiveDelay
local currentAPS = output.Speed

output.KineticFusilladeMaxEffectiveAPS = maxEffectiveAPS

if breakdown then
local breakdownAPS = {}
t_insert(breakdownAPS, s_format("^1(These calculations are speculative and best-effort)", actualDuration))
t_insert(breakdownAPS, s_format("^8Delay of^7 %.3fs ^8before projectiles start firing", actualDuration))
t_insert(breakdownAPS, s_format("^8Each projectile fires sequentially with a^7 %.3fs ^8delay between each projectile", timePerProjectile))
t_insert(breakdownAPS, s_format("^8Server tick time:^7 %.3fs", data.misc.ServerTickTime))
t_insert(breakdownAPS, s_format("^8Ticks needed:^7 %d ^8(rounded up)", ticksNeededForInitialDelay + math.ceil(timeForAllProjectiles / data.misc.ServerTickTime)))
t_insert(breakdownAPS, s_format("^8Effective delay:^7 %.3fs", effectiveDelay))
t_insert(breakdownAPS, s_format("^8Max effective attack rate:^7 1 / %.3f = %.2f", effectiveDelay, maxEffectiveAPS))
if currentAPS and currentAPS > maxEffectiveAPS then
t_insert(breakdownAPS, "")
t_insert(breakdownAPS, s_format("^1Current attack rate (%.2f) exceeds max effective rate!", currentAPS))
t_insert(breakdownAPS, s_format("^1DPS is reduced by %.1f%%", (1 - maxEffectiveAPS / currentAPS) * 100))
elseif currentAPS then
t_insert(breakdownAPS, "")
t_insert(breakdownAPS, s_format("^2Current attack rate (%.2f) is within effective limits", currentAPS))
end
breakdown.KineticFusilladeMaxEffectiveAPS = breakdownAPS
end

-- Adjust dpsMultiplier if attacking too fast (only for "All Projectiles" mode)
if activeSkill.skillPart == 1 then
if currentAPS and currentAPS > maxEffectiveAPS then
local efficiencyRatio = maxEffectiveAPS / currentAPS
local originalMultiplier = skillData.dpsMultiplier or output.ProjectileCount
skillData.dpsMultiplier = originalMultiplier * efficiencyRatio
end
end
end,
statMap = {
["kinetic_fusillade_damage_+%_final_per_projectile_fired"] = {
skill("KineticFusilladeSequentialDamage", nil),
},
["quality_display_kinetic_fusillade_is_gem"] = {
},
},
#mods

#skill KineticRain
Expand Down
2 changes: 2 additions & 0 deletions src/Modules/CalcSections.lua
Original file line number Diff line number Diff line change
Expand Up @@ -737,6 +737,8 @@ return {
{ label = "Normal Hits/Cast", haveOutput = "NormalHitsPerCast", { format = "{3:output:NormalHitsPerCast}", { breakdown = "NormalHitsPerCast" }, }, },
{ label = "Super Hits/Cast", haveOutput = "SuperchargedHitsPerCast", { format = "{3:output:SuperchargedHitsPerCast}", { breakdown = "SuperchargedHitsPerCast" }, }, },
{ label = "DPS Multiplier", haveOutput = "SkillDPSMultiplier", { format = "{3:output:SkillDPSMultiplier}", { breakdown = "SkillDPSMultiplier" }, }, },
{ label = "Average Seq More", haveOutput = "KineticFusilladeAvgMoreMult", { format = "{1:output:KineticFusilladeAvgMoreMult}%", { breakdown = "KineticFusilladeSequentialBreakdown" }, }, },
{ label = "Max Effective APS", haveOutput = "KineticFusilladeMaxEffectiveAPS", { format = "{2:output:KineticFusilladeMaxEffectiveAPS}", { breakdown = "KineticFusilladeMaxEffectiveAPS" }, }, },
-- Traps
{ label = "Avg. Active Traps", haveOutput = "AverageActiveTraps", { format = "{2:output:AverageActiveTraps}", { breakdown = "AverageActiveTraps" }, }, },
{ label = "Active Trap Limit", flag = "trap", { format = "{0:output:ActiveTrapLimit}", { modName = "ActiveTrapLimit", cfg = "skill" }, }, },
Expand Down