From 2982a010bd95b4d2d182d49c92b2558a220b3fbc Mon Sep 17 00:00:00 2001 From: wh98yap-hub Date: Thu, 25 Sep 2025 17:05:33 +0800 Subject: [PATCH 1/3] Update CalcOffence.lua --- src/Modules/CalcOffence.lua | 127 ++++++++++++++++++++++++++++++++++-- 1 file changed, 123 insertions(+), 4 deletions(-) diff --git a/src/Modules/CalcOffence.lua b/src/Modules/CalcOffence.lua index 12ea10c452..6084a46246 100644 --- a/src/Modules/CalcOffence.lua +++ b/src/Modules/CalcOffence.lua @@ -127,13 +127,103 @@ local function calcDamage(activeSkill, output, cfg, breakdown, damageType, typeF round(((baseMax * inc * more) * genericMoreMaxDamage + addMax) * moreMaxDamage) end +-- Calculate how much of a source damage type contributes to the target ailment type through conversion chain +local function calcAilmentConversionMultiplier(activeSkill, sourceDamageType, targetAilmentType) + local conversionTable = activeSkill.conversionTable + + -- Safety check: if no conversion table, no conversion is possible + if not conversionTable then + return 0 + end + + -- For poison, the target damage type is Chaos + local targetDamageType = "Chaos" + if targetAilmentType == "ignite" then + targetDamageType = "Fire" + elseif targetAilmentType == "chill" or targetAilmentType == "freeze" then + targetDamageType = "Cold" + elseif targetAilmentType == "shock" then + targetDamageType = "Lightning" + end + + -- If source equals target, check if it stays unconverted + if sourceDamageType == targetDamageType then + return conversionTable[sourceDamageType] and conversionTable[sourceDamageType].mult or 0 + end + + -- Calculate conversion multiplier through the damage type chain + -- PoE damage conversion order: Physical → Lightning → Cold → Fire → Chaos + local dmgTypeOrder = {"Physical", "Lightning", "Cold", "Fire", "Chaos"} + local sourceIndex, targetIndex + + for i, damageType in ipairs(dmgTypeOrder) do + if damageType == sourceDamageType then + sourceIndex = i + end + if damageType == targetDamageType then + targetIndex = i + end + end + + -- Can't convert backwards in the chain + if not sourceIndex or not targetIndex or sourceIndex >= targetIndex then + return 0 + end + + -- Calculate the multiplier through the conversion chain + local multiplier = 1.0 + for i = sourceIndex, targetIndex - 1 do + local fromType = dmgTypeOrder[i] + local toType = dmgTypeOrder[i + 1] + + -- Safety check: ensure conversion table structure exists + local convRate = 0 + if conversionTable[fromType] and conversionTable[fromType][toType] then + convRate = conversionTable[fromType][toType] + end + + -- Each step in the chain multiplies the conversion rate + multiplier = multiplier * convRate + + -- If any step has 0 conversion, the whole chain stops + if convRate == 0 then + return 0 + end + end + + return multiplier +end + local function calcAilmentSourceDamage(activeSkill, output, cfg, breakdown, damageType, typeFlags) local min, max = calcDamage(activeSkill, output, cfg, breakdown, damageType, typeFlags) local convMult = activeSkill.conversionTable[damageType].mult + + -- For ailment calculations, we need to consider damage that converts through the conversion chain + -- Check what ailment we're calculating by looking at typeFlags + local ailmentType = nil + if band(typeFlags, dmgTypeFlags.Chaos) ~= 0 then + ailmentType = "poison" + elseif band(typeFlags, dmgTypeFlags.Fire) ~= 0 then + ailmentType = "ignite" + end + + if ailmentType then + -- Calculate how much of this damage type contributes to the ailment through conversions + local ailmentContribution = calcAilmentConversionMultiplier(activeSkill, damageType, ailmentType) + if ailmentContribution > 0 then + convMult = ailmentContribution + end + end + if breakdown and convMult ~= 1 then t_insert(breakdown, "Source damage:") t_insert(breakdown, s_format("%d to %d ^8(total damage)", min, max)) - t_insert(breakdown, s_format("x %g ^8(%g%% converted to other damage types)", convMult, (1-convMult)*100)) + if ailmentType and convMult ~= activeSkill.conversionTable[damageType].mult then + local targetDamageType = ailmentType == "poison" and "chaos" or (ailmentType == "ignite" and "fire" or "unknown") + t_insert(breakdown, s_format("x %g ^8(%g%% converts to %s through conversion chain)", convMult, convMult*100, targetDamageType)) + else + t_insert(breakdown, s_format("x %g ^8(%g%% converted to other damage types)", convMult, (1-convMult)*100)) + end t_insert(breakdown, s_format("= %d to %d", min * convMult, max * convMult)) end return min * convMult, max * convMult @@ -3770,7 +3860,10 @@ function calcs.offence(env, actor, activeSkill) if not skillFlags.hit or skillModList:Flag(cfg, "CannotPoison") then output.PoisonChanceOnCrit = 0 else - output.PoisonChanceOnCrit = m_min(100, skillModList:Sum("BASE", cfg, "PoisonChance") + enemyDB:Sum("BASE", nil, "SelfPoisonChance")) + local regularPoisonChance = skillModList:Sum("BASE", cfg, "PoisonChance") + enemyDB:Sum("BASE", nil, "SelfPoisonChance") + local chaosPoisonChance = skillModList:Sum("BASE", cfg, "ChaosPoisonChance") + -- For conversion builds, we need to consider chaos poison chance as contributing to overall poison chance + output.PoisonChanceOnCrit = m_min(100, regularPoisonChance + chaosPoisonChance) end if not skillFlags.hit then output.ImpaleChanceOnCrit = 0 @@ -3788,12 +3881,27 @@ function calcs.offence(env, actor, activeSkill) else output.BleedChanceOnHit = m_min(100, skillModList:Sum("BASE", cfg, "BleedChance") + enemyDB:Sum("BASE", nil, "SelfBleedChance")) end + + -- Enable poison calculation for damage types that convert to chaos when chaos can poison + -- This handles cases like The Consuming Dark where Fire converts to Chaos and Chaos can poison + if skillModList:Sum("BASE", cfg, "ChaosPoisonChance") > 0 then + for _, damageType in ipairs({"Physical", "Lightning", "Cold", "Fire"}) do + local chaosMult = calcAilmentConversionMultiplier(activeSkill, damageType, "poison") + if chaosMult > 0 then + skillModList:NewMod(damageType.."CanPoison", "FLAG", true, "Conversion to Chaos", cfg.flags) + end + end + end + if not skillFlags.hit or skillModList:Flag(cfg, "CannotPoison") then output.PoisonChanceOnHit = 0 output.ChaosPoisonChance = 0 else - output.PoisonChanceOnHit = m_min(100, skillModList:Sum("BASE", cfg, "PoisonChance") + enemyDB:Sum("BASE", nil, "SelfPoisonChance")) - output.ChaosPoisonChance = m_min(100, skillModList:Sum("BASE", cfg, "ChaosPoisonChance")) + local regularPoisonChance = skillModList:Sum("BASE", cfg, "PoisonChance") + enemyDB:Sum("BASE", nil, "SelfPoisonChance") + local chaosPoisonChance = skillModList:Sum("BASE", cfg, "ChaosPoisonChance") + -- For conversion builds, we need to consider chaos poison chance as contributing to overall poison chance + output.PoisonChanceOnHit = m_min(100, regularPoisonChance + chaosPoisonChance) + output.ChaosPoisonChance = m_min(100, chaosPoisonChance) end -- Elemental Ailment Affliction Chance | Elemental Ailment Additionals for _, ailment in ipairs(elementalAilmentTypeList) do @@ -4622,6 +4730,17 @@ function calcs.offence(env, actor, activeSkill) for sub_pass = 1, 2 do dotCfg.skillCond["CriticalStrike"] = sub_pass ~= 1 + -- Enable ignite calculation for damage types that convert to fire when fire can ignite + -- This handles cases like Avatar of Fire or other conversion effects + if output.IgniteChanceOnHit + output.IgniteChanceOnCrit > 0 then + for _, damageType in ipairs({"Physical", "Lightning", "Cold", "Chaos"}) do + local fireMult = calcAilmentConversionMultiplier(activeSkill, damageType, "ignite") + if fireMult > 0 then + skillModList:NewMod(damageType.."CanIgnite", "FLAG", true, "Conversion to Fire", cfg.flags) + end + end + end + local totalMin, totalMax = 0, 0 if canDeal.Physical and skillModList:Flag(cfg, "PhysicalCanIgnite") then local min, max = calcAilmentSourceDamage(activeSkill, output, dotCfg, sub_pass == 1 and breakdown and breakdown.IgnitePhysical, "Physical", dmgTypeFlags.Fire) From 275d89ce6f6c5048ff6460e0c204cbb645816110 Mon Sep 17 00:00:00 2001 From: LocalIdentity Date: Fri, 17 Apr 2026 05:21:44 +1000 Subject: [PATCH 2/3] Revert to dev --- src/Modules/CalcOffence.lua | 129 ++---------------------------------- 1 file changed, 5 insertions(+), 124 deletions(-) diff --git a/src/Modules/CalcOffence.lua b/src/Modules/CalcOffence.lua index 6d2e143ef2..08ee962aac 100644 --- a/src/Modules/CalcOffence.lua +++ b/src/Modules/CalcOffence.lua @@ -138,104 +138,14 @@ local function calcDamage(activeSkill, output, cfg, breakdown, damageType, typeF round(((baseMax * inc * more) * genericMoreMaxDamage + addMax) * moreMaxDamage) end --- Calculate how much of a source damage type contributes to the target ailment type through conversion chain -local function calcAilmentConversionMultiplier(activeSkill, sourceDamageType, targetAilmentType) - local conversionTable = activeSkill.conversionTable - - -- Safety check: if no conversion table, no conversion is possible - if not conversionTable then - return 0 - end - - -- For poison, the target damage type is Chaos - local targetDamageType = "Chaos" - if targetAilmentType == "ignite" then - targetDamageType = "Fire" - elseif targetAilmentType == "chill" or targetAilmentType == "freeze" then - targetDamageType = "Cold" - elseif targetAilmentType == "shock" then - targetDamageType = "Lightning" - end - - -- If source equals target, check if it stays unconverted - if sourceDamageType == targetDamageType then - return conversionTable[sourceDamageType] and conversionTable[sourceDamageType].mult or 0 - end - - -- Calculate conversion multiplier through the damage type chain - -- PoE damage conversion order: Physical → Lightning → Cold → Fire → Chaos - local dmgTypeOrder = {"Physical", "Lightning", "Cold", "Fire", "Chaos"} - local sourceIndex, targetIndex - - for i, damageType in ipairs(dmgTypeOrder) do - if damageType == sourceDamageType then - sourceIndex = i - end - if damageType == targetDamageType then - targetIndex = i - end - end - - -- Can't convert backwards in the chain - if not sourceIndex or not targetIndex or sourceIndex >= targetIndex then - return 0 - end - - -- Calculate the multiplier through the conversion chain - local multiplier = 1.0 - for i = sourceIndex, targetIndex - 1 do - local fromType = dmgTypeOrder[i] - local toType = dmgTypeOrder[i + 1] - - -- Safety check: ensure conversion table structure exists - local convRate = 0 - if conversionTable[fromType] and conversionTable[fromType][toType] then - convRate = conversionTable[fromType][toType] - end - - -- Each step in the chain multiplies the conversion rate - multiplier = multiplier * convRate - - -- If any step has 0 conversion, the whole chain stops - if convRate == 0 then - return 0 - end - end - - return multiplier -end - local function calcAilmentSourceDamage(activeSkill, output, cfg, breakdown, damageType, typeFlags) local min, max = calcDamage(activeSkill, output, cfg, breakdown, damageType, typeFlags) local conversionTable = (cfg and cfg.conversionTable) or activeSkill.conversionTable - local convMult = activeSkill.conversionTable[damageType].mult - - -- For ailment calculations, we need to consider damage that converts through the conversion chain - -- Check what ailment we're calculating by looking at typeFlags - local ailmentType = nil - if band(typeFlags, dmgTypeFlags.Chaos) ~= 0 then - ailmentType = "poison" - elseif band(typeFlags, dmgTypeFlags.Fire) ~= 0 then - ailmentType = "ignite" - end - - if ailmentType then - -- Calculate how much of this damage type contributes to the ailment through conversions - local ailmentContribution = calcAilmentConversionMultiplier(activeSkill, damageType, ailmentType) - if ailmentContribution > 0 then - convMult = ailmentContribution - end - end - + local convMult = conversionTable[damageType].mult if breakdown and convMult ~= 1 then t_insert(breakdown, "Source damage:") t_insert(breakdown, s_format("%d to %d ^8(total damage)", min, max)) - if ailmentType and convMult ~= activeSkill.conversionTable[damageType].mult then - local targetDamageType = ailmentType == "poison" and "chaos" or (ailmentType == "ignite" and "fire" or "unknown") - t_insert(breakdown, s_format("x %g ^8(%g%% converts to %s through conversion chain)", convMult, convMult*100, targetDamageType)) - else - t_insert(breakdown, s_format("x %g ^8(%g%% converted to other damage types)", convMult, (1-convMult)*100)) - end + t_insert(breakdown, s_format("x %g ^8(%g%% converted to other damage types)", convMult, (1-convMult)*100)) t_insert(breakdown, s_format("= %d to %d", min * convMult, max * convMult)) end return min * convMult, max * convMult @@ -3994,10 +3904,7 @@ function calcs.offence(env, actor, activeSkill) if not skillFlags.hit or skillModList:Flag(cfg, "CannotPoison") then output.PoisonChanceOnCrit = 0 else - local regularPoisonChance = skillModList:Sum("BASE", cfg, "PoisonChance") + enemyDB:Sum("BASE", nil, "SelfPoisonChance") - local chaosPoisonChance = skillModList:Sum("BASE", cfg, "ChaosPoisonChance") - -- For conversion builds, we need to consider chaos poison chance as contributing to overall poison chance - output.PoisonChanceOnCrit = m_min(100, regularPoisonChance + chaosPoisonChance) + output.PoisonChanceOnCrit = m_min(100, skillModList:Sum("BASE", cfg, "PoisonChance") + enemyDB:Sum("BASE", nil, "SelfPoisonChance")) end if not skillFlags.hit then output.ImpaleChanceOnCrit = 0 @@ -4015,27 +3922,12 @@ function calcs.offence(env, actor, activeSkill) else output.BleedChanceOnHit = m_min(100, skillModList:Sum("BASE", cfg, "BleedChance") + enemyDB:Sum("BASE", nil, "SelfBleedChance")) end - - -- Enable poison calculation for damage types that convert to chaos when chaos can poison - -- This handles cases like The Consuming Dark where Fire converts to Chaos and Chaos can poison - if skillModList:Sum("BASE", cfg, "ChaosPoisonChance") > 0 then - for _, damageType in ipairs({"Physical", "Lightning", "Cold", "Fire"}) do - local chaosMult = calcAilmentConversionMultiplier(activeSkill, damageType, "poison") - if chaosMult > 0 then - skillModList:NewMod(damageType.."CanPoison", "FLAG", true, "Conversion to Chaos", cfg.flags) - end - end - end - if not skillFlags.hit or skillModList:Flag(cfg, "CannotPoison") then output.PoisonChanceOnHit = 0 output.ChaosPoisonChance = 0 else - local regularPoisonChance = skillModList:Sum("BASE", cfg, "PoisonChance") + enemyDB:Sum("BASE", nil, "SelfPoisonChance") - local chaosPoisonChance = skillModList:Sum("BASE", cfg, "ChaosPoisonChance") - -- For conversion builds, we need to consider chaos poison chance as contributing to overall poison chance - output.PoisonChanceOnHit = m_min(100, regularPoisonChance + chaosPoisonChance) - output.ChaosPoisonChance = m_min(100, chaosPoisonChance) + output.PoisonChanceOnHit = m_min(100, skillModList:Sum("BASE", cfg, "PoisonChance") + enemyDB:Sum("BASE", nil, "SelfPoisonChance")) + output.ChaosPoisonChance = m_min(100, skillModList:Sum("BASE", cfg, "ChaosPoisonChance")) end -- Elemental Ailment Affliction Chance | Elemental Ailment Additionals for _, ailment in ipairs(elementalAilmentTypeList) do @@ -4911,17 +4803,6 @@ function calcs.offence(env, actor, activeSkill) for sub_pass = 1, 2 do dotCfg.skillCond["CriticalStrike"] = sub_pass ~= 1 - -- Enable ignite calculation for damage types that convert to fire when fire can ignite - -- This handles cases like Avatar of Fire or other conversion effects - if output.IgniteChanceOnHit + output.IgniteChanceOnCrit > 0 then - for _, damageType in ipairs({"Physical", "Lightning", "Cold", "Chaos"}) do - local fireMult = calcAilmentConversionMultiplier(activeSkill, damageType, "ignite") - if fireMult > 0 then - skillModList:NewMod(damageType.."CanIgnite", "FLAG", true, "Conversion to Fire", cfg.flags) - end - end - end - local totalMin, totalMax = 0, 0 if canDeal.Physical and skillModList:Flag(cfg, "PhysicalCanIgnite") then local min, max = calcAilmentSourceDamage(activeSkill, output, dotCfg, sub_pass == 1 and breakdown and breakdown.IgnitePhysical, "Physical", dmgTypeFlags.flags.Fire) From c8b96f332611648d3ce5e8ad05a0bf247dde41d7 Mon Sep 17 00:00:00 2001 From: LocalIdentity Date: Fri, 17 Apr 2026 05:21:49 +1000 Subject: [PATCH 3/3] Fix --- src/Modules/CalcOffence.lua | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Modules/CalcOffence.lua b/src/Modules/CalcOffence.lua index 08ee962aac..2c14ea7ddf 100644 --- a/src/Modules/CalcOffence.lua +++ b/src/Modules/CalcOffence.lua @@ -4418,7 +4418,10 @@ function calcs.offence(env, actor, activeSkill) durationMod = m_max(durationMod, 0) globalOutput.PoisonDuration = durationBase * durationMod / rateMod * debuffDurationMult -- The chance any given hit applies poison - local poisonChance = output.PoisonChanceOnHit / 100 * (1 - output.CritChance / 100) + output.PoisonChanceOnCrit / 100 * output.CritChance / 100 + local chaosPoisonChance = (output.ChaosHitAverage or 0) > 0 and output.ChaosPoisonChance or 0 + local poisonChanceOnHit = m_min(100, output.PoisonChanceOnHit + chaosPoisonChance) + local poisonChanceOnCrit = m_min(100, output.PoisonChanceOnCrit + chaosPoisonChance) + local poisonChance = poisonChanceOnHit / 100 * (1 - output.CritChance / 100) + poisonChanceOnCrit / 100 * output.CritChance / 100 -- Handling of "inflict x additional poisons" local additionalPoisonStacks = 1