From 7fbdfa4b5d52b717e2f83304eb845e1eccb95002 Mon Sep 17 00:00:00 2001 From: Trifall Date: Tue, 4 Aug 2026 20:20:57 -0400 Subject: [PATCH] Fix Ambush modifiers applying to unarmed attacks and channeled attacks --- spec/System/TestSkills_spec.lua | 78 +++++++++++++++++++++++++++++++++ src/Data/Skills/act_dex.lua | 7 +-- src/Export/Skills/act_dex.txt | 5 ++- 3 files changed, 85 insertions(+), 5 deletions(-) diff --git a/spec/System/TestSkills_spec.lua b/spec/System/TestSkills_spec.lua index bbd0e280d7a..f9fc0d5b33b 100644 --- a/spec/System/TestSkills_spec.lua +++ b/spec/System/TestSkills_spec.lua @@ -365,4 +365,82 @@ describe("TestAttacks", function() assert.are.equals(0, nonDurationSkill.skillModList:Sum("BASE", nonDurationSkill.skillCfg, "BaseFlagTest")) assert.are.equals(1, nonDurationSkill.skillModList:Sum("BASE", nonDurationSkill.skillCfg, "NegatedBaseFlagTest")) end) + + + -- 4 Tests for Ambush + -- - unarmed check (Doryani's Fist) + -- - one handed check (double strike) + -- - non-exertable check (Cyclone) + -- - two handed check (rage vortex with weapon swap) + for _, case in ipairs({ + { + description = "does not apply Ambush to the unarmed Doryani's Touch attack", + item = "Doryani's Fist\nVaal Gauntlets\nGrants Level 20 Doryani's Touch Skill", + skill = "Doryani's Touch", + expectedCritChance = 0, + expectedCritMultiplier = 0, + }, + { + description = "applies Ambush to one handed weapon attacks", + item = "New Item\nSabre", + socketGroup = "Slot: Weapon 1\nDouble Strike 20/0 1\n", + skill = "Double Strike", + expectedCritChance = 25, + expectedCritMultiplier = 138, + }, + { + description = "does not apply Ambush to channelling attacks", + item = "New Item\nSabre", + socketGroup = "Slot: Weapon 1\nCyclone 20/0 1\n", + skill = "Cyclone", + expectedCritChance = 0, + expectedCritMultiplier = 0, + }, + { + description = "applies Ambush to two handed Rage Vortex after a modelled weapon swap", + item = "New Item\nCorroded Blade", + socketGroup = "Slot: Weapon 1\nRage Vortex 20/0 1\n", + skill = "Rage Vortex", + expectedCritChance = 25, + expectedCritMultiplier = 138, + }, + }) do + it(case.description, function() + build.itemsTab:CreateDisplayItemFromRaw(case.item) + build.itemsTab:AddDisplayItem() + if case.socketGroup then + build.skillsTab:PasteSocketGroup(case.socketGroup) + end + build.skillsTab:PasteSocketGroup("Ambush 20/0 1\n") + local ambushGem = build.skillsTab.socketGroupList[#build.skillsTab.socketGroupList].gemList[1] + runCallback("OnFrame") + + local skillWithAmbush + for _, skill in ipairs(build.calcsTab.mainEnv.player.activeSkillList) do + if skill.activeEffect.grantedEffect.name == case.skill then + skillWithAmbush = skill + end + end + assert.is_not_nil(skillWithAmbush) + local critChanceWithAmbush = skillWithAmbush.skillModList:Sum("BASE", skillWithAmbush.skillCfg, "CritChance") + local critMultiplierWithAmbush = skillWithAmbush.skillModList:Sum("BASE", skillWithAmbush.skillCfg, "CritMultiplier") + + ambushGem.enableGlobal1 = false + build.buildFlag = true + runCallback("OnFrame") + + local skillWithoutAmbush + for _, skill in ipairs(build.calcsTab.mainEnv.player.activeSkillList) do + if skill.activeEffect.grantedEffect.name == case.skill then + skillWithoutAmbush = skill + end + end + assert.is_not_nil(skillWithoutAmbush) + local critChanceWithoutAmbush = skillWithoutAmbush.skillModList:Sum("BASE", skillWithoutAmbush.skillCfg, "CritChance") + local critMultiplierWithoutAmbush = skillWithoutAmbush.skillModList:Sum("BASE", skillWithoutAmbush.skillCfg, "CritMultiplier") + + assert.are.equals(case.expectedCritChance, critChanceWithAmbush - critChanceWithoutAmbush) + assert.are.equals(case.expectedCritMultiplier, critMultiplierWithAmbush - critMultiplierWithoutAmbush) + end) + end end) diff --git a/src/Data/Skills/act_dex.lua b/src/Data/Skills/act_dex.lua index cee1a3e6d7c..52a20629531 100644 --- a/src/Data/Skills/act_dex.lua +++ b/src/Data/Skills/act_dex.lua @@ -106,12 +106,13 @@ skills["Ambush"] = { castTime = 0.3, statMap = { ["ambush_additional_critical_strike_chance_permyriad"] = { - mod("CritChance", "BASE", nil, ModFlag.Melee, 0, { type = "GlobalEffect", effectType = "Buff", effectName = "Ambush" }), + mod("CritChance", "BASE", nil, bit.bor(ModFlag.Melee, ModFlag.Weapon), 0, { type = "GlobalEffect", effectType = "Buff", effectName = "Ambush" }, { type = "SkillType", skillType = SkillType.NeverExertable, neg = true }, { type = "SkillType", skillType = SkillType.Triggered, neg = true }, { type = "SkillType", skillType = SkillType.Channel, neg = true }, { type = "SkillType", skillType = SkillType.OtherThingUsesSkill, neg = true }), div = 100, }, ["vanishing_ambush_critical_strike_multiplier_+"] = { - mod("CritMultiplier", "BASE", nil, ModFlag.Melee, 0, { type = "GlobalEffect", effectType = "Buff", effectName = "Ambush" }), + mod("CritMultiplier", "BASE", nil, bit.bor(ModFlag.Melee, ModFlag.Weapon), 0, { type = "GlobalEffect", effectType = "Buff", effectName = "Ambush" }, { type = "SkillType", skillType = SkillType.NeverExertable, neg = true }, { type = "SkillType", skillType = SkillType.Triggered, neg = true }, { type = "SkillType", skillType = SkillType.Channel, neg = true }, { type = "SkillType", skillType = SkillType.OtherThingUsesSkill, neg = true }), }, + -- needing a Weapon excludes unarmed attacks, SkillType restrictions mirror attacks that can be exerted -- not excluding Exert for Two-Handed weapons, to simulate a potential weapon swap for skills with a duration ( Rage Vortex ) }, baseFlags = { @@ -18480,4 +18481,4 @@ skills["QuickstepHardMode"] = { [9] = { cooldown = 10, levelRequirement = 1, storedUses = 1, }, [10] = { cooldown = 10, levelRequirement = 1, storedUses = 1, }, }, -} \ No newline at end of file +} diff --git a/src/Export/Skills/act_dex.txt b/src/Export/Skills/act_dex.txt index 7da4a605fa8..fc3509424e7 100644 --- a/src/Export/Skills/act_dex.txt +++ b/src/Export/Skills/act_dex.txt @@ -30,12 +30,13 @@ local skills, mod, flag, skill = ... #flags spell movement duration travel statMap = { ["ambush_additional_critical_strike_chance_permyriad"] = { - mod("CritChance", "BASE", nil, ModFlag.Melee, 0, { type = "GlobalEffect", effectType = "Buff", effectName = "Ambush" }), + mod("CritChance", "BASE", nil, bit.bor(ModFlag.Melee, ModFlag.Weapon), 0, { type = "GlobalEffect", effectType = "Buff", effectName = "Ambush" }, { type = "SkillType", skillType = SkillType.NeverExertable, neg = true }, { type = "SkillType", skillType = SkillType.Triggered, neg = true }, { type = "SkillType", skillType = SkillType.Channel, neg = true }, { type = "SkillType", skillType = SkillType.OtherThingUsesSkill, neg = true }), div = 100, }, ["vanishing_ambush_critical_strike_multiplier_+"] = { - mod("CritMultiplier", "BASE", nil, ModFlag.Melee, 0, { type = "GlobalEffect", effectType = "Buff", effectName = "Ambush" }), + mod("CritMultiplier", "BASE", nil, bit.bor(ModFlag.Melee, ModFlag.Weapon), 0, { type = "GlobalEffect", effectType = "Buff", effectName = "Ambush" }, { type = "SkillType", skillType = SkillType.NeverExertable, neg = true }, { type = "SkillType", skillType = SkillType.Triggered, neg = true }, { type = "SkillType", skillType = SkillType.Channel, neg = true }, { type = "SkillType", skillType = SkillType.OtherThingUsesSkill, neg = true }), }, + -- needing a Weapon excludes unarmed attacks, SkillType restrictions mirror attacks that can be exerted -- not excluding Exert for Two-Handed weapons, to simulate a potential weapon swap for skills with a duration ( Rage Vortex ) }, #mods