Skip to content

Commit a5dd258

Browse files
majochemLocalIdentity
andauthored
Add support for "Fleshrender" unique (and general poison stacking mechanics) (#9533)
* Rename `doublePoisonChance` to `additionalPoisonChance` Renamed for clarity as it is not actually a "doubling" effect * Add additional poisons and poison stack limits - parsing and logic for application of more than 1 additional poison per hit via "inflict X additional poisons on the same target ..." - parsing and logic for poison stack limit via "cannot poison enemies with at least X poisons on them" - adds parsing and logic for additional `CannotMultiplie` flag that will apply to Viper Strike of the Mamba as of 3.28 * Adjust `PoisonStacks` breakdown * Add parsing for "Wither on Hit with this weapon against Enemies with at least 12 Poisons on them" Just setting `Condition:CanWither` flag for now, as actually checking for number of poisons seems overkill and eliminates too many edge cases * Add `ModStoreClass:Min()` to `ModStore.lua` This is the counterpart to `ModStoreClass:Max()` and gives back the lowest modifier value. This makes sense for things like the poison stack limit, where neither "BASE" nor "OVERRIDE" would work for multiple mods * Change `PoisonStackLimit` to use `"MIN"` modType * Add `"MAX"` & `"MIN"` modType explanation to docs "MAX" already existed and was used, but was missing from `modSyntax.md`, so I added the explanation for both now * Add `PoisonStackLimit` to Viper Strike of the Mamba * Further improve PoisonStacks breakdown - Now shows active poison stack limit - Now shows what uncapped poison stacks would be (to gauge if you're close to being under cap) - Changed wording of "Capped to 1" for `Condition:SinglePoison` hint to "Assume non-Poisoned Enemy" because that is what it is actually used for * Appease the spell checking gods `hitrate` -> `hit rate` --------- Co-authored-by: LocalIdentity <localidentity2@gmail.com>
1 parent 1826a83 commit a5dd258

7 files changed

Lines changed: 66 additions & 10 deletions

File tree

docs/modSyntax.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ Used as a key, so you can reference this mod elsewhere in PoB. Can really be an
1313
- "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)
1414
- "FLAG": used for conditions. Value will be true/false when this type is used.
1515
- 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" })`
16+
- "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"
1617
### Value
1718
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.
1819
### Source

src/Classes/ModStore.lua

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,17 @@ function ModStoreClass:Max(cfg, ...)
220220
return max
221221
end
222222

223+
function ModStoreClass:Min(cfg, ...)
224+
local min
225+
for _, value in ipairs(self:Tabulate("MIN", cfg, ...)) do
226+
local val = self:EvalMod(value.mod, cfg)
227+
if min == nil or val < min then
228+
min = val
229+
end
230+
end
231+
return min
232+
end
233+
223234
---HasMod
224235
--- Checks if a mod exists with the given properties.
225236
--- Useful for determining if the other aggregate functions will find

src/Data/ModCache.lua

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5086,7 +5086,7 @@ c["325% increased Energy Shield"]={{[1]={flags=0,keywordFlags=0,name="EnergyShie
50865086
c["33% chance to Blind nearby Enemies when gaining Her Blessing"]={{}," to Blind nearby Enemies when gaining Her Blessing "}
50875087
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 "}
50885088
c["33% chance to gain a Frenzy Charge on Kill"]={nil,"a Frenzy Charge "}
5089-
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}
5089+
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}
50905090
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}
50915091
c["33% increased Attack Speed while Ignited"]={{[1]={[1]={type="Condition",var="Ignited"},flags=1,keywordFlags=0,name="Speed",type="INC",value=33}},nil}
50925092
c["33% increased Cast Speed"]={{[1]={flags=16,keywordFlags=0,name="Speed",type="INC",value=33}},nil}
@@ -5291,7 +5291,7 @@ c["40% chance to Suppress Spell Damage while your Off Hand is empty"]={{[1]={[1]
52915291
c["40% chance to cause Bleeding on Melee Hit"]={{[1]={flags=260,keywordFlags=0,name="BleedChance",type="BASE",value=40}},nil}
52925292
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}
52935293
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 "}
5294-
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}
5294+
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}
52955295
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 "}
52965296
c["40% faster start of Energy Shield Recharge"]={{[1]={flags=0,keywordFlags=0,name="EnergyShieldRechargeFaster",type="INC",value=40}},nil}
52975297
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}

src/Data/SkillStatMap.lua

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -150,9 +150,10 @@ return {
150150
},
151151
["cannot_poison_poisoned_enemies"] = {
152152
flag("Condition:SinglePoison"),
153+
mod("PoisonStackLimit", "MIN", 1),
153154
},
154155
["cannot_inflict_additional_poisons"] = {
155-
flag("CannotMultiPoison"),
156+
flag("CannotMultiplePoison"),
156157
},
157158
["spell_damage_modifiers_apply_to_skill_dot"] = {
158159
skill("dotIsSpell", true),

src/Modules/CalcOffence.lua

Lines changed: 35 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4412,12 +4412,35 @@ function calcs.offence(env, actor, activeSkill)
44124412
globalOutput.PoisonDuration = durationBase * durationMod / rateMod * debuffDurationMult
44134413
-- The chance any given hit applies poison
44144414
local poisonChance = output.PoisonChanceOnHit / 100 * (1 - output.CritChance / 100) + output.PoisonChanceOnCrit / 100 * output.CritChance / 100
4415-
local doublePoisonChance = 1 + (skillModList:Flag(nil, "CannotMultiPoison") and 0 or m_min(skillModList:Sum("BASE", cfg, "DoublePoisonChance")/ 100, 1))
4416-
-- The average number of poisons that will be active on the enemy at once
4417-
local PoisonStacks = output.HitChance / 100 * poisonChance * doublePoisonChance * skillData.dpsMultiplier * (skillData.stackMultiplier or 1) * quantityMultiplier
4415+
4416+
-- Handling of "inflict x additional poisons"
4417+
local additionalPoisonStacks = 1
4418+
if not skillModList:Flag(nil, "CannotMultiplePoison") then
4419+
additionalPoisonStacks = 1 + m_min(skillModList:Sum("BASE", cfg, "AdditionalPoisonChance")/ 100, 1) + (skillModList:Sum("BASE", cfg, "AdditionalPoisonStacks"))
4420+
end
4421+
4422+
-- Calculate average number of poisons that will be active on the enemy at once
4423+
local poisonStackLimit = skillModList:Min(cfg, "PoisonStackLimit")
4424+
local PoisonStacks = output.HitChance / 100 * poisonChance * additionalPoisonStacks * skillData.dpsMultiplier * (skillData.stackMultiplier or 1) * quantityMultiplier
4425+
local uncappedPoisonStacks
44184426
if (globalOutput.HitSpeed or globalOutput.Speed) > 0 then
44194427
--assume skills with no cast, attack, or cooldown time are single cast
44204428
PoisonStacks = PoisonStacks * globalOutput.PoisonDuration * (globalOutput.HitSpeed or globalOutput.Speed)
4429+
4430+
-- If stack limit exists, avg. poison stack is more complicated
4431+
if poisonStackLimit and poisonStackLimit > 0 and PoisonStacks > poisonStackLimit then
4432+
-- Calc number of avg. poisons applied per hit (without hit rate multipliers)
4433+
local singleHitPoisonChance = output.HitChance / 100 * poisonChance
4434+
local singleHitPoisonStacks = singleHitPoisonChance * additionalPoisonStacks
4435+
4436+
-- 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
4437+
local numPoisoningHits = m_ceil(poisonStackLimit / singleHitPoisonStacks)
4438+
local maxPoisonStacks = numPoisoningHits * singleHitPoisonStacks
4439+
4440+
-- Only use `maxPoisonStacks` if original value exceeds it
4441+
uncappedPoisonStacks = m_max(PoisonStacks, maxPoisonStacks)
4442+
PoisonStacks = m_min(PoisonStacks, maxPoisonStacks)
4443+
end
44214444
end
44224445
if PoisonStacks < 1 and (env.configInput.multiplierPoisonOnEnemy or 0) <= 1 then
44234446
skillModList:NewMod("Condition:SinglePoison", "FLAG", true, "poison")
@@ -4428,15 +4451,22 @@ function calcs.offence(env, actor, activeSkill)
44284451
base = { "%.2fs ^8(poison duration)", globalOutput.PoisonDuration },
44294452
{ "%.2f ^8(poison chance)", poisonChance },
44304453
{ "%.2f ^8(hit chance)", output.HitChance / 100 },
4431-
{ "%.2f ^8(double poison chance)", doublePoisonChance },
4454+
{ "%.2f ^8(avg. # of poisons inflicted)", additionalPoisonStacks },
44324455
{ "%.2f ^8(hits per second)", globalOutput.HitSpeed or globalOutput.Speed },
44334456
{ "%g ^8(dps multiplier for this skill)", skillData.dpsMultiplier or 1 },
44344457
{ "%g ^8(stack multiplier for this skill)", skillData.stackMultiplier or 1 },
44354458
{ "%g ^8(quantity multiplier for this skill)", quantityMultiplier },
44364459
total = s_format("= %.2f", PoisonStacks),
44374460
})
44384461
if skillModList:Flag(nil, "Condition:SinglePoison") then
4439-
t_insert(globalBreakdown.PoisonStacks, "Capped to 1")
4462+
t_insert(globalBreakdown.PoisonStacks, "Assuming 'non-Poisoned' Enemy")
4463+
end
4464+
if poisonStackLimit and PoisonStacks >= poisonStackLimit then
4465+
t_insert(globalBreakdown.PoisonStacks, "^8(affected by poison stack limit of: " .. poisonStackLimit .. ")")
4466+
if uncappedPoisonStacks then
4467+
t_insert(globalBreakdown.PoisonStacks, "^8(uncapped poison stacks: " .. s_format("%.2f", uncappedPoisonStacks) .. ")")
4468+
end
4469+
44404470
end
44414471
end
44424472
for sub_pass = 1, 2 do

