Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Updated Bleed/Ignite Ailment calculation to use weighted average #3927

Merged
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
66cd033
fix: updated Bleed Ailment calculaton to use weighted avg
Nostrademous Jan 20, 2022
4354e47
fix: setting bleed stack global too early
Nostrademous Jan 20, 2022
91813af
doc: removed commented out lines
Nostrademous Jan 20, 2022
ac75681
fix: stat combination for bleed ailment
Nostrademous Jan 20, 2022
3374952
fix: more bleed calc improvements
Nostrademous Jan 20, 2022
d81324b
fix: hit chance impact; dual-wield calculation; breakdowns
Nostrademous Jan 20, 2022
19210d5
fix: minor comment mis-spelling
Nostrademous Jan 20, 2022
5680d85
Merge branch 'dev' into FixStackLimitedAilmentCalcs
Nostrademous Jan 25, 2022
f83e820
fix: add ignite weighted avg.
Nostrademous Jan 25, 2022
3b112ba
fix: correct display when stacks exist
Nostrademous Jan 25, 2022
e17dc85
Merge branch 'dev' into FixStackLimitedAilmentCalcs
Nostrademous Jan 26, 2022
090c423
fix: accidentally double max-stack multiplied ignite DPS and compensa…
Nostrademous Jan 26, 2022
4a87e8e
fix: proper BleedExpireRate calcs
Nostrademous Jan 29, 2022
9f1e4af
Update src/Modules/CalcOffence.lua
Nostrademous Feb 1, 2022
ff50f7a
Merge branch 'dev' into FixStackLimitedAilmentCalcs
Nostrademous Feb 1, 2022
06b8ba4
fix: requested changes
Nostrademous Feb 1, 2022
76d515e
Merge branch 'FixStackLimitedAilmentCalcs' of github.com:Nostrademous…
Nostrademous Feb 1, 2022
ca3e7d4
feat: provide a lot of breakdowns and fix up ignites
Nostrademous Feb 1, 2022
bc429a8
fix: spelling
Nostrademous Feb 1, 2022
95b6012
fix: Update ignite breakdown text
Wires77 Feb 4, 2022
4d7fd90
Merge remote-tracking branch 'fork/dev' into FixStackLimitedAilmentCalcs
Wires77 Feb 4, 2022
15697e0
fix: Don't allow negative duration for ignite
Wires77 Feb 4, 2022
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
125 changes: 91 additions & 34 deletions src/Modules/CalcOffence.lua
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ local m_ceil = math.ceil
local m_min = math.min
local m_max = math.max
local m_sqrt = math.sqrt
local m_pow = math.pow
local bor = bit.bor
local band = bit.band
local bnot = bit.bnot
Expand Down Expand Up @@ -49,6 +50,9 @@ local damageStatsForTypes = setmetatable({ }, { __index = function(t, k)
return modNames
end })

globalOutput = nil
globalBreakdown = nil

