diff --git a/spec/System/TestAilments_spec.lua b/spec/System/TestAilments_spec.lua index 83f6ac5f2d..4b9b1f44a9 100644 --- a/spec/System/TestAilments_spec.lua +++ b/spec/System/TestAilments_spec.lua @@ -28,4 +28,56 @@ describe("TestAilments", function() assert.are.equals(1.1, build.calcsTab.mainOutput.PoisonEffMult) end) + + -- Pseudo-hits: a skill flagged with skillData.pseudoHitAilment inflicts that one + -- ailment as though dealing its hit damage, but the hit itself deals no damage + -- (e.g. Infernal Legion's burn). No in-tree skill sets the flag yet, so run the + -- calcs directly on an env with the flag injected. + describe("pseudo-hit ailment sources", function() + local function setupFireball(customMods) + build.skillsTab:PasteSocketGroup("Fireball 20/0 1\n") + runCallback("OnFrame") + build.configTab.input.customMods = customMods + build.configTab:BuildModList() + runCallback("OnFrame") + end + + local function performWith(pseudoHitAilment) + local calcs = build.calcsTab.calcs + local env = calcs.initEnv(build, "MAIN") + env.player.mainSkill.skillData.pseudoHitAilment = pseudoHitAilment + calcs.perform(env) + return env.player.output + end + + it("deals no hit damage but still applies its ailment", function() + setupFireball("100% chance to Ignite") + local real = performWith(nil) + assert.True(real.TotalDPS and real.TotalDPS > 0) + assert.True(real.IgniteDPS and real.IgniteDPS > 0) + local pseudo = performWith("Ignite") + assert.are.equals(0, pseudo.TotalDPS) + assert.are.equals(0, pseudo.AverageDamage) + -- the ailment still sees the full stored hit damage + assert.are.equals(real.IgniteDPS, pseudo.IgniteDPS) + end) + + it("does not let other on-hit ailments ride on the notional damage", function() + setupFireball("100% chance to Ignite\n100% chance to Poison on Hit\nAdds 50 to 70 Chaos Damage to Spells") + local real = performWith(nil) + assert.True(real.PoisonDPS and real.PoisonDPS > 0) + local pseudo = performWith("Ignite") + assert.True(not pseudo.PoisonDPS or pseudo.PoisonDPS == 0, "pseudo-hit must not produce poison DPS") + assert.are.equals(real.IgniteDPS, pseudo.IgniteDPS) + end) + + it("still scales with ailment magnitude", function() + setupFireball("100% chance to Ignite") + local base = performWith("Ignite").IgniteDPS + assert.True(base and base > 0) + setupFireball("100% chance to Ignite\n50% increased Magnitude of Ailments") + local scaled = performWith("Ignite").IgniteDPS + assert.True(math.abs(scaled / base - 1.5) < 1e-6, string.format("expected 1.5x, got %.6fx", scaled / base)) + end) + end) end) diff --git a/src/Modules/CalcOffence.lua b/src/Modules/CalcOffence.lua index b6de2bf85d..cc742f5661 100644 --- a/src/Modules/CalcOffence.lua +++ b/src/Modules/CalcOffence.lua @@ -4836,6 +4836,12 @@ function calcs.offence(env, actor, activeSkill) if not canDeal[damageType] then return false end + -- A pseudo-hit (e.g. Infernal Legion "ignites as though dealing X") is not a + -- real hit, so it only inflicts the one ailment it represents -- on-hit ailment + -- sources (a Poison support, etc.) can't ride along on its notional damage. + if skillData.pseudoHitAilment and ailmentType ~= skillData.pseudoHitAilment then + return false + end -- check against input valid types if ((defaultDamageTypes and defaultDamageTypes[damageType]) or (ailmentData[ailmentType] and damageType == ailmentData[ailmentType].associatedType)) then @@ -5488,8 +5494,9 @@ function calcs.offence(env, actor, activeSkill) end end - -- Calculate damaging ailment values - for _, damagingAilment in ipairs({"Bleed", "Poison", "Ignite"}) do + -- Calculate damaging ailment values. A pseudo-hit only seeds the ailment it + -- represents; that restriction lives in canDoAilment, so the loop stays general. + for _, damagingAilment in ipairs({ "Bleed", "Poison", "Ignite" }) do local damageType = data.defaultAilmentDamageTypes[damagingAilment]["DamageType"] if not canDeal[damageType] then for _, type in ipairs(dmgTypeList) do @@ -6133,6 +6140,16 @@ function calcs.offence(env, actor, activeSkill) end end + -- pseudoHitAilment: the hit deals no damage of its own (e.g. Infernal Legion + -- "ignites as though dealing X"), it only seeds its ailment, which was already + -- derived from the stored hit damage above. Zero the hit outputs so only the + -- ailment/DoT counts toward DPS. + if skillData.pseudoHitAilment then + output.AverageDamage = 0 + output.AverageHit = 0 + output.TotalDPS = 0 + end + -- Calculate combined DPS estimate, including DoTs local baseDPS = output[(skillData.showAverage and "AverageDamage") or "TotalDPS"] output.CombinedDPS = baseDPS