From 8eba3ee5249011771f8b43a38340082f77a2c46d Mon Sep 17 00:00:00 2001 From: LocalIdentity Date: Wed, 31 May 2023 14:32:54 +1000 Subject: [PATCH 1/2] Add support for triggered skills with Snipe Support --- src/Data/SkillStatMap.lua | 2 +- src/Data/Skills/act_dex.lua | 4 +-- src/Export/Skills/act_dex.txt | 4 +-- src/Modules/CalcOffence.lua | 5 +--- src/Modules/CalcPerform.lua | 46 +++++++++++++++++++++++++++++++++++ src/Modules/Common.lua | 1 + src/Modules/ModParser.lua | 8 +++--- 7 files changed, 56 insertions(+), 14 deletions(-) diff --git a/src/Data/SkillStatMap.lua b/src/Data/SkillStatMap.lua index cc2552a6e8..d20501b706 100644 --- a/src/Data/SkillStatMap.lua +++ b/src/Data/SkillStatMap.lua @@ -206,7 +206,7 @@ return { skill("triggeredWhileChannelling", true, { type = "SkillType", skillType = SkillType.Triggerable }, { type = "SkillType", skillType = SkillType.Spell }), }, ["skill_triggered_by_snipe"] = { - skill("triggered", true, { type = "SkillType", skillType = SkillType.Triggerable }), + skill("triggeredBySnipe", true, { type = "SkillType", skillType = SkillType.Triggerable }), }, ["triggered_by_spiritual_cry"] = { skill("triggeredByGeneralsCry", true, { type = "SkillType", skillType = SkillType.Melee }, { type = "SkillType", skillType = SkillType.Attack }), diff --git a/src/Data/Skills/act_dex.lua b/src/Data/Skills/act_dex.lua index 09546777bf..4b5baf7753 100644 --- a/src/Data/Skills/act_dex.lua +++ b/src/Data/Skills/act_dex.lua @@ -10797,8 +10797,8 @@ skills["ChannelledSnipe"] = { }, statDescriptionScope = "skill_stat_descriptions", castTime = 1, - initialFunc = function(activeSkill, output) - activeSkill.skillData.dpsMultiplier = 1 / math.min(math.max(activeSkill.skillModList:Sum("BASE", cfg, "Multiplier:SnipeStage"), 1), activeSkill.skillModList:Sum("BASE", cfg, "Multiplier:SnipeStagesMax")) + preDamageFunc = function(activeSkill, output) + activeSkill.skillData.hitTimeMultiplier = math.min(math.max(activeSkill.skillModList:Sum("BASE", activeSkill.skillCfg, "Multiplier:SnipeStage") - 0.5, 0.5), activeSkill.skillModList:Sum("BASE", activeSkill.skillCfg, "Multiplier:SnipeStagesMax")) --First stage takes 0.5x time to channel compared to subsequent stages end, statMap = { ["snipe_max_stacks"] = { diff --git a/src/Export/Skills/act_dex.txt b/src/Export/Skills/act_dex.txt index f5846606a0..5b7c60fc0a 100644 --- a/src/Export/Skills/act_dex.txt +++ b/src/Export/Skills/act_dex.txt @@ -2032,8 +2032,8 @@ local skills, mod, flag, skill = ... #skill ChannelledSnipe #flags attack projectile - initialFunc = function(activeSkill, output) - activeSkill.skillData.dpsMultiplier = 1 / math.min(math.max(activeSkill.skillModList:Sum("BASE", cfg, "Multiplier:SnipeStage"), 1), activeSkill.skillModList:Sum("BASE", cfg, "Multiplier:SnipeStagesMax")) + preDamageFunc = function(activeSkill, output) + activeSkill.skillData.hitTimeMultiplier = math.min(math.max(activeSkill.skillModList:Sum("BASE", activeSkill.skillCfg, "Multiplier:SnipeStage") - 0.5, 0.5), activeSkill.skillModList:Sum("BASE", activeSkill.skillCfg, "Multiplier:SnipeStagesMax")) --First stage takes 0.5x time to channel compared to subsequent stages end, statMap = { ["snipe_max_stacks"] = { diff --git a/src/Modules/CalcOffence.lua b/src/Modules/CalcOffence.lua index b4a7490d9b..5147831180 100644 --- a/src/Modules/CalcOffence.lua +++ b/src/Modules/CalcOffence.lua @@ -72,6 +72,7 @@ local function isTriggered(skillData) or skillData.triggeredByManaPercentSpent or skillData.triggeredByParentAttack or skillData.triggeredWhenHexEnds + or skillData.triggeredBySnipe end -- Calculate min/max damage for the given damage type @@ -766,10 +767,6 @@ function calcs.offence(env, actor, activeSkill) end end end - --Snipe doesn't grab the max stages multiplier from the gem when granted by Assailum so we add it back here - if skillModList:Flag(nil, "TriggeredByAssailum") and activeSkill.skillTypes[SkillType.Triggerable] then - skillModList:NewMod("Multiplier:SnipeStagesMax", "BASE", 6, "Snipe Max Stages", { type = "GlobalEffect", effectType = "Buff", unscalable = true }) - end if skillModList:Sum("BASE", nil, "CritMultiplierAppliesToDegen") > 0 then for i, value in ipairs(skillModList:Tabulate("BASE", skillCfg, "CritMultiplier")) do local mod = value.mod diff --git a/src/Modules/CalcPerform.lua b/src/Modules/CalcPerform.lua index 22e7e16738..6e66eba9ab 100644 --- a/src/Modules/CalcPerform.lua +++ b/src/Modules/CalcPerform.lua @@ -3597,6 +3597,52 @@ function calcs.perform(env, avoidCache, fullDPSSkipEHP) end end + -- Snipe Support + if env.player.mainSkill.skillData.triggeredBySnipe and not env.player.mainSkill.skillFlags.minion then + local triggerName = "Snipe" + local snipeStages = math.min(math.max(env.player.modDB:Sum("BASE", nil, "Multiplier:SnipeStage"), 0), env.player.modDB:Sum("BASE", nil, "Multiplier:SnipeStagesMax")) + local trigRate = 0 + local source = nil + for _, skill in ipairs(env.player.activeSkillList) do + if skill.activeEffect.grantedEffect.name == "Snipe" and skill ~= env.player.mainSkill and skill.socketGroup and skill.socketGroup.slot == env.player.mainSkill.socketGroup.slot then + source, trigRate = findTriggerSkill(env, skill, source, trigRate) + end + end + if not source or snipeStages < 1 then + env.player.mainSkill.skillData.triggeredBySnipe = nil + env.player.mainSkill.infoMessage = s_format("Not enough Snipe stages to Trigger Skill") + env.player.mainSkill.infoMessage2 = "DPS reported assuming Self-Cast" + env.player.mainSkill.infoTrigger = "" + else + env.player.mainSkill.skillData.triggered = true + + local uuid = cacheSkillUUID(source) + local sourceTime = GlobalCache.cachedData["CACHE"][uuid].HitSpeed + + output.ActionTriggerRate = getTriggerActionTriggerRate(env.player.mainSkill.skillData.cooldown, env, breakdown) + output.SourceTriggerRate = sourceTime + output.ServerTriggerRate = m_min(sourceTime, output.ActionTriggerRate) + + if breakdown then + breakdown.SimData = { + s_format("Trigger rate:"), + s_format("1 / %.2f ^8(Snipe channel time)", 1 / sourceTime), + 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.SourceTriggerRate + env.player.mainSkill.skillData.triggerSource = source + env.player.mainSkill.infoMessage = "Triggered by Snipe" + env.player.mainSkill.infoTrigger = triggerName + env.player.mainSkill.skillFlags.noDisplay = true + end + end -- Doom Blast (from Impending Doom) if env.player.mainSkill.skillData.triggeredWhenHexEnds and not env.player.mainSkill.skillFlags.minion then diff --git a/src/Modules/Common.lua b/src/Modules/Common.lua index 57401767fd..fd023e2f8d 100644 --- a/src/Modules/Common.lua +++ b/src/Modules/Common.lua @@ -712,6 +712,7 @@ function cacheData(uuid, env) GlobalCache.cachedData[mode][uuid] = { Name = env.player.mainSkill.activeEffect.grantedEffect.name, Speed = env.player.output.Speed, + HitSpeed = env.player.output.HitSpeed, ManaCost = env.player.output.ManaCost, LifeCost = env.player.output.LifeCost, ESCost = env.player.output.ESCost, diff --git a/src/Modules/ModParser.lua b/src/Modules/ModParser.lua index 1ea66bb546..7f44b2068a 100644 --- a/src/Modules/ModParser.lua +++ b/src/Modules/ModParser.lua @@ -2559,13 +2559,11 @@ local specialModList = { ["socketed gems gain (%d+)%% of physical damage as extra lightning damage"] = function(num) return { mod("ExtraSkillMod", "LIST", { mod = mod("PhysicalDamageGainAsLightning", "BASE", num) }, { type = "SocketedIn", slotName = "{SlotName}" }) } end, ["socketed red gems get (%d+)%% physical damage as extra fire damage"] = function(num) return { mod("ExtraSkillMod", "LIST", { mod = mod("PhysicalDamageGainAsFire", "BASE", num) }, { type = "SocketedIn", slotName = "{SlotName}", keyword = "strength" }) } end, ["socketed non%-channelling bow skills are triggered by snipe"] = { - mod("ExtraSkillMod", "LIST", { mod = flag("TriggeredByAssailum") }, { type = "SocketedIn", slotName = "{SlotName}", keyword = "bow" }, { type = "SkillType", skillType = SkillType.Triggerable }), - mod("ExtraSkillMod", "LIST", { mod = mod("SkillData", "LIST", { key = "showAverage", value = true }) }, { type = "SocketedIn", slotName = "{SlotName}", keyword = "bow" }, { type = "SkillType", skillType = SkillType.Triggerable }), - mod("ExtraSkillMod", "LIST", { mod = mod("SkillData", "LIST", { key = "triggered", value = 1 }) }, { type = "SocketedIn", slotName = "{SlotName}", keyword = "bow" }, { type = "SkillType", skillType = SkillType.Triggerable }), }, ["grants level (%d+) snipe skill"] = function(num) return { - mod("ExtraSupport", "LIST", { skillId = "ChannelledSnipeSupport", level = num }, { type = "SocketedIn", slotName = "{SlotName}" }) } - end, + mod("ExtraSkill", "LIST", { skillId = "ChannelledSnipe", level = num }), + mod("ExtraSupport", "LIST", { skillId = "ChannelledSnipeSupport", level = num }, { type = "SocketedIn", slotName = "{SlotName}" }), + } end, ["socketed triggered bow skills deal (%d+)%% less damage"] = function(num) return { mod("ExtraSkillMod", "LIST", { mod = mod("Damage", "MORE", -num) }, { type = "SocketedIn", slotName = "{SlotName}", keyword = "bow" }, { type = "SkillType", skillType = SkillType.Triggerable }) } end, ["socketed vaal skills require (%d+)%% less souls per use"] = function(num) return { mod("ExtraSkillMod", "LIST", { mod = mod("SoulCost", "MORE", -num) }, { type = "SocketedIn", slotName = "{SlotName}" }, { type = "SkillType", skillType = SkillType.Vaal }) } end, ["hits from socketed vaal skills ignore enemy monster resistances"] = { From 096cb93742ea7b5af771052905ad665f8b9775b9 Mon Sep 17 00:00:00 2001 From: LocalIdentity Date: Tue, 8 Aug 2023 15:00:02 +1000 Subject: [PATCH 2/2] Fix Snipe calcs for triggered skills and Assailum --- src/Data/SkillStatMap.lua | 8 ++--- src/Data/Skills/act_dex.lua | 3 -- src/Export/Skills/act_dex.txt | 3 -- src/Modules/CalcOffence.lua | 8 ++++- src/Modules/CalcPerform.lua | 56 +++++++++++++++++++++++++++++------ src/Modules/Common.lua | 1 - 6 files changed, 58 insertions(+), 21 deletions(-) diff --git a/src/Data/SkillStatMap.lua b/src/Data/SkillStatMap.lua index d20501b706..a0b6e600d0 100644 --- a/src/Data/SkillStatMap.lua +++ b/src/Data/SkillStatMap.lua @@ -1699,11 +1699,11 @@ return { ["channelled_skill_damage_+%"] = { mod("Damage", "INC", nil, 0, 0, { type = "SkillType", skillType = SkillType.Channel }), }, -["snipe_triggered_skill_hit_damage_+%_final_per_stage"] = { - mod("Damage", "MORE", nil, ModFlag.Hit, 0, { type = "Multiplier", var = "SnipeStage", limitVar = "SnipeStagesMax" }), -}, ["snipe_triggered_skill_ailment_damage_+%_final_per_stage"] = { - mod("Damage", "MORE", nil, ModFlag.Ailment, 0, { type = "Multiplier", var = "SnipeStage", limitVar = "SnipeStagesMax" }), + mod("snipeAilmentMulti", "BASE", nil), +}, +["snipe_triggered_skill_hit_damage_+%_final_per_stage"] = { + mod("snipeHitMulti", "BASE", nil), }, ["snipe_triggered_skill_damage_+%_final"] = { mod("Damage", "MORE", nil), diff --git a/src/Data/Skills/act_dex.lua b/src/Data/Skills/act_dex.lua index 4b5baf7753..f26a4811b6 100644 --- a/src/Data/Skills/act_dex.lua +++ b/src/Data/Skills/act_dex.lua @@ -10797,9 +10797,6 @@ skills["ChannelledSnipe"] = { }, statDescriptionScope = "skill_stat_descriptions", castTime = 1, - preDamageFunc = function(activeSkill, output) - activeSkill.skillData.hitTimeMultiplier = math.min(math.max(activeSkill.skillModList:Sum("BASE", activeSkill.skillCfg, "Multiplier:SnipeStage") - 0.5, 0.5), activeSkill.skillModList:Sum("BASE", activeSkill.skillCfg, "Multiplier:SnipeStagesMax")) --First stage takes 0.5x time to channel compared to subsequent stages - end, statMap = { ["snipe_max_stacks"] = { mod("Multiplier:SnipeStagesMax", "BASE", nil, 0, 0, { type = "GlobalEffect", effectType = "Buff", unscalable = true }), diff --git a/src/Export/Skills/act_dex.txt b/src/Export/Skills/act_dex.txt index 5b7c60fc0a..28acc46474 100644 --- a/src/Export/Skills/act_dex.txt +++ b/src/Export/Skills/act_dex.txt @@ -2032,9 +2032,6 @@ local skills, mod, flag, skill = ... #skill ChannelledSnipe #flags attack projectile - preDamageFunc = function(activeSkill, output) - activeSkill.skillData.hitTimeMultiplier = math.min(math.max(activeSkill.skillModList:Sum("BASE", activeSkill.skillCfg, "Multiplier:SnipeStage") - 0.5, 0.5), activeSkill.skillModList:Sum("BASE", activeSkill.skillCfg, "Multiplier:SnipeStagesMax")) --First stage takes 0.5x time to channel compared to subsequent stages - end, statMap = { ["snipe_max_stacks"] = { mod("Multiplier:SnipeStagesMax", "BASE", nil, 0, 0, { type = "GlobalEffect", effectType = "Buff", unscalable = true }), diff --git a/src/Modules/CalcOffence.lua b/src/Modules/CalcOffence.lua index 5147831180..67e41aaa59 100644 --- a/src/Modules/CalcOffence.lua +++ b/src/Modules/CalcOffence.lua @@ -2045,7 +2045,13 @@ function calcs.offence(env, actor, activeSkill) end elseif skillData.hitTimeMultiplier and output.Time and not skillData.triggeredOnDeath then output.HitTime = output.Time * skillData.hitTimeMultiplier - output.HitSpeed = 1 / output.HitTime + if output.Cooldown and skillData.triggered then + output.HitSpeed = 1 / (m_max(output.HitTime, output.Cooldown)) + elseif output.Cooldown then + output.HitSpeed = 1 / (output.HitTime + output.Cooldown) + else + output.HitSpeed = 1 / output.HitTime + end end -- Other Misc DPS multipliers (like custom source) diff --git a/src/Modules/CalcPerform.lua b/src/Modules/CalcPerform.lua index 6e66eba9ab..eb06dfbbc8 100644 --- a/src/Modules/CalcPerform.lua +++ b/src/Modules/CalcPerform.lua @@ -3598,35 +3598,72 @@ function calcs.perform(env, avoidCache, fullDPSSkipEHP) end -- Snipe Support - if env.player.mainSkill.skillData.triggeredBySnipe and not env.player.mainSkill.skillFlags.minion then + if env.player.mainSkill.skillData.triggeredBySnipe or env.player.mainSkill.activeEffect.grantedEffect.name == "Snipe" and not env.player.mainSkill.skillFlags.minion then local triggerName = "Snipe" - local snipeStages = math.min(math.max(env.player.modDB:Sum("BASE", nil, "Multiplier:SnipeStage"), 0), env.player.modDB:Sum("BASE", nil, "Multiplier:SnipeStagesMax")) + local snipeStages = math.min(env.player.modDB:Sum("BASE", nil, "Multiplier:SnipeStage"), env.player.modDB:Sum("BASE", nil, "Multiplier:SnipeStagesMax")) - 0.5 --First stage takes 0.5x time to channel compared to subsequent stages + local maxSnipeStages = env.player.modDB:Sum("BASE", nil, "Multiplier:SnipeStagesMax") - 0.5 + if env.player.mainSkill.marked then + if env.player.mainSkill.activeEffect.grantedEffect.name == "Snipe" then + local snipeHitMulti = env.player.mainSkill.skillModList:Sum("BASE", env.player.mainSkill.skillCfg, "snipeHitMulti") + local snipeAilmentMulti = env.player.mainSkill.skillModList:Sum("BASE", env.player.mainSkill.skillCfg, "snipeAilmentMulti") + env.player.mainSkill.skillModList:NewMod("Damage", "MORE", snipeHitMulti * (maxSnipeStages + 0.5), "Snipe", ModFlag.Hit, 0) + env.player.mainSkill.skillModList:NewMod("Damage", "MORE", snipeAilmentMulti * (maxSnipeStages + 0.5), "Snipe", ModFlag.Ailment, 0) + env.player.mainSkill.skillData.hitTimeMultiplier = maxSnipeStages + else + snipeStages = 0 --Triggered skills cannot be channeled by Mirage Archer + end + else + local snipeHitMulti = env.player.mainSkill.skillModList:Sum("BASE", env.player.mainSkill.skillCfg, "snipeHitMulti") + local snipeAilmentMulti = env.player.mainSkill.skillModList:Sum("BASE", env.player.mainSkill.skillCfg, "snipeAilmentMulti") + env.player.mainSkill.skillModList:NewMod("Damage", "MORE", snipeHitMulti * (snipeStages + 0.5), "Snipe", ModFlag.Hit, 0) + env.player.mainSkill.skillModList:NewMod("Damage", "MORE", snipeAilmentMulti * (snipeStages + 0.5), "Snipe", ModFlag.Ailment, 0) + end + if env.player.mainSkill.activeEffect.grantedEffect.name == "Snipe" and env.player.mainSkill.marked then + env.player.mainSkill.skillData.hitTimeMultiplier = env.player.modDB:Sum("BASE", nil, "Multiplier:SnipeStagesMax") - 0.5 + end + local triggerSkillCount = 0 + local triggerSkillPosition = 1 + local posFound = false local trigRate = 0 local source = nil for _, skill in ipairs(env.player.activeSkillList) do - if skill.activeEffect.grantedEffect.name == "Snipe" and skill ~= env.player.mainSkill and skill.socketGroup and skill.socketGroup.slot == env.player.mainSkill.socketGroup.slot then + if skill.activeEffect.grantedEffect.name == "Snipe" and env.player.mainSkill.activeEffect.grantedEffect.name ~= "Snipe" and skill ~= env.player.mainSkill and skill.socketGroup and skill.socketGroup.slot == env.player.mainSkill.socketGroup.slot then source, trigRate = findTriggerSkill(env, skill, source, trigRate) end + if skill.skillData.triggeredBySnipe and env.player.mainSkill.socketGroup.slot == skill.socketGroup.slot then + triggerSkillCount = triggerSkillCount + 1 + if skill ~= env.player.mainSkill and not posFound then + triggerSkillPosition = triggerSkillPosition + 1 + else + posFound = true + end + end end - if not source or snipeStages < 1 then + if env.player.mainSkill.activeEffect.grantedEffect.name == "Snipe" then + if triggerSkillCount > 0 then + env.player.mainSkill.skillData.baseMultiplier = 0 + env.player.mainSkill.infoMessage = "Triggering Support Skills:" + end + env.player.mainSkill.skillData.hitTimeMultiplier = snipeStages + elseif not source or (snipeStages + 0.5) < triggerSkillPosition then env.player.mainSkill.skillData.triggeredBySnipe = nil env.player.mainSkill.infoMessage = s_format("Not enough Snipe stages to Trigger Skill") - env.player.mainSkill.infoMessage2 = "DPS reported assuming Self-Cast" + env.player.mainSkill.infoMessage2 = "DPS reported assuming Self-Cast"..triggerSkillCount env.player.mainSkill.infoTrigger = "" else env.player.mainSkill.skillData.triggered = true local uuid = cacheSkillUUID(source) - local sourceTime = GlobalCache.cachedData["CACHE"][uuid].HitSpeed + local sourceTime = GlobalCache.cachedData["CACHE"][uuid].Speed output.ActionTriggerRate = getTriggerActionTriggerRate(env.player.mainSkill.skillData.cooldown, env, breakdown) output.SourceTriggerRate = sourceTime - output.ServerTriggerRate = m_min(sourceTime, output.ActionTriggerRate) + output.ServerTriggerRate = m_min(output.SourceTriggerRate, output.ActionTriggerRate) if breakdown then breakdown.SimData = { s_format("Trigger rate:"), - s_format("1 / %.2f ^8(Snipe channel time)", 1 / sourceTime), + s_format("1 / %.2f ^8(Snipe channel time)", snipeStages / sourceTime), s_format("= %.2f ^8per second", output.SourceTriggerRate), } breakdown.ServerTriggerRate = { @@ -3636,8 +3673,9 @@ function calcs.perform(env, avoidCache, fullDPSSkipEHP) -- Account for Trigger-related INC/MORE modifiers addTriggerIncMoreMods(env.player.mainSkill, env.player.mainSkill) - env.player.mainSkill.skillData.triggerRate = output.SourceTriggerRate + env.player.mainSkill.skillData.triggerRate = sourceTime env.player.mainSkill.skillData.triggerSource = source + env.player.mainSkill.skillData.hitTimeMultiplier = snipeStages env.player.mainSkill.infoMessage = "Triggered by Snipe" env.player.mainSkill.infoTrigger = triggerName env.player.mainSkill.skillFlags.noDisplay = true diff --git a/src/Modules/Common.lua b/src/Modules/Common.lua index fd023e2f8d..57401767fd 100644 --- a/src/Modules/Common.lua +++ b/src/Modules/Common.lua @@ -712,7 +712,6 @@ function cacheData(uuid, env) GlobalCache.cachedData[mode][uuid] = { Name = env.player.mainSkill.activeEffect.grantedEffect.name, Speed = env.player.output.Speed, - HitSpeed = env.player.output.HitSpeed, ManaCost = env.player.output.ManaCost, LifeCost = env.player.output.LifeCost, ESCost = env.player.output.ESCost,