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
4 changes: 4 additions & 0 deletions src/Data/SkillStatMap.lua
Original file line number Diff line number Diff line change
Expand Up @@ -1639,6 +1639,10 @@ return {
["curse_maximum_doom"] = {
mod("MaxDoom", "BASE", nil),
},
["triggered_vicious_hex_explosion"] = {
skill("triggeredWhenHexEnds", true, { type = "SkillType", skillType = SkillType.Triggerable }, { type = "SkillType", skillType = SkillType.Spell }),
},

-- Aura
["non_curse_aura_effect_+%"] = {
mod("AuraEffect", "INC", nil, 0, 0, { type = "SkillType", skillType = SkillType.AppliesCurse, neg = true }),
Expand Down
10 changes: 9 additions & 1 deletion src/Data/Skills/sup_int.lua
Original file line number Diff line number Diff line change
Expand Up @@ -2805,13 +2805,21 @@ skills["ViciousHexExplosion"] = {
skillTypes = { [SkillType.Spell] = true, [SkillType.Area] = true, [SkillType.Damage] = true, [SkillType.Triggerable] = true, [SkillType.Triggered] = true, [SkillType.AreaSpell] = true, [SkillType.Chaos] = true, [SkillType.Cooldown] = true, [SkillType.InbuiltTrigger] = true, [SkillType.SkillGrantedBySupport] = true, },
statDescriptionScope = "skill_stat_descriptions",
castTime = 1,
parts = {
{
name = "No Overlaps",
},
{
name = "Overlaps (# hits per cast)",
stages = true,
}
},
baseFlags = {
spell = true,
area = true,
},
baseMods = {
skill("radius", 20),
skill("showAverage", true),
},
qualityStats = {
Default = {
Expand Down
2 changes: 1 addition & 1 deletion src/Data/Uniques/gloves.lua
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ Vixen's Entrapment
Embroidered Gloves
Requires Level 36, 54 Int
Implicits: 0
Trigger Socketed Curse Spells when you cast a Curse
Trigger Socketed Curse Spell when you Cast a Curse Spell, with a 0.25 second Cooldown
+(50-90) to maximum Energy Shield
0.2% of Spell Damage Leeched as Energy Shield for each Curse on Enemy
You can apply an additional Curse
Expand Down
10 changes: 9 additions & 1 deletion src/Export/Skills/sup_int.txt
Original file line number Diff line number Diff line change
Expand Up @@ -356,8 +356,16 @@ local skills, mod, flag, skill = ...

#skill ViciousHexExplosion
#flags spell area
parts = {
{
name = "No Overlaps",
},
{
name = "Overlaps",
stages = true,
}
}
#baseMod skill("radius", 20)
#baseMod skill("showAverage", true)
#mods

#skill SupportIncreasedAreaOfEffect
Expand Down
46 changes: 35 additions & 11 deletions src/Modules/CalcOffence.lua
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,21 @@ end })
local globalOutput = nil
local globalBreakdown = nil

local function isTriggered(skillData)
return skillData.triggeredWhileChannelling
or skillData.triggeredByCoC
or skillData.triggeredByMeleeKill
or skillData.triggeredByCospris
or skillData.triggeredByMjolner
or skillData.triggeredByUnique
or skillData.triggeredByFocus
or skillData.triggeredByCraft
or skillData.triggeredByManaSpent
or skillData.triggeredByManaPercentSpent
or skillData.triggeredByParentAttack
or skillData.triggeredWhenHexEnds
end

-- 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 @@ -262,8 +277,15 @@ end
function calcSkillCooldown(skillModList, skillCfg, skillData)
local cooldownOverride = skillModList:Override(skillCfg, "CooldownRecovery")
local cooldown = cooldownOverride or (skillData.cooldown + skillModList:Sum("BASE", skillCfg, "CooldownRecovery")) / m_max(0, calcLib.mod(skillModList, skillCfg, "CooldownRecovery"))
cooldown = m_ceil(cooldown * data.misc.ServerTickRate) / data.misc.ServerTickRate
return cooldown
-- If a skill can store extra uses and has a cooldown, it doesn't round the cooldown value to server ticks
local rounded = false
if (skillData.storedUses and skillData.storedUses > 1) or (skillData.VaalStoredUses and skillData.VaalStoredUses > 1) or skillModList:Sum("BASE", skillCfg, "AdditionalCooldownUses") > 0 then
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You could probably move the stored uses part before this so you could just have.

Suggested change
if (skillData.storedUses and skillData.storedUses > 1) or (skillData.VaalStoredUses and skillData.VaalStoredUses > 1) or skillModList:Sum("BASE", skillCfg, "AdditionalCooldownUses") > 0 then
if (skillData.storedUses and skillData.storedUses > 1) or (skillData.VaalStoredUses and skillData.VaalStoredUses > 1) then

return cooldown, rounded
else
cooldown = m_ceil(cooldown * data.misc.ServerTickRate) / data.misc.ServerTickRate
rounded = true
return cooldown, rounded
end
end

local function calcWarcryCastTime(skillModList, skillCfg, actor)
Expand Down Expand Up @@ -403,8 +425,8 @@ function calcs.offence(env, actor, activeSkill)

runSkillFunc("initialFunc")

local isTriggered = skillData.triggeredWhileChannelling or skillData.triggeredByCoC or skillData.triggeredByMeleeKill or skillData.triggeredByCospris or skillData.triggeredByMjolner or skillData.triggeredByUnique or skillData.triggeredByFocus or skillData.triggeredByCraft or skillData.triggeredByManaSpent or skillData.triggeredByParentAttack or skillData.triggeredByManaPercentSpent
skillCfg.skillCond["SkillIsTriggered"] = skillData.triggered or isTriggered
local triggered = isTriggered(skillData)
skillCfg.skillCond["SkillIsTriggered"] = skillData.triggered or triggered
if skillCfg.skillCond["SkillIsTriggered"] then
skillFlags.triggered = true
end
Expand Down Expand Up @@ -1024,14 +1046,17 @@ function calcs.offence(env, actor, activeSkill)
breakdown.TrapTriggerRadius = breakdown.area(data.misc.TrapTriggerRadiusBase, areaMod, output.TrapTriggerRadius, incAreaBreakpoint, moreAreaBreakpoint, redAreaBreakpoint, lessAreaBreakpoint)
end
elseif skillData.cooldown then
output.Cooldown = calcSkillCooldown(skillModList, skillCfg, skillData)
local cooldown, rounded = calcSkillCooldown(skillModList, skillCfg, skillData)
output.Cooldown = cooldown
if breakdown then
breakdown.Cooldown = {
s_format("%.2fs ^8(base)", skillData.cooldown + skillModList:Sum("BASE", skillCfg, "CooldownRecovery")),
s_format("/ %.2f ^8(increased/reduced cooldown recovery)", 1 + skillModList:Sum("INC", skillCfg, "CooldownRecovery") / 100),
s_format("rounded up to nearest server tick"),
s_format("= %.3fs", output.Cooldown)
}
if rounded then
t_insert(breakdown.Cooldown, s_format("rounded up to nearest server tick"))
end
t_insert(breakdown.Cooldown, s_format("= %.3fs", output.Cooldown))
end
end
if skillData.storedUses then
Expand Down Expand Up @@ -3155,7 +3180,7 @@ function calcs.offence(env, actor, activeSkill)
s_format("%.1f ^8(average damage)", output.AverageDamage),
output.HitSpeed and s_format("x %.2f ^8(hit rate)", output.HitSpeed) or s_format("x %.2f ^8(attack rate)", output.Speed),
}
elseif isTriggered then
elseif triggered then
breakdown.TotalDPS = {
s_format("%.1f ^8(average damage)", output.AverageDamage),
output.HitSpeed and s_format("x %.2f ^8(hit rate)", output.HitSpeed) or s_format("x %.2f ^8(trigger rate)", output.Speed),
Expand All @@ -3177,7 +3202,7 @@ function calcs.offence(env, actor, activeSkill)
local rateType = "cast"
if isAttack then
rateType = "attack"
elseif isTriggered then
elseif triggered then
rateType = "trigger"
end
breakdown.PvpTotalDPS = {
Expand Down Expand Up @@ -4642,7 +4667,6 @@ function calcs.offence(env, actor, activeSkill)
local repeats = output.Repeats or 1
local useSpeed = 1
local timeType
local isTriggered = skillData.triggeredWhileChannelling or skillData.triggeredByCoC or skillData.triggeredByMeleeKill or skillData.triggeredByCospris or skillData.triggeredByMjolner or skillData.triggeredByUnique or skillData.triggeredByFocus or skillData.triggeredByCraft or skillData.triggeredByManaSpent or skillData.triggeredByParentAttack or skillData.triggeredByManaPercentSpent
if skillFlags.trap or skillFlags.mine then
local preSpeed = output.TrapThrowingSpeed or output.MineLayingSpeed
local cooldown = output.TrapCooldown or output.Cooldown
Expand All @@ -4656,7 +4680,7 @@ function calcs.offence(env, actor, activeSkill)
timeType = "full unleash"
else
useSpeed = (output.Cooldown and output.Cooldown > 0 and (output.Speed > 0 and output.Speed or 1 / output.Cooldown) or output.Speed) / repeats
timeType = isTriggered and "trigger" or (skillFlags.totem and "totem placement" or skillFlags.attack and "attack" or "cast")
timeType = isTriggered(skillData) and "trigger" or (skillFlags.totem and "totem placement" or skillFlags.attack and "attack" or "cast")
end

output[usedResource.."PerSecondHasCost"] = true
Expand Down
75 changes: 75 additions & 0 deletions src/Modules/CalcPerform.lua
Original file line number Diff line number Diff line change
Expand Up @@ -3275,6 +3275,81 @@ function calcs.perform(env, avoidCache, fullDPSSkipEHP)
end


-- Doom Blast (from Impending Doom)
if env.player.mainSkill.skillData.triggeredWhenHexEnds and not env.player.mainSkill.skillFlags.minion then
local source = nil
local hexCastRate = 0

for _, skill in ipairs(env.player.activeSkillList) do
local match1 = env.player.mainSkill.activeEffect.grantedEffect.fromItem and skill.socketGroup.slot == env.player.mainSkill.socketGroup.slot
local match2 = (not env.player.mainSkill.activeEffect.grantedEffect.fromItem) and skill.socketGroup == env.player.mainSkill.socketGroup
if skill.skillTypes[SkillType.Hex] and (match1 or match2) then
source, hexCastRate = findTriggerSkill(env, skill, source, hexCastRate)
end
end

if not source then
env.player.mainSkill.skillData.triggeredWhenHexEnds = nil
env.player.mainSkill.infoMessage = "No Triggering Hex Found"
env.player.mainSkill.infoTrigger = ""
else
env.player.mainSkill.skillData.triggered = true

local vixen_trigger_cap = modDB:Flag(nil, "VixenTriggerCap")
if vixen_trigger_cap then
local curseTriggerCooldown = env.player.modDB:Sum("BASE", nil, "VixensCurseOnCurseCooldown")
local maxVixenTriggerRate = getTriggerActionTriggerRate(curseTriggerCooldown, env)

if hexCastRate > maxVixenTriggerRate then
hexCastRate = hexCastRate - m_ceil(hexCastRate - maxVixenTriggerRate)
end
end

-- Doom Blast stores three uses. If we assume that the spell is triggered again before all three
-- charges have been restored, we can ignore the rate cap imposed on other skills (there are no breakpoints).
-- That is why we have separated this piece of code rather than calling getTriggerActionTriggerRate().
local baseActionCooldown = env.player.mainSkill.skillData.cooldown
local icdr = calcLib.mod(env.player.mainSkill.skillModList, env.player.mainSkill.skillCfg, "CooldownRecovery")
local modActionCooldown = baseActionCooldown / icdr
if breakdown then
breakdown.ActionTriggerRate = {
s_format("%.2f ^8(base cooldown of triggered skill)", baseActionCooldown),
s_format("/ %.2f ^8(increased/reduced cooldown recovery)", icdr),
s_format("= %.4f ^8(final cooldown of trigger)", modActionCooldown),
s_format(""),
s_format("Trigger rate:"),
s_format("1 / %.3f", modActionCooldown),
s_format("= %.2f ^8per second", 1 / modActionCooldown),
}
end

-- Set trigger rate
local hits_per_cast = env.player.mainSkill.skillPart == 2 and env.player.mainSkill.activeEffect.srcInstance.skillStageCount or 1
output.ActionTriggerRate = 1 / modActionCooldown
output.SourceTriggerRate = hexCastRate * hits_per_cast
output.ServerTriggerRate = m_min(output.SourceTriggerRate, output.ActionTriggerRate)
if breakdown then
breakdown.SourceTriggerRate = {
s_format("%.2f ^8(%s %s)", hexCastRate, vixen_trigger_cap and "Vixen's Entrapment" or source.activeEffect.grantedEffect.name, vixen_trigger_cap and "trigger rate" or "casts per second"),
s_format("* %.2f ^8(hits per cast from overlaps)", hits_per_cast),
s_format("= %.2f ^8per second", output.SourceTriggerRate),
}

breakdown.ServerTriggerRate = {
s_format("%.2f ^8(smaller of 'cap' and 'skill' trigger rates)", output.ServerTriggerRate),
}
end

-- Account for Trigger-related INC/MORE modifiers
addTriggerIncMoreMods(env.player.mainSkill, env.player.mainSkill)
env.player.mainSkill.skillData.triggerRate = output.ServerTriggerRate
env.player.mainSkill.skillData.triggerSource = source
env.player.mainSkill.infoMessage = "Triggering Hex: " .. source.activeEffect.grantedEffect.name
env.player.mainSkill.infoTrigger = "DoomBlast"
end
end


-- Triggered by parent attack
if env.minion and env.player.mainSkill.minion then
if env.minion.mainSkill.skillData.triggeredByParentAttack then
Expand Down
3 changes: 3 additions & 0 deletions src/Modules/ConfigOptions.lua
Original file line number Diff line number Diff line change
Expand Up @@ -1322,6 +1322,9 @@ Huge sets the radius to 11.
{ var = "conditionHaveManaStorm", type = "check", label = "Do you have Manastorm's ^xADAA47Lightning ^7Buff?", ifFlag = "Condition:HaveManaStorm", tooltip = "This option enables Manastorm's ^xADAA47Lightning ^7Damage Buff.\n(When you cast a Spell, Sacrifice all ^x7070FFMana ^7to gain Added Maximum ^xADAA47Lightning ^7Damage\nequal to 25% of Sacrificed ^x7070FFMana ^7for 4 seconds)", apply = function(val, modList, enemyModList)
modList:NewMod("Condition:SacrificeManaForLightning", "FLAG", true, "Config", { type = "Condition", var = "Combat" })
end },
{ var = "conditionHaveVixensEntrapment", type = "check", defaultState = true, label = "Use Vixen's Entrapment cooldown?", ifFlag = "Condition:HaveVixensEntrapment", tooltip = "Causes Impending Doom calculations to take the cooldown of Vixen's Entrapment into account,\nlowering DPS if cast rate is higher than the gloves' trigger cooldown.", apply = function(val, modList, enemyModList)
modList:NewMod("VixenTriggerCap", "FLAG", true, "Config", { type = "Condition", var = "Combat" })
end },
{ var = "buffFanaticism", type = "check", label = "Do you have Fanaticism?", ifFlag = "Condition:CanGainFanaticism", tooltip = "This will enable the Fanaticism buff itself. (Grants 75% more cast speed, reduced skill cost, and increased area of effect)", apply = function(val, modList, enemyModList)
modList:NewMod("Condition:Fanaticism", "FLAG", true, "Config", { type = "Condition", var = "Combat" }, { type = "Condition", var = "CanGainFanaticism" })
end },
Expand Down
4 changes: 4 additions & 0 deletions src/Modules/ModParser.lua
Original file line number Diff line number Diff line change
Expand Up @@ -4021,6 +4021,10 @@ local specialModList = {
["every 8 seconds, gain avatar of fire for 4 seconds"] = {
flag("Condition:HaveVulconus"),
},
["trigger socketed curse spell when you cast a curse spell, with a ([%d%.]+) second cooldown"] = function(cooldown) return {
flag("Condition:HaveVixensEntrapment"),
mod("VixensCurseOnCurseCooldown", "BASE", cooldown, "Vixen's Entrapment"),
} end,
["modifiers to attributes instead apply to omniscience"] = { flag("Omniscience") },
["attribute requirements can be satisfied by (%d+)%% of omniscience"] = function(num) return {
mod("OmniAttributeRequirements", "INC", num),
Expand Down