Files
PathOfBuilding/Modules/CalcDefence.lua
2020-11-08 20:35:00 +01:00

1131 lines
51 KiB
Lua

-- Path of Building
--
-- Module: Calc Defence
-- Performs 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 s_format = string.format
local tempTable1 = { }
local isElemental = { Fire = true, Cold = true, Lightning = true }
-- List of all damage types, ordered according to the conversion sequence
local dmgTypeList = {"Physical", "Lightning", "Cold", "Fire", "Chaos"}
local resistTypeList = { "Fire", "Cold", "Lightning", "Chaos" }
-- Calculate hit chance
function calcs.hitChance(evasion, accuracy)
if accuracy < 0 then
return 5
end
local rawChance = accuracy / (accuracy + (evasion / 4) ^ 0.8) * 115
return m_max(m_min(round(rawChance), 100), 5)
end
-- Calculate physical damage reduction from armour, float
function calcs.armourReductionF(armour, raw)
return (armour / (armour + raw * 10) * 100)
end
-- Calculate physical damage reduction from armour, int
function calcs.armourReduction(armour, raw)
return round(calcs.armourReductionF(armour, raw))
end
--- Calculates Damage Reduction from Armour
---@param armour number
---@param damage number
---@param doubleChance number @Chance to Defend with Double Armour
---@return number @Damage Reduction
function calcs.armourReductionDouble(armour, damage, doubleChance)
return calcs.armourReduction(armour, damage) * (1 - doubleChance) + calcs.armourReduction(armour * 2, damage) * doubleChance
end
function calcs.actionSpeedMod(actor)
local modDB = actor.modDB
local actionSpeedMod = 1 + (m_max(-data.misc.TemporalChainsEffectCap, modDB:Sum("INC", nil, "TemporalChainsActionSpeed")) + modDB:Sum("INC", nil, "ActionSpeed")) / 100
if modDB:Flag(nil, "ActionSpeedCannotBeBelowBase") then
actionSpeedMod = m_max(1, actionSpeedMod)
end
return actionSpeedMod
end
-- Performs all defensive calculations
function calcs.defence(env, actor)
local modDB = actor.modDB
local enemyDB = actor.enemy.modDB
local output = actor.output
local breakdown = actor.breakdown
local condList = modDB.conditions
-- Action Speed
output.ActionSpeedMod = calcs.actionSpeedMod(actor)
-- Resistances
output.DamageReductionMax = modDB:Override(nil, "DamageReductionMax") or data.misc.DamageReductionCap
output.PhysicalResist = m_min(output.DamageReductionMax, modDB:Sum("BASE", nil, "PhysicalDamageReduction"))
output.PhysicalResistWhenHit = m_min(output.DamageReductionMax, output.PhysicalResist + modDB:Sum("BASE", nil, "PhysicalDamageReductionWhenHit"))
for _, elem in ipairs(resistTypeList) do
local max, total
if elem == "Chaos" and modDB:Flag(nil, "ChaosInoculation") then
max = 100
total = 100
else
max = modDB:Override(nil, elem.."ResistMax") or m_min(data.misc.MaxResistCap, modDB:Sum("BASE", nil, elem.."ResistMax", isElemental[elem] and "ElementalResistMax"))
total = modDB:Override(nil, elem.."Resist")
if not total then
local base = modDB:Sum("BASE", nil, elem.."Resist", isElemental[elem] and "ElementalResist")
total = base * calcLib.mod(modDB, nil, elem.."Resist", isElemental[elem] and "ElementalResist")
end
end
local final = m_min(total, max)
output[elem.."Resist"] = final
output[elem.."ResistTotal"] = total
output[elem.."ResistOverCap"] = m_max(0, total - max)
output[elem.."ResistOver75"] = m_max(0, final - 75)
output["Missing"..elem.."Resist"] = m_max(0, max - final)
if breakdown then
breakdown[elem.."Resist"] = {
"Max: "..max.."%",
"Total: "..total.."%",
}
end
end
-- Primary defences: Energy shield, evasion and armour
do
local ironReflexes = modDB:Flag(nil, "IronReflexes")
local energyShield = 0
local armour = 0
local evasion = 0
if breakdown then
breakdown.EnergyShield = { slots = { } }
breakdown.Armour = { slots = { } }
breakdown.Evasion = { slots = { } }
end
local energyShieldBase, armourBase, evasionBase
local gearEnergyShield = 0
local gearArmour = 0
local gearEvasion = 0
local slotCfg = wipeTable(tempTable1)
for _, slot in pairs({"Helmet","Body Armour","Gloves","Boots","Weapon 2","Weapon 3"}) do
local armourData = actor.itemList[slot] and actor.itemList[slot].armourData
if armourData then
slotCfg.slotName = slot
energyShieldBase = armourData.EnergyShield or 0
if energyShieldBase > 0 then
output["EnergyShieldOn"..slot] = energyShieldBase
energyShield = energyShield + energyShieldBase * calcLib.mod(modDB, slotCfg, "EnergyShield", "Defences")
gearEnergyShield = gearEnergyShield + energyShieldBase
if breakdown then
breakdown.slot(slot, nil, slotCfg, energyShieldBase, nil, "EnergyShield", "Defences")
end
end
armourBase = armourData.Armour or 0
if armourBase > 0 then
output["ArmourOn"..slot] = armourBase
if slot == "Body Armour" and modDB:Flag(nil, "Unbreakable") then
armourBase = armourBase * 2
end
armour = armour + armourBase * calcLib.mod(modDB, slotCfg, "Armour", "ArmourAndEvasion", "Defences")
gearArmour = gearArmour + armourBase
if breakdown then
breakdown.slot(slot, nil, slotCfg, armourBase, nil, "Armour", "ArmourAndEvasion", "Defences")
end
end
evasionBase = armourData.Evasion or 0
if evasionBase > 0 then
output["EvasionOn"..slot] = evasionBase
if ironReflexes then
armour = armour + evasionBase * calcLib.mod(modDB, slotCfg, "Armour", "Evasion", "ArmourAndEvasion", "Defences")
gearArmour = gearArmour + evasionBase
if breakdown then
breakdown.slot(slot, nil, slotCfg, evasionBase, nil, "Armour", "Evasion", "ArmourAndEvasion", "Defences")
end
else
evasion = evasion + evasionBase * calcLib.mod(modDB, slotCfg, "Evasion", "ArmourAndEvasion", "Defences")
gearEvasion = gearEvasion + evasionBase
if breakdown then
breakdown.slot(slot, nil, slotCfg, evasionBase, nil, "Evasion", "ArmourAndEvasion", "Defences")
end
end
end
end
end
energyShieldBase = modDB:Sum("BASE", nil, "EnergyShield")
if energyShieldBase > 0 then
energyShield = energyShield + energyShieldBase * calcLib.mod(modDB, nil, "EnergyShield", "Defences")
if breakdown then
breakdown.slot("Global", nil, nil, energyShieldBase, nil, "EnergyShield", "Defences")
end
end
armourBase = modDB:Sum("BASE", nil, "Armour", "ArmourAndEvasion")
if armourBase > 0 then
armour = armour + armourBase * calcLib.mod(modDB, nil, "Armour", "ArmourAndEvasion", "Defences")
if breakdown then
breakdown.slot("Global", nil, nil, armourBase, nil, "Armour", "ArmourAndEvasion", "Defences")
end
end
evasionBase = modDB:Sum("BASE", nil, "Evasion", "ArmourAndEvasion")
if evasionBase > 0 then
if ironReflexes then
armour = armour + evasionBase * calcLib.mod(modDB, nil, "Armour", "Evasion", "ArmourAndEvasion", "Defences")
if breakdown then
breakdown.slot("Conversion", "Evasion to Armour", nil, evasionBase, nil, "Armour", "Evasion", "ArmourAndEvasion", "Defences")
end
else
evasion = evasion + evasionBase * calcLib.mod(modDB, nil, "Evasion", "ArmourAndEvasion", "Defences")
if breakdown then
breakdown.slot("Global", nil, nil, evasionBase, nil, "Evasion", "ArmourAndEvasion", "Defences")
end
end
end
local convManaToArmour = modDB:Sum("BASE", nil, "ManaConvertToArmour")
if convManaToArmour > 0 then
armourBase = 2 * modDB:Sum("BASE", nil, "Mana") * convManaToArmour / 100
local total = armourBase * calcLib.mod(modDB, nil, "Mana", "Armour", "ArmourAndEvasion", "Defences")
armour = armour + total
if breakdown then
breakdown.slot("Conversion", "Mana to Armour", nil, armourBase, total, "Armour", "ArmourAndEvasion", "Defences", "Mana")
end
end
local convManaToES = modDB:Sum("BASE", nil, "ManaGainAsEnergyShield")
if convManaToES > 0 then
energyShieldBase = modDB:Sum("BASE", nil, "Mana") * convManaToES / 100
energyShield = energyShield + energyShieldBase * calcLib.mod(modDB, nil, "Mana", "EnergyShield", "Defences")
if breakdown then
breakdown.slot("Conversion", "Mana to Energy Shield", nil, energyShieldBase, nil, "EnergyShield", "Defences", "Mana")
end
end
local convLifeToArmour = modDB:Sum("BASE", nil, "LifeGainAsArmour")
if convLifeToArmour > 0 then
armourBase = modDB:Sum("BASE", nil, "Life") * convLifeToArmour / 100
local total
if modDB:Flag(nil, "ChaosInoculation") then
total = 1
else
total = armourBase * calcLib.mod(modDB, nil, "Life", "Armour", "ArmourAndEvasion", "Defences")
end
armour = armour + total
if breakdown then
breakdown.slot("Conversion", "Life to Armour", nil, armourBase, total, "Armour", "ArmourAndEvasion", "Defences", "Life")
end
end
local convLifeToES = modDB:Sum("BASE", nil, "LifeConvertToEnergyShield", "LifeGainAsEnergyShield")
if convLifeToES > 0 then
energyShieldBase = modDB:Sum("BASE", nil, "Life") * convLifeToES / 100
local total
if modDB:Flag(nil, "ChaosInoculation") then
total = 1
else
total = energyShieldBase * calcLib.mod(modDB, nil, "Life", "EnergyShield", "Defences")
end
energyShield = energyShield + total
if breakdown then
breakdown.slot("Conversion", "Life to Energy Shield", nil, energyShieldBase, total, "EnergyShield", "Defences", "Life")
end
end
output.EnergyShield = modDB:Override(nil, "EnergyShield") or m_max(round(energyShield), 0)
output.Armour = m_max(round(armour), 0)
output.DoubleArmourChance = m_min(modDB:Sum("BASE", nil, "DoubleArmourChance"), 100)
output.Evasion = m_max(round(evasion), 0)
output.LowestOfArmourAndEvasion = m_min(output.Armour, output.Evasion)
output["Gear:EnergyShield"] = gearEnergyShield
output["Gear:Armour"] = gearArmour
output["Gear:Evasion"] = gearEvasion
if modDB:Flag(nil, "CannotEvade") then
output.EvadeChance = 0
output.MeleeEvadeChance = 0
output.ProjectileEvadeChance = 0
else
local enemyAccuracy = round(calcLib.val(enemyDB, "Accuracy"))
output.EvadeChance = 100 - (calcs.hitChance(output.Evasion, enemyAccuracy) - modDB:Sum("BASE", nil, "EvadeChance")) * calcLib.mod(enemyDB, nil, "HitChance")
output.MeleeEvadeChance = m_max(0, m_min(data.misc.EvadeChanceCap, output.EvadeChance * calcLib.mod(modDB, nil, "EvadeChance", "MeleeEvadeChance")))
output.ProjectileEvadeChance = m_max(0, m_min(data.misc.EvadeChanceCap, output.EvadeChance * calcLib.mod(modDB, nil, "EvadeChance", "ProjectileEvadeChance")))
-- Condition for displayng evade chance only if melee or projectile evade chance have the same values
if output.MeleeEvadeChance ~= output.ProjectileEvadeChance then
output.splitEvade = true
else
output.EvadeChance = output.MeleeEvadeChance
output.dontSplitEvade = true
end
if breakdown then
breakdown.EvadeChance = {
s_format("Enemy level: %d ^8(%s the Configuration tab)", env.enemyLevel, env.configInput.enemyLevel and "overridden from" or "can be overridden in"),
s_format("Average enemy accuracy: %d", enemyAccuracy),
s_format("Approximate evade chance: %d%%", output.EvadeChance),
}
breakdown.MeleeEvadeChance = {
s_format("Enemy level: %d ^8(%s the Configuration tab)", env.enemyLevel, env.configInput.enemyLevel and "overridden from" or "can be overridden in"),
s_format("Average enemy accuracy: %d", enemyAccuracy),
s_format("Approximate melee evade chance: %d%%", output.MeleeEvadeChance),
}
breakdown.ProjectileEvadeChance = {
s_format("Enemy level: %d ^8(%s the Configuration tab)", env.enemyLevel, env.configInput.enemyLevel and "overridden from" or "can be overridden in"),
s_format("Average enemy accuracy: %d", enemyAccuracy),
s_format("Approximate projectile evade chance: %d%%", output.ProjectileEvadeChance),
}
end
end
end
-- Block
output.BlockChanceMax = modDB:Sum("BASE", nil, "BlockChanceMax")
local baseBlockChance = 0
if actor.itemList["Weapon 2"] and actor.itemList["Weapon 2"].armourData then
baseBlockChance = baseBlockChance + actor.itemList["Weapon 2"].armourData.BlockChance
end
if actor.itemList["Weapon 3"] and actor.itemList["Weapon 3"].armourData then
baseBlockChance = baseBlockChance + actor.itemList["Weapon 3"].armourData.BlockChance
end
output.ShieldBlockChance = baseBlockChance
if modDB:Flag(nil, "MaxBlockIfNotBlockedRecently") then
output.BlockChance = output.BlockChanceMax
else
output.BlockChance = m_min((baseBlockChance + modDB:Sum("BASE", nil, "BlockChance")) * calcLib.mod(modDB, nil, "BlockChance"), output.BlockChanceMax)
end
output.ProjectileBlockChance = m_min(output.BlockChance + modDB:Sum("BASE", nil, "ProjectileBlockChance") * calcLib.mod(modDB, nil, "BlockChance"), output.BlockChanceMax)
if modDB:Flag(nil, "SpellBlockChanceMaxIsBlockChanceMax") then
output.SpellBlockChanceMax = output.BlockChanceMax
else
output.SpellBlockChanceMax = modDB:Sum("BASE", nil, "SpellBlockChanceMax")
end
if modDB:Flag(nil, "SpellBlockChanceIsBlockChance") then
output.SpellBlockChance = output.BlockChance
output.SpellProjectileBlockChance = output.ProjectileBlockChance
else
output.SpellBlockChance = m_min(modDB:Sum("BASE", nil, "SpellBlockChance") * calcLib.mod(modDB, nil, "SpellBlockChance"), output.SpellBlockChanceMax)
output.SpellProjectileBlockChance = output.SpellBlockChance
end
if breakdown then
breakdown.BlockChance = breakdown.simple(baseBlockChance, nil, output.BlockChance, "BlockChance")
breakdown.SpellBlockChance = breakdown.simple(0, nil, output.SpellBlockChance, "SpellBlockChance")
end
if modDB:Flag(nil, "CannotBlockAttacks") then
output.BlockChance = 0
output.ProjectileBlockChance = 0
end
if modDB:Flag(nil, "CannotBlockSpells") then
output.SpellBlockChance = 0
output.SpellProjectileBlockChance = 0
end
output.AverageBlockChance = (output.BlockChance + output.ProjectileBlockChance + output.SpellBlockChance + output.SpellProjectileBlockChance) / 4
output.BlockEffect = modDB:Sum("BASE", nil, "BlockEffect")
if output.BlockEffect == 0 then
output.BlockEffect = 100
else
output.ShowBlockEffect = true
end
output.LifeOnBlock = modDB:Sum("BASE", nil, "LifeOnBlock")
output.ManaOnBlock = modDB:Sum("BASE", nil, "ManaOnBlock")
output.EnergyShieldOnBlock = modDB:Sum("BASE", nil, "EnergyShieldOnBlock")
-- Dodge
output.AttackDodgeChance = m_min(modDB:Sum("BASE", nil, "AttackDodgeChance"), data.misc.DodgeChanceCap)
output.SpellDodgeChance = m_min(modDB:Sum("BASE", nil, "SpellDodgeChance"), data.misc.DodgeChanceCap)
if env.mode_effective and modDB:Flag(nil, "DodgeChanceIsUnlucky") then
output.AttackDodgeChance = output.AttackDodgeChance / 100 * output.AttackDodgeChance
output.SpellDodgeChance = output.SpellDodgeChance / 100 * output.SpellDodgeChance
end
-- Recovery modifiers
output.LifeRecoveryRateMod = calcLib.mod(modDB, nil, "LifeRecoveryRate")
output.ManaRecoveryRateMod = calcLib.mod(modDB, nil, "ManaRecoveryRate")
output.EnergyShieldRecoveryRateMod = calcLib.mod(modDB, nil, "EnergyShieldRecoveryRate")
-- Leech caps
output.MaxLifeLeechInstance = output.Life * calcLib.val(modDB, "MaxLifeLeechInstance") / 100
output.MaxLifeLeechRate = output.Life * calcLib.val(modDB, "MaxLifeLeechRate") / 100
if breakdown then
breakdown.MaxLifeLeechRate = {
s_format("%d ^8(maximum life)", output.Life),
s_format("x %d%% ^8(percentage of life to maximum leech rate)", calcLib.val(modDB, "MaxLifeLeechRate")),
s_format("= %.1f", output.MaxLifeLeechRate)
}
end
output.MaxEnergyShieldLeechInstance = output.EnergyShield * calcLib.val(modDB, "MaxEnergyShieldLeechInstance") / 100
output.MaxEnergyShieldLeechRate = output.EnergyShield * calcLib.val(modDB, "MaxEnergyShieldLeechRate") / 100
if breakdown then
breakdown.MaxEnergyShieldLeechRate = {
s_format("%d ^8(maximum energy shield)", output.EnergyShield),
s_format("x %d%% ^8(percentage of energy shield to maximum leech rate)", calcLib.val(modDB, "MaxEnergyShieldLeechRate")),
s_format("= %.1f", output.MaxEnergyShieldLeechRate)
}
end
output.MaxManaLeechInstance = output.Mana * calcLib.val(modDB, "MaxManaLeechInstance") / 100
output.MaxManaLeechRate = output.Mana * calcLib.val(modDB, "MaxManaLeechRate") / 100
if breakdown then
breakdown.MaxManaLeechRate = {
s_format("%d ^8(maximum mana)", output.Mana),
s_format("x %d%% ^8(percentage of mana to maximum leech rate)", modDB:Sum("BASE", nil, "MaxManaLeechRate")),
s_format("= %.1f", output.MaxManaLeechRate)
}
end
-- Mana, life, energy shield, and rage regen
if modDB:Flag(nil, "NoManaRegen") then
output.ManaRegen = 0
else
local base = modDB:Sum("BASE", nil, "ManaRegen") + output.Mana * modDB:Sum("BASE", nil, "ManaRegenPercent") / 100
output.ManaRegenInc = modDB:Sum("INC", nil, "ManaRegen")
local more = modDB:More(nil, "ManaRegen")
if modDB:Flag(nil, "ManaRegenToRageRegen") then
output.ManaRegenInc = 0
end
local regen = base * (1 + output.ManaRegenInc/100) * more
local regenRate = round(regen * output.ManaRecoveryRateMod, 1)
local degen = modDB:Sum("BASE", nil, "ManaDegen")
output.ManaRegen = regenRate - degen
if breakdown then
breakdown.ManaRegen = { }
breakdown.multiChain(breakdown.ManaRegen, {
label = "Mana Regeneration:",
base = s_format("%.1f ^8(base)", base),
{ "%.2f ^8(increased/reduced)", 1 + output.ManaRegenInc/100 },
{ "%.2f ^8(more/less)", more },
total = s_format("= %.1f ^8per second", regen),
})
breakdown.multiChain(breakdown.ManaRegen, {
label = "Effective Mana Regeneration:",
base = s_format("%.1f", regen),
{ "%.2f ^8(recovery rate modifier)", output.ManaRecoveryRateMod },
total = s_format("= %.1f ^8per second", regenRate),
})
if degen ~= 0 then
t_insert(breakdown.ManaRegen, s_format("- %d", degen))
t_insert(breakdown.ManaRegen, s_format("= %.1f ^8per second", output.ManaRegen))
end
end
end
if modDB:Flag(nil, "NoLifeRegen") then
output.LifeRegen = 0
elseif modDB:Flag(nil, "ZealotsOath") then
output.LifeRegen = 0
local lifeBase = modDB:Sum("BASE", nil, "LifeRegen")
if lifeBase > 0 then
modDB:NewMod("EnergyShieldRegen", "BASE", lifeBase, "Zealot's Oath")
end
local lifePercent = modDB:Sum("BASE", nil, "LifeRegenPercent")
if lifePercent > 0 then
modDB:NewMod("EnergyShieldRegenPercent", "BASE", lifePercent, "Zealot's Oath")
end
else
local lifeBase = modDB:Sum("BASE", nil, "LifeRegen")
local lifePercent = modDB:Sum("BASE", nil, "LifeRegenPercent")
if lifePercent > 0 then
lifeBase = lifeBase + output.Life * lifePercent / 100
end
if lifeBase > 0 then
output.LifeRegen = lifeBase * output.LifeRecoveryRateMod * (1 + modDB:Sum("MORE", nil, "LifeRegen") / 100)
else
output.LifeRegen = 0
end
end
output.LifeRegen = output.LifeRegen - modDB:Sum("BASE", nil, "LifeDegen") + modDB:Sum("BASE", nil, "LifeRecovery") * output.LifeRecoveryRateMod
output.LifeRegenPercent = round(output.LifeRegen / output.Life * 100, 1)
if modDB:Flag(nil, "NoEnergyShieldRegen") then
output.EnergyShieldRegen = 0 - modDB:Sum("BASE", nil, "EnergyShieldDegen")
output.EnergyShieldRegenPercent = round(output.EnergyShieldRegen / output.EnergyShield * 100, 1)
else
local esBase = modDB:Sum("BASE", nil, "EnergyShieldRegen")
local esPercent = modDB:Sum("BASE", nil, "EnergyShieldRegenPercent")
if esPercent > 0 then
esBase = esBase + output.EnergyShield * esPercent / 100
end
if esBase > 0 then
output.EnergyShieldRegen = esBase * output.EnergyShieldRecoveryRateMod * calcLib.mod(modDB, nil, "EnergyShieldRegen") - modDB:Sum("BASE", nil, "EnergyShieldDegen")
output.EnergyShieldRegenPercent = round(output.EnergyShieldRegen / output.EnergyShield * 100, 1)
else
output.EnergyShieldRegen = 0 - modDB:Sum("BASE", nil, "EnergyShieldDegen")
end
end
if modDB:Sum("BASE", nil, "RageRegen") > 0 then
modDB:NewMod("Condition:CanGainRage", "FLAG", true, "RageRegen")
modDB:NewMod("Dummy", "DUMMY", 1, "RageRegen", 0, { type = "Condition", var = "CanGainRage" }) -- Make the Configuration option appear
local base = modDB:Sum("BASE", nil, "RageRegen")
if modDB:Flag(nil, "ManaRegenToRageRegen") then
local mana = modDB:Sum("INC", nil, "ManaRegen")
modDB:NewMod("RageRegen", "INC", mana, "Mana Regen to Rage Regen")
end
local inc = modDB:Sum("INC", nil, "RageRegen")
local more = modDB:More(nil, "RageRegen")
output.RageRegen = base * (1 + inc /100) * more
if breakdown then
breakdown.RageRegen = { }
breakdown.multiChain(breakdown.RageRegen, {
base = s_format("%.1f ^8(base)", base),
{ "%.2f ^8(increased/reduced)", 1 + inc/100 },
{ "%.2f ^8(more/less)", more },
total = s_format("= %.1f ^8per second", output.RageRegen),
})
end
end
-- Energy Shield Recharge
if modDB:Flag(nil, "NoEnergyShieldRecharge") then
output.EnergyShieldRecharge = 0
else
local inc = modDB:Sum("INC", nil, "EnergyShieldRecharge")
local more = modDB:More(nil, "EnergyShieldRecharge")
if modDB:Flag(nil, "EnergyShieldRechargeAppliesToLife") then
output.EnergyShieldRechargeAppliesToLife = true
local recharge = output.Life * data.misc.EnergyShieldRechargeBase * (1 + inc/100) * more
output.LifeRecharge = round(recharge * output.LifeRecoveryRateMod)
if breakdown then
breakdown.LifeRecharge = { }
breakdown.multiChain(breakdown.LifeRecharge, {
label = "Recharge rate:",
base = s_format("%.1f ^8(20%% per second)", output.Life * data.misc.EnergyShieldRechargeBase),
{ "%.2f ^8(increased/reduced)", 1 + inc/100 },
{ "%.2f ^8(more/less)", more },
total = s_format("= %.1f ^8per second", recharge),
})
breakdown.multiChain(breakdown.LifeRecharge, {
label = "Effective Recharge rate:",
base = s_format("%.1f", recharge),
{ "%.2f ^8(recovery rate modifier)", output.LifeRecoveryRateMod },
total = s_format("= %.1f ^8per second", output.LifeRecharge),
})
end
else
output.EnergyShieldRechargeAppliesToEnergyShield = true
local recharge = output.EnergyShield * data.misc.EnergyShieldRechargeBase * (1 + inc/100) * more
output.EnergyShieldRecharge = round(recharge * output.EnergyShieldRecoveryRateMod)
if breakdown then
breakdown.EnergyShieldRecharge = { }
breakdown.multiChain(breakdown.EnergyShieldRecharge, {
label = "Recharge rate:",
base = s_format("%.1f ^8(20%% per second)", output.EnergyShield * data.misc.EnergyShieldRechargeBase),
{ "%.2f ^8(increased/reduced)", 1 + inc/100 },
{ "%.2f ^8(more/less)", more },
total = s_format("= %.1f ^8per second", recharge),
})
breakdown.multiChain(breakdown.EnergyShieldRecharge, {
label = "Effective Recharge rate:",
base = s_format("%.1f", recharge),
{ "%.2f ^8(recovery rate modifier)", output.EnergyShieldRecoveryRateMod },
total = s_format("= %.1f ^8per second", output.EnergyShieldRecharge),
})
end
end
output.EnergyShieldRechargeDelay = 2 / (1 + modDB:Sum("INC", nil, "EnergyShieldRechargeFaster") / 100)
if breakdown then
if output.EnergyShieldRechargeDelay ~= 2 then
breakdown.EnergyShieldRechargeDelay = {
"2.00s ^8(base)",
s_format("/ %.2f ^8(faster start)", 1 + modDB:Sum("INC", nil, "EnergyShieldRechargeFaster") / 100),
s_format("= %.2fs", output.EnergyShieldRechargeDelay)
}
end
end
end
-- Miscellaneous: move speed, stun recovery, avoidance
output.MovementSpeedMod = modDB:Override(nil, "MovementSpeed") or calcLib.mod(modDB, nil, "MovementSpeed")
if modDB:Flag(nil, "MovementSpeedCannotBeBelowBase") then
output.MovementSpeedMod = m_max(output.MovementSpeedMod, 1)
end
output.EffectiveMovementSpeedMod = output.MovementSpeedMod * output.ActionSpeedMod
if breakdown then
breakdown.EffectiveMovementSpeedMod = { }
breakdown.multiChain(breakdown.EffectiveMovementSpeedMod, {
{ "%.2f ^8(movement speed modifier)", output.MovementSpeedMod },
{ "%.2f ^8(action speed modifier)", output.ActionSpeedMod },
total = s_format("= %.2f ^8(effective movement speed modifier)", output.EffectiveMovementSpeedMod)
})
end
if modDB:Flag(nil, "Elusive") then
output.ElusiveEffectMod = calcLib.mod(modDB, nil, "ElusiveEffect", "BuffEffectOnSelf") * 100
end
-- damage avoidances
for _, damageType in ipairs(dmgTypeList) do
output["Avoid"..damageType.."DamageChance"] = m_min(modDB:Sum("BASE", nil, "Avoid"..damageType.."DamageChance"), data.misc.AvoidChanceCap)
end
output.AvoidProjectilesChance = m_min(modDB:Sum("BASE", nil, "AvoidProjectilesChance"), data.misc.AvoidChanceCap)
-- other avoidances etc
local stunChance = 100 - m_min(modDB:Sum("BASE", nil, "AvoidStun"), 100)
if output.EnergyShield > output.Life * 2 then
stunChance = stunChance * 0.5
end
output.StunAvoidChance = 100 - stunChance
if output.StunAvoidChance >= 100 then
output.StunDuration = 0
output.BlockDuration = 0
else
output.StunDuration = 0.35 / (1 + modDB:Sum("INC", nil, "StunRecovery") / 100)
output.BlockDuration = 0.35 / (1 + modDB:Sum("INC", nil, "StunRecovery", "BlockRecovery") / 100)
if breakdown then
breakdown.StunDuration = {
"0.35s ^8(base)",
s_format("/ %.2f ^8(increased/reduced recovery)", 1 + modDB:Sum("INC", nil, "StunRecovery") / 100),
s_format("= %.2fs", output.StunDuration)
}
breakdown.BlockDuration = {
"0.35s ^8(base)",
s_format("/ %.2f ^8(increased/reduced recovery)", 1 + modDB:Sum("INC", nil, "StunRecovery", "BlockRecovery") / 100),
s_format("= %.2fs", output.BlockDuration)
}
end
end
output.InteruptStunAvoidChance = m_min(modDB:Sum("BASE", nil, "AvoidInteruptStun"), 100)
output.BlindAvoidChance = m_min(modDB:Sum("BASE", nil, "AvoidBlind"), 100)
output.ShockAvoidChance = m_min(modDB:Sum("BASE", nil, "AvoidShock"), 100)
output.FreezeAvoidChance = m_min(modDB:Sum("BASE", nil, "AvoidFreeze"), 100)
output.ChillAvoidChance = m_min(modDB:Sum("BASE", nil, "AvoidChill"), 100)
output.IgniteAvoidChance = m_min(modDB:Sum("BASE", nil, "AvoidIgnite"), 100)
output.BleedAvoidChance = m_min(modDB:Sum("BASE", nil, "AvoidBleed"), 100)
output.PoisonAvoidChance = m_min(modDB:Sum("BASE", nil, "AvoidPoison"), 100)
output.CritExtraDamageReduction = m_min(modDB:Sum("BASE", nil, "ReduceCritExtraDamage"), 100)
output.LightRadiusMod = calcLib.mod(modDB, nil, "LightRadius")
if breakdown then
breakdown.LightRadiusMod = breakdown.mod(modDB, nil, "LightRadius")
end
-- Energy Shield bypass
output.AnyBypass = false
for _, damageType in ipairs(dmgTypeList) do
if modDB:Flag(nil, "BlockedDamageDoesntBypassES") and modDB:Flag(nil, "UnblockedDamageDoesBypassES") then
local DamageTypeConfig = env.configInput.EhpCalcMode or "Average"
if DamageTypeConfig == "Minimum" then
output[damageType.."EnergyShieldBypass"] = 100 - m_min(output.BlockChance, output.ProjectileBlockChance, output.SpellBlockChance, output.SpellProjectileBlockChance)
elseif DamageTypeConfig == "Melee" then
output[damageType.."EnergyShieldBypass"] = 100 - output.BlockChance
else
output[damageType.."EnergyShieldBypass"] = 100 - output[DamageTypeConfig.."BlockChance"]
end
output.AnyBypass = true
else
output[damageType.."EnergyShieldBypass"] = modDB:Sum("BASE", nil, damageType.."EnergyShieldBypass") or 0
if output[damageType.."EnergyShieldBypass"] ~= 0 then
output.AnyBypass = true
end
if damageType == "Chaos" then
if not modDB:Flag(nil, "ChaosNotBypassEnergyShield") then
output[damageType.."EnergyShieldBypass"] = output[damageType.."EnergyShieldBypass"] + 100
else
output.AnyBypass = true
end
end
end
output[damageType.."EnergyShieldBypass"] = m_max(m_min(output[damageType.."EnergyShieldBypass"], 100), 0)
end
-- Mind over Matter
output.AnyMindOverMatter = 0
for _, damageType in ipairs(dmgTypeList) do
output[damageType.."MindOverMatter"] = m_min(modDB:Sum("BASE", nil, "DamageTakenFromManaBeforeLife") + modDB:Sum("BASE", nil, damageType.."DamageTakenFromManaBeforeLife"), 100)
if output[damageType.."MindOverMatter"] > 0 then
output.AnyMindOverMatter = output.AnyMindOverMatter + output[damageType.."MindOverMatter"]
local sourcePool = m_max(output.ManaUnreserved or 0, 0)
local manatext = "unreserved mana"
if modDB:Flag(nil, "EnergyShieldProtectsMana") and output[damageType.."EnergyShieldBypass"] < 100 then
manatext = manatext.." + non-bypassed energy shield"
if output[damageType.."EnergyShieldBypass"] > 0 then
local manaProtected = output.EnergyShield / (1 - output[damageType.."EnergyShieldBypass"] / 100) * (output[damageType.."EnergyShieldBypass"] / 100)
sourcePool = m_max(sourcePool - manaProtected, 0) + m_min(sourcePool, manaProtected) / (output[damageType.."EnergyShieldBypass"] / 100)
else
sourcePool = sourcePool + output.EnergyShield
end
end
local lifeProtected = sourcePool / (output[damageType.."MindOverMatter"] / 100) * (1 - output[damageType.."MindOverMatter"] / 100)
if output[damageType.."MindOverMatter"] >= 100 then
output[damageType.."EffectiveLife"] = output.LifeUnreserved + sourcePool
else
output[damageType.."EffectiveLife"] = m_max(output.LifeUnreserved - lifeProtected, 0) + m_min(output.LifeUnreserved, lifeProtected) / (1 - output[damageType.."MindOverMatter"] / 100)
end
if breakdown then
if output[damageType.."MindOverMatter"] then
breakdown[damageType.."MindOverMatter"] = {
s_format("Total life protected:"),
s_format("%d ^8(%s)", sourcePool, manatext),
s_format("/ %.2f ^8(portion taken from mana)", output[damageType.."MindOverMatter"] / 100),
s_format("x %.2f ^8(portion taken from life)", 1 - output[damageType.."MindOverMatter"] / 100),
s_format("= %d", lifeProtected),
s_format("Effective life: %d", output[damageType.."EffectiveLife"])
}
end
end
else
output[damageType.."EffectiveLife"] = output.LifeUnreserved
end
end
--total pool
for _, damageType in ipairs(dmgTypeList) do
output[damageType.."TotalPool"] = output[damageType.."EffectiveLife"]
local manatext = "Mana"
if output[damageType.."EnergyShieldBypass"] < 100 then
if modDB:Flag(nil, "EnergyShieldProtectsMana") then
manatext = manatext.." and non-bypassed Energy Shield"
else
if output[damageType.."EnergyShieldBypass"] > 0 then
local poolProtected = output.EnergyShield / (1 - output[damageType.."EnergyShieldBypass"] / 100) * (output[damageType.."EnergyShieldBypass"] / 100)
output[damageType.."TotalPool"] = m_max(output[damageType.."TotalPool"] - poolProtected, 0) + m_min(output[damageType.."TotalPool"], poolProtected) / (output[damageType.."EnergyShieldBypass"] / 100)
else
output[damageType.."TotalPool"] = output[damageType.."TotalPool"] + output.EnergyShield
end
end
end
if breakdown then
breakdown[damageType.."TotalPool"] = {
s_format("Life: %d", output.LifeUnreserved),
s_format("%s through MoM: %d", manatext, output[damageType.."EffectiveLife"] - output.LifeUnreserved)
}
if (not modDB:Flag(nil, "EnergyShieldProtectsMana")) and output[damageType.."EnergyShieldBypass"] < 100 then
t_insert(breakdown[damageType.."TotalPool"], s_format("Non-bypassed Energy Shield: %d", output[damageType.."TotalPool"] - output[damageType.."EffectiveLife"]))
end
t_insert(breakdown[damageType.."TotalPool"], s_format("TotalPool: %d", output[damageType.."TotalPool"]))
end
end
-- Damage taken multipliers/Degen calculations
for _, damageType in ipairs(dmgTypeList) do
local baseTakenInc = modDB:Sum("INC", nil, "DamageTaken", damageType.."DamageTaken")
local baseTakenMore = modDB:More(nil, "DamageTaken", damageType.."DamageTaken")
if isElemental[damageType] then
baseTakenInc = baseTakenInc + modDB:Sum("INC", nil, "ElementalDamageTaken")
baseTakenMore = baseTakenMore * modDB:More(nil, "ElementalDamageTaken")
end
do
-- Hit
local takenInc = baseTakenInc + modDB:Sum("INC", nil, "DamageTakenWhenHit", damageType.."DamageTakenWhenHit")
local takenMore = baseTakenMore * modDB:More(nil, "DamageTakenWhenHit", damageType.."DamageTakenWhenHit")
if isElemental[damageType] then
takenInc = takenInc + modDB:Sum("INC", nil, "ElementalDamageTakenWhenHit")
takenMore = takenMore * modDB:More(nil, "ElementalDamageTakenWhenHit")
end
output[damageType.."TakenHit"] = m_max((1 + takenInc / 100) * takenMore, 0)
do
-- Reflect
takenInc = takenInc + modDB:Sum("INC", nil, damageType.."ReflectedDamageTaken")
takenMore = takenMore * modDB:More(nil, damageType.."ReflectedDamageTaken")
if isElemental[damageType] then
takenInc = takenInc + modDB:Sum("INC", nil, "ElementalReflectedDamageTaken")
takenMore = takenMore * modDB:More(nil, "ElementalReflectedDamageTaken")
end
output[damageType.."TakenReflect"] = m_max((1 + takenInc / 100) * takenMore, 0)
end
end
do
-- Dot
local takenInc = baseTakenInc + modDB:Sum("INC", nil, "DamageTakenOverTime", damageType.."DamageTakenOverTime")
local takenMore = baseTakenMore * modDB:More(nil, "DamageTakenOverTime", damageType.."DamageTakenOverTime")
if isElemental[damageType] then
takenInc = takenInc + modDB:Sum("INC", nil, "ElementalDamageTakenOverTime")
takenMore = takenMore * modDB:More(nil, "ElementalDamageTakenOverTime")
end
local resist = modDB:Flag(nil, "SelfIgnore"..damageType.."Resistance") and 0 or output[damageType.."Resist"]
output[damageType.."TakenDotMult"] = (1 - resist / 100) * (1 + takenInc / 100) * takenMore
if breakdown then
breakdown[damageType.."TakenDotMult"] = { }
breakdown.multiChain(breakdown[damageType.."TakenDotMult"], {
label = "DoT Multiplier:",
{ "%.2f ^8(%s)", (1 - resist / 100), damageType == "Physical" and "physical damage reduction" or "resistance" },
{ "%.2f ^8(increased/reduced damage taken)", (1 + takenInc / 100) },
{ "%.2f ^8(more/less damage taken)", takenMore },
total = s_format("= %.2f", output[damageType.."TakenDotMult"]),
})
end
-- Degens
local baseVal = modDB:Sum("BASE", nil, damageType.."Degen")
if baseVal > 0 then
local total = baseVal * output[damageType.."TakenDotMult"]
output[damageType.."Degen"] = total
output.TotalDegen = (output.TotalDegen or 0) + total
if breakdown then
breakdown.TotalDegen = breakdown.TotalDegen or {
rowList = { },
colList = {
{ label = "Type", key = "type" },
{ label = "Base", key = "base" },
{ label = "Multiplier", key = "mult" },
{ label = "Total", key = "total" },
}
}
t_insert(breakdown.TotalDegen.rowList, {
type = damageType,
base = s_format("%.1f", baseVal),
mult = s_format("x %.2f", output[damageType.."TakenDotMult"]),
total = s_format("%.1f", total),
})
breakdown[damageType.."Degen"] = {
rowList = { },
colList = {
{ label = "Type", key = "type" },
{ label = "Base", key = "base" },
{ label = "Multiplier", key = "mult" },
{ label = "Total", key = "total" },
}
}
t_insert(breakdown[damageType.."Degen"].rowList, {
type = damageType,
base = s_format("%.1f", baseVal),
mult = s_format("x %.2f", output[damageType.."TakenDotMult"]),
total = s_format("%.1f", total),
})
end
end
end
end
output.AnyTakenReflect = 0
for _, damageType in ipairs(dmgTypeList) do
if output[damageType.."TakenReflect"] ~= output[damageType.."TakenHit"] then
output.AnyTakenReflect = true
end
end
if output.TotalDegen then
output.NetLifeRegen = output.LifeRegen
output.NetManaRegen = output.ManaRegen
output.NetEnergyShieldRegen = output.EnergyShieldRegen
local totalLifeDegen = 0
local totalManaDegen = 0
local totalEnergyShieldDegen = 0
if breakdown then
breakdown.NetLifeRegen = {
label = "Total Life Degen",
rowList = { },
colList = {
{ label = "Type", key = "type" },
{ label = "Degen", key = "degen" },
},
}
breakdown.NetManaRegen = {
label = "Total Mana Degen",
rowList = { },
colList = {
{ label = "Type", key = "type" },
{ label = "Degen", key = "degen" },
},
}
breakdown.NetEnergyShieldRegen = {
label = "Total Energy Shield Degen",
rowList = { },
colList = {
{ label = "Type", key = "type" },
{ label = "Degen", key = "degen" },
},
}
end
for _, damageType in ipairs(dmgTypeList) do
if output[damageType.."Degen"] then
local energyShieldDegen = 0
local lifeDegen = 0
local manaDegen = 0
if output.EnergyShieldRegen > 0 then
if modDB:Flag(nil, "EnergyShieldProtectsMana") then
lifeDegen = output[damageType.."Degen"] * (1 - output[damageType.."MindOverMatter"] / 100)
energyShieldDegen = output[damageType.."Degen"] * (1 - output[damageType.."EnergyShieldBypass"] / 100) * (output[damageType.."MindOverMatter"] / 100)
else
lifeDegen = output[damageType.."Degen"] * (output[damageType.."EnergyShieldBypass"] / 100) * (1 - output[damageType.."MindOverMatter"] / 100)
energyShieldDegen = output[damageType.."Degen"] * (1 - output[damageType.."EnergyShieldBypass"] / 100)
end
manaDegen = output[damageType.."Degen"] * (output[damageType.."EnergyShieldBypass"] / 100) * (output[damageType.."MindOverMatter"] / 100)
else
lifeDegen = output[damageType.."Degen"] * (1 - output[damageType.."MindOverMatter"] / 100)
manaDegen = output[damageType.."Degen"] * (output[damageType.."MindOverMatter"] / 100)
end
totalLifeDegen = totalLifeDegen + lifeDegen
totalManaDegen = totalManaDegen + manaDegen
totalEnergyShieldDegen = totalEnergyShieldDegen + energyShieldDegen
if breakdown then
t_insert(breakdown.NetLifeRegen.rowList, {
type = s_format("%s", damageType),
degen = s_format("%.2f", lifeDegen),
})
t_insert(breakdown.NetManaRegen.rowList, {
type = s_format("%s", damageType),
degen = s_format("%.2f", manaDegen),
})
t_insert(breakdown.NetEnergyShieldRegen.rowList, {
type = s_format("%s", damageType),
degen = s_format("%.2f", energyShieldDegen),
})
end
end
end
output.NetLifeRegen = output.NetLifeRegen - totalLifeDegen
output.NetManaRegen = output.NetManaRegen - totalManaDegen
output.NetEnergyShieldRegen = output.NetEnergyShieldRegen - totalEnergyShieldDegen
output.TotalNetRegen = output.NetLifeRegen + output.NetManaRegen + output.NetEnergyShieldRegen
if breakdown then
t_insert(breakdown.NetLifeRegen, s_format("%.1f ^8(total life regen)", output.LifeRegen))
t_insert(breakdown.NetLifeRegen, s_format("- %.1f ^8(total life degen)", totalLifeDegen))
t_insert(breakdown.NetLifeRegen, s_format("= %.1f", output.NetLifeRegen))
t_insert(breakdown.NetManaRegen, s_format("%.1f ^8(total mana regen)", output.ManaRegen))
t_insert(breakdown.NetManaRegen, s_format("- %.1f ^8(total mana degen)", totalManaDegen))
t_insert(breakdown.NetManaRegen, s_format("= %.1f", output.NetManaRegen))
t_insert(breakdown.NetEnergyShieldRegen, s_format("%.1f ^8(total energy shield regen)", output.EnergyShieldRegen))
t_insert(breakdown.NetEnergyShieldRegen, s_format("- %.1f ^8(total energy shield degen)", totalEnergyShieldDegen))
t_insert(breakdown.NetEnergyShieldRegen, s_format("= %.1f", output.NetEnergyShieldRegen))
breakdown.TotalNetRegen = {
s_format("Net Life Regen: %.1f", output.NetLifeRegen),
s_format("+ Net Mana Regen: %.1f", output.NetManaRegen),
s_format("+ Net Energy Shield Regen: %.1f", output.NetEnergyShieldRegen),
s_format("= Total Net Regen: %.1f", output.TotalNetRegen)
}
end
end
-- Incoming hit damage multipliers
local doubleArmourChance = (output.DoubleArmourChance == 100 or env.configInput.armourCalculationMode == "MAX") and 1 or env.configInput.armourCalculationMode == "MIN" and 0 or output.DoubleArmourChance / 100
actor.damageShiftTable = wipeTable(actor.damageShiftTable)
for _, damageType in ipairs(dmgTypeList) do
-- Build damage shift table
local shiftTable = { }
local destTotal = 0
for _, destType in ipairs(dmgTypeList) do
if destType ~= damageType then
shiftTable[destType] = modDB:Sum("BASE", nil, damageType.."DamageTakenAs"..destType, isElemental[damageType] and "ElementalDamageTakenAs"..destType or nil)
destTotal = destTotal + shiftTable[destType]
end
end
if destTotal > 100 then
local factor = 100 / destTotal
for destType, portion in pairs(shiftTable) do
shiftTable[destType] = portion * factor
end
destTotal = 100
end
shiftTable[damageType] = 100 - destTotal
actor.damageShiftTable[damageType] = shiftTable
-- Calculate incoming damage multiplier
local mult = 0
local multReflect = 0
if breakdown then
breakdown[damageType.."TakenHitMult"] = {
label = "Hit Damage taken as",
rowList = { },
colList = {
{ label = "Type", key = "type" },
{ label = "Mitigation", key = "resist" },
{ label = "Taken", key = "taken" },
{ label = "Final", key = "final" },
},
}
breakdown[damageType.."TakenReflectMult"] = {
label = "Hit Damage taken as",
rowList = { },
colList = {
{ label = "Type", key = "type" },
{ label = "Mitigation", key = "resist" },
{ label = "Taken", key = "taken" },
{ label = "Final", key = "final" },
},
}
end
for _, destType in ipairs(dmgTypeList) do
local portion = shiftTable[destType]
if portion > 0 then
local resist = modDB:Flag(nil, "SelfIgnore"..destType.."Resistance") and 0 or output[destType.."ResistWhenHit"] or output[destType.."Resist"]
if destType == "Physical" or modDB:Flag(nil, "ArmourAppliesTo"..destType.."DamageTaken") then
local damage = env.configInput.enemyHit or env.data.monsterDamageTable[env.enemyLevel] * 1.5
local armourReduct = 0
local portionArmour = 100
if destType == "Physical" then
if not modDB:Flag(nil, "ArmourDoesNotApplyToPhysicalDamageTaken") then
armourReduct = calcs.armourReductionDouble(output.Armour, damage * portion / 100, doubleArmourChance)
resist = m_min(output.DamageReductionMax, resist + armourReduct)
end
else
portionArmour = 100 - resist
armourReduct = calcs.armourReductionDouble(output.Armour, damage * portion / 100 * portionArmour / 100, doubleArmourChance)
resist = resist + m_min(output.DamageReductionMax, armourReduct) * portionArmour / 100
end
if damageType == destType then
output[damageType.."DamageReduction"] = damageType == "Physical" and resist or m_min(output.DamageReductionMax, armourReduct) * portionArmour / 100
if breakdown then
breakdown[damageType.."DamageReduction"] = {
s_format("Enemy Hit Damage: %d ^8(%s the Configuration tab)", damage, env.configInput.enemyHit and "overridden from" or "can be overridden in"),
}
if portion < 100 then
t_insert(breakdown[damageType.."DamageReduction"], s_format("Portion taken as %s: %d%%", damageType, portion))
end
if portionArmour < 100 then
t_insert(breakdown[damageType.."DamageReduction"], s_format("Portion mitigated by Armour: %d%%", portionArmour))
end
t_insert(breakdown[damageType.."DamageReduction"], s_format("Reduction from Armour: %d%%", armourReduct))
end
end
end
local takenMult = output[destType.."TakenHit"]
local takenMultReflect = output[destType.."TakenReflect"]
local final = portion / 100 * (1 - resist / 100) * takenMult
local finalReflect = portion / 100 * (1 - resist / 100) * takenMultReflect
mult = mult + final
multReflect = multReflect + finalReflect
if breakdown then
t_insert(breakdown[damageType.."TakenHitMult"].rowList, {
type = s_format("%d%% as %s", portion, destType),
resist = s_format("x %.2f", 1 - resist / 100),
taken = takenMult ~= 1 and s_format("x %.2f", takenMult),
final = s_format("x %.2f", final),
})
if output.AnyTakenReflect then
t_insert(breakdown[damageType.."TakenReflectMult"].rowList, {
type = s_format("%d%% as %s", portion, destType),
resist = s_format("x %.2f", 1 - resist / 100),
taken = takenMultReflect ~= 1 and s_format("x %.2f", takenMultReflect),
finalReflect = s_format("x %.2f", finalReflect),
})
end
end
end
end
output[damageType.."TakenHitMult"] = mult
if output.AnyTakenReflect then
output[damageType.."TakenReflectMult"] = multReflect
end
end
-- cumulative defences
--chance to not be hit
output.MeleeNotHitChance = 100 - (1 - output.MeleeEvadeChance / 100) * (1 - output.AttackDodgeChance / 100) * 100
output.ProjectileNotHitChance = 100 - (1 - output.ProjectileEvadeChance / 100) * (1 - output.AttackDodgeChance / 100) * 100
output.SpellNotHitChance = 100 - (1 - output.SpellDodgeChance / 100) * 100
output.SpellProjectileNotHitChance = output.SpellNotHitChance
output.AverageNotHitChance = (output.MeleeNotHitChance + output.ProjectileNotHitChance + output.SpellNotHitChance + output.SpellProjectileNotHitChance) / 4
if breakdown then
breakdown.MeleeNotHitChance = { }
breakdown.multiChain(breakdown.MeleeNotHitChance, {
{ "%.2f ^8(chance for evasion to fail)", 1 - output.MeleeEvadeChance / 100 },
{ "%.2f ^8(chance for dodge to fail)", 1 - output.AttackDodgeChance / 100 },
total = s_format("= %d%% ^8(chance to be hit by a melee attack)", 100 - output.MeleeNotHitChance),
})
breakdown.ProjectileNotHitChance = { }
breakdown.multiChain(breakdown.ProjectileNotHitChance, {
{ "%.2f ^8(chance for evasion to fail)", 1 - output.ProjectileEvadeChance / 100 },
{ "%.2f ^8(chance for dodge to fail)", 1 - output.AttackDodgeChance / 100 },
total = s_format("= %d%% ^8(chance to be hit by a projectile attack)", 100 - output.ProjectileNotHitChance),
})
breakdown.SpellNotHitChance = { }
breakdown.multiChain(breakdown.SpellNotHitChance, {
{ "%.2f ^8(chance for dodge to fail)", 1 - output.SpellDodgeChance / 100 },
total = s_format("= %d%% ^8(chance to be hit by a spell)", 100 - output.SpellNotHitChance),
})
breakdown.SpellProjectileNotHitChance = { }
breakdown.multiChain(breakdown.SpellProjectileNotHitChance, {
{ "%.2f ^8(chance for dodge to fail)", 1 - output.SpellDodgeChance / 100 },
total = s_format("= %d%% ^8(chance to be hit by a projectile spell)", 100 - output.SpellProjectileNotHitChance),
})
end
--chance to not take damage if hit
function chanceToNotTakeDamage(outputText, outputName, BlockChance, AvoidChance)
output[outputName] = 100 - (1 - BlockChance * output.BlockEffect / 100 / 100 ) * (1 - AvoidChance / 100) * 100
if breakdown then
breakdown[outputName] = { }
if output.ShowBlockEffect then
breakdown.multiChain(breakdown[outputName], {
{ "%.2f ^8(chance for block to fail)", 1 - BlockChance / 100 },
{ "%d%% Damage taken from blocks", output.BlockEffect },
{ "%.2f ^8(chance for avoidance to fail)", 1 - AvoidChance / 100 },
total = s_format("= %d%% ^8(chance to take damage from a %s)", 100 - output[outputName], outputText),
})
else
breakdown.multiChain(breakdown[outputName], {
{ "%.2f ^8(chance for block to fail)", 1 - BlockChance / 100 },
{ "%.2f ^8(chance for avoidance to fail)", 1 - AvoidChance / 100 },
total = s_format("= %d%% ^8(chance to take damage from a %s)", 100 - output[outputName], outputText),
})
end
end
end
for _, damageType in ipairs(dmgTypeList) do
chanceToNotTakeDamage("Melee Attack", damageType.."MeleeDamageChance", output.BlockChance, output["Avoid"..damageType.."DamageChance"])
chanceToNotTakeDamage("Projectile Attack", damageType.."ProjectileDamageChance", output.ProjectileBlockChance, m_min(output["Avoid"..damageType.."DamageChance"] + output.AvoidProjectilesChance, data.misc.AvoidChanceCap))
chanceToNotTakeDamage("Spell", damageType.."SpellDamageChance", output.SpellBlockChance, output["Avoid"..damageType.."DamageChance"])
chanceToNotTakeDamage("Projectile Spell", damageType.."SpellProjectileDamageChance", output.SpellProjectileBlockChance, m_min(output["Avoid"..damageType.."DamageChance"] + output.AvoidProjectilesChance, data.misc.AvoidChanceCap))
--average
output[damageType.."AverageDamageChance"] = (output[damageType.."MeleeDamageChance"] + output[damageType.."ProjectileDamageChance"] + output[damageType.."SpellDamageChance"] + output[damageType.."SpellProjectileDamageChance"] ) / 4
end
--effective health pool vs dots
for _, damageType in ipairs(dmgTypeList) do
output[damageType.."DotEHP"] = output[damageType.."TotalPool"] / output[damageType.."TakenDotMult"]
if breakdown then
breakdown[damageType.."DotEHP"] = {
s_format("Total Pool: %d", output[damageType.."TotalPool"]),
s_format("Dot Damage Taken modifier: %.2f", output[damageType.."TakenDotMult"]),
s_format("Total Effective Dot Pool: %d", output[damageType.."DotEHP"]),
}
end
end
--maximum hit taken
--FIX X TAKEN AS Y (output[damageType.."TotalPool"] should use the damage types that are converted to in output[damageType.."TakenHitMult"])
for _, damageType in ipairs(dmgTypeList) do
output[damageType.."MaximumHitTaken"] = output[damageType.."TotalPool"] / output[damageType.."TakenHitMult"]
if breakdown then
breakdown[damageType.."MaximumHitTaken"] = {
s_format("Total Pool: %d", output[damageType.."TotalPool"]),
s_format("Damage Taken modifier: %.2f", output[damageType.."TakenHitMult"]),
s_format("Maximum hit you can take: %d", output[damageType.."MaximumHitTaken"]),
}
end
end
local DamageTypeConfig = env.configInput.EhpCalcMode or "Average"
local minimumEHP = 2147483648
local minimumEHPMode = "NONE"
if DamageTypeConfig == "Minimum" then
DamageTypeConfig = {"Melee", "Projectile", "Spell", "SpellProjectile"}
minimumEHPMode = "Melee"
else
DamageTypeConfig = {DamageTypeConfig}
end
for _, DamageType in ipairs(DamageTypeConfig) do
--total EHP
for _, damageType in ipairs(dmgTypeList) do
local convertedAvoidance = 0
for _, damageConvertedType in ipairs(dmgTypeList) do
convertedAvoidance = convertedAvoidance + output[damageConvertedType..DamageType.."DamageChance"] * actor.damageShiftTable[damageType][damageConvertedType] / 100
end
output[damageType.."TotalEHP"] = output[damageType.."MaximumHitTaken"] / (1 - output[DamageType.."NotHitChance"] / 100) / (1 - convertedAvoidance / 100)
if minimumEHPMode ~= "NONE" then
if output[damageType.."TotalEHP"] < minimumEHP then
minimumEHP = output[damageType.."TotalEHP"]
minimumEHPMode = DamageType
end
elseif breakdown then
breakdown[damageType.."TotalEHP"] = {
s_format("EHP calculation Mode: %s", DamageType),
s_format("Maximum Hit taken: %d", output[damageType.."MaximumHitTaken"]),
s_format("%s chance not to be hit: %d%%", DamageType, output[DamageType.."NotHitChance"]),
s_format("%s chance to not take damage when hit: %d%%", DamageType, convertedAvoidance),
s_format("Total Effective Hit Pool: %d", output[damageType.."TotalEHP"]),
}
end
end
end
if minimumEHPMode ~= "NONE" then
for _, damageType in ipairs(dmgTypeList) do
local convertedAvoidance = 0
for _, damageConvertedType in ipairs(dmgTypeList) do
convertedAvoidance = convertedAvoidance + output[damageConvertedType..minimumEHPMode.."DamageChance"] * actor.damageShiftTable[damageType][damageConvertedType] / 100
end
output[damageType.."TotalEHP"] = output[damageType.."MaximumHitTaken"] / (1 - output[minimumEHPMode.."NotHitChance"] / 100) / (1 - convertedAvoidance / 100)
if breakdown then
breakdown[damageType.."TotalEHP"] = {
s_format("EHP calculation Mode: Minimum"),
s_format("Minimum is of type %s", minimumEHPMode),
s_format("Maximum Hit taken: %d", output[damageType.."MaximumHitTaken"]),
s_format("%s chance not to be hit: %d%%", minimumEHPMode, output[minimumEHPMode.."NotHitChance"]),
s_format("%s chance to not take damage when hit: %d%%", minimumEHPMode, convertedAvoidance),
s_format("Total Effective Hit Pool: %d", output[damageType.."TotalEHP"]),
}
end
end
end
end