diff --git a/spec/System/TestSkills_spec.lua b/spec/System/TestSkills_spec.lua index 9c756768e..88766f448 100644 --- a/spec/System/TestSkills_spec.lua +++ b/spec/System/TestSkills_spec.lua @@ -426,4 +426,127 @@ describe("TestSkills", function() runCallback("OnFrame") assert.True(build.calcsTab.mainOutput.TotalDPS > iceShotDPS) end) + + describe("Combo stacking", function() + local CHAKRA_MOD = "Skills deal 8% increased Damage per Combo consumed, up to 40%" + + local function equipQuarterstaff() + build.itemsTab:CreateDisplayItemFromRaw([[ + New Item + Razor Quarterstaff + Quality: 0 + ]]) + build.itemsTab:AddDisplayItem() + runCallback("OnFrame") + end + + local function applyComboConfig(stacks, customMods) + build.configTab.input.multiplierCombo = stacks + build.configTab.input.customMods = customMods or "" + build.configTab:BuildModList() + runCallback("OnFrame") + build.calcsTab:BuildOutput() + runCallback("OnFrame") + end + + local function findSkillIndex(skillName) + for index, skill in ipairs(build.calcsTab.mainEnv.player.activeSkillList) do + if skill.activeEffect.grantedEffect.name == skillName then + return index + end + end + error("Skill not found: " .. skillName) + end + + local function getSkillIncDamage(skillIndex) + local skill = build.calcsTab.mainEnv.player.activeSkillList[skillIndex] + return skill.skillModList:Sum("INC", skill.skillCfg, "Damage") + end + + local function getSkillMoreDamage(skillIndex) + local skill = build.calcsTab.mainEnv.player.activeSkillList[skillIndex] + return skill.skillModList:Sum("MORE", skill.skillCfg, "Damage") + end + + local function getAverageHit() + return build.calcsTab.mainOutput.AverageHit or 0 + end + + it("does not apply damage per combo consumed to non-ComboStacking skills", function() + equipQuarterstaff() + build.skillsTab:PasteSocketGroup("Ball Lightning 20/0 1\n") + build.skillsTab:PasteSocketGroup("Tempest Bell 20/0 1\n") + runCallback("OnFrame") + + applyComboConfig(10, "") + local ballIncBase = getSkillIncDamage(findSkillIndex("Ball Lightning")) + local tempestIncBase = getSkillIncDamage(findSkillIndex("Tempest Bell")) + + applyComboConfig(10, CHAKRA_MOD) + local ballIncWith = getSkillIncDamage(findSkillIndex("Ball Lightning")) + local tempestIncWith = getSkillIncDamage(findSkillIndex("Tempest Bell")) + + assert.are.equals(ballIncBase, ballIncWith) + assert.True(tempestIncWith > tempestIncBase) + end) + + it("caps damage per combo consumed at the stated limit", function() + equipQuarterstaff() + build.skillsTab:PasteSocketGroup("Tempest Bell 20/0 1\n") + runCallback("OnFrame") + + applyComboConfig(0, "") + local tempestIncBase = getSkillIncDamage(findSkillIndex("Tempest Bell")) + + applyComboConfig(3, CHAKRA_MOD) + local incAt3 = getSkillIncDamage(findSkillIndex("Tempest Bell")) + + applyComboConfig(5, CHAKRA_MOD) + local incAt5 = getSkillIncDamage(findSkillIndex("Tempest Bell")) + + applyComboConfig(10, CHAKRA_MOD) + local incAt10 = getSkillIncDamage(findSkillIndex("Tempest Bell")) + + assert.are.equals(24, incAt3 - tempestIncBase) + assert.are.equals(40, incAt5 - tempestIncBase) + assert.are.equals(40, incAt10 - tempestIncBase) + assert.are.equals(incAt5, incAt10) + end) + + it("Culmination I caps combo damage at 10 stacks", function() + equipQuarterstaff() + build.skillsTab:PasteSocketGroup("Quarterstaff Strike 20/0 1\nCulmination I 20/0 1\n") + runCallback("OnFrame") + + applyComboConfig(10) + local moreAt10 = getSkillMoreDamage(findSkillIndex("Quarterstaff Strike")) + local hitAt10 = getAverageHit() + + applyComboConfig(50) + local moreAt50 = getSkillMoreDamage(findSkillIndex("Quarterstaff Strike")) + local hitAt50 = getAverageHit() + + assert.are.equals(30, moreAt10) + assert.are.equals(moreAt10, moreAt50) + assert.are.equals(hitAt10, hitAt50) + end) + + it("Culmination II caps combo damage at 20 stacks", function() + equipQuarterstaff() + build.skillsTab:PasteSocketGroup("Quarterstaff Strike 20/0 1\nCulmination II 20/0 1\n") + runCallback("OnFrame") + + applyComboConfig(20) + local moreAt20 = getSkillMoreDamage(findSkillIndex("Quarterstaff Strike")) + local hitAt20 = getAverageHit() + + applyComboConfig(50) + local moreAt50 = getSkillMoreDamage(findSkillIndex("Quarterstaff Strike")) + local hitAt50 = getAverageHit() + + assert.are.equals(40, moreAt20) + assert.are.equals(moreAt20, moreAt50) + assert.are.equals(hitAt20, hitAt50) + end) + end) end) diff --git a/src/Data/ModCache.lua b/src/Data/ModCache.lua index fa62f77c9..94a4703d8 100644 --- a/src/Data/ModCache.lua +++ b/src/Data/ModCache.lua @@ -5930,7 +5930,7 @@ c["Skills Cost +3 Rage"]={{[1]={flags=0,keywordFlags=0,name="RageCostBase",type= c["Skills Supported by Unleash have 10% increased Seal gain frequency"]={{[1]={flags=0,keywordFlags=0,name="SealGainFrequency",type="INC",value=10}},nil} c["Skills Supported by Unleash have 25% increased Seal gain frequency"]={{[1]={flags=0,keywordFlags=0,name="SealGainFrequency",type="INC",value=25}},nil} c["Skills deal 20% increased Damage per Connected Red Support Gem"]={{[1]={flags=0,keywordFlags=0,name="SkillDamageIncreasedPerRedSupport",type="FLAG",value=20}},nil} -c["Skills deal 8% increased Damage per Combo consumed, up to 40%"]={{[1]={flags=0,keywordFlags=0,name="Damage",type="INC",value=8}}," per Combo consumed, up to 40% "} +c["Skills deal 8% increased Damage per Combo consumed, up to 40%"]={{[1]={[1]={limit=40,limitTotal=true,type="Multiplier",var="ComboStacks"},[2]={skillType=152,type="SkillType"},flags=0,keywordFlags=0,name="Damage",type="INC",value=8}},nil} c["Skills fire an additional Projectile"]={{[1]={flags=0,keywordFlags=0,name="ProjectileCount",type="BASE",value=1}},nil} c["Skills gain 1 Glory every 2 seconds for each Rare or Unique monster in your Presence"]={{}," Glory every 2 seconds for each Rare or Unique monster in your Presence "} c["Skills gain 1% of Damage as Chaos Damage per 3 Life Cost"]={{[1]={[1]={div=3,stat="LifeCost",type="PerStat"},flags=0,keywordFlags=0,name="DamageAsChaos",type="BASE",value=1}},nil} diff --git a/src/Data/SkillStatMap.lua b/src/Data/SkillStatMap.lua index 8aa1dae89..d1b960816 100644 --- a/src/Data/SkillStatMap.lua +++ b/src/Data/SkillStatMap.lua @@ -824,6 +824,12 @@ return { ["active_skill_damage_+%_final"] = { mod("Damage", "MORE", nil), }, +["support_damage_+%_final_per_combo_stack"] = { + mod("Damage", "MORE", nil, 0, 0, { type = "Multiplier", var = "ComboStacks", limitVar = "ComboStacksMax" }), +}, +["skill_maximum_number_of_combo_stacks"] = { + mod("Multiplier:ComboStacksMax", "BASE", nil), +}, ["support_no_fear_damage_+%_final_per_second_up_to_30%"] = { mod("Damage", "MORE", nil, 0, 0, { type = "Condition", var = "UsingStoicism" }, diff --git a/src/Modules/ConfigOptions.lua b/src/Modules/ConfigOptions.lua index b148d9a4a..d9d0ca229 100644 --- a/src/Modules/ConfigOptions.lua +++ b/src/Modules/ConfigOptions.lua @@ -919,6 +919,9 @@ Huge sets the radius to 11. { var = "multiplierRage", type = "count", label = "^xFF9922Rage:", ifFlag = "Condition:CanGainRage", tooltip = "Base Maximum ^xFF9922Rage ^7is 30, and inherently grants 1% More Attack Damage per 1 ^xFF9922Rage^7\nYou lose 10 ^xFF9922Rage ^7every second if you have not been Hit or gained ^xFF9922Rage ^7in the last 2 seconds.", apply = function(val, modList, enemyModList) modList:NewMod("Multiplier:RageStack", "BASE", val, "Config", { type = "IgnoreCond" }, { type = "Condition", var = "Combat" }, { type = "Condition", var = "CanGainRage" }) end }, + { var = "multiplierCombo", type = "count", label = "Combo:", ifMult = "ComboStacks", tooltip = "Some skills and effects require a certain Combo count to use.\nCombo is built by successfully Striking Enemies.", apply = function(val, modList, enemyModList) + modList:NewMod("Multiplier:ComboStacks", "BASE", val, "Config", { type = "IgnoreCond" }, { type = "Condition", var = "Combat" }) + end }, { var = "conditionLeeching", type = "check", label = "Are you Leeching?", ifCond = "Leeching", tooltip = "You will automatically be considered to be Leeching if you have '^xE05030Life ^7Leech effects are not removed at Full ^xE05030Life^7',\nbut you can use this option to force it if necessary.", apply = function(val, modList, enemyModList) modList:NewMod("Condition:Leeching", "FLAG", true, "Config", { type = "Condition", var = "Combat" }) end }, diff --git a/src/Modules/ModParser.lua b/src/Modules/ModParser.lua index cc20d66c3..16177e3ea 100644 --- a/src/Modules/ModParser.lua +++ b/src/Modules/ModParser.lua @@ -1439,6 +1439,7 @@ local modTagList = { ["per crab barrier"] = { tag = { type = "Multiplier", var = "CrabBarrier" } }, ["per rage"] = { tag = { type = "Multiplier", var = "Rage" } }, ["per rage while you are not losing rage"] = { tag = { type = "Multiplier", var = "Rage" } }, + ["per combo consumed, up to (%d+)%%"] = function(num) return { tagList = { { type = "Multiplier", var = "ComboStacks", limit = tonumber(num), limitTotal = true }, { type = "SkillType", skillType = SkillType.ComboStacking } } } end, ["per (%d+) rage"] = function(num) return { tag = { type = "Multiplier", var = "Rage", div = num } } end, ["per mana burn"] = { tag = { type = "Multiplier", var = "ManaBurnStacks" } }, ["per mana burn on you"] = { tag = { type = "Multiplier", var = "ManaBurnStacks" } },