Skip to content
Open
1 change: 1 addition & 0 deletions docs/modSyntax.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ Used as a key, so you can reference this mod elsewhere in PoB. Can really be an
- "OVERRIDE": used when you want to ignore any calculations done on this mod and just use the value (e.g. "your resistances are 78%" from Loreweave)
- "FLAG": used for conditions. Value will be true/false when this type is used.
- When you need the "FLAG" ModType, consider using the function `flag(name, source, modFlags, keywordFlags, extraTags)` instead. This method shortens the code and clarifies the intent. For example, `flag("ZealotsOath", { type = "Condition", var = "UsingFlask" })` is the same as `mod("ZealotsOath", "FLAG", true, { type = "Condition", var = "UsingFlask" })`
- "MAX" and "MIN": used for values where only the highest or lowest value should take effect respectively. Examples are `"ImprovedMinionDamageAppliesToPlayer"` for "Increases and Reductions to Minion Damage apply ... at X% of their value" or `"PoisonStackLimit"` for "Cannot Poison Enemies with at least X Poisons on them"
### Value
This represents the raw value of the mod. When it's used in the skills to map from the skill data, this will be `nil`, as it pulls the number from the gem based on the level.
### Source
Expand Down
11 changes: 11 additions & 0 deletions src/Classes/ModStore.lua
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,17 @@ function ModStoreClass:Max(cfg, ...)
return max
end

function ModStoreClass:Min(cfg, ...)
local min
for _, value in ipairs(self:Tabulate("MIN", cfg, ...)) do
local val = self:EvalMod(value.mod, cfg)
if min == nil or val < min then
min = val
end
end
return min
end

