Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
13 changes: 11 additions & 2 deletions src/Data/Skills/other.lua
Original file line number Diff line number Diff line change
Expand Up @@ -11665,8 +11665,13 @@ skills["ParryPlayer"] = {
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" }),
["base_maximum_active_block_distance_for_non_projectiles"] = {
skill("parryRangeNonProj", nil),
div = 10,
},
["base_maximum_active_block_distance_for_projectiles"] = {
skill("parryRangeProj", nil),
div = 10,
},
},
baseFlags = {
Expand Down Expand Up @@ -12635,6 +12640,10 @@ skills["RefutationPlayer"] = {
incrementalEffectiveness = 0.054999999701977,
statDescriptionScope = "refutation",
baseFlags = {
duration = true,
},
baseMods = {
skill("debuff", true),
},
constantStats = {
{ "base_skill_effect_duration", 4000 },
Expand Down
12 changes: 9 additions & 3 deletions src/Export/Skills/other.txt
Original file line number Diff line number Diff line change
Expand Up @@ -764,8 +764,13 @@ 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),
div = 10,
},
["base_maximum_active_block_distance_for_projectiles"] = {
skill("parryRangeProj", nil),
div = 10,
},
},
#baseMod skill("debuff", true)
Expand Down Expand Up @@ -847,7 +852,8 @@ statMap = {

#skill RefutationPlayer
#set RefutationPlayer
#flags
#flags duration
#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