diff --git a/spec/System/TestDefence_spec.lua b/spec/System/TestDefence_spec.lua index 07543b9ad..296627bf5 100644 --- a/spec/System/TestDefence_spec.lua +++ b/spec/System/TestDefence_spec.lua @@ -588,4 +588,98 @@ describe("TestDefence", function() assertClose(block.EffectiveBlockChance, 10) assert.is_true(block.TotalEHP > base.TotalEHP) end) + + describe("damage taken from companion's life before you", function() + it("redirects damage to the configured companion life pool", function() + build.configTab.input.enemyIsBoss = "None" + build.configTab.input.customMods = "" + pob1and2Compat() + local baseMaxHit = build.calcsTab.calcsOutput.PhysicalMaximumHitTaken + + build.configTab.input.customMods = "15% of Damage from Hits is taken from your Damageable Companion's Life before you" + build.configTab.input.TotalCompanionLife = 1000 + pob1and2Compat() + + local output = build.calcsTab.calcsOutput + assert.are.equals(15, output.CompanionAllyDamageMitigation) + assert.are.equals(1000, output.TotalCompanionLife) + assert.is_true(output.PhysicalMaximumHitTaken > baseMaxHit) + + local totalTaken = takenHitFromTypeMaxHit("Physical") + local pools = poolsRemainingAfterTypeMaxHit("Physical") + local drained = 1000 - pools.AlliesTakenBeforeYou["companion"].remaining + assert.are.near(totalTaken * 0.15, drained, 1) + end) + + it("stacks redirect sources additively", function() + build.configTab.input.enemyIsBoss = "None" + build.configTab.input.customMods = "\z + 5% of Damage from Hits is taken from your Damageable Companion's Life before you\n\z + 15% of Damage from Hits is taken from your Damageable Companion's Life before you\n\z + " + build.configTab.input.TotalCompanionLife = 100 + pob1and2Compat() + + assert.are.equals(20, build.calcsTab.calcsOutput.CompanionAllyDamageMitigation) + end) + + it("scales the deflected-hits redirect by deflect chance", function() + build.configTab.input.enemyIsBoss = "None" + build.configTab.input.customMods = "\z + 10% of Damage from Deflected Hits is taken from Damageable Companion's Life before you\n\z + +1000 to Deflection Rating\n\z + " + build.configTab.input.TotalCompanionLife = 500 + pob1and2Compat() + + local output = build.calcsTab.calcsOutput + assert.is_true(output.DeflectChance > 0) + assert.are.near(10 * output.DeflectChance / 100, output.CompanionAllyDamageMitigation, 0.001) + end) + + it("sums companion life automatically and applies Loyalty's redirect and life penalty", function() + build.skillsTab:PasteSocketGroup("Companion: Lightless Abomination 20/0 1") + runCallback("OnFrame") + local unsupportedLife = build.calcsTab.calcsEnv.minion.output.Life + + newBuild() + build.skillsTab:PasteSocketGroup("Companion: Lightless Abomination 20/0 1\nLoyalty 1/0 1") + runCallback("OnFrame") + + local output = build.calcsTab.calcsOutput + local companionLife = build.calcsTab.calcsEnv.minion.output.Life + assert.are.equals(10, output.CompanionAllyDamageMitigation) + assert.are.equals(companionLife, output.TotalCompanionLife) + assert.are.near(unsupportedLife * 0.7, companionLife, 1) + end) + + it("sums companion life when the companion is not the main skill", function() + build.skillsTab:PasteSocketGroup("Companion: Lightless Abomination 20/0 1\nLoyalty 1/0 1") + runCallback("OnFrame") + local mainSkillCompanionLife = build.calcsTab.calcsOutput.TotalCompanionLife + + newBuild() + build.skillsTab:PasteSocketGroup("Quarterstaff Strike 20/0 1") + build.skillsTab:PasteSocketGroup("Companion: Lightless Abomination 20/0 1\nLoyalty 1/0 1") + build.mainSocketGroup = 1 + runCallback("OnFrame") + + local output = build.calcsTab.calcsOutput + assert.are.equals(10, output.CompanionAllyDamageMitigation) + assert.are.near(mainSkillCompanionLife, output.TotalCompanionLife, 1) + end) + + it("has no effect with no companions and no config override", function() + build.configTab.input.enemyIsBoss = "None" + build.configTab.input.customMods = "" + pob1and2Compat() + local baseMaxHit = build.calcsTab.calcsOutput.PhysicalMaximumHitTaken + + build.configTab.input.customMods = "15% of Damage from Hits is taken from your Damageable Companion's Life before you" + pob1and2Compat() + + assert.are.equals(0, build.calcsTab.calcsOutput.TotalCompanionLife) + assert.are.equals(baseMaxHit, build.calcsTab.calcsOutput.PhysicalMaximumHitTaken) + end) + end) end) diff --git a/src/Data/ModCache.lua b/src/Data/ModCache.lua index f78bedac5..43d8feff1 100644 --- a/src/Data/ModCache.lua +++ b/src/Data/ModCache.lua @@ -1538,6 +1538,7 @@ c["13% increased Skill Speed"]={{[1]={flags=0,keywordFlags=0,name="Speed",type=" c["13% increased Spell damage for each 200 total Mana you have Spent Recently"]={{[1]={[1]={div=200,type="Multiplier",var="ManaSpentRecently"},flags=2,keywordFlags=0,name="Damage",type="INC",value=13}},nil} c["13% increased maximum Life"]={{[1]={flags=0,keywordFlags=0,name="Life",type="INC",value=13}},nil} c["13% increased maximum Mana"]={{[1]={flags=0,keywordFlags=0,name="Mana",type="INC",value=13}},nil} +c["13% of Damage from Deflected Hits is taken from Damageable Companion's Life before you"]={{[1]={flags=0,keywordFlags=0,name="takenFromCompanionBeforeYouFromDeflected",type="BASE",value=13}},nil} c["13% reduced Attack Speed"]={{[1]={flags=1,keywordFlags=0,name="Speed",type="INC",value=-13}},nil} c["13% reduced Attack and Cast Speed"]={{[1]={flags=0,keywordFlags=0,name="Speed",type="INC",value=-13}},nil} c["13% reduced Charges per use"]={{[1]={flags=0,keywordFlags=0,name="FlaskChargesUsed",type="INC",value=-13}},nil} @@ -1736,8 +1737,8 @@ c["15% less maximum Mana"]={{[1]={flags=0,keywordFlags=0,name="Mana",type="MORE" c["15% more Damage against Enemies affected by Blood Boils"]={{[1]={flags=0,keywordFlags=0,name="Damage",type="MORE",value=15}}," against Enemies affected by Blood Boils "} c["15% more Damage against Enemies affected by Blood Boils Grants Skill: Blood Boil"]={{[1]={[1]={includeTransfigured=true,skillName="Blood Boil",type="SkillName"},flags=0,keywordFlags=0,name="Damage",type="MORE",value=15}}," against Enemies affected by Blood Boils Grants Skill:"} c["15% more Maximum Life"]={{[1]={flags=0,keywordFlags=0,name="Life",type="MORE",value=15}},nil} -c["15% of Damage from Deflected Hits is taken from Damageable Companion's Life before you"]={{[1]={flags=0,keywordFlags=0,name="Damage",type="BASE",value=15}}," from Deflected Hits is taken from Damageable Companion's Life before you "} -c["15% of Damage from Hits is taken from your Damageable Companion's Life before you"]={{[1]={flags=0,keywordFlags=0,name="Damage",type="BASE",value=15}}," from Hits is taken from your Damageable Companion's Life before you "} +c["15% of Damage from Deflected Hits is taken from Damageable Companion's Life before you"]={{[1]={flags=0,keywordFlags=0,name="takenFromCompanionBeforeYouFromDeflected",type="BASE",value=15}},nil} +c["15% of Damage from Hits is taken from your Damageable Companion's Life before you"]={{[1]={flags=0,keywordFlags=0,name="takenFromCompanionBeforeYou",type="BASE",value=15}},nil} c["15% of Damage is taken from Mana before Life"]={{[1]={flags=0,keywordFlags=0,name="DamageTakenFromManaBeforeLife",type="BASE",value=15}},nil} c["15% of Damage taken Recouped as Life"]={{[1]={flags=0,keywordFlags=0,name="LifeRecoup",type="BASE",value=15}},nil} c["15% of Damage taken from Deflected Hits Recouped as Life"]={{[1]={flags=0,keywordFlags=0,name="DamageTaken",type="BASE",value=15}}," from Deflected Hits Recouped as Life "} @@ -3074,8 +3075,7 @@ c["5% increased Stun Threshold"]={{[1]={flags=0,keywordFlags=0,name="StunThresho c["5% increased Stun Threshold per 25 Tribute"]={{[1]={[1]={actor="parent",div=25,stat="Tribute",type="PerStat"},flags=0,keywordFlags=0,name="StunThreshold",type="INC",value=5}},nil} c["5% increased effect of Archon Buffs on you"]={{[1]={flags=0,keywordFlags=0,name="LocalEffect",type="INC",value=5}}," of Archon Buffs on you "} c["5% increased total Power counted by Warcries"]={{[1]={flags=0,keywordFlags=0,name="WarcryPower",type="INC",value=5}},nil} -c["5% of Damage from Hits is taken from your Damageable Companion's Life before you"]={{[1]={flags=0,keywordFlags=0,name="Damage",type="BASE",value=5}}," from Hits is taken from your Damageable Companion's Life before you "} -c["5% of Damage from Hits is taken from your Damageable Companion's Life before you 20% increased Armour, Evasion and Energy Shield while your Companion is in your Presence"]={{[1]={[1]={type="Condition",var="CompanionInPresence"},flags=0,keywordFlags=0,name="Damage",type="BASE",value=5}}," from Hits is taken from your Damageable Companion's Life before you 20% increased Armour, Evasion and Energy Shield "} +c["5% of Damage from Hits is taken from your Damageable Companion's Life before you"]={{[1]={flags=0,keywordFlags=0,name="takenFromCompanionBeforeYou",type="BASE",value=5}},nil} c["5% of Damage taken Recouped as Life"]={{[1]={flags=0,keywordFlags=0,name="LifeRecoup",type="BASE",value=5}},nil} c["5% of Damage taken bypasses Energy Shield"]={{[1]={flags=0,keywordFlags=0,name="PhysicalEnergyShieldBypass",type="BASE",value=5},[2]={flags=0,keywordFlags=0,name="LightningEnergyShieldBypass",type="BASE",value=5},[3]={flags=0,keywordFlags=0,name="ColdEnergyShieldBypass",type="BASE",value=5},[4]={flags=0,keywordFlags=0,name="FireEnergyShieldBypass",type="BASE",value=5},[5]={flags=0,keywordFlags=0,name="ChaosEnergyShieldBypass",type="BASE",value=5}},nil} c["5% of Maximum Life Converted to Energy Shield"]={{[1]={flags=0,keywordFlags=0,name="LifeConvertToEnergyShield",type="BASE",value=5}},nil} diff --git a/src/Data/SkillStatMap.lua b/src/Data/SkillStatMap.lua index 7a31555da..d63211a95 100644 --- a/src/Data/SkillStatMap.lua +++ b/src/Data/SkillStatMap.lua @@ -2521,6 +2521,9 @@ return { ["companions_are_gigantic"] = { mod("MinionModifier", "LIST", { mod = flag("Gigantic") }), }, +["companion_takes_%_damage_before_you_from_support"] = { + mod("takenFromCompanionBeforeYou", "BASE", nil, 0, 0, { type = "GlobalEffect", effectType = "Buff", unscalable = true }), +}, ["minion_damage_+%_final_per_different_elemental_ailment_on_target"] = { mod("MinionModifier", "LIST", { mod = mod("Damage", "MORE", nil, 0, 0, { type = "ActorCondition", actor = "enemy", var = "Electrocuted" }) }), mod("MinionModifier", "LIST", { mod = mod("Damage", "MORE", nil, 0, 0, { type = "ActorCondition", actor = "enemy", var = "Frozen" }) }), diff --git a/src/Modules/CalcDefence.lua b/src/Modules/CalcDefence.lua index 93cc61d98..e473ae811 100644 --- a/src/Modules/CalcDefence.lua +++ b/src/Modules/CalcDefence.lua @@ -481,6 +481,9 @@ function calcs.reducePoolsByDamage(poolTable, damageTable, actor) if output.TotalRadianceSentinelLife then alliesTakenBeforeYou["radianceSentinel"] = { remaining = output.TotalRadianceSentinelLife, percent = output.RadianceSentinelAllyDamageMitigation / 100 } end + if output.TotalCompanionLife then + alliesTakenBeforeYou["companion"] = { remaining = output.TotalCompanionLife, percent = output.CompanionAllyDamageMitigation / 100 } + end if output.AlliedEnergyShield then alliesTakenBeforeYou["soulLink"] = { remaining = output.AlliedEnergyShield, percent = output.SoulLinkMitigation / 100 } end @@ -698,6 +701,9 @@ local function incomingDamageBreakdown(breakdownTable, poolsRemaining, output) if output.TotalRadianceSentinelLife and output.TotalRadianceSentinelLife > 0 then t_insert(breakdownTable, s_format("\t%d "..colorCodes.GEM.."Total Sentinel of Radiance Life ^7(%d remaining)", output.TotalRadianceSentinelLife - poolsRemaining.AlliesTakenBeforeYou["radianceSentinel"].remaining, poolsRemaining.AlliesTakenBeforeYou["radianceSentinel"].remaining)) end + if output.TotalCompanionLife and output.TotalCompanionLife > 0 then + t_insert(breakdownTable, s_format("\t%d "..colorCodes.GEM.."Total Companion Life ^7(%d remaining)", output.TotalCompanionLife - poolsRemaining.AlliesTakenBeforeYou["companion"].remaining, poolsRemaining.AlliesTakenBeforeYou["companion"].remaining)) + end if output.AlliedEnergyShield and output.AlliedEnergyShield > 0 then t_insert(breakdownTable, s_format("\t%d "..colorCodes.GEM.."Total Allied Energy shield ^7(%d remaining)", output.AlliedEnergyShield - poolsRemaining.AlliesTakenBeforeYou["soulLink"].remaining, poolsRemaining.AlliesTakenBeforeYou["soulLink"].remaining)) end @@ -2921,6 +2927,31 @@ function calcs.buildDefenceEstimations(env, actor) output["TotalRadianceSentinelLife"] = modDB:Sum("BASE", nil, "TotalRadianceSentinelLife") end + -- from companions + local companionMitigation = modDB:Sum("BASE", nil, "takenFromCompanionBeforeYou") + local companionMitigationFromDeflected = modDB:Sum("BASE", nil, "takenFromCompanionBeforeYouFromDeflected") + output["CompanionAllyDamageMitigation"] = companionMitigation + companionMitigationFromDeflected * (output.DeflectChance or 0) / 100 + if output["CompanionAllyDamageMitigation"] ~= 0 then + output["TotalCompanionLife"] = modDB:Override(nil, "TotalCompanionLife") or modDB:Sum("BASE", nil, "TotalCompanionLife") + if breakdown then + breakdown["TotalCompanionLife"] = { } + if modDB:Override(nil, "TotalCompanionLife") then + t_insert(breakdown["TotalCompanionLife"], s_format("%d ^8(from config)", output["TotalCompanionLife"])) + else + for _, companion in ipairs(actor.companionLifeList or { }) do + t_insert(breakdown["TotalCompanionLife"], s_format("%d ^8(%s)", companion.life, companion.name)) + end + end + if companionMitigationFromDeflected ~= 0 then + breakdown["CompanionAllyDamageMitigation"] = { + s_format("%d%% ^8(taken from hits)", companionMitigation), + s_format("+ %d%% x %.2f ^8(taken from deflected hits, scaled by deflect chance)", companionMitigationFromDeflected, (output.DeflectChance or 0) / 100), + s_format("= %.1f%%", output["CompanionAllyDamageMitigation"]), + } + end + end + end + -- from Allied Energy Shield output["SoulLinkMitigation"] = modDB:Sum("BASE", nil, "TakenFromParentESBeforeYou") if output["SoulLinkMitigation"] ~= 0 then @@ -3023,6 +3054,9 @@ function calcs.buildDefenceEstimations(env, actor) if output.TotalRadianceSentinelLife then alliesTakenBeforeYou["radianceSentinel"] = { remaining = output.TotalRadianceSentinelLife, percent = output.RadianceSentinelAllyDamageMitigation / 100 } end + if output.TotalCompanionLife then + alliesTakenBeforeYou["companion"] = { remaining = output.TotalCompanionLife, percent = output.CompanionAllyDamageMitigation / 100 } + end if output.AlliedEnergyShield then alliesTakenBeforeYou["soulLink"] = { remaining = output.AlliedEnergyShield, percent = output.SoulLinkMitigation / 100 } end @@ -3588,6 +3622,15 @@ function calcs.buildDefenceEstimations(env, actor) local poolProtected = output["TotalRadianceSentinelLife"] / (output["RadianceSentinelAllyDamageMitigation"] / 100) * (1 - output["RadianceSentinelAllyDamageMitigation"] / 100) output[damageType.."TotalHitPool"] = m_max(output[damageType.."TotalHitPool"] - poolProtected, 0) + m_min(output[damageType.."TotalHitPool"], poolProtected) / (1 - output["RadianceSentinelAllyDamageMitigation"] / 100) end + -- companions + if output["TotalCompanionLife"] and output["TotalCompanionLife"] > 0 then + if output["CompanionAllyDamageMitigation"] >= 100 then + output[damageType.."TotalHitPool"] = output[damageType.."TotalHitPool"] + output["TotalCompanionLife"] + else + local poolProtected = output["TotalCompanionLife"] / (output["CompanionAllyDamageMitigation"] / 100) * (1 - output["CompanionAllyDamageMitigation"] / 100) + output[damageType.."TotalHitPool"] = m_max(output[damageType.."TotalHitPool"] - poolProtected, 0) + m_min(output[damageType.."TotalHitPool"], poolProtected) / (1 - output["CompanionAllyDamageMitigation"] / 100) + end + end -- soul link if output["AlliedEnergyShield"] and output["AlliedEnergyShield"] > 0 then local poolProtected = output["AlliedEnergyShield"] / (output["SoulLinkMitigation"] / 100) * (1 - output["SoulLinkMitigation"] / 100) diff --git a/src/Modules/CalcPerform.lua b/src/Modules/CalcPerform.lua index c903d6b35..3fa20d79d 100644 --- a/src/Modules/CalcPerform.lua +++ b/src/Modules/CalcPerform.lua @@ -1034,6 +1034,104 @@ function calcs.actionSpeedMod(actor) return actionSpeedMod end +-- Initialises a minion's modifier database with its base stats (life, defences, resists), +-- monster type mods, tamed beast mods and player-granted mods, for the given owning skill +local function initMinionModDB(env, activeSkill) + local modDB = env.modDB + local minion = activeSkill.minion + minion.modDB.multipliers["Level"] = minion.level + calcs.initModDB(env, minion.modDB) + local baseLife = minion.lifeTable[minion.level] * minion.minionData.life + if minion.hostile then + baseLife = baseLife * (env.data.mapLevelLifeMult[env.enemyLevel] or 1) + end + minion.modDB:NewMod("Life", "BASE", m_floor(baseLife), "Base") + if minion.minionData.energyShield then + minion.modDB:NewMod("LifeConvertToEnergyShield", "BASE", minion.minionData.energyShield * 100, "Base") + end + --Armour formula is math.floor((10 + 2 * level) * 1.067 ^ level) + minion.modDB:NewMod("Armour", "BASE", round(env.data.monsterArmourTable[minion.level] * (minion.minionData.armour or 1)), "Base") + --Evasion formula is math.floor((50 + 16 * level + 16 * level * (MonsterType.Evasion / 100)) * (1.0212 ^ level) + minion.modDB:NewMod("Evasion", "BASE", round(env.data.monsterEvasionTable[minion.level] * (minion.minionData.evasion or 1)), "Base") + if modDB:Flag(nil, "MinionAccuracyEqualsAccuracy") then + minion.modDB:NewMod("Accuracy", "BASE", calcLib.val(modDB, "Accuracy") + calcLib.val(modDB, "Dex") * (modDB:Override(nil, "DexAccBonusOverride") or data.misc.AccuracyPerDexBase), "Player") + else + -- Minions no longer need Accuracy as of patch 0.3.0 + minion.modDB:NewMod("CannotBeEvaded", "FLAG", 1, "Minion Attacks always hit") + end + minion.modDB:NewMod("CritMultiplier", "BASE", env.data.monsterConstants["base_critical_hit_damage_bonus"] + env.data.playerMinionIntrinsicStats["base_critical_hit_damage_bonus"], "Base") + minion.modDB:NewMod("FireResist", "BASE", minion.minionData.fireResist, "Base") + minion.modDB:NewMod("ColdResist", "BASE", minion.minionData.coldResist, "Base") + minion.modDB:NewMod("LightningResist", "BASE", minion.minionData.lightningResist, "Base") + minion.modDB:NewMod("ChaosResist", "BASE", minion.minionData.chaosResist, "Base") + minion.modDB:NewMod("ProjectileCount", "BASE", 1, "Base") + minion.modDB:NewMod("PhysicalHeavyStunBuildup", "MORE", data.monsterConstants["physical_hit_damage_stun_multiplier_+%_final_from_ot"], "Physical Damage") + minion.modDB:NewMod("EnemyHeavyStunBuildup", "MORE", data.monsterConstants["melee_hit_damage_stun_multiplier_+%_final_from_ot"], "Melee Damage", ModFlag.Melee) + minion.modDB:NewMod("Damage", "MORE", minion.hiddenDamageFixup * 100, "Hidden Level Scaling") + for _, mod in ipairs(minion.minionData.modList) do + minion.modDB:AddMod(mod) + end + for _, mod in ipairs(activeSkill.extraSkillModList) do + minion.modDB:AddMod(mod) + end + if env.talismanModList then + -- Adding mods provided by "Necromantic Talisman" + minion.modDB:AddList(env.talismanModList) + end + if env.theIronMass and minion.type == "RaisedSkeleton" then + minion.modDB:AddList(env.theIronMass) + end + if activeSkill.skillData.minionUseBowAndQuiver then + if env.player.weaponData1.type == "Bow" then + 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 + local quiverEffectMod = env.player.modDB:Sum("INC", nil, "EffectOfBonusesFromQuiver") / 100 + if quiverEffectMod > 0 then + for _, mod in ipairs(env.player.itemList["Weapon 2"].modList) do + local modCopy = copyTable(mod) + modCopy.source = "Many Sources:" .. tostring(quiverEffectMod * 100) .. "% Quiver Bonus Effect" + minion.modDB:ScaleAddMod(modCopy, quiverEffectMod) + end + end + end + end + if minion.itemSet or minion.uses then + for slotName, slot in pairs(env.build.itemsTab.slots) do + if minion.uses[slotName] then + local item + if minion.itemSet then + if slot.weaponSet == 1 and minion.itemSet.useSecondWeaponSet then + slotName = slotName .. " Swap" + end + item = env.build.itemsTab.items[minion.itemSet[slotName].selItemId] + else + item = env.player.itemList[slotName] + end + if item then + minion.itemList[slotName] = item + minion.modDB:AddList(item.modList or item.slotModList[slot.slotNum]) + end + end + end + end + if modDB:Flag(nil, "StrengthAddedToMinions") then + minion.modDB:NewMod("Str", "BASE", round(calcLib.val(modDB, "Str")), "Player") + end + if modDB:Flag(nil, "StrengthAddedToCompanions") and activeSkill.skillTypes[SkillType.Companion] then + minion.modDB:NewMod("Str", "BASE", round(calcLib.val(modDB, "Str")), "Sturdy Ally") + end + if modDB:Flag(nil, "HalfStrengthAddedToMinions") then + minion.modDB:NewMod("Str", "BASE", round(calcLib.val(modDB, "Str") * 0.5), "Player") + end + if modDB:Flag(nil, "DexterityAddedToMinions") then + minion.modDB:NewMod("Dex", "BASE", round(calcLib.val(modDB, "Dex")), "Dead can Dance") + end + if modDB:Flag(nil, "DexterityAddedToCompanions") and activeSkill.skillTypes[SkillType.Companion] then + minion.modDB:NewMod("Dex", "BASE", round(calcLib.val(modDB, "Dex")), "Tandem Assault") + end +end + -- Finalises the environment and performs the stat calculations: -- 1. Merges keystone modifiers -- 2. Initialises minion skills @@ -1075,97 +1173,7 @@ function calcs.perform(env, skipEHP) -- 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) - local baseLife = env.minion.lifeTable[env.minion.level] * env.minion.minionData.life - if env.minion.hostile then - baseLife = baseLife * (env.data.mapLevelLifeMult[env.enemyLevel] or 1) - end - env.minion.modDB:NewMod("Life", "BASE", m_floor(baseLife), "Base") - if env.minion.minionData.energyShield then - env.minion.modDB:NewMod("LifeConvertToEnergyShield", "BASE", env.minion.minionData.energyShield * 100, "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 - -- Minions no longer need Accuracy as of patch 0.3.0 - env.minion.modDB:NewMod("CannotBeEvaded", "FLAG", 1, "Minion Attacks always hit") - end - env.minion.modDB:NewMod("CritMultiplier", "BASE", env.data.monsterConstants["base_critical_hit_damage_bonus"] + env.data.playerMinionIntrinsicStats["base_critical_hit_damage_bonus"], "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("ProjectileCount", "BASE", 1, "Base") - env.minion.modDB:NewMod("PhysicalHeavyStunBuildup", "MORE", data.monsterConstants["physical_hit_damage_stun_multiplier_+%_final_from_ot"], "Physical Damage") - env.minion.modDB:NewMod("EnemyHeavyStunBuildup", "MORE", data.monsterConstants["melee_hit_damage_stun_multiplier_+%_final_from_ot"], "Melee Damage", ModFlag.Melee) - env.minion.modDB:NewMod("Damage", "MORE", env.minion.hiddenDamageFixup * 100, "Hidden Level Scaling") - 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.talismanModList then - -- Adding mods provided by "Necromantic Talisman" - env.minion.modDB:AddList(env.talismanModList) - 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 - local quiverEffectMod = env.player.modDB:Sum("INC", nil, "EffectOfBonusesFromQuiver") / 100 - if quiverEffectMod > 0 then - for _, mod in ipairs(env.player.itemList["Weapon 2"].modList) do - local modCopy = copyTable(mod) - modCopy.source = "Many Sources:" .. tostring(quiverEffectMod * 100) .. "% Quiver Bonus Effect" - env.minion.modDB:ScaleAddMod(modCopy, quiverEffectMod) - end - end - 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, "StrengthAddedToCompanions") and env.player.mainSkill.skillTypes[SkillType.Companion] then - env.minion.modDB:NewMod("Str", "BASE", round(calcLib.val(modDB, "Str")), "Sturdy Ally") - end - if modDB:Flag(nil, "HalfStrengthAddedToMinions") then - env.minion.modDB:NewMod("Str", "BASE", round(calcLib.val(modDB, "Str") * 0.5), "Player") - end - if modDB:Flag(nil, "DexterityAddedToMinions") then - env.minion.modDB:NewMod("Dex", "BASE", round(calcLib.val(modDB, "Dex")), "Dead can Dance") - end - if modDB:Flag(nil, "DexterityAddedToCompanions") and env.player.mainSkill.skillTypes[SkillType.Companion] then - env.minion.modDB:NewMod("Dex", "BASE", round(calcLib.val(modDB, "Dex")), "Tandem Assault") - end + initMinionModDB(env, env.player.mainSkill) end if env.talismanModList then -- Accounting for "Necromantic Talisman" @@ -3294,6 +3302,52 @@ function calcs.perform(env, skipEHP) enemyDB:NewMod("DamageTaken", "INC", enemyDB:Sum("INC", nil, "DamageTakenConsecratedGround") * effect, "Consecrated Ground") end + -- Total life of damageable companions, for "% of Damage from Hits is taken from your Companion's Life before you" + if modDB:HasMod("BASE", nil, "takenFromCompanionBeforeYou", "takenFromCompanionBeforeYouFromDeflected") and not modDB:Override(nil, "TotalCompanionLife") then + local totalCompanionLife = 0 + local companionLifeList = { } + local seenMinions = { } + for _, activeSkill in ipairs(env.player.activeSkillList) do + local skillFlags = env.mode == "CALCS" and activeSkill.activeEffect.statSetCalcs.skillFlags or activeSkill.activeEffect.statSet.skillFlags + local minion = activeSkill.minion + if minion and not seenMinions[minion] and not skillFlags.disable + and activeSkill.skillTypes[SkillType.Companion] + and not activeSkill.skillTypes[SkillType.MinionsAreUndamagable] then + seenMinions[minion] = true + if minion ~= env.minion then + minion.output = minion.output or { } + initMinionModDB(env, activeSkill) + for _, value in ipairs(activeSkill.skillModList:List(activeSkill.skillCfg, "MinionModifier")) do + if not value.type or minion.type == value.type then + minion.modDB:AddMod(value.mod) + end + end + for _, name in ipairs(minion.modDB:List(nil, "Keystone")) do + if env.spec.tree.keystoneMap[name] then + minion.modDB:AddList(env.spec.tree.keystoneMap[name].modList) + end + end + for _, modList in pairs(buffs) do + for _, value in ipairs(modList:List(activeSkill.skillCfg, "MinionModifier")) do + if not value.type or minion.type == value.type then + minion.modDB:AddMod(value.mod) + end + end + end + doActorAttribsConditions(env, minion) + end + calcs.doActorLifeManaSpirit(minion, true) + totalCompanionLife = totalCompanionLife + minion.output.Life + -- Companion gems share a single granted effect ("Companion: {0}") whose name is mutated + -- globally for display, so derive the entry name from this skill's own minion + local companionName = minion.minionData and minion.minionData.name or activeSkill.activeEffect.grantedEffect.name + t_insert(companionLifeList, { name = companionName, life = minion.output.Life }) + end + end + modDB:NewMod("TotalCompanionLife", "BASE", totalCompanionLife, "Companions") + env.player.companionLifeList = companionLifeList + end + -- Defence/offence calculations calcs.defence(env, env.player) local function getSkillExposureEffect(source, element) diff --git a/src/Modules/CalcSections.lua b/src/Modules/CalcSections.lua index 4872f633a..c60f3e03a 100644 --- a/src/Modules/CalcSections.lua +++ b/src/Modules/CalcSections.lua @@ -2370,6 +2370,12 @@ return { { modName = { "TotalVaalRejuvenationTotemLife", "takenFromVaalRejuvenationTotemsBeforeYou", "takenFromTotemsBeforeYou" } }, }, }, + { label = "Companion Ally", haveOutput = "TotalCompanionLife", + { format = "{0:output:TotalCompanionLife}", + { breakdown = "TotalCompanionLife" }, + { modName = { "TotalCompanionLife", "takenFromCompanionBeforeYou", "takenFromCompanionBeforeYouFromDeflected" } }, + }, + }, { label = "Soul Link", haveOutput = "AlliedEnergyShield", { format = "{0:output:AlliedEnergyShield}", { breakdown = "AlliedEnergyShield" }, diff --git a/src/Modules/ConfigOptions.lua b/src/Modules/ConfigOptions.lua index ffabe09e1..adfe237a6 100644 --- a/src/Modules/ConfigOptions.lua +++ b/src/Modules/ConfigOptions.lua @@ -780,7 +780,7 @@ Huge sets the radius to 11. { var = "enemyRadius", type = "countAllowZero", label = "Enemy radius:", ifSkill = { "Seismic Trap", "Lightning Spire Trap", "Explosive Trap" }, includeTransfigured = true, tooltip = "Configure the radius of an enemy hitbox to calculate some area overlapping (shotgunning) effects.", apply = function(val, modList, enemyModList) modList:NewMod("EnemyRadius", "OVERRIDE", m_max(val, 1), "Config") end }, - { var = "TotalSpectreLife", type = "countAllowZero", label = "Total Spectre Life:", ifMod = "takenFromSpectresBeforeYou", ifSkill = "Raise Spectre", includeTransfigured = true, tooltip = "The total life of your Spectres that can be taken before yours (used by jinxed juju)", apply = function(val, modList, enemyModList) + { var = "TotalSpectreLife", type = "countAllowZero", label = "Total Spectre Life:", ifMod = "takenFromSpectresBeforeYou", tooltip = "The total life of your Spectres that can be taken before yours", apply = function(val, modList, enemyModList) modList:NewMod("TotalSpectreLife", "BASE", val, "Config") end }, { var = "TotalTotemLife", type = "countAllowZero", label = "Total Totem Life:", ifOption = "conditionHaveTotem", ifMod = "takenFromTotemsBeforeYou", tooltip = "The total life of your Totems (excluding Vaal Rejuvenation Totem) that can be taken before yours (used by totem mastery)", apply = function(val, modList, enemyModList) @@ -792,6 +792,9 @@ Huge sets the radius to 11. { var = "TotalVaalRejuvenationTotemLife", type = "countAllowZero", label = "Total Vaal Rejuvenation Totem Life:", ifSkill = { "Vaal Rejuvenation Totem" }, ifMod = "takenFromVaalRejuvenationTotemsBeforeYou", tooltip = "The total life of your Vaal Rejuvenation Totems that can be taken before yours", apply = function(val, modList, enemyModList) modList:NewMod("TotalVaalRejuvenationTotemLife", "BASE", val, "Config") end }, + { var = "TotalCompanionLife", type = "countAllowZero", label = "Total Companion Life:", ifMod = { "takenFromCompanionBeforeYou", "takenFromCompanionBeforeYouFromDeflected" }, tooltip = "Overrides the automatically calculated total life of your damageable Companions\nthat can be taken before yours (e.g. Starkonja's Head, Loyalty)", apply = function(val, modList, enemyModList) + modList:NewMod("TotalCompanionLife", "OVERRIDE", val, "Config") + end }, -- Section: Combat options { section = "When In Combat", col = 1 }, { var = "usePowerCharges", type = "check", label = "Do you use Power Charges?", apply = function(val, modList, enemyModList) diff --git a/src/Modules/ModParser.lua b/src/Modules/ModParser.lua index 00f4268b7..6a9c04131 100644 --- a/src/Modules/ModParser.lua +++ b/src/Modules/ModParser.lua @@ -5337,6 +5337,8 @@ local specialModList = { } end, ["(%d+)%% of damage from hits is taken from your spectres' life before you"] = function(num) return { mod("takenFromSpectresBeforeYou", "BASE", num) } end, ["(%d+)%% of damage from hits is taken from your nearest totem's life before you"] = function(num) return { mod("takenFromTotemsBeforeYou", "BASE", num, { type = "Condition", var = "HaveTotem" }) } end, + ["(%d+)%% of damage from hits is taken from your d?a?m?a?g?e?a?b?l?e? ?companion's life before you"] = function(num) return { mod("takenFromCompanionBeforeYou", "BASE", num) } end, + ["(%d+)%% of damage from deflected hits is taken from damageable companion's life before you"] = function(num) return { mod("takenFromCompanionBeforeYouFromDeflected", "BASE", num) } end, ["(%a+) resistance cannot be penetrated"] = function(_, res) return { flag("EnemyCannotPen"..(res:gsub("^%l", string.upper)).."Resistance") } end, ["your base energy shield recharge delay is (%d+) seconds"] = function(num) return { mod("EnergyShieldRechargeBase", "OVERRIDE", num), } end, -- Knockback