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

Fix maximum sustainable stages calculations ignoring certain buffs/mods #7678

Merged
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
33 changes: 33 additions & 0 deletions spec/System/TestSkills_spec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -76,4 +76,37 @@ describe("TestAttacks", function()

assert.True(build.calcsTab.mainOutput.MirageDPS ~= nil)
end)

it("Test Scorching ray applying exposure at max stages", function()
build.skillsTab:PasteSocketGroup("Scorching Ray 20/0 Default 1\n")
runCallback("OnFrame")

local mainSocketGroup = build.skillsTab.socketGroupList[build.mainSocketGroup]
local srcInstance = mainSocketGroup.displaySkillList[mainSocketGroup.mainActiveSkill].activeEffect.srcInstance
srcInstance.skillStageCount = 8
build.modFlag = true
build.buildFlag = true
runCallback("OnFrame")

assert.True(build.calcsTab.mainEnv.enemyDB:Sum("BASE", nil, "FireResist") < 0)
end)

it("Test Adrenaline affecting blight max stage count", function()
build.skillsTab:PasteSocketGroup("Blight 20/0 Default 1\n")
runCallback("OnFrame")

local mainSocketGroup = build.skillsTab.socketGroupList[build.mainSocketGroup]
local srcInstance = mainSocketGroup.displaySkillList[mainSocketGroup.mainActiveSkill].activeEffect.srcInstance
srcInstance.skillPart = 2
build.modFlag = true
build.buildFlag = true
runCallback("OnFrame")

local preAdrenalineMaxStages = build.calcsTab.mainEnv.player.activeSkillList[1].skillModList:Sum("BASE", nil, "Multiplier:BlightMaxStages")
build.configTab.input.buffAdrenaline = true
build.configTab:BuildModList()
runCallback("OnFrame")

assert.True(preAdrenalineMaxStages < build.calcsTab.mainEnv.player.activeSkillList[1].skillModList:Sum("BASE", nil, "Multiplier:BlightMaxStages"))
end)
end)
188 changes: 51 additions & 137 deletions src/Modules/CalcPerform.lua
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,26 @@ local m_huge = math.huge
local bor = bit.bor
local band = bit.band

--- getCachedOutputValue
--- retrieves a value specified by key from a cached version of skill
--- specified by @uuid or if not found in cache computes teh cache.
--- @param env table
--- @param activeSkill table active skill to be used as main when calculating output values
--- @param ... table keys to values to be returned (Note: EmmyLua does not natively support documenting variadic parameters)
--- @return table unpacked table containing the desired values
local function getCachedOutputValue(env, activeSkill, ...)
local uuid = cacheSkillUUID(activeSkill, env)
if not GlobalCache.cachedData[env.mode][uuid] or env.mode == "CALCULATOR" then
calcs.buildActiveSkill(env, env.mode, activeSkill, uuid, {[uuid] = true})
end

local tempValues = {}
for i,v in ipairs({...}) do
tempValues[i] = GlobalCache.cachedData[env.mode][uuid].Env.player.output[v]
end
return unpack(tempValues)
end

-- Merge an instance of a buff, taking the highest value of each modifier
local function mergeBuff(src, destTable, destKey)
if not destTable[destKey] then
Expand Down Expand Up @@ -1778,6 +1798,37 @@ function calcs.perform(env, skipEHP)
end
end

