Files
PathOfBuilding/Modules/CalcPerform.lua
Jack Lockwood f27c68ce30 Release 1.4.152.3
Implemented logic for melee distance scaling attack multipliers (Close combat and Slayers Impact node)
Add counterattack double damage bonus from Gladiators Painforged node
Implement parsing for all of Slayer's nodes
 Add support for Assassins Mistwalker node and Ascendants  node for Assassin
Add support for travel skills cooldown recovery
Add Badge of Brotherhood mod parsing
Add incremental shock values instead of the default locked value of 50%
2019-10-06 22:16:45 +11:00

1055 lines
42 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 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 tempTable1 = { }
-- 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)
local modDB = env.modDB
for _, name in ipairs(modDB:List(nil, "Keystone")) do
if not env.keystonesAdded[name] then
env.keystonesAdded[name] = true
modDB:AddList(env.spec.tree.keystoneMap[name].modList)
end
end
end
-- Calculate attributes and life/mana pools, and set conditions
local function doActorAttribsPoolsConditions(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
end
if not actor.itemList["Weapon 2"] then
condList["OffHandIsEmpty"] = true
end
if actor.weaponData1.type == "None" then
condList["Unarmed"] = true
else
local info = env.data.weaponTypeInfo[actor.weaponData1.type]
condList["Using"..info.flag] = true
if actor.weaponData1.countsAsAll1H then
condList["UsingAxe"] = true
condList["UsingSword"] = true
condList["UsingDagger"] = true
condList["UsingMace"] = true
condList["UsingClaw"] = 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.weaponData2.type then
local info = env.data.weaponTypeInfo[actor.weaponData2.type]
condList["Using"..info.flag] = true
if actor.weaponData2.countsAsAll1H then
condList["UsingAxe"] = true
condList["UsingSword"] = true
condList["UsingDagger"] = true
condList["UsingMace"] = true
condList["UsingClaw"] = 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" and actor.weaponData2.type == "Claw" then
condList["DualWieldingClaws"] = true
end
end
if env.mode_combat then
if not modDB:Flag(nil, "NeverCrit") then
condList["CritInPast8Sec"] = true
end
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.MovementSkill] then
condList["UsedMovementSkillRecently"] = true
end
if actor.mainSkill.skillFlags.minion then
condList["UsedMinionSkillRecently"] = true
end
if actor.mainSkill.skillTypes[SkillType.Vaal] then
condList["UsedVaalSkillRecently"] = true
end
if actor.mainSkill.skillTypes[SkillType.Channelled] 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
end
if actor.mainSkill.skillFlags.totem then
condList["HaveTotem"] = true
condList["SummonedTotemRecently"] = true
end
if actor.mainSkill.skillFlags.mine then
condList["DetonatedMinesRecently"] = true
end
end
-- Calculate attributes
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
output.LowestAttribute = m_min(output.Str, output.Dex, output.Int)
condList["DexHigherThanInt"] = output.Dex > output.Int
condList["StrHigherThanDex"] = output.Str > output.Dex
condList["IntHigherThanStr"] = output.Int > output.Str
-- Add attribute bonuses
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 = round((output.Str + modDB:Sum("BASE", nil, "DexIntToMeleeBonus")) * strDmgBonusRatioOverride)
else
actor.strDmgBonus = round((output.Str + modDB:Sum("BASE", nil, "DexIntToMeleeBonus")) / 5)
end
modDB:NewMod("PhysicalDamage", "INC", actor.strDmgBonus, "Strength", ModFlag.Melee)
modDB:NewMod("Accuracy", "BASE", output.Dex * 2, "Dexterity")
if not modDB:Flag(nil, "IronReflexes") then
modDB:NewMod("Evasion", "INC", round(output.Dex / 5), "Dexterity")
end
if not modDB:Flag(nil, "NoIntBonusToMana") then
modDB:NewMod("Mana", "BASE", round(output.Int / 2), "Intelligence")
end
modDB:NewMod("EnergyShield", "INC", round(output.Int / 5), "Intelligence")
-- Life/mana pools
if modDB:Flag(nil, "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 = round(base * (1 + inc/100) * more * (1 - conv/100))
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
output.Mana = round(calcLib.val(modDB, "Mana"))
if breakdown then
breakdown.Mana = breakdown.simple(nil, nil, output.Mana, "Mana")
end
-- Life/mana reservation
for _, pool in pairs({"Life", "Mana"}) do
local max = output[pool]
local reserved
if max > 0 then
reserved = (actor["reserved_"..pool.."Base"] or 0) + m_ceil(max * (actor["reserved_"..pool.."Percent"] or 0) / 100)
output[pool.."Reserved"] = reserved
output[pool.."ReservedPercent"] = reserved / max * 100
output[pool.."Unreserved"] = max - reserved
output[pool.."UnreservedPercent"] = (max - reserved) / max * 100
if (max - reserved) / max <= 0.35 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
-- Process charges, 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
-- Calculate current and maximum charges
output.PowerChargesMin = modDB:Sum("BASE", nil, "PowerChargesMin")
output.PowerChargesMax = modDB:Sum("BASE", nil, "PowerChargesMax")
output.FrenzyChargesMin = modDB:Sum("BASE", nil, "FrenzyChargesMin")
output.FrenzyChargesMax = modDB:Flag(nil, "MaximumFrenzyChargesIsMaximumPowerCharges") and output.PowerChargesMax or modDB:Sum("BASE", nil, "FrenzyChargesMax")
output.EnduranceChargesMin = modDB:Sum("BASE", nil, "EnduranceChargesMin")
output.EnduranceChargesMax = modDB:Flag(nil, "MaximumEnduranceChargesIsMaximumFrenzyCharges") and output.FrenzyChargesMax or modDB:Sum("BASE", nil, "EnduranceChargesMax")
output.SiphoningChargesMax = modDB:Sum("BASE", nil, "SiphoningChargesMax")
output.ChallengerChargesMax = modDB:Sum("BASE", nil, "ChallengerChargesMax")
output.BlitzChargesMax = modDB:Sum("BASE", nil, "BlitzChargesMax")
output.InspirationChargesMax = modDB:Sum("BASE", nil, "InspirationChargesMax")
output.CrabBarriersMax = modDB:Sum("BASE", nil, "CrabBarriersMax")
if modDB:Flag(nil, "UsePowerCharges") then
output.PowerCharges = modDB:Override(nil, "PowerCharges") or output.PowerChargesMax
else
output.PowerCharges = 0
end
output.PowerCharges = m_max(output.PowerCharges, output.PowerChargesMin)
output.RemovablePowerCharges = output.PowerCharges - output.PowerChargesMin
if modDB:Flag(nil, "UseFrenzyCharges") then
output.FrenzyCharges = modDB:Override(nil, "FrenzyCharges") or output.FrenzyChargesMax
else
output.FrenzyCharges = 0
end
output.FrenzyCharges = m_max(output.FrenzyCharges, output.FrenzyChargesMin)
output.RemovableFrenzyCharges = output.FrenzyCharges - output.FrenzyChargesMin
if modDB:Flag(nil, "UseEnduranceCharges") then
output.EnduranceCharges = modDB:Override(nil, "EnduranceCharges") or output.EnduranceChargesMax
else
output.EnduranceCharges = 0
end
output.EnduranceCharges = m_max(output.EnduranceCharges, output.EnduranceChargesMin)
output.RemovableEnduranceCharges = output.EnduranceCharges - output.EnduranceChargesMin
if modDB:Flag(nil, "UseSiphoningCharges") then
output.SiphoningCharges = modDB:Override(nil, "SiphoningCharges") or output.SiphoningChargesMax
else
output.SiphoningCharges = 0
end
if modDB:Flag(nil, "UseChallengerCharges") then
output.ChallengerCharges = modDB:Override(nil, "ChallengerCharges") or output.ChallengerChargesMax
else
output.ChallengerCharges = 0
end
if modDB:Flag(nil, "UseBlitzCharges") then
output.BlitzCharges = modDB:Override(nil, "BlitzCharges") or output.BlitzChargesMax
else
output.BlitzCharges = 0
end
if modDB:Flag(nil, "UseInspirationCharges") then
output.InspirationCharges = modDB:Override(nil, "InspirationCharges") or output.InspirationChargesMax
else
output.InspirationCharges = 0
end
output.CrabBarriers = m_max(modDB:Override(nil, "CrabBarriers") or output.CrabBarriersMax, output.CrabBarriersMax)
modDB.multipliers["PowerCharge"] = output.PowerCharges
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["SiphoningCharge"] = output.SiphoningCharges
modDB.multipliers["ChallengerCharge"] = output.ChallengerCharges
modDB.multipliers["BlitzCharge"] = output.BlitzCharges
modDB.multipliers["InspirationCharge"] = output.InspirationCharges
modDB.multipliers["CrabBarrier"] = output.CrabBarriers
-- Process enemy modifiers
for _, value in ipairs(modDB:List(nil, "EnemyModifier")) do
enemyDB:AddMod(value.mod)
end
-- Add misc buffs/debuffs
if env.mode_combat then
if modDB:Flag(nil, "Fortify") then
local effect = m_floor(20 * (1 + modDB:Sum("INC", nil, "FortifyEffectOnSelf", "BuffEffectOnSelf") / 100))
modDB:NewMod("DamageTakenWhenHit", "INC", -effect, "Fortify")
modDB.multipliers["BuffOnSelf"] = (modDB.multipliers["BuffOnSelf"] or 0) + 1
end
if modDB:Flag(nil, "Onslaught") then
local effect = m_floor(20 * (1 + modDB:Sum("INC", nil, "OnslaughtEffect", "BuffEffectOnSelf") / 100))
modDB:NewMod("Speed", "INC", effect, "Onslaught")
modDB:NewMod("MovementSpeed", "INC", effect, "Onslaught")
end
if modDB:Flag(nil, "UnholyMight") then
local effect = m_floor(30 * (1 + modDB:Sum("INC", nil, "BuffEffectOnSelf") / 100))
modDB:NewMod("PhysicalDamageGainAsChaos", "BASE", effect, "Unholy Might")
end
if modDB:Flag(nil, "Tailwind") then
local effect = m_floor(10 * (1 + modDB:Sum("INC", nil, "TailwindEffectOnSelf", "BuffEffectOnSelf") / 100))
modDB:NewMod("ActionSpeed", "INC", effect, "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")
modDB:NewMod("MovementSpeed", "INC", m_floor(25 * effectMod), "Adrenaline")
modDB:NewMod("PhysicalDamageReduction", "BASE", m_floor(10 * effectMod), "Adrenaline")
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")
modDB:NewMod("MovementSpeed", "INC", 20, "Her Embrace")
end
if modDB:Flag(nil, "Elusive") then
local effect = 1 + modDB:Sum("INC", nil, "ElusiveEffect", "BuffEffectOnSelf") / 100
condList["Elusive"] = true
modDB:NewMod("AttackDodgeChance", "BASE", m_floor(20 * effect), "Elusive")
modDB:NewMod("SpellDodgeChance", "BASE", m_floor(20 * effect), "Elusive")
modDB:NewMod("MovementSpeed", "INC", m_floor(40 * effect), "Elusive")
end
if modDB:Flag(nil, "Chill") then
local effect = m_max(m_floor(30 * calcLib.mod(modDB, nil, "SelfChillEffect")), 0)
modDB:NewMod("ActionSpeed", "INC", effect * (modDB:Flag(nil, "SelfChillEffectIsReversed") and 1 or -1), "Chill")
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") then
condList["Leeching"] = true
condList["LeechingLife"] = true
env.configInput.conditionLeeching = true
end
end
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. Calculates reservations
-- 6. Sets conditions and calculates attributes and life/mana pools (doActorAttribsPoolsConditions)
-- 7. Processes buffs and debuffs
-- 8. Processes charges and misc buffs (doActorMisc)
-- 9. Calculates defence and offence stats (calcs.defence, calcs.offence)
function calcs.perform(env)
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)
end
end
env.player.output = { }
env.enemy.output = { }
local output = env.player.output
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
if env.minion.minionData.armour then
env.minion.modDB:NewMod("Armour", "BASE", m_floor((10 + env.minion.level * 2) * env.minion.minionData.armour * 1.038 ^ env.minion.level), "Base")
end
env.minion.modDB:NewMod("Evasion", "BASE", round((30 + env.minion.level * 5) * 1.03 ^ env.minion.level), "Base")
env.minion.modDB:NewMod("Accuracy", "BASE", round((17 + env.minion.level / 2) * (env.minion.minionData.accuracy or 1) * 1.03 ^ env.minion.level), "Base")
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", 200, "Base", { type = "Multiplier", var = "PowerCharge" })
env.minion.modDB:NewMod("Speed", "INC", 15, "Base", { type = "Multiplier", var = "FrenzyCharge" })
env.minion.modDB:NewMod("Damage", "MORE", 4, "Base", { type = "Multiplier", var = "FrenzyCharge" })
env.minion.modDB:NewMod("MovementSpeed", "INC", 5, "Base", { type = "Multiplier", var = "FrenzyCharge" })
env.minion.modDB:NewMod("PhysicalDamageReduction", "BASE", 15, "Base", { type = "Multiplier", var = "EnduranceCharge" })
env.minion.modDB:NewMod("ElementalResist", "BASE", 15, "Base", { type = "Multiplier", var = "EnduranceCharge" })
env.minion.modDB:NewMod("ProjectileCount", "BASE", 1, "Base")
if env.build.targetVersion ~= "2_6" then
env.minion.modDB:NewMod("Damage", "MORE", -50, "Base", 0, KeywordFlag.Poison)
env.minion.modDB:NewMod("Damage", "MORE", -50, "Base", 0, KeywordFlag.Ignite)
env.minion.modDB:NewMod("SkillData", "LIST", { key = "bleedBasePercent", value = 70/6 }, "Base")
end
env.minion.modDB:NewMod("Damage", "MORE", 500, "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.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:AddList(env.player.itemList["Weapon 2"].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:Flag(nil, "StrengthAddedToMinions") then
env.minion.modDB:NewMod("Str", "BASE", round(calcLib.val(modDB, "Str")), "Player")
end
if modDB:Flag(nil, "HalfStrengthAddedToMinions") then
env.minion.modDB:NewMod("Str", "BASE", round(calcLib.val(modDB, "Str") * 0.5), "Player")
end
end
if env.aegisModList then
env.player.itemList["Weapon 2"] = nil
end
local breakdown
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
-- Merge flask modifiers
if env.mode_combat then
local effectInc = modDB:Sum("INC", nil, "FlaskEffect")
local flaskBuffs = { }
for item in pairs(env.flasks) do
-- 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
local effectMod = 1 + (effectInc + item.flaskData.effectInc) / 100
if item.buffModList[1] then
local srcList = new("ModList")
srcList:ScaleAddList(item.buffModList, effectMod)
mergeBuff(srcList, flaskBuffs, item.baseName)
end
if item.modList[1] then
local srcList = new("ModList")
srcList:ScaleAddList(item.modList, effectMod)
local key
if item.rarity == "UNIQUE" then
key = item.title
else
key = ""
for _, mod in ipairs(item.modList) do
key = key .. modLib.formatModParams(mod) .. "&"
end
end
mergeBuff(srcList, flaskBuffs, key)
end
end
if not modDB:Flag(nil, "FlasksDoNotApplyToPlayer") then
for _, buffModList in pairs(flaskBuffs) do
modDB.conditions["UsingFlask"] = true
modDB:AddList(buffModList)
end
end
if env.minion and modDB:Flag(env.player.mainSkill.skillCfg, "FlasksApplyToMinion") then
for _, buffModList in pairs(flaskBuffs) do
env.minion.modDB.conditions["UsingFlask"] = true
env.minion.modDB:AddList(buffModList)
end
end
end
-- Merge keystones again to catch any that were added by flasks
mergeKeystones(env)
-- 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
if breakdown then
breakdown.LifeReserved = { reservations = { } }
breakdown.ManaReserved = { reservations = { } }
end
for _, activeSkill in ipairs(env.player.activeSkillList) do
if activeSkill.skillTypes[SkillType.ManaCostReserved] and not activeSkill.skillFlags.totem then
local skillModList = activeSkill.skillModList
local skillCfg = activeSkill.skillCfg
local suffix = activeSkill.skillTypes[SkillType.ManaCostPercent] and "Percent" or "Base"
local baseVal = activeSkill.skillData.manaCostOverride or activeSkill.activeEffect.grantedEffectLevel.manaCost or 0
local mult = skillModList:More(skillCfg, "SupportManaMultiplier")
local more = skillModList:More(skillCfg, "ManaReserved")
local inc = skillModList:Sum("INC", skillCfg, "ManaReserved")
local base = m_floor(baseVal * mult)
local cost
if activeSkill.skillData.manaCostForced then
cost = activeSkill.skillData.manaCostForced
else
cost = m_max(base - m_modf(base * -m_floor((100 + inc) * more - 100) / 100), 0)
end
if activeSkill.activeMineCount then
cost = cost * activeSkill.activeMineCount
end
local pool
if skillModList:Flag(skillCfg, "BloodMagic", "SkillBloodMagic") then
pool = "Life"
else
pool = "Mana"
end
env.player["reserved_"..pool..suffix] = env.player["reserved_"..pool..suffix] + cost
if breakdown then
t_insert(breakdown[pool.."Reserved"].reservations, {
skillName = activeSkill.activeEffect.grantedEffect.name,
base = baseVal .. (activeSkill.skillTypes[SkillType.ManaCostPercent] and "%" or ""),
mult = mult ~= 1 and ("x "..mult),
more = more ~= 1 and ("x "..more),
inc = inc ~= 0 and ("x "..(1 + inc/100)),
total = cost .. (activeSkill.skillTypes[SkillType.ManaCostPercent] and "%" or ""),
})
end
end
end
-- Calculate attributes and life/mana pools
doActorAttribsPoolsConditions(env, 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
env.minion.modDB:AddList(env.spec.tree.keystoneMap[name].modList)
end
doActorAttribsPoolsConditions(env, env.minion)
end
-- Process attribute requirements
do
local reqMult = calcLib.mod(modDB, nil, "GlobalAttributeRequirements")
for _, attr in ipairs({"Str","Dex","Int"}) do
if env.mode == "CALCS" then
breakdown["Req"..attr] = {
rowList = { },
colList = {
{ label = attr, key = "req" },
{ label = "Source", key = "source" },
{ label = "Source Name", key = "sourceName" },
}
}
end
local out = 0
for _, reqSource in ipairs(env.requirementsTable) do
if reqSource[attr] and reqSource[attr] > 0 then
local req = m_floor(reqSource[attr] * reqMult)
out = m_max(out, req)
if env.mode == "CALCS" then
local row = {
req = req > output[attr] 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"..attr].rowList, row)
end
end
end
output["Req"..attr] = out
if env.mode == "CALCS" then
output["Req"..attr.."String"] = out > output[attr] and colorCodes.NEGATIVE..out or out
table.sort(breakdown["Req"..attr].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
end
-- Check for extra modifiers to apply to aura skills
local extraAuraModList = { }
for _, value in ipairs(modDB:List(nil, "ExtraAuraEffect")) do
t_insert(extraAuraModList, value.mod)
end
-- Combine buffs/debuffs
output.EnemyCurseLimit = modDB:Sum("BASE", nil, "EnemyCurseLimit")
local buffs = { }
env.buffs = buffs
local minionBuffs = { }
env.minionBuffs = minionBuffs
local debuffs = { }
env.debuffs = debuffs
local curses = {
limit = output.EnemyCurseLimit,
}
local minionCurses = {
limit = 1,
}
local affectedByAura = { }
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")
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) then
activeSkill.minionBuffSkill = true
env.minion.modDB.conditions["AffectedBy"..buff.name] = 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
end
elseif buff.type == "Aura" then
if env.mode_buffs then
if not activeSkill.skillData.auraCannotAffectSelf then
activeSkill.buffSkill = true
affectedByAura[env.player] = true
modDB.conditions["AffectedBy"..buff.name:gsub(" ","")] = true
local srcList = new("ModList")
local inc = skillModList:Sum("INC", skillCfg, "AuraEffect", "BuffEffect", "BuffEffectOnSelf", "AuraEffectOnSelf")
local more = skillModList:More(skillCfg, "AuraEffect", "BuffEffect", "BuffEffectOnSelf", "AuraEffectOnSelf")
srcList:ScaleAddList(buff.modList, (1 + inc / 100) * more)
srcList:ScaleAddList(extraAuraModList, (1 + inc / 100) * more)
mergeBuff(srcList, buffs, buff.name)
end
if env.minion and not modDB:Flag(nil, "SelfAurasCannotAffectAllies") then
activeSkill.minionBuffSkill = true
affectedByAura[env.minion] = true
env.minion.modDB.conditions["AffectedBy"..buff.name] = true
local srcList = new("ModList")
local inc = skillModList:Sum("INC", skillCfg, "AuraEffect", "BuffEffect") + env.minion.modDB:Sum("INC", nil, "BuffEffectOnSelf", "AuraEffectOnSelf")
local more = skillModList:More(skillCfg, "AuraEffect", "BuffEffect") * env.minion.modDB:More(nil, "BuffEffectOnSelf", "AuraEffectOnSelf")
srcList:ScaleAddList(buff.modList, (1 + inc / 100) * more)
srcList:ScaleAddList(extraAuraModList, (1 + inc / 100) * more)
mergeBuff(srcList, minionBuffs, buff.name)
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
modDB.conditions["AffectedBy"..buff.name:gsub(" ","")] = true
local srcList = new("ModList")
local mult = 1
if buff.type == "AuraDebuff" then
local inc = skillModList:Sum("INC", skillCfg, "AuraEffect", "BuffEffect")
local more = skillModList:More(skillCfg, "AuraEffect", "BuffEffect")
mult = (1 + inc / 100) * more
end
srcList:ScaleAddList(buff.modList, mult * stackCount)
if activeSkill.skillData.stackCount or buff.stackVar then
srcList:NewMod("Multiplier:"..buff.name.."Stack", "BASE", stackCount, buff.name)
end
mergeBuff(srcList, debuffs, buff.name)
end
elseif buff.type == "Curse" or buff.type == "CurseBuff" then
if env.mode_effective and (not enemyDB:Flag(nil, "Hexproof") or modDB:Flag(nil, "CursesIgnoreHexproof")) then
local curse = {
name = buff.name,
fromPlayer = true,
priority = activeSkill.skillTypes[SkillType.Aura] and 3 or 1,
}
local inc = skillModList:Sum("INC", skillCfg, "CurseEffect") + enemyDB:Sum("INC", nil, "CurseEffectOnSelf")
local more = skillModList:More(skillCfg, "CurseEffect") * enemyDB:More(nil, "CurseEffectOnSelf")
if buff.type == "Curse" then
curse.modList = new("ModList")
curse.modList:ScaleAddList(buff.modList, (1 + inc / 100) * more)
else
-- Curse applies a buff; scale by curse effect, then buff effect
local temp = new("ModList")
temp:ScaleAddList(buff.modList, (1 + inc / 100) * more)
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
end
end
if activeSkill.minion then
local castingMinion = activeSkill.minion
for _, activeSkill in ipairs(activeSkill.minion.activeSkillList) do
local skillModList = activeSkill.skillModList
local skillCfg = activeSkill.skillCfg
for _, buff in ipairs(activeSkill.buffList) do
if buff.type == "Buff" then
if env.mode_buffs and activeSkill.skillData.enable then
local skillCfg = buff.activeSkillBuff and skillCfg
local modStore = buff.activeSkillBuff and skillModList or env.minion.modDB
if buff.applyAllies then
modDB.conditions["AffectedBy"..buff.name] = true
local srcList = new("ModList")
local inc = modStore:Sum("INC", skillCfg, "BuffEffect") + modDB:Sum("INC", nil, "BuffEffectOnSelf")
local more = modStore:More(skillCfg, "BuffEffect") * modDB:More(nil, "BuffEffectOnSelf")
srcList:ScaleAddList(buff.modList, (1 + inc / 100) * more)
mergeBuff(srcList, buffs, buff.name)
end
if env.minion and (env.minion == castingMinion or buff.applyAllies) then
env.minion.modDB.conditions["AffectedBy"..buff.name:gsub(" ","")] = true
local srcList = new("ModList")
local inc = modStore:Sum("INC", skillCfg, "BuffEffect", "BuffEffectOnSelf")
local more = modStore:More(skillCfg, "BuffEffect", "BuffEffectOnSelf")
srcList:ScaleAddList(buff.modList, (1 + inc / 100) * more)
mergeBuff(srcList, minionBuffs, buff.name)
end
end
elseif buff.type == "Aura" then
if env.mode_buffs and activeSkill.skillData.enable then
if not modDB:Flag(nil, "AlliesAurasCannotAffectSelf") then
local srcList = new("ModList")
local inc = skillModList:Sum("INC", skillCfg, "AuraEffect", "BuffEffect") + modDB:Sum("INC", nil, "BuffEffectOnSelf", "AuraEffectOnSelf")
local more = skillModList:More(skillCfg, "AuraEffect", "BuffEffect") + modDB:More(nil, "BuffEffectOnSelf", "AuraEffectOnSelf")
srcList:ScaleAddList(buff.modList, (1 + inc / 100) * more)
mergeBuff(srcList, buffs, buff.name)
end
if env.minion and (env.minion ~= activeSkill.minion or not activeSkill.skillData.auraCannotAffectSelf) then
local srcList = new("ModList")
local inc = skillModList:Sum("INC", skillCfg, "AuraEffect", "BuffEffect") + env.minion.modDB:Sum("INC", nil, "BuffEffectOnSelf", "AuraEffectOnSelf")
local more = skillModList:More(skillCfg, "AuraEffect", "BuffEffect") + env.minion.modDB:More(nil, "BuffEffectOnSelf", "AuraEffectOnSelf")
srcList:ScaleAddList(buff.modList, (1 + inc / 100) * more)
mergeBuff(srcList, minionBuffs, buff.name)
end
end
elseif buff.type == "Curse" then
if env.mode_effective and activeSkill.skillData.enable and not enemyDB:Flag(nil, "Hexproof") then
local curse = {
name = buff.name,
priority = 1,
}
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" then
local stackCount
if buff.stackVar then
stackCount = modDB:Sum("BASE", skillCfg, "Multiplier:"..buff.stackVar)
if buff.stackLimit then
stackCount = m_min(stackCount, buff.stackLimit)
elseif buff.stackLimitVar then
stackCount = m_min(stackCount, modDB: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
local srcList = new("ModList")
srcList:ScaleAddList(buff.modList, stackCount)
if activeSkill.skillData.stackCount then
srcList:NewMod("Multiplier:"..buff.name.."Stack", "BASE", activeSkill.skillData.stackCount, buff.name)
end
mergeBuff(srcList, debuffs, buff.name)
end
end
end
end
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")
modDB:ScaleAddList(curseModList, (1 + inc / 100) * more)
end
elseif not enemyDB:Flag(nil, "Hexproof") or modDB:Flag(nil, "CursesIgnoreHexproof") then
local curse = {
name = grantedEffect.name,
fromPlayer = (dest == curses),
priority = 2,
}
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
-- Assign curses to slots
local curseSlots = { }
env.curseSlots = curseSlots
for _, source in ipairs({curses, minionCurses}) do
for _, curse in ipairs(source) do
local slot
for i = 1, source.limit do
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
curseSlots[slot] = curse
end
end
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
local affectedByCurse = { }
for _, slot in ipairs(curseSlots) do
enemyDB.conditions["Cursed"] = true
if slot.fromPlayer then
affectedByCurse[env.enemy] = 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
-- Check for extra auras
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 env.minion and not modDB:Flag(nil, "SelfAurasCannotAffectAllies") 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
-- 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
for _, value in ipairs(modDB:List(nil, "AffectedByCurseMod")) do
for actor in pairs(affectedByCurse) 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
elseif env.weaponModList1 then
modDB:AddList(env.weaponModList1)
end
-- Process misc buffs/modifiers
doActorMisc(env, env.player)
if env.minion then
doActorMisc(env, env.minion)
end
doActorMisc(env, env.enemy)
-- Defence/offence calculations
calcs.defence(env, env.player)
calcs.offence(env, env.player, env.player.mainSkill)
if env.minion then
calcs.defence(env, env.minion)
calcs.offence(env, env.minion, env.minion.mainSkill)
end
end