Skip to content
Open
52 changes: 52 additions & 0 deletions spec/System/TestSkills_spec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -987,4 +987,56 @@ describe("TestSkills", function()
local expectedAverageEffect = 1 + (build.calcsTab.calcsOutput.MaxAncestralEmpowermentCombinedDamageEffect - 1) * build.calcsTab.calcsOutput.AncestralEmpowermentCombinedUptimeRatio / 100
assert.are.equals(round(expectedAverageEffect, 4), round(build.calcsTab.calcsOutput.AvgAncestralEmpowermentCombinedDamageEffect, 4))
end)

it("calculates effects of parry debuff correctly", function()
build.itemsTab:CreateDisplayItemFromRaw([[
Generic EV Shield
Desert Buckler
Evasion: 230
Quality: 20
LevelReq: 80
]])
build.itemsTab:AddDisplayItem()
runCallback("OnFrame")
build.skillsTab:PasteSocketGroup("Parry 20/0 1")
runCallback("OnFrame")
build.configTab:BuildModList()
runCallback("OnFrame")
build.calcsTab:BuildOutput()
runCallback("OnFrame")

-- Test general debuff
local preParryDmg = build.calcsTab.mainOutput.AverageDamage
build.configTab.configSets[1].input.parryActive = true
build.configTab:BuildModList()
build.calcsTab:BuildOutput()
runCallback("OnFrame")
local postParryDmg = build.calcsTab.mainOutput.AverageDamage
assert.True(postParryDmg > preParryDmg, "Damage should be higher with Parry active")

-- Test Magnitude
build.configTab.input.customMods = "50% increased parried debuff magnitude"
build.configTab:BuildModList()
runCallback("OnFrame")
build.calcsTab:BuildOutput()
runCallback("OnFrame")
local incMagnitudeDmg = build.calcsTab.mainOutput.AverageDamage
assert.True(incMagnitudeDmg > postParryDmg, "Damage should be higher with increased parried debuff magnitude")

-- Test effect on spells
build.skillsTab:PasteSocketGroup("Bone Cage 20/0 1")
runCallback("OnFrame")
selectActiveSkillById(build.skillsTab.socketGroupList[#build.skillsTab.socketGroupList], "BoneCagePlayer")
runCallback("OnFrame")
build.calcsTab:BuildOutput()
runCallback("OnFrame")
local withParrySpellDmg = build.calcsTab.mainOutput.AverageDamage
build.configTab.configSets[1].input.parryActive = false
build.configTab:BuildModList()
runCallback("OnFrame")
build.calcsTab:BuildOutput()
runCallback("OnFrame")
local noParrySpellDmg = build.calcsTab.mainOutput.AverageDamage
assert.equals(withParrySpellDmg, noParrySpellDmg, "Parry should not affect spell damage")
end)
end)
36 changes: 16 additions & 20 deletions src/Data/ModCache.lua

Large diffs are not rendered by default.

10 changes: 10 additions & 0 deletions src/Data/SkillStatMap.lua
Original file line number Diff line number Diff line change
Expand Up @@ -2783,6 +2783,16 @@ return {
["frost_wall_maximum_life"] = {
mod("IceCrystalLifeBase", "BASE", nil),
},
-- Parry
["base_parry_buff_damage_taken_+%_final_to_apply"] = {
mod("DamageTaken", "MORE", nil, ModFlag.Attack, 0, { type = "GlobalEffect", effectType = "Debuff", effectName = "Parry Debuff", effectCond = "ParryActive" }, { type = "Condition", var = "Effective" }),
skill("parryDebuffBaseMagnitude", nil),
flag("CanParry"),
},
["base_parry_duration_ms"] = {
skill("parryDebuffDuration", nil),
div = 1000,
},
-- Other
["triggered_skill_damage_+%"] = {
mod("TriggeredDamage", "INC", nil, 0, 0, { type = "SkillType", skillType = SkillType.Triggered }),
Expand Down
16 changes: 11 additions & 5 deletions src/Data/Skills/other.lua
Original file line number Diff line number Diff line change
Expand Up @@ -11664,18 +11664,21 @@ skills["ParryPlayer"] = {
label = "Parry",
incrementalEffectiveness = 0.054999999701977,
statDescriptionScope = "parry",
statMap = {
["base_parry_buff_damage_taken_+%_final_to_apply"] = {
mod("DamageTaken", "MORE", nil, ModFlag.Attack, 0, { type = "GlobalEffect", effectType = "Debuff", effectName = "Parry" }, { type = "Condition", var = "ParryActive" }),
},
},
baseFlags = {
attack = true,
melee = true,
duration = true,
shieldAttack = true,
area = true,
},
statMap = {
["base_maximum_active_block_distance_for_non_projectiles"] = {
skill("parryRangeNonProj", nil),
},
["base_maximum_active_block_distance_for_projectiles"] = {
skill("parryRangeProj", nil)
},
},
baseMods = {
skill("debuff", true),
},
Expand Down Expand Up @@ -12636,6 +12639,9 @@ skills["RefutationPlayer"] = {
statDescriptionScope = "refutation",
baseFlags = {
},
baseMods = {
skill("debuff", true),
},
constantStats = {
{ "base_skill_effect_duration", 4000 },
{ "movement_speed_+%_final_while_performing_action", -70 },
Expand Down
8 changes: 6 additions & 2 deletions src/Export/Skills/other.txt
Original file line number Diff line number Diff line change
Expand Up @@ -764,8 +764,11 @@ statMap = {
#set ParryPlayer
#flags attack melee duration shieldAttack area
statMap = {
["base_parry_buff_damage_taken_+%_final_to_apply"] = {
mod("DamageTaken", "MORE", nil, ModFlag.Attack, 0, { type = "GlobalEffect", effectType = "Debuff", effectName = "Parry" }, { type = "Condition", var = "ParryActive" }),
["base_maximum_active_block_distance_for_non_projectiles"] = {
skill("parryRangeNonProj", nil),
},
["base_maximum_active_block_distance_for_projectiles"] = {
skill("parryRangeProj", nil)
},
},
#baseMod skill("debuff", true)
Expand Down Expand Up @@ -848,6 +851,7 @@ statMap = {
#skill RefutationPlayer
#set RefutationPlayer
#flags
#baseMod skill("debuff", true)
#mods
#skillEnd

Expand Down
71 changes: 70 additions & 1 deletion src/Modules/CalcOffence.lua
Original file line number Diff line number Diff line change
Expand Up @@ -358,14 +358,19 @@ local function calcWarcryCastTime(skillModList, skillCfg, skillData, actor)
return warcryCastTime
end

--- Calculates effect of buff/debuff expiration rate on actors
local function calcBuffExpirationMult(actorDB, cfg)
return 1 / m_max(data.misc.BuffExpirationSlowCap, calcLib.mod(actorDB, cfg, "BuffExpireFaster"))
end

function calcSkillDuration(skillModList, skillCfg, skillData, env, enemyDB)
local durationMod = calcLib.mod(skillModList, skillCfg, "Duration", "PrimaryDuration", "DamagingAilmentDuration", skillData.mineDurationAppliesToSkill and "MineDuration" or nil)
durationMod = m_max(durationMod, 0)
local durationBase = (skillData.duration or 0) + skillModList:Sum("BASE", skillCfg, "Duration", "PrimaryDuration")
local duration = durationBase * durationMod
local debuffDurationMult = 1
if env.mode_effective then
debuffDurationMult = 1 / m_max(data.misc.BuffExpirationSlowCap, calcLib.mod(enemyDB, skillCfg, "BuffExpireFaster"))
debuffDurationMult = calcBuffExpirationMult(enemyDB, skillCfg)
end
if skillData.debuff then
duration = duration * debuffDurationMult
Expand Down Expand Up @@ -6121,6 +6126,70 @@ function calcs.offence(env, actor, activeSkill)
if skillFlags.monsterExplode then
output.CombinedAvgToMonsterLife = output.CombinedAvg / monsterLife * 100
end
-- Parry Stats
-- NOTE: This section is mainly for skill-specific breakdowns. Actual application of damage modifier is handled in `CalcPerform`
local parryDebuffMagnitudeMod = calcLib.mod(skillModList, skillCfg, "ParryDebuffMagnitude")
if skillData.parryDebuffBaseMagnitude and parryDebuffMagnitudeMod and parryDebuffMagnitudeMod ~= 1 then
output.ParryDebuffMagnitudeMod = parryDebuffMagnitudeMod
if breakdown then
local inc = skillModList:Sum("INC", skillCfg, "ParryDebuffMagnitude")
local more = skillModList:More(skillCfg, "ParryDebuffMagnitude") * calcLib.mod(skillModList, skillCfg, "DebuffEffect")
breakdown.ParryDebuffMagnitudeMod = {
s_format("Modifiers to Parry Debuff Magnitude:"),
s_format(""),
s_format("x %.2f ^8(increased magnitude)", 1 + inc / 100),
s_format("x %.2f ^8(more magnitude)", more + 1),
s_format("= %.2f", parryDebuffMagnitudeMod),
s_format(""),
s_format("Resulting Parry Debuff Magnitude:"),
s_format("%.2f%% more damage taken ^8(base magnitude)", skillData.parryDebuffBaseMagnitude),
s_format("x %.2f", parryDebuffMagnitudeMod),
s_format("= %.2f%% more damage taken", skillData.parryDebuffBaseMagnitude * parryDebuffMagnitudeMod),
s_format("^8Note: Only the highest Parry Debuff magnitude will be counted"),
}
end
end
if skillData.parryDebuffDuration and skillData.parryDebuffDuration > 0 then
local expirationMult = calcBuffExpirationMult(enemyDB, skillCfg)
--skillModList:NewMod("ParryDebuffDuration", "BASE", skillData.parryDebuffDuration, "Base value from skill")
output.ParryDebuffDuration = skillData.parryDebuffDuration * calcLib.mod(skillModList, skillCfg, "ParryDebuffDuration") * (expirationMult or 0)
if breakdown then
breakdown.ParryDebuffDuration = {
s_format("Duration of parry debuff on enemy:\n"),
s_format(""),
s_format("%.2fs ^8(base duration)", skillData.parryDebuffDuration),
s_format("x %.2f ^8(modifier)", calcLib.mod(skillModList, skillCfg, "ParryDebuffDuration")),
}
if expirationMult and expirationMult ~= 1 then
t_insert(breakdown.ParryDebuffDuration, s_format("x %.2f ^8(buff expiration multiplier)", expirationMult))
end
t_insert(breakdown.ParryDebuffDuration, s_format("= %.2fs", output.ParryDebuffDuration))
end
end
if skillData.parryRangeNonProj or skillData.parryRangeProj then
output.ParryRangeNonProj = (skillData.parryRangeNonProj or 0) * calcLib.mod(skillModList, skillCfg, "ParryRangeNonProj")
output.ParryRangeProj = (skillData.parryRangeProj or 0) * calcLib.mod(skillModList, skillCfg, "ParryRangeProj")
if breakdown then
if output.ParryRangeNonProj > 0 then
breakdown.ParryRangeNonProj = {
s_format("Max Parry distance vs. non-projectiles:"),
s_format(""),
s_format("%.1f m ^8(base parry range for non-projectiles)", skillData.parryRangeNonProj),
s_format("x %.1f ^8(modifier)", calcLib.mod(skillModList, skillCfg, "ParryRangeNonProj")),
s_format("= %.1f m", output.ParryRangeNonProj),
}
end
if output.ParryRangeProj > 0 then
breakdown.ParryRangeProj = {
s_format("Max Parry distance vs. projectiles:\n"),
s_format(""),
s_format("%.1f m ^8(base parry range for projectiles)", skillData.parryRangeProj),
s_format("x %.1f ^8(modifier)", calcLib.mod(skillModList, skillCfg, "ParryRangeProj")),
s_format("= %.1f m", output.ParryRangeProj),
}
end
end
end
if skillFlags.impale then
local mainHandImpaleDPS, offHandImpaleDPS
if skillFlags.attack and skillData.doubleHitsWhenDualWielding and skillFlags.bothWeaponAttack then
Expand Down
7 changes: 6 additions & 1 deletion src/Modules/CalcPerform.lua
Original file line number Diff line number Diff line change
Expand Up @@ -2257,8 +2257,10 @@ function calcs.perform(env, skipEHP)
end
end
if buff.type == "Debuff" then
local specificDebuffMult = calcLib.mod(skillModList, skillCfg, buff.name:gsub(" ", "").."Magnitude") -- non-skill mods specific to that debuff type
local skillMagnitudeMult = calcLib.mod(skillModList, skillCfg, "Magnitude")
local inc = skillModList:Sum("INC", skillCfg, "DebuffEffect")
local more = skillModList:More(skillCfg, "DebuffEffect") * calcLib.mod(skillModList, skillCfg, "Magnitude")
local more = skillModList:More(skillCfg, "DebuffEffect") * skillMagnitudeMult * specificDebuffMult
mult = (1 + inc / 100) * more
end
srcList:ScaleAddList(buff.modList, mult * stackCount)
Expand Down Expand Up @@ -2372,6 +2374,9 @@ function calcs.perform(env, skipEHP)
if activeSkill.skillModList:Flag(nil, "ApplyCriticalWeakness") then
modDB:NewMod("ApplyCriticalWeakness", "FLAG", true)
end
if activeSkill.skillModList:Flag(nil, "CanParry") then
modDB:NewMod("CanParry", "FLAG", true)
end
--Handle combustion
if enemyDB:Flag(nil, "Condition:Ignited") and (activeSkill.skillTypes[SkillType.Damage] or activeSkill.skillTypes[SkillType.Attack]) and not appliedCombustion then
for _, support in ipairs(activeSkill.supportList) do
Expand Down
19 changes: 19 additions & 0 deletions src/Modules/CalcSections.lua
Original file line number Diff line number Diff line change
Expand Up @@ -850,6 +850,25 @@ return {
{ breakdown = "SealGainTime" },
{ modName = "SealGainFrequency", cfg = "skill" },
}, },
-- Parry
{ label = "Parry Effect Mod", haveOutput = "ParryDebuffMagnitudeMod", { format = "x {2:output:ParryDebuffMagnitudeMod}",
{ breakdown = "ParryDebuffMagnitudeMod" },
{ label = "Parry Magnitude", modName = "ParryDebuffMagnitude", cfg = "skill" },
{ label = "Debuff Effect", modName = "DebuffEffect", cfg = "skill" },
}, },
{ label = "Parry Duration", haveOutput = "ParryDebuffDuration", { format = "{2:output:ParryDebuffDuration}s",
{ breakdown = "ParryDebuffDuration" },
{ label = "Player modifiers", modName = "ParryDebuffDuration", cfg = "skill" },
{ label = "Enemy modifiers", modName = "BuffExpireFaster", enemy = true },
}, },
{ label = "Parry Range", haveOutput = "ParryRangeNonProj", { format = "{1:output:ParryRangeNonProj}m",
{ breakdown = "ParryRangeNonProj" },
{ label = "Range modifiers", modName = "ParryRangeNonProj", cfg = "skill" },
}, },
{ label = "Parry Range Proj", haveOutput = "ParryRangeProj", { format = "{1:output:ParryRangeProj}m",
{ breakdown = "ParryRangeProj" },
{ label = "Range modifiers", modName = "ParryRangeProj", cfg = "skill" },
}, },
-- Mines
{ label = "Active Mine Limit", flag = "mine", { format = "{0:output:ActiveMineLimit}", { modName = "ActiveMineLimit", cfg = "skill" }, }, },
{ label = "Mine Throw Rate", flag = "mine", { format = "{2:output:MineLayingSpeed}",
Expand Down
6 changes: 3 additions & 3 deletions src/Modules/ConfigOptions.lua
Original file line number Diff line number Diff line change
Expand Up @@ -481,9 +481,9 @@ local configSettings = {
modList:NewMod("Multiplier:StoicismSeconds", "BASE", m_min(m_max(val, 0), 20), "Config")
modList:NewMod("Multiplier:StoicismCap", "BASE", 20, "Config")
end },
{ label = "Parry:", ifSkill = "Parry" },
{ 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)
enemyModList:NewMod("Condition:ParryActive", "FLAG", true, "Config")
{ label = "Parry:", ifFlag = "CanParry" },
{ 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)
modList:NewMod("Condition:ParryActive", "FLAG", true, "Config")
end },
{ label = "Plague Bearer:", ifSkill = "Plague Bearer"},
{ var = "plagueBearerState", type = "list", label = "State:", ifSkill = "Plague Bearer", list = {{val="INC",label="Incubating"},{val="INF",label="Infecting"}}, apply = function(val, modList, enemyModList)
Expand Down
4 changes: 3 additions & 1 deletion src/Modules/ModParser.lua
Original file line number Diff line number Diff line change
Expand Up @@ -503,6 +503,9 @@ local modNameList = {
["maximum fortification"] = "MaximumFortification",
["fortification"] = "MinimumFortification",
["maximum valour"] = "MaximumValour",
["parried debuff magnitude"] = "ParryDebuffMagnitude",
["parried debuff duration"] = "ParryDebuffDuration",
["parry range"] = { "ParryRangeNonProj", "ParryRangeProj" },
-- Charges
["maximum power charge"] = "PowerChargesMax",
["maximum power charges"] = "PowerChargesMax",
Expand Down Expand Up @@ -662,7 +665,6 @@ local modNameList = {
["cooldown recovery rate"] = "CooldownRecovery",
["cooldown use"] = "AdditionalCooldownUses",
["cooldown uses"] = "AdditionalCooldownUses",
["range"] = "WeaponRange",
["weapon range"] = "WeaponRange",
["metres to weapon range"] = "WeaponRangeMetre",
["metre to weapon range"] = "WeaponRangeMetre",
Expand Down
Loading