-- To support maximum sustainable stages for the following skills we need to get the data from already
-- computed cached versions to satisfy the order of operations.
-- See: https://github.com/PathOfBuildingCommunity/PathOfBuilding/pull/5164
for _, activeSkill in ipairs(env.player.activeSkillList) do
if not activeSkill.skillFlags.disable and not activeSkill.skillData.limitedProcessing then
if (activeSkill.activeEffect.grantedEffect.name == "Blight" or activeSkill.activeEffect.grantedEffect.name == "Blight of Contagion" or activeSkill.activeEffect.grantedEffect.name == "Blight of Atrophy") and activeSkill.skillPart == 2 then
local rate, duration = getCachedOutputValue(env, activeSkill, "Speed", "Duration")
local baseMaxStages = activeSkill.skillModList:Sum("BASE", env.player.mainSkill.skillCfg, "BlightBaseMaxStages")
local maximum = m_min((m_floor(rate * duration) - 1), baseMaxStages - 1)
activeSkill.skillModList:NewMod("Multiplier:"..activeSkill.activeEffect.grantedEffect.name:gsub("%s+", "").."MaxStages", "BASE", maximum, "Base")
activeSkill.skillModList:NewMod("Multiplier:"..activeSkill.activeEffect.grantedEffect.name:gsub("%s+", "").."StageAfterFirst", "BASE", maximum, "Base")
end
if activeSkill.activeEffect.grantedEffect.name == "Penance Brand of Dissipation" and activeSkill.skillPart == 2 then
local activation_frequency, duration = getCachedOutputValue(env, activeSkill, "HitSpeed", "Duration") -- HitSpeed is the brand activation frequency
local ticks = m_min((m_floor(activation_frequency * duration) - 1), 19)
activeSkill.skillModList:NewMod("Multiplier:PenanceBrandofDissipationMaxStages", "BASE", ticks, "Base")
activeSkill.skillModList:NewMod("Multiplier:PenanceBrandofDissipationStageAfterFirst", "BASE", ticks, "Base")
end
if (activeSkill.activeEffect.grantedEffect.name == "Scorching Ray" or activeSkill.activeEffect.grantedEffect.name == "Scorching Ray of Immolation") and activeSkill.skillPart == 2 then
local maximum = 7
activeSkill.skillModList:NewMod("Multiplier:"..activeSkill.activeEffect.grantedEffect.name:gsub("%s+", "").."MaxStages", "BASE", maximum, "Base")
activeSkill.skillModList:NewMod("Multiplier:"..activeSkill.activeEffect.grantedEffect.name:gsub("%s+", "").."StageAfterFirst", "BASE", maximum, "Base")
end
if (activeSkill.activeEffect.grantedEffect.name == "Earthquake of Amplification") and activeSkill.skillPart == 2 then
local duration = getCachedOutputValue(env, activeSkill, "Duration")
local durationMulti = m_floor(duration * 10)
activeSkill.skillModList:NewMod("Multiplier:100msEarthquakeDuration", "BASE", durationMulti, "Skill:EarthquakeAltX")
end
end
end

local appliedCombustion = false
for _, activeSkill in ipairs(env.player.activeSkillList) do
local skillModList = activeSkill.skillModList
Expand Down Expand Up @@ -2641,143 +2692,6 @@ function calcs.perform(env, skipEHP)
end
end

