From 8cc4181c25b3a0f172862c7e92b82b8c58ed405b Mon Sep 17 00:00:00 2001 From: local Date: Fri, 12 Jun 2026 16:10:09 +0900 Subject: [PATCH 1/2] Add network mode option for Kinetic Fusillade effective attack rate Kinetic Fusillade's max effective attack rate differs between network modes: Lockstep rounds the projectile fire delay up to server ticks while Predictive does not. The calculation already derived both values but only used the Lockstep one, and only to scale dpsMultiplier for the "All Projectiles" part, so the sidebar Attack Rate still showed the raw (wasted) attack speed. Changes: - Add a "Network mode" list option (Lockstep/Predictive) to the Configuration tab, shown only when Kinetic Fusillade or Kinetic Fusillade of Detonation is used. Defaults to Lockstep. - Cap output.Speed at the selected mode's max effective rate so the sidebar Attack Rate, DPS and downstream calcs (ailments) reflect it. - Remove the dpsMultiplier efficiency scaling, as capping output.Speed is mathematically equivalent for part 1 (avg * APS * count * cap/APS == avg * min(APS, cap) * count) and would otherwise double-count the penalty. The cap now also applies to the "1 Projectile" part. - Show the selected mode in the Max Effective APS breakdown. --- src/Data/Skills/act_int.lua | 44 +++++++++++++++++++++-------------- src/Modules/ConfigOptions.lua | 6 +++++ 2 files changed, 32 insertions(+), 18 deletions(-) diff --git a/src/Data/Skills/act_int.lua b/src/Data/Skills/act_int.lua index 4fecc8f91ec..bcb93233e21 100644 --- a/src/Data/Skills/act_int.lua +++ b/src/Data/Skills/act_int.lua @@ -11063,8 +11063,17 @@ skills["KineticFusillade"] = { local maxEffectiveAPS = 1 / effectiveDelayRounded local maxEffectivePredictiveAPS = 1 / effectiveDelay local currentAPS = output.Speed + -- Network mode is selected in the Configuration tab (defaults to Lockstep) + local isPredictive = activeSkill.skillModList:Flag(activeSkill.skillCfg, "Condition:KineticFusilladePredictive") + local selectedEffectiveAPS = isPredictive and maxEffectivePredictiveAPS or maxEffectiveAPS - output.KineticFusilladeMaxEffectiveAPS = maxEffectiveAPS + output.KineticFusilladeMaxEffectiveAPS = selectedEffectiveAPS + + -- Cap the attack rate so the sidebar Attack Rate and DPS reflect the effective rate + if currentAPS > selectedEffectiveAPS then + output.Speed = selectedEffectiveAPS + output.Time = 1 / selectedEffectiveAPS + end if breakdown then local breakdownAPS = {} @@ -11107,14 +11116,9 @@ skills["KineticFusillade"] = { 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 + -- The stock dpsMultiplier efficiency scaling is disabled: the rate reduction is + -- handled by capping output.Speed above (network-mode aware), so scaling + -- dpsMultiplier here as well would double-count the penalty. end, statMap = { ["kinetic_fusillade_damage_+%_final_per_projectile_fired"] = { @@ -11281,8 +11285,17 @@ skills["KineticFusilladeAltX"] = { local maxEffectiveAPS = 1 / effectiveDelayRounded local maxEffectivePredictiveAPS = 1 / effectiveDelay local currentAPS = output.Speed + -- Network mode is selected in the Configuration tab (defaults to Lockstep) + local isPredictive = activeSkill.skillModList:Flag(activeSkill.skillCfg, "Condition:KineticFusilladePredictive") + local selectedEffectiveAPS = isPredictive and maxEffectivePredictiveAPS or maxEffectiveAPS - output.KineticFusilladeMaxEffectiveAPS = maxEffectiveAPS + output.KineticFusilladeMaxEffectiveAPS = selectedEffectiveAPS + + -- Cap the attack rate so the sidebar Attack Rate and DPS reflect the effective rate + if currentAPS > selectedEffectiveAPS then + output.Speed = selectedEffectiveAPS + output.Time = 1 / selectedEffectiveAPS + end if breakdown then local breakdownAPS = {} @@ -11325,14 +11338,9 @@ skills["KineticFusilladeAltX"] = { 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 + -- The stock dpsMultiplier efficiency scaling is disabled: the rate reduction is + -- handled by capping output.Speed above (network-mode aware), so scaling + -- dpsMultiplier here as well would double-count the penalty. end, statMap = { ["kinetic_fusillade_damage_+%_final_per_projectile_fired"] = { diff --git a/src/Modules/ConfigOptions.lua b/src/Modules/ConfigOptions.lua index e77099db2db..3cb71ef08e4 100644 --- a/src/Modules/ConfigOptions.lua +++ b/src/Modules/ConfigOptions.lua @@ -501,6 +501,12 @@ return { { var = "OverloadedIntensity", type = "count", label = "# of Overloaded Intensity:", ifSkill = "Overloaded Intensity", apply = function(val, modList, enemyModList) modList:NewMod("Multiplier:OverloadedIntensity", "BASE", m_min(val, 3), "Config") end }, + { label = "Kinetic Fusillade:", ifSkill = { "Kinetic Fusillade", "Kinetic Fusillade of Detonation" } }, + { var = "kineticFusilladeNetworkMode", type = "list", label = "Network mode:", ifSkill = { "Kinetic Fusillade", "Kinetic Fusillade of Detonation" }, tooltip = "Selects the network mode used to calculate the maximum effective attack rate:\n\tLockstep: projectile fire delay is rounded up to server ticks\n\tPredictive: no server tick rounding\nThe summary Attack Rate and DPS are capped at the effective rate.", list = {{val="LOCKSTEP",label="Lockstep"},{val="PREDICTIVE",label="Predictive"}}, apply = function(val, modList, enemyModList) + if val == "PREDICTIVE" then + modList:NewMod("Condition:KineticFusilladePredictive", "FLAG", true, "Config") + end + end }, { label = "Link Skills:", ifSkill = { "Destructive Link", "Flame Link", "Intuitive Link", "Protective Link", "Soul Link", "Vampiric Link" } }, { var = "multiplierLinkedTargets", type = "count", label = "# of linked Targets:", ifSkill = { "Destructive Link", "Flame Link", "Intuitive Link", "Protective Link", "Soul Link", "Vampiric Link" }, apply = function(val, modList, enemyModList) modList:NewMod("Multiplier:LinkedTargets", "BASE", val, "Config") From e11f84c2755788b6623f53d6246bf95e2bd82a9e Mon Sep 17 00:00:00 2001 From: local Date: Fri, 12 Jun 2026 16:16:17 +0900 Subject: [PATCH 2/2] Apply Kinetic Fusillade network mode changes to Export skill definitions src/Data/Skills/act_int.lua is generated from src/Export/Skills/act_int.txt, so the same changes are applied to the export source to keep them in sync. --- src/Export/Skills/act_int.txt | 46 +++++++++++++++++++++-------------- 1 file changed, 28 insertions(+), 18 deletions(-) diff --git a/src/Export/Skills/act_int.txt b/src/Export/Skills/act_int.txt index 4c746132496..19d7d4c1d5c 100644 --- a/src/Export/Skills/act_int.txt +++ b/src/Export/Skills/act_int.txt @@ -2345,11 +2345,21 @@ local skills, mod, flag, skill = ... local maxEffectiveAPS = 1 / effectiveDelayRounded local maxEffectivePredictiveAPS = 1 / effectiveDelay local currentAPS = output.Speed + -- Network mode is selected in the Configuration tab (defaults to Lockstep) + local isPredictive = activeSkill.skillModList:Flag(activeSkill.skillCfg, "Condition:KineticFusilladePredictive") + local selectedEffectiveAPS = isPredictive and maxEffectivePredictiveAPS or maxEffectiveAPS - output.KineticFusilladeMaxEffectiveAPS = maxEffectiveAPS + output.KineticFusilladeMaxEffectiveAPS = selectedEffectiveAPS + + -- Cap the attack rate so the sidebar Attack Rate and DPS reflect the effective rate + if currentAPS > selectedEffectiveAPS then + output.Speed = selectedEffectiveAPS + output.Time = 1 / selectedEffectiveAPS + end if breakdown then local breakdownAPS = {} + t_insert(breakdownAPS, s_format("^8Selected network mode (Configuration tab):^7 %s", isPredictive and "Predictive" or "Lockstep")) t_insert(breakdownAPS, s_format("^8Base hover delay:^7 %.1fs", hoverDelay)) t_insert(breakdownAPS, s_format("^8Base delay between projectiles:^7 %.2fs", baseDelayBetweenProjectiles)) t_insert(breakdownAPS, s_format("^8Base time for^7 %d added ^8projectiles:^7 %.2fs x %d = %.1fs", (projectileCount - 1), baseDelayBetweenProjectiles, (projectileCount - 1), baseTimeForAllProjectiles)) @@ -2389,14 +2399,9 @@ local skills, mod, flag, skill = ... 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 + -- The stock dpsMultiplier efficiency scaling is disabled: the rate reduction is + -- handled by capping output.Speed above (network-mode aware), so scaling + -- dpsMultiplier here as well would double-count the penalty. end, statMap = { ["kinetic_fusillade_damage_+%_final_per_projectile_fired"] = { @@ -2487,11 +2492,21 @@ local skills, mod, flag, skill = ... local maxEffectiveAPS = 1 / effectiveDelayRounded local maxEffectivePredictiveAPS = 1 / effectiveDelay local currentAPS = output.Speed + -- Network mode is selected in the Configuration tab (defaults to Lockstep) + local isPredictive = activeSkill.skillModList:Flag(activeSkill.skillCfg, "Condition:KineticFusilladePredictive") + local selectedEffectiveAPS = isPredictive and maxEffectivePredictiveAPS or maxEffectiveAPS - output.KineticFusilladeMaxEffectiveAPS = maxEffectiveAPS + output.KineticFusilladeMaxEffectiveAPS = selectedEffectiveAPS + + -- Cap the attack rate so the sidebar Attack Rate and DPS reflect the effective rate + if currentAPS > selectedEffectiveAPS then + output.Speed = selectedEffectiveAPS + output.Time = 1 / selectedEffectiveAPS + end if breakdown then local breakdownAPS = {} + t_insert(breakdownAPS, s_format("^8Selected network mode (Configuration tab):^7 %s", isPredictive and "Predictive" or "Lockstep")) t_insert(breakdownAPS, s_format("^8Base hover delay:^7 %.1fs", hoverDelay)) t_insert(breakdownAPS, s_format("^8Base delay between projectiles:^7 %.2fs", baseDelayBetweenProjectiles)) t_insert(breakdownAPS, s_format("^8Base time for^7 %d added ^8projectiles:^7 %.2fs x %d = %.1fs", (projectileCount - 1), baseDelayBetweenProjectiles, (projectileCount - 1), baseTimeForAllProjectiles)) @@ -2531,14 +2546,9 @@ local skills, mod, flag, skill = ... 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 + -- The stock dpsMultiplier efficiency scaling is disabled: the rate reduction is + -- handled by capping output.Speed above (network-mode aware), so scaling + -- dpsMultiplier here as well would double-count the penalty. end, statMap = { ["kinetic_fusillade_damage_+%_final_per_projectile_fired"] = {