---HasMod
--- Checks if a mod exists with the given properties.
--- Useful for determining if the other aggregate functions will find
Expand Down
4 changes: 2 additions & 2 deletions src/Data/ModCache.lua
Original file line number Diff line number Diff line change
Expand Up @@ -5034,7 +5034,7 @@ c["325% increased Energy Shield"]={{[1]={flags=0,keywordFlags=0,name="EnergyShie
c["33% chance to Blind nearby Enemies when gaining Her Blessing"]={{}," to Blind nearby Enemies when gaining Her Blessing "}
c["33% chance to Blind nearby Enemies when gaining Her Blessing 100% chance to Avoid being Ignited, Chilled or Frozen with Her Blessing"]={{[1]={flags=0,keywordFlags=0,name="AvoidIgnite",type="BASE",value=33}}," to Blind nearby Enemies when gaining Her Blessing 100% chance , Chilled or Frozen with Her Blessing "}
c["33% chance to gain a Frenzy Charge on Kill"]={nil,"a Frenzy Charge "}
c["33% chance to inflict an additional Poison on the same Target when you inflict Poison"]={{[1]={flags=0,keywordFlags=0,name="DoublePoisonChance",type="BASE",value=33}},nil}
c["33% chance to inflict an additional Poison on the same Target when you inflict Poison"]={{[1]={flags=0,keywordFlags=0,name="AdditionalPoisonChance",type="BASE",value=33}},nil}
c["33% increased Attack Damage against Bleeding Enemies"]={{[1]={[1]={actor="enemy",type="ActorCondition",var="Bleeding"},flags=1,keywordFlags=0,name="Damage",type="INC",value=33}},nil}
c["33% increased Attack Speed while Ignited"]={{[1]={[1]={type="Condition",var="Ignited"},flags=1,keywordFlags=0,name="Speed",type="INC",value=33}},nil}
c["33% increased Cast Speed"]={{[1]={flags=16,keywordFlags=0,name="Speed",type="INC",value=33}},nil}
Expand Down Expand Up @@ -5240,7 +5240,7 @@ c["40% chance to Suppress Spell Damage while your Off Hand is empty"]={{[1]={[1]
c["40% chance to cause Bleeding on Melee Hit"]={{[1]={flags=260,keywordFlags=0,name="BleedChance",type="BASE",value=40}},nil}
c["40% chance to deal Double Damage while Focused"]={{[1]={[1]={type="Condition",var="Focused"},flags=0,keywordFlags=0,name="DoubleDamageChance",type="BASE",value=40}},nil}
c["40% chance to gain a Frenzy Charge for each enemy you hit with a Critical Strike"]={nil,"a Frenzy Charge for each enemy you hit with a Critical Strike "}
c["40% chance to inflict an additional Poison on the same Target when you inflict Poison"]={{[1]={flags=0,keywordFlags=0,name="DoublePoisonChance",type="BASE",value=40}},nil}
c["40% chance to inflict an additional Poison on the same Target when you inflict Poison"]={{[1]={flags=0,keywordFlags=0,name="AdditionalPoisonChance",type="BASE",value=40}},nil}
c["40% chance when you Kill a Scorched Enemy to Burn Each surrounding Enemy for 4 seconds, dealing 8% of the Killed Enemy's Life as Fire Damage per second"]={{[1]={flags=0,keywordFlags=0,name="Life",type="BASE",value=40}}," when you Kill a Scorched Enemy to Burn Each surrounding Enemy , dealing 8% of the Killed Enemy's as Fire Damage per second "}
c["40% faster start of Energy Shield Recharge"]={{[1]={flags=0,keywordFlags=0,name="EnergyShieldRechargeFaster",type="INC",value=40}},nil}
c["40% faster start of Energy Shield Recharge while affected by Discipline"]={{[1]={[1]={type="Condition",var="AffectedByDiscipline"},flags=0,keywordFlags=0,name="EnergyShieldRechargeFaster",type="INC",value=40}},nil}
Expand Down
1 change: 1 addition & 0 deletions src/Data/SkillStatMap.lua
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,7 @@ return {
},
["cannot_poison_poisoned_enemies"] = {
flag("Condition:SinglePoison"),
mod("PoisonStackLimit", "MIN", 1),
},
["spell_damage_modifiers_apply_to_skill_dot"] = {
skill("dotIsSpell", true),
Expand Down
40 changes: 35 additions & 5 deletions src/Modules/CalcOffence.lua
Original file line number Diff line number Diff line change
Expand Up @@ -4363,12 +4363,35 @@ function calcs.offence(env, actor, activeSkill)
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 doublePoisonChance = 1 + m_min(skillModList:Sum("BASE", cfg, "DoublePoisonChance")/ 100, 1)
-- The average number of poisons that will be active on the enemy at once
local PoisonStacks = output.HitChance / 100 * poisonChance * doublePoisonChance * skillData.dpsMultiplier * (skillData.stackMultiplier or 1) * quantityMultiplier

-- Handling of "inflict x additional poisons"
local additionalPoisonStacks = 1
if not skillModList:Flag(nil, "CannotMultiplePoison") then
additionalPoisonStacks = 1 + m_min(skillModList:Sum("BASE", cfg, "AdditionalPoisonChance")/ 100, 1) + (skillModList:Sum("BASE", cfg, "AdditionalPoisonStacks"))
end

-- Calculate average number of poisons that will be active on the enemy at once
local poisonStackLimit = skillModList:Min(cfg, "PoisonStackLimit")
local PoisonStacks = output.HitChance / 100 * poisonChance * additionalPoisonStacks * skillData.dpsMultiplier * (skillData.stackMultiplier or 1) * quantityMultiplier
local uncappedPoisonStacks
if (globalOutput.HitSpeed or globalOutput.Speed) > 0 then
--assume skills with no cast, attack, or cooldown time are single cast
PoisonStacks = PoisonStacks * globalOutput.PoisonDuration * (globalOutput.HitSpeed or globalOutput.Speed)

-- If stack limit exists, avg. poison stack is more complicated
if poisonStackLimit and poisonStackLimit > 0 and PoisonStacks > poisonStackLimit then
-- Calc number of avg. poisons applied per hit (without hit rate multipliers)
local singleHitPoisonChance = output.HitChance / 100 * poisonChance
local singleHitPoisonStacks = singleHitPoisonChance * additionalPoisonStacks

-- Calc how many hits will poison before limit is reached and theoretical max poison stacks, which is different from `poisonStackLimit` due to "additional" poison mechanics
local numPoisoningHits = m_ceil(poisonStackLimit / singleHitPoisonStacks)
local maxPoisonStacks = numPoisoningHits * singleHitPoisonStacks

-- Only use `maxPoisonStacks` if original value exceeds it
uncappedPoisonStacks = m_max(PoisonStacks, maxPoisonStacks)
PoisonStacks = m_min(PoisonStacks, maxPoisonStacks)
end
end
if PoisonStacks < 1 and (env.configInput.multiplierPoisonOnEnemy or 0) <= 1 then
skillModList:NewMod("Condition:SinglePoison", "FLAG", true, "poison")
Expand All @@ -4379,15 +4402,22 @@ function calcs.offence(env, actor, activeSkill)
base = { "%.2fs ^8(poison duration)", globalOutput.PoisonDuration },
{ "%.2f ^8(poison chance)", poisonChance },
{ "%.2f ^8(hit chance)", output.HitChance / 100 },
{ "%.2f ^8(double poison chance)", doublePoisonChance },
{ "%.2f ^8(avg. # of poisons inflicted)", additionalPoisonStacks },
{ "%.2f ^8(hits per second)", globalOutput.HitSpeed or globalOutput.Speed },
{ "%g ^8(dps multiplier for this skill)", skillData.dpsMultiplier or 1 },
{ "%g ^8(stack multiplier for this skill)", skillData.stackMultiplier or 1 },
{ "%g ^8(quantity multiplier for this skill)", quantityMultiplier },
total = s_format("= %.2f", PoisonStacks),
})
if skillModList:Flag(nil, "Condition:SinglePoison") then
t_insert(globalBreakdown.PoisonStacks, "Capped to 1")
t_insert(globalBreakdown.PoisonStacks, "Assuming 'non-Poisoned' Enemy")
end
if poisonStackLimit and PoisonStacks >= poisonStackLimit then
t_insert(globalBreakdown.PoisonStacks, "^8(affected by poison stack limit of: " .. poisonStackLimit .. ")")
if uncappedPoisonStacks then
t_insert(globalBreakdown.PoisonStacks, "^8(uncapped poison stacks: " .. s_format("%.2f", uncappedPoisonStacks) .. ")")
end

end
end
for sub_pass = 1, 2 do
Expand Down
11 changes: 10 additions & 1 deletion src/Modules/CalcSections.lua
Original file line number Diff line number Diff line change
Expand Up @@ -916,7 +916,16 @@ return {
{ label = "Main Hand", flag = "weapon1Attack", modName = "PoisonChance", modType = "BASE", cfg = "weapon1" },
{ label = "Off Hand", flag = "weapon2Attack", modName = "PoisonChance", modType = "BASE", cfg = "weapon2" },
}, },
{ label = "Poison Stacks", { format = "{2:output:PoisonStacks}", { breakdown = "PoisonStacks" } }},
{ label = "Poison Stacks", { format = "{2:output:PoisonStacks}",
{ breakdown = "PoisonStacks" },
{ label = "% chance to inflict 1 additional poison", notFlag = "attack", modName = { "AdditionalPoisonChance" }, modType = "BASE", cfg = "skill" },
{ label = "% chance to inflict 1 additional poison (Main Hand)", flag = "weapon1Attack", modName = { "AdditionalPoisonChance" }, modType = "BASE", cfg = "weapon1" },
{ label = "% chance to inflict 1 additional poison (Off Hand)", flag = "weapon2Attack", modName = { "AdditionalPoisonChance" }, modType = "BASE", cfg = "weapon2" },
{ label = "inflict # additional poisons", notFlag = "attack", modName = { "AdditionalPoisonStacks" }, modType = "BASE", cfg = "skill" },
{ label = "inflict # additional poisons (Main Hand)", flag = "weapon1Attack", modName = { "AdditionalPoisonStacks" }, modType = "BASE", cfg = "weapon1" },
{ label = "inflict # additional poisons (Off Hand)", flag = "weapon2Attack", modName = { "AdditionalPoisonStacks" }, modType = "BASE", cfg = "weapon2" },
{ label = "Poison Stack Limits", modName = { "PoisonStackLimit", "CannotMultiplePoison" }, 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 = "Eff. DoT Multi", notFlag = "attack", haveOutput = "PoisonDotMulti", { format = "x {2:output:PoisonDotMulti}", { breakdown = "PoisonDotMulti" }, { modName = { "DotMultiplier", "ChaosDotMultiplier" }, cfg = "poison" }, }, },
Expand Down
6 changes: 5 additions & 1 deletion src/Modules/ModParser.lua
Original file line number Diff line number Diff line change
Expand Up @@ -3686,7 +3686,11 @@ local specialModList = {
mod("Damage", "INC", num, nil, 0, KeywordFlag.Poison, { type = "Condition", var = "SinglePoison" }, { type = "SkillName", skillNameList = { "Sunder", "Ground Slam" }, includeTransfigured = true })
} end,
["poisons on you expire (%d+)%% slower"] = function(num) return { mod("SelfPoisonDebuffExpirationRate", "BASE", -num) } end,
["(%d+)%% chance to inflict an additional poison on the same target when you inflict poison"] = function(num) return { mod("DoublePoisonChance", "BASE", num) } end,
["(%d+)%% chance to inflict an additional poison on the same target when you inflict poison"] = function(num) return { mod("AdditionalPoisonChance", "BASE", num) } end,
["inflict (%d+) additional poisons? on the same target when you inflict poisons? with this weapon"] = function(num) return { mod("AdditionalPoisonStacks", "BASE", num, { type = "Condition", var = "{Hand}Attack" } ) } end,
["cannot poison enemies with at least (%d+) poisons? on them"] = function(num) return { mod("PoisonStackLimit", "MIN", num ) } end,
["cannot inflict multiple poisons in the same hit"] = { flag("CannotMultiplePoison") },
["wither on hit with this weapon against enemies with at least (%d+) poisons on them"] = { flag("Condition:CanWither") },
-- Suppression
["y?o?u?r? ?chance to suppress spell damage is lucky"] = { flag("SpellSuppressionChanceIsLucky") },
["y?o?u?r? ?chance to suppress spell damage is unlucky"] = { flag("SpellSuppressionChanceIsUnlucky") },
Expand Down