-- Calculate min/max damage for the given damage type
local function calcDamage(activeSkill, output, cfg, breakdown, damageType, typeFlags, convDst)
local skillModList = activeSkill.skillModList
Expand Down Expand Up @@ -1391,6 +1395,43 @@ function calcs.offence(env, actor, activeSkill)
else
output[stat] = output.MainHand[stat] or output.OffHand[stat]
end
elseif mode == "CHANCE_AILMENT" then
if output.MainHand[stat] and output.OffHand[stat] then
local mainChance = output.MainHand[...] * output.MainHand.HitChance
local offChance = output.OffHand[...] * output.OffHand.HitChance
local mainPortion = mainChance / (mainChance + offChance)
local offPortion = offChance / (mainChance + offChance)
local maxInstance = m_max(output.MainHand[stat], output.OffHand[stat])
local minInstance = m_min(output.MainHand[stat], output.OffHand[stat])
local maxInstanceStacks = m_min(1, globalOutput.BleedStacks / globalOutput.BleedStacksMax)
output[stat] = maxInstance * maxInstanceStacks + minInstance * (1 - maxInstanceStacks)
if breakdown then
breakdown[stat] = { }
t_insert(breakdown[stat], colorCodes.CUSTOM.."NOTE: Calculation use new Weighted Avg Ailment formula")
t_insert(breakdown[stat], "")
t_insert(breakdown[stat], s_format("%.2f%% of ailment stacks use maximum damage", maxInstanceStacks * 100))
t_insert(breakdown[stat], s_format("Max Damage comes from %s", output.MainHand[stat] > output.OffHand[stat] and "Main Hand" or "Off Hand"))
t_insert(breakdown[stat], s_format("= %.1f", maxInstance * maxInstanceStacks))
if maxInstanceStacks < 1 then
t_insert(breakdown[stat], s_format("%.2f%% of ailment stacks use non-maximum damage", (1-maxInstanceStacks) * 100))
t_insert(breakdown[stat], s_format("= %.1f", minInstance * (1 - maxInstanceStacks)))
end
t_insert(breakdown[stat], "")
t_insert(breakdown[stat], "Total:")
if maxInstanceStacks < 1 then
t_insert(breakdown[stat], s_format("%.1f + %.1f", maxInstance * maxInstanceStacks, minInstance * (1 - maxInstanceStacks)))
end
t_insert(breakdown[stat], s_format("= %.1f", output[stat]))
end
else
output[stat] = output.MainHand[stat] or output.OffHand[stat]
if breakdown then
breakdown[stat] = { }
t_insert(breakdown[stat], colorCodes.CUSTOM.."NOTE: Calculation use new Weighted Avg Ailment formula")
t_insert(breakdown[stat], "")
t_insert(breakdown[stat], s_format("All ailment stacks comes from %s", output.MainHand[stat] and "Main Hand" or "Off Hand"))
end
end
elseif mode == "DPS" then
output[stat] = (output.MainHand[stat] or 0) + (output.OffHand[stat] or 0)
if not skillData.doubleHitsWhenDualWielding then
Expand All @@ -1401,7 +1442,7 @@ function calcs.offence(env, actor, activeSkill)

local storedMainHandAccuracy = nil
for _, pass in ipairs(passList) do
local globalOutput, globalBreakdown = output, breakdown
globalOutput, globalBreakdown = output, breakdown
local source, output, cfg, breakdown = pass.source, pass.output, pass.cfg, pass.breakdown

-- Calculate hit chance
Expand Down Expand Up @@ -1571,7 +1612,7 @@ function calcs.offence(env, actor, activeSkill)
end

for _, pass in ipairs(passList) do
local globalOutput, globalBreakdown = output, breakdown
globalOutput, globalBreakdown = output, breakdown
local source, output, cfg, breakdown = pass.source, pass.output, pass.cfg, pass.breakdown

-- Exerted Attack members
Expand Down Expand Up @@ -2582,7 +2623,7 @@ function calcs.offence(env, actor, activeSkill)
skillFlags.brittle = false
skillFlags.sap = false
for _, pass in ipairs(passList) do
local globalOutput, globalBreakdown = output, breakdown
globalOutput, globalBreakdown = output, breakdown
local source, output, cfg, breakdown = pass.source, pass.output, pass.cfg, pass.breakdown

-- Calculate chance to inflict secondary dots/status effects
Expand Down Expand Up @@ -2834,6 +2875,18 @@ function calcs.offence(env, actor, activeSkill)
if breakdown then
breakdown.BleedPhysical = { damageTypes = { } }
end

