-- 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 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 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: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.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 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["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, "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 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, "ManaCost") 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 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, "YourAurasCannotAffectAllies") 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 == "AuraDebuff" then if env.mode_effective then activeSkill.debuffSkill = true local srcList = new("ModList") local inc = skillModList:Sum("INC", skillCfg, "AuraEffect", "BuffEffect") local more = skillModList:More(skillCfg, "AuraEffect", "BuffEffect") srcList:ScaleAddList(buff.modList, (1 + inc / 100) * more) mergeBuff(srcList, debuffs, buff.name) end elseif buff.type == "Debuff" 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 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 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 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