src/Modules/CalcSections.lua

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -916,7 +916,16 @@ return {
916916
{ label = "Main Hand", flag = "weapon1Attack", modName = "PoisonChance", modType = "BASE", cfg = "weapon1" },
917917
{ label = "Off Hand", flag = "weapon2Attack", modName = "PoisonChance", modType = "BASE", cfg = "weapon2" },
918918
}, },
919-
{ label = "Poison Stacks", { format = "{2:output:PoisonStacks}", { breakdown = "PoisonStacks" } }},
919+
{ label = "Poison Stacks", { format = "{2:output:PoisonStacks}",
920+
{ breakdown = "PoisonStacks" },
921+
{ label = "% chance to inflict 1 additional poison", notFlag = "attack", modName = { "AdditionalPoisonChance" }, modType = "BASE", cfg = "skill" },
922+
{ label = "% chance to inflict 1 additional poison (Main Hand)", flag = "weapon1Attack", modName = { "AdditionalPoisonChance" }, modType = "BASE", cfg = "weapon1" },
923+
{ label = "% chance to inflict 1 additional poison (Off Hand)", flag = "weapon2Attack", modName = { "AdditionalPoisonChance" }, modType = "BASE", cfg = "weapon2" },
924+
{ label = "inflict # additional poisons", notFlag = "attack", modName = { "AdditionalPoisonStacks" }, modType = "BASE", cfg = "skill" },
925+
{ label = "inflict # additional poisons (Main Hand)", flag = "weapon1Attack", modName = { "AdditionalPoisonStacks" }, modType = "BASE", cfg = "weapon1" },
926+
{ label = "inflict # additional poisons (Off Hand)", flag = "weapon2Attack", modName = { "AdditionalPoisonStacks" }, modType = "BASE", cfg = "weapon2" },
927+
{ label = "Poison Stack Limits", modName = { "PoisonStackLimit", "CannotMultiplePoison" }, cfg = "skill" },
928+
}, },
920929
{ label = "Total Increased", { format = "{0:mod:1}%", { modName = { "Damage", "ChaosDamage" }, modType = "INC", cfg = "poison" }, }, },
921930
{ label = "Total More", { format = "{0:mod:1}%", { modName = { "Damage", "ChaosDamage" }, modType = "MORE", cfg = "poison" }, }, },
922931
{ label = "Eff. DoT Multi", notFlag = "attack", haveOutput = "PoisonDotMulti", { format = "x {2:output:PoisonDotMulti}", { breakdown = "PoisonDotMulti" }, { modName = { "DotMultiplier", "ChaosDotMultiplier" }, cfg = "poison" }, }, },