-- For bleeds we will be using a weighted average calculation
local configStacks = enemyDB:Sum("BASE", nil, "Multiplier:BleedStacks")
local maxStacks = skillModList:Override(cfg, "BleedStacksMax") or skillModList:Sum("BASE", cfg, "BleedStacksMax")
globalOutput.BleedStacksMax = maxStacks
local durationBase = skillData.bleedDurationIsSkillDuration and skillData.duration or data.misc.BleedDurationBase
local durationMod = calcLib.mod(skillModList, dotCfg, "EnemyBleedDuration", "SkillAndDamagingAilmentDuration", skillData.bleedIsSkillEffect and "Duration" or nil) * calcLib.mod(enemyDB, nil, "SelfBleedDuration") / calcLib.mod(enemyDB, dotCfg, "BleedExpireRate")
local rateMod = calcLib.mod(skillModList, cfg, "BleedFaster") + enemyDB:Sum("INC", nil, "SelfBleedFaster") / 100
globalOutput.BleedDuration = durationBase * durationMod / rateMod * debuffDurationMult
local bleedStacks = (output.HitChance / 100) * (globalOutput.BleedDuration / output.Time) / maxStacks
bleedStacks = configStacks > 0 and m_min(bleedStacks, configStacks / maxStacks) or bleedStacks

for sub_pass = 1, 2 do
if skillModList:Flag(dotCfg, "AilmentsAreNeverFromCrit") or sub_pass == 1 then
dotCfg.skillCond["CriticalStrike"] = false
Expand All @@ -2845,10 +2898,10 @@ function calcs.offence(env, actor, activeSkill)
output.BleedPhysicalMax = max
if sub_pass == 2 then
output.CritBleedDotMulti = 1 + skillModList:Sum("BASE", dotCfg, "DotMultiplier", "PhysicalDotMultiplier") / 100
sourceCritDmg = (min + max) / 2 * output.CritBleedDotMulti
sourceCritDmg = (min + (max - min) / m_pow(2, 1 / (bleedStacks + 1))) * output.CritBleedDotMulti
else
output.BleedDotMulti = 1 + skillModList:Sum("BASE", dotCfg, "DotMultiplier", "PhysicalDotMultiplier") / 100
sourceHitDmg = (min + max) / 2 * output.BleedDotMulti
sourceHitDmg = (min + (max - min) / m_pow(2, 1 / (bleedStacks + 1))) * output.BleedDotMulti
end
end
local igniteMode = env.configInput.igniteMode or "AVERAGE"
Expand All @@ -2857,7 +2910,7 @@ function calcs.offence(env, actor, activeSkill)
end
if globalBreakdown then
globalBreakdown.BleedDPS = {
s_format("Ailment mode: %s ^8(can be changed in the Configuration tab)", igniteMode == "CRIT" and "Crits Only" or "Average Damage")
s_format(colorCodes.CUSTOM.."NOTE: Calculation use new Weighted Avg Ailment formula")
}
end
local basePercent = skillData.bleedBasePercent or data.misc.BleedPercentBase
Expand All @@ -2876,25 +2929,14 @@ function calcs.offence(env, actor, activeSkill)
globalBreakdown.BleedEffMult = breakdown.effMult("Physical", resist, 0, takenInc, effMult, takenMore)
end
end
local mult = skillModList:Sum("BASE", dotCfg, "PhysicalDotMultiplier", "BleedMultiplier")
local effectMod = calcLib.mod(skillModList, dotCfg, "AilmentEffect")
local rateMod = calcLib.mod(skillModList, cfg, "BleedFaster") + enemyDB:Sum("INC", nil, "SelfBleedFaster") / 100
local maxStacks = skillModList:Override(cfg, "BleedStacksMax") or skillModList:Sum("BASE", cfg, "BleedStacksMax")
local configStacks = enemyDB:Sum("BASE", nil, "Multiplier:BleedStacks")
local bleedStacks = configStacks > 0 and m_min(configStacks, maxStacks) or maxStacks
output.BaseBleedDPS = baseVal * effectMod * rateMod * effMult
output.BleedDPS = (baseVal * effectMod * rateMod * effMult) * bleedStacks
local durationBase
if skillData.bleedDurationIsSkillDuration then
durationBase = skillData.duration
else
durationBase = data.misc.BleedDurationBase
end
local durationMod = calcLib.mod(skillModList, dotCfg, "EnemyBleedDuration", "SkillAndDamagingAilmentDuration", skillData.bleedIsSkillEffect and "Duration" or nil) * calcLib.mod(enemyDB, nil, "SelfBleedDuration")
globalOutput.BleedDuration = durationBase * durationMod / rateMod * debuffDurationMult
globalOutput.BleedDamage = output.BaseBleedDPS * globalOutput.BleedDuration
globalOutput.BleedStacksMax = maxStacks
bleedStacks = m_min(maxStacks, (output.HitChance / 100) * globalOutput.BleedDuration / output.Time)
local chanceToHitInOneSecInterval = 1 - m_pow(1 - (output.HitChance / 100), output.Speed)
output.BleedDPS = (baseVal * effectMod * rateMod * effMult) * bleedStacks * chanceToHitInOneSecInterval
-- reset bleed stacks to actual number doing damage after weighted avg DPS calculation is done
globalOutput.BleedStacks = bleedStacks
globalOutput.BleedDamage = output.BaseBleedDPS * globalOutput.BleedDuration
if breakdown then
if output.CritBleedDotMulti and (output.CritBleedDotMulti ~= output.BleedDotMulti) then
local chanceFromHit = output.BleedChanceOnHit / 100 * (1 - globalOutput.CritChance / 100)
Expand Down Expand Up @@ -2924,6 +2966,8 @@ function calcs.offence(env, actor, activeSkill)
{ "%.2f ^8(ailment effect modifier)", effectMod },
{ "%.2f ^8(damage rate modifier)", rateMod },
{ "%.3f ^8(effective DPS modifier)", effMult },
{ "%d ^8(bleed stacks)", globalOutput.BleedStacks },
{ "%.3f ^8(bleed chance based on HitChance in 1 second interval)", chanceToHitInOneSecInterval },
total = s_format("= %.1f ^8per second", output.BleedDPS),
})
if globalOutput.BleedDuration ~= durationBase then
Expand Down Expand Up @@ -3149,6 +3193,20 @@ function calcs.offence(env, actor, activeSkill)
breakdown.IgniteFire = { damageTypes = { } }
breakdown.IgniteChaos = { damageTypes = { } }
end