local function processBuffDebuff(activeSkill)
local skillModList = activeSkill.skillModList
local skillCfg = activeSkill.skillCfg
local newBuffs = {}
local newDebuffs = {}
local newMinionBuffs = {}
for _, buff in ipairs(activeSkill.buffList) do
if buff.cond and not skillModList:GetCondition(buff.cond, skillCfg) then
elseif buff.enemyCond and not enemyDB:GetCondition(buff.enemyCond) then
elseif buff.type == "Buff" and not buffs[buff.name] then
if env.mode_buffs and (not activeSkill.skillFlags.totem or buff.allowTotemBuff) then
local skillCfg = buff.activeSkillBuff and skillCfg
local modStore = buff.activeSkillBuff and skillModList or modDB
if not buff.applyNotPlayer then
activeSkill.buffSkill = true
modDB.conditions["AffectedBy"..buff.name:gsub(" ","")] = true
local srcList = new("ModList")
local inc = modStore:Sum("INC", skillCfg, "BuffEffect", "BuffEffectOnSelf", "BuffEffectOnPlayer") + skillModList:Sum("INC", skillCfg, buff.name:gsub(" ", "").."Effect")
local more = modStore:More(skillCfg, "BuffEffect", "BuffEffectOnSelf")
srcList:ScaleAddList(buff.modList, (1 + inc / 100) * more)
mergeBuff(srcList, buffs, buff.name)
if activeSkill.skillData.thisIsNotABuff then
buffs[buff.name].notBuff = true
end
mergeBuff(srcList, newBuffs, buff.name)
if activeSkill.skillData.thisIsNotABuff then
newBuffs[buff.name].notBuff = true
end
end
if env.minion and (buff.applyMinions or buff.applyAllies) and not minionBuffs[buff.name] then
activeSkill.minionBuffSkill = true
env.minion.modDB.conditions["AffectedBy"..buff.name:gsub(" ","")] = true
local srcList = new("ModList")
local inc = modStore:Sum("INC", skillCfg, "BuffEffect") + env.minion.modDB:Sum("INC", nil, "BuffEffectOnSelf")
local more = modStore:More(skillCfg, "BuffEffect") * env.minion.modDB:More(nil, "BuffEffectOnSelf")
srcList:ScaleAddList(buff.modList, (1 + inc / 100) * more)
mergeBuff(srcList, minionBuffs, buff.name)
mergeBuff(srcList, newMinionBuffs, buff.name)
end
end
elseif (buff.type == "Debuff" or buff.type == "AuraDebuff") and not debuffs[buff.name] then
local stackCount
if buff.stackVar then
stackCount = skillModList:Sum("BASE", skillCfg, "Multiplier:"..buff.stackVar)
if buff.stackLimit then
stackCount = m_min(stackCount, buff.stackLimit)
elseif buff.stackLimitVar then
stackCount = m_min(stackCount, skillModList:Sum("BASE", skillCfg, "Multiplier:"..buff.stackLimitVar))
end
else
stackCount = activeSkill.skillData.stackCount or 1
end
if env.mode_effective and stackCount > 0 then
activeSkill.debuffSkill = true
modDB.conditions["AffectedBy"..buff.name:gsub(" ","")] = true
local srcList = new("ModList")
local mult = 1
if buff.type == "AuraDebuff" then
mult = 0
if not modDB:Flag(nil, "SelfAurasOnlyAffectYou") then
local inc = skillModList:Sum("INC", skillCfg, "AuraEffect", "BuffEffect", "DebuffEffect")
local more = skillModList:More(skillCfg, "AuraEffect", "BuffEffect", "DebuffEffect")
mult = (1 + inc / 100) * more
end
end
if buff.type == "Debuff" then
local inc = skillModList:Sum("INC", skillCfg, "DebuffEffect")
local more = skillModList:More(skillCfg, "DebuffEffect")
mult = (1 + inc / 100) * more
end
srcList:ScaleAddList(buff.modList, mult * stackCount)
if activeSkill.skillData.stackCount or buff.stackVar then
srcList:NewMod("Multiplier:"..buff.name.."Stack", "BASE", stackCount, buff.name)
end
mergeBuff(srcList, debuffs, buff.name)
mergeBuff(srcList, newDebuffs, buff.name)
end
end
end
-- Apply buff/debuff modifiers
for _, modList in pairs(newBuffs) do
modDB:AddList(modList)
if not modList.notBuff then
modDB.multipliers["BuffOnSelf"] = (modDB.multipliers["BuffOnSelf"] or 0) + 1
end
if env.minion then
for _, value in ipairs(modList:List(env.player.mainSkill.skillCfg, "MinionModifier")) do
if not value.type or env.minion.type == value.type then
env.minion.modDB:AddMod(value.mod)
end
end
end
end
if env.minion then
for _, modList in pairs(newMinionBuffs) do
env.minion.modDB:AddList(modList)
end
end
for _, modList in pairs(newDebuffs) do
enemyDB:AddList(modList)
end
end

