From 22c09b12e5c079963fdc71186b396e640fa43f07 Mon Sep 17 00:00:00 2001 From: AdamZ Date: Sun, 6 Sep 2026 13:19:19 -0700 Subject: [PATCH] Support Follow Through cluster jewel notable --- spec/System/TestSkills_spec.lua | 30 ++++++++++++++++++++++++++++++ src/Data/ModCache.lua | 3 +-- src/Modules/ModParser.lua | 6 ++++++ 3 files changed, 37 insertions(+), 2 deletions(-) diff --git a/spec/System/TestSkills_spec.lua b/spec/System/TestSkills_spec.lua index 8075f75884c..1375a5590c2 100644 --- a/spec/System/TestSkills_spec.lua +++ b/spec/System/TestSkills_spec.lua @@ -328,6 +328,36 @@ describe("TestSkills", function() assert.are.near(singleBrandDPS * 2, build.calcsTab.mainOutput.TotalDot, 10 ^ -9) end) + it("applies Follow-Through to projectile hits and ailments with a shared cap", function() + build.skillsTab:PasteSocketGroup("Ethereal Knives 20/0 1\n") + local modText = "Projectiles deal %d%% increased Damage with Hits and Ailments for each remaining Chain%s" + for _, case in ipairs({ + { suffix = ", up to a maximum of 100%", value = 20, chains = 3, used = 0, copies = 1, expected = 60 }, + { suffix = ", up to a maximum of 100%", value = 20, chains = 3, used = 0, copies = 2, expected = 100 }, + { suffix = ", up to a maximum of 100%", value = 20, chains = 6, used = 0, copies = 1, expected = 100 }, + { suffix = ", up to a maximum of 100%", value = 20, chains = 3, used = 2, copies = 1, expected = 20 }, + { suffix = ", up to a maximum of 100%", value = 20, chains = 3, used = 3, copies = 1, expected = 0 }, + { suffix = ", up to 100%", value = 20, chains = 3, used = 0, copies = 2, expected = 100 }, + { suffix = "", value = 15, chains = 6, used = 0, copies = 2, expected = 180 }, + }) do + local baseMods = "100% chance to Poison on Hit\nSkills Chain +" .. case.chains .. " times\n" + build.configTab.input.skillChainCount = case.used + build.configTab.input.customMods = baseMods .. case.expected .. "% increased Damage with Hits and Ailments" + build.configTab:BuildModList() + runCallback("OnFrame") + local expectedHit = build.calcsTab.mainOutput.AverageHit + local expectedPoison = build.calcsTab.mainOutput.PoisonDPS + assert.is_true(expectedHit > 0 and expectedPoison > 0) + + build.configTab.input.customMods = baseMods .. (modText:format(case.value, case.suffix) .. "\n"):rep(case.copies) + build.configTab:BuildModList() + runCallback("OnFrame") + assert.are.equals(case.chains - case.used, build.calcsTab.mainOutput.ChainRemaining) + assert.are.near(expectedHit, build.calcsTab.mainOutput.AverageHit, 10 ^ -9) + assert.are.near(expectedPoison, build.calcsTab.mainOutput.PoisonDPS, 10 ^ -9) + end + end) + it("caps total Brand Recall Cooldown Recovery from multiple Chip Away notables at 40%", function() build.skillsTab:PasteSocketGroup("Storm Brand 20/0 1\n") build.configTab.input.ActiveBrands = 10 diff --git a/src/Data/ModCache.lua b/src/Data/ModCache.lua index c975e8c33ab..9dcbbc37d4d 100755 --- a/src/Data/ModCache.lua +++ b/src/Data/ModCache.lua @@ -18716,7 +18716,7 @@ c["Projectiles created by this Graft that have Pierced deal 25% more Damage"]={n c["Projectiles deal 18% increased Damage with Hits and Ailments for each Enemy Pierced"]={{[1]={[1]={stat="PiercedCount",type="PerStat"},[2]={skillType=3,type="SkillType"},flags=0,keywordFlags=786432,name="Damage",type="INC",value=18}},nil} c["Projectiles deal 20% increased Damage with Hits and Ailments for"]={{[1]={flags=1024,keywordFlags=786432,name="Damage",type="INC",value=20}}," for "} c["Projectiles deal 20% increased Damage with Hits and Ailments for each Enemy Pierced"]={{[1]={[1]={stat="PiercedCount",type="PerStat"},[2]={skillType=3,type="SkillType"},flags=0,keywordFlags=786432,name="Damage",type="INC",value=20}},nil} -c["Projectiles deal 20% increased Damage with Hits and Ailments for each remaining Chain, up to a maximum of 100%"]={{[1]={[1]={stat="ChainRemaining",type="PerStat"},flags=1024,keywordFlags=786432,name="Damage",type="INC",value=20}}," , up to a maximum of 100% "} +c["Projectiles deal 20% increased Damage with Hits and Ailments for each remaining Chain, up to a maximum of 100%"]={{[1]={[1]={globalLimit=100,globalLimitKey="FollowThrough",stat="ChainRemaining",type="PerStat"},[2]={skillType=3,type="SkillType"},flags=0,keywordFlags=786432,name="Damage",type="INC",value=20}},nil} c["Projectiles deal 20% increased Damage with Hits and Ailments for each time they have Chained"]={{[1]={[1]={stat="Chain",type="PerStat"},[2]={skillType=3,type="SkillType"},flags=0,keywordFlags=786432,name="Damage",type="INC",value=20}},nil} c["Projectiles deal 23% increased Damage with Hits and Ailments for each Enemy Pierced"]={{[1]={[1]={stat="PiercedCount",type="PerStat"},[2]={skillType=3,type="SkillType"},flags=0,keywordFlags=786432,name="Damage",type="INC",value=23}},nil} c["Projectiles deal 23% increased Damage with Hits and Ailments for each time they have Chained"]={{[1]={[1]={stat="Chain",type="PerStat"},[2]={skillType=3,type="SkillType"},flags=0,keywordFlags=786432,name="Damage",type="INC",value=23}},nil} @@ -23172,7 +23172,6 @@ c["be inflicted on a nearby Minion instead"]={nil,"be inflicted on a nearby Mini c["be inflicted on a nearby Minion instead Limited to 1 Runegraft of Loyalty"]={nil,"be inflicted on a nearby Minion instead Limited to 1 Runegraft of Loyalty "} c["each Herald affecting you, up to a maximum of 40%"]={nil,"each Herald affecting you, up to a maximum of 40% "} c["each Unique item they have equipped"]={nil,"each Unique item they have equipped "} -c["each remaining Chain, up to a maximum of 100%"]={nil,"each remaining Chain, up to a maximum of 100% "} c["grant 100% increased Flask Charges"]={nil,"grant 100% increased Flask Charges "} c["has a 20% increased angle"]={nil,"has a 20% increased angle "} c["has a 20% increased angle With at least 40 Strength in Radius, Ground Slam"]={nil,"has a 20% increased angle With at least 40 Strength in Radius, Ground Slam "} diff --git a/src/Modules/ModParser.lua b/src/Modules/ModParser.lua index bc1f7b97366..27bfc43f358 100644 --- a/src/Modules/ModParser.lua +++ b/src/Modules/ModParser.lua @@ -2649,6 +2649,12 @@ local specialModList = { ["you and nearby allies have tailwind"] = { mod("ExtraAura", "LIST", { mod = flag("Condition:Tailwind") }) }, ["projectiles deal (%d+)%% more damage for each remaining chain"] = function(num) return { mod("Damage", "MORE", num, nil, ModFlag.Projectile, { type = "PerStat", stat = "ChainRemaining" }) } end, ["projectiles deal (%d+)%% increased damage with hits and ailments for each remaining chain"] = function(num) return { mod("Damage", "INC", num, nil, 0, bor(KeywordFlag.Hit, KeywordFlag.Ailment), { type = "PerStat", stat = "ChainRemaining" }, { type = "SkillType", skillType = SkillType.Projectile }) } end, + ["projectiles deal (%d+)%% increased damage with hits and ailments for each remaining chain, up to a maximum of (%d+)%%"] = function(num, _, limit) return { + mod("Damage", "INC", num, nil, 0, bor(KeywordFlag.Hit, KeywordFlag.Ailment), { type = "PerStat", stat = "ChainRemaining", globalLimit = tonumber(limit), globalLimitKey = "FollowThrough" }, { type = "SkillType", skillType = SkillType.Projectile }) + } end, + ["projectiles deal (%d+)%% increased damage with hits and ailments for each remaining chain, up to (%d+)%%"] = function(num, _, limit) return { + mod("Damage", "INC", num, nil, 0, bor(KeywordFlag.Hit, KeywordFlag.Ailment), { type = "PerStat", stat = "ChainRemaining", globalLimit = tonumber(limit), globalLimitKey = "FollowThrough" }, { type = "SkillType", skillType = SkillType.Projectile }) + } end, ["projectiles deal (%d+)%% increased damage for each remaining chain"] = function(num) return { mod("Damage", "INC", num, nil, ModFlag.Projectile, { type = "PerStat", stat = "ChainRemaining" }) } end, ["far shot"] = { flag("FarShot") }, ["projectiles gain damage as they travel farther, dealing up to (%d+)%% more damage with hits and ailments"] = function(num) return { mod("Damage", "MORE", num, nil, 0, bor(KeywordFlag.Hit, KeywordFlag.Ailment), { type = "DistanceRamp", ramp = { {35,0},{70,1} } }) } end,