Skip to content
Draft
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
71 changes: 70 additions & 1 deletion spec/System/TestSkills_spec.lua
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
describe("TestAttacks", function()
describe("TestSkills", function()
before_each(function()
newBuild()
end)
Expand Down Expand Up @@ -249,4 +249,73 @@ describe("TestAttacks", function()
assert.are.equals(0, nonDurationSkill.skillModList:Sum("BASE", nonDurationSkill.skillCfg, "BaseFlagTest"))
assert.are.equals(1, nonDurationSkill.skillModList:Sum("BASE", nonDurationSkill.skillCfg, "NegatedBaseFlagTest"))
end)

it("Test Empowered Spells - Pact of Beidat", function()
build.skillsTab:PasteSocketGroup("Fireball 20/0 1")
runCallback("OnFrame")
local baseDPS = build.calcsTab.calcsOutput.TotalDPS

build.skillsTab:PasteSocketGroup("Pact of Beidat 1/0 1")
runCallback("OnFrame")
assert.True(build.calcsTab.calcsOutput.BeidatUpTimeRatio > 0)
assert.True(build.calcsTab.calcsOutput.TotalDPS > baseDPS)

newBuild()
build.skillsTab:PasteSocketGroup("Vaal Arc 20/0 1\nPact of Beidat 1/0 1")
runCallback("OnFrame")
assert.True(build.calcsTab.calcsOutput.BeidatUpTimeRatio == nil)
end)

it("Test Empowered Spells - Pact of Ghorr", function()
build.skillsTab:PasteSocketGroup("Creeping Frost 20/0 1")
runCallback("OnFrame")
local baseDPS = build.calcsTab.calcsOutput.TotalDotDPS

build.skillsTab:PasteSocketGroup("Pact of Ghorr 1/0 1")
runCallback("OnFrame")
assert.True(build.calcsTab.calcsOutput.GhorrUpTimeRatio > 0)
assert.True(build.calcsTab.calcsOutput.TotalDotDPS > baseDPS)

newBuild()
build.skillsTab:PasteSocketGroup("Vaal Arc 20/0 1\n Pact of Ghorr 1/0 1")
runCallback("OnFrame")
assert.True(build.calcsTab.calcsOutput.GhorrUpTimeRatio == nil)
end)

it("Test Empowered Spells - Pact of K'Tash", function()
build.skillsTab:PasteSocketGroup("Vaal Arc 20/0 1")
runCallback("OnFrame")
local baseDPS = build.calcsTab.calcsOutput.TotalDPS

build.skillsTab:PasteSocketGroup("Pact of K'Tash 1/0 1")
runCallback("OnFrame")
assert.True(build.calcsTab.calcsOutput.KtashUpTimeRatio > 0)
assert.True(build.calcsTab.calcsOutput.TotalDPS > baseDPS)

newBuild()
build.skillsTab:PasteSocketGroup("Fireball 20/0 1\nPact of K'Tash 1/0 1")
runCallback("OnFrame")
assert.True(build.calcsTab.calcsOutput.KtashUpTimeRatio == nil)
end)

it("Test Empowered Spells - Pact of Lycia", function()
build.skillsTab:PasteSocketGroup("Lightning Tendrils 20/0 1")
runCallback("OnFrame")
local baseDPS = build.calcsTab.calcsOutput.TotalDPS

build.skillsTab:PasteSocketGroup("Pact of Lycia 1/0 1")
runCallback("OnFrame")
assert.True(build.calcsTab.calcsOutput.LyciaUpTimeRatio > 0)
assert.True(build.calcsTab.calcsOutput.TotalDPS > baseDPS)

newBuild()
build.skillsTab:PasteSocketGroup("Fireball 20/0 1\nPact of Lycia 1/0 1")
runCallback("OnFrame")
assert.True(build.calcsTab.calcsOutput.LyciaUpTimeRatio == nil)

newBuild()
build.skillsTab:PasteSocketGroup("Vaal Arc 20/0 1\nPact of Lycia 1/0 1")
runCallback("OnFrame")
assert.True(build.calcsTab.calcsOutput.LyciaUpTimeRatio == nil)
end)
end)
3 changes: 3 additions & 0 deletions src/Data/SkillStatMap.lua
Original file line number Diff line number Diff line change
Expand Up @@ -2365,4 +2365,7 @@ return {
["is_totem"] = {
-- Display only
},
["pact_empower_limitation_specifier_for_stat_description"] = {
-- display only
},
}
318 changes: 318 additions & 0 deletions src/Data/Skills/other.lua

Large diffs are not rendered by default.

51 changes: 51 additions & 0 deletions src/Export/Skills/other.txt
Original file line number Diff line number Diff line change
Expand Up @@ -745,6 +745,57 @@ local skills, mod, flag, skill = ...
#baseMod skill("debuff", true)
#mods

#skill PactOfBeidat
#flags spell pact
statMap = {
["skill_empowers_next_x_spells_cast"] = {
mod("BeidatEmpoweredSpells", "BASE", nil),
},
["pact_skill_damage_+%_final_with_hits_and_ailments_to_grant"] = {
mod("BeidatPactDamage", "BASE", nil),
},
},
#mods

#skill PactOfGhorr
#flags spell pact duration
statMap = {
["skill_empowers_next_x_spells_cast"] = {
mod("GhorrEmpoweredSpells", "BASE", nil),
},
["pact_skill_grant_damage_over_time_+%_final_to_spells"] = {
mod("GhorrPactDamage", "BASE", nil),
},
},
#mods

#skill PactOfKtash
#flags spell pact
statMap = {
["skill_empowers_next_x_spells_cast"] = {
mod("KtashEmpoweredSpells", "BASE", nil),
},
["pact_skill_grant_damage_+%_final_to_exerted_skills"] = {
mod("KtashPactDamage", "BASE", nil),
},
["active_skill_osm_vaal_skill_soul_gain_prevention_+%_final_to_grant"] = {
mod("KtashPactSoulGainPrevention", "BASE", nil),
},
},
#mods

#skill PactOfLycia
#flags spell pact
statMap = {
["skill_empowers_next_x_spells_cast"] = {
mod("LyciaEmpoweredSpells", "BASE", nil),
},
["pact_skill_grant_damage_+%_final_to_exerted_skills"] = {
mod("LyciaPactDamage", "BASE", nil),
},
},
#mods

#skill Ravenous
#flags spell
fromTree = true,
Expand Down
131 changes: 131 additions & 0 deletions src/Modules/CalcOffence.lua
Original file line number Diff line number Diff line change
Expand Up @@ -2783,6 +2783,137 @@ function calcs.offence(env, actor, activeSkill)
globalOutput.ExertedAttackUptimeRatioCalculated = true
end
end

-- Pacts // Empowered Spells
if activeSkill.skillTypes[SkillType.Spell] and not activeSkill.skillTypes[SkillType.Instant] and not activeSkill.skillTypes[SkillType.Brand] then
for index, value in ipairs(actor.activeSkillList) do
if not activeSkill.skillTypes[SkillType.Vaal] then
if value.activeEffect.grantedEffect.name == "Pact of Beidat" and not globalOutput.PactOfBeidatCalculated and not value.skillFlags.disable
and (skillFlags.projectile or skillFlags.area or skillFlags.chaining) and not activeSkill.skillTypes[SkillType.Channel] then
globalOutput.CreatePactOffensiveCalcSection = true
globalOutput.PactOfBeidatDuration = calcSkillDuration(value.skillModList, value.skillCfg, value.skillData, env, enemyDB)
globalOutput.PactOfBeidatCooldown = calcSkillCooldown(value.skillModList, value.skillCfg, value.skillData)
globalOutput.PactOfBeidatCastTime = modDB:Sum("BASE", nil, "BeidatBaseCastTime")
globalOutput.BeidatEmpoweredCount = skillModList:Sum("BASE", nil, "BeidatEmpoweredSpells")
local baseUptimeRatio = m_min((globalOutput.BeidatEmpoweredCount / globalOutput.Speed) / (globalOutput.PactOfBeidatCooldown + globalOutput.PactOfBeidatCastTime), 1) * 100
local storedUses = value.skillData.storedUses + value.skillModList:Sum("BASE", value.skillCfg, "AdditionalCooldownUses")
globalOutput.BeidatUpTimeRatio = m_min(100, baseUptimeRatio * storedUses)
if globalBreakdown then
globalBreakdown.BeidatUpTimeRatio = { }
t_insert(globalBreakdown.BeidatUpTimeRatio, s_format("(%d ^8(number of Empowered)", globalOutput.BeidatEmpoweredCount))
t_insert(globalBreakdown.BeidatUpTimeRatio, s_format("/ %.2f) ^8(casts per second)", globalOutput.Speed))
if globalOutput.PactOfBeidatCastTime > 0 then
t_insert(globalBreakdown.BeidatUpTimeRatio, s_format("/ (%.2f ^8(pact cooldown)", globalOutput.PactOfBeidatCooldown))
t_insert(globalBreakdown.BeidatUpTimeRatio, s_format("+ %.2f) ^8(pact casttime)", globalOutput.PactOfBeidatCastTime))
else
t_insert(globalBreakdown.BeidatUpTimeRatio, s_format("/ %.2f ^8(average pact cooldown)", globalOutput.PactOfBeidatCooldown))
end
t_insert(globalBreakdown.BeidatUpTimeRatio, s_format("* %d ^8(stored uses)", storedUses))
t_insert(globalBreakdown.BeidatUpTimeRatio, s_format("= %d%%", globalOutput.BeidatUpTimeRatio))
end
local beidatDamage = modDB:Sum("BASE", nil, "BeidatPactDamage")
if beidatDamage > 0 then
local beidatUptime = skillModList:Flag(nil, "Condition:PactMaxHit") and 100 or globalOutput.BeidatUpTimeRatio
skillModList:NewMod("Damage", "MORE", beidatDamage * beidatUptime / 100, "Uptime Scaled Pact of Beidat", ModFlag.Spell, bor(KeywordFlag.Hit, KeywordFlag.Ailment))
end
globalOutput.PactOfBeidatCalculated = true
end
if value.activeEffect.grantedEffect.name == "Pact of Ghorr" and not globalOutput.PactOfGhorrCalculated and not value.skillFlags.disable
and activeSkill.skillTypes[SkillType.DamageOverTime] then
globalOutput.CreatePactOffensiveCalcSection = true
globalOutput.PactOfGhorrDuration = calcSkillDuration(value.skillModList, value.skillCfg, value.skillData, env, enemyDB)
globalOutput.PactOfGhorrCooldown = calcSkillCooldown(value.skillModList, value.skillCfg, value.skillData)
globalOutput.PactOfGhorrCastTime = modDB:Sum("BASE", nil, "GhorrBaseCastTime")
globalOutput.GhorrEmpoweredCount = skillModList:Sum("BASE", nil, "GhorrEmpoweredSpells")
local baseUptimeRatio = m_min((globalOutput.GhorrEmpoweredCount / globalOutput.Speed) / (globalOutput.PactOfGhorrCooldown + globalOutput.PactOfGhorrCastTime), 1) * 100
local storedUses = value.skillData.storedUses + value.skillModList:Sum("BASE", value.skillCfg, "AdditionalCooldownUses")
globalOutput.GhorrUpTimeRatio = m_min(100, baseUptimeRatio * storedUses)
if globalBreakdown then
globalBreakdown.GhorrUpTimeRatio = { }
t_insert(globalBreakdown.GhorrUpTimeRatio, s_format("(%d ^8(number of Empowered)", globalOutput.GhorrEmpoweredCount))
t_insert(globalBreakdown.GhorrUpTimeRatio, s_format("/ %.2f) ^8(casts per second)", globalOutput.Speed))
if globalOutput.PactOfGhorrCastTime > 0 then
t_insert(globalBreakdown.GhorrUpTimeRatio, s_format("/ (%.2f ^8(pact cooldown)", globalOutput.PactOfGhorrCooldown))
t_insert(globalBreakdown.GhorrUpTimeRatio, s_format("+ %.2f) ^8(pact casttime)", globalOutput.PactOfGhorrCastTime))
else
t_insert(globalBreakdown.GhorrUpTimeRatio, s_format("/ %.2f ^8(average pact cooldown)", globalOutput.PactOfGhorrCooldown))
end
t_insert(globalBreakdown.GhorrUpTimeRatio, s_format("* %d ^8(stored uses)", storedUses))
t_insert(globalBreakdown.GhorrUpTimeRatio, s_format("= %d%%", globalOutput.GhorrUpTimeRatio))
end
local ghorrDamage = modDB:Sum("BASE", nil, "GhorrPactDamage")
if ghorrDamage > 0 then
local ghorrUptime = skillModList:Flag(nil, "Condition:PactMaxHit") and 100 or globalOutput.GhorrUpTimeRatio
skillModList:NewMod("Damage", "MORE", ghorrDamage * ghorrUptime / 100, "Uptime Scaled Pact of Ghorr", ModFlag.Dot)
end
globalOutput.PactOfGhorrCalculated = true
end
if value.activeEffect.grantedEffect.name == "Pact of Lycia" and not globalOutput.PactOfLyciaCalculated and not value.skillFlags.disable
and activeSkill.skillTypes[SkillType.Channel] then
globalOutput.CreatePactOffensiveCalcSection = true
globalOutput.PactOfLyciaDuration = calcSkillDuration(value.skillModList, value.skillCfg, value.skillData, env, enemyDB)
globalOutput.PactOfLyciaCooldown = calcSkillCooldown(value.skillModList, value.skillCfg, value.skillData)
globalOutput.PactOfLyciaCastTime = modDB:Sum("BASE", nil, "LyciaBaseCastTime")
globalOutput.LyciaEmpoweredCount = skillModList:Sum("BASE", nil, "LyciaEmpoweredSpells")
local baseUptimeRatio = m_min((globalOutput.LyciaEmpoweredCount / globalOutput.Speed) / (globalOutput.PactOfLyciaCooldown + globalOutput.PactOfLyciaCastTime), 1) * 100
local storedUses = value.skillData.storedUses + value.skillModList:Sum("BASE", value.skillCfg, "AdditionalCooldownUses")
globalOutput.LyciaUpTimeRatio = m_min(100, baseUptimeRatio * storedUses)
if globalBreakdown then
globalBreakdown.LyciaUpTimeRatio = { }
t_insert(globalBreakdown.LyciaUpTimeRatio, s_format("(%d ^8(number of Empowered)", globalOutput.LyciaEmpoweredCount))
t_insert(globalBreakdown.LyciaUpTimeRatio, s_format("/ %.2f) ^8(casts per second)", globalOutput.Speed))
if globalOutput.PactOfLyciaCastTime > 0 then
t_insert(globalBreakdown.LyciaUpTimeRatio, s_format("/ (%.2f ^8(pact cooldown)", globalOutput.PactOfLyciaCooldown))
t_insert(globalBreakdown.LyciaUpTimeRatio, s_format("+ %.2f) ^8(pact casttime)", globalOutput.PactOfLyciaCastTime))
else
t_insert(globalBreakdown.LyciaUpTimeRatio, s_format("/ %.2f ^8(average pact cooldown)", globalOutput.PactOfLyciaCooldown))
end
t_insert(globalBreakdown.LyciaUpTimeRatio, s_format("* %d ^8(stored uses)", storedUses))
t_insert(globalBreakdown.LyciaUpTimeRatio, s_format("= %d%%", globalOutput.LyciaUpTimeRatio))
end
local lyciaDamage = modDB:Sum("BASE", nil, "LyciaPactDamage")
if lyciaDamage > 0 then
local lyciaUptime = skillModList:Flag(nil, "Condition:PactMaxHit") and 100 or globalOutput.LyciaUpTimeRatio
skillModList:NewMod("Damage", "MORE", lyciaDamage * lyciaUptime / 100, "Uptime Scaled Pact of Lycia", ModFlag.Spell)
end
globalOutput.PactOfLyciaCalculated = true
end
else
if value.activeEffect.grantedEffect.name == "Pact of K'Tash" and not globalOutput.PactOfKtashCalculated and not value.skillFlags.disable then
globalOutput.CreatePactOffensiveCalcSection = true
globalOutput.PactOfKtashDuration = calcSkillDuration(value.skillModList, value.skillCfg, value.skillData, env, enemyDB)
globalOutput.PactOfKtashCooldown = calcSkillCooldown(value.skillModList, value.skillCfg, value.skillData)
globalOutput.PactOfKtashCastTime = modDB:Sum("BASE", nil, "KtashBaseCastTime")
globalOutput.KtashEmpoweredCount = skillModList:Sum("BASE", nil, "KtashEmpoweredSpells")
local baseUptimeRatio = m_min((globalOutput.KtashEmpoweredCount / globalOutput.Speed) / (globalOutput.PactOfKtashCooldown + globalOutput.PactOfKtashCastTime), 1) * 100
local storedUses = value.skillData.storedUses + value.skillModList:Sum("BASE", value.skillCfg, "AdditionalCooldownUses")
globalOutput.KtashUpTimeRatio = m_min(100, baseUptimeRatio * storedUses)
if globalBreakdown then
globalBreakdown.KtashUpTimeRatio = { }
t_insert(globalBreakdown.KtashUpTimeRatio, s_format("(%d ^8(number of Empowered)", globalOutput.KtashEmpoweredCount))
t_insert(globalBreakdown.KtashUpTimeRatio, s_format("/ %.2f) ^8(casts per second)", globalOutput.Speed))
if globalOutput.PactOfKtashCastTime > 0 then
t_insert(globalBreakdown.KtashUpTimeRatio, s_format("/ (%.2f ^8(pact cooldown)", globalOutput.PactOfKtashCooldown))
t_insert(globalBreakdown.KtashUpTimeRatio, s_format("+ %.2f) ^8(pact casttime)", globalOutput.PactOfKtashCastTime))
else
t_insert(globalBreakdown.KtashUpTimeRatio, s_format("/ %.2f ^8(average pact cooldown)", globalOutput.PactOfKtashCooldown))
end
t_insert(globalBreakdown.KtashUpTimeRatio, s_format("* %d ^8(stored uses)", storedUses))
t_insert(globalBreakdown.KtashUpTimeRatio, s_format("= %d%%", globalOutput.KtashUpTimeRatio))
end
local ktashDamage = modDB:Sum("BASE", nil, "KtashPactDamage")
local ktashSoulGainPrevention = modDB:Sum("BASE", nil, "KtashPactSoulGainPrevention")
if ktashDamage > 0 then
local ktashUptime = skillModList:Flag(nil, "Condition:PactMaxHit") and 100 or globalOutput.KtashUpTimeRatio
skillModList:NewMod("Damage", "MORE", ktashDamage * ktashUptime / 100, "Uptime Scaled Pact of Ktash", ModFlag.Spell)
-- happens too late, duration is calculated earlier
-- skillModList:NewMod("SoulGainPreventionDuration", "MORE", KtashSoulGainPrevention * KtashUptime / 100, "Uptime Scaled Pact of Ktash")
end
globalOutput.PactOfKtashCalculated = true
end
end
end
end
end

output.RuthlessBlowHitEffect = 1
Expand Down
20 changes: 20 additions & 0 deletions src/Modules/CalcPerform.lua
Original file line number Diff line number Diff line change
Expand Up @@ -1430,6 +1430,26 @@ function calcs.perform(env, skipEHP)
modDB:NewMod("SeismicActive", "FLAG", true) -- Prevents effect from applying multiple times
end
end
local function calcCastTime(baseSpeed, skillModList, skillCfg, actor)
local castTime = baseSpeed / (calcLib.mod(skillModList, skillCfg, "Speed") * calcs.actionSpeedMod(actor))
castTime = m_min(castTime, data.misc.ServerTickRate)
return castTime
end
if env.mode_buffs and activeSkill.skillFlags.pact then
for _, pactName in ipairs({ "Beidat", "Ghorr", "Ktash", "Lycia" }) do
if (activeSkill.activeEffect.grantedEffect.name == (("Pact of "..pactName)) or
activeSkill.activeEffect.grantedEffect.name == "Pact of K'Tash" and pactName == "Ktash")
and not modDB:Flag(nil, pactName.."Active") then
env.player.modDB:NewMod(pactName.."PactDamage", "BASE", activeSkill.skillModList:Sum("BASE", env.player.mainSkill.skillCfg, pactName.."PactDamage"))
env.player.modDB:NewMod(pactName.."EmpoweredSpells", "BASE", activeSkill.skillModList:Sum("BASE", env.player.mainSkill.skillCfg, pactName.."EmpoweredSpells"))
env.player.modDB:NewMod(pactName.."BaseCastTime", "BASE", calcCastTime(activeSkill.activeEffect.grantedEffect.castTime, activeSkill.skillModList, activeSkill.skillCfg, env.player))
if pactName == "Ktash" then
env.player.modDB:NewMod("KtashPactSoulGainPrevention", "BASE", activeSkill.skillModList:Sum("BASE", env.player.mainSkill.skillCfg, "KtashPactSoulGainPrevention"))
end
modDB:NewMod(pactName.."Active", "FLAG", true) -- Prevents effect from applying multiple times
end
end
end
if activeSkill.skillData.triggeredOnDeath and not activeSkill.skillFlags.minion then
activeSkill.skillData.triggered = true
for _, value in ipairs(activeSkill.skillModList:Tabulate("INC", env.player.mainSkill.skillCfg, "TriggeredDamage")) do
Expand Down
40 changes: 40 additions & 0 deletions src/Modules/CalcSections.lua
Original file line number Diff line number Diff line change
Expand Up @@ -416,6 +416,46 @@ return {
},
} }
} },
{ 3, "Pacts", 1, colorCodes.OFFENCE, { { defaultCollapsed = false, label = "Empowered Spells", data = {
colWidth = 114,
{
haveOutput = "CreatePactOffensiveCalcSection",
{ format = "Effective Impact" },
{ format = "Avg Dmg Effect" },
{ format = "Uptime" },
{ format = "# of Empowered" },
{ format = "Max 1-Hit Impact" },
},
{ label = "Pact of Beidat", haveOutput = "BeidatUpTimeRatio",
{ format = "" },
{ format = "" },
{ format = "{0:output:BeidatUpTimeRatio}%", { breakdown = "BeidatUpTimeRatio" }, },
{ format = "{0:output:BeidatEmpoweredCount}" },
{ format = "" },
},
{ label = "Pact of Ghorr", haveOutput = "GhorrUpTimeRatio",
{ format = "" },
{ format = "" },
{ format = "{0:output:GhorrUpTimeRatio}%", { breakdown = "GhorrUpTimeRatio" }, },
{ format = "{0:output:GhorrEmpoweredCount}" },
{ format = "" },
},
{ label = "Pact of K'Tash", haveOutput = "KtashUpTimeRatio",
{ format = "" },
{ format = "" },
{ format = "{0:output:KtashUpTimeRatio}%", { breakdown = "KtashUpTimeRatio" }, },
{ format = "{0:output:KtashEmpoweredCount}" },
{ format = "" },
},
{ label = "Pact of Lycia", haveOutput = "LyciaUpTimeRatio",
{ format = "" },
{ format = "" },
{ format = "{0:output:LyciaUpTimeRatio}%", { breakdown = "LyciaUpTimeRatio" }, },
{ format = "{0:output:LyciaEmpoweredCount}" },
{ format = "" },
},
} }
} },
{ 3, "Dot", 1, colorCodes.OFFENCE, {{ defaultCollapsed = false, label = "Skill Damage over Time", data = {
extra = "{1:output:TotalDotCalcSection} total DoT",
flag = "dot",
Expand Down
Loading