From 0c3d17b2cf466c06de64698e08d6ac22004c689d Mon Sep 17 00:00:00 2001 From: Openarl Date: Mon, 30 Jan 2017 02:18:50 +1000 Subject: [PATCH] Release 1.2.35 - Split hit damage calculations into 2 passes for crit and non-crit - Added support for Inevitable Judgement - Improved DoT code to allow for different chances to inflict on crit and non-crit --- Modules/CalcSections.lua | 14 +- Modules/Calcs.lua | 523 ++++++++++++++++++++++++--------------- Modules/ModParser.lua | 13 +- README.md | 19 ++ changelog.txt | 18 ++ manifest.xml | 10 +- 6 files changed, 379 insertions(+), 218 deletions(-) diff --git a/Modules/CalcSections.lua b/Modules/CalcSections.lua index 5e7da216..f45ba3ce 100644 --- a/Modules/CalcSections.lua +++ b/Modules/CalcSections.lua @@ -184,7 +184,7 @@ return { { 1, "Bleed", 1, "Bleed", data.colorCodes.OFFENCE, { extra = "{0:output:BleedChance}% {1:output:BleedDPS} {2:output:BleedDuration}s", flag = "bleed", - { label = "Chance to Bleed", { format = "{0:output:BleedChance}%", { modName = "BleedChance", cfg = "skill" }, }, }, + { label = "Chance to Bleed", { format = "{0:output:BleedChance}%", { breakdown = "BleedChance" }, { modName = "BleedChance", cfg = "skill" }, }, }, { label = "Total Increased", { format = "{0:mod:1}%", { modName = { "Damage", "PhysicalDamage" }, modType = "INC", cfg = "bleed" }, }, }, { label = "Total More", { format = "{0:mod:1}%", { modName = { "Damage", "PhysicalDamage" }, modType = "MORE", cfg = "bleed" }, }, }, { label = "Effective DPS Mod", flag = "effective", { format = "x {3:output:BleedEffMult}", { breakdown = "BleedEffMult" }, { label = "Enemy modifiers", modName = { "DamageTaken", "DotTaken", "PhysicalDamageTaken", "PhysicalDamageReduction" }, enemy = true }, }, }, @@ -194,7 +194,7 @@ return { { 1, "Poison", 1, "Poison", data.colorCodes.OFFENCE, { extra = "{0:output:PoisonChance}% {1:output:PoisonDPS} {2:output:PoisonDuration}s", flag = "poison", - { label = "Chance to Poison", { format = "{0:output:PoisonChance}%", { modName = "PoisonChance", cfg = "skill" }, }, }, + { label = "Chance to Poison", { format = "{0:output:PoisonChance}%", { breakdown = "PoisonChance" }, { modName = "PoisonChance", cfg = "skill" }, }, }, { label = "Total Increased", { format = "{0:mod:1}%", { modName = { "Damage", "ChaosDamage" }, modType = "INC", cfg = "poison" }, }, }, { label = "Total More", { format = "{0:mod:1}%", { modName = { "Damage", "ChaosDamage" }, modType = "MORE", cfg = "poison" }, }, }, { label = "Effective DPS Mod", flag = "effective", { format = "x {3:output:PoisonEffMult}", { breakdown = "PoisonEffMult" }, { label = "Enemy modifiers", modName = { "ChaosResist", "DamageTaken", "DotTaken", "ChaosDamageTaken" }, enemy = true }, }, }, @@ -205,7 +205,7 @@ return { { 1, "Ignite", 1, "Ignite", data.colorCodes.OFFENCE, { extra = "{0:output:IgniteChance}% {1:output:IgniteDPS} {2:output:IgniteDuration}s", flag = "ignite", - { label = "Chance to Ignite", { format = "{0:output:IgniteChance}%", { label = "Player modifiers", modName = "EnemyIgniteChance", cfg = "skill" }, { label = "Enemy modifiers", modName = "SelfIgniteChance", enemy = true }, }, }, + { label = "Chance to Ignite", { format = "{0:output:IgniteChance}%", { breakdown = "IgniteChance" }, { label = "Player modifiers", modName = "EnemyIgniteChance", cfg = "skill" }, { label = "Enemy modifiers", modName = "SelfIgniteChance", enemy = true }, }, }, { label = "Total Increased", { format = "{0:mod:1}%", { modName = { "Damage", "FireDamage", "ElementalDamage" }, modType = "INC", cfg = "ignite" }, }, }, { label = "Total More", { format = "{0:mod:1}%", { modName = { "Damage", "FireDamage", "ElementalDamage" }, modType = "MORE", cfg = "ignite" }, }, }, { label = "Effective DPS Mod", flag = "effective", { format = "x {3:output:IgniteEffMult}", { breakdown = "IgniteEffMult" }, { label = "Enemy modifiers", modName = { "FireResist", "ElementalResist", "DamageTaken", "DotTaken", "FireDamageTaken", "BurningDamageTaken", "ElementalDamageTaken" }, enemy = true }, }, }, @@ -215,10 +215,10 @@ return { } }, { 1, "MiscEffects", 1, "Other Effects", data.colorCodes.OFFENCE, { flag = "hit", - { label = "Chance to Shock", flag = "shock", { format = "{0:output:ShockChance}%", { label = "Player modifiers", modName = "EnemyShockChance", cfg = "skill" }, { label = "Enemy modifiers", modName = "SelfShockChance", enemy = true }, }, }, - { label = "Shock Dur. Mod", flag = "shock", { format = "x {2:output:ShockDurationMod}", { label = "Player modifiers", modName = "EnemyShockDuration", cfg = "skill" }, { label = "Enemy modifiers", modName = "SelfShockDuration", enemy = true }, }, }, - { label = "Chance to Freeze", flag = "freeze", { format = "{0:output:FreezeChance}%", { label = "Player modifiers", modName = "EnemyFreezeChance", cfg = "skill" }, { label = "Enemy modifiers", modName = "SelfFreezeChance", enemy = true }, }, }, - { label = "Freeze Dur. Mod", flag = "freeze", { format = "x {2:output:FreezeDurationMod}", { label = "Player modifiers", modName = "EnemyFreezeDuration", cfg = "skill" }, { label = "Enemy modifiers", modName = "SelfFreezeDuration", enemy = true }, }, }, + { label = "Chance to Shock", flag = "shock", { format = "{0:output:ShockChance}%", { breakdown = "ShockChance" }, { label = "Player modifiers", modName = "EnemyShockChance", cfg = "skill" }, { label = "Enemy modifiers", modName = "SelfShockChance", enemy = true }, }, }, + { label = "Shock Dur. Mod", flag = "shock", { format = "x {2:output:ShockDurationMod}", { breakdown = "ShockDPS" }, { label = "Player modifiers", modName = "EnemyShockDuration", cfg = "skill" }, { label = "Enemy modifiers", modName = "SelfShockDuration", enemy = true }, }, }, + { label = "Chance to Freeze", flag = "freeze", { format = "{0:output:FreezeChance}%", { breakdown = "FreezeChance" }, { label = "Player modifiers", modName = "EnemyFreezeChance", cfg = "skill" }, { label = "Enemy modifiers", modName = "SelfFreezeChance", enemy = true }, }, }, + { label = "Freeze Dur. Mod", flag = "freeze", { format = "x {2:output:FreezeDurationMod}", { breakdown = "FreezeDPS" }, { label = "Player modifiers", modName = "EnemyFreezeDuration", cfg = "skill" }, { label = "Enemy modifiers", modName = "SelfFreezeDuration", enemy = true }, }, }, { label = "Stun Thresh. Mod", { format = "x {2:output:EnemyStunThresholdMod}", { modName = "EnemyStunThreshold", cfg = "skill" }, }, }, { label = "Stun Duration", { format = "{2:output:EnemyStunDuration}s", { breakdown = "EnemyStunDuration" }, { label = "Player modifiers", modName = { "EnemyStunDuration" }, cfg = "skill" }, { label = "Enemy modifiers", modName = { "StunRecovery" }, enemy = true }, }, }, { label = "Inc. Item Quantity", { format = "{0:mod:1}%", { modName = "LootQuantity", modType = "INC", cfg = "skill" }, }, }, diff --git a/Modules/Calcs.lua b/Modules/Calcs.lua index 7bae1898..e4130880 100644 --- a/Modules/Calcs.lua +++ b/Modules/Calcs.lua @@ -3,7 +3,7 @@ -- Module: Calcs -- Performs all the offense and defense calculations. -- Here be dragons! --- This file is 2400 lines long, over half of which is in one function... +-- This file is 2700 lines long, over half of which is in one function... -- local pairs = pairs @@ -933,7 +933,7 @@ local function mergeMainMods(env, repSlotName, repItem) end -- Finalise environment and perform the calculations --- This function is 1300 lines long. Enjoy! +-- This function is 1600 lines long. Enjoy! local function performCalcs(env) local modDB = env.modDB local enemyDB = env.enemyDB @@ -1811,6 +1811,32 @@ local function performCalcs(env) env.conversionTable[damageType] = dmgTable end env.conversionTable["Chaos"] = { mult = 1 } + + -- Calculate mana cost (may be slightly off due to rounding differences) + do + local more = m_floor(modDB:Sum("MORE", skillCfg, "ManaCost") * 100 + 0.0001) / 100 + local inc = modDB:Sum("INC", skillCfg, "ManaCost") + local base = modDB:Sum("BASE", skillCfg, "ManaCost") + output.ManaCost = m_floor(m_max(0, (skillData.manaCost or 0) * more * (1 + inc / 100) + base)) + if env.mainSkill.skillTypes[SkillType.ManaCostPercent] and skillFlags.totem then + output.ManaCost = m_floor(output.Mana * output.ManaCost / 100) + end + if breakdown and output.ManaCost ~= (skillData.manaCost or 0) then + breakdown.ManaCost = { + s_format("%d ^8(base mana cost)", skillData.manaCost or 0) + } + if more ~= 1 then + t_insert(breakdown.ManaCost, s_format("x %.2f ^8(mana cost multiplier)", more)) + end + if inc ~= 0 then + t_insert(breakdown.ManaCost, s_format("x %.2f ^8(increased/reduced mana cost)", 1 + inc/100)) + end + if base ~= 0 then + t_insert(breakdown.ManaCost, s_format("- %d ^8(- mana cost)", -base)) + end + t_insert(breakdown.ManaCost, s_format("= %d", output.ManaCost)) + end + end -- Calculate hit chance output.Accuracy = calcVal(modDB, "Accuracy", skillCfg) @@ -1925,9 +1951,7 @@ local function performCalcs(env) end end end - if modDB:Sum("FLAG", skillCfg, "NoCritDamage") then - output.CritMultiplier = 0 - elseif modDB:Sum("FLAG", skillCfg, "NoCritMultiplier") then + if modDB:Sum("FLAG", skillCfg, "NoCritMultiplier") then output.CritMultiplier = 1 else local extraDamage = 0.5 + modDB:Sum("BASE", skillCfg, "CritMultiplier") / 100 @@ -1961,8 +1985,11 @@ local function performCalcs(env) end -- Calculate hit damage for each damage type - local totalMin, totalMax = 0, 0 - do + local totalHitMin, totalHitMax = 0, 0 + local totalCritMin, totalCritMax = 0, 0 + for pass = 1, 2 do + -- Pass 1 is critical strike damage, pass 2 is non-critical strike + condList["CriticalStrike"] = (pass == 1) local hitSource = (env.mode_skillType == "ATTACK") and env.weaponData1 or env.mainSkill.skillData for _, damageType in ipairs(dmgTypeList) do local min, max @@ -1983,6 +2010,11 @@ local function performCalcs(env) end min = min * convMult max = max * convMult + if pass == 1 then + -- Apply crit multiplier + min = min * output.CritMultiplier + max = max * output.CritMultiplier + end if (min ~= 0 or max ~= 0) and env.mode_effective then -- Apply enemy resistances and damage taken modifiers local preMult @@ -2001,7 +2033,10 @@ local function performCalcs(env) if skillFlags.projectile then taken = taken + enemyDB:Sum("INC", nil, "ProjectileDamageTaken") end - local effMult = (1 - (resist - pen) / 100) * (1 + taken / 100) + local effMult = (1 + taken / 100) + if not isElemental[damageType] or not modDB:Sum("FLAG", skillCfg, "IgnoreElementalResistances") then + effMult = effMult * (1 - (resist - pen) / 100) + end min = min * effMult max = max * effMult if env.mode == "CALCS" then @@ -2023,25 +2058,31 @@ local function performCalcs(env) } end end - if env.mode == "CALCS" then - output[damageType.."Min"] = min - output[damageType.."Max"] = max + if pass == 1 then + output[damageType.."CritAverage"] = (min + max) / 2 + totalCritMin = totalCritMin + min + totalCritMax = totalCritMax + max + else + if env.mode == "CALCS" then + output[damageType.."Min"] = min + output[damageType.."Max"] = max + end + output[damageType.."HitAverage"] = (min + max) / 2 + totalHitMin = totalHitMin + min + totalHitMax = totalHitMax + max end - output[damageType.."Average"] = (min + max) / 2 - totalMin = totalMin + min - totalMax = totalMax + max end end - output.TotalMin = totalMin - output.TotalMax = totalMax + output.TotalMin = totalHitMin + output.TotalMax = totalHitMax -- Update enemy hit-by-damage-type conditions - enemyDB.conditions.HitByFireDamage = output.FireAverage > 0 - enemyDB.conditions.HitByColdDamage = output.ColdAverage > 0 - enemyDB.conditions.HitByLightningDamage = output.LightningAverage > 0 + enemyDB.conditions.HitByFireDamage = output.FireHitAverage > 0 + enemyDB.conditions.HitByColdDamage = output.ColdHitAverage > 0 + enemyDB.conditions.HitByLightningDamage = output.LightningHitAverage > 0 -- Calculate average damage and final DPS - output.AverageHit = (totalMin + totalMax) / 2 * output.CritEffect + output.AverageHit = (totalHitMin + totalHitMax) / 2 * (1 - output.CritChance / 100) + (totalCritMin + totalCritMax) / 2 * output.CritChance / 100 output.AverageDamage = output.AverageHit * output.HitChance / 100 output.TotalDPS = output.AverageDamage * (output.HitSpeed or output.Speed) * (skillData.dpsMultiplier or 1) if env.mode == "CALCS" then @@ -2054,8 +2095,8 @@ local function performCalcs(env) if breakdown then if output.CritEffect ~= 1 then breakdown.AverageHit = { - s_format("%.1f ^8(non-crit average)", (totalMin + totalMax) / 2), - s_format("x %.3f ^8(crit effect modifier)", output.CritEffect), + s_format("%.1f x (1 - %.4f) ^8(damage from non-crits)", (totalHitMin + totalHitMax) / 2, output.CritChance / 100), + s_format("+ %.1f x %.4f ^8(damage from crits)", (totalCritMin + totalCritMax) / 2, output.CritChance / 100), s_format("= %.1f", output.AverageHit), } end @@ -2081,32 +2122,6 @@ local function performCalcs(env) t_insert(breakdown.TotalDPS, s_format("= %.1f", output.TotalDPS)) end - -- Calculate mana cost (may be slightly off due to rounding differences) - do - local more = m_floor(modDB:Sum("MORE", skillCfg, "ManaCost") * 100 + 0.0001) / 100 - local inc = modDB:Sum("INC", skillCfg, "ManaCost") - local base = modDB:Sum("BASE", skillCfg, "ManaCost") - output.ManaCost = m_floor(m_max(0, (skillData.manaCost or 0) * more * (1 + inc / 100) + base)) - if env.mainSkill.skillTypes[SkillType.ManaCostPercent] and skillFlags.totem then - output.ManaCost = m_floor(output.Mana * output.ManaCost / 100) - end - if breakdown and output.ManaCost ~= (skillData.manaCost or 0) then - breakdown.ManaCost = { - s_format("%d ^8(base mana cost)", skillData.manaCost or 0) - } - if more ~= 1 then - t_insert(breakdown.ManaCost, s_format("x %.2f ^8(mana cost multiplier)", more)) - end - if inc ~= 0 then - t_insert(breakdown.ManaCost, s_format("x %.2f ^8(increased/reduced mana cost)", 1 + inc/100)) - end - if base ~= 0 then - t_insert(breakdown.ManaCost, s_format("- %d ^8(- mana cost)", -base)) - end - t_insert(breakdown.ManaCost, s_format("= %d", output.ManaCost)) - end - end - -- Calculate skill DOT components local dotCfg = { skillName = skillCfg.skillName, @@ -2133,13 +2148,14 @@ local function performCalcs(env) if damageType == "Physical" then resist = enemyDB:Sum("INC", nil, "PhysicalDamageReduction") else + resist = enemyDB:Sum("BASE", nil, damageType.."Resist") if isElemental[damageType] then + resist = resist + enemyDB:Sum("BASE", nil, "ElementalResist") taken = taken + enemyDB:Sum("INC", nil, "ElementalDamageTaken") end if damageType == "Fire" then taken = taken + enemyDB:Sum("INC", nil, "BurningDamageTaken") end - resist = output["Enemy"..damageType.."Resist"] end effMult = (1 - resist / 100) * (1 + taken / 100) output[damageType.."DotEffMult"] = effMult @@ -2159,148 +2175,241 @@ local function performCalcs(env) end end - -- Calculate bleeding chance and damage - skillFlags.bleed = false - output.BleedChance = m_min(100, modDB:Sum("BASE", skillCfg, "BleedChance")) - if canDeal.Physical and not modDB:Sum("FLAG", skillCfg, "CannotBleed") and output.BleedChance > 0 and output.PhysicalAverage > 0 then - skillFlags.bleed = true - skillFlags.duration = true - local dotCfg = { - slotName = skillCfg.slotName, - flags = bor(band(skillCfg.flags, ModFlag.SourceMask), ModFlag.Dot, skillData.dotIsSpell and ModFlag.Spell or 0), - keywordFlags = bor(skillCfg.keywordFlags, KeywordFlag.Bleed) - } - env.mainSkill.bleedCfg = dotCfg - local baseVal = output.PhysicalAverage * output.CritEffect * 0.1 - local effMult = 1 - if env.mode_effective then - local resist = enemyDB:Sum("INC", nil, "PhysicalDamageReduction") - local taken = enemyDB:Sum("INC", dotCfg, "DamageTaken", "PhysicalDamageTaken", "DotTaken") - effMult = (1 - resist / 100) * (1 + taken / 100) - output["BleedEffMult"] = effMult - if breakdown and effMult ~= 1 then - breakdown.BleedEffMult = effMultBreakdown("Physical", resist, 0, taken, effMult) + -- Calculate chance to inflict secondary dots/status effects + condList["CriticalStrike"] = true + if modDB:Sum("FLAG", skillCfg, "CannotBleed") then + output.BleedChanceOnCrit = 0 + else + output.BleedChanceOnCrit = m_min(100, modDB:Sum("BASE", skillCfg, "BleedChance")) + end + output.PoisonChanceOnCrit = m_min(100, modDB:Sum("BASE", skillCfg, "PoisonChance")) + if modDB:Sum("FLAG", skillCfg, "CannotIgnite") then + output.IgniteChanceOnCrit = 0 + else + output.IgniteChanceOnCrit = 100 + end + if modDB:Sum("FLAG", skillCfg, "CannotShock") then + output.ShockChanceOnCrit = 0 + else + output.ShockChanceOnCrit = 100 + end + if modDB:Sum("FLAG", skillCfg, "CannotFreeze") then + output.FreezeChanceOnCrit = 0 + else + output.FreezeChanceOnCrit = 100 + end + condList["CriticalStrike"] = false + if modDB:Sum("FLAG", skillCfg, "CannotBleed") then + output.BleedChanceOnHit = 0 + else + output.BleedChanceOnHit = m_min(100, modDB:Sum("BASE", skillCfg, "BleedChance")) + end + output.PoisonChanceOnHit = m_min(100, modDB:Sum("BASE", skillCfg, "PoisonChance")) + if modDB:Sum("FLAG", skillCfg, "CannotIgnite") then + output.IgniteChanceOnHit = 0 + else + output.IgniteChanceOnHit = m_min(100, modDB:Sum("BASE", skillCfg, "EnemyIgniteChance") + enemyDB:Sum("BASE", nil, "SelfIgniteChance")) + end + if modDB:Sum("FLAG", skillCfg, "CannotShock") then + output.ShockChanceOnHit = 0 + else + output.ShockChanceOnHit = m_min(100, modDB:Sum("BASE", skillCfg, "EnemyShockChance") + enemyDB:Sum("BASE", nil, "SelfShockChance")) + end + if modDB:Sum("FLAG", skillCfg, "CannotFreeze") then + output.FreezeChanceOnHit = 0 + else + output.FreezeChanceOnHit = m_min(100, modDB:Sum("BASE", skillCfg, "EnemyFreezeChance") + enemyDB:Sum("BASE", nil, "SelfFreezeChance")) + if modDB:Sum("FLAG", skillCfg, "CritsDontAlwaysFreeze") then + output.FreezeChanceOnCrit = output.FreezeChanceOnHit + end + end + + local function calcSecondaryEffectBase(type, sourceHitDmg, sourceCritDmg) + -- Calculate the inflict chance and base damage of a secondary effect (bleed/poison/ignite/shock/freeze) + local chanceOnHit, chanceOnCrit = output[type.."ChanceOnHit"], output[type.."ChanceOnCrit"] + local chanceFromHit = chanceOnHit * (1 - output.CritChance / 100) + local chanceFromCrit = chanceOnCrit * output.CritChance / 100 + local chance = chanceFromHit + chanceFromCrit + output[type.."Chance"] = chance + local baseFromHit = sourceHitDmg * chanceFromHit / (chanceFromHit + chanceFromCrit) + local baseFromCrit = sourceCritDmg * chanceFromCrit / (chanceFromHit + chanceFromCrit) + local baseVal = baseFromHit + baseFromCrit + if breakdown and chance ~= 0 then + local breakdownChance = { + s_format("Chance on Non-crit: %d%%", chanceOnHit), + s_format("Chance on Crit: %d%%", chanceOnCrit), + } + if chanceOnHit ~= chanceOnCrit then + t_insert(breakdownChance, "Combined chance:") + t_insert(breakdownChance, s_format("%d x (1 - %.4f) ^8(chance from non-crits)", chanceOnHit, output.CritChance/100)) + t_insert(breakdownChance, s_format("+ %d x %.4f ^8(chance from crits)", chanceOnCrit, output.CritChance/100)) + t_insert(breakdownChance, s_format("= %.2f", chance)) + end + breakdown[type.."Chance"] = breakdownChance + end + if breakdown and baseVal > 0 then + local breakdownDPS = breakdown[type.."DPS"] + if sourceHitDmg == sourceCritDmg then + t_insert(breakdownDPS, "Base damage:") + t_insert(breakdownDPS, s_format("%.1f ^8(source damage)",sourceHitDmg)) + else + if baseFromHit > 0 then + t_insert(breakdownDPS, "Base from Non-crits:") + t_insert(breakdownDPS, s_format("%.1f ^8(source damage from non-crits)", sourceHitDmg)) + t_insert(breakdownDPS, s_format("x %.3f ^8(portion of instances created by non-crits)", chanceFromHit / (chanceFromHit + chanceFromCrit))) + t_insert(breakdownDPS, s_format("= %.1f", baseFromHit)) + end + if baseFromCrit > 0 then + t_insert(breakdownDPS, "Base from Crits:") + t_insert(breakdownDPS, s_format("%.1f ^8(source damage from crits)", sourceCritDmg)) + t_insert(breakdownDPS, s_format("x %.3f ^8(portion of instances created by crits)", chanceFromCrit / (chanceFromHit + chanceFromCrit))) + t_insert(breakdownDPS, s_format("= %.1f", baseFromCrit)) + end + if baseFromHit > 0 and baseFromCrit > 0 then + t_insert(breakdownDPS, "Total base damage:") + t_insert(breakdownDPS, s_format("%.1f + %.1f", baseFromHit, baseFromCrit)) + t_insert(breakdownDPS, s_format("= %.1f", baseVal)) + end end end - local inc = modDB:Sum("INC", dotCfg, "Damage", "PhysicalDamage") - local more = round(modDB:Sum("MORE", dotCfg, "Damage", "PhysicalDamage"), 2) - output.BleedDPS = baseVal * (1 + inc/100) * more * effMult - output.BleedDuration = 5 * calcMod(modDB, dotCfg, "Duration") * debuffDurationMult - if breakdown then - breakdown.BleedDPS = { - "Base damage:", - s_format("%.1f ^8(average physical non-crit damage)", output.PhysicalAverage) + return baseVal + end + + -- Calculate bleeding chance and damage + skillFlags.bleed = false + if canDeal.Physical and (output.BleedChanceOnHit + output.BleedChanceOnCrit) > 0 then + local sourceHitDmg = output.PhysicalHitAverage + local sourceCritDmg = output.PhysicalCritAverage + if sourceHitDmg + sourceCritDmg > 0 then + skillFlags.bleed = true + skillFlags.duration = true + local dotCfg = { + slotName = skillCfg.slotName, + flags = bor(band(skillCfg.flags, ModFlag.SourceMask), ModFlag.Dot, skillData.dotIsSpell and ModFlag.Spell or 0), + keywordFlags = bor(skillCfg.keywordFlags, KeywordFlag.Bleed) } - if output.CritEffect ~= 1 then - t_insert(breakdown.BleedDPS, s_format("x %.3f ^8(crit effect modifier)", output.CritEffect)) + env.mainSkill.bleedCfg = dotCfg + if breakdown then + breakdown.BleedDPS = { } end - t_insert(breakdown.BleedDPS, "x 0.1 ^8(bleed deals 10% per second)") - t_insert(breakdown.BleedDPS, s_format("= %.1f", baseVal, 1)) - t_insert(breakdown.BleedDPS, "Bleed DPS:") - dotBreakdown(breakdown.BleedDPS, baseVal, inc, more, effMult, output.BleedDPS) - if output.BleedDuration ~= 5 then - breakdown.BleedDuration = { - "5.00s ^8(base duration)" - } - if output.DurationMod ~= 1 then - t_insert(breakdown.BleedDuration, s_format("x %.2f ^8(duration modifier)", calcMod(modDB, dotCfg, "Duration"))) + local baseVal = calcSecondaryEffectBase("Bleed", sourceHitDmg, sourceCritDmg) * 0.1 + local effMult = 1 + if env.mode_effective then + local resist = enemyDB:Sum("INC", nil, "PhysicalDamageReduction") + local taken = enemyDB:Sum("INC", dotCfg, "DamageTaken", "PhysicalDamageTaken", "DotTaken") + effMult = (1 - resist / 100) * (1 + taken / 100) + output["BleedEffMult"] = effMult + if breakdown and effMult ~= 1 then + breakdown.BleedEffMult = effMultBreakdown("Physical", resist, 0, taken, effMult) end - if debuffDurationMult ~= 1 then - t_insert(breakdown.BleedDuration, s_format("/ %.2f ^8(debuff expires slower/faster)", 1 / debuffDurationMult)) + end + local inc = modDB:Sum("INC", dotCfg, "Damage", "PhysicalDamage") + local more = round(modDB:Sum("MORE", dotCfg, "Damage", "PhysicalDamage"), 2) + output.BleedDPS = baseVal * (1 + inc/100) * more * effMult + local durationMod = calcMod(modDB, dotCfg, "Duration") + output.BleedDuration = 5 * durationMod * debuffDurationMult + if breakdown then + t_insert(breakdown.BleedDPS, "x 0.1 ^8(bleed deals 10% per second)") + t_insert(breakdown.BleedDPS, s_format("= %.1f", baseVal)) + t_insert(breakdown.BleedDPS, "Bleed DPS:") + dotBreakdown(breakdown.BleedDPS, baseVal, inc, more, effMult, output.BleedDPS) + if output.BleedDuration ~= 5 then + breakdown.BleedDuration = { + "5.00s ^8(base duration)" + } + if durationMod ~= 1 then + t_insert(breakdown.BleedDuration, s_format("x %.2f ^8(duration modifier)", durationMod)) + end + if debuffDurationMult ~= 1 then + t_insert(breakdown.BleedDuration, s_format("/ %.2f ^8(debuff expires slower/faster)", 1 / debuffDurationMult)) + end + t_insert(breakdown.BleedDuration, s_format("= %.2fs", output.BleedDuration)) end - t_insert(breakdown.BleedDuration, s_format("= %.2fs", output.BleedDuration)) end end end -- Calculate poison chance and damage skillFlags.poison = false - output.PoisonChance = m_min(100, modDB:Sum("BASE", skillCfg, "PoisonChance")) - if canDeal.Chaos and output.PoisonChance > 0 and (output.PhysicalAverage > 0 or output.ChaosAverage > 0) then - skillFlags.poison = true - skillFlags.duration = true - local dotCfg = { - slotName = skillCfg.slotName, - flags = bor(band(skillCfg.flags, ModFlag.SourceMask), ModFlag.Dot, skillData.dotIsSpell and ModFlag.Spell or 0), - keywordFlags = bor(skillCfg.keywordFlags, KeywordFlag.Poison) - } - env.mainSkill.poisonCfg = dotCfg - local poisonCritEffect = 1 - output.CritChance / 100 + output.CritChance / 100 * output.CritMultiplier * modDB:Sum("MORE", skillCfg, "PoisonDamageOnCrit") - local baseVal = (output.PhysicalAverage + output.ChaosAverage) * poisonCritEffect * 0.08 - local effMult = 1 - if env.mode_effective then - local resist = output["EnemyChaosResist"] - local taken = enemyDB:Sum("INC", nil, "DamageTaken", "ChaosDamageTaken", "DotTaken") - effMult = (1 - resist / 100) * (1 + taken / 100) - output["PoisonEffMult"] = effMult - if breakdown then - breakdown.PoisonEffMult = effMultBreakdown("Chaos", resist, 0, taken, effMult) - end - end - local inc = modDB:Sum("INC", dotCfg, "Damage", "ChaosDamage") - local more = round(modDB:Sum("MORE", dotCfg, "Damage", "ChaosDamage"), 2) - output.PoisonDPS = baseVal * (1 + inc/100) * more * effMult - local durationBase - if skillData.poisonDurationIsSkillDuration then - durationBase = skillData.duration - else - durationBase = 2 - end - output.PoisonDuration = durationBase * calcMod(modDB, dotCfg, "Duration") * debuffDurationMult - output.PoisonDamage = output.PoisonDPS * output.PoisonDuration - if breakdown then - breakdown.PoisonDPS = { } - if poisonCritEffect ~= output.CritEffect then - t_insert(breakdown.PoisonDPS, "Crit effect modifier for poison base damage:") - t_insert(breakdown.PoisonDPS, s_format("(1 - %g) ^8(portion of damage from non-crits)", output.CritChance/100)) - t_insert(breakdown.PoisonDPS, s_format("+ (%g x %g x %g) ^8(portion of damage from crits)", output.CritChance/100, output.CritMultiplier, modDB:Sum("MORE", skillCfg, "PoisonDamageOnCrit"))) - t_insert(breakdown.PoisonDPS, s_format("= %.3f", poisonCritEffect)) - end - t_insert(breakdown.PoisonDPS, "Base damage:") - t_insert(breakdown.PoisonDPS, s_format("%.1f ^8(average physical + chaos non-crit damage)", output.PhysicalAverage + output.ChaosAverage)) - if output.CritEffect ~= 1 then - t_insert(breakdown.PoisonDPS, s_format("x %.3f ^8(crit effect modifier)", poisonCritEffect)) - end - t_insert(breakdown.PoisonDPS, "x 0.08 ^8(poison deals 8% per second)") - t_insert(breakdown.PoisonDPS, s_format("= %.1f", baseVal, 1)) - t_insert(breakdown.PoisonDPS, "Poison DPS:") - dotBreakdown(breakdown.PoisonDPS, baseVal, inc, more, effMult, output.PoisonDPS) - if output.PoisonDuration ~= 2 then - breakdown.PoisonDuration = { - s_format("%.2fs ^8(base duration)", durationBase) - } - if output.DurationMod ~= 1 then - t_insert(breakdown.PoisonDuration, s_format("x %.2f ^8(duration modifier)", calcMod(modDB, dotCfg, "Duration"))) - end - if debuffDurationMult ~= 1 then - t_insert(breakdown.PoisonDuration, s_format("/ %.2f ^8(debuff expires slower/faster)", 1 / debuffDurationMult)) - end - t_insert(breakdown.PoisonDuration, s_format("= %.2fs", output.PoisonDuration)) - end - breakdown.PoisonDamage = { - s_format("%.1f ^8(damage per second)", output.PoisonDPS), - s_format("x %.2fs ^8(poison duration)", output.PoisonDuration), - s_format("= %.1f ^8damage per poison stack", output.PoisonDamage), + if canDeal.Chaos and (output.PoisonChanceOnHit + output.PoisonChanceOnCrit) > 0 then + local sourceHitDmg = output.PhysicalHitAverage + output.ChaosHitAverage + local sourceCritDmg = output.PhysicalCritAverage + output.ChaosCritAverage + if sourceHitDmg + sourceCritDmg > 0 then + skillFlags.poison = true + skillFlags.duration = true + local dotCfg = { + slotName = skillCfg.slotName, + flags = bor(band(skillCfg.flags, ModFlag.SourceMask), ModFlag.Dot, skillData.dotIsSpell and ModFlag.Spell or 0), + keywordFlags = bor(skillCfg.keywordFlags, KeywordFlag.Poison) } + env.mainSkill.poisonCfg = dotCfg + if breakdown then + breakdown.PoisonDPS = { } + end + local baseVal = calcSecondaryEffectBase("Poison", sourceHitDmg, sourceCritDmg * modDB:Sum("MORE", skillCfg, "PoisonDamageOnCrit")) * 0.08 + local effMult = 1 + if env.mode_effective then + local resist = output["EnemyChaosResist"] + local taken = enemyDB:Sum("INC", nil, "DamageTaken", "ChaosDamageTaken", "DotTaken") + effMult = (1 - resist / 100) * (1 + taken / 100) + output["PoisonEffMult"] = effMult + if breakdown then + breakdown.PoisonEffMult = effMultBreakdown("Chaos", resist, 0, taken, effMult) + end + end + local inc = modDB:Sum("INC", dotCfg, "Damage", "ChaosDamage") + local more = round(modDB:Sum("MORE", dotCfg, "Damage", "ChaosDamage"), 2) + output.PoisonDPS = baseVal * (1 + inc/100) * more * effMult + local durationBase + if skillData.poisonDurationIsSkillDuration then + durationBase = skillData.duration + else + durationBase = 2 + end + local durationMod = calcMod(modDB, dotCfg, "Duration") + output.PoisonDuration = durationBase * durationMod * debuffDurationMult + output.PoisonDamage = output.PoisonDPS * output.PoisonDuration + if breakdown then + t_insert(breakdown.PoisonDPS, "x 0.08 ^8(poison deals 8% per second)") + t_insert(breakdown.PoisonDPS, s_format("= %.1f", baseVal, 1)) + t_insert(breakdown.PoisonDPS, "Poison DPS:") + dotBreakdown(breakdown.PoisonDPS, baseVal, inc, more, effMult, output.PoisonDPS) + if output.PoisonDuration ~= 2 then + breakdown.PoisonDuration = { + s_format("%.2fs ^8(base duration)", durationBase) + } + if durationMod ~= 1 then + t_insert(breakdown.PoisonDuration, s_format("x %.2f ^8(duration modifier)", durationMod)) + end + if debuffDurationMult ~= 1 then + t_insert(breakdown.PoisonDuration, s_format("/ %.2f ^8(debuff expires slower/faster)", 1 / debuffDurationMult)) + end + t_insert(breakdown.PoisonDuration, s_format("= %.2fs", output.PoisonDuration)) + end + breakdown.PoisonDamage = { + s_format("%.1f ^8(damage per second)", output.PoisonDPS), + s_format("x %.2fs ^8(poison duration)", output.PoisonDuration), + s_format("= %.1f ^8damage per poison stack", output.PoisonDamage), + } + end end end -- Calculate ignite chance and damage skillFlags.ignite = false skillFlags.igniteCanStack = false - if modDB:Sum("FLAG", skillCfg, "CannotIgnite") then - output.IgniteChance = 0 - else - local igniteMode = env.configInput.igniteMode or "AVERAGE" - output.IgniteChance = m_min(100, modDB:Sum("BASE", skillCfg, "EnemyIgniteChance") + enemyDB:Sum("BASE", nil, "SelfIgniteChance")) - local sourceDmg = 0 + if canDeal.Fire and (output.IgniteChanceOnHit + output.IgniteChanceOnCrit) > 0 then + local sourceHitDmg = 0 + local sourceCritDmg = 0 if canDeal.Fire and not modDB:Sum("FLAG", skillCfg, "FireCannotIgnite") then - sourceDmg = sourceDmg + output.FireAverage + sourceHitDmg = sourceHitDmg + output.FireHitAverage + sourceCritDmg = sourceCritDmg + output.FireCritAverage end if canDeal.Cold and modDB:Sum("FLAG", skillCfg, "ColdCanIgnite") then - sourceDmg = sourceDmg + output.ColdAverage + sourceHitDmg = sourceHitDmg + output.ColdHitAverage + sourceCritDmg = sourceCritDmg + output.ColdCritAverage end - if canDeal.Fire and (output.IgniteChance > 0 or igniteMode == "CRIT") and sourceDmg > 0 then + if sourceHitDmg + sourceCritDmg > 0 then skillFlags.ignite = true local dotCfg = { slotName = skillCfg.slotName, @@ -2308,12 +2417,16 @@ local function performCalcs(env) keywordFlags = skillCfg.keywordFlags, } env.mainSkill.igniteCfg = dotCfg - local baseVal + local igniteMode = env.configInput.igniteMode or "AVERAGE" if igniteMode == "CRIT" then - baseVal = sourceDmg * output.CritMultiplier * 0.2 - else - baseVal = sourceDmg * output.CritEffect * 0.2 + output.IgniteChanceOnHit = 0 end + if breakdown then + breakdown.IgniteDPS = { + s_format("Ignite mode: %s ^8(can be changed in the Configuration tab)", igniteMode == "CRIT" and "Crit Damage" or "Average Damage") + } + end + local baseVal = calcSecondaryEffectBase("Ignite", sourceHitDmg, sourceCritDmg) * 0.2 local effMult = 1 if env.mode_effective then local resist = m_min(enemyDB:Sum("BASE", nil, "FireResist", "ElementalResist"), 75) @@ -2334,20 +2447,6 @@ local function performCalcs(env) skillFlags.igniteCanStack = true end if breakdown then - breakdown.IgniteDPS = { - s_format("Ignite mode: %s ^8(can be changed in the Configuration tab)", igniteMode == "CRIT" and "Crit Damage" or "Average Damage"), - "Base damage:", - s_format("%.1f ^8(average non-crit damage from sources)", sourceDmg), - } - if igniteMode == "CRIT" then - if output.CritMultiplier ~= 1 then - t_insert(breakdown.IgniteDPS, s_format("x %.2f ^8(crit multiplier)", output.CritMultiplier)) - end - else - if output.CritEffect ~= 1 then - t_insert(breakdown.IgniteDPS, s_format("x %.3f ^8(crit effect modifier)", output.CritEffect)) - end - end t_insert(breakdown.IgniteDPS, "x 0.2 ^8(ignite deals 20% per second)") t_insert(breakdown.IgniteDPS, s_format("= %.1f", baseVal, 1)) t_insert(breakdown.IgniteDPS, "Ignite DPS:") @@ -2377,43 +2476,59 @@ local function performCalcs(env) -- Calculate shock and freeze chance + duration modifier skillFlags.shock = false - if modDB:Sum("FLAG", skillCfg, "CannotShock") then - output.ShockChance = 0 - else - output.ShockChance = m_min(100, modDB:Sum("BASE", skillCfg, "EnemyShockChance") + enemyDB:Sum("BASE", nil, "SelfShockChance")) - local sourceDmg = 0 + if (output.ShockChanceOnHit + output.ShockChanceOnCrit) > 0 then + local sourceHitDmg = 0 + local sourceCritDmg = 0 if canDeal.Lightning and not modDB:Sum("FLAG", skillCfg, "LightningCannotShock") then - sourceDmg = sourceDmg + output.LightningAverage + sourceHitDmg = sourceHitDmg + output.LightningHitAverage + sourceCritDmg = sourceCritDmg + output.LightningCritAverage end if canDeal.Physical and modDB:Sum("FLAG", skillCfg, "PhysicalCanShock") then - sourceDmg = sourceDmg + output.PhysicalAverage + sourceHitDmg = sourceHitDmg + output.PhysicalHitAverage + sourceCritDmg = sourceCritDmg + output.PhysicalCritAverage end if canDeal.Fire and modDB:Sum("FLAG", skillCfg, "FireCanShock") then - sourceDmg = sourceDmg + output.FireAverage + sourceHitDmg = sourceHitDmg + output.FireHitAverage + sourceCritDmg = sourceCritDmg + output.FireCritAverage end if canDeal.Chaos and modDB:Sum("FLAG", skillCfg, "ChaosCanShock") then - sourceDmg = sourceDmg + output.ChaosAverage + sourceHitDmg = sourceHitDmg + output.ChaosHitAverage + sourceCritDmg = sourceCritDmg + output.ChaosCritAverage end - if output.ShockChance > 0 and sourceDmg > 0 then + if sourceHitDmg + sourceCritDmg > 0 then skillFlags.shock = true + if breakdown then + breakdown.ShockDPS = { } + end + local baseVal = calcSecondaryEffectBase("Shock", sourceHitDmg, sourceCritDmg) output.ShockDurationMod = 1 + modDB:Sum("INC", dotCfg, "EnemyShockDuration") / 100 + enemyDB:Sum("INC", nil, "SelfShockDuration") / 100 + if breakdown then + t_insert(breakdown.ShockDPS, s_format("For shock to apply, target must have no more than %d life.", baseVal * 20 * output.ShockDurationMod)) + end end end skillFlags.freeze = false - if modDB:Sum("FLAG", skillCfg, "CannotFreeze") then - output.FreezeChance = 0 - else - output.FreezeChance = m_min(100, modDB:Sum("BASE", skillCfg, "EnemyFreezeChance") + enemyDB:Sum("BASE", nil, "SelfFreezeChance")) - local sourceDmg = 0 + if (output.FreezeChanceOnHit + output.FreezeChanceOnCrit) > 0 then + local sourceHitDmg = 0 + local sourceCritDmg = 0 if canDeal.Cold and not modDB:Sum("FLAG", skillCfg, "ColdCannotFreeze") then - sourceDmg = sourceDmg + output.ColdAverage + sourceHitDmg = sourceHitDmg + output.ColdHitAverage + sourceCritDmg = sourceCritDmg + output.ColdCritAverage end if canDeal.Lightning and modDB:Sum("FLAG", skillCfg, "LightningCanFreeze") then - sourceDmg = sourceDmg + output.LightningAverage + sourceHitDmg = sourceHitDmg + output.LightningHitAverage + sourceCritDmg = sourceCritDmg + output.LightningCritAverage end - if output.FreezeChance > 0 and sourceDmg > 0 then + if sourceHitDmg + sourceCritDmg > 0 then skillFlags.freeze = true + if breakdown then + breakdown.FreezeDPS = { } + end + local baseVal = calcSecondaryEffectBase("Freeze", sourceHitDmg, sourceCritDmg) output.FreezeDurationMod = 1 + modDB:Sum("INC", dotCfg, "EnemyFreezeDuration") / 100 + enemyDB:Sum("INC", nil, "SelfFreezeDuration") / 100 + if breakdown then + t_insert(breakdown.FreezeDPS, s_format("For freeze to apply, target must have no more than %d life.", baseVal * 20 * output.FreezeDurationMod)) + end end end diff --git a/Modules/ModParser.lua b/Modules/ModParser.lua index dbcde714..22616a49 100644 --- a/Modules/ModParser.lua +++ b/Modules/ModParser.lua @@ -288,6 +288,7 @@ local modFlagList = { -- List of modifier flags/tags that appear at the start of a line local preFlagList = { ["^hits deal "] = { flags = ModFlag.Hit }, + ["^critical strikes deal "] = { tag = { type = "Condition", var = "CriticalStrike" } }, ["^minions have "] = { keywordFlags = KeywordFlag.Minion }, ["^minions deal "] = { keywordFlags = KeywordFlag.Minion }, ["^attacks used by totems have "] = { keywordFlags = KeywordFlag.Totem }, @@ -476,6 +477,8 @@ local specialModList = { ["(%d+)%% less totem damage per totem"] = function(num) return { mod("Damage", "MORE", -num, nil, 0, KeywordFlag.Totem, { type = "PerStat", stat = "ActiveTotemLimit", div = 1 }) } end, ["poison you inflict with critical strikes deals (%d+)%% more damage"] = function(num) return { mod("PoisonDamageOnCrit", "MORE", 100) } end, ["bleeding you inflict on maimed enemies deals (%d+)%% more damage"] = function(num) return { mod("Damage", "MORE", num, nil, 0, KeywordFlag.Bleed, { type = "Condition", var = "EnemyMaimed"}) } end, + ["critical strikes ignore enemy monster elemental resistances"] = { flag("IgnoreElementalResistances", { type = "Condition", var = "CriticalStrike" }) }, + ["non%-critical strikes penetrate (%d+)%% of enemy elemental resistances"] = function(num) return { mod("ElementalPenetration", "BASE", num, { type = "Condition", var = "CriticalStrike", neg = true }) } end, -- Special node types ["(%d+)%% of block chance applied to spells"] = function(num) return { mod("BlockChanceConv", "BASE", num) } end, ["(%d+)%% additional block chance with staves"] = function(num) return { mod("BlockChance", "BASE", num, { type = "Condition", var = "UsingStaff" }) } end, @@ -501,7 +504,7 @@ local specialModList = { ["spells have an additional projectile"] = { mod("ProjectileCount", "BASE", 1, nil, ModFlag.Spell) }, ["skills chain %+(%d) times"] = function(num) return { mod("ChainCount", "BASE", num) } end, ["reflects (%d+) physical damage to melee attackers"] = { }, - ["critical strikes with daggers have a (%d+)%% chance to poison the enemy"] = function(num) return { mod("PoisonChance", "BASE", 0.3, nil, ModFlag.Dagger, { type = "PerStat", stat = "CritChance", div = 1 }) } end, + ["critical strikes with daggers have a (%d+)%% chance to poison the enemy"] = function(num) return { mod("PoisonChance", "BASE", num, nil, ModFlag.Dagger, { type = "Condition", var = "CriticalStrike" }) } end, -- Special item local modifiers ["no physical damage"] = { mod("Misc", "LIST", { type = "WeaponData", key = "PhysicalMin" }), mod("Misc", "LIST", { type = "WeaponData", key = "PhysicalMax" }), mod("Misc", "LIST", { type = "WeaponData", key = "PhysicalDPS" }) }, ["all attacks with this weapon are critical strikes"] = { mod("Misc", "LIST", { type = "WeaponData", key = "critChance", value = 100 }) }, @@ -539,11 +542,16 @@ local specialModList = { ["your chaos damage can shock"] = { flag("ChaosCanShock") }, ["your physical damage can chill"] = { flag("PhysicalCanChill") }, ["your physical damage can shock"] = { flag("PhysicalCanShock") }, + ["critical strikes do not always freeze"] = { flag("CritsDontAlwaysFreeze") }, ["your chaos damage poisons enemies"] = { mod("PoisonChance", "BASE", 100) }, ["you can inflict up to (%d+) ignites on an enemy"] = { flag("IgniteCanStack") }, ["melee attacks cause bleeding"] = { mod("BleedChance", "BASE", 100, nil, ModFlag.Melee) }, ["melee attacks poison on hit"] = { mod("PoisonChance", "BASE", 100, nil, ModFlag.Melee) }, ["attacks cause bleeding when hitting cursed enemies"] = { mod("BleedChance", "BASE", 100, { type = "Condition", var = "EnemyCursed" }) }, + ["melee critical strikes cause bleeding"] = { mod("BleedChance", "BASE", 100, nil, ModFlag.Melee, { type = "Condition", var = "CriticalStrike" }) }, + ["melee critical strikes have (%d+)%% chance to cause bleeding"] = function(num) return { mod("BleedChance", "BASE", num, nil, ModFlag.Melee, { type = "Condition", var = "CriticalStrike" }) } end, + ["melee critical strikes have (%d+)%% chance to poison the enemy"] = function(num) return { mod("PoisonChance", "BASE", num, nil, ModFlag.Melee, { type = "Condition", var = "CriticalStrike" }) } end, + ["causes bleeding on melee critical strike"] = { mod("BleedChance", "BASE", num, nil, ModFlag.Melee, { type = "Condition", var = "CriticalStrike" }) }, ["traps and mines deal (%d+)%-(%d+) additional physical damage"] = function(_, min, max) return { mod("PhysicalMin", "BASE", tonumber(min), nil, 0, bor(KeywordFlag.Trap, KeywordFlag.Mine)), mod("PhysicalMax", "BASE", tonumber(max), nil, 0, bor(KeywordFlag.Trap, KeywordFlag.Mine)) } end, ["traps and mines deal (%d+) to (%d+) additional physical damage"] = function(_, min, max) return { mod("PhysicalMin", "BASE", tonumber(min), nil, 0, bor(KeywordFlag.Trap, KeywordFlag.Mine)), mod("PhysicalMax", "BASE", tonumber(max), nil, 0, bor(KeywordFlag.Trap, KeywordFlag.Mine)) } end, ["traps and mines have a (%d+)%% chance to poison on hit"] = function(num) return { mod("PoisonChance", "BASE", num, nil, 0, bor(KeywordFlag.Trap, KeywordFlag.Mine)) } end, @@ -568,11 +576,12 @@ local specialModList = { ["armour is increased by uncapped fire resistance"] = { mod("Armour", "INC", 1, { type = "PerStat", stat = "FireResistTotal", div = 1 }) }, ["evasion rating is increased by uncapped cold resistance"] = { mod("Evasion", "INC", 1, { type = "PerStat", stat = "ColdResistTotal", div = 1 }) }, ["critical strike chance is increased by uncapped lightning resistance"] = { mod("CritChance", "INC", 1, { type = "PerStat", stat = "LightningResistTotal", div = 1 }) }, - ["critical strikes deal no damage"] = { flag("NoCritDamage") }, + ["critical strikes deal no damage"] = { mod("Damage", "MORE", -100, { type = "Condition", var = "CriticalStrike" }) }, ["enemies chilled by you take (%d+)%% increased burning damage"] = function(num) return { mod("Misc", "LIST", { type = "EnemyModifier", mod = mod("BurningDamageTaken", "INC", num) }, { type = "Condition", var = "EnemyChilled" }) } end, ["attacks with this weapon penetrate (%d+)%% elemental resistances"] = function(num) return { mod("ElementalPenetration", "BASE", num, { type = "Condition", var = "XHandAttack" }) } end, ["attacks with this weapon deal double damage to chilled enemies"] = { mod("Damage", "MORE", 100, nil, ModFlag.Hit, { type = "Condition", var = "XHandAttack" }, { type = "Condition", var = "EnemyChilled" }) }, ["(%d+)%% of maximum life converted to energy shield"] = function(num) return { mod("LifeConvertToEnergyShield", "BASE", num) } end, + ["non%-critical strikes deal (%d+)%% damage"] = function(num) return { mod("Damage", "MORE", -100+num, nil, ModFlag.Hit, { type = "Condition", var = "CriticalStrike", neg = true }) } end, } local keystoneList = { -- List of keystones that can be found on uniques diff --git a/README.md b/README.md index 8b34308f..32d7df8d 100644 --- a/README.md +++ b/README.md @@ -48,6 +48,25 @@ Head over to the [Releases](https://github.com/Openarl/PathOfBuilding/releases) ![ss3](https://cloud.githubusercontent.com/assets/19189971/18089780/f0ff234a-6f04-11e6-8c88-6193fe59a5c4.png) ## Changelog +### 1.2.35 - 2017/01/29 +With this update, the way the program handles the calculation of crit damage has been improved. +Damage for crits and non-crits are now calculated and tallied separately, and combined later, instead of only +calculating non-crit damage, and deriving crit damage from that. This has allowed for the following changes: + * Inevitable Judgement is now supported! + * Other modifiers that only apply to crit or non-crit damage are now supported: + * Choir of the Storm's increased lightning damage modifier + * Marylene's Fallacy's less damage on non-critical strikes +Additionally, the handling of secondary effects (bleed, poison, ignite, shock, and freeze) has been improved. +The calculations for base damage and overall chance to inflict can now handle having different chances to inflict on +crits and non-crits. This has allowed for the following changes: + * Ignite/shock/freeze calculations now account for the guaranteed chance to inflict on critical strike + * This will greatly improve the accuracy of ignite DPS calculations for crit-based builds when in "Average Damage" mode, + as ignite's base damage will be heavily skewed in favour of crit + * Modifiers that grant a chance to poison/bleed on crit are now supported and correctly simulated + * The existing support for Adder's Touch has been reworked to use the new system + * The base damage for shock and freeze is now calculated, and used to compute the maximum enemy life against + which those effects will be able to apply; the results appear in the breakdowns for Shock/Freeze Dur. Mod + ### 1.2.34 - 2017/01/27 * IIQ/IIR totals are now shown in the "Other Effects" section in the Calcs tab * Enabling the "on Consecrated Ground" option now applies the 4% life regen granted by that ground effect diff --git a/changelog.txt b/changelog.txt index f263543d..aae9d047 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,3 +1,21 @@ +VERSION[1.2.35][2017/01/29] +With this update, the way the program handles the calculation of crit damage has been improved. +Damage for crits and non-crits are now calculated and tallied separately, and combined later, instead of only +calculating non-crit damage, and deriving crit damage from that. This has allowed for the following changes: + * Inevitable Judgement is now supported! + * Other modifiers that only apply to crit or non-crit damage are now supported: + * Choir of the Storm's increased lightning damage modifier + * Marylene's Fallacy's less damage on non-critical strikes +Additionally, the handling of secondary effects (bleed, poison, ignite, shock, and freeze) has been improved. +The calculations for base damage and overall chance to inflict can now handle having different chances to inflict on +crits and non-crits. This has allowed for the following changes: + * Ignite/shock/freeze calculations now account for the guaranteed chance to inflict on critical strike + * This will greatly improve the accuracy of ignite DPS calculations for crit-based builds when in "Average Damage" mode, + as ignite's base damage will be heavily skewed in favour of crit + * Modifiers that grant a chance to poison/bleed on crit are now supported and correctly simulated + * The existing support for Adder's Touch has been reworked to use the new system + * The base damage for shock and freeze is now calculated, and used to compute the maximum enemy life against + which those effects will be able to apply; the results appear in the breakdowns for Shock/Freeze Dur. Mod VERSION[1.2.34][2017/01/27] * IIQ/IIR totals are now shown in the "Other Effects" section in the Calcs tab * Enabling the "on Consecrated Ground" option now applies the 4% life regen granted by that ground effect diff --git a/manifest.xml b/manifest.xml index 0b097cee..08e1e3de 100644 --- a/manifest.xml +++ b/manifest.xml @@ -1,13 +1,13 @@ - + - + @@ -44,13 +44,13 @@ - - + + - +