-- For ignites we will be using a weighted average calculation
local maxStacks = 1
if skillFlags.igniteCanStack then
maxStacks = maxStacks + skillModList:Sum("BASE", cfg, "IgniteStacks")
end
globalOutput.TotalIgniteStacks = maxStacks

local rateMod = (calcLib.mod(skillModList, cfg, "IgniteBurnFaster") + enemyDB:Sum("INC", nil, "SelfIgniteBurnFaster") / 100) / calcLib.mod(skillModList, cfg, "IgniteBurnSlower")
local durationBase = data.misc.IgniteDurationBase
local durationMod = calcLib.mod(skillModList, dotCfg, "EnemyIgniteDuration", "SkillAndDamagingAilmentDuration") * calcLib.mod(enemyDB, nil, "SelfIgniteDuration")
globalOutput.IgniteDuration = durationBase * durationMod / rateMod * debuffDurationMult
local igniteStacks = (globalOutput.IgniteDuration / output.Time) / maxStacks

for sub_pass = 1, 2 do
if skillModList:Flag(dotCfg, "AilmentsAreNeverFromCrit") or sub_pass == 1 then
dotCfg.skillCond["CriticalStrike"] = false
Expand Down Expand Up @@ -3193,10 +3251,10 @@ function calcs.offence(env, actor, activeSkill)
end
if sub_pass == 2 then
output.CritIgniteDotMulti = 1 + skillModList:Sum("BASE", dotCfg, "DotMultiplier", "FireDotMultiplier") / 100
sourceCritDmg = (totalMin + totalMax) / 2 * output.CritIgniteDotMulti
sourceCritDmg = (totalMin + (totalMax - totalMin) / m_pow(2, 1 / (igniteStacks + 1))) * output.CritIgniteDotMulti
else
output.IgniteDotMulti = 1 + skillModList:Sum("BASE", dotCfg, "DotMultiplier", "FireDotMultiplier") / 100
sourceHitDmg = (totalMin + totalMax) / 2 * output.IgniteDotMulti
sourceHitDmg = (totalMin + (totalMax - totalMin) / m_pow(2, 1 / (igniteStacks + 1))) * output.IgniteDotMulti
end
end
local igniteMode = env.configInput.igniteMode or "AVERAGE"
Expand All @@ -3205,7 +3263,7 @@ function calcs.offence(env, actor, activeSkill)
end
if globalBreakdown then
globalBreakdown.IgniteDPS = {
s_format("Ailment mode: %s ^8(can be changed in the Configuration tab)", igniteMode == "CRIT" and "Crits Only" or "Average Damage")
s_format(colorCodes.CUSTOM.."NOTE: Calculation use new Weighted Avg Ailment formula")
}
end
local baseVal = calcAilmentDamage("Ignite", sourceHitDmg, sourceCritDmg) * data.misc.IgnitePercentBase * output.FistOfWarAilmentEffect * globalOutput.AilmentWarcryEffect
Expand Down Expand Up @@ -3234,17 +3292,15 @@ function calcs.offence(env, actor, activeSkill)
end
end
local effectMod = calcLib.mod(skillModList, dotCfg, "AilmentEffect")
local rateMod = (calcLib.mod(skillModList, cfg, "IgniteBurnFaster") + enemyDB:Sum("INC", nil, "SelfIgniteBurnFaster") / 100) / calcLib.mod(skillModList, cfg, "IgniteBurnSlower")
output.IgniteDPS = baseVal * effectMod * rateMod * effMult
local durationBase = data.misc.IgniteDurationBase
local durationMod = calcLib.mod(skillModList, dotCfg, "EnemyIgniteDuration", "SkillAndDamagingAilmentDuration") * calcLib.mod(enemyDB, nil, "SelfIgniteDuration")
globalOutput.IgniteDuration = durationBase * durationMod / rateMod * debuffDurationMult
igniteStacks = m_min(maxStacks, (output.HitChance / 100) * globalOutput.IgniteDuration / output.Time)
output.IgniteDPS = baseVal * effectMod * rateMod * effMult * igniteStacks
globalOutput.IgniteDamage = output.IgniteDPS * globalOutput.IgniteDuration
if skillFlags.igniteCanStack then
output.IgniteDamage = output.IgniteDPS * globalOutput.IgniteDuration
output.TotalIgniteStacks = 1 + skillModList:Sum("BASE", cfg, "IgniteStacks")
output.TotalIgniteDPS = output.IgniteDPS * output.TotalIgniteStacks
output.TotalIgniteStacks = maxStacks
output.TotalIgniteDPS = output.IgniteDPS
end

