3395 lines
166 KiB
Lua
3395 lines
166 KiB
Lua
-- Path of Building
|
|
--
|
|
-- Module: Calc Perform
|
|
-- Manages the offence/defence calculations.
|
|
--
|
|
local calcs = ...
|
|
|
|
local pairs = pairs
|
|
local ipairs = ipairs
|
|
local t_insert = table.insert
|
|
local t_remove = table.remove
|
|
local m_min = math.min
|
|
local m_max = math.max
|
|
local m_ceil = math.ceil
|
|
local m_floor = math.floor
|
|
local m_modf = math.modf
|
|
local s_format = string.format
|
|
local m_huge = math.huge
|
|
local bor = bit.bor
|
|
local band = bit.band
|
|
local bnot = bit.bnot
|
|
|
|
--- 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
|
|
destTable[destKey] = new("ModList")
|
|
end
|
|
local dest = destTable[destKey]
|
|
for _, mod in ipairs(src) do
|
|
local match = false
|
|
if mod.type ~= "LIST" then
|
|
for index, destMod in ipairs(dest) do
|
|
if modLib.compareModParams(mod, destMod) then
|
|
if type(destMod.value) == "number" and mod.value > destMod.value then
|
|
dest[index] = mod
|
|
end
|
|
match = true
|
|
break
|
|
end
|
|
end
|
|
end
|
|
if not match then
|
|
t_insert(dest, mod)
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Merge keystone modifiers
|
|
local function mergeKeystones(env)
|
|
for _, modObj in ipairs(env.modDB:Tabulate("LIST", nil, "Keystone")) do
|
|
if not env.keystonesAdded[modObj.value] and env.spec.tree.keystoneMap[modObj.value] then
|
|
env.keystonesAdded[modObj.value] = true
|
|
local fromTree = modObj.mod.source and not modObj.mod.source:lower():match("tree")
|
|
for _, mod in ipairs(env.spec.tree.keystoneMap[modObj.value].modList) do
|
|
env.modDB:AddMod(fromTree and modLib.setSource(mod, modObj.mod.source) or mod)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
function doActorLifeMana(actor)
|
|
local modDB = actor.modDB
|
|
local output = actor.output
|
|
local breakdown = actor.breakdown
|
|
local condList = modDB.conditions
|
|
|
|
local lowLifePerc = modDB:Sum("BASE", nil, "LowLifePercentage")
|
|
output.LowLifePercentage = 100.0 * (lowLifePerc > 0 and lowLifePerc or data.misc.LowPoolThreshold)
|
|
local fullLifePerc = modDB:Sum("BASE", nil, "FullLifePercentage")
|
|
output.FullLifePercentage = 100.0 * (fullLifePerc > 0 and fullLifePerc or 1.0)
|
|
|
|
output.ChaosInoculation = modDB:Flag(nil, "ChaosInoculation")
|
|
-- Life/mana pools
|
|
if output.ChaosInoculation then
|
|
output.Life = 1
|
|
condList["FullLife"] = true
|
|
else
|
|
local base = modDB:Sum("BASE", nil, "Life")
|
|
local inc = modDB:Sum("INC", nil, "Life")
|
|
local more = modDB:More(nil, "Life")
|
|
local conv = modDB:Sum("BASE", nil, "LifeConvertToEnergyShield")
|
|
output.Life = m_max(round(base * (1 + inc/100) * more * (1 - conv/100)), 1)
|
|
if breakdown then
|
|
if inc ~= 0 or more ~= 1 or conv ~= 0 then
|
|
breakdown.Life = { }
|
|
breakdown.Life[1] = s_format("%g ^8(base)", base)
|
|
if inc ~= 0 then
|
|
t_insert(breakdown.Life, s_format("x %.2f ^8(increased/reduced)", 1 + inc/100))
|
|
end
|
|
if more ~= 1 then
|
|
t_insert(breakdown.Life, s_format("x %.2f ^8(more/less)", more))
|
|
end
|
|
if conv ~= 0 then
|
|
t_insert(breakdown.Life, s_format("x %.2f ^8(converted to Energy Shield)", 1 - conv/100))
|
|
end
|
|
t_insert(breakdown.Life, s_format("= %g", output.Life))
|
|
end
|
|
end
|
|
end
|
|
local manaConv = modDB:Sum("BASE", nil, "ManaConvertToArmour")
|
|
output.Mana = round(calcLib.val(modDB, "Mana") * (1 - manaConv / 100))
|
|
local base = modDB:Sum("BASE", nil, "Mana")
|
|
local inc = modDB:Sum("INC", nil, "Mana")
|
|
local more = modDB:More(nil, "Mana")
|
|
if breakdown then
|
|
if inc ~= 0 or more ~= 1 or manaConv ~= 0 then
|
|
breakdown.Mana = { }
|
|
breakdown.Mana[1] = s_format("%g ^8(base)", base)
|
|
if inc ~= 0 then
|
|
t_insert(breakdown.Mana, s_format("x %.2f ^8(increased/reduced)", 1 + inc/100))
|
|
end
|
|
if more ~= 1 then
|
|
t_insert(breakdown.Mana, s_format("x %.2f ^8(more/less)", more))
|
|
end
|
|
if manaConv ~= 0 then
|
|
t_insert(breakdown.Mana, s_format("x %.2f ^8(converted to Armour)", 1 - manaConv/100))
|
|
end
|
|
t_insert(breakdown.Mana, s_format("= %g", output.Mana))
|
|
end
|
|
end
|
|
output.LowestOfMaximumLifeAndMaximumMana = m_min(output.Life, output.Mana)
|
|
end
|
|
|
|
-- Calculate attributes, and set conditions
|
|
---@param env table
|
|
---@param actor table
|
|
local function doActorAttribsConditions(env, actor)
|
|
local modDB = actor.modDB
|
|
local output = actor.output
|
|
local breakdown = actor.breakdown
|
|
local condList = modDB.conditions
|
|
|
|
-- Set conditions
|
|
if (actor.itemList["Weapon 2"] and actor.itemList["Weapon 2"].type == "Shield") or (actor == env.player and env.aegisModList) then
|
|
condList["UsingShield"] = true
|
|
elseif not actor.itemList["Weapon 2"] then
|
|
condList["OffHandIsEmpty"] = true
|
|
end
|
|
if actor.weaponData1.type == "None" then
|
|
condList["Unarmed"] = true
|
|
if not actor.itemList["Weapon 2"] and not actor.itemList["Gloves"] then
|
|
condList["Unencumbered"] = true
|
|
end
|
|
else
|
|
local info = env.data.weaponTypeInfo[actor.weaponData1.type]
|
|
condList["Using"..info.flag] = true
|
|
if actor.weaponData1.countsAsAll1H then
|
|
actor.weaponData1["AddedUsingAxe"] = not condList["UsingAxe"]
|
|
condList["UsingAxe"] = true
|
|
actor.weaponData1["AddedUsingSword"] = actor.weaponData1.name:match("Varunastra") or not condList["UsingSword"] --Varunastra is a sword
|
|
condList["UsingSword"] = true
|
|
actor.weaponData1["AddedUsingDagger"] = not condList["UsingDagger"]
|
|
condList["UsingDagger"] = true
|
|
actor.weaponData1["AddedUsingMace"] = not condList["UsingMace"]
|
|
condList["UsingMace"] = true
|
|
actor.weaponData1["AddedUsingClaw"] = not condList["UsingClaw"]
|
|
condList["UsingClaw"] = true
|
|
-- GGG stated that a single Varunastra satisfied requirement for wielding two different weapons
|
|
condList["WieldingDifferentWeaponTypes"] = true
|
|
end
|
|
if info.melee then
|
|
condList["UsingMeleeWeapon"] = true
|
|
end
|
|
if info.oneHand then
|
|
condList["UsingOneHandedWeapon"] = true
|
|
else
|
|
condList["UsingTwoHandedWeapon"] = true
|
|
end
|
|
end
|
|
local armourSlots = { "Helmet", "Body Armour", "Gloves", "Boots" }
|
|
for _, slotName in ipairs(armourSlots) do
|
|
if actor.itemList[slotName] then
|
|
condList["Using"..slotName] = true
|
|
end
|
|
end
|
|
if actor.weaponData2.type then
|
|
local info = env.data.weaponTypeInfo[actor.weaponData2.type]
|
|
condList["Using"..info.flag] = true
|
|
if actor.weaponData2.countsAsAll1H then
|
|
actor.weaponData2["AddedUsingAxe"] = not condList["UsingAxe"]
|
|
condList["UsingAxe"] = true
|
|
actor.weaponData2["AddedUsingSword"] = actor.weaponData2.name:match("Varunastra") or not condList["UsingSword"] --Varunastra is a sword
|
|
condList["UsingSword"] = true
|
|
actor.weaponData2["AddedUsingDagger"] = not condList["UsingDagger"]
|
|
condList["UsingDagger"] = true
|
|
actor.weaponData2["AddedUsingMace"] = not condList["UsingMace"]
|
|
condList["UsingMace"] = true
|
|
actor.weaponData2["AddedUsingClaw"] = not condList["UsingClaw"]
|
|
condList["UsingClaw"] = true
|
|
-- GGG stated that a single Varunastra satisfied requirement for wielding two different weapons
|
|
condList["WieldingDifferentWeaponTypes"] = true
|
|
end
|
|
if info.melee then
|
|
condList["UsingMeleeWeapon"] = true
|
|
end
|
|
if info.oneHand then
|
|
condList["UsingOneHandedWeapon"] = true
|
|
else
|
|
condList["UsingTwoHandedWeapon"] = true
|
|
end
|
|
end
|
|
if actor.weaponData1.type and actor.weaponData2.type then
|
|
condList["DualWielding"] = true
|
|
if (actor.weaponData1.type == "Claw" or actor.weaponData1.countsAsAll1H) and (actor.weaponData2.type == "Claw" or actor.weaponData2.countsAsAll1H) then
|
|
condList["DualWieldingClaws"] = true
|
|
end
|
|
if (actor.weaponData1.type == "Dagger" or actor.weaponData1.countsAsAll1H) and (actor.weaponData2.type == "Dagger" or actor.weaponData2.countsAsAll1H) then
|
|
condList["DualWieldingDaggers"] = true
|
|
end
|
|
if (env.data.weaponTypeInfo[actor.weaponData1.type].label or actor.weaponData1.type) ~= (env.data.weaponTypeInfo[actor.weaponData2.type].label or actor.weaponData2.type) then
|
|
local info1 = env.data.weaponTypeInfo[actor.weaponData1.type]
|
|
local info2 = env.data.weaponTypeInfo[actor.weaponData2.type]
|
|
if info1.oneHand and info2.oneHand then
|
|
condList["WieldingDifferentWeaponTypes"] = true
|
|
end
|
|
end
|
|
end
|
|
if env.mode_combat then
|
|
if not actor.mainSkill.skillData.triggered and not actor.mainSkill.skillFlags.trap and not actor.mainSkill.skillFlags.mine and not actor.mainSkill.skillFlags.totem then
|
|
if actor.mainSkill.skillFlags.attack then
|
|
condList["AttackedRecently"] = true
|
|
elseif actor.mainSkill.skillFlags.spell then
|
|
condList["CastSpellRecently"] = true
|
|
end
|
|
if actor.mainSkill.skillTypes[SkillType.Movement] then
|
|
condList["UsedMovementSkillRecently"] = true
|
|
end
|
|
if actor.mainSkill.skillFlags.minion and not actor.mainSkill.skillFlags.permanentMinion then
|
|
condList["UsedMinionSkillRecently"] = true
|
|
end
|
|
if actor.mainSkill.skillTypes[SkillType.Vaal] then
|
|
condList["UsedVaalSkillRecently"] = true
|
|
end
|
|
if actor.mainSkill.skillTypes[SkillType.Channel] then
|
|
condList["Channelling"] = true
|
|
end
|
|
end
|
|
if actor.mainSkill.skillFlags.hit and not actor.mainSkill.skillFlags.trap and not actor.mainSkill.skillFlags.mine and not actor.mainSkill.skillFlags.totem then
|
|
condList["HitRecently"] = true
|
|
if actor.mainSkill.skillFlags.spell then
|
|
condList["HitSpellRecently"] = true
|
|
end
|
|
end
|
|
if actor.mainSkill.skillFlags.totem then
|
|
condList["HaveTotem"] = true
|
|
condList["SummonedTotemRecently"] = true
|
|
if actor.mainSkill.skillFlags.hit then
|
|
condList["TotemsHitRecently"] = true
|
|
if actor.mainSkill.skillFlags.spell then
|
|
condList["TotemsSpellHitRecently"] = true
|
|
end
|
|
end
|
|
end
|
|
if actor.mainSkill.skillFlags.mine then
|
|
condList["DetonatedMinesRecently"] = true
|
|
end
|
|
if actor.mainSkill.skillFlags.trap then
|
|
condList["TriggeredTrapsRecently"] = true
|
|
end
|
|
if modDB:Sum("BASE", nil, "EnemyScorchChance") > 0 or modDB:Flag(nil, "CritAlwaysAltAilments") and not modDB:Flag(env.player.mainSkill.skillCfg, "NeverCrit") or modDB:Flag(nil, "IgniteCanScorch") then
|
|
condList["CanInflictScorch"] = true
|
|
end
|
|
if modDB:Sum("BASE", nil, "EnemyBrittleChance") > 0 or modDB:Flag(nil, "CritAlwaysAltAilments") and not modDB:Flag(env.player.mainSkill.skillCfg, "NeverCrit") then
|
|
condList["CanInflictBrittle"] = true
|
|
end
|
|
if modDB:Sum("BASE", nil, "EnemySapChance") > 0 or modDB:Flag(nil, "CritAlwaysAltAilments") and not modDB:Flag(env.player.mainSkill.skillCfg, "NeverCrit") then
|
|
condList["CanInflictSap"] = true
|
|
end
|
|
-- Shrine Buffs: Must be done before life pool calculated for massive shrine
|
|
local shrineEffectMod = 1 + modDB:Sum("INC", nil, "BuffEffectOnSelf", "ShrineBuffEffect") / 100
|
|
if modDB:Flag(nil, "AccelerationShrine") then
|
|
modDB:NewMod("ActionSpeed", "INC", m_floor(50 * shrineEffectMod), "Acceleration Shrine")
|
|
modDB:NewMod("ProjectileSpeed", "INC", m_floor(80 * shrineEffectMod), "Acceleration Shrine")
|
|
end
|
|
if modDB:Flag(nil, "BrutalShrine") then
|
|
modDB:NewMod("Damage", "INC", m_floor(50 * shrineEffectMod), "Brutal Shrine")
|
|
modDB:NewMod("EnemyStunDuration", "INC", m_floor(30 * shrineEffectMod), "Brutal Shrine")
|
|
modDB:NewMod("EnemyKnockbackChance", "INC", 100, "Brutal Shrine")
|
|
end
|
|
if modDB:Flag(nil, "DiamondShrine") then
|
|
modDB:NewMod("CritChance", "OVERRIDE", 100, "Diamond Shrine")
|
|
end
|
|
if modDB:Flag(nil, "DivineShrine") then
|
|
modDB:NewMod("DamageTaken", "MORE", -100, "Divine Shrine")
|
|
end
|
|
if modDB:Flag(nil, "EchoingShrine") then
|
|
modDB:NewMod("Speed", "MORE", m_floor(100 * shrineEffectMod), "Echoing Shrine", ModFlag.Attack)
|
|
modDB:NewMod("Speed", "MORE", m_floor(100 * shrineEffectMod), "Echoing Shrine", ModFlag.Cast)
|
|
modDB:NewMod("RepeatCount", "BASE", m_floor(1 * shrineEffectMod), "Echoing Shrine")
|
|
end
|
|
if modDB:Flag(nil, "GloomShrine") then
|
|
modDB:NewMod("NonChaosDamageGainAsChaos", "BASE", m_floor(10 * shrineEffectMod), "Gloom Shrine")
|
|
end
|
|
if modDB:Flag(nil, "ImpenetrableShrine") then
|
|
modDB:NewMod("Armour", "INC", m_floor(100 * shrineEffectMod), "Impenetrable Shrine")
|
|
modDB:NewMod("Evasion", "INC", m_floor(100 * shrineEffectMod), "Impenetrable Shrine")
|
|
modDB:NewMod("EnergyShield", "INC", m_floor(100 * shrineEffectMod), "Impenetrable Shrine")
|
|
end
|
|
if modDB:Flag(nil, "MassiveShrine") then
|
|
modDB:NewMod("Life", "INC", m_floor(40 * shrineEffectMod), "Massive Shrine")
|
|
modDB:NewMod("AreaOfEffect", "INC", m_floor(40 * shrineEffectMod), "Massive Shrine")
|
|
end
|
|
if modDB:Flag(nil, "ReplenishingShrine") then
|
|
modDB:NewMod("ManaRegenPercent", "BASE", 10 * shrineEffectMod, "Replenishing Shrine")
|
|
modDB:NewMod("LifeRegenPercent", "BASE", 6.7 * shrineEffectMod, "Replenishing Shrine")
|
|
end
|
|
if modDB:Flag(nil, "ResistanceShrine") then
|
|
modDB:NewMod("ElementalResist", "BASE", m_floor(50 * shrineEffectMod), "Resistance Shrine")
|
|
modDB:NewMod("ElementalResistMax", "BASE", m_floor(10 * shrineEffectMod), "Resistance Shrine")
|
|
end
|
|
if modDB:Flag(nil, "ResonatingShrine") then
|
|
modDB:NewMod("CritChance", "INC", m_floor(50 * shrineEffectMod), "Resonating Shrine", { type = "Multiplier", var = "PowerCharge" })
|
|
modDB:NewMod("Speed", "INC", m_floor(4 * shrineEffectMod), "Resonating Shrine", ModFlag.Attack, { type = "Multiplier", var = "FrenzyCharge" })
|
|
modDB:NewMod("Speed", "INC", m_floor(4 * shrineEffectMod), "Resonating Shrine", ModFlag.Cast, { type = "Multiplier", var = "FrenzyCharge" })
|
|
modDB:NewMod("Damage", "MORE", m_floor(4 * shrineEffectMod), "Resonating Shrine", { type = "Multiplier", var = "FrenzyCharge" })
|
|
modDB:NewMod("PhysicalDamageReduction", "BASE", m_floor(4 * shrineEffectMod), "Resonating Shrine", { type = "Multiplier", var = "EnduranceCharge" })
|
|
modDB:NewMod("ElementalDamageReduction", "BASE", m_floor(4 * shrineEffectMod), "Resonating Shrine", { type = "Multiplier", var = "EnduranceCharge" })
|
|
modDB:NewMod("Damage", "INC", m_floor(4 * shrineEffectMod), "Resonating Shrine", { type = "Multiplier", var = "PowerCharge" }, { type = "Multiplier", var = "FrenzyCharge" }, { type = "Multiplier", var = "EnduranceCharge" })
|
|
end
|
|
if modDB:Flag(nil, "LesserAccelerationShrine") and not modDB:Flag(nil, "AccelerationShrine") then
|
|
modDB:NewMod("ActionSpeed", "INC", m_floor(10 * shrineEffectMod), "Lesser Acceleration Shrine")
|
|
modDB:NewMod("ProjectileSpeed", "INC", m_floor(30 * shrineEffectMod), "Lesser Acceleration Shrine")
|
|
end
|
|
if modDB:Flag(nil, "LesserBrutalShrine") then
|
|
modDB:NewMod("Damage", "INC", m_floor(20 * shrineEffectMod), "Lesser Brutal Shrine")
|
|
modDB:NewMod("EnemyStunDuration", "INC", m_floor(20 * shrineEffectMod), "Lesser Brutal Shrine")
|
|
modDB:NewMod("EnemyKnockbackChance", "INC", 100, "Lesser Brutal Shrine")
|
|
end
|
|
if modDB:Flag(nil, "LesserImpenetrableShrine") then
|
|
modDB:NewMod("Armour", "INC", m_floor(50 * shrineEffectMod), "Lesser Impenetrable Shrine")
|
|
modDB:NewMod("Evasion", "INC", m_floor(50 * shrineEffectMod), "Lesser Impenetrable Shrine")
|
|
modDB:NewMod("EnergyShield", "INC", m_floor(50 * shrineEffectMod), "Lesser Impenetrable Shrine")
|
|
end
|
|
if modDB:Flag(nil, "LesserMassiveShrine") then
|
|
modDB:NewMod("Life", "INC", m_floor(20 * shrineEffectMod), "Lesser Massive Shrine")
|
|
modDB:NewMod("AreaOfEffect", "INC", m_floor(20 * shrineEffectMod), "Lesser Massive Shrine")
|
|
end
|
|
if modDB:Flag(nil, "LesserReplenishingShrine") then
|
|
modDB:NewMod("ManaRegenPercent", "BASE", 3.3 * shrineEffectMod, "Lesser Replenishing Shrine")
|
|
modDB:NewMod("LifeRegenPercent", "BASE", 3.3 * shrineEffectMod, "Lesser Replenishing Shrine")
|
|
end
|
|
if modDB:Flag(nil, "LesserResistanceShrine") then
|
|
modDB:NewMod("ElementalResist", "BASE", m_floor(25 * shrineEffectMod), "Lesser Resistance Shrine")
|
|
modDB:NewMod("ElementalResistMax", "BASE", m_floor(2 * shrineEffectMod), "Lesser Resistance Shrine")
|
|
end
|
|
end
|
|
if env.mode_effective then
|
|
if env.player.mainSkill.skillModList:Sum("BASE", env.player.mainSkill.skillCfg, "FireExposureChance") > 0 or modDB:Sum("BASE", nil, "FireExposureChance") > 0 then
|
|
condList["CanApplyFireExposure"] = true
|
|
end
|
|
if env.player.mainSkill.skillModList:Sum("BASE", env.player.mainSkill.skillCfg, "ColdExposureChance") > 0 or modDB:Sum("BASE", nil, "ColdExposureChance") > 0 then
|
|
condList["CanApplyColdExposure"] = true
|
|
end
|
|
if env.player.mainSkill.skillModList:Sum("BASE", env.player.mainSkill.skillCfg, "LightningExposureChance") > 0 or modDB:Sum("BASE", nil, "LightningExposureChance") > 0 then
|
|
condList["CanApplyLightningExposure"] = true
|
|
end
|
|
end
|
|
|
|
-- Calculate attributes
|
|
local calculateAttributes = function()
|
|
for pass = 1, 2 do -- Calculate twice because of circular dependency (X attribute higher than Y attribute)
|
|
for _, stat in pairs({"Str","Dex","Int"}) do
|
|
output[stat] = m_max(round(calcLib.val(modDB, stat)), 0)
|
|
if breakdown then
|
|
breakdown[stat] = breakdown.simple(nil, nil, output[stat], stat)
|
|
end
|
|
end
|
|
|
|
local stats = { output.Str, output.Dex, output.Int }
|
|
table.sort(stats)
|
|
output.LowestAttribute = stats[1]
|
|
condList["TwoHighestAttributesEqual"] = stats[2] == stats[3]
|
|
|
|
condList["DexHigherThanInt"] = output.Dex > output.Int
|
|
condList["StrHigherThanInt"] = output.Str > output.Int
|
|
condList["IntHigherThanDex"] = output.Int > output.Dex
|
|
condList["StrHigherThanDex"] = output.Str > output.Dex
|
|
condList["IntHigherThanStr"] = output.Int > output.Str
|
|
condList["DexHigherThanStr"] = output.Dex > output.Str
|
|
|
|
condList["StrHighestAttribute"] = output.Str >= output.Dex and output.Str >= output.Int
|
|
condList["IntHighestAttribute"] = output.Int >= output.Str and output.Int >= output.Dex
|
|
condList["DexHighestAttribute"] = output.Dex >= output.Str and output.Dex >= output.Int
|
|
end
|
|
end
|
|
|
|
local calculateOmniscience = function (convert)
|
|
local classStats = env.spec.tree.characterData and env.spec.tree.characterData[env.classId] or env.spec.tree.classes[env.classId]
|
|
|
|
for pass = 1, 2 do -- Calculate twice because of circular dependency (X attribute higher than Y attribute)
|
|
if pass ~= 1 then
|
|
for _, stat in pairs({"Str","Dex","Int"}) do
|
|
local base = classStats["base_"..stat:lower()]
|
|
output[stat] = m_min(round(calcLib.val(modDB, stat)), base)
|
|
if breakdown then
|
|
breakdown[stat] = breakdown.simple(nil, nil, output[stat], stat)
|
|
end
|
|
|
|
modDB:NewMod("Omni", "BASE", (modDB:Sum("BASE", nil, stat) - base), stat.." conversion Omniscience")
|
|
modDB:NewMod("Omni", "INC", modDB:Sum("INC", nil, stat), "Omniscience")
|
|
modDB:NewMod("Omni", "MORE", modDB:Sum("MORE", nil, stat), "Omniscience")
|
|
end
|
|
end
|
|
|
|
if pass ~= 2 then
|
|
-- Subtract out double and triple dips
|
|
local conversion = { }
|
|
local reduction = { }
|
|
for _, type in pairs({"BASE", "INC", "MORE"}) do
|
|
conversion[type] = { }
|
|
for _, stat in pairs({"StrDex", "StrInt", "DexInt", "All"}) do
|
|
conversion[type][stat] = modDB:Sum(type, nil, stat) or 0
|
|
end
|
|
reduction[type] = conversion[type].StrDex + conversion[type].StrInt + conversion[type].DexInt + 2*conversion[type].All
|
|
end
|
|
modDB:NewMod("Omni", "BASE", -reduction["BASE"], "Reduction from Double/Triple Dipped attributes to Omniscience")
|
|
modDB:NewMod("Omni", "INC", -reduction["INC"], "Reduction from Double/Triple Dipped attributes to Omniscience")
|
|
modDB:NewMod("Omni", "MORE", -reduction["MORE"], "Reduction from Double/Triple Dipped attributes to Omniscience")
|
|
end
|
|
|
|
for _, stat in pairs({"Str","Dex","Int"}) do
|
|
local base = classStats["base_"..stat:lower()]
|
|
output[stat] = base
|
|
end
|
|
|
|
output["Omni"] = m_max(round(calcLib.val(modDB, "Omni")), 0)
|
|
if breakdown then
|
|
breakdown["Omni"] = breakdown.simple(nil, nil, output["Omni"], "Omni")
|
|
end
|
|
|
|
local stats = { output.Str, output.Dex, output.Int }
|
|
table.sort(stats)
|
|
output.LowestAttribute = stats[1]
|
|
condList["TwoHighestAttributesEqual"] = stats[2] == stats[3]
|
|
|
|
condList["DexHigherThanInt"] = output.Dex > output.Int
|
|
condList["StrHigherThanInt"] = output.Str > output.Int
|
|
condList["IntHigherThanDex"] = output.Int > output.Dex
|
|
condList["StrHigherThanDex"] = output.Str > output.Dex
|
|
condList["IntHigherThanStr"] = output.Int > output.Str
|
|
condList["DexHigherThanStr"] = output.Dex > output.Str
|
|
|
|
condList["StrHighestAttribute"] = output.Str >= output.Dex and output.Str >= output.Int
|
|
condList["IntHighestAttribute"] = output.Int >= output.Str and output.Int >= output.Dex
|
|
condList["DexHighestAttribute"] = output.Dex >= output.Str and output.Dex >= output.Int
|
|
end
|
|
end
|
|
|
|
if modDB:Flag(nil, "Omniscience") then
|
|
calculateOmniscience()
|
|
else
|
|
calculateAttributes()
|
|
end
|
|
|
|
-- Calculate total attributes
|
|
output.TotalAttr = output.Str + output.Dex + output.Int
|
|
|
|
-- Special case for Devotion
|
|
output.Devotion = modDB:Sum("BASE", nil, "Devotion")
|
|
|
|
-- Add attribute bonuses
|
|
if not modDB:Flag(nil, "NoAttributeBonuses") then
|
|
if not modDB:Flag(nil, "NoStrengthAttributeBonuses") then
|
|
if not modDB:Flag(nil, "NoStrBonusToLife") then
|
|
modDB:NewMod("Life", "BASE", m_floor(output.Str / 2), "Strength")
|
|
end
|
|
local strDmgBonusRatioOverride = modDB:Sum("BASE", nil, "StrDmgBonusRatioOverride")
|
|
if strDmgBonusRatioOverride > 0 then
|
|
actor.strDmgBonus = m_floor((output.Str + modDB:Sum("BASE", nil, "DexIntToMeleeBonus")) * strDmgBonusRatioOverride)
|
|
else
|
|
actor.strDmgBonus = m_floor((output.Str + modDB:Sum("BASE", nil, "DexIntToMeleeBonus")) / 5)
|
|
end
|
|
modDB:NewMod("PhysicalDamage", "INC", actor.strDmgBonus, "Strength", ModFlag.Melee)
|
|
end
|
|
if not modDB:Flag(nil, "NoDexterityAttributeBonuses") then
|
|
modDB:NewMod("Accuracy", "BASE", output.Dex * (modDB:Override(nil, "DexAccBonusOverride") or data.misc.AccuracyPerDexBase), "Dexterity")
|
|
if not modDB:Flag(nil, "NoDexBonusToEvasion") then
|
|
modDB:NewMod("Evasion", "INC", m_floor(output.Dex / 5), "Dexterity")
|
|
end
|
|
end
|
|
if not modDB:Flag(nil, "NoIntelligenceAttributeBonuses") then
|
|
if not modDB:Flag(nil, "NoIntBonusToMana") then
|
|
modDB:NewMod("Mana", "BASE", m_floor(output.Int / 2), "Intelligence")
|
|
end
|
|
if not modDB:Flag(nil, "NoIntBonusToES") then
|
|
modDB:NewMod("EnergyShield", "INC", m_floor(output.Int / 5), "Intelligence")
|
|
end
|
|
end
|
|
end
|
|
|
|
doActorLifeMana(actor)
|
|
end
|
|
|
|
-- Calculate life/mana reservation
|
|
---@param actor table
|
|
function doActorLifeManaReservation(actor)
|
|
local modDB = actor.modDB
|
|
local output = actor.output
|
|
local condList = modDB.conditions
|
|
|
|
for _, pool in pairs({"Life", "Mana"}) do
|
|
local max = output[pool]
|
|
local reserved
|
|
if max > 0 then
|
|
local lowPerc = modDB:Sum("BASE", nil, "Low" .. pool .. "Percentage")
|
|
reserved = (actor["reserved_"..pool.."Base"] or 0) + m_ceil(max * (actor["reserved_"..pool.."Percent"] or 0) / 100)
|
|
uncancellableReservation = actor["uncancellable_"..pool.."Reservation"] or 0
|
|
output[pool.."Reserved"] = m_min(reserved, max)
|
|
output[pool.."ReservedPercent"] = m_min(reserved / max * 100, 100)
|
|
output[pool.."Unreserved"] = max - reserved
|
|
output[pool.."UnreservedPercent"] = (max - reserved) / max * 100
|
|
output[pool.."UncancellableReservation"] = m_min(uncancellableReservation, 0)
|
|
output[pool.."CancellableReservation"] = 100 - uncancellableReservation
|
|
if (max - reserved) / max <= (lowPerc > 0 and lowPerc or data.misc.LowPoolThreshold) then
|
|
condList["Low"..pool] = true
|
|
end
|
|
else
|
|
reserved = 0
|
|
end
|
|
for _, value in ipairs(modDB:List(nil, "GrantReserved"..pool.."AsAura")) do
|
|
local auraMod = copyTable(value.mod)
|
|
auraMod.value = m_floor(auraMod.value * m_min(reserved, max))
|
|
modDB:NewMod("ExtraAura", "LIST", { mod = auraMod })
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Helper function to determine curse priority when processing curses beyond the curse limit
|
|
local function determineCursePriority(curseName, activeSkill)
|
|
local curseName = curseName or ""
|
|
local source = ""
|
|
local slot = ""
|
|
local socket = 1
|
|
if activeSkill and activeSkill.socketGroup then
|
|
source = activeSkill.socketGroup.source or ""
|
|
slot = activeSkill.socketGroup.slot or ""
|
|
for k, v in ipairs(activeSkill.socketGroup.gemList) do
|
|
if v.gemData and v.gemData.name == curseName then
|
|
-- We need to enforce a limit of 8 here to avoid collision with data.cursePriority["CurseFromEquipment"]
|
|
socket = m_min(k, 8)
|
|
break
|
|
end
|
|
end
|
|
end
|
|
local basePriority = data.cursePriority[curseName] or 0
|
|
local socketPriority = socket * data.cursePriority["SocketPriorityBase"]
|
|
local slotPriority = data.cursePriority[slot:gsub(" (Swap)", "")] or 0
|
|
local sourcePriority = 0
|
|
if activeSkill and activeSkill.skillTypes and activeSkill.skillTypes[SkillType.Aura] then
|
|
sourcePriority = data.cursePriority["CurseFromAura"]
|
|
elseif source ~= "" then
|
|
sourcePriority = data.cursePriority["CurseFromEquipment"]
|
|
end
|
|
if source ~= "" and slotPriority == data.cursePriority["Ring 2"] then
|
|
-- Implicit and explicit curses from rings have equal priority; only curses from socketed skill gems care about which ring slot they're equipped in
|
|
slotPriority = data.cursePriority["Ring 1"]
|
|
end
|
|
return basePriority + socketPriority + slotPriority + sourcePriority
|
|
end
|
|
|
|
-- Process enemy modifiers and other buffs
|
|
local function doActorMisc(env, actor)
|
|
local modDB = actor.modDB
|
|
local enemyDB = actor.enemy.modDB
|
|
local output = actor.output
|
|
local condList = modDB.conditions
|
|
|
|
-- Process enemy modifiers
|
|
for _, value in ipairs(modDB:Tabulate(nil, nil, "EnemyModifier")) do
|
|
enemyDB:AddMod(modLib.setSource(value.value.mod, value.value.mod.source or value.mod.source))
|
|
end
|
|
|
|
-- Add misc buffs/debuffs
|
|
if env.mode_combat then
|
|
if env.player.mainSkill.baseSkillModList:Flag(nil, "Cruelty") then
|
|
modDB.multipliers["Cruelty"] = modDB:Override(nil, "Cruelty") or 40
|
|
end
|
|
-- Minimum Rage
|
|
if modDB:Sum("BASE", nil, "MinimumRage") > (modDB.multipliers["Rage"] or 0) then
|
|
modDB.multipliers["Rage"] = modDB:Sum("BASE", nil, "MinimumRage")
|
|
end
|
|
local alliedFortify = modDB:Flag(nil, "YourFortifyEqualToParent") and actor.parent.output.FortificationStacks or env.partyMembers and env.partyMembers.modDB:Flag(nil, "PartyMemberFortifyEqualToYours") and env.partyMembers.output.FortificationStacks or 0
|
|
-- Minimum Fortification from King Maker or Perfect Naval Officer spectres or Ally override
|
|
if modDB:Sum("BASE", nil, "MinimumFortification") > 0 or alliedFortify > 0 then
|
|
condList["Fortified"] = true
|
|
end
|
|
-- Fortify
|
|
if modDB:Flag(nil, "Fortified") or modDB:Sum("BASE", nil, "Multiplier:Fortification") > 0 then
|
|
local maxStacks = modDB:Override(nil, "MaximumFortification") or modDB:Sum("BASE", skillCfg, "MaximumFortification")
|
|
local minStacks = m_min(modDB:Flag(nil, "Condition:HaveMaxFortification") and maxStacks or modDB:Sum("BASE", nil, "MinimumFortification"), maxStacks)
|
|
local stacks = modDB:Override(nil, "FortificationStacks") or (alliedFortify > 0 and alliedFortify) or (minStacks > 0 and minStacks) or maxStacks
|
|
output.FortificationStacks = stacks
|
|
output.FortificationStacksOver20 = m_min(m_max(0, stacks - 20), maxStacks - 20)
|
|
if not modDB:Flag(nil,"Condition:NoFortificationMitigation") then
|
|
local effectScale = 1 + modDB:Sum("INC", nil, "BuffEffectOnSelf") / 100
|
|
local effect = m_floor(effectScale * stacks)
|
|
modDB:NewMod("DamageTakenWhenHit", "MORE", -effect, "Fortification")
|
|
end
|
|
if stacks >= maxStacks then
|
|
modDB:NewMod("Condition:HaveMaximumFortification", "FLAG", true, "")
|
|
end
|
|
modDB.multipliers["BuffOnSelf"] = (modDB.multipliers["BuffOnSelf"] or 0) + 1
|
|
end
|
|
if modDB:Flag(nil, "Onslaught") then
|
|
local effect
|
|
--Loop detects if a Silver flask is used to grant Onslaught. If statement adds flask effect to calculation if one is being used
|
|
local onslaughtFromFlask
|
|
--This value is set to negative and not 0 or else reduced effect would not properly apply
|
|
local flaskEffectInc = -100
|
|
for item in pairs(env.flasks) do
|
|
if item.baseName:match("Silver Flask") then
|
|
onslaughtFromFlask = true
|
|
|
|
local curFlaskEffectInc = item.flaskData.effectInc + modDB:Sum("INC", { actor = "player" }, "FlaskEffect")
|
|
if item.rarity == "MAGIC" then
|
|
curFlaskEffectInc = curFlaskEffectInc + modDB:Sum("INC", { actor = "player" }, "MagicUtilityFlaskEffect")
|
|
end
|
|
|
|
if flaskEffectInc < curFlaskEffectInc / 100 then
|
|
flaskEffectInc = curFlaskEffectInc / 100
|
|
end
|
|
end
|
|
end
|
|
local onslaughtEffectInc = modDB:Sum("INC", nil, "OnslaughtEffect", "BuffEffectOnSelf") / 100
|
|
if onslaughtFromFlask then
|
|
effect = m_floor(20 * (1 + flaskEffectInc + onslaughtEffectInc))
|
|
else
|
|
effect = m_floor(20 * (1 + onslaughtEffectInc))
|
|
end
|
|
modDB:NewMod("Speed", "INC", effect, "Onslaught", ModFlag.Attack)
|
|
modDB:NewMod("Speed", "INC", effect, "Onslaught", ModFlag.Cast)
|
|
modDB:NewMod("MovementSpeed", "INC", effect, "Onslaught")
|
|
end
|
|
if modDB.conditions["AffectedByArcaneSurge"] or modDB:Flag(nil, "Condition:ArcaneSurge") then
|
|
modDB.conditions["AffectedByArcaneSurge"] = true
|
|
local effect = 1 + modDB:Sum("INC", nil, "ArcaneSurgeEffect", "BuffEffectOnSelf") / 100
|
|
modDB:NewMod("ManaRegen", "INC", (modDB:Max(nil, "ArcaneSurgeManaRegen") or 30) * effect, "Arcane Surge")
|
|
local arcaneSurgeCastSpeed = (modDB:Max(nil, "ArcaneSurgeCastSpeed") or 20) * effect
|
|
modDB:NewMod("Speed", "INC", arcaneSurgeCastSpeed, "Arcane Surge", ModFlag.Cast)
|
|
if modDB:Flag(nil, "ArcaneSurgeCastSpeedToMovementSpeed") then
|
|
modDB:NewMod("MovementSpeed", "INC", arcaneSurgeCastSpeed, "Arcane Surge")
|
|
end
|
|
local arcaneSurgeDamage = modDB:Max(nil, "ArcaneSurgeDamage") or 0
|
|
if arcaneSurgeDamage ~= 0 then modDB:NewMod("Damage", "MORE", arcaneSurgeDamage * effect, "Arcane Surge", ModFlag.Spell) end
|
|
end
|
|
if modDB:Flag(nil, "Fanaticism") and actor.mainSkill and actor.mainSkill.skillFlags.selfCast then
|
|
local effect = m_floor(75 * (1 + modDB:Sum("INC", nil, "BuffEffectOnSelf") / 100))
|
|
modDB:NewMod("Speed", "MORE", effect, "Fanaticism", ModFlag.Cast)
|
|
modDB:NewMod("Cost", "INC", -effect, "Fanaticism", ModFlag.Cast)
|
|
modDB:NewMod("AreaOfEffect", "INC", effect, "Fanaticism", ModFlag.Cast)
|
|
end
|
|
if modDB:Flag(nil, "UnholyMight") then
|
|
local effect = 1 + modDB:Sum("INC", nil, "BuffEffectOnSelf") / 100
|
|
modDB:NewMod("PhysicalDamageConvertToChaos", "BASE", m_floor(100 * effect), "Unholy Might")
|
|
modDB:NewMod("Condition:CanWither", "FLAG", true, "Unholy Might")
|
|
end
|
|
if modDB:Flag(nil, "ChaoticMight") then
|
|
local effect = m_floor(30 * (1 + modDB:Sum("INC", nil, "BuffEffectOnSelf") / 100))
|
|
modDB:NewMod("PhysicalDamageGainAsChaos", "BASE", effect, "Chaotic Might")
|
|
end
|
|
if modDB:Flag(nil, "Tailwind") then
|
|
local effect = m_floor(8 * (1 + modDB:Sum("INC", nil, "TailwindEffectOnSelf", "BuffEffectOnSelf") / 100))
|
|
modDB:NewMod("ActionSpeed", "INC", effect, "Tailwind")
|
|
end
|
|
if modDB:Flag(nil, "Condition:TotemTailwind") then
|
|
modDB:NewMod("TotemActionSpeed", "INC", 8, "Tailwind")
|
|
end
|
|
if modDB:Flag(nil, "Adrenaline") then
|
|
local effectMod = 1 + modDB:Sum("INC", nil, "BuffEffectOnSelf") / 100
|
|
modDB:NewMod("Damage", "INC", m_floor(100 * effectMod), "Adrenaline")
|
|
modDB:NewMod("Speed", "INC", m_floor(25 * effectMod), "Adrenaline", ModFlag.Attack)
|
|
modDB:NewMod("Speed", "INC", m_floor(25 * effectMod), "Adrenaline", ModFlag.Cast)
|
|
modDB:NewMod("MovementSpeed", "INC", m_floor(25 * effectMod), "Adrenaline")
|
|
modDB:NewMod("PhysicalDamageReduction", "BASE", m_floor(10 * effectMod), "Adrenaline")
|
|
end
|
|
if modDB:Flag(nil, "Convergence") then
|
|
local effect = m_floor(30 * (1 + modDB:Sum("INC", nil, "BuffEffectOnSelf") / 100))
|
|
modDB:NewMod("ElementalDamage", "MORE", effect, "Convergence")
|
|
end
|
|
if modDB:Flag(nil, "HerEmbrace") then
|
|
condList["HerEmbrace"] = true
|
|
modDB:NewMod("AvoidStun", "BASE", 100, "Her Embrace")
|
|
modDB:NewMod("PhysicalDamageGainAsFire", "BASE", 123, "Her Embrace", ModFlag.Sword)
|
|
modDB:NewMod("AvoidFreeze", "BASE", 100, "Her Embrace")
|
|
modDB:NewMod("AvoidChill", "BASE", 100, "Her Embrace")
|
|
modDB:NewMod("AvoidIgnite", "BASE", 100, "Her Embrace")
|
|
modDB:NewMod("Speed", "INC", 20, "Her Embrace", ModFlag.Attack)
|
|
modDB:NewMod("Speed", "INC", 20, "Her Embrace", ModFlag.Cast)
|
|
modDB:NewMod("MovementSpeed", "INC", 20, "Her Embrace")
|
|
end
|
|
if modDB:Flag(nil, "Condition:PhantasmalMight") then
|
|
modDB.multipliers["BuffOnSelf"] = (modDB.multipliers["BuffOnSelf"] or 0) + (output.ActivePhantasmLimit or 1) - 1 -- slight hack to not double count the initial buff
|
|
end
|
|
if modDB:Flag(nil, "Elusive") then
|
|
local maxSkillInc = modDB:Max({ source = "Skill" }, "ElusiveEffect") or 0
|
|
local inc = modDB:Sum("INC", nil, "ElusiveEffect", "BuffEffectOnSelf")
|
|
if actor.mainSkill.skillModList:Flag(nil, "SupportedByNightblade") then
|
|
inc = inc + modDB:Sum("INC", nil, "NightbladeSupportedElusiveEffect")
|
|
end
|
|
inc = inc + maxSkillInc
|
|
local elusiveEffectMod = (1 + inc / 100) * modDB:More(nil, "ElusiveEffect", "BuffEffectOnSelf") * 100
|
|
output.ElusiveEffectMod = (elusiveEffectMod + (modDB:Override(nil, "ElusiveEffectMinThreshold") or 0)) / 2
|
|
-- if we want the max skill to not be noted as its own breakdown table entry, comment out below
|
|
modDB:NewMod("ElusiveEffect", "INC", maxSkillInc, "Max Skill Effect")
|
|
-- Override elusive effect if set.
|
|
if modDB:Override(nil, "ElusiveEffect") then
|
|
output.ElusiveEffectMod = m_min(modDB:Override(nil, "ElusiveEffect"), elusiveEffectMod)
|
|
end
|
|
local effect = output.ElusiveEffectMod / 100
|
|
condList["Elusive"] = true
|
|
modDB:NewMod("AvoidAllDamageFromHitsChance", "BASE", m_floor(15 * effect), "Elusive")
|
|
modDB:NewMod("MovementSpeed", "INC", m_floor(30 * effect), "Elusive")
|
|
end
|
|
if modDB:Max(nil, "WitherEffectStack") then
|
|
modDB:NewMod("Condition:CanWither", "FLAG", true, "Config")
|
|
local effect = modDB:Max(nil, "WitherEffectStack")
|
|
enemyDB:NewMod("ChaosDamageTaken", "INC", effect, "Withered", { type = "Multiplier", var = "WitheredStack", limit = 15 } )
|
|
end
|
|
if modDB:Flag(nil, "Condition:CanBeWithered") then
|
|
local effect = 6 * (100 + modDB:Sum("INC", nil, "WitherEffectOnSelf")) / 100 * modDB:More(nil, "WitherEffectOnSelf")
|
|
modDB:NewMod("ChaosDamageTaken", "INC", effect, "Withered", { type = "Multiplier", var = "WitheredStack", limit = 15 } )
|
|
end
|
|
if modDB:Flag(nil, "Excommunicated") then
|
|
modDB:NewMod("ChaosDamage", "MORE", -100, "Excommunicated")
|
|
end
|
|
if modDB:Flag(nil, "Blind") and not modDB:Flag(nil, "CannotBeBlinded") then
|
|
if not modDB:Flag(nil, "IgnoreBlindHitChance") then
|
|
local effect = 1 + modDB:Sum("INC", nil, "BlindEffect", "BuffEffectOnSelf") / 100
|
|
-- Override Blind effect if set.
|
|
if modDB:Override(nil, "BlindEffect") then
|
|
effect = m_min(modDB:Override(nil, "BlindEffect") / 100, effect)
|
|
end
|
|
modDB:NewMod("Accuracy", "MORE", m_floor(-20 * effect), "Blind")
|
|
modDB:NewMod("Evasion", "MORE", m_floor(-20 * effect), "Blind")
|
|
end
|
|
end
|
|
if modDB:Flag(nil, "Chill") then
|
|
local ailmentData = data.nonDamagingAilment
|
|
local chillValue = m_max(modDB:Sum("BASE", nil, "SelfChillOverride"), modDB:Override(nil, "ChillVal")) or ailmentData.Chill.default
|
|
local totalChillSelfEffect = calcLib.mod(modDB, nil, "SelfChillEffect")
|
|
local avoidChill = modDB:Flag(nil, "ChillImmune", "ElementalAilmentImmune") and 100 or m_floor(m_min(modDB:Sum("BASE", nil, "AvoidChill", "AvoidAilments", "AvoidElementalAilments") + (modDB:Flag(nil, "ShockAvoidAppliesToElementalAilments") and modDB:Sum("BASE", nil, "AvoidShock") or 0), 100))
|
|
|
|
local effect = avoidChill == 100 and 0 or m_min(m_max(m_floor(chillValue * totalChillSelfEffect), 0), modDB:Override(nil, "ChillMax") or ailmentData.Chill.max)
|
|
if modDB:Flag(nil, "SkitterbotBonechill") then
|
|
modDB:NewMod("ColdDamageTaken", "INC", effect * (modDB:Flag(nil, "SelfChillEffectIsReversed") and -1 or 1), "Bonechill")
|
|
end
|
|
modDB:NewMod("ActionSpeed", "INC", effect * (modDB:Flag(nil, "SelfChillEffectIsReversed") and 1 or -1), "Chill")
|
|
end
|
|
if modDB:Flag(nil, "Shock") then
|
|
local ailmentData = data.nonDamagingAilment
|
|
local shockValue = m_max(modDB:Sum("BASE", nil, "SelfShockOverride"), modDB:Override(nil, "ShockVal")) or ailmentData.Shock.default
|
|
local totalShockSelfEffect = calcLib.mod(modDB, nil, "SelfShockEffect")
|
|
local avoidShock = modDB:Flag(nil, "ShockImmune", "ElementalAilmentImmune") and 100 or m_floor(m_min(modDB:Sum("BASE", nil, "AvoidShock", "AvoidAilments", "AvoidElementalAilments"), 100))
|
|
|
|
local effect = avoidShock == 100 and 0 or m_min(m_max(m_floor(shockValue * totalShockSelfEffect), 0), modDB:Override(nil, "ShockMax") or ailmentData.Shock.max)
|
|
modDB:NewMod("DamageTaken", "INC", effect, "Shock")
|
|
end
|
|
if modDB:Flag(nil, "Scorch") then
|
|
local ailmentData = data.nonDamagingAilment
|
|
local scorchValue = m_max(modDB:Sum("BASE", nil, "SelfScorchOverride"), modDB:Override(nil, "ScorchVal")) or ailmentData.Scorch.default
|
|
local totalScorchSelfEffect = calcLib.mod(modDB, nil, "SelfScorchEffect")
|
|
local avoidScorch = modDB:Flag(nil, "ScorchImmune", "ElementalAilmentImmune") and 100 or m_floor(m_min(modDB:Sum("BASE", nil, "AvoidScorch", "AvoidAilments", "AvoidElementalAilments") + (modDB:Flag(nil, "ShockAvoidAppliesToElementalAilments") and modDB:Sum("BASE", nil, "AvoidShock") or 0), 100))
|
|
|
|
local effect = avoidScorch == 100 and 0 or m_min(m_max(m_floor(scorchValue * totalScorchSelfEffect), 0), modDB:Override(nil, "ScorchMax") or ailmentData.Scorch.max)
|
|
modDB:NewMod("ElementalResist", "BASE", -effect, "Scorch")
|
|
end
|
|
if modDB:Flag(nil, "Freeze") then
|
|
local effect = m_max(m_floor(70 * calcLib.mod(modDB, nil, "SelfChillEffect")), 0)
|
|
modDB:NewMod("ActionSpeed", "INC", -effect, "Freeze")
|
|
end
|
|
if modDB:Flag(nil, "CanLeechLifeOnFullLife") and not modDB:Flag(nil, "GhostReaver") then
|
|
condList["Leeching"] = true
|
|
condList["LeechingLife"] = true
|
|
end
|
|
if modDB:Flag(nil, "CanLeechEnergyShieldOnFullEnergyShield") then
|
|
condList["Leeching"] = true
|
|
condList["LeechingEnergyShield"] = true
|
|
end
|
|
if modDB:Flag(nil, "Condition:CanGainRage") or modDB:Sum("BASE", nil, "RageRegen") > 0 then
|
|
local maxStacks = modDB:Sum("BASE", skillCfg, "MaximumRage")
|
|
local minStacks = m_min(modDB:Sum("BASE", nil, "MinimumRage"), maxStacks)
|
|
local rageConfig = modDB:Sum("BASE", nil, "Multiplier:RageStack")
|
|
local stacks = m_max(m_min(rageConfig, maxStacks), (minStacks > 0 and minStacks) or 0)
|
|
output.RageEffect = m_floor(stacks * calcLib.mod(modDB, nil, "RageEffect"))
|
|
modDB:NewMod("Multiplier:RageEffect", "BASE", output.RageEffect, "Base")
|
|
output.Rage = stacks
|
|
output.MaximumRage = maxStacks
|
|
modDB:NewMod("Multiplier:Rage", "BASE", output.Rage, "Base")
|
|
if modDB:Flag(nil, "Condition:RageSpellDamage") then
|
|
modDB:NewMod("Damage", "MORE", output.RageEffect, "Rage", ModFlag.Spell)
|
|
else
|
|
modDB:NewMod("Damage", "MORE", output.RageEffect, "Rage", ModFlag.Attack)
|
|
end
|
|
if stacks == maxStacks then
|
|
modDB:NewMod("Condition:HaveMaximumRage", "FLAG", true, "")
|
|
end
|
|
output.InherentRageLossDelay = 2 + modDB:Sum("BASE", nil, "InherentRageLossDelay")
|
|
output.InherentRageLoss = (not modDB:Flag(nil, "InherentRageLossIsPrevented")) and 10 * (1 + modDB:Sum("INC", nil, "InherentRageLoss") / 100) or 0
|
|
end
|
|
if (env.configInput.multiplierManaBurnStacks or 0) > 0 then
|
|
local maxManaBurn = modDB:Sum("BASE", nil, "MaxManaBurnStacks")
|
|
if maxManaBurn == 0 then
|
|
maxManaBurn = 9999
|
|
end
|
|
local manaBurnStacks = m_min((env.configInput.multiplierManaBurnStacks or 0), maxManaBurn)
|
|
modDB:NewMod("Multiplier:ManaBurnStacks", "BASE", manaBurnStacks, "Config")
|
|
manaBurnStacks = manaBurnStacks + modDB:Sum("BASE", { actor = "player" }, "EffectiveManaBurnStacks")
|
|
if modDB:Flag(nil, "Condition:WeepingWoundsInsteadOfManaBurn") then
|
|
modDB:NewMod("Multiplier:WeepingWoundsStacks", "BASE", manaBurnStacks, "Config")
|
|
else
|
|
modDB:NewMod("Multiplier:EffectiveManaBurnStacks", "BASE", manaBurnStacks, "Config")
|
|
end
|
|
end
|
|
if modDB:Sum("BASE", nil, "CoveredInAshEffect") > 0 then
|
|
local effect = modDB:Sum("BASE", nil, "CoveredInAshEffect")
|
|
enemyDB:NewMod("FireDamageTaken", "INC", m_min(effect, 20), "Covered in Ash")
|
|
end
|
|
if modDB:Sum("BASE", nil, "CoveredInFrostEffect") > 0 then
|
|
local effect = modDB:Sum("BASE", nil, "CoveredInFrostEffect")
|
|
enemyDB:NewMod("ColdDamageTaken", "INC", m_min(effect, 20), "Covered in Frost")
|
|
end
|
|
if modDB:Flag(nil, "HasMalediction") then
|
|
modDB:NewMod("DamageTaken", "INC", 10, "Malediction")
|
|
modDB:NewMod("Damage", "INC", -10, "Malediction")
|
|
end
|
|
if modDB:Flag(nil, "HasMaddeningPresence") then
|
|
modDB:NewMod("ActionSpeed", "INC", -10, "Maddening Presence")
|
|
modDB:NewMod("Damage", "INC", -10, "Maddening Presence")
|
|
end
|
|
if modDB:Flag(nil, "HasShapersPresence") then
|
|
modDB:NewMod("BuffExpireFaster", "MORE", -20, "Shapers Presence")
|
|
end
|
|
if modDB:Flag(nil, "Condition:CanHaveSoulEater") then
|
|
local max = modDB:Override(nil, "SoulEaterMax") or modDB:Sum("BASE", nil, "SoulEaterMax")
|
|
modDB:NewMod("Multiplier:SoulEater", "BASE", 1, "Base", { type = "Multiplier", var = "SoulEaterStack", limit = max })
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Process charges
|
|
local function doActorCharges(env, actor)
|
|
local modDB = actor.modDB
|
|
local output = actor.output
|
|
|
|
-- Calculate current and maximum charges
|
|
output.PowerChargesMin = m_max(modDB:Sum("BASE", nil, "PowerChargesMin"), 0)
|
|
output.PowerChargesMax = m_max(modDB:Sum("BASE", nil, "PowerChargesMax"), 0)
|
|
output.PowerChargesDuration = m_floor(modDB:Sum("BASE", nil, "ChargeDuration") * calcLib.mod(modDB, nil, "PowerChargesDuration", "ChargeDuration"))
|
|
if modDB:Flag(nil, "MaximumFrenzyChargesIsMaximumPowerCharges") then
|
|
local source = modDB.mods["MaximumFrenzyChargesIsMaximumPowerCharges"][1].source
|
|
if not modDB:HasMod("OVERRIDE", {source = source:match("[^:]+")}, "FrenzyChargesMax") then
|
|
modDB:NewMod("FrenzyChargesMax", "OVERRIDE", output.PowerChargesMax, source)
|
|
end
|
|
end
|
|
output.FrenzyChargesMin = m_max(modDB:Sum("BASE", nil, "FrenzyChargesMin"), 0)
|
|
output.FrenzyChargesMax = m_max(modDB:Flag(nil, "MaximumFrenzyChargesIsMaximumPowerCharges") and output.PowerChargesMax or modDB:Sum("BASE", nil, "FrenzyChargesMax"), 0)
|
|
output.FrenzyChargesDuration = m_floor(modDB:Sum("BASE", nil, "ChargeDuration") * calcLib.mod(modDB, nil, "FrenzyChargesDuration", "ChargeDuration"))
|
|
if modDB:Flag(nil, "MaximumEnduranceChargesIsMaximumFrenzyCharges") then
|
|
local source = modDB.mods["MaximumEnduranceChargesIsMaximumFrenzyCharges"][1].source
|
|
if not modDB:HasMod("OVERRIDE", {source = source:match("[^:]+")}, "EnduranceChargesMax") then
|
|
modDB:NewMod("EnduranceChargesMax", "OVERRIDE", output.FrenzyChargesMax, source)
|
|
end
|
|
end
|
|
output.EnduranceChargesMin = m_max(modDB:Sum("BASE", nil, "EnduranceChargesMin"), 0)
|
|
output.EnduranceChargesMax = m_max(env.partyMembers.modDB:Flag(nil, "PartyMemberMaximumEnduranceChargesEqualToYours") and env.partyMembers.output.EnduranceChargesMax or (modDB:Flag(nil, "MaximumEnduranceChargesIsMaximumFrenzyCharges") and output.FrenzyChargesMax or modDB:Sum("BASE", nil, "EnduranceChargesMax")), 0)
|
|
output.EnduranceChargesDuration = m_floor(modDB:Sum("BASE", nil, "ChargeDuration") * calcLib.mod(modDB, nil, "EnduranceChargesDuration", "ChargeDuration"))
|
|
output.SiphoningChargesMax = m_max(modDB:Sum("BASE", nil, "SiphoningChargesMax"), 0)
|
|
output.ChallengerChargesMax = m_max(modDB:Sum("BASE", nil, "ChallengerChargesMax"), 0)
|
|
output.BlitzChargesMax = m_max(modDB:Sum("BASE", nil, "BlitzChargesMax"), 0)
|
|
output.InspirationChargesMax = m_max(modDB:Sum("BASE", nil, "InspirationChargesMax"), 0)
|
|
output.CrabBarriersMax = m_max(modDB:Sum("BASE", nil, "CrabBarriersMax"), 0)
|
|
output.BrutalChargesMin = m_max(modDB:Flag(nil, "MinimumEnduranceChargesEqualsMinimumBrutalCharges") and (modDB:Flag(nil, "MinimumEnduranceChargesIsMaximumEnduranceCharges") and output.EnduranceChargesMax or output.EnduranceChargesMin) or 0 , 0)
|
|
output.BrutalChargesMax = m_max(modDB:Flag(nil, "MaximumEnduranceChargesEqualsMaximumBrutalCharges") and output.EnduranceChargesMax or 0, 0)
|
|
output.AbsorptionChargesMin = m_max(modDB:Flag(nil, "MinimumPowerChargesEqualsMinimumAbsorptionCharges") and (modDB:Flag(nil, "MinimumPowerChargesIsMaximumPowerCharges") and output.PowerChargesMax or output.PowerChargesMin) or 0, 0)
|
|
output.AbsorptionChargesMax = m_max(modDB:Flag(nil, "MaximumPowerChargesEqualsMaximumAbsorptionCharges") and output.PowerChargesMax or 0, 0)
|
|
output.AfflictionChargesMin = m_max(modDB:Flag(nil, "MinimumFrenzyChargesEqualsMinimumAfflictionCharges") and (modDB:Flag(nil, "MinimumFrenzyChargesIsMaximumFrenzyCharges") and output.FrenzyChargesMax or output.FrenzyChargesMin) or 0, 0)
|
|
output.AfflictionChargesMax = m_max(modDB:Flag(nil, "MaximumFrenzyChargesEqualsMaximumAfflictionCharges") and output.FrenzyChargesMax or 0, 0)
|
|
output.BloodChargesMax = m_max(modDB:Sum("BASE", nil, "BloodChargesMax"), 0)
|
|
output.SpiritChargesMax = m_max(modDB:Sum("BASE", nil, "SpiritChargesMax"), 0)
|
|
|
|
-- Initialize Charges
|
|
output.PowerCharges = 0
|
|
output.FrenzyCharges = 0
|
|
output.EnduranceCharges = 0
|
|
output.SiphoningCharges = 0
|
|
output.ChallengerCharges = 0
|
|
output.BlitzCharges = 0
|
|
output.InspirationCharges = 0
|
|
output.GhostShrouds = 0
|
|
output.BrutalCharges = 0
|
|
output.AbsorptionCharges = 0
|
|
output.AfflictionCharges = 0
|
|
output.BloodCharges = 0
|
|
output.SpiritCharges = 0
|
|
|
|
-- Conditionally over-write Charge values
|
|
if modDB:Flag(nil, "MinimumFrenzyChargesIsMaximumFrenzyCharges") then
|
|
output.FrenzyChargesMin = output.FrenzyChargesMax
|
|
end
|
|
if modDB:Flag(nil, "MinimumEnduranceChargesIsMaximumEnduranceCharges") then
|
|
output.EnduranceChargesMin = output.EnduranceChargesMax
|
|
end
|
|
if modDB:Flag(nil, "MinimumPowerChargesIsMaximumPowerCharges") then
|
|
output.PowerChargesMin = output.PowerChargesMax
|
|
end
|
|
if modDB:Flag(nil, "UsePowerCharges") then
|
|
output.PowerCharges = modDB:Override(nil, "PowerCharges") or output.PowerChargesMax
|
|
end
|
|
if modDB:Flag(nil, "PowerChargesConvertToAbsorptionCharges") then
|
|
-- we max with possible Power Charge Override from Config since Absorption Charges won't have their own config entry
|
|
-- and are converted from Power Charges
|
|
output.AbsorptionCharges = m_max(output.PowerCharges, m_min(output.AbsorptionChargesMax, output.AbsorptionChargesMin))
|
|
output.PowerCharges = 0
|
|
else
|
|
output.PowerCharges = m_max(output.PowerCharges, m_min(output.PowerChargesMax, output.PowerChargesMin))
|
|
end
|
|
output.RemovablePowerCharges = m_max(output.PowerCharges - output.PowerChargesMin, 0)
|
|
if modDB:Flag(nil, "UseFrenzyCharges") then
|
|
output.FrenzyCharges = modDB:Override(nil, "FrenzyCharges") or output.FrenzyChargesMax
|
|
end
|
|
if modDB:Flag(nil, "FrenzyChargesConvertToAfflictionCharges") then
|
|
-- we max with possible Power Charge Override from Config since Absorption Charges won't have their own config entry
|
|
-- and are converted from Power Charges
|
|
output.AfflictionCharges = m_max(output.FrenzyCharges, m_min(output.AfflictionChargesMax, output.AfflictionChargesMin))
|
|
output.FrenzyCharges = 0
|
|
else
|
|
output.FrenzyCharges = m_max(output.FrenzyCharges, m_min(output.FrenzyChargesMax, output.FrenzyChargesMin))
|
|
end
|
|
output.RemovableFrenzyCharges = m_max(output.FrenzyCharges - output.FrenzyChargesMin, 0)
|
|
if modDB:Flag(nil, "UseEnduranceCharges") then
|
|
output.EnduranceCharges = modDB:Override(nil, "EnduranceCharges") or output.EnduranceChargesMax
|
|
end
|
|
if modDB:Flag(nil, "EnduranceChargesConvertToBrutalCharges") then
|
|
-- we max with possible Endurance Charge Override from Config since Brutal Charges won't have their own config entry
|
|
-- and are converted from Endurance Charges
|
|
output.BrutalCharges = m_max(output.EnduranceCharges, m_min(output.BrutalChargesMax, output.BrutalChargesMin))
|
|
output.EnduranceCharges = 0
|
|
else
|
|
output.EnduranceCharges = m_max(output.EnduranceCharges, m_min(output.EnduranceChargesMax, output.EnduranceChargesMin))
|
|
end
|
|
output.RemovableEnduranceCharges = m_max(output.EnduranceCharges - output.EnduranceChargesMin, 0)
|
|
if modDB:Flag(nil, "UseSiphoningCharges") then
|
|
output.SiphoningCharges = modDB:Override(nil, "SiphoningCharges") or output.SiphoningChargesMax
|
|
end
|
|
if modDB:Flag(nil, "UseChallengerCharges") then
|
|
output.ChallengerCharges = modDB:Override(nil, "ChallengerCharges") or output.ChallengerChargesMax
|
|
end
|
|
if modDB:Flag(nil, "UseBlitzCharges") then
|
|
output.BlitzCharges = modDB:Override(nil, "BlitzCharges") or output.BlitzChargesMax
|
|
end
|
|
if actor == env.player then
|
|
output.InspirationCharges = modDB:Override(nil, "InspirationCharges") or output.InspirationChargesMax
|
|
end
|
|
if modDB:Flag(nil, "UseGhostShrouds") then
|
|
output.GhostShrouds = modDB:Override(nil, "GhostShrouds") or 3
|
|
end
|
|
output.BloodCharges = m_min(modDB:Override(nil, "BloodCharges") or output.BloodChargesMax, output.BloodChargesMax)
|
|
output.SpiritCharges = m_min(modDB:Override(nil, "SpiritCharges") or 0, output.SpiritChargesMax)
|
|
|
|
output.CrabBarriers = m_min(modDB:Override(nil, "CrabBarriers") or output.CrabBarriersMax, output.CrabBarriersMax)
|
|
if modDB:Flag(nil, "HaveMaximumPowerCharges") then
|
|
output.PowerCharges = output.PowerChargesMax
|
|
end
|
|
if modDB:Flag(nil, "HaveMaximumFrenzyCharges") then
|
|
output.FrenzyCharges = output.FrenzyChargesMax
|
|
end
|
|
if modDB:Flag(nil, "HaveMaximumEnduranceCharges") then
|
|
output.EnduranceCharges = output.EnduranceChargesMax
|
|
end
|
|
output.TotalCharges = output.PowerCharges + output.FrenzyCharges + output.EnduranceCharges
|
|
output.RemovableTotalCharges = output.RemovableEnduranceCharges + output.RemovableFrenzyCharges + output.RemovablePowerCharges
|
|
modDB.multipliers["PowerCharge"] = output.PowerCharges
|
|
modDB.multipliers["PowerChargeMax"] = output.PowerChargesMax
|
|
modDB.multipliers["RemovablePowerCharge"] = output.RemovablePowerCharges
|
|
modDB.multipliers["FrenzyCharge"] = output.FrenzyCharges
|
|
modDB.multipliers["RemovableFrenzyCharge"] = output.RemovableFrenzyCharges
|
|
modDB.multipliers["EnduranceCharge"] = output.EnduranceCharges
|
|
modDB.multipliers["RemovableEnduranceCharge"] = output.RemovableEnduranceCharges
|
|
modDB.multipliers["TotalCharges"] = output.TotalCharges
|
|
modDB.multipliers["RemovableTotalCharges"] = output.RemovableTotalCharges
|
|
modDB.multipliers["SiphoningCharge"] = output.SiphoningCharges
|
|
modDB.multipliers["ChallengerCharge"] = output.ChallengerCharges
|
|
modDB.multipliers["BlitzCharge"] = output.BlitzCharges
|
|
modDB.multipliers["InspirationCharge"] = output.InspirationCharges
|
|
modDB.multipliers["GhostShroud"] = output.GhostShrouds
|
|
modDB.multipliers["CrabBarrier"] = output.CrabBarriers
|
|
modDB.multipliers["BrutalCharge"] = output.BrutalCharges
|
|
modDB.multipliers["AbsorptionCharge"] = output.AbsorptionCharges
|
|
modDB.multipliers["AfflictionCharge"] = output.AfflictionCharges
|
|
modDB.multipliers["BloodCharge"] = output.BloodCharges
|
|
modDB.multipliers["SpiritCharge"] = output.SpiritCharges
|
|
end
|
|
|
|
function calcs.actionSpeedMod(actor)
|
|
local modDB = actor.modDB
|
|
local minimumActionSpeed = modDB:Max(nil, "MinimumActionSpeed") or 0
|
|
local maximumActionSpeedReduction = modDB:Max(nil, "MaximumActionSpeedReduction")
|
|
local actionSpeedMod = 1 + (m_max(-data.misc.TemporalChainsEffectCap, modDB:Sum("INC", nil, "TemporalChainsActionSpeed")) + modDB:Sum("INC", nil, "ActionSpeed")) / 100
|
|
actionSpeedMod = m_max(minimumActionSpeed / 100, actionSpeedMod)
|
|
if maximumActionSpeedReduction then
|
|
actionSpeedMod = m_min((100 - maximumActionSpeedReduction) / 100, actionSpeedMod)
|
|
end
|
|
return actionSpeedMod
|
|
end
|
|
|
|
-- Finalises the environment and performs the stat calculations:
|
|
-- 1. Merges keystone modifiers
|
|
-- 2. Initialises minion skills
|
|
-- 3. Initialises the main skill's minion, if present
|
|
-- 4. Merges flask effects
|
|
-- 5. Sets conditions and calculates attributes (doActorAttribsConditions)
|
|
-- 6. Calculates life and mana (doActorLifeMana)
|
|
-- 6. Calculates reservations
|
|
-- 7. Sets life/mana reservation (doActorLifeManaReservation)
|
|
-- 8. Processes buffs and debuffs
|
|
-- 9. Processes charges and misc buffs (doActorCharges, doActorMisc)
|
|
-- 10. Calculates defence and offence stats (calcs.defence, calcs.offence)
|
|
function calcs.perform(env, skipEHP)
|
|
local modDB = env.modDB
|
|
local enemyDB = env.enemyDB
|
|
|
|
-- Merge keystone modifiers
|
|
env.keystonesAdded = { }
|
|
mergeKeystones(env)
|
|
|
|
-- Build minion skills
|
|
for _, activeSkill in ipairs(env.player.activeSkillList) do
|
|
activeSkill.skillModList = new("ModList", activeSkill.baseSkillModList)
|
|
if activeSkill.minion then
|
|
activeSkill.minion.modDB = new("ModDB")
|
|
activeSkill.minion.modDB.actor = activeSkill.minion
|
|
calcs.createMinionSkills(env, activeSkill)
|
|
activeSkill.skillPartName = activeSkill.minion.mainSkill.activeEffect.grantedEffect.name
|
|
end
|
|
end
|
|
|
|
env.player.output = { }
|
|
env.enemy.output = { }
|
|
local output = env.player.output
|
|
|
|
env.partyMembers = env.build.partyTab.actor
|
|
env.player.partyMembers = env.partyMembers
|
|
local partyTabEnableExportBuffs = env.build.partyTab.enableExportBuffs and env.mode ~= "CALCULATOR"
|
|
|
|
env.minion = env.player.mainSkill.minion
|
|
if env.minion then
|
|
-- Initialise minion modifier database
|
|
output.Minion = { }
|
|
env.minion.output = output.Minion
|
|
env.minion.modDB.multipliers["Level"] = env.minion.level
|
|
calcs.initModDB(env, env.minion.modDB)
|
|
env.minion.modDB:NewMod("Life", "BASE", m_floor(env.minion.lifeTable[env.minion.level] * env.minion.minionData.life), "Base")
|
|
if env.minion.minionData.energyShield then
|
|
env.minion.modDB:NewMod("EnergyShield", "BASE", m_floor(env.data.monsterAllyLifeTable[env.minion.level] * env.minion.minionData.life * env.minion.minionData.energyShield), "Base")
|
|
end
|
|
--Armour formula is math.floor((10 + 2 * level) * 1.067 ^ level)
|
|
env.minion.modDB:NewMod("Armour", "BASE", round(env.data.monsterArmourTable[env.minion.level] * (env.minion.minionData.armour or 1)), "Base")
|
|
--Evasion formula is math.floor((50 + 16 * level + 16 * level * (MonsterType.Evasion / 100)) * (1.0212 ^ level)
|
|
env.minion.modDB:NewMod("Evasion", "BASE", round(env.data.monsterEvasionTable[env.minion.level] * (env.minion.minionData.evasion or 1)), "Base")
|
|
if modDB:Flag(nil, "MinionAccuracyEqualsAccuracy") then
|
|
env.minion.modDB:NewMod("Accuracy", "BASE", calcLib.val(modDB, "Accuracy") + calcLib.val(modDB, "Dex") * (modDB:Override(nil, "DexAccBonusOverride") or data.misc.AccuracyPerDexBase), "Player")
|
|
else
|
|
env.minion.modDB:NewMod("Accuracy", "BASE", round(env.data.monsterAccuracyTable[env.minion.level] * (env.minion.minionData.accuracy or 1)), "Base")
|
|
end
|
|
env.minion.modDB:NewMod("CritMultiplier", "BASE", 30, "Base")
|
|
env.minion.modDB:NewMod("CritDegenMultiplier", "BASE", 30, "Base")
|
|
env.minion.modDB:NewMod("FireResist", "BASE", env.minion.minionData.fireResist, "Base")
|
|
env.minion.modDB:NewMod("ColdResist", "BASE", env.minion.minionData.coldResist, "Base")
|
|
env.minion.modDB:NewMod("LightningResist", "BASE", env.minion.minionData.lightningResist, "Base")
|
|
env.minion.modDB:NewMod("ChaosResist", "BASE", env.minion.minionData.chaosResist, "Base")
|
|
env.minion.modDB:NewMod("CritChance", "INC", 50, "Base", { type = "Multiplier", var = "PowerCharge" })
|
|
env.minion.modDB:NewMod("Speed", "INC", 4, "Base", ModFlag.Attack, { type = "Multiplier", var = "FrenzyCharge" })
|
|
env.minion.modDB:NewMod("Speed", "INC", 4, "Base", ModFlag.Cast, { type = "Multiplier", var = "FrenzyCharge" })
|
|
env.minion.modDB:NewMod("Damage", "MORE", 4, "Base", { type = "Multiplier", var = "FrenzyCharge" })
|
|
env.minion.modDB:NewMod("PhysicalDamageReduction", "BASE", 4, "Base", { type = "Multiplier", var = "EnduranceCharge" })
|
|
env.minion.modDB:NewMod("ElementalDamageReduction", "BASE", 4, "Base", { type = "Multiplier", var = "EnduranceCharge" })
|
|
env.minion.modDB:NewMod("ProjectileCount", "BASE", 1, "Base")
|
|
env.minion.modDB:NewMod("MaximumFortification", "BASE", 20, "Base")
|
|
env.minion.modDB:NewMod("Damage", "MORE", 200, "Base", 0, KeywordFlag.Bleed, { type = "ActorCondition", actor = "enemy", var = "Moving" })
|
|
for _, mod in ipairs(env.minion.minionData.modList) do
|
|
env.minion.modDB:AddMod(mod)
|
|
end
|
|
for _, mod in ipairs(env.player.mainSkill.extraSkillModList) do
|
|
env.minion.modDB:AddMod(mod)
|
|
end
|
|
if env.aegisModList then
|
|
env.minion.itemList["Weapon 3"] = env.player.itemList["Weapon 2"]
|
|
env.minion.modDB:AddList(env.aegisModList)
|
|
end
|
|
if env.theIronMass and env.minion.type == "RaisedSkeleton" then
|
|
env.minion.modDB:AddList(env.theIronMass)
|
|
end
|
|
if env.player.mainSkill.skillData.minionUseBowAndQuiver then
|
|
if env.player.weaponData1.type == "Bow" then
|
|
env.minion.modDB:AddList(env.player.itemList["Weapon 1"].slotModList[1])
|
|
end
|
|
if env.player.itemList["Weapon 2"] and env.player.itemList["Weapon 2"].type == "Quiver" then
|
|
env.minion.modDB:ScaleAddList(env.player.itemList["Weapon 2"].modList, m_max(modDB:Sum("BASE", nil, "WidowHailMultiplier"), 1))
|
|
end
|
|
if modDB:Flag(nil, "BlinkAndMirrorUseGloves") and env.player.itemList["Gloves"] then
|
|
env.minion.modDB:AddList(env.player.itemList["Gloves"].modList)
|
|
end
|
|
end
|
|
if env.minion.itemSet or env.minion.uses then
|
|
for slotName, slot in pairs(env.build.itemsTab.slots) do
|
|
if env.minion.uses[slotName] then
|
|
local item
|
|
if env.minion.itemSet then
|
|
if slot.weaponSet == 1 and env.minion.itemSet.useSecondWeaponSet then
|
|
slotName = slotName .. " Swap"
|
|
end
|
|
item = env.build.itemsTab.items[env.minion.itemSet[slotName].selItemId]
|
|
else
|
|
item = env.player.itemList[slotName]
|
|
end
|
|
if item then
|
|
env.minion.itemList[slotName] = item
|
|
env.minion.modDB:AddList(item.modList or item.slotModList[slot.slotNum])
|
|
end
|
|
end
|
|
end
|
|
end
|
|
if modDB:Sum("BASE", nil, "StrengthAddedToMinions") > 0 then
|
|
env.minion.modDB:NewMod("Str", "BASE", round(calcLib.val(modDB, "Str") * modDB:Sum("BASE", nil, "StrengthAddedToMinions") / 100), "Player")
|
|
end
|
|
end
|
|
if env.aegisModList then
|
|
env.player.itemList["Weapon 2"] = nil
|
|
end
|
|
if modDB:Flag(nil, "AlchemistsGenius") then
|
|
local effectMod = 1 + modDB:Sum("INC", nil, "BuffEffectOnSelf") / 100
|
|
modDB:NewMod("FlaskEffect", "INC", m_floor(10 * effectMod), "Alchemist's Genius")
|
|
modDB:NewMod("FlaskChargesGained", "INC", m_floor(20 * effectMod), "Alchemist's Genius")
|
|
end
|
|
|
|
local hasGuaranteedBonechill = false
|
|
|
|
-- Banners
|
|
if modDB:Flag(nil,"Condition:BannerPlanted") then
|
|
local max = modDB:Sum("BASE", nil, "MaximumValour")
|
|
local stacks = modDB:Sum("BASE", nil, "Multiplier:ValourStacks")
|
|
modDB:NewMod("Multiplier:BannerValour", "BASE", m_min(stacks, max), "Base")
|
|
end
|
|
|
|
if modDB:Flag(nil, "CryWolfMinimumPower") and modDB:Sum("BASE", nil, "WarcryPower") < 10 then
|
|
modDB:NewMod("WarcryPower", "OVERRIDE", 10, "Minimum Warcry Power from CryWolf")
|
|
end
|
|
if modDB:Flag(nil, "WarcryInfinitePower") then
|
|
modDB:NewMod("WarcryPower", "OVERRIDE", 999999, "Warcries have infinite power")
|
|
end
|
|
output.WarcryPower = modDB:Override(nil, "WarcryPower") or modDB:Sum("BASE", nil, "WarcryPower") or 0
|
|
modDB.multipliers["WarcryPower"] = output.WarcryPower
|
|
|
|
for _, activeSkill in ipairs(env.player.activeSkillList) do
|
|
if activeSkill.skillTypes[SkillType.Brand] then
|
|
local attachLimit = activeSkill.skillModList:Sum("BASE", activeSkill.skillCfg, "BrandsAttachedLimit")
|
|
local attached = modDB:Sum("BASE", nil, "Multiplier:ConfigBrandsAttachedToEnemy")
|
|
local activeBrands = modDB:Sum("BASE", nil, "Multiplier:ConfigActiveBrands")
|
|
local actual = m_min(attachLimit, attached)
|
|
-- Cap the number of active brands by the limit, which is 3 by default
|
|
modDB.multipliers["ActiveBrand"] = m_min(activeBrands, modDB:Sum("BASE", nil, "ActiveBrandLimit"))
|
|
modDB.multipliers["BrandsAttachedToEnemy"] = m_max(actual, modDB.multipliers["BrandsAttachedToEnemy"] or 0)
|
|
enemyDB.multipliers["BrandsAttached"] = m_max(actual, enemyDB.multipliers["BrandsAttached"] or 0)
|
|
end
|
|
if activeSkill.skillFlags.totem then
|
|
local limit = env.player.mainSkill.skillModList:Sum("BASE", env.player.mainSkill.skillCfg, "ActiveTotemLimit", "ActiveBallistaLimit" )
|
|
output.ActiveTotemLimit = m_max(limit, output.ActiveTotemLimit or 0)
|
|
output.TotemsSummoned = modDB:Override(nil, "TotemsSummoned") or output.ActiveTotemLimit
|
|
enemyDB.multipliers["TotemsSummoned"] = m_max(output.TotemsSummoned or 0, enemyDB.multipliers["TotemsSummoned"] or 0)
|
|
end
|
|
-- The actual hexes as opposed to hex related skills all have the curse flag. TotemCastsWhenNotDetached is to remove blasphemy
|
|
-- Note that this doesn't work for triggers yet, insufficient support
|
|
if activeSkill.skillFlags.hex and activeSkill.skillFlags.curse and not activeSkill.skillTypes[SkillType.TotemCastsWhenNotDetached] and activeSkill.skillModList:Sum("BASE", nil, "MaxDoom") then
|
|
local hexDoom = modDB:Sum("BASE", nil, "Multiplier:HexDoomStack")
|
|
local maxDoom = activeSkill.skillModList:Sum("BASE", nil, "MaxDoom")
|
|
local doomEffect = activeSkill.skillModList:More(nil, "DoomEffect")
|
|
-- Update the max doom limit
|
|
output.HexDoomLimit = m_max(maxDoom, output.HexDoomLimit or 0)
|
|
-- Update the Hex Doom to apply
|
|
activeSkill.skillModList:NewMod("CurseEffect", "INC", m_min(hexDoom, maxDoom) * doomEffect, "Doom")
|
|
modDB.multipliers["HexDoom"] = m_min(m_max(hexDoom, modDB.multipliers["HexDoom"] or 0), output.HexDoomLimit)
|
|
end
|
|
if (activeSkill.activeEffect.grantedEffect.name == "Vaal Lightning Trap" or activeSkill.activeEffect.grantedEffect.name == "Shock Ground") then
|
|
-- Shock effect applies to shocked ground
|
|
local effect = activeSkill.skillModList:Sum("BASE", nil, "ShockedGroundEffect") * (1 + activeSkill.skillModList:Sum("INC", nil, "EnemyShockEffect") / 100)
|
|
modDB:NewMod("ShockOverride", "BASE", effect, "Shocked Ground", { type = "ActorCondition", actor = "enemy", var = "OnShockedGround" } )
|
|
end
|
|
if activeSkill.skillData.supportBonechill and (activeSkill.skillTypes[SkillType.ChillingArea] or activeSkill.skillTypes[SkillType.NonHitChill] or not activeSkill.skillModList:Flag(nil, "CannotChill")) then
|
|
output.HasBonechill = true
|
|
end
|
|
if activeSkill.activeEffect.grantedEffect.name == "Summon Skitterbots" then
|
|
local skitterbotAilmentEffect = activeSkill.skillModList:Sum("INC", nil, "SkitterbotAilmentEffect")
|
|
if not activeSkill.skillModList:Flag(nil, "SkitterbotsCannotShock") then
|
|
local effect = data.nonDamagingAilment.Shock.default * (1 + (activeSkill.skillModList:Sum("INC", { source = "Skill" }, "EnemyShockEffect") + skitterbotAilmentEffect) / 100)
|
|
modDB:NewMod("ShockOverride", "BASE", effect, activeSkill.activeEffect.grantedEffect.name)
|
|
enemyDB:NewMod("Condition:Shocked", "FLAG", true, activeSkill.activeEffect.grantedEffect.name)
|
|
if activeSkill.skillModList:Flag(nil, "SkitterbotAffectPlayer") then
|
|
modDB:NewMod("Shock", "FLAG", true, activeSkill.activeEffect.grantedEffect.name)
|
|
modDB:NewMod("SelfShockOverride", "BASE", effect, activeSkill.activeEffect.grantedEffect.name)
|
|
end
|
|
end
|
|
if not activeSkill.skillModList:Flag(nil, "SkitterbotsCannotChill") then
|
|
local effect = data.nonDamagingAilment.Chill.default * (1 + (activeSkill.skillModList:Sum("INC", { source = "Skill" }, "EnemyChillEffect") + skitterbotAilmentEffect) / 100)
|
|
modDB:NewMod("ChillOverride", "BASE", effect, activeSkill.activeEffect.grantedEffect.name)
|
|
enemyDB:NewMod("Condition:Chilled", "FLAG", true, activeSkill.activeEffect.grantedEffect.name)
|
|
if activeSkill.skillModList:Flag(nil, "SkitterbotAffectPlayer") then
|
|
modDB:NewMod("Chill", "FLAG", true, activeSkill.activeEffect.grantedEffect.name)
|
|
modDB:NewMod("SelfChillOverride", "BASE", effect, activeSkill.activeEffect.grantedEffect.name)
|
|
end
|
|
if activeSkill.skillData.supportBonechill then
|
|
hasGuaranteedBonechill = true
|
|
modDB:NewMod("SkitterbotBonechill", "FLAG", true, activeSkill.activeEffect.grantedEffect.name)
|
|
end
|
|
end
|
|
if activeSkill.skillModList:Flag(nil, "ScorchingSkitterbot") then
|
|
local effect = data.nonDamagingAilment.Scorch.default * (1 + (activeSkill.skillModList:Sum("INC", { source = "Skill" }, "EnemyScorchEffect") + skitterbotAilmentEffect) / 100)
|
|
modDB:NewMod("ScorchOverride", "BASE", effect, activeSkill.activeEffect.grantedEffect.name)
|
|
enemyDB:NewMod("Condition:Scorched", "FLAG", true, activeSkill.activeEffect.grantedEffect.name)
|
|
if activeSkill.skillModList:Flag(nil, "SkitterbotAffectPlayer") then
|
|
modDB:NewMod("Scorch", "FLAG", true, activeSkill.activeEffect.grantedEffect.name)
|
|
modDB:NewMod("SelfScorchOverride", "BASE", effect, activeSkill.activeEffect.grantedEffect.name)
|
|
end
|
|
end
|
|
elseif activeSkill.skillTypes[SkillType.ChillingArea] or (activeSkill.skillTypes[SkillType.NonHitChill] and not activeSkill.skillModList:Flag(nil, "CannotChill")) then
|
|
local effect = data.nonDamagingAilment.Chill.default * calcLib.mod(activeSkill.skillModList, activeSkill.skillCfg, "EnemyChillEffect")
|
|
modDB:NewMod("ChillOverride", "BASE", effect, activeSkill.activeEffect.grantedEffect.name)
|
|
enemyDB:NewMod("Condition:Chilled", "FLAG", true, activeSkill.activeEffect.grantedEffect.name)
|
|
if activeSkill.skillData.supportBonechill then
|
|
hasGuaranteedBonechill = true
|
|
end
|
|
end
|
|
if activeSkill.minion and activeSkill.minion.minionData and activeSkill.minion.minionData.limit then
|
|
local limit = m_floor(modDB:Override(nil, activeSkill.minion.minionData.limit) or calcLib.val(activeSkill.skillModList, activeSkill.minion.minionData.limit))
|
|
output[activeSkill.minion.minionData.limit] = m_max(limit, output[activeSkill.minion.minionData.limit] or 0)
|
|
end
|
|
if activeSkill.skillTypes[SkillType.CreatesMinion] and not activeSkill.skillTypes[SkillType.MinionsAreUndamageable] then
|
|
modDB:NewMod("Condition:HaveDamageableMinion", "FLAG", true)
|
|
end
|
|
if env.mode_buffs and activeSkill.skillFlags.warcry then
|
|
if activeSkill.activeEffect.grantedEffect.name == "Rallying Cry" and not activeSkill.skillModList:Flag(nil, "CannotShareWarcryBuffs") and not modDB:Flag(nil, "RallyingActive") then
|
|
env.player.modDB:NewMod("RallyingExertMoreDamagePerAlly", "BASE", activeSkill.skillModList:Sum("BASE", env.player.mainSkill.skillCfg, "RallyingCryExertDamageBonus"))
|
|
modDB:NewMod("RallyingActive", "FLAG", true) -- Prevents effect from applying multiple times
|
|
elseif activeSkill.activeEffect.grantedEffect.name == "Seismic Cry" and not modDB:Flag(nil, "SeismicActive") then
|
|
env.player.modDB:NewMod("SeismicMoreAoE", "BASE", activeSkill.skillModList:Sum("BASE", env.player.mainSkill.skillCfg, "SeismicAoEMoreMultiplier"))
|
|
modDB:NewMod("SeismicActive", "FLAG", true) -- Prevents effect from applying multiple times
|
|
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
|
|
activeSkill.skillModList:NewMod("Damage", "INC", value.mod.value, value.mod.source, value.mod.flags, value.mod.keywordFlags, unpack(value.mod))
|
|
end
|
|
for _, value in ipairs(activeSkill.skillModList:Tabulate("MORE", env.player.mainSkill.skillCfg, "TriggeredDamage")) do
|
|
activeSkill.skillModList:NewMod("Damage", "MORE", value.mod.value, value.mod.source, value.mod.flags, value.mod.keywordFlags, unpack(value.mod))
|
|
end
|
|
-- Set trigger time to 1 min in ms ( == 6000 ). Technically any large value would do.
|
|
activeSkill.skillData.triggerTime = 60 * 1000
|
|
end
|
|
-- The Saviour
|
|
if activeSkill.activeEffect.grantedEffect.name == "Reflection" or activeSkill.skillData.triggeredBySaviour then
|
|
activeSkill.infoMessage = "Triggered by a Crit from The Saviour"
|
|
activeSkill.infoTrigger = "Saviour"
|
|
end
|
|
end
|
|
|
|
-- Special Rarity / Quantity Calc for Bisco's
|
|
local lootQuantityNormalEnemies = modDB:Sum("INC", nil, "LootQuantityNormalEnemies")
|
|
output.LootQuantityNormalEnemies = (lootQuantityNormalEnemies > 0) and lootQuantityNormalEnemies + modDB:Sum("INC", nil, "LootQuantity") or 0
|
|
local lootRarityMagicEnemies = modDB:Sum("INC", nil, "LootRarityMagicEnemies")
|
|
output.LootRarityMagicEnemies = (lootRarityMagicEnemies > 0) and lootRarityMagicEnemies + modDB:Sum("INC", nil, "LootRarity") or 0
|
|
|
|
local breakdown = nil
|
|
if env.mode == "CALCS" then
|
|
-- Initialise breakdown module
|
|
breakdown = LoadModule(calcs.breakdownModule, modDB, output, env.player)
|
|
env.player.breakdown = breakdown
|
|
if env.minion then
|
|
env.minion.breakdown = LoadModule(calcs.breakdownModule, env.minion.modDB, env.minion.output, env.minion)
|
|
end
|
|
end
|
|
|
|
if modDB:Flag(nil, "ConvertArmourESToLife") then
|
|
local energyShieldBase
|
|
local tempTable1 = { }
|
|
local slotCfg = wipeTable(tempTable1)
|
|
for _, slot in pairs({"Helmet","Gloves","Boots","Body Armour","Weapon 2","Weapon 3"}) do
|
|
local armourData = env.player.itemList[slot] and env.player.itemList[slot].armourData
|
|
if armourData then
|
|
slotCfg.slotName = slot
|
|
energyShieldBase = armourData.EnergyShield or 0
|
|
if energyShieldBase > 0 then
|
|
modDB:NewMod("Life", "BASE", energyShieldBase, slot.." ES to Life Conversion")
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
if modDB:Flag(nil, "ConvertBodyArmourArmourEvasionToWard") then
|
|
local ward
|
|
local armourData = env.player.itemList["Body Armour"] and env.player.itemList["Body Armour"].armourData
|
|
if armourData then
|
|
ward = armourData.Evasion + armourData.Armour
|
|
if ward > 0 then
|
|
local wardMult = ((modDB:Sum("BASE", nil,"BodyArmourArmourEvasionToWardPercent") or 0) / 100)
|
|
modDB:NewMod("Ward", "BASE", ward * wardMult , "Body Armour Armour And Evasion Rating to Ward Conversion")
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Special handling of Mageblood
|
|
local maxActiveMagicUtilityCount = modDB:Sum("BASE", nil, "ActiveMagicUtilityFlasks")
|
|
if maxActiveMagicUtilityCount > 0 then
|
|
local curActiveMagicUtilityCount = 0
|
|
for _, slot in pairs(env.build.itemsTab.orderedSlots) do
|
|
local slotName = slot.slotName
|
|
local item = env.build.itemsTab.items[slot.selItemId]
|
|
if item and item.type == "Flask" then
|
|
local mageblood_applies = item.rarity == "MAGIC" and not (item.baseName:match("Life Flask") or
|
|
item.baseName:match("Mana Flask") or item.baseName:match("Hybrid Flask")) and
|
|
curActiveMagicUtilityCount < maxActiveMagicUtilityCount
|
|
if mageblood_applies then
|
|
env.flasks[item] = true
|
|
curActiveMagicUtilityCount = curActiveMagicUtilityCount + 1
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
local effectInc = modDB:Sum("INC", {actor = "player"}, "FlaskEffect")
|
|
local effectIncMagic = modDB:Sum("INC", {actor = "player"}, "MagicUtilityFlaskEffect")
|
|
local effectIncNonPlayer = modDB:Sum("INC", nil, "FlaskEffect")
|
|
local effectIncMagicNonPlayer = modDB:Sum("INC", nil, "MagicUtilityFlaskEffect")
|
|
local flasksApplyToMinion = env.minion and modDB:Flag(env.player.mainSkill.skillCfg, "FlasksApplyToMinion")
|
|
local quickSilverAppliesToAllies = env.minion and modDB:Flag(env.player.mainSkill.skillCfg, "QuickSilverAppliesToAllies")
|
|
local nonUniqueFlasksApplyToMinion = env.minion and env.minion.modDB:Flag(nil, "ParentNonUniqueFlasksAppliedToYou")
|
|
local flaskTotalRateInc = modDB:Sum("INC", nil, "FlaskRecoveryRate")
|
|
local flaskDurInc = modDB:Sum("INC", nil, "FlaskDuration")
|
|
|
|
-- flask breakdown
|
|
if breakdown then
|
|
local chargesGenerated = modDB:Sum("BASE", nil, "FlaskChargesGenerated")
|
|
local usedFlasks = 0
|
|
for i, v in pairs(env.flasks) do
|
|
if v then
|
|
usedFlasks = usedFlasks + 1
|
|
end
|
|
end
|
|
|
|
local chargesGeneratedPerFlask = modDB:Sum("BASE", nil, "FlaskChargesGeneratedPerEmptyFlask") * (5 - usedFlasks)
|
|
local totalChargesGenerated = chargesGenerated + chargesGeneratedPerFlask
|
|
local utilityChargesGenerated = modDB:Sum("BASE", nil, "UtilityFlaskChargesGenerated")
|
|
local lifeChargesGenerated = modDB:Sum("BASE", nil, "LifeFlaskChargesGenerated")
|
|
local manaChargesGenerated = modDB:Sum("BASE", nil, "ManaFlaskChargesGenerated")
|
|
|
|
output.FlaskEffect = effectInc
|
|
output.FlaskChargeGen = totalChargesGenerated
|
|
output.LifeFlaskChargeGen = totalChargesGenerated + lifeChargesGenerated
|
|
output.ManaFlaskChargeGen = totalChargesGenerated + manaChargesGenerated
|
|
output.UtilityFlaskChargeGen = totalChargesGenerated + utilityChargesGenerated
|
|
output.FlaskChargeOnCritChance = m_min(100, modDB:Sum("BASE", nil, "FlaskChargeOnCritChance"))
|
|
end
|
|
|
|
-- Merge flask modifiers
|
|
local function calcFlaskRecovery(type, item)
|
|
local out = {}
|
|
local lType = type:lower()
|
|
|
|
if not item.flaskData[lType.."EffectNotRemoved"] and not modDB:Flag(nil, type.."FlaskEffectNotRemoved") then
|
|
return out
|
|
end
|
|
|
|
local name = item.name
|
|
local base = item.flaskData[lType.."Base"]
|
|
local dur = item.flaskData.duration
|
|
local instPerc = item.flaskData.instantPerc
|
|
local flaskRecInc = modDB:Sum("INC", nil, "Flask"..type.."Recovery")
|
|
local flaskRecMore = modDB:More(nil, "Flask"..type.."Recovery")
|
|
local flaskRateInc = modDB:Sum("INC", nil, "Flask"..type.."RecoveryRate")
|
|
local flaskTotal = base * (1 - instPerc / 100) * (1 + flaskRecInc / 100) * flaskRecMore * (1 + flaskDurInc / 100)
|
|
local flaskDur = dur * (1 + flaskDurInc / 100) / (1 + flaskTotalRateInc / 100) / (1 + flaskRateInc / 100)
|
|
|
|
-- More life recovery while on low life is not affected by flask effect (verified ingame).
|
|
-- Since this will be multiplied by the flask effect value below we have to counteract this by removing the flask effect from the value beforehand.
|
|
-- This is also the reason why this value needs a separate multiplier and cannot just be calculated into FlaskLifeRecovery.
|
|
local lowLifeFlaskRecMore = modDB:More(nil, "FlaskLifeRecoveryLowLife")
|
|
if lowLifeFlaskRecMore > 1 then
|
|
flaskTotal = flaskTotal * (lType == "life" and ((lowLifeFlaskRecMore - 1) / (1 + (effectInc) / 100)) + 1 or 1)
|
|
end
|
|
|
|
t_insert(out, modLib.createMod(type.."Recovery", "BASE", flaskTotal / flaskDur, name))
|
|
|
|
if (modDB:Flag(nil, type.."FlaskAppliesToEnergyShield")) then
|
|
t_insert(out, modLib.createMod("EnergyShieldRecovery", "BASE", flaskTotal / flaskDur, name))
|
|
end
|
|
|
|
if (modDB:Flag(nil, type.."FlaskAppliesToLife")) then
|
|
t_insert(out, modLib.createMod("LifeRecovery", "BASE", flaskTotal / flaskDur, name))
|
|
end
|
|
|
|
return out
|
|
end
|
|
|
|
local function mergeFlasks(flasks, onlyRecovery, checkNonRecoveryFlasksForMinions)
|
|
local flaskBuffs = { }
|
|
local flaskConditions = {}
|
|
local flaskBuffsPerBase = {}
|
|
local flaskBuffsNonPlayer = {}
|
|
local flaskBuffsPerBaseNonPlayer = {}
|
|
|
|
local function calcFlaskMods(item, baseName, buffModList, modList, onlyMinion)
|
|
local flaskEffectInc = effectInc + item.flaskData.effectInc
|
|
local flaskEffectIncNonPlayer = effectIncNonPlayer + item.flaskData.effectInc
|
|
if item.rarity == "MAGIC" and not (item.base.flask.life or item.base.flask.mana) then
|
|
flaskEffectInc = flaskEffectInc + effectIncMagic
|
|
flaskEffectIncNonPlayer = flaskEffectIncNonPlayer + effectIncMagicNonPlayer
|
|
end
|
|
local effectMod = 1 + (flaskEffectInc) / 100
|
|
local effectModNonPlayer = 1 + (flaskEffectIncNonPlayer) / 100
|
|
|
|
-- Avert thine eyes, lest they be forever scarred
|
|
-- I have no idea how to determine which buff is applied by a given flask,
|
|
-- so utility flasks are grouped by base, unique flasks are grouped by name, and magic flasks by their modifiers
|
|
if buffModList[1] then
|
|
if not onlyMinion then
|
|
local srcList = new("ModList")
|
|
srcList:ScaleAddList(buffModList, effectMod)
|
|
mergeBuff(srcList, flaskBuffs, baseName)
|
|
mergeBuff(srcList, flaskBuffsPerBase[item.baseName], baseName)
|
|
end
|
|
if (not onlyRecovery or checkNonRecoveryFlasksForMinions) and (flasksApplyToMinion or quickSilverAppliesToAllies or (nonUniqueFlasksApplyToMinion and item.rarity ~= "UNIQUE" and item.rarity ~= "RELIC")) then
|
|
srcList = new("ModList")
|
|
srcList:ScaleAddList(buffModList, effectModNonPlayer)
|
|
mergeBuff(srcList, flaskBuffsNonPlayer, baseName)
|
|
mergeBuff(srcList, flaskBuffsPerBaseNonPlayer[item.baseName], baseName)
|
|
end
|
|
end
|
|
|
|
if modList[1] then
|
|
local srcList = new("ModList")
|
|
srcList:ScaleAddList(modList, effectMod)
|
|
local key
|
|
if item.rarity == "UNIQUE" or item.rarity == "RELIC" then
|
|
key = item.title
|
|
else
|
|
key = ""
|
|
for _, mod in ipairs(modList) do
|
|
key = key .. modLib.formatModParams(mod) .. "&"
|
|
end
|
|
end
|
|
if not onlyRecovery then
|
|
mergeBuff(srcList, flaskBuffs, key)
|
|
mergeBuff(srcList, flaskBuffsPerBase[item.baseName], key)
|
|
end
|
|
if (not onlyRecovery or checkNonRecoveryFlasksForMinions) and (flasksApplyToMinion or quickSilverAppliesToAllies or (nonUniqueFlasksApplyToMinion and item.rarity ~= "UNIQUE" and item.rarity ~= "RELIC")) then
|
|
srcList = new("ModList")
|
|
srcList:ScaleAddList(modList, effectModNonPlayer)
|
|
mergeBuff(srcList, flaskBuffsNonPlayer, key)
|
|
mergeBuff(srcList, flaskBuffsPerBaseNonPlayer[item.baseName], key)
|
|
end
|
|
end
|
|
end
|
|
|
|
for item in pairs(flasks) do
|
|
flaskBuffsPerBase[item.baseName] = flaskBuffsPerBase[item.baseName] or {}
|
|
flaskBuffsPerBaseNonPlayer[item.baseName] = flaskBuffsPerBaseNonPlayer[item.baseName] or {}
|
|
flaskConditions["UsingFlask"] = true
|
|
flaskConditions["Using"..item.baseName:gsub("%s+", "")] = true
|
|
if item.base.flask.life and not modDB:Flag(nil, "CannotRecoverLifeOutsideLeech") then
|
|
flaskConditions["UsingLifeFlask"] = true
|
|
end
|
|
if item.base.flask.mana then
|
|
flaskConditions["UsingManaFlask"] = true
|
|
end
|
|
|
|
if onlyRecovery then
|
|
if item.base.flask.life and not modDB:Flag(nil, "CannotRecoverLifeOutsideLeech") then
|
|
calcFlaskMods(item, "LifeFlask", calcFlaskRecovery("Life", item), {})
|
|
end
|
|
if item.base.flask.mana then
|
|
calcFlaskMods(item, "ManaFlask", calcFlaskRecovery("Mana", item), {})
|
|
end
|
|
if checkNonRecoveryFlasksForMinions then
|
|
calcFlaskMods(item, item.baseName, item.buffModList, item.modList, true)
|
|
end
|
|
else
|
|
calcFlaskMods(item, item.baseName, item.buffModList, item.modList)
|
|
end
|
|
end
|
|
if not modDB:Flag(nil, "FlasksDoNotApplyToPlayer") then
|
|
for flaskCond, status in pairs(flaskConditions) do
|
|
modDB.conditions[flaskCond] = status
|
|
end
|
|
for _, buffModList in pairs(flaskBuffs) do
|
|
modDB:AddList(buffModList)
|
|
end
|
|
end
|
|
if env.minion then
|
|
if flasksApplyToMinion or nonUniqueFlasksApplyToMinion then -- nonUniqueAlreadyFiltered
|
|
local minionModDB = env.minion.modDB
|
|
for flaskCond, status in pairs(flaskConditions) do
|
|
minionModDB.conditions[flaskCond] = status
|
|
end
|
|
for _, buffModList in pairs(flaskBuffsNonPlayer) do
|
|
minionModDB:AddList(buffModList)
|
|
end
|
|
else -- Not all flasks apply to minions. Check if some flasks need to be selectively applied
|
|
if quickSilverAppliesToAllies and flaskBuffsPerBaseNonPlayer["Quicksilver Flask"] then
|
|
local minionModDB = env.minion.modDB
|
|
minionModDB.conditions["UsingQuicksilverFlask"] = flaskConditions["UsingQuicksilverFlask"]
|
|
minionModDB.conditions["UsingFlask"] = flaskConditions["UsingFlask"]
|
|
for _, buffModList in pairs(flaskBuffsPerBaseNonPlayer["Quicksilver Flask"]) do
|
|
minionModDB:AddList(buffModList)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
local effectInc = modDB:Sum("INC", {actor = "player"}, "TinctureEffect")
|
|
local effectIncMagic = modDB:Sum("INC", {actor = "player"}, "MagicTinctureEffect")
|
|
local tinctureLimit = modDB:Sum("BASE", nil, "TinctureLimit")
|
|
|
|
-- tincture breakdown
|
|
if breakdown then
|
|
output.TinctureEffect = effectInc
|
|
output.TinctureLimit = tinctureLimit
|
|
end
|
|
|
|
|
|
local function mergeTinctures(tinctures)
|
|
local tinctureBuffs = { }
|
|
local tinctureConditions = {}
|
|
local tinctureBuffsPerBase = {}
|
|
|
|
local function calcTinctureMods(item, baseName, buffModList, modList)
|
|
local tinctureEffectInc = effectInc + item.tinctureData.effectInc
|
|
if item.rarity == "MAGIC" then
|
|
tinctureEffectInc = tinctureEffectInc + effectIncMagic
|
|
end
|
|
local effectMod = (1 + (tinctureEffectInc) / 100) * (1 + (item.quality or 0) / 100)
|
|
|
|
-- same deal as flasks, go look at the comment there
|
|
if buffModList[1] then
|
|
local srcList = new("ModList")
|
|
srcList:ScaleAddList(buffModList, effectMod)
|
|
mergeBuff(srcList, tinctureBuffs, baseName)
|
|
mergeBuff(srcList, tinctureBuffsPerBase[item.baseName], baseName)
|
|
end
|
|
|
|
if modList[1] then
|
|
local srcList = new("ModList")
|
|
srcList:ScaleAddList(modList, effectMod)
|
|
local key
|
|
if item.rarity == "UNIQUE" or item.rarity == "RELIC" then
|
|
key = item.title
|
|
else
|
|
key = ""
|
|
for _, mod in ipairs(modList) do
|
|
key = key .. modLib.formatModParams(mod) .. "&"
|
|
end
|
|
end
|
|
mergeBuff(srcList, tinctureBuffs, key)
|
|
mergeBuff(srcList, tinctureBuffsPerBase[item.baseName], key)
|
|
end
|
|
end
|
|
for item in pairs(tinctures) do
|
|
if tinctureLimit <= 0 then
|
|
break
|
|
end
|
|
tinctureLimit = tinctureLimit - 1
|
|
tinctureBuffsPerBase[item.baseName] = tinctureBuffsPerBase[item.baseName] or {}
|
|
tinctureConditions["UsingTincture"] = true
|
|
tinctureConditions["Using"..item.baseName:gsub("%s+", "")] = true
|
|
calcTinctureMods(item, item.baseName, item.buffModList, item.modList)
|
|
end
|
|
for tinctureCond, status in pairs(tinctureConditions) do
|
|
modDB.conditions[tinctureCond] = status
|
|
end
|
|
for _, buffModList in pairs(tinctureBuffs) do
|
|
modDB:AddList(buffModList)
|
|
end
|
|
if modDB:Flag(nil, "TinctureRangedWeapons") then
|
|
for key, buffModList in pairs(tinctureBuffs) do
|
|
for _, buff in ipairs(buffModList) do
|
|
if band(buff.flags, ModFlag.WeaponMelee) == ModFlag.WeaponMelee then
|
|
newMod = copyTable(buff, true)
|
|
newMod.flags = bor(band(newMod.flags, bnot(ModFlag.WeaponMelee)), ModFlag.WeaponRanged)
|
|
modDB:AddList({newMod})
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
if env.mode_combat then
|
|
-- This needs to be done in 2 steps to account for effects affecting life recovery from flasks
|
|
-- For example Sorrow of the Divine and buffs (like flask recovery watchers eye)
|
|
mergeFlasks(env.flasks, false, true)
|
|
mergeTinctures(env.tinctures)
|
|
|
|
-- Merge keystones again to catch any that were added by flasks
|
|
mergeKeystones(env)
|
|
end
|
|
|
|
-- Calculate attributes and life/mana pools
|
|
doActorAttribsConditions(env, env.player)
|
|
doActorLifeMana(env.player)
|
|
if env.minion then
|
|
for _, value in ipairs(env.player.mainSkill.skillModList: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
|
|
for _, name in ipairs(env.minion.modDB:List(nil, "Keystone")) do
|
|
if env.spec.tree.keystoneMap[name] then
|
|
env.minion.modDB:AddList(env.spec.tree.keystoneMap[name].modList)
|
|
end
|
|
end
|
|
doActorAttribsConditions(env, env.minion)
|
|
end
|
|
|
|
-- Calculate skill life and mana reservations
|
|
env.player.reserved_LifeBase = 0
|
|
env.player.reserved_LifePercent = modDB:Sum("BASE", nil, "ExtraLifeReserved")
|
|
env.player.reserved_ManaBase = 0
|
|
env.player.reserved_ManaPercent = 0
|
|
env.player.uncancellable_LifeReservation = modDB:Sum("BASE", nil, "ExtraLifeReserved")
|
|
env.player.uncancellable_ManaReservation = modDB:Sum("BASE", nil, "ExtraManaReserved")
|
|
if breakdown then
|
|
breakdown.LifeReserved = { reservations = { } }
|
|
breakdown.ManaReserved = { reservations = { } }
|
|
end
|
|
for _, activeSkill in ipairs(env.player.activeSkillList) do
|
|
if activeSkill.skillTypes[SkillType.HasReservation] and not activeSkill.skillTypes[SkillType.ReservationBecomesCost] then
|
|
local skillModList = activeSkill.skillModList
|
|
local skillCfg = activeSkill.skillCfg
|
|
local mult = floor(skillModList:More(skillCfg, "SupportManaMultiplier"), 4)
|
|
local pool = { ["Mana"] = { }, ["Life"] = { } }
|
|
pool.Mana.baseFlat = activeSkill.skillData.manaReservationFlat or activeSkill.activeEffect.grantedEffectLevel.manaReservationFlat or 0
|
|
if skillModList:Flag(skillCfg, "ManaCostGainAsReservation") and activeSkill.activeEffect.grantedEffectLevel.cost then
|
|
pool.Mana.baseFlat = skillModList:Sum("BASE", skillCfg, "ManaCostBase") + (activeSkill.activeEffect.grantedEffectLevel.cost.Mana or 0)
|
|
end
|
|
pool.Mana.basePercent = activeSkill.skillData.manaReservationPercent or activeSkill.activeEffect.grantedEffectLevel.manaReservationPercent or 0
|
|
pool.Life.baseFlat = activeSkill.skillData.lifeReservationFlat or activeSkill.activeEffect.grantedEffectLevel.lifeReservationFlat or 0
|
|
if skillModList:Flag(skillCfg, "LifeCostGainAsReservation") and activeSkill.activeEffect.grantedEffectLevel.cost then
|
|
pool.Life.baseFlat = skillModList:Sum("BASE", skillCfg, "LifeCostBase") + (activeSkill.activeEffect.grantedEffectLevel.cost.Life or 0)
|
|
end
|
|
pool.Life.basePercent = activeSkill.skillData.lifeReservationPercent or activeSkill.activeEffect.grantedEffectLevel.lifeReservationPercent or 0
|
|
if skillModList:Flag(skillCfg, "BloodMagicReserved") then
|
|
pool.Life.baseFlat = pool.Life.baseFlat + pool.Mana.baseFlat
|
|
pool.Mana.baseFlat = 0
|
|
activeSkill.skillData["LifeReservationFlatForced"] = activeSkill.skillData["ManaReservationFlatForced"]
|
|
activeSkill.skillData["ManaReservationFlatForced"] = nil
|
|
pool.Life.basePercent = pool.Life.basePercent + pool.Mana.basePercent
|
|
pool.Mana.basePercent = 0
|
|
activeSkill.skillData["LifeReservationPercentForced"] = activeSkill.skillData["ManaReservationPercentForced"]
|
|
activeSkill.skillData["ManaReservationPercentForced"] = nil
|
|
end
|
|
for name, values in pairs(pool) do
|
|
values.more = skillModList:More(skillCfg, name.."Reserved", "Reserved")
|
|
values.inc = skillModList:Sum("INC", skillCfg, name.."Reserved", "Reserved")
|
|
values.efficiency = m_max(skillModList:Sum("INC", skillCfg, name.."ReservationEfficiency", "ReservationEfficiency"), -100)
|
|
-- used for Arcane Cloak calculations in ModStore.GetStat
|
|
env.player[name.."Efficiency"] = values.efficiency
|
|
if activeSkill.skillData[name.."ReservationFlatForced"] then
|
|
values.reservedFlat = activeSkill.skillData[name.."ReservationFlatForced"]
|
|
else
|
|
local baseFlatVal = m_floor(values.baseFlat * mult)
|
|
values.reservedFlat = 0
|
|
if values.more > 0 and values.inc > -100 and baseFlatVal ~= 0 then
|
|
values.reservedFlat = m_max(round(baseFlatVal * (100 + values.inc) / 100 * values.more / (1 + values.efficiency / 100), 0), 0)
|
|
end
|
|
end
|
|
if activeSkill.skillData[name.."ReservationPercentForced"] then
|
|
values.reservedPercent = activeSkill.skillData[name.."ReservationPercentForced"]
|
|
else
|
|
local basePercentVal = values.basePercent * mult
|
|
values.reservedPercent = 0
|
|
if values.more > 0 and values.inc > -100 and basePercentVal ~= 0 then
|
|
values.reservedPercent = m_max(round(basePercentVal * (100 + values.inc) / 100 * values.more / (1 + values.efficiency / 100), 2), 0)
|
|
end
|
|
end
|
|
if activeSkill.activeMineCount then
|
|
values.reservedFlat = values.reservedFlat * activeSkill.activeMineCount
|
|
values.reservedPercent = values.reservedPercent * activeSkill.activeMineCount
|
|
end
|
|
-- Blood Sacrament increases reservation per stage channelled
|
|
if activeSkill.skillCfg.skillName == "Blood Sacrament" and activeSkill.activeStageCount then
|
|
values.reservedFlat = values.reservedFlat * (activeSkill.activeStageCount + 1)
|
|
values.reservedPercent = values.reservedPercent * (activeSkill.activeStageCount + 1)
|
|
end
|
|
if values.reservedFlat ~= 0 then
|
|
activeSkill.skillData[name.."ReservedBase"] = values.reservedFlat
|
|
env.player["reserved_"..name.."Base"] = env.player["reserved_"..name.."Base"] + values.reservedFlat
|
|
if breakdown then
|
|
t_insert(breakdown[name.."Reserved"].reservations, {
|
|
skillName = activeSkill.activeEffect.grantedEffect.name,
|
|
base = values.baseFlat,
|
|
mult = mult ~= 1 and ("x "..mult),
|
|
more = values.more ~= 1 and ("x "..values.more),
|
|
inc = values.inc ~= 0 and ("x "..(1 + values.inc / 100)),
|
|
efficiency = values.efficiency ~= 0 and ("x " .. round(100 / (100 + values.efficiency), 4)),
|
|
total = values.reservedFlat,
|
|
})
|
|
end
|
|
end
|
|
if values.reservedPercent ~= 0 then
|
|
activeSkill.skillData[name.."ReservedPercent"] = values.reservedPercent
|
|
activeSkill.skillData[name.."ReservedBase"] = (values.reservedFlat or 0) + m_ceil(output[name] * values.reservedPercent / 100)
|
|
env.player["reserved_"..name.."Percent"] = env.player["reserved_"..name.."Percent"] + values.reservedPercent
|
|
if breakdown then
|
|
t_insert(breakdown[name.."Reserved"].reservations, {
|
|
skillName = activeSkill.activeEffect.grantedEffect.name,
|
|
base = values.basePercent .. "%",
|
|
mult = mult ~= 1 and ("x "..mult),
|
|
more = values.more ~= 1 and ("x "..values.more),
|
|
inc = values.inc ~= 0 and ("x "..(1 + values.inc / 100)),
|
|
efficiency = values.efficiency ~= 0 and ("x " .. round(100 / (100 + values.efficiency), 4)),
|
|
total = values.reservedPercent .. "%",
|
|
})
|
|
end
|
|
end
|
|
if skillModList:Flag(skillCfg, "HasUncancellableReservation") then
|
|
env.player["uncancellable_"..name.."Reservation"] = env.player["uncancellable_"..name.."Reservation"] + values.reservedPercent
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Set the life/mana reservations
|
|
doActorLifeManaReservation(env.player)
|
|
|
|
-- Process attribute requirements
|
|
do
|
|
local reqMult = calcLib.mod(modDB, nil, "GlobalAttributeRequirements")
|
|
local omniRequirements = modDB:Flag(nil, "OmniscienceRequirements") and calcLib.mod(modDB, nil, "OmniAttributeRequirements")
|
|
local ignoreAttrReq = modDB:Flag(nil, "IgnoreAttributeRequirements")
|
|
local attrTable = omniRequirements and {"Omni","Str","Dex","Int"} or {"Str","Dex","Int"}
|
|
for _, attr in ipairs(attrTable) do
|
|
local breakdownAttr = omniRequirements and "Omni" or attr
|
|
if breakdown then
|
|
breakdown["Req"..attr] = {
|
|
rowList = { },
|
|
colList = {
|
|
{ label = attr, key = "req" },
|
|
{ label = "Source", key = "source" },
|
|
{ label = "Source Name", key = "sourceName" },
|
|
}
|
|
}
|
|
end
|
|
local out = {val = 0, source = nil}
|
|
for _, reqSource in ipairs(env.requirementsTable) do
|
|
if reqSource[attr] and reqSource[attr] > 0 then
|
|
local req = m_floor(reqSource[attr] * reqMult)
|
|
if omniRequirements then
|
|
local omniReqMult = 1 / (omniRequirements - 1)
|
|
local attributereq = m_floor(reqSource[attr] * reqMult)
|
|
req = m_floor(attributereq * omniReqMult)
|
|
end
|
|
if req > out.val then
|
|
out.val = req
|
|
out.source = reqSource
|
|
end
|
|
if breakdown then
|
|
local row = {
|
|
req = req > output[breakdownAttr] and colorCodes.NEGATIVE..req or req,
|
|
reqNum = req,
|
|
source = reqSource.source,
|
|
}
|
|
if reqSource.source == "Item" then
|
|
local item = reqSource.sourceItem
|
|
row.sourceName = colorCodes[item.rarity]..item.name
|
|
row.sourceNameTooltip = function(tooltip)
|
|
env.build.itemsTab:AddItemTooltip(tooltip, item, reqSource.sourceSlot)
|
|
end
|
|
elseif reqSource.source == "Gem" then
|
|
row.sourceName = s_format("%s%s ^7%d/%d", reqSource.sourceGem.color, reqSource.sourceGem.nameSpec, reqSource.sourceGem.level, reqSource.sourceGem.quality)
|
|
end
|
|
t_insert(breakdown["Req"..breakdownAttr].rowList, row)
|
|
end
|
|
end
|
|
end
|
|
if ignoreAttrReq then
|
|
out.val = 0
|
|
end
|
|
output["Req"..attr.."String"] = 0
|
|
if out.val > (output["Req"..breakdownAttr] or 0) then
|
|
output["Req"..breakdownAttr.."String"] = out.val
|
|
output["Req"..breakdownAttr] = out.val
|
|
output["Req"..breakdownAttr.."Item"] = out.source
|
|
if breakdown then
|
|
output["Req"..breakdownAttr.."String"] = out.val > (output[breakdownAttr] or 0) and colorCodes.NEGATIVE..(out.val) or out.val
|
|
end
|
|
end
|
|
end
|
|
if breakdown and breakdown["ReqOmni"] then
|
|
table.sort(breakdown["ReqOmni"].rowList, function(a, b)
|
|
if a.reqNum ~= b.reqNum then
|
|
return a.reqNum > b.reqNum
|
|
elseif a.source ~= b.source then
|
|
return a.source < b.source
|
|
else
|
|
return a.sourceName < b.sourceName
|
|
end
|
|
end)
|
|
end
|
|
end
|
|
|
|
-- Calculate number of active heralds and auras affecting self
|
|
if env.mode_buffs then
|
|
local heraldList = { }
|
|
local auraList = { }
|
|
for _, activeSkill in ipairs(env.player.activeSkillList) do
|
|
if activeSkill.skillTypes[SkillType.Herald] and not heraldList[activeSkill.skillCfg.skillName] then
|
|
heraldList[activeSkill.skillCfg.skillName] = true
|
|
modDB.multipliers["Herald"] = (modDB.multipliers["Herald"] or 0) + 1
|
|
modDB.conditions["AffectedByHerald"] = true
|
|
elseif activeSkill.skillTypes[SkillType.Aura] and not activeSkill.skillTypes[SkillType.AuraAffectsEnemies] and not activeSkill.skillData.auraCannotAffectSelf and not auraList[activeSkill.skillCfg.skillName] then
|
|
auraList[activeSkill.skillCfg.skillName] = true
|
|
modDB.multipliers["AuraAffectingSelf"] = (modDB.multipliers["AuraAffectingSelf"] or 0) + 1
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Deal with Consecrated Ground
|
|
if modDB:Flag(nil, "Condition:OnConsecratedGround") then
|
|
local effect = 1 + modDB:Sum("INC", nil, "ConsecratedGroundEffect") / 100
|
|
modDB:NewMod("LifeRegenPercent", "BASE", 5 * effect, "Consecrated Ground")
|
|
modDB:NewMod("CurseEffectOnSelf", "INC", -50 * effect, "Consecrated Ground")
|
|
end
|
|
|
|
if modDB:Flag(nil, "ManaAppliesToShockEffect") then
|
|
-- Maximum Mana conversion from Lightning Mastery
|
|
local multiplier = (modDB:Max(nil, "ImprovedManaAppliesToShockEffect") or 100) / 100
|
|
for _, value in ipairs(modDB:Tabulate("INC", nil, "Mana")) do
|
|
local mod = value.mod
|
|
local modifiers = calcLib.getConvertedModTags(mod, multiplier)
|
|
modDB:NewMod("EnemyShockEffect", "INC", m_floor(mod.value * multiplier), mod.source, mod.flags, mod.keywordFlags, unpack(modifiers))
|
|
end
|
|
end
|
|
|
|
-- Combine buffs/debuffs
|
|
local buffs = { }
|
|
env.buffs = buffs
|
|
local guards = { }
|
|
local minionBuffs = { }
|
|
env.minionBuffs = minionBuffs
|
|
local debuffs = { }
|
|
env.debuffs = debuffs
|
|
local curses = { }
|
|
local minionCurses = {
|
|
limit = 1,
|
|
}
|
|
local linkSkills = { }
|
|
local allyBuffs = env.partyMembers["Aura"]
|
|
local buffExports = { Aura = {}, Curse = {}, Warcry = {}, Link = {}, EnemyMods = {}, EnemyConditions = {}, PlayerMods = {} }
|
|
for spectreId = 1, #env.spec.build.spectreList do
|
|
local spectreData = data.minions[env.spec.build.spectreList[spectreId]]
|
|
for modId = 1, #spectreData.modList do
|
|
local modData = spectreData.modList[modId]
|
|
if modData.name == "EnemyCurseLimit" then
|
|
minionCurses.limit = modData.value + 1
|
|
break
|
|
elseif modData.name == "AllyModifier" and modData.type == "LIST" then
|
|
buffs["Spectre"] = buffs["Spectre"] or new("ModList")
|
|
minionBuffs["Spectre"] = minionBuffs["Spectre"] or new("ModList")
|
|
for _, modValue in pairs(modData.value) do
|
|
local copyModValue = copyTable(modValue)
|
|
copyModValue.source = "Spectre:"..spectreData.name
|
|
t_insert(minionBuffs["Spectre"], copyModValue)
|
|
t_insert(buffs["Spectre"], copyModValue)
|
|
end
|
|
elseif modData.name == "MinionModifier" and modData.type == "LIST" then
|
|
minionBuffs["Spectre"] = minionBuffs["Spectre"] or new("ModList")
|
|
for _, modValue in pairs(modData.value) do
|
|
local copyModValue = copyTable(modValue)
|
|
copyModValue.source = "Spectre:"..spectreData.name
|
|
t_insert(minionBuffs["Spectre"], copyModValue)
|
|
end
|
|
elseif modData.name == "PlayerModifier" and modData.type == "LIST" then
|
|
buffs["Spectre"] = buffs["Spectre"] or new("ModList")
|
|
for _, modValue in pairs(modData.value) do
|
|
local copyModValue = copyTable(modValue)
|
|
copyModValue.source = "Spectre:"..spectreData.name
|
|
t_insert(buffs["Spectre"], copyModValue)
|
|
end
|
|
end
|
|
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 (env.limitedSkills and env.limitedSkills[cacheSkillUUID(activeSkill, env)]) 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_max(m_min((m_floor((activation_frequency or 0) * duration) - 1), 19), 0)
|
|
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
|
|
local skillCfg = activeSkill.skillCfg
|
|
for _, buff in ipairs(activeSkill.buffList) do
|
|
if buff.cond and not skillModList:GetCondition(buff.cond, skillCfg) then
|
|
-- Nothing!
|
|
elseif buff.enemyCond and not enemyDB:GetCondition(buff.enemyCond) then
|
|
-- Also nothing :/
|
|
elseif buff.type == "Buff" 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
|
|
end
|
|
if env.minion and (buff.applyMinions or buff.applyAllies or skillModList:Flag(nil, "BuffAppliesToAllies")) 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)
|
|
end
|
|
if partyTabEnableExportBuffs and (buff.applyAllies or skillModList:Flag(nil, "BuffAppliesToAllies") or skillModList:Flag(nil, "BuffAppliesToPartyMembers")) then
|
|
local inc = modStore:Sum("INC", skillCfg, "BuffEffect") + skillModList:Sum("INC", skillCfg, buff.name:gsub(" ", "").."Effect")
|
|
local more = modStore:More(skillCfg, "BuffEffect")
|
|
buffExports["Aura"]["otherEffects"] = buffExports["Aura"]["otherEffects"] or { }
|
|
buffExports["Aura"]["otherEffects"][buff.name] = { effectMult = (1 + inc / 100) * more, modList = buff.modList }
|
|
end
|
|
end
|
|
elseif buff.type == "Guard" 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
|
|
local srcList = new("ModList")
|
|
local inc = modStore:Sum("INC", skillCfg, "BuffEffect", "BuffEffectOnSelf", "BuffEffectOnPlayer")
|
|
local more = modStore:More(skillCfg, "BuffEffect", "BuffEffectOnSelf")
|
|
srcList:ScaleAddList(buff.modList, (1 + inc / 100) * more)
|
|
mergeBuff(srcList, guards, buff.name)
|
|
end
|
|
end
|
|
elseif buff.type == "Warcry" then
|
|
if env.mode_buffs then
|
|
local skillCfg = skillCfg
|
|
local modStore = skillModList or modDB
|
|
local warcryName = buff.name:gsub(" Cry", ""):gsub("'s",""):gsub(" ","")
|
|
local baseExerts = modStore:Sum("BASE", env.player.mainSkill.skillCfg, warcryName.."ExertedAttacks")
|
|
if baseExerts > 0 then
|
|
local extraExertions = modStore:Sum("BASE", nil, "ExtraExertedAttacks") or 0
|
|
local exertMultiplier = modStore:More(nil, "ExtraExertedAttacks")
|
|
env.player.modDB:NewMod("Num"..warcryName.."Exerts", "BASE", m_floor((baseExerts + extraExertions) * exertMultiplier))
|
|
env.player.modDB:NewMod("Multiplier:ExertingWarcryCount", "BASE", 1)
|
|
end
|
|
if not activeSkill.skillModList:Flag(nil, "CannotShareWarcryBuffs") then
|
|
local warcryPower = modDB:Override(nil, "WarcryPower") or m_max((modDB:Sum("BASE", nil, "WarcryPower") or 0) * (1 + (modDB:Sum("INC", nil, "WarcryPower") or 0)/100), (modDB:Sum("BASE", nil, "MinimumWarcryPower") or 0))
|
|
for _, warcryBuff in ipairs(buff.modList) do
|
|
if warcryBuff[1] and warcryBuff[1].effectType == "Warcry" and warcryBuff[1].div then
|
|
warcryBuff[1].warcryPowerBonus = m_floor((warcryBuff[1].limit and m_min(warcryPower, warcryBuff[1].limit) or warcryPower) / warcryBuff[1].div)
|
|
end
|
|
end
|
|
local full_duration = calcSkillDuration(modStore, skillCfg, activeSkill.skillData, env, enemyDB)
|
|
local cooldownOverride = modStore:Override(skillCfg, "CooldownRecovery")
|
|
local actual_cooldown = cooldownOverride or (activeSkill.skillData.cooldown + modStore:Sum("BASE", skillCfg, "CooldownRecovery")) / calcLib.mod(modStore, skillCfg, "CooldownRecovery")
|
|
local uptime = modDB:Flag(nil, "Condition:WarcryMaxHit") and 1 or m_min(full_duration / actual_cooldown, 1)
|
|
local extraWarcryModList = activeSkill.activeEffect.grantedEffect.name == "Rallying Cry" and new("ModList") or {}
|
|
if not modDB:Flag(nil, "CannotGainWarcryBuffs") then
|
|
if not buff.applyNotPlayer then
|
|
activeSkill.buffSkill = true
|
|
modDB.conditions["AffectedBy"..warcryName] = true
|
|
local srcList = new("ModList")
|
|
local inc = modStore:Sum("INC", skillCfg, "BuffEffect", "BuffEffectOnSelf", "BuffEffectOnPlayer")
|
|
local more = modStore:More(skillCfg, "BuffEffect", "BuffEffectOnSelf")
|
|
for _, warcryBuff in ipairs(buff.modList) do
|
|
local mult = (1 + inc / 100) * more * (warcryBuff[1].warcryPowerBonus or 1) * uptime
|
|
srcList:ScaleAddList({warcryBuff}, mult)
|
|
end
|
|
mergeBuff(srcList, buffs, buff.name)
|
|
end
|
|
end
|
|
if env.minion then
|
|
activeSkill.minionBuffSkill = true
|
|
env.minion.modDB.conditions["AffectedBy"..warcryName] = true
|
|
local srcList = new("ModList")
|
|
local inc = skillModList:Sum("INC", skillCfg, "BuffEffect") + env.minion.modDB:Sum("INC", skillCfg, "BuffEffectOnSelf")
|
|
local more = skillModList:More(skillCfg, "BuffEffect") * env.minion.modDB:More(skillCfg, "BuffEffectOnSelf")
|
|
for _, warcryBuff in ipairs(buff.modList) do
|
|
local mult = (1 + inc / 100) * more * (warcryBuff[1].warcryPowerBonus or 1) * uptime
|
|
srcList:ScaleAddList({warcryBuff}, mult)
|
|
end
|
|
-- Special handling for the minion side to add the flat damage bonus
|
|
if activeSkill.activeEffect.grantedEffect.name == "Rallying Cry" then
|
|
local warcryPowerBonus = m_floor((m_min(warcryPower, 30)) / 5)
|
|
local rallyingWeaponEffect = m_floor(activeSkill.skillModList:Sum("BASE", env.player.mainSkill.skillCfg, "RallyingCryAllyDamageBonusPer5Power") * warcryPowerBonus)
|
|
local inc = modStore:Sum("INC", skillCfg, "BuffEffect") + env.minion.modDB:Sum("INC", skillCfg, "BuffEffectOnSelf")
|
|
local rallyingBonusMoreMultiplier = 1 + (activeSkill.skillModList:Sum("BASE", env.player.mainSkill.skillCfg, "RallyingCryMinionDamageBonusMultiplier") or 0)
|
|
-- Add all damage types
|
|
local dmgTypeList = {"Physical", "Lightning", "Cold", "Fire", "Chaos"}
|
|
for _, damageType in ipairs(dmgTypeList) do
|
|
if env.player.weaponData1[damageType.."Min"] then
|
|
extraWarcryModList:NewMod(damageType.."Min", "BASE", (env.player.weaponData1[damageType.."Min"] * rallyingWeaponEffect / 100), "Rallying Cry", 0, KeywordFlag.Attack, { type = "GlobalEffect", effectType = "Warcry", div = 5, limit = 30 })
|
|
end
|
|
if env.player.weaponData1[damageType.."Max"] then
|
|
extraWarcryModList:NewMod(damageType.."Max", "BASE", (env.player.weaponData1[damageType.."Max"] * rallyingWeaponEffect / 100), "Rallying Cry", 0, KeywordFlag.Attack, { type = "GlobalEffect", effectType = "Warcry", div = 5, limit = 30 })
|
|
end
|
|
end
|
|
srcList:ScaleAddList(extraWarcryModList, (1 + inc / 100) * rallyingBonusMoreMultiplier * uptime)
|
|
end
|
|
mergeBuff(srcList, minionBuffs, buff.name)
|
|
end
|
|
if partyTabEnableExportBuffs then
|
|
local newModList = new("ModList")
|
|
local inc = skillModList:Sum("INC", skillCfg, "BuffEffect")
|
|
local more = skillModList:More(skillCfg, "BuffEffect")
|
|
newModList:AddList(buff.modList)
|
|
newModList:AddList(extraWarcryModList)
|
|
buffExports["Warcry"][buff.name] = { effectMult = (1 + inc / 100) * more * uptime, modList = newModList }
|
|
end
|
|
end
|
|
end
|
|
elseif buff.type == "Aura" then
|
|
if env.mode_buffs then
|
|
-- Check for extra modifiers to apply to aura skills
|
|
local extraAuraModList = { }
|
|
for _, value in ipairs(modDB:List(skillCfg, "ExtraAuraEffect")) do
|
|
local add = true
|
|
for _, mod in ipairs(extraAuraModList) do
|
|
if modLib.compareModParams(mod, value.mod) then
|
|
mod.value = mod.value + value.mod.value
|
|
add = false
|
|
break
|
|
end
|
|
end
|
|
if add then
|
|
t_insert(extraAuraModList, copyTable(value.mod, true))
|
|
end
|
|
end
|
|
if not activeSkill.skillData.auraCannotAffectSelf or activeSkill.skillModList:Flag(skillCfg, "SelfAurasAffectYouAndLinkedTarget") then
|
|
local inc = skillModList:Sum("INC", skillCfg, "AuraEffect", "BuffEffect", "BuffEffectOnSelf", "AuraEffectOnSelf", "AuraBuffEffect", "SkillAuraEffectOnSelf")
|
|
local more = skillModList:More(skillCfg, "AuraEffect", "BuffEffect", "BuffEffectOnSelf", "AuraEffectOnSelf", "AuraBuffEffect", "SkillAuraEffectOnSelf")
|
|
local mult = (1 + inc / 100) * more
|
|
if modDB:Flag(nil, "AlliesAurasCannotAffectSelf") or not allyBuffs["Aura"] or not allyBuffs["Aura"][buff.name] or allyBuffs["Aura"][buff.name].effectMult / 100 <= mult then
|
|
activeSkill.buffSkill = true
|
|
modDB.conditions["AffectedByAura"] = true
|
|
if buff.name:sub(1,4) == "Vaal" then
|
|
modDB.conditions["AffectedBy"..buff.name:sub(6):gsub(" ","")] = true
|
|
end
|
|
modDB.conditions["AffectedBy"..buff.name:gsub(" ","")] = true
|
|
local srcList = new("ModList")
|
|
srcList:ScaleAddList(buff.modList, mult)
|
|
srcList:ScaleAddList(extraAuraModList, mult)
|
|
mergeBuff(srcList, buffs, buff.name)
|
|
end
|
|
end
|
|
if not (modDB:Flag(nil, "SelfAurasCannotAffectAllies") or modDB:Flag(nil, "SelfAurasOnlyAffectYou") or modDB:Flag(nil, "SelfAuraSkillsCannotAffectAllies") ) then
|
|
if env.minion then
|
|
local inc = skillModList:Sum("INC", skillCfg, "AuraEffect", "BuffEffect") + env.minion.modDB:Sum("INC", skillCfg, "BuffEffectOnSelf", "AuraEffectOnSelf")
|
|
local more = skillModList:More(skillCfg, "AuraEffect", "BuffEffect") * env.minion.modDB:More(skillCfg, "BuffEffectOnSelf", "AuraEffectOnSelf")
|
|
local mult = (1 + inc / 100) * more
|
|
if not allyBuffs["Aura"] or not allyBuffs["Aura"][buff.name] or allyBuffs["Aura"][buff.name].effectMult / 100 <= mult then
|
|
activeSkill.minionBuffSkill = true
|
|
env.minion.modDB.conditions["AffectedBy"..buff.name:gsub(" ","")] = true
|
|
env.minion.modDB.conditions["AffectedByAura"] = true
|
|
local srcList = new("ModList")
|
|
srcList:ScaleAddList(buff.modList, mult)
|
|
srcList:ScaleAddList(extraAuraModList, mult)
|
|
mergeBuff(srcList, minionBuffs, buff.name)
|
|
end
|
|
end
|
|
local inc = skillModList:Sum("INC", skillCfg, "AuraEffect", "BuffEffect")
|
|
local more = skillModList:More(skillCfg, "AuraEffect", "BuffEffect")
|
|
local mult = (1 + inc / 100) * more
|
|
local newModList = new("ModList")
|
|
newModList:AddList(buff.modList)
|
|
newModList:AddList(extraAuraModList)
|
|
if buffExports["Aura"][buff.name] then
|
|
buffExports["Aura"][buff.name.."_Debuff"] = buffExports["Aura"][buff.name]
|
|
end
|
|
buffExports["Aura"][buff.name] = { effectMult = mult, modList = newModList }
|
|
if modDB:Flag(nil, "AurasAffectEnemies") and not activeSkill.skillModList:Flag(skillCfg, "SelfAurasAffectYouAndLinkedTarget") then
|
|
local newModList = {}
|
|
local srcList = new("ModList")
|
|
for _, mod in ipairs(buff.modList) do
|
|
t_insert(newModList, mod)
|
|
end
|
|
for _, mod in ipairs(extraAuraModList) do
|
|
t_insert(newModList, mod)
|
|
end
|
|
buffExports["Aura"][buff.name..(buffExports["Aura"][buff.name] and "_Debuff" or "")] = { effectMult = mult, modList = newModList }
|
|
srcList:ScaleAddList(buff.modList, mult)
|
|
srcList:ScaleAddList(extraAuraModList, mult)
|
|
mergeBuff(srcList, debuffs, buff.name)
|
|
end
|
|
end
|
|
if env.player.mainSkill.skillFlags.totem and not (modDB:Flag(nil, "SelfAurasCannotAffectAllies") or modDB:Flag(nil, "SelfAuraSkillsCannotAffectAllies")) then
|
|
activeSkill.totemBuffSkill = true
|
|
env.player.mainSkill.skillModList.conditions["AffectedBy"..buff.name:gsub(" ","")] = true
|
|
env.player.mainSkill.skillModList.conditions["AffectedByAura"] = true
|
|
|
|
local srcList = new("ModList")
|
|
local inc = skillModList:Sum("INC", skillCfg, "AuraEffect", "BuffEffect", "AuraBuffEffect")
|
|
local more = skillModList:More(skillCfg, "AuraEffect", "BuffEffect", "AuraBuffEffect")
|
|
local lists = {extraAuraModList, buff.modList}
|
|
local scale = (1 + inc / 100) * more
|
|
scale = m_max(scale, 0)
|
|
|
|
for _, modList in ipairs(lists) do
|
|
for _, mod in ipairs(modList) do
|
|
if mod.name == "EnergyShield" or mod.name == "Armour" or mod.name == "Evasion" or mod.name:match("Resist?M?a?x?$") then
|
|
local totemMod = copyTable(mod)
|
|
totemMod.name = "Totem"..totemMod.name
|
|
if scale ~= 1 then
|
|
if type(totemMod.value) == "number" then
|
|
totemMod.value = (m_floor(totemMod.value) == totemMod.value) and m_modf(round(totemMod.value * scale, 2)) or totemMod.value * scale
|
|
elseif type(totemMod.value) == "table" and totemMod.value.mod then
|
|
totemMod.value.mod.value = (m_floor(totemMod.value.mod.value) == totemMod.value.mod.value) and m_modf(round(totemMod.value.mod.value * scale, 2)) or totemMod.value.mod.value * scale
|
|
end
|
|
end
|
|
srcList:AddMod(totemMod)
|
|
end
|
|
end
|
|
end
|
|
mergeBuff(srcList, buffs, "Totem "..buff.name)
|
|
end
|
|
-- check if aura has a debuff added to it, but does not have an auraDebuff
|
|
if env.mode_effective and #modDB:List(skillCfg, "ExtraAuraDebuffEffect") > 0 and not (modDB:Flag(nil, "SelfAurasOnlyAffectYou") or activeSkill.skillModList:Flag(skillCfg, "SelfAurasAffectYouAndLinkedTarget")) then
|
|
local auraDebuffFound = false
|
|
for _, buff in ipairs(activeSkill.buffList) do
|
|
if buff.type == "AuraDebuff" then
|
|
auraDebuffFound = true
|
|
break
|
|
end
|
|
end
|
|
if not auraDebuffFound then
|
|
activeSkill.debuffSkill = true
|
|
local extraAuraModList = { }
|
|
for _, value in ipairs(modDB:List(skillCfg, "ExtraAuraDebuffEffect")) do
|
|
local add = true
|
|
for _, mod in ipairs(extraAuraModList) do
|
|
if modLib.compareModParams(mod, value.mod) then
|
|
mod.value = mod.value + value.mod.value
|
|
add = false
|
|
break
|
|
end
|
|
end
|
|
if add then
|
|
t_insert(extraAuraModList, copyTable(value.mod, true))
|
|
end
|
|
end
|
|
local inc = skillModList:Sum("INC", skillCfg, "AuraEffect", "BuffEffect", "DebuffEffect")
|
|
local more = skillModList:More(skillCfg, "AuraEffect", "BuffEffect", "DebuffEffect")
|
|
mult = (1 + inc / 100) * more
|
|
local newModList = new("ModList")
|
|
newModList:AddList(extraAuraModList)
|
|
buffExports["Aura"][buff.name..(buffExports["Aura"][buff.name] and "_Debuff" or "")] = { effectMult = mult, modList = newModList }
|
|
if allyBuffs["AuraDebuff"] and allyBuffs["AuraDebuff"][buff.name] and allyBuffs["AuraDebuff"][buff.name].effectMult / 100 > mult then
|
|
mult = 0
|
|
end
|
|
mergeBuff(newModList, debuffs, buff.name)
|
|
end
|
|
end
|
|
end
|
|
elseif buff.type == "Debuff" or buff.type == "AuraDebuff" 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
|
|
enemyDB.conditions["AffectedBy"..buff.name:gsub(" ","")] = true
|
|
modDB.conditions["AffectedBy"..buff.name:gsub(" ","")] = true
|
|
local srcList = new("ModList")
|
|
local mult = 1
|
|
local extraAuraModList = { }
|
|
if buff.type == "AuraDebuff" then
|
|
for _, value in ipairs(modDB:List(skillCfg, "ExtraAuraDebuffEffect")) do
|
|
local add = true
|
|
for _, mod in ipairs(extraAuraModList) do
|
|
if modLib.compareModParams(mod, value.mod) then
|
|
mod.value = mod.value + value.mod.value
|
|
add = false
|
|
break
|
|
end
|
|
end
|
|
if add then
|
|
t_insert(extraAuraModList, copyTable(value.mod, true))
|
|
end
|
|
end
|
|
mult = 0
|
|
if not (modDB:Flag(nil, "SelfAurasOnlyAffectYou") or activeSkill.skillModList:Flag(skillCfg, "SelfAurasAffectYouAndLinkedTarget")) then
|
|
local inc = skillModList:Sum("INC", skillCfg, "AuraEffect", "BuffEffect", "DebuffEffect")
|
|
local more = skillModList:More(skillCfg, "AuraEffect", "BuffEffect", "DebuffEffect")
|
|
mult = (1 + inc / 100) * more
|
|
local newModList = {}
|
|
for _, mod in ipairs(buff.modList) do
|
|
t_insert(newModList, mod)
|
|
end
|
|
for _, mod in ipairs(extraAuraModList) do
|
|
t_insert(newModList, mod)
|
|
end
|
|
-- A full modlist causes issues with copy table for mine auras
|
|
--local newModList = new("ModList")
|
|
--newModList:AddList(buff.modList)
|
|
--newModList:AddList(extraAuraModList)
|
|
buffExports["Aura"][buff.name..(buffExports["Aura"][buff.name] and "_Debuff" or "")] = { effectMult = mult, modList = newModList }
|
|
if allyBuffs["AuraDebuff"] and allyBuffs["AuraDebuff"][buff.name] and allyBuffs["AuraDebuff"][buff.name].effectMult / 100 > mult then
|
|
mult = 0
|
|
end
|
|
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)
|
|
srcList:ScaleAddList(extraAuraModList, 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)
|
|
end
|
|
elseif buff.type == "Curse" or buff.type == "CurseBuff" then
|
|
local mark = activeSkill.skillTypes[SkillType.Mark]
|
|
modDB.conditions["SelfCast"..buff.name:gsub(" ","")] = not (activeSkill.skillTypes[SkillType.Triggered] or activeSkill.skillTypes[SkillType.Aura])
|
|
if env.mode_effective and (not enemyDB:Flag(nil, "Hexproof") or modDB:Flag(nil, "CursesIgnoreHexproof") or activeSkill.skillData.ignoreHexLimit or activeSkill.skillData.ignoreHexproof) or mark then
|
|
local curse = {
|
|
name = buff.name,
|
|
fromPlayer = true,
|
|
priority = determineCursePriority(buff.name, activeSkill),
|
|
isMark = mark,
|
|
ignoreHexLimit = (modDB:Flag(activeSkill.skillCfg, "CursesIgnoreHexLimit") or activeSkill.skillData.ignoreHexLimit) and not mark or false,
|
|
socketedCursesHexLimit = modDB:Flag(activeSkill.skillCfg, "SocketedCursesAdditionalLimit")
|
|
}
|
|
local inc = skillModList:Sum("INC", skillCfg, "CurseEffect") + enemyDB:Sum("INC", nil, "CurseEffectOnSelf")
|
|
if activeSkill.skillTypes[SkillType.Aura] then
|
|
inc = inc + skillModList:Sum("INC", skillCfg, "AuraEffect")
|
|
end
|
|
local more = skillModList:More(skillCfg, "CurseEffect")
|
|
local moreMark = more
|
|
-- This is non-ideal, but the only More for enemy is the boss effect
|
|
if not curse.isMark then
|
|
more = more * enemyDB:More(nil, "CurseEffectOnSelf")
|
|
end
|
|
local mult = 0
|
|
if not ((modDB:Flag(nil, "SelfAurasOnlyAffectYou") or activeSkill.skillModList:Flag(skillCfg, "SelfAurasAffectYouAndLinkedTarget")) and activeSkill.skillTypes[SkillType.Aura]) then --If your aura only effect you blasphemy does nothing
|
|
mult = (1 + inc / 100) * more
|
|
end
|
|
if buff.type == "Curse" then
|
|
curse.modList = new("ModList")
|
|
curse.modList:ScaleAddList(buff.modList, mult)
|
|
if partyTabEnableExportBuffs then
|
|
buffExports["Curse"][buff.name] = { isMark = curse.isMark, effectMult = curse.isMark and mult or (1 + inc / 100) * moreMark, modList = buff.modList }
|
|
end
|
|
else
|
|
-- Curse applies a buff; scale by curse effect, then buff effect
|
|
local temp = new("ModList")
|
|
temp:ScaleAddList(buff.modList, mult)
|
|
curse.buffModList = new("ModList")
|
|
local buffInc = modDB:Sum("INC", skillCfg, "BuffEffectOnSelf")
|
|
local buffMore = modDB:More(skillCfg, "BuffEffectOnSelf")
|
|
curse.buffModList:ScaleAddList(temp, (1 + buffInc / 100) * buffMore)
|
|
if env.minion then
|
|
curse.minionBuffModList = new("ModList")
|
|
local buffInc = env.minion.modDB:Sum("INC", nil, "BuffEffectOnSelf")
|
|
local buffMore = env.minion.modDB:More(nil, "BuffEffectOnSelf")
|
|
curse.minionBuffModList:ScaleAddList(temp, (1 + buffInc / 100) * buffMore)
|
|
end
|
|
end
|
|
t_insert(curses, curse)
|
|
end
|
|
elseif buff.type == "Link" then
|
|
local linksApplyToMinions = env.minion and modDB:Flag(nil, "Condition:CanLinkToMinions") and modDB:Flag(nil, "Condition:LinkedToMinion")
|
|
and not env.minion.modDB:Flag(nil, "Condition:CannotBeDamaged") and not env.minion.mainSkill.summonSkill.skillTypes[SkillType.MinionsAreUndamageable]
|
|
if env.mode_buffs and (#linkSkills < 1) and (partyTabEnableExportBuffs or linksApplyToMinions) then
|
|
-- Check for extra modifiers to apply to link skills
|
|
local extraLinkModList = { }
|
|
for _, value in ipairs(modDB:List(skillCfg, "ExtraLinkEffect")) do
|
|
local add = true
|
|
for _, mod in ipairs(extraLinkModList) do
|
|
if modLib.compareModParams(mod, value.mod) then
|
|
mod.value = mod.value + value.mod.value
|
|
add = false
|
|
break
|
|
end
|
|
end
|
|
if add then
|
|
t_insert(extraLinkModList, copyTable(value.mod, true))
|
|
-- special handling to add this early
|
|
if value.mod.name == "ParentNonUniqueFlasksAppliedToYou" then
|
|
nonUniqueFlasksApplyToMinion = true
|
|
end
|
|
end
|
|
end
|
|
local inc = skillModList:Sum("INC", skillCfg, "LinkEffect", "BuffEffect")
|
|
local more = skillModList:More(skillCfg, "LinkEffect", "BuffEffect")
|
|
local mult = (1 + inc / 100) * more
|
|
if partyTabEnableExportBuffs then
|
|
local newModList = new("ModList")
|
|
newModList:AddList(buff.modList)
|
|
newModList:AddList(extraLinkModList)
|
|
buffExports["Link"][buff.name] = { effectMult = mult, modList = newModList }
|
|
end
|
|
if linksApplyToMinions then
|
|
activeSkill.minionBuffSkill = true
|
|
env.minion.modDB.conditions["AffectedBy"..buff.name:gsub(" ","")] = true
|
|
env.minion.modDB.conditions["AffectedByLink"] = true
|
|
local srcList = new("ModList")
|
|
inc = inc + env.minion.modDB:Sum("INC", nil, "BuffEffectOnSelf", "LinkEffectOnSelf")
|
|
more = more * env.minion.modDB:More(nil, "BuffEffectOnSelf", "LinkEffectOnSelf")
|
|
mult = (1 + inc / 100) * more
|
|
srcList:ScaleAddList(buff.modList, mult)
|
|
srcList:ScaleAddList(extraLinkModList, mult)
|
|
mergeBuff(srcList, minionBuffs, buff.name)
|
|
linkSkills[1] = buff.name
|
|
end
|
|
end
|
|
end
|
|
end
|
|
if activeSkill.skillModList:Flag(nil, "Condition:CanWither") or (activeSkill.minion and env.minion and env.minion.modDB:Flag(nil, "Condition:CanWither")) then
|
|
local effect = activeSkill.minion and m_floor(6 * (1 + modDB:Sum("INC", nil, "MinionWitherEffect") / 100)) or m_floor(6 * (1 + modDB:Sum("INC", nil, "WitherEffect") / 100))
|
|
modDB:NewMod("WitherEffectStack", "MAX", effect)
|
|
end
|
|
--Handle combustion
|
|
if enemyDB:Flag(nil, "Condition:Ignited") and (activeSkill.skillTypes[SkillType.Damage] or activeSkill.skillTypes[SkillType.Attack]) and not appliedCombustion then
|
|
for _, support in ipairs(activeSkill.supportList) do
|
|
if support.grantedEffect.name == "Combustion" then
|
|
if not activeSkill.skillModList:Flag(activeSkill.skillCfg, "CannotIgnite") then
|
|
local value = activeSkill.skillModList:Sum("BASE", activeSkill.skillCfg, "CombustionFireResist")
|
|
enemyDB:NewMod("FireResist", "BASE", value, "Combustion", { type = "GlobalEffect", effectType = "Debuff", effectName = "Combustion" }, { type = "Condition", var = "Ignited" })
|
|
appliedCombustion = true
|
|
end
|
|
break
|
|
end
|
|
end
|
|
end
|
|
if activeSkill.minion and activeSkill.minion.activeSkillList then
|
|
local castingMinion = activeSkill.minion
|
|
for _, activeMinionSkill in ipairs(activeSkill.minion.activeSkillList) do
|
|
local skillModList = activeMinionSkill.skillModList
|
|
local skillCfg = activeMinionSkill.skillCfg
|
|
for _, buff in ipairs(activeMinionSkill.buffList) do
|
|
if buff.type == "Buff" then
|
|
if env.mode_buffs and activeMinionSkill.skillData.enable then
|
|
local skillCfg = buff.activeSkillBuff and skillCfg
|
|
local modStore = buff.activeSkillBuff and skillModList or castingMinion.modDB
|
|
if buff.applyAllies then
|
|
activeMinionSkill.buffSkill = true
|
|
modDB.conditions["AffectedBy"..buff.name:gsub(" ","")] = true
|
|
local srcList = new("ModList")
|
|
local inc = modStore:Sum("INC", skillCfg, "BuffEffect", "BuffEffectOnPlayer") + modDB:Sum("INC", nil, "BuffEffectOnSelf")
|
|
local more = modStore:More(skillCfg, "BuffEffect", "BuffEffectOnPlayer") * modDB:More(nil, "BuffEffectOnSelf")
|
|
srcList:ScaleAddList(buff.modList, (1 + inc / 100) * more)
|
|
mergeBuff(srcList, buffs, buff.name)
|
|
mergeBuff(buff.modList, buffs, buff.name)
|
|
if activeMinionSkill.skillData.thisIsNotABuff then
|
|
buffs[buff.name].notBuff = true
|
|
end
|
|
if partyTabEnableExportBuffs then
|
|
local inc = modStore:Sum("INC", skillCfg, "BuffEffect")
|
|
local more = modStore:More(skillCfg, "BuffEffect")
|
|
buffExports["Aura"]["otherEffects"] = buffExports["Aura"]["otherEffects"] or { }
|
|
buffExports["Aura"]["otherEffects"][buff.name] = { effectMult = (1 + inc / 100) * more, modList = buff.modList }
|
|
end
|
|
end
|
|
local envMinionCheck = (env.minion and (env.minion == castingMinion or buff.applyAllies))
|
|
if buff.applyMinions or envMinionCheck then
|
|
activeMinionSkill.minionBuffSkill = true
|
|
if envMinionCheck then
|
|
env.minion.modDB.conditions["AffectedBy"..buff.name:gsub(" ","")] = true
|
|
else
|
|
activeSkill.minion.modDB.conditions["AffectedBy"..buff.name:gsub(" ","")] = true
|
|
end
|
|
local srcList = new("ModList")
|
|
local inc = modStore:Sum("INC", skillCfg, "BuffEffect", (env.minion == castingMinion) and "BuffEffectOnSelf" or nil)
|
|
local more = modStore:More(skillCfg, "BuffEffect", (env.minion == castingMinion) and "BuffEffectOnSelf" or nil)
|
|
srcList:ScaleAddList(buff.modList, (1 + inc / 100) * more)
|
|
mergeBuff(srcList, minionBuffs, buff.name)
|
|
mergeBuff(buff.modList, minionBuffs, buff.name)
|
|
if activeMinionSkill.skillData.thisIsNotABuff then
|
|
buffs[buff.name].notBuff = true
|
|
end
|
|
end
|
|
end
|
|
elseif buff.type == "Aura" then
|
|
if env.mode_buffs and activeMinionSkill.skillData.enable then
|
|
-- Check for extra modifiers to apply to aura skills
|
|
local extraAuraModList = { }
|
|
for _, value in ipairs(activeSkill.minion.modDB:List(skillCfg, "ExtraAuraEffect")) do
|
|
local add = true
|
|
for _, mod in ipairs(extraAuraModList) do
|
|
if modLib.compareModParams(mod, value.mod) then
|
|
mod.value = mod.value + value.mod.value
|
|
add = false
|
|
break
|
|
end
|
|
end
|
|
if add then
|
|
t_insert(extraAuraModList, copyTable(value.mod, true))
|
|
end
|
|
end
|
|
if not (activeSkill.minion.modDB:Flag(nil, "SelfAurasCannotAffectAllies") or activeSkill.minion.modDB:Flag(nil, "SelfAurasOnlyAffectYou") or activeSkill.minion.modDB:Flag(nil, "SelfAuraSkillsCannotAffectAllies") or skillModList:Flag(skillCfg, "SelfAurasAffectYouAndLinkedTarget")) then
|
|
if not modDB:Flag(nil, "AlliesAurasCannotAffectSelf") and not modDB.conditions["AffectedBy"..buff.name:gsub(" ","")] then
|
|
local inc = skillModList:Sum("INC", skillCfg, "AuraEffect", "BuffEffect", "BuffEffectOnPlayer", "AuraBuffEffect") + modDB:Sum("INC", skillCfg, "BuffEffectOnSelf", "AuraEffectOnSelf")
|
|
local more = skillModList:More(skillCfg, "AuraEffect", "BuffEffect", "AuraBuffEffect") * modDB:More(skillCfg, "BuffEffectOnSelf", "AuraEffectOnSelf")
|
|
local mult = (1 + inc / 100) * more
|
|
if not allyBuffs["Aura"] or not allyBuffs["Aura"][buff.name] or allyBuffs["Aura"][buff.name].effectMult / 100 <= mult then
|
|
activeMinionSkill.buffSkill = true
|
|
modDB.conditions["AffectedByAura"] = true
|
|
if buff.name:sub(1,4) == "Vaal" then
|
|
modDB.conditions["AffectedBy"..buff.name:sub(6):gsub(" ","")] = true
|
|
end
|
|
modDB.conditions["AffectedBy"..buff.name:gsub(" ","")] = true
|
|
local srcList = new("ModList")
|
|
srcList:ScaleAddList(buff.modList, mult)
|
|
srcList:ScaleAddList(extraAuraModList, mult)
|
|
mergeBuff(srcList, buffs, buff.name)
|
|
end
|
|
end
|
|
if env.minion and not env.minion.modDB.conditions["AffectedBy"..buff.name:gsub(" ","")] and (env.minion ~= activeSkill.minion or not activeSkill.skillData.auraCannotAffectSelf) then
|
|
local inc = skillModList:Sum("INC", skillCfg, "AuraEffect", "BuffEffect") + env.minion.modDB:Sum("INC", skillCfg, "BuffEffectOnSelf", "AuraEffectOnSelf")
|
|
local more = skillModList:More(skillCfg, "AuraEffect", "BuffEffect") * env.minion.modDB:More(skillCfg, "BuffEffectOnSelf", "AuraEffectOnSelf")
|
|
local mult = (1 + inc / 100) * more
|
|
if not allyBuffs["Aura"] or not allyBuffs["Aura"][buff.name] or allyBuffs["Aura"][buff.name].effectMult / 100 <= mult then
|
|
activeMinionSkill.minionBuffSkill = true
|
|
env.minion.modDB.conditions["AffectedBy"..buff.name:gsub(" ","")] = true
|
|
env.minion.modDB.conditions["AffectedByAura"] = true
|
|
local srcList = new("ModList")
|
|
srcList:ScaleAddList(buff.modList, mult)
|
|
srcList:ScaleAddList(extraAuraModList, mult)
|
|
mergeBuff(srcList, minionBuffs, buff.name)
|
|
end
|
|
end
|
|
local inc = skillModList:Sum("INC", skillCfg, "AuraEffect", "BuffEffect")
|
|
local more = skillModList:More(skillCfg, "AuraEffect", "BuffEffect")
|
|
local mult = (1 + inc / 100) * more
|
|
local newModList = new("ModList")
|
|
newModList:AddList(buff.modList)
|
|
newModList:AddList(extraAuraModList)
|
|
if buffExports["Aura"][buff.name] then
|
|
buffExports["Aura"][buff.name.."_Debuff"] = buffExports["Aura"][buff.name]
|
|
end
|
|
buffExports["Aura"][buff.name] = { effectMult = mult, modList = newModList }
|
|
if env.player.mainSkill.skillFlags.totem and not env.player.mainSkill.skillModList.conditions["AffectedBy"..buff.name:gsub(" ","")] then
|
|
activeMinionSkill.totemBuffSkill = true
|
|
env.player.mainSkill.skillModList.conditions["AffectedBy"..buff.name:gsub(" ","")] = true
|
|
env.player.mainSkill.skillModList.conditions["AffectedByAura"] = true
|
|
|
|
local srcList = new("ModList")
|
|
local inc = skillModList:Sum("INC", skillCfg, "AuraEffect", "BuffEffect", "AuraBuffEffect")
|
|
local more = skillModList:More(skillCfg, "AuraEffect", "BuffEffect", "AuraBuffEffect")
|
|
local lists = {extraAuraModList, buff.modList}
|
|
local scale = (1 + inc / 100) * more
|
|
scale = m_max(scale, 0)
|
|
|
|
for _, modList in ipairs(lists) do
|
|
for _, mod in ipairs(modList) do
|
|
if mod.name == "EnergyShield" or mod.name == "Armour" or mod.name == "Evasion" or mod.name:match("Resist?M?a?x?$") then
|
|
local totemMod = copyTable(mod)
|
|
totemMod.name = "Totem"..totemMod.name
|
|
if scale ~= 1 then
|
|
if type(totemMod.value) == "number" then
|
|
totemMod.value = (m_floor(totemMod.value) == totemMod.value) and m_modf(round(totemMod.value * scale, 2)) or totemMod.value * scale
|
|
elseif type(totemMod.value) == "table" and totemMod.value.mod then
|
|
totemMod.value.mod.value = (m_floor(totemMod.value.mod.value) == totemMod.value.mod.value) and m_modf(round(totemMod.value.mod.value * scale, 2)) or totemMod.value.mod.value * scale
|
|
end
|
|
end
|
|
srcList:AddMod(totemMod)
|
|
end
|
|
end
|
|
end
|
|
mergeBuff(srcList, buffs, "Totem "..buff.name)
|
|
end
|
|
end
|
|
end
|
|
elseif buff.type == "Curse" then
|
|
if env.mode_effective and activeMinionSkill.skillData.enable and (not enemyDB:Flag(nil, "Hexproof") or activeMinionSkill.skillTypes[SkillType.Mark]) then
|
|
local curse = {
|
|
name = buff.name,
|
|
priority = determineCursePriority(buff.name, activeMinionSkill),
|
|
}
|
|
local inc = skillModList:Sum("INC", skillCfg, "CurseEffect") + enemyDB:Sum("INC", nil, "CurseEffectOnSelf")
|
|
local more = skillModList:More(skillCfg, "CurseEffect") * enemyDB:More(nil, "CurseEffectOnSelf")
|
|
curse.modList = new("ModList")
|
|
curse.modList:ScaleAddList(buff.modList, (1 + inc / 100) * more)
|
|
t_insert(minionCurses, curse)
|
|
end
|
|
elseif buff.type == "Debuff" or buff.type == "AuraDebuff" 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 = activeMinionSkill.skillData.stackCount or 1
|
|
end
|
|
if env.mode_effective and stackCount > 0 then
|
|
activeMinionSkill.debuffSkill = true
|
|
local srcList = new("ModList")
|
|
local mult = 1
|
|
if buff.type == "AuraDebuff" then
|
|
mult = 0
|
|
if not skillModList:Flag(nil, "SelfAurasOnlyAffectYou") or skillModList:Flag(skillCfg, "SelfAurasAffectYouAndLinkedTarget") then
|
|
local inc = skillModList:Sum("INC", skillCfg, "AuraEffect", "BuffEffect", "DebuffEffect")
|
|
local more = skillModList:More(skillCfg, "AuraEffect", "BuffEffect", "DebuffEffect")
|
|
mult = (1 + inc / 100) * more
|
|
if not enemyDB.conditions["AffectedBy"..buff.name:gsub(" ","")] then
|
|
buffExports["Aura"][buff.name..(buffExports["Aura"][buff.name] and "_Debuff" or "")] = { effectMult = mult, modList = buff.modList }
|
|
if allyBuffs["AuraDebuff"] and allyBuffs["AuraDebuff"][buff.name] and allyBuffs["AuraDebuff"][buff.name].effectMult / 100 > mult then
|
|
mult = 0
|
|
end
|
|
else
|
|
mult = 0
|
|
end
|
|
end
|
|
end
|
|
enemyDB.conditions["AffectedBy"..buff.name:gsub(" ","")] = true
|
|
if env.minion and env.minion == activeSkill.minion then
|
|
env.minion.modDB.conditions["AffectedBy"..buff.name:gsub(" ","")] = true
|
|
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 activeMinionSkill.skillData.stackCount or buff.stackVar then
|
|
srcList:NewMod("Multiplier:"..buff.name.."Stack", "BASE", activeMinionSkill.skillData.stackCount, buff.name)
|
|
end
|
|
mergeBuff(srcList, debuffs, buff.name)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
if allyBuffs["otherEffects"] then
|
|
for buffName, buff in pairs(allyBuffs["otherEffects"]) do
|
|
modDB.conditions["AffectedBy"..buffName:gsub(" ","")] = true
|
|
local inc = modDB:Sum("INC", nil, "BuffEffectOnSelf", "AuraEffectOnSelf")
|
|
local more = modDB:More(nil, "BuffEffectOnSelf", "AuraEffectOnSelf")
|
|
local srcList = new("ModList")
|
|
srcList:ScaleAddList(buff.modList, (buff.effectMult + inc) / 100 * more)
|
|
mergeBuff(srcList, buffs, buffName)
|
|
if env.minion then
|
|
env.minion.modDB.conditions["AffectedBy"..buffName:gsub(" ","")] = true
|
|
local inc = env.minion.modDB:Sum("INC", nil, "BuffEffectOnSelf", "AuraEffectOnSelf")
|
|
local more = env.minion.modDB:More(nil, "BuffEffectOnSelf", "AuraEffectOnSelf")
|
|
local srcList = new("ModList")
|
|
srcList:ScaleAddList(buff.modList, (buff.effectMult + inc) / 100 * more)
|
|
mergeBuff(srcList, minionBuffs, buffName)
|
|
end
|
|
end
|
|
end
|
|
if allyBuffs["Aura"] then
|
|
for auraName, aura in pairs(allyBuffs["Aura"]) do
|
|
if auraName ~= "Vaal" then
|
|
local auraNameCompressed = auraName:gsub(" ","")
|
|
if not modDB:Flag(nil, "AlliesAurasCannotAffectSelf") and not modDB.conditions["AffectedBy"..auraNameCompressed] then
|
|
modDB.conditions["AffectedByAura"] = true
|
|
modDB.conditions["AffectedBy"..auraNameCompressed] = true
|
|
local srcList = new("ModList")
|
|
srcList:ScaleAddList(aura.modList, aura.effectMult / 100)
|
|
mergeBuff(srcList, buffs, auraName)
|
|
end
|
|
if env.minion and not env.minion.modDB.conditions["AffectedBy"..auraNameCompressed] then
|
|
env.minion.modDB.conditions["AffectedByAura"] = true
|
|
env.minion.modDB.conditions["AffectedBy"..auraNameCompressed] = true
|
|
local srcList = new("ModList")
|
|
srcList:ScaleAddList(aura.modList, aura.effectMult / 100)
|
|
mergeBuff(srcList, minionBuffs, auraName)
|
|
end
|
|
end
|
|
end
|
|
if allyBuffs["Aura"]["Vaal"] then
|
|
for auraName, aura in pairs(allyBuffs["Aura"]["Vaal"]) do
|
|
local auraNameCompressed = auraName:gsub(" ","")
|
|
if not modDB:Flag(nil, "AlliesAurasCannotAffectSelf") and not modDB.conditions["AffectedBy"..auraNameCompressed] then
|
|
modDB.conditions["AffectedByAura"] = true
|
|
modDB.conditions["AffectedBy"..auraName:sub(6):gsub(" ","")] = true
|
|
modDB.conditions["AffectedBy"..auraNameCompressed] = true
|
|
local srcList = new("ModList")
|
|
srcList:ScaleAddList(aura.modList, aura.effectMult / 100)
|
|
mergeBuff(srcList, buffs, auraName)
|
|
end
|
|
if env.minion and not env.minion.modDB.conditions["AffectedBy"..auraNameCompressed] then
|
|
env.minion.modDB.conditions["AffectedByAura"] = true
|
|
env.minion.modDB.conditions["AffectedBy"..auraNameCompressed] = true
|
|
local srcList = new("ModList")
|
|
srcList:ScaleAddList(aura.modList, aura.effectMult / 100)
|
|
mergeBuff(srcList, minionBuffs, auraName)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
if allyBuffs["AuraDebuff"] and env.mode_effective then
|
|
for auraName, aura in pairs(allyBuffs["AuraDebuff"]) do
|
|
if auraName ~= "Vaal" then
|
|
local auraNameCompressed = auraName:gsub(" ","")
|
|
if not enemyDB.conditions["AffectedBy"..auraNameCompressed] then
|
|
enemyDB.conditions["AffectedBy"..auraNameCompressed] = true
|
|
modDB.conditions["AffectedBy"..auraNameCompressed] = true
|
|
local srcList = new("ModList")
|
|
srcList:ScaleAddList(aura.modList, aura.effectMult / 100)
|
|
mergeBuff(srcList, debuffs, auraName)
|
|
end
|
|
end
|
|
end
|
|
if allyBuffs["AuraDebuff"]["Vaal"] then
|
|
for auraName, aura in pairs(allyBuffs["AuraDebuff"]["Vaal"]) do
|
|
local auraNameCompressed = auraName:gsub(" ","")
|
|
if not enemyDB.conditions["AffectedBy"..auraNameCompressed] then
|
|
enemyDB.conditions["AffectedBy"..auraNameCompressed] = true
|
|
modDB.conditions["AffectedBy"..auraNameCompressed] = true
|
|
local srcList = new("ModList")
|
|
srcList:ScaleAddList(aura.modList, aura.effectMult / 100)
|
|
mergeBuff(srcList, debuffs, auraName)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
if env.partyMembers["Warcry"] and env.partyMembers["Warcry"]["Warcry"] then
|
|
for warcryName, warcry in pairs(env.partyMembers["Warcry"]["Warcry"]) do
|
|
local warcryNameCompressed = warcryName:gsub(" ","")
|
|
if not modDB.conditions["AffectedBy"..warcryNameCompressed] then
|
|
modDB.conditions["AffectedByWarcry"] = true
|
|
modDB.conditions["AffectedBy"..warcryNameCompressed] = true
|
|
local srcList = new("ModList")
|
|
for _, warcryBuff in ipairs(warcry.modList) do
|
|
srcList:ScaleAddList({warcryBuff}, (warcry.effectMult or 100) / 100 * (warcryBuff[1].warcryPowerBonus or 1))
|
|
end
|
|
mergeBuff(srcList, buffs, warcryName)
|
|
end
|
|
if env.minion and not env.minion.modDB.conditions["AffectedBy"..warcryNameCompressed] then
|
|
env.minion.modDB.conditions["AffectedByWarcry"] = true
|
|
env.minion.modDB.conditions["AffectedBy"..warcryNameCompressed] = true
|
|
local srcList = new("ModList")
|
|
for _, warcryBuff in ipairs(warcry.modList) do
|
|
srcList:ScaleAddList({warcryBuff}, (warcry.effectMult or 100) / 100 * (warcryBuff[1].warcryPowerBonus or 1))
|
|
end
|
|
mergeBuff(srcList, minionBuffs, warcryName)
|
|
end
|
|
end
|
|
end
|
|
if env.partyMembers["Link"] and env.partyMembers["Link"]["Link"] then
|
|
for linkName, link in pairs(env.partyMembers["Link"]["Link"]) do
|
|
local linkNameCompressed = linkName:gsub(" ","")
|
|
if not modDB.conditions["AffectedBy"..linkNameCompressed] then
|
|
modDB.conditions["AffectedByLink"] = true
|
|
modDB.conditions["AffectedBy"..linkNameCompressed] = true
|
|
local srcList = new("ModList")
|
|
srcList:ScaleAddList(link.modList, (link.effectMult or 100) / 100)
|
|
mergeBuff(srcList, buffs, linkName)
|
|
end
|
|
end
|
|
end
|
|
|
|
if env.mode_combat then
|
|
-- This needs to be done in 2 steps to account for effects affecting life recovery from flasks
|
|
-- For example Sorrow of the Divine and buffs (like flask recovery watchers eye)
|
|
mergeFlasks(env.flasks, true, nonUniqueFlasksApplyToMinion)
|
|
end
|
|
|
|
-- Calculate charges early to enable usage of stats that depend on charge count
|
|
doActorCharges(env, env.player)
|
|
|
|
-- Process stats from alternate charges
|
|
if env.mode_combat then
|
|
if modDB:Flag(nil, "UseEnduranceCharges") and modDB:Flag(nil, "EnduranceChargesConvertToBrutalCharges") then
|
|
local tripleDmgChancePerEndurance = modDB:Sum("BASE", nil, "PerBrutalTripleDamageChance")
|
|
modDB:NewMod("TripleDamageChance", "BASE", tripleDmgChancePerEndurance, { type = "Multiplier", var = "BrutalCharge" } )
|
|
end
|
|
if modDB:Flag(nil, "UseFrenzyCharges") and modDB:Flag(nil, "FrenzyChargesConvertToAfflictionCharges") then
|
|
local dmgPerAffliction = modDB:Sum("BASE", nil, "PerAfflictionAilmentDamage")
|
|
local effectPerAffliction = modDB:Sum("BASE", nil, "PerAfflictionNonDamageEffect")
|
|
modDB:NewMod("Damage", "MORE", dmgPerAffliction, "Affliction Charges", 0, KeywordFlag.Ailment, { type = "Multiplier", var = "AfflictionCharge" } )
|
|
modDB:NewMod("EnemyChillEffect", "MORE", effectPerAffliction, "Affliction Charges", { type = "Multiplier", var = "AfflictionCharge" } )
|
|
modDB:NewMod("EnemyShockEffect", "MORE", effectPerAffliction, "Affliction Charges", { type = "Multiplier", var = "AfflictionCharge" } )
|
|
modDB:NewMod("EnemyFreezeEffect", "MORE", effectPerAffliction, "Affliction Charges", { type = "Multiplier", var = "AfflictionCharge" } )
|
|
modDB:NewMod("EnemyScorchEffect", "MORE", effectPerAffliction, "Affliction Charges", { type = "Multiplier", var = "AfflictionCharge" } )
|
|
modDB:NewMod("EnemyBrittleEffect", "MORE", effectPerAffliction, "Affliction Charges", { type = "Multiplier", var = "AfflictionCharge" } )
|
|
modDB:NewMod("EnemySapEffect", "MORE", effectPerAffliction, "Affliction Charges", { type = "Multiplier", var = "AfflictionCharge" } )
|
|
end
|
|
end
|
|
|
|
-- Check for extra curses
|
|
for dest, modDB in pairs({[curses] = modDB, [minionCurses] = env.minion and env.minion.modDB}) do
|
|
for _, value in ipairs(modDB:List(nil, "ExtraCurse")) do
|
|
local gemModList = new("ModList")
|
|
local grantedEffect = env.data.skills[value.skillId]
|
|
if grantedEffect then
|
|
calcs.mergeSkillInstanceMods(env, gemModList, {
|
|
grantedEffect = grantedEffect,
|
|
level = value.level,
|
|
quality = 0,
|
|
})
|
|
local curseModList = { }
|
|
for _, mod in ipairs(gemModList) do
|
|
for _, tag in ipairs(mod) do
|
|
if tag.type == "GlobalEffect" and tag.effectType == "Curse" then
|
|
t_insert(curseModList, mod)
|
|
break
|
|
end
|
|
end
|
|
end
|
|
if value.applyToPlayer then
|
|
-- Sources for curses on the player don't usually respect any kind of limit, so there's little point bothering with slots
|
|
if modDB:Sum("BASE", nil, "AvoidCurse") < 100 then
|
|
modDB.conditions["Cursed"] = true
|
|
modDB.multipliers["CurseOnSelf"] = (modDB.multipliers["CurseOnSelf"] or 0) + 1
|
|
modDB.conditions["AffectedBy"..grantedEffect.name:gsub(" ","")] = true
|
|
local cfg = { skillName = grantedEffect.name }
|
|
local inc = modDB:Sum("INC", cfg, "CurseEffectOnSelf") + gemModList:Sum("INC", nil, "CurseEffectAgainstPlayer")
|
|
local more = modDB:More(cfg, "CurseEffectOnSelf") * gemModList:More(nil, "CurseEffectAgainstPlayer")
|
|
modDB:ScaleAddList(curseModList, m_max((1 + inc / 100) * more, 0))
|
|
end
|
|
elseif not enemyDB:Flag(nil, "Hexproof") or modDB:Flag(nil, "CursesIgnoreHexproof") then
|
|
local curse = {
|
|
name = grantedEffect.name,
|
|
fromPlayer = (dest == curses),
|
|
priority = determineCursePriority(grantedEffect.name),
|
|
}
|
|
curse.modList = new("ModList")
|
|
curse.modList:ScaleAddList(curseModList, (1 + enemyDB:Sum("INC", nil, "CurseEffectOnSelf") / 100) * enemyDB:More(nil, "CurseEffectOnSelf"))
|
|
t_insert(dest, curse)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
local allyCurses = {}
|
|
local allyPartyCurses = env.partyMembers["Curse"]
|
|
if allyPartyCurses["Curse"] then
|
|
allyCurses.limit = allyPartyCurses.limit
|
|
else
|
|
allyPartyCurses = { Curse = {} }
|
|
end
|
|
for curseName, curse in pairs(allyPartyCurses["Curse"]) do
|
|
local newCurse = {
|
|
name = curseName,
|
|
priority = 0,
|
|
modList = new("ModList")
|
|
}
|
|
local mult = curse.effectMult / 100
|
|
if curse.isMark then
|
|
newCurse.isMark = true
|
|
else
|
|
mult = mult * enemyDB:More(nil, "CurseEffectOnSelf")
|
|
end
|
|
newCurse.modList:ScaleAddList(curse.modList, mult)
|
|
t_insert(allyCurses, newCurse)
|
|
end
|
|
|
|
|
|
-- Set curse limit
|
|
output.EnemyCurseLimit = modDB:Flag(nil, "CurseLimitIsMaximumPowerCharges") and output.PowerChargesMax or modDB:Sum("BASE", nil, "EnemyCurseLimit")
|
|
curses.limit = output.EnemyCurseLimit
|
|
buffExports["CurseLimit"] = curses.limit
|
|
-- Assign curses to slots
|
|
local curseSlots = { }
|
|
env.curseSlots = curseSlots
|
|
-- Currently assume only 1 mark is possible
|
|
local markSlotted = false
|
|
for _, source in ipairs({curses, minionCurses, allyCurses}) do
|
|
for _, curse in ipairs(source) do
|
|
-- Calculate curses that ignore hex limit after
|
|
if not curse.ignoreHexLimit and not curse.socketedCursesHexLimit then
|
|
local slot
|
|
local skipAddingCurse = false
|
|
-- Check if we need to disable a certain curse aura.
|
|
for _, activeSkill in ipairs(env.player.activeSkillList) do
|
|
if (activeSkill.buffList[1] and curse.name == activeSkill.buffList[1].name and activeSkill.skillTypes[SkillType.Aura]) then
|
|
if modDB:Flag(nil, "SelfAurasOnlyAffectYou") or activeSkill.skillModList:Flag(env.player.mainSkill.skillCfg, "SelfAurasAffectYouAndLinkedTarget") then
|
|
skipAddingCurse = true
|
|
end
|
|
break
|
|
end
|
|
end
|
|
for i = 1, source.limit do
|
|
-- Prevent multiple marks from being considered
|
|
if curse.isMark then
|
|
if markSlotted then
|
|
slot = nil
|
|
break
|
|
end
|
|
end
|
|
if not curseSlots[i] then
|
|
slot = i
|
|
break
|
|
elseif curseSlots[i].name == curse.name then
|
|
if curseSlots[i].priority < curse.priority then
|
|
slot = i
|
|
else
|
|
slot = nil
|
|
end
|
|
break
|
|
elseif curseSlots[i].priority < curse.priority then
|
|
slot = i
|
|
end
|
|
end
|
|
if slot then
|
|
if curseSlots[slot] and curseSlots[slot].isMark then
|
|
markSlotted = false
|
|
end
|
|
if skipAddingCurse == false then
|
|
curseSlots[slot] = curse
|
|
end
|
|
if curse.isMark then
|
|
markSlotted = true
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
for _, source in ipairs({curses, minionCurses}) do
|
|
for _, curse in ipairs(source) do
|
|
if curse.ignoreHexLimit then
|
|
local skipAddingCurse = false
|
|
for i = 1, #curseSlots do
|
|
if curseSlots[i].name == curse.name then
|
|
-- if curse is higher priority, replace current curse with it, otherwise if same or lower priority skip it entirely
|
|
if curseSlots[i].priority < curse.priority then
|
|
curseSlots[i] = curse
|
|
end
|
|
skipAddingCurse = true
|
|
break
|
|
end
|
|
end
|
|
if not skipAddingCurse then
|
|
curseSlots[#curseSlots + 1] = curse
|
|
end
|
|
end
|
|
if curse.socketedCursesHexLimit then
|
|
local socketedCursesHexLimitValue = modDB:Sum("BASE", nil, "SocketedCursesHexLimitValue")
|
|
local skipAddingCurse = false
|
|
for i = 1, #curseSlots do
|
|
if curseSlots[i].name == curse.name then
|
|
-- if curse is higher priority, replace current curse with it, otherwise if same or lower priority skip it entirely
|
|
if curseSlots[i].priority < curse.priority then
|
|
curseSlots[i] = curse
|
|
end
|
|
skipAddingCurse = true
|
|
break
|
|
end
|
|
if i >= socketedCursesHexLimitValue then
|
|
skipAddingCurse = true
|
|
end
|
|
end
|
|
if not skipAddingCurse then
|
|
curseSlots[#curseSlots + 1] = curse
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Process guard buffs
|
|
local guardSlots = { }
|
|
local nonVaal = false
|
|
for name, modList in pairs(guards) do
|
|
if name == "Vaal Molten Shell" then
|
|
wipeTable(guardSlots)
|
|
nonVaal = false
|
|
t_insert(guardSlots, { name = name, modList = modList })
|
|
break
|
|
elseif name:match("^Vaal") then
|
|
t_insert(guardSlots, { name = name, modList = modList })
|
|
elseif not nonVaal then
|
|
t_insert(guardSlots, { name = name, modList = modList })
|
|
nonVaal = true
|
|
end
|
|
end
|
|
if nonVaal then
|
|
modDB.conditions["AffectedByNonVaalGuardSkill"] = true
|
|
end
|
|
for _, guard in ipairs(guardSlots) do
|
|
modDB.conditions["AffectedByGuardSkill"] = true
|
|
modDB.conditions["AffectedBy"..guard.name:gsub(" ","")] = true
|
|
mergeBuff(guard.modList, buffs, guard.name)
|
|
end
|
|
|
|
-- Apply buff/debuff modifiers
|
|
for _, modList in pairs(buffs) 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(minionBuffs) do
|
|
env.minion.modDB:AddList(modList)
|
|
end
|
|
end
|
|
for _, modList in pairs(debuffs) do
|
|
enemyDB:AddList(modList)
|
|
end
|
|
modDB.multipliers["CurseOnEnemy"] = #curseSlots
|
|
for _, slot in ipairs(curseSlots) do
|
|
enemyDB.conditions["Cursed"] = true
|
|
if slot.isMark then
|
|
enemyDB.conditions["Marked"] = true
|
|
end
|
|
if slot.modList then
|
|
enemyDB:AddList(slot.modList)
|
|
end
|
|
if slot.buffModList then
|
|
modDB:AddList(slot.buffModList)
|
|
end
|
|
if slot.minionBuffModList then
|
|
env.minion.modDB:AddList(slot.minionBuffModList)
|
|
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
|
|
local maxImpaleStacks = modDB:Sum("BASE", nil, "ImpaleStacksMax") * (1 + modDB:Sum("BASE", nil, "ImpaleAdditionalDurationChance") / 100)
|
|
if not enemyDB:HasMod("BASE", nil, "Multiplier:ImpaleStacks") then
|
|
enemyDB:NewMod("Multiplier:ImpaleStacks", "BASE", maxImpaleStacks, "Config", { type = "Condition", var = "Combat" })
|
|
elseif enemyDB:Sum("BASE", nil, "Multiplier:ImpaleStacks") > maxImpaleStacks then
|
|
enemyDB:ReplaceMod("Multiplier:ImpaleStacks", "BASE", maxImpaleStacks, "Config", { type = "Condition", var = "Combat" })
|
|
end
|
|
|
|
-- Check for extra auras
|
|
buffExports["Aura"]["extraAura"] = { effectMult = 1, modList = new("ModList") }
|
|
for _, value in ipairs(modDB:List(nil, "ExtraAura")) do
|
|
local modList = { value.mod }
|
|
if not value.onlyAllies then
|
|
local inc = modDB:Sum("INC", nil, "BuffEffectOnSelf", "AuraEffectOnSelf")
|
|
local more = modDB:More(nil, "BuffEffectOnSelf", "AuraEffectOnSelf")
|
|
modDB:ScaleAddList(modList, (1 + inc / 100) * more)
|
|
if not value.notBuff then
|
|
modDB.multipliers["BuffOnSelf"] = (modDB.multipliers["BuffOnSelf"] or 0) + 1
|
|
end
|
|
end
|
|
if not modDB:Flag(nil, "SelfAurasCannotAffectAllies") then
|
|
if env.minion then
|
|
local inc = env.minion.modDB:Sum("INC", nil, "BuffEffectOnSelf", "AuraEffectOnSelf")
|
|
local more = env.minion.modDB:More(nil, "BuffEffectOnSelf", "AuraEffectOnSelf")
|
|
env.minion.modDB:ScaleAddList(modList, (1 + inc / 100) * more)
|
|
end
|
|
buffExports["Aura"]["extraAura"].modList:AddMod(value.mod)
|
|
local totemModBlacklist = value.mod.name and (value.mod.name == "Speed" or value.mod.name == "CritMultiplier" or value.mod.name == "CritChance")
|
|
if env.player.mainSkill.skillFlags.totem and not totemModBlacklist then
|
|
local totemMod = copyTable(value.mod)
|
|
local totemModName, matches = totemMod.name:gsub("Condition:", "Condition:Totem")
|
|
if matches < 1 then
|
|
totemModName = "Totem" .. totemMod.name
|
|
end
|
|
totemMod.name = totemModName
|
|
modDB:AddMod(totemMod)
|
|
end
|
|
end
|
|
end
|
|
if allyBuffs["extraAura"] then
|
|
for _, buff in pairs(allyBuffs["extraAura"]) do
|
|
local modList = buff.modList
|
|
local inc = modDB:Sum("INC", nil, "BuffEffectOnSelf", "AuraEffectOnSelf")
|
|
local more = modDB:More(nil, "BuffEffectOnSelf", "AuraEffectOnSelf")
|
|
modDB:ScaleAddList(modList, (1 + inc / 100) * more)
|
|
if env.minion then
|
|
local inc = env.minion.modDB:Sum("INC", nil, "BuffEffectOnSelf", "AuraEffectOnSelf")
|
|
local more = env.minion.modDB:More(nil, "BuffEffectOnSelf", "AuraEffectOnSelf")
|
|
env.minion.modDB:ScaleAddList(modList, (1 + inc / 100) * more)
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Check for modifiers to apply to actors affected by player auras or curses
|
|
for _, value in ipairs(modDB:List(nil, "AffectedByAuraMod")) do
|
|
for actor in pairs(affectedByAura) do
|
|
actor.modDB:AddMod(value.mod)
|
|
end
|
|
end
|
|
|
|
-- Merge keystones again to catch any that were added by buffs
|
|
mergeKeystones(env)
|
|
|
|
-- Special handling for Dancing Dervish
|
|
if modDB:Flag(nil, "DisableWeapons") then
|
|
env.player.weaponData1 = copyTable(env.data.unarmedWeaponData[env.classId])
|
|
modDB.conditions["Unarmed"] = true
|
|
if not env.player.Gloves or env.player.Gloves == None then
|
|
modDB.conditions["Unencumbered"] = true
|
|
end
|
|
elseif env.weaponModList1 then
|
|
modDB:AddList(env.weaponModList1)
|
|
end
|
|
|
|
-- Process misc buffs/modifiers
|
|
doActorCharges(env, env.player)
|
|
doActorMisc(env, env.player)
|
|
if env.minion then
|
|
doActorCharges(env, env.minion)
|
|
doActorMisc(env, env.minion)
|
|
end
|
|
|
|
-- Calculate maximum and apply the strongest non-damaging ailments
|
|
local ailmentData = data.nonDamagingAilment
|
|
local ailments = {
|
|
["Chill"] = {
|
|
condition = "Chilled",
|
|
mods = function(num)
|
|
local mods = { modLib.createMod("ActionSpeed", "INC", -num, "Chill", { type = "Condition", var = "Chilled" }) }
|
|
if output.HasBonechill and (hasGuaranteedBonechill or enemyDB:Sum("BASE", nil, "ChillVal") > 0) then
|
|
t_insert(mods, modLib.createMod("ColdDamageTaken", "INC", num, "Bonechill", { type = "Condition", var = "Chilled" }))
|
|
end
|
|
if modDB:Flag(nil, "ChillEffectIncDamageTaken") then
|
|
t_insert(mods, modLib.createMod("DamageTaken", "INC", num, "Ahuana's Bite", { type = "Condition", var = "Chilled" }))
|
|
end
|
|
if modDB:Flag(nil, "ChillEffectLessDamageDealt") then
|
|
t_insert(mods, modLib.createMod("Damage", "MORE", -num / 2, "Shaper of Winter", { type = "Condition", var = "Chilled" }))
|
|
end
|
|
return mods
|
|
end
|
|
},
|
|
["Shock"] = {
|
|
condition = "Shocked",
|
|
mods = function(num)
|
|
local mods = { }
|
|
if modDB:Flag(nil, "ShockCanStack") then
|
|
t_insert(mods, modLib.createMod("DamageTaken", "INC", num, "Shock", { type = "Condition", var = "Shocked" }, { type = "Multiplier", var = "ShockStacks", limit = modDB:Override(nil, "ShockStacksMax") or modDB:Sum("BASE", nil, "ShockStacksMax")}))
|
|
output["CurrentShock"] = num * m_min(enemyDB:Sum("BASE", nil, "Multiplier:ShockStacks"), modDB:Override(nil, "ShockStacksMax") or modDB:Sum("BASE", nil, "ShockStacksMax"))
|
|
if breakdown then
|
|
t_insert(mods, modLib.createMod("DamageTakenByShock", "INC", num, "Shock Stacks", { type = "Condition", var = "Shocked" }, { type = "Multiplier", var = "ShockStacks", limit = modDB:Override(nil, "ShockStacksMax") or modDB:Sum("BASE", nil, "ShockStacksMax")}))
|
|
end
|
|
else
|
|
t_insert(mods, modLib.createMod("DamageTaken", "INC", num, "Shock", { type = "Condition", var = "Shocked" }))
|
|
end
|
|
return mods
|
|
end
|
|
},
|
|
["Scorch"] = {
|
|
condition = "Scorched",
|
|
mods = function(num)
|
|
local mods = { }
|
|
if modDB:Flag(nil, "ScorchCanStack") then
|
|
t_insert(mods, modLib.createMod("ElementalResist", "BASE", -num, "Scorch", { type = "Condition", var = "Scorched" }, { type = "Multiplier", var = "ScorchStacks", limit = modDB:Override(nil, "ScorchStacksMax") or modDB:Sum("BASE", nil, "ScorchStacksMax")}))
|
|
output["CurrentScorch"] = num * m_min(enemyDB:Sum("BASE", nil, "Multiplier:ScorchStacks"), modDB:Override(nil, "ScorchStacksMax") or modDB:Sum("BASE", nil, "ScorchStacksMax"))
|
|
if breakdown then
|
|
t_insert(mods, modLib.createMod("ElementalResistByScorch", "BASE", -num, "Scorch Stacks", { type = "Condition", var = "Scorched" }, { type = "Multiplier", var = "ScorchStacks", limit = modDB:Override(nil, "ScorchStacksMax") or modDB:Sum("BASE", nil, "ScorchStacksMax")}))
|
|
end
|
|
else
|
|
t_insert(mods, modLib.createMod("ElementalResist", "BASE", -num, "Scorch", { type = "Condition", var = "Scorched" }))
|
|
end
|
|
return mods
|
|
end
|
|
},
|
|
["Brittle"] = {
|
|
condition = "Brittle",
|
|
mods = function(num)
|
|
return { modLib.createMod("SelfCritChance", "BASE", num, "Brittle", { type = "Condition", var = "Brittle" }) }
|
|
end
|
|
},
|
|
["Sap"] = {
|
|
condition = "Sapped",
|
|
mods = function(num)
|
|
return { modLib.createMod("Damage", "MORE", -num, "Sap", { type = "Condition", var = "Sapped" }) }
|
|
end
|
|
},
|
|
}
|
|
|
|
for ailment, val in pairs(ailments) do
|
|
if (enemyDB:Sum("BASE", nil, ailment.."Val") > 0
|
|
or modDB:Sum("BASE", nil, ailment.."Base", ailment.."Override", ailment.."Minimum"))
|
|
and not enemyDB:Flag(nil, "Condition:Already"..val.condition) then
|
|
local override = 0
|
|
local minimum = 0
|
|
for _, value in ipairs(modDB:Tabulate("BASE", nil, ailment.."Base", ailment.."Override", ailment.."Minimum")) do
|
|
local mod = value.mod
|
|
local effect = mod.value
|
|
if mod.name == ailment.."Override" then
|
|
enemyDB:NewMod("Condition:"..val.condition, "FLAG", true, mod.source)
|
|
end
|
|
if mod.name == ailment.."Base" or mod.name == ailment.."Minimum" then
|
|
-- If the main skill can inflict the ailment, the ailment is inflicted with a hit, and we have a node allocated that checks what our highest damage is, then
|
|
-- use the skill's ailment modifiers
|
|
-- if not, use the generic modifiers
|
|
-- Scorch/Sap/Brittle do not have guaranteed sources from hits, and therefore will only end up in this bit of code if it's not supposed to apply the skillModList, which is bad
|
|
if ailment ~= "Scorch" and ailment ~= "Sap" and ailment ~= "Brittle" and not env.player.mainSkill.skillModList:Flag(nil, "Cannot"..ailment) and env.player.mainSkill.skillFlags.hit and modDB:Flag(nil, "ChecksHighestDamage") then
|
|
effect = effect * calcLib.mod(env.player.mainSkill.skillModList, nil, "Enemy"..ailment.."Effect")
|
|
else
|
|
effect = effect * calcLib.mod(modDB, nil, "Enemy"..ailment.."Effect")
|
|
end
|
|
modDB:NewMod(ailment.."Override", "BASE", effect, mod.source, mod.flags, mod.keywordFlags, unpack(mod))
|
|
if mod.name == ailment.."Minimum" then
|
|
minimum = minimum + effect
|
|
end
|
|
end
|
|
override = m_max(m_max(override, effect or 0), minimum)
|
|
end
|
|
local maxAilment = modDB:Override(nil, ailment.."Max") or 0
|
|
if not modDB:Override(nil, ailment.."Max") then
|
|
for _, skill in ipairs(env.player.activeSkillList) do
|
|
local skillMax = modDB:Override(nil, ailment.."Max") or (ailmentData[ailment].max + skill.baseSkillModList:Sum("BASE", nil, ailment.."Max"))
|
|
maxAilment = skillMax > maxAilment and skillMax or maxAilment
|
|
end
|
|
end
|
|
output["Maximum"..ailment] = maxAilment
|
|
output["Current"..ailment] = m_floor(m_min(m_max(override, enemyDB:Sum("BASE", nil, ailment.."Val")), output["Maximum"..ailment]) * (10 ^ ailmentData[ailment].precision)) / (10 ^ ailmentData[ailment].precision)
|
|
for _, mod in ipairs(val.mods(output["Current"..ailment])) do
|
|
enemyDB:AddMod(mod)
|
|
end
|
|
enemyDB:NewMod("Condition:Already"..val.condition, "FLAG", true, { type = "Condition", var = val.condition } ) -- Prevents ailment from applying doubly for minions
|
|
end
|
|
end
|
|
|
|
-- Update chill and shock multipliers
|
|
local chillEffectMultiplier = enemyDB:Sum("BASE", nil, "Multiplier:ChillEffect")
|
|
if chillEffectMultiplier < output["CurrentChill"] then
|
|
enemyDB:NewMod("Multiplier:ChillEffect", "BASE", output["CurrentChill"] - chillEffectMultiplier, "")
|
|
end
|
|
local shockEffectMultiplier = enemyDB:Sum("BASE", nil, "Multiplier:ShockEffect")
|
|
if shockEffectMultiplier < output["CurrentShock"] then
|
|
enemyDB:NewMod("Multiplier:ShockEffect", "BASE", output["CurrentShock"] - shockEffectMultiplier, "")
|
|
end
|
|
|
|
doActorCharges(env, env.enemy)
|
|
doActorMisc(env, env.enemy)
|
|
|
|
local major, minor = env.spec.treeVersion:match("(%d+)_(%d+)")
|
|
|
|
-- Apply exposures
|
|
for _, element in ipairs({"Fire", "Cold", "Lightning"}) do
|
|
if tonumber(major) <= 3 and tonumber(minor) <= 15 -- Elemental Equilibrium pre-3.16 does not remove Exposure effects
|
|
or not modDB:Flag(nil, "ElementalEquilibrium") -- if Elemental Equilibrium isn't active we just process Exposure normally
|
|
or element == "Fire" and not enemyDB:Flag(nil, "Condition:HitByFireDamage")
|
|
or element == "Cold" and not enemyDB:Flag(nil, "Condition:HitByColdDamage")
|
|
or element == "Lightning" and not enemyDB:Flag(nil, "Condition:HitByLightningDamage") then
|
|
local min = math.huge
|
|
local source = ""
|
|
for _, mod in ipairs(enemyDB:Tabulate("BASE", nil, element.."Exposure")) do
|
|
if mod.value < min then
|
|
min = mod.value
|
|
source = mod.mod.source
|
|
end
|
|
end
|
|
if min ~= math.huge then
|
|
-- Modify the magnitude of all exposures
|
|
for _, mod in ipairs(modDB:Tabulate("BASE", nil, "ExtraExposure", "Extra"..element.."Exposure")) do
|
|
min = min + mod.value
|
|
end
|
|
enemyDB:NewMod("Condition:Has"..element.."Exposure", "FLAG", true, "")
|
|
enemyDB:NewMod(element.."Resist", "BASE", m_min(min, modDB:Override(nil, "ExposureMin")), source)
|
|
modDB:NewMod("Condition:AppliedExposureRecently", "FLAG", true, "")
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Handle consecrated ground effects on enemies
|
|
if enemyDB:Flag(nil, "Condition:OnConsecratedGround") then
|
|
local effect = 1 + modDB:Sum("INC", nil, "ConsecratedGroundEffect") / 100
|
|
enemyDB:NewMod("DamageTaken", "INC", enemyDB:Sum("INC", nil, "DamageTakenConsecratedGround") * effect, "Consecrated Ground")
|
|
end
|
|
|
|
-- Defence/offence calculations
|
|
calcs.defence(env, env.player)
|
|
if not skipEHP then
|
|
calcs.buildDefenceEstimations(env, env.player)
|
|
end
|
|
|
|
calcs.triggers(env, env.player)
|
|
if not calcs.mirages(env) then
|
|
calcs.offence(env, env.player, env.player.mainSkill)
|
|
end
|
|
|
|
if env.minion then
|
|
calcs.defence(env, env.minion)
|
|
if not skipEHP then -- main.build.calcsTab.input.showMinion and -- should be disabled unless "calcsTab.input.showMinion" is true
|
|
calcs.buildDefenceEstimations(env, env.minion)
|
|
end
|
|
calcs.triggers(env, env.minion)
|
|
calcs.offence(env, env.minion, env.minion.mainSkill)
|
|
end
|
|
|
|
-- Export modifiers to enemy conditions and stats for party tab
|
|
if partyTabEnableExportBuffs then
|
|
for k, mod in pairs(enemyDB.mods) do
|
|
if k:find("Condition") and not k:find("Party") then
|
|
buffExports["EnemyConditions"][k] = true
|
|
elseif (k:find("Resist") and not k:find("Totem") and not k:find("Max")) or k:find("Damage") or k:find("ActionSpeed") or k:find("SelfCrit") or (k:find("Multiplier") and not k:find("Max") and not k:find("Impale")) then
|
|
for _, v in ipairs(mod) do
|
|
if not v.party and v.value ~= 0 and v.source ~= "EnemyConfig" and v.source ~= "Base" and not v.source:find("Delirious") and not v.source:find("^Party") then
|
|
local skipValue = false
|
|
for _, tag in ipairs(v) do
|
|
if tag.effectType == "Curse" or tag.effectType == "AuraDebuff" then
|
|
skipValue = true
|
|
break
|
|
end
|
|
end
|
|
if not skipValue and (not v[1] or (
|
|
(v[1].type ~= "Condition" or (v[1].var and (enemyDB.mods["Condition:"..v[1].var] and enemyDB.mods["Condition:"..v[1].var][1].value) or v[1].varList and (enemyDB.mods["Condition:"..v[1].varList[1]] and enemyDB.mods["Condition:"..v[1].varList[1]][1].value)))
|
|
and (v[1].type ~= "Multiplier" or (enemyDB.mods["Multiplier:"..v[1].var] and enemyDB.mods["Multiplier:"..v[1].var][1].value)))) then
|
|
if buffExports["EnemyMods"][k] then
|
|
if not buffExports["EnemyMods"][k].MultiStat then
|
|
buffExports["EnemyMods"][k] = { MultiStat = true, buffExports["EnemyMods"][k] }
|
|
end
|
|
t_insert(buffExports["EnemyMods"][k], v)
|
|
else
|
|
buffExports["EnemyMods"][k] = v
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
for _, damageType in ipairs({"Physical", "Lightning", "Cold", "Fire", "Chaos"}) do
|
|
if env.modDB:Flag(nil, "Enemy"..damageType.."ResistEqualToYours") and output[damageType.."Resist"] then
|
|
buffExports.PlayerMods["Enemy"..damageType.."ResistEqualToYours"] = true
|
|
buffExports.PlayerMods[damageType.."Resist="..tostring(output[damageType.."Resist"])] = true
|
|
end
|
|
end
|
|
if env.modDB:Flag(nil, "PartyMemberMaximumEnduranceChargesEqualToYours") and output["EnduranceChargesMax"] then
|
|
buffExports.PlayerMods["PartyMemberMaximumEnduranceChargesEqualToYours"] = true
|
|
buffExports.PlayerMods["EnduranceChargesMax="..tostring(output["EnduranceChargesMax"])] = true
|
|
end
|
|
|
|
buffExports.PlayerMods["MovementSpeedMod|percent|max="..tostring(output["MovementSpeedMod"] * 100)] = true
|
|
|
|
for _, mod in ipairs(buffExports["Aura"]["extraAura"].modList) do
|
|
-- leaving comment to make it easier for future similar mods
|
|
--if mod.name:match("Parent") then
|
|
-- ConPrintTable(mod)
|
|
--end
|
|
if mod.name == "YourFortifyEqualToParent" then
|
|
buffExports.PlayerMods["PartyMemberFortifyEqualToYours"] = true
|
|
buffExports.PlayerMods["FortificationStacks="..tostring(output.FortificationStacks or 0)] = true
|
|
end
|
|
end
|
|
|
|
-- preStack Mine auras
|
|
for auraName, aura in pairs(buffExports["Aura"]) do
|
|
if auraName:match("Mine") and not auraName:match(" Limit") then
|
|
buffExports["Aura"][auraName] = copyTable(buffExports["Aura"][auraName])
|
|
aura = buffExports["Aura"][auraName]
|
|
local stackCount = buffExports["EnemyMods"]["Multiplier:"..auraName.."Stack"] and buffExports["EnemyMods"]["Multiplier:"..auraName.."Stack"].value or 0
|
|
buffExports["EnemyMods"]["Multiplier:"..auraName.."Stack"] = nil
|
|
if stackCount == 0 then
|
|
buffExports["Aura"][auraName] = nil
|
|
else
|
|
for _, mod in ipairs(aura.modList) do
|
|
for _, tag in ipairs(mod) do
|
|
if (tag.effectStackVar or "") == "ActiveMineCount" then
|
|
tag.effectStackVar = nil
|
|
mod.value = mod.value * stackCount
|
|
break
|
|
end
|
|
end
|
|
end
|
|
end
|
|
if buffExports["Aura"][auraName.." Limit"] then
|
|
buffExports["Aura"][auraName.." Limit"] = copyTable(buffExports["Aura"][auraName.." Limit"])
|
|
aura = buffExports["Aura"][auraName.." Limit"]
|
|
if stackCount == 0 then
|
|
buffExports["Aura"][auraName.." Limit"] = nil
|
|
else
|
|
for _, mod in ipairs(aura.modList) do
|
|
for _, tag in ipairs(mod) do
|
|
if (tag.effectStackVar or "") == "ActiveMineCount" then
|
|
tag.effectStackVar = nil
|
|
mod.value = mod.value * stackCount
|
|
break
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
for linkName, link in pairs(buffExports["Link"]) do
|
|
if linkName == "Flame Link" then
|
|
buffExports.PlayerMods["Life="..tostring(output["Life"])] = true
|
|
end
|
|
for _, mod in ipairs(link.modList) do
|
|
if mod.name:match("Parent") then
|
|
if mod.name == "MaximumBlockAttackChanceIsEqualToParent" then
|
|
buffExports.PlayerMods["BlockChanceMax="..tostring(output["BlockChanceMax"])] = true
|
|
elseif mod.name == "BlockAttackChanceIsEqualToParent" then
|
|
buffExports.PlayerMods["BlockChance="..tostring(output["BlockChance"])] = true
|
|
elseif mod.name == "MaximumLifeLeechIsEqualToPartyMember" then
|
|
buffExports.PlayerMods["MaxLifeLeechRatePercent="..tostring(output["MaxLifeLeechRatePercent"])] = true
|
|
elseif mod.name == "TakenFromParentESBeforeYou" then
|
|
buffExports.PlayerMods["EnergyShieldRecoveryCap="..tostring(output["EnergyShieldRecoveryCap"])] = true
|
|
elseif mod.name == "MainHandCritIsEqualToParent" then
|
|
if output.MainHand then
|
|
buffExports.PlayerMods["MainHand.CritChance="..tostring(output["MainHand"]["CritChance"])] = true
|
|
elseif env.player.weaponData1 then
|
|
buffExports.PlayerMods["MainHand.CritChance="..tostring(env.player.weaponData1.CritChance)] = true
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
env.build.partyTab:setBuffExports(buffExports)
|
|
end
|
|
|
|
-- calculate Gem Level of MainSkill
|
|
if env.player.mainSkill then
|
|
local mainSkill = env.player.mainSkill
|
|
if mainSkill.activeEffect and mainSkill.activeEffect.level and mainSkill.activeEffect.srcInstance then
|
|
local baseLevel = mainSkill.skillModList:Sum("BASE", mainSkill.skillCfg, "GemLevel")
|
|
local totalItemLevel = mainSkill.skillModList:Sum("BASE", mainSkill.skillCfg, "GemItemLevel")
|
|
local totalSupportLevel = mainSkill.skillModList:Sum("BASE", mainSkill.skillCfg, "GemSupportLevel")
|
|
|
|
output.GemHasLevel = true
|
|
output.GemLevel = baseLevel + totalSupportLevel + totalItemLevel
|
|
|
|
if env.player.breakdown then
|
|
env.player.breakdown.GemLevel = {}
|
|
t_insert(env.player.breakdown.GemLevel, s_format("%d ^8(level from gem)", baseLevel))
|
|
if totalSupportLevel > 0 then
|
|
t_insert(env.player.breakdown.GemLevel, s_format("+ %d ^8(level from support)", totalSupportLevel))
|
|
end
|
|
if totalItemLevel > 0 then
|
|
t_insert(env.player.breakdown.GemLevel, s_format("+ %d ^8(level from items)", totalItemLevel))
|
|
end
|
|
t_insert(env.player.breakdown.GemLevel, s_format("= %d", output.GemLevel))
|
|
end
|
|
end
|
|
end
|
|
|
|
cacheData(cacheSkillUUID(env.player.mainSkill, env), env)
|
|
end
|