for _, activeSkill in ipairs(env.player.activeSkillList) do -- Do another pass on the SkillList to catch effects of buffs, if needed
if not activeSkill.skillFlags.disable then
if (activeSkill.activeEffect.grantedEffect.name == "Blight" or activeSkill.activeEffect.grantedEffect.name == "Blight of Contagion" or activeSkill.activeEffect.grantedEffect.name == "Blight of Atrophy") and activeSkill.skillPart == 2 then
local rate = (1 / activeSkill.activeEffect.grantedEffect.castTime) * calcLib.mod(activeSkill.skillModList, activeSkill.skillCfg, "Speed") * calcs.actionSpeedMod(env.player)
local duration = calcSkillDuration(activeSkill.skillModList, activeSkill.skillCfg, activeSkill.skillData, env, enemyDB)
local baseMaxStages = activeSkill.skillModList:Sum("BASE", env.player.mainSkill.skillCfg, "BlightBaseMaxStages")
local maximum = m_min((m_floor(rate * duration) - 1), baseMaxStages - 1)
activeSkill.skillModList:NewMod("Multiplier:"..activeSkill.activeEffect.grantedEffect.name:gsub("%s+", "").."MaxStages", "BASE", maximum, "Base")
activeSkill.skillModList:NewMod("Multiplier:"..activeSkill.activeEffect.grantedEffect.name:gsub("%s+", "").."StageAfterFirst", "BASE", maximum, "Base")
processBuffDebuff(activeSkill)
end
if activeSkill.activeEffect.grantedEffect.name == "Penance Brand of Dissipation" and activeSkill.skillPart == 2 then
local rate = 1 / (activeSkill.skillData.repeatFrequency / (1 + env.player.mainSkill.skillModList:Sum("INC", env.player.mainSkill.skillCfg, "Speed", "BrandActivationFrequency") / 100) / activeSkill.skillModList:More(activeSkill.skillCfg, "BrandActivationFrequency"))
local duration = calcSkillDuration(activeSkill.skillModList, activeSkill.skillCfg, activeSkill.skillData, env, enemyDB)
local ticks = m_min((m_floor(rate * duration) - 1), 19)
activeSkill.skillModList:NewMod("Multiplier:PenanceBrandofDissipationMaxStages", "BASE", ticks, "Base")
activeSkill.skillModList:NewMod("Multiplier:PenanceBrandofDissipationStageAfterFirst", "BASE", ticks, "Base")
processBuffDebuff(activeSkill)
end
if (activeSkill.activeEffect.grantedEffect.name == "Scorching Ray" or activeSkill.activeEffect.grantedEffect.name == "Scorching Ray of Immolation") and activeSkill.skillPart == 2 then
local maximum = 7
activeSkill.skillModList:NewMod("Multiplier:"..activeSkill.activeEffect.grantedEffect.name:gsub("%s+", "").."MaxStages", "BASE", maximum, "Base")
activeSkill.skillModList:NewMod("Multiplier:"..activeSkill.activeEffect.grantedEffect.name:gsub("%s+", "").."StageAfterFirst", "BASE", maximum, "Base")
processBuffDebuff(activeSkill)
end
if (activeSkill.activeEffect.grantedEffect.name == "Earthquake of Amplification") and activeSkill.skillPart == 2 then
local full_duration = calcSkillDuration(activeSkill.skillModList, activeSkill.skillCfg, activeSkill.skillData, env, enemyDB)
local durationMulti = m_floor(full_duration * 10)
activeSkill.skillModList:NewMod("Multiplier:100msEarthquakeDuration", "BASE", durationMulti, "Skill:EarthquakeAltX")
processBuffDebuff(activeSkill)
end
end
end

-- Fix the configured impale stacks on the enemy
-- If the config is missing (blank), then use the maximum number of stacks
-- If the config is larger than the maximum number of stacks, replace it with the correct maximum
Expand Down
Loading