if breakdown then
t_insert(breakdown.IgniteDPS, "x 1.25 ^8(ignite deals 125% per second)")
t_insert(breakdown.IgniteDPS, s_format("= %.1f", baseVal, 1))
Expand All @@ -3254,6 +3310,7 @@ function calcs.offence(env, actor, activeSkill)
{ "%.2f ^8(ailment effect modifier)", effectMod },
{ "%.2f ^8(burn rate modifier)", rateMod },
{ "%.3f ^8(effective DPS modifier)", effMult },
{ "%d ^8(ignite stacks)", output.TotalIgniteStacks },
total = s_format("= %.1f ^8per second", output.IgniteDPS),
})
if output.CritIgniteDotMulti and (output.CritIgniteDotMulti ~= output.IgniteDotMulti) then
Expand Down Expand Up @@ -3723,7 +3780,7 @@ function calcs.offence(env, actor, activeSkill)
-- Combine secondary effect stats
if isAttack then
combineStat("BleedChance", "AVERAGE")
combineStat("BleedDPS", "CHANCE", "BleedChance")
combineStat("BleedDPS", "CHANCE_AILMENT", "BleedChance")
combineStat("PoisonChance", "AVERAGE")
combineStat("PoisonDPS", "CHANCE", "PoisonChance")
combineStat("TotalPoisonDPS", "DPS")
Expand All @@ -3734,7 +3791,7 @@ function calcs.offence(env, actor, activeSkill)
combineStat("TotalPoisonStacks", "DPS")
end
combineStat("IgniteChance", "AVERAGE")
combineStat("IgniteDPS", "CHANCE", "IgniteChance")
combineStat("IgniteDPS", "CHANCE_AILMENT", "IgniteChance")
if skillFlags.igniteCanStack then
combineStat("IgniteDamage", "CHANCE", "IgniteChance")
if skillData.showAverage then
Expand Down
4 changes: 2 additions & 2 deletions src/Modules/CalcSections.lua
Original file line number Diff line number Diff line change
Expand Up @@ -719,7 +719,7 @@ return {
{ label = "Bleed Duration", { format = "{2:output:BleedDuration}s",
{ breakdown = "BleedDuration" },
{ label = "Player modifiers", modName = { "EnemyBleedDuration", "SkillAndDamagingAilmentDuration", "BleedFaster" }, cfg = "bleed" },
{ label = "Enemy modifiers", modName = { "SelfBleedDuration", "SelfBleedFaster" }, enemy = true },
{ label = "Enemy modifiers", modName = { "SelfBleedDuration", "SelfBleedFaster", "BleedExpireRate" }, enemy = true },
}, },
} }
} },
Expand Down Expand Up @@ -827,7 +827,7 @@ return {
{ label = "Player modifiers", modName = { "EnemyIgniteDuration", "SkillAndDamagingAilmentDuration", "IgniteBurnFaster", "IgniteBurnSlower" }, cfg = "skill" },
{ label = "Enemy modifiers", modName = {"SelfIgniteDuration", "SelfIgniteBurnFaster"}, enemy = true },
}, },
{ label = "Dmg. per Ignite", flag = "igniteCanStack", { format = "{1:output:IgniteDamage}",
{ label = "Dmg. per Ignite", { format = "{1:output:IgniteDamage}",
{ breakdown = "MainHand.IgniteDamage" },
{ breakdown = "OffHand.IgniteDamage" },
{ breakdown = "IgniteDamage" },
Expand Down
2 changes: 1 addition & 1 deletion src/Modules/ConfigOptions.lua
Original file line number Diff line number Diff line change
Expand Up @@ -1137,7 +1137,7 @@ return {
{ var = "multiplierRuptureStacks", type = "count", label = "# of Rupture stacks?", ifFlag = "Condition:CanInflictRupture", tooltip = "Rupture applies 25% more bleed damage and 25% faster bleeds for 3 seconds, up to 3 stacks", apply = function(val, modList, enemyModList)
enemyModList:NewMod("Multiplier:RuptureStack", "BASE", val, "Config", { type = "Condition", var = "Effective" })
enemyModList:NewMod("DamageTaken", "MORE", 25, "Rupture", nil, KeywordFlag.Bleed, { type = "Multiplier", var = "RuptureStack", limit = 3 }, { type = "ActorCondition", actor = "enemy", var = "CanInflictRupture" })
modList:NewMod("EnemyBleedDuration", "INC", -25, "Rupture", { type = "Multiplier", var = "RuptureStack", limit = 3, actor = "enemy" }, { type = "ActorCondition", var = "CanInflictRupture" })
enemyModList:NewMod("BleedExpireRate", "MORE", 25, "Rupture", nil, KeywordFlag.Bleed, { type = "Multiplier", var = "RuptureStack", limit = 3 }, { type = "ActorCondition", actor = "enemy", var = "CanInflictRupture" })
end },
{ var = "conditionEnemyPoisoned", type = "check", label = "Is the enemy Poisoned?", ifEnemyCond = "Poisoned", apply = function(val, modList, enemyModList)
enemyModList:NewMod("Condition:Poisoned", "FLAG", true, "Config", { type = "Condition", var = "Effective" })
Expand Down