Skip to content

Commit ee0e4b7

Browse files
majochemLocalIdentity
andauthored
Add support for various Parry modifiers (Debuff Magnitude, Range, Duration) (#2080)
* Add mod parsing for Parried Debuff Magnitude * Add specific debuff magnitude processing Adds processing of "<DebuffName>Magnitude" modifiers, in addition to existing checks for generif "DebuffEffect" and skill-specific "Magnitude" modifiers * Change processing of Parry debuff stats - Moved the process of Debuff to `skillStatMap` as "Refutation" introduced new ways to gain access to parry debuff - Changed the mod details slightly to enable better debuff processing * Add stats for Parry duration and range Parry duration hasn't been processed at all yet, and mods affecting Parry range currently false apply to the weapon range used for the Parry attack, rather than blocking distance * Separate buff expiry function from skill duration Makes it easier to reuse in other contexts. No functional change otherwise * Enable parry debuff recognition for "Refutation" * Add support for Parry Debuff Duration & Range mods Includes: - Parsing of mods - Calculation and adding to `output` - Breakdowns for each stat in CalcsSections * Add breakdown for parry debuff magnitude Also slight changes to calculation approach in `CalcPerform` as it previously didn't account for debuff effect being multiplicative * Make parry config option available for other skills * Fix config option not appearing for other skills * Add parry test to `TestSkills_spec.lua` Automatically tests: - parry debuff increases damage when active - parry magnitude further increases damage - parry debuff does not affect spell damage NOTE: that last one will have to be adjusted once we support the mod that changes parry to apply to spell damage instead, but I guess that's what the test is for... * Fix parry range not dividing by 10 The base stat divides by 10 so you have a base range of 1m --------- Co-authored-by: LocalIdentity <localidentity2@gmail.com>
1 parent 98a68b1 commit ee0e4b7

10 files changed

Lines changed: 199 additions & 31 deletions

File tree

spec/System/TestSkills_spec.lua

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1054,4 +1054,56 @@ describe("TestSkills", function()
10541054
local expectedAverageEffect = 1 + (build.calcsTab.calcsOutput.MaxAncestralEmpowermentCombinedDamageEffect - 1) * build.calcsTab.calcsOutput.AncestralEmpowermentCombinedUptimeRatio / 100
10551055
assert.are.equals(round(expectedAverageEffect, 4), round(build.calcsTab.calcsOutput.AvgAncestralEmpowermentCombinedDamageEffect, 4))
10561056
end)
1057+
1058+
it("calculates effects of parry debuff correctly", function()
1059+
build.itemsTab:CreateDisplayItemFromRaw([[
1060+
Generic EV Shield
1061+
Desert Buckler
1062+
Evasion: 230
1063+
Quality: 20
1064+
LevelReq: 80
1065+
]])
1066+
build.itemsTab:AddDisplayItem()
1067+
runCallback("OnFrame")
1068+
build.skillsTab:PasteSocketGroup("Parry 20/0 1")
1069+
runCallback("OnFrame")
1070+
build.configTab:BuildModList()
1071+
runCallback("OnFrame")
1072+
build.calcsTab:BuildOutput()
1073+
runCallback("OnFrame")
1074+
1075+
-- Test general debuff
1076+
local preParryDmg = build.calcsTab.mainOutput.AverageDamage
1077+
build.configTab.configSets[1].input.parryActive = true
1078+
build.configTab:BuildModList()
1079+
build.calcsTab:BuildOutput()
1080+
runCallback("OnFrame")
1081+
local postParryDmg = build.calcsTab.mainOutput.AverageDamage
1082+
assert.True(postParryDmg > preParryDmg, "Damage should be higher with Parry active")
1083+
1084+
-- Test Magnitude
1085+
build.configTab.input.customMods = "50% increased parried debuff magnitude"
1086+
build.configTab:BuildModList()
1087+
runCallback("OnFrame")
1088+
build.calcsTab:BuildOutput()
1089+
runCallback("OnFrame")
1090+
local incMagnitudeDmg = build.calcsTab.mainOutput.AverageDamage
1091+
assert.True(incMagnitudeDmg > postParryDmg, "Damage should be higher with increased parried debuff magnitude")
1092+
1093+
-- Test effect on spells
1094+
build.skillsTab:PasteSocketGroup("Bone Cage 20/0 1")
1095+
runCallback("OnFrame")
1096+
selectActiveSkillById(build.skillsTab.socketGroupList[#build.skillsTab.socketGroupList], "BoneCagePlayer")
1097+
runCallback("OnFrame")
1098+
build.calcsTab:BuildOutput()
1099+
runCallback("OnFrame")
1100+
local withParrySpellDmg = build.calcsTab.mainOutput.AverageDamage
1101+
build.configTab.configSets[1].input.parryActive = false
1102+
build.configTab:BuildModList()
1103+
runCallback("OnFrame")
1104+
build.calcsTab:BuildOutput()
1105+
runCallback("OnFrame")
1106+
local noParrySpellDmg = build.calcsTab.mainOutput.AverageDamage
1107+
assert.equals(withParrySpellDmg, noParrySpellDmg, "Parry should not affect spell damage")
1108+
end)
10571109
end)

src/Data/ModCache.lua

Lines changed: 16 additions & 20 deletions
Large diffs are not rendered by default.

src/Data/SkillStatMap.lua

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2802,6 +2802,16 @@ return {
28022802
["frost_wall_maximum_life"] = {
28032803
mod("IceCrystalLifeBase", "BASE", nil),
28042804
},
2805+
-- Parry
2806+
["base_parry_buff_damage_taken_+%_final_to_apply"] = {
2807+
mod("DamageTaken", "MORE", nil, ModFlag.Attack, 0, { type = "GlobalEffect", effectType = "Debuff", effectName = "Parry Debuff", effectCond = "ParryActive" }, { type = "Condition", var = "Effective" }),
2808+
skill("parryDebuffBaseMagnitude", nil),
2809+
flag("CanParry"),
2810+
},
2811+
["base_parry_duration_ms"] = {
2812+
skill("parryDebuffDuration", nil),
2813+
div = 1000,
2814+
},
28052815
-- Other
28062816
["triggered_skill_damage_+%"] = {
28072817
mod("TriggeredDamage", "INC", nil, 0, 0, { type = "SkillType", skillType = SkillType.Triggered }),

src/Data/Skills/other.lua

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12453,8 +12453,13 @@ skills["ParryPlayer"] = {
1245312453
incrementalEffectiveness = 0.054999999701977,
1245412454
statDescriptionScope = "parry",
1245512455
statMap = {
12456-
["base_parry_buff_damage_taken_+%_final_to_apply"] = {
12457-
mod("DamageTaken", "MORE", nil, ModFlag.Attack, 0, { type = "GlobalEffect", effectType = "Debuff", effectName = "Parry" }, { type = "Condition", var = "ParryActive" }),
12456+
["base_maximum_active_block_distance_for_non_projectiles"] = {
12457+
skill("parryRangeNonProj", nil),
12458+
div = 10,
12459+
},
12460+
["base_maximum_active_block_distance_for_projectiles"] = {
12461+
skill("parryRangeProj", nil),
12462+
div = 10,
1245812463
},
1245912464
},
1246012465
baseFlags = {
@@ -13423,6 +13428,10 @@ skills["RefutationPlayer"] = {
1342313428
incrementalEffectiveness = 0.054999999701977,
1342413429
statDescriptionScope = "refutation",
1342513430
baseFlags = {
13431+
duration = true,
13432+
},
13433+
baseMods = {
13434+
skill("debuff", true),
1342613435
},
1342713436
constantStats = {
1342813437
{ "base_skill_effect_duration", 4000 },

src/Export/Skills/other.txt

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -835,8 +835,13 @@ statMap = {
835835
#set ParryPlayer
836836
#flags attack melee duration shieldAttack area
837837
statMap = {
838-
["base_parry_buff_damage_taken_+%_final_to_apply"] = {
839-
mod("DamageTaken", "MORE", nil, ModFlag.Attack, 0, { type = "GlobalEffect", effectType = "Debuff", effectName = "Parry" }, { type = "Condition", var = "ParryActive" }),
838+
["base_maximum_active_block_distance_for_non_projectiles"] = {
839+
skill("parryRangeNonProj", nil),
840+
div = 10,
841+
},
842+
["base_maximum_active_block_distance_for_projectiles"] = {
843+
skill("parryRangeProj", nil),
844+
div = 10,
840845
},
841846
},
842847
#baseMod skill("debuff", true)
@@ -918,7 +923,8 @@ statMap = {
918923

919924
#skill RefutationPlayer
920925
#set RefutationPlayer
921-
#flags
926+
#flags duration
927+
#baseMod skill("debuff", true)
922928
#mods
923929
#skillEnd
924930

src/Modules/CalcOffence.lua

Lines changed: 70 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -358,14 +358,19 @@ local function calcWarcryCastTime(skillModList, skillCfg, skillData, actor)
358358
return warcryCastTime
359359
end
360360

361+
--- Calculates effect of buff/debuff expiration rate on actors
362+
local function calcBuffExpirationMult(actorDB, cfg)
363+
return 1 / m_max(data.misc.BuffExpirationSlowCap, calcLib.mod(actorDB, cfg, "BuffExpireFaster"))
364+
end
365+
361366
function calcSkillDuration(skillModList, skillCfg, skillData, env, enemyDB)
362367
local durationMod = calcLib.mod(skillModList, skillCfg, "Duration", "PrimaryDuration", "DamagingAilmentDuration", skillData.mineDurationAppliesToSkill and "MineDuration" or nil)
363368
durationMod = m_max(durationMod, 0)
364369
local durationBase = (skillData.duration or 0) + skillModList:Sum("BASE", skillCfg, "Duration", "PrimaryDuration")
365370
local duration = durationBase * durationMod
366371
local debuffDurationMult = 1
367372
if env.mode_effective then
368-
debuffDurationMult = 1 / m_max(data.misc.BuffExpirationSlowCap, calcLib.mod(enemyDB, skillCfg, "BuffExpireFaster"))
373+
debuffDurationMult = calcBuffExpirationMult(enemyDB, skillCfg)
369374
end
370375
if skillData.debuff then
371376
duration = duration * debuffDurationMult
@@ -6125,6 +6130,70 @@ function calcs.offence(env, actor, activeSkill)
61256130
if skillFlags.monsterExplode then
61266131
output.CombinedAvgToMonsterLife = output.CombinedAvg / monsterLife * 100
61276132
end
6133+
-- Parry Stats
6134+
-- NOTE: This section is mainly for skill-specific breakdowns. Actual application of damage modifier is handled in `CalcPerform`
6135+
local parryDebuffMagnitudeMod = calcLib.mod(skillModList, skillCfg, "ParryDebuffMagnitude")
6136+
if skillData.parryDebuffBaseMagnitude and parryDebuffMagnitudeMod and parryDebuffMagnitudeMod ~= 1 then
6137+
output.ParryDebuffMagnitudeMod = parryDebuffMagnitudeMod
6138+
if breakdown then
6139+
local inc = skillModList:Sum("INC", skillCfg, "ParryDebuffMagnitude")
6140+
local more = skillModList:More(skillCfg, "ParryDebuffMagnitude") * calcLib.mod(skillModList, skillCfg, "DebuffEffect")
6141+
breakdown.ParryDebuffMagnitudeMod = {
6142+
s_format("Modifiers to Parry Debuff Magnitude:"),
6143+
s_format(""),
6144+
s_format("x %.2f ^8(increased magnitude)", 1 + inc / 100),
6145+
s_format("x %.2f ^8(more magnitude)", more + 1),
6146+
s_format("= %.2f", parryDebuffMagnitudeMod),
6147+
s_format(""),
6148+
s_format("Resulting Parry Debuff Magnitude:"),
6149+
s_format("%.2f%% more damage taken ^8(base magnitude)", skillData.parryDebuffBaseMagnitude),
6150+
s_format("x %.2f", parryDebuffMagnitudeMod),
6151+
s_format("= %.2f%% more damage taken", skillData.parryDebuffBaseMagnitude * parryDebuffMagnitudeMod),
6152+
s_format("^8Note: Only the highest Parry Debuff magnitude will be counted"),
6153+
}
6154+
end
6155+
end
6156+
if skillData.parryDebuffDuration and skillData.parryDebuffDuration > 0 then
6157+
local expirationMult = calcBuffExpirationMult(enemyDB, skillCfg)
6158+
--skillModList:NewMod("ParryDebuffDuration", "BASE", skillData.parryDebuffDuration, "Base value from skill")
6159+
output.ParryDebuffDuration = skillData.parryDebuffDuration * calcLib.mod(skillModList, skillCfg, "ParryDebuffDuration") * (expirationMult or 0)
6160+
if breakdown then
6161+
breakdown.ParryDebuffDuration = {
6162+
s_format("Duration of parry debuff on enemy:\n"),
6163+
s_format(""),
6164+
s_format("%.2fs ^8(base duration)", skillData.parryDebuffDuration),
6165+
s_format("x %.2f ^8(modifier)", calcLib.mod(skillModList, skillCfg, "ParryDebuffDuration")),
6166+
}
6167+
if expirationMult and expirationMult ~= 1 then
6168+
t_insert(breakdown.ParryDebuffDuration, s_format("x %.2f ^8(buff expiration multiplier)", expirationMult))
6169+
end
6170+
t_insert(breakdown.ParryDebuffDuration, s_format("= %.2fs", output.ParryDebuffDuration))
6171+
end
6172+
end
6173+
if skillData.parryRangeNonProj or skillData.parryRangeProj then
6174+
output.ParryRangeNonProj = (skillData.parryRangeNonProj or 0) * calcLib.mod(skillModList, skillCfg, "ParryRangeNonProj")
6175+
output.ParryRangeProj = (skillData.parryRangeProj or 0) * calcLib.mod(skillModList, skillCfg, "ParryRangeProj")
6176+
if breakdown then
6177+
if output.ParryRangeNonProj > 0 then
6178+
breakdown.ParryRangeNonProj = {
6179+
s_format("Max Parry distance vs. non-projectiles:"),
6180+
s_format(""),
6181+
s_format("%.1f m ^8(base parry range for non-projectiles)", skillData.parryRangeNonProj),
6182+
s_format("x %.1f ^8(modifier)", calcLib.mod(skillModList, skillCfg, "ParryRangeNonProj")),
6183+
s_format("= %.1f m", output.ParryRangeNonProj),
6184+
}
6185+
end
6186+
if output.ParryRangeProj > 0 then
6187+
breakdown.ParryRangeProj = {
6188+
s_format("Max Parry distance vs. projectiles:\n"),
6189+
s_format(""),
6190+
s_format("%.1f m ^8(base parry range for projectiles)", skillData.parryRangeProj),
6191+
s_format("x %.1f ^8(modifier)", calcLib.mod(skillModList, skillCfg, "ParryRangeProj")),
6192+
s_format("= %.1f m", output.ParryRangeProj),
6193+
}
6194+
end
6195+
end
6196+
end
61286197
if skillFlags.impale then
61296198
local mainHandImpaleDPS, offHandImpaleDPS
61306199
if skillFlags.attack and skillData.doubleHitsWhenDualWielding and skillFlags.bothWeaponAttack then

src/Modules/CalcPerform.lua

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2272,8 +2272,10 @@ function calcs.perform(env, skipEHP)
22722272
end
22732273
end
22742274
if buff.type == "Debuff" then
2275+
local specificDebuffMult = calcLib.mod(skillModList, skillCfg, buff.name:gsub(" ", "").."Magnitude") -- non-skill mods specific to that debuff type
2276+
local skillMagnitudeMult = calcLib.mod(skillModList, skillCfg, "Magnitude")
22752277
local inc = skillModList:Sum("INC", skillCfg, "DebuffEffect")
2276-
local more = skillModList:More(skillCfg, "DebuffEffect") * calcLib.mod(skillModList, skillCfg, "Magnitude")
2278+
local more = skillModList:More(skillCfg, "DebuffEffect") * skillMagnitudeMult * specificDebuffMult
22772279
mult = (1 + inc / 100) * more
22782280
end
22792281
srcList:ScaleAddList(buff.modList, mult * stackCount)
@@ -2387,6 +2389,9 @@ function calcs.perform(env, skipEHP)
23872389
if activeSkill.skillModList:Flag(nil, "ApplyCriticalWeakness") then
23882390
modDB:NewMod("ApplyCriticalWeakness", "FLAG", true)
23892391
end
2392+
if activeSkill.skillModList:Flag(nil, "CanParry") then
2393+
modDB:NewMod("CanParry", "FLAG", true)
2394+
end
23902395
--Handle combustion
23912396
if enemyDB:Flag(nil, "Condition:Ignited") and (activeSkill.skillTypes[SkillType.Damage] or activeSkill.skillTypes[SkillType.Attack]) and not appliedCombustion then
23922397
for _, support in ipairs(activeSkill.supportList) do

src/Modules/CalcSections.lua

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -850,6 +850,25 @@ return {
850850
{ breakdown = "SealGainTime" },
851851
{ modName = "SealGainFrequency", cfg = "skill" },
852852
}, },
853+
-- Parry
854+
{ label = "Parry Effect Mod", haveOutput = "ParryDebuffMagnitudeMod", { format = "x {2:output:ParryDebuffMagnitudeMod}",
855+
{ breakdown = "ParryDebuffMagnitudeMod" },
856+
{ label = "Parry Magnitude", modName = "ParryDebuffMagnitude", cfg = "skill" },
857+
{ label = "Debuff Effect", modName = "DebuffEffect", cfg = "skill" },
858+
}, },
859+
{ label = "Parry Duration", haveOutput = "ParryDebuffDuration", { format = "{2:output:ParryDebuffDuration}s",
860+
{ breakdown = "ParryDebuffDuration" },
861+
{ label = "Player modifiers", modName = "ParryDebuffDuration", cfg = "skill" },
862+
{ label = "Enemy modifiers", modName = "BuffExpireFaster", enemy = true },
863+
}, },
864+
{ label = "Parry Range", haveOutput = "ParryRangeNonProj", { format = "{1:output:ParryRangeNonProj}m",
865+
{ breakdown = "ParryRangeNonProj" },
866+
{ label = "Range modifiers", modName = "ParryRangeNonProj", cfg = "skill" },
867+
}, },
868+
{ label = "Parry Range Proj", haveOutput = "ParryRangeProj", { format = "{1:output:ParryRangeProj}m",
869+
{ breakdown = "ParryRangeProj" },
870+
{ label = "Range modifiers", modName = "ParryRangeProj", cfg = "skill" },
871+
}, },
853872
-- Mines
854873
{ label = "Active Mine Limit", flag = "mine", { format = "{0:output:ActiveMineLimit}", { modName = "ActiveMineLimit", cfg = "skill" }, }, },
855874
{ label = "Mine Throw Rate", flag = "mine", { format = "{2:output:MineLayingSpeed}",

src/Modules/ConfigOptions.lua

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -491,9 +491,9 @@ local configSettings = {
491491
modList:NewMod("Multiplier:StoicismSeconds", "BASE", m_min(m_max(val, 0), 20), "Config")
492492
modList:NewMod("Multiplier:StoicismCap", "BASE", 20, "Config")
493493
end },
494-
{ label = "Parry:", ifSkill = "Parry" },
495-
{ var = "parryActive", type = "check", label = "Enemy has Parry Debuff", ifSkill = "Parry", tooltip = "The Parry debuff grants:\n\tEnemies take 50% more Attack Damage", apply = function(val, modList, enemyModList)
496-
enemyModList:NewMod("Condition:ParryActive", "FLAG", true, "Config")
494+
{ label = "Parry:", ifFlag = "CanParry" },
495+
{ var = "parryActive", type = "check", label = "Enemy has Parry Debuff", ifFlag = "CanParry", tooltip = "The Parry debuff grants:\n\tEnemies take 50% more Attack Damage", apply = function(val, modList, enemyModList)
496+
modList:NewMod("Condition:ParryActive", "FLAG", true, "Config")
497497
end },
498498
{ label = "Plague Bearer:", ifSkill = "Plague Bearer"},
499499
{ var = "plagueBearerState", type = "list", label = "State:", ifSkill = "Plague Bearer", list = {{val="INC",label="Incubating"},{val="INF",label="Infecting"}}, apply = function(val, modList, enemyModList)

src/Modules/ModParser.lua

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -503,6 +503,9 @@ local modNameList = {
503503
["maximum fortification"] = "MaximumFortification",
504504
["fortification"] = "MinimumFortification",
505505
["maximum valour"] = "MaximumValour",
506+
["parried debuff magnitude"] = "ParryDebuffMagnitude",
507+
["parried debuff duration"] = "ParryDebuffDuration",
508+
["parry range"] = { "ParryRangeNonProj", "ParryRangeProj" },
506509
-- Charges
507510
["maximum power charge"] = "PowerChargesMax",
508511
["maximum power charges"] = "PowerChargesMax",
@@ -662,7 +665,6 @@ local modNameList = {
662665
["cooldown recovery rate"] = "CooldownRecovery",
663666
["cooldown use"] = "AdditionalCooldownUses",
664667
["cooldown uses"] = "AdditionalCooldownUses",
665-
["range"] = "WeaponRange",
666668
["weapon range"] = "WeaponRange",
667669
["metres to weapon range"] = "WeaponRangeMetre",
668670
["metre to weapon range"] = "WeaponRangeMetre",

0 commit comments

Comments
 (0)