src/Modules/ModParser.lua

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3725,7 +3725,11 @@ local specialModList = {
37253725
mod("Damage", "INC", num, nil, 0, KeywordFlag.Poison, { type = "Condition", var = "SinglePoison" }, { type = "SkillName", skillNameList = { "Sunder", "Ground Slam" }, includeTransfigured = true })
37263726
} end,
37273727
["poisons on you expire (%d+)%% slower"] = function(num) return { mod("SelfPoisonDebuffExpirationRate", "BASE", -num) } end,
3728-
["(%d+)%% chance to inflict an additional poison on the same target when you inflict poison"] = function(num) return { mod("DoublePoisonChance", "BASE", num) } end,
3728+
["(%d+)%% chance to inflict an additional poison on the same target when you inflict poison"] = function(num) return { mod("AdditionalPoisonChance", "BASE", num) } end,
3729+
["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,
3730+
["cannot poison enemies with at least (%d+) poisons? on them"] = function(num) return { mod("PoisonStackLimit", "MIN", num ) } end,
3731+
["cannot inflict multiple poisons in the same hit"] = { flag("CannotMultiplePoison") },
3732+
["wither on hit with this weapon against enemies with at least (%d+) poisons on them"] = { flag("Condition:CanWither") },
37293733
-- Suppression
37303734
["y?o?u?r? ?chance to suppress spell damage is lucky"] = { flag("SpellSuppressionChanceIsLucky") },
37313735
["y?o?u?r? ?chance to suppress spell damage is unlucky"] = { flag("SpellSuppressionChanceIsUnlucky") },

0 commit comments

Comments
 (0)