Skip to content

Commit abe5f5e

Browse files
majochemLocalIdentity
andauthored
Add support various immunity mods and dynamic immunity mod parsing (#2142)
* Add support for dynamic immunity mod parsing Previously immunity mods did not work with flag conditions or tags as they were not supported base mod forms. This adds "IMMUNE" as a new form and thus enables dynamic parsing of many conditions and automatically attaches the correct tags, if they exist. Example mod lines that are now supported or would work automatically: - "Immune to Bleeding while Shapeshifted" - "Cannot be Light Stunned if you haven't been hit recently" - "Immune to Freeze and Chill while Shocked" * Fix typo --------- Co-authored-by: LocalIdentity <localidentity2@gmail.com>
1 parent afbbb7d commit abe5f5e

2 files changed

Lines changed: 111 additions & 16 deletions

File tree

src/Data/ModCache.lua

Lines changed: 12 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -4770,12 +4770,12 @@ c["Cannot Recover Life other than from Leech"]={{[1]={flags=0,keywordFlags=0,nam
47704770
c["Cannot Regenerate Mana if you haven't dealt a Critical Hit Recently"]={{[1]={[1]={neg=true,type="Condition",var="CritRecently"},flags=0,keywordFlags=0,name="NoManaRegen",type="FLAG",value=true}},nil}
47714771
c["Cannot be Blinded"]={{[1]={flags=0,keywordFlags=0,name="Condition:CannotBeBlinded",type="FLAG",value=true},[2]={flags=0,keywordFlags=0,name="BlindImmune",type="FLAG",value=true}},nil}
47724772
c["Cannot be Blinded while on Full Life"]={{[1]={[1]={type="Condition",var="FullLife"},flags=0,keywordFlags=0,name="Condition:CannotBeBlinded",type="FLAG",value=true}},nil}
4773-
c["Cannot be Critically Hit while Parrying"]={nil,"Cannot be Critically Hit while Parrying "}
4773+
c["Cannot be Critically Hit while Parrying"]={{},"Critically Hit while Parrying "}
47744774
c["Cannot be Heavy Stunned while Sprinting"]={{[1]={[1]={type="Condition",var="Sprinting"},flags=0,keywordFlags=0,name="StunImmune",type="FLAG",value=true}},nil}
47754775
c["Cannot be Ignited"]={{[1]={flags=0,keywordFlags=0,name="IgniteImmune",type="FLAG",value=true}},nil}
47764776
c["Cannot be Light Stunned"]={{[1]={flags=0,keywordFlags=0,name="StunImmune",type="FLAG",value=true}},nil}
4777-
c["Cannot be Light Stunned by Deflected Hits"]={nil,"Cannot be Light Stunned by Deflected Hits "}
4778-
c["Cannot be Light Stunned if you haven't been Hit Recently"]={nil,"Cannot be Light Stunned if you haven't been Hit Recently "}
4777+
c["Cannot be Light Stunned by Deflected Hits"]={{},"Light Stunned by Deflected Hits "}
4778+
c["Cannot be Light Stunned if you haven't been Hit Recently"]={{[1]={[1]={neg=true,type="Condition",var="BeenHitRecently"},flags=0,keywordFlags=0,name="StunImmune",type="FLAG",value=true}},nil}
47794779
c["Cannot be Poisoned"]={{[1]={flags=0,keywordFlags=0,name="PoisonImmune",type="FLAG",value=true}},nil}
47804780
c["Cannot be Shocked"]={{[1]={flags=0,keywordFlags=0,name="ShockImmune",type="FLAG",value=true}},nil}
47814781
c["Cannot be Stunned"]={{[1]={flags=0,keywordFlags=0,name="StunImmune",type="FLAG",value=true}},nil}
@@ -5679,29 +5679,25 @@ c["Ignore Warcry Cooldowns"]={{[1]={[1]={skillType=63,type="SkillType"},flags=0,
56795679
c["Ignore all Movement Penalties from Armour"]={{[1]={flags=0,keywordFlags=0,name="Condition:IgnoreMovementPenalties",type="FLAG",value=true}},nil}
56805680
c["Immobilise enemies at 50% buildup instead of 100%"]={{[1]={flags=0,keywordFlags=0,name="EnemyModifier",type="LIST",value={mod={flags=0,keywordFlags=0,name="PoiseThreshold",type="MORE",value=-50}}}},nil}
56815681
c["Immune to Bleeding if Equipped Helmet has higher Armour than Evasion Rating"]={{[1]={[1]={type="Condition",var="HelmetArmourHigherThanEvasion"},flags=0,keywordFlags=0,name="BleedImmune",type="FLAG",value=true}},nil}
5682-
c["Immune to Bleeding while Shapeshifted"]={nil,"Immune to Bleeding while Shapeshifted "}
5683-
c["Immune to Bleeding while Shapeshifted Immune to Maim while Shapeshifted"]={nil,"Immune to Bleeding while Shapeshifted Immune to Maim while Shapeshifted "}
5684-
c["Immune to Bleeding while affected by an Archon Buff"]={nil,"Immune to Bleeding while affected by an Archon Buff "}
5682+
c["Immune to Bleeding while Shapeshifted"]={{[1]={[1]={type="Condition",var="Shapeshifted"},flags=0,keywordFlags=0,name="BleedImmune",type="FLAG",value=true}},nil}
5683+
c["Immune to Bleeding while affected by an Archon Buff"]={{},"Bleeding while affected by an Archon Buff "}
56855684
c["Immune to Chaos Damage and Bleeding"]={{[1]={flags=0,keywordFlags=0,name="ChaosInoculation",type="FLAG",value=true},[2]={flags=0,keywordFlags=0,name="ChaosDamageTaken",type="MORE",value=-100},[3]={flags=0,keywordFlags=0,name="BleedImmune",type="FLAG",value=true}},nil}
56865685
c["Immune to Chill if a majority of your Socketed Support Gems are Blue"]={{[1]={[1]={type="Condition",var="MajorityBlueSocketedSupports"},flags=0,keywordFlags=0,name="ChillImmune",type="FLAG",value=true}},nil}
5687-
c["Immune to Corrupted Blood"]={nil,"Immune to Corrupted Blood "}
5688-
c["Immune to Corrupted Blood 40% reduced Duration of Bleeding on You"]={nil,"Immune to Corrupted Blood 40% reduced Duration of Bleeding on You "}
5686+
c["Immune to Corrupted Blood"]={{[1]={flags=0,keywordFlags=0,name="CorruptedBloodImmune",type="FLAG",value=true}},nil}
56895687
c["Immune to Elemental Ailments while on Consecrated Ground if you have at least 150 Devotion"]={{[1]={[1]={type="Condition",var="OnConsecratedGround"},[2]={stat="Devotion",threshold=150,type="StatThreshold"},flags=0,keywordFlags=0,name="ElementalAilmentImmune",type="FLAG",value=true}},nil}
5690-
c["Immune to Exposure"]={nil,"Immune to Exposure "}
5691-
c["Immune to Exposure Unaffected by Elemental Weakness"]={nil,"Immune to Exposure Unaffected by Elemental Weakness "}
5688+
c["Immune to Exposure"]={{[1]={flags=0,keywordFlags=0,name="ExposureImmune",type="FLAG",value=true}},nil}
56925689
c["Immune to Freeze"]={{[1]={flags=0,keywordFlags=0,name="FreezeImmune",type="FLAG",value=true}},nil}
5693-
c["Immune to Freeze and Chill while affected by an Archon Buff"]={nil,"Immune to Freeze and Chill while affected by an Archon Buff "}
5694-
c["Immune to Hinder"]={nil,"Immune to Hinder "}
5695-
c["Immune to Hinder Immune to Maim"]={nil,"Immune to Hinder Immune to Maim "}
5690+
c["Immune to Freeze and Chill while affected by an Archon Buff"]={{},"Freeze and Chill while affected by an Archon Buff "}
5691+
c["Immune to Hinder"]={{[1]={flags=0,keywordFlags=0,name="HinderImmune",type="FLAG",value=true}},nil}
56965692
c["Immune to Ignite"]={{[1]={flags=0,keywordFlags=0,name="IgniteImmune",type="FLAG",value=true}},nil}
56975693
c["Immune to Ignite if a majority of your Socketed Support Gems are Red"]={{[1]={[1]={type="Condition",var="MajorityRedSocketedSupports"},flags=0,keywordFlags=0,name="IgniteImmune",type="FLAG",value=true}},nil}
5698-
c["Immune to Maim"]={nil,"Immune to Maim "}
5699-
c["Immune to Maim while Shapeshifted"]={nil,"Immune to Maim while Shapeshifted "}
5694+
c["Immune to Maim"]={{[1]={flags=0,keywordFlags=0,name="MaimImmune",type="FLAG",value=true}},nil}
5695+
c["Immune to Maim while Shapeshifted"]={{[1]={[1]={type="Condition",var="Shapeshifted"},flags=0,keywordFlags=0,name="MaimImmune",type="FLAG",value=true}},nil}
57005696
c["Immune to Poison"]={{[1]={flags=0,keywordFlags=0,name="PoisonImmune",type="FLAG",value=true}},nil}
57015697
c["Immune to Poison if Equipped Helmet has higher Evasion Rating than Armour"]={{[1]={[1]={type="Condition",var="HelmetEvasionHigherThanArmour"},flags=0,keywordFlags=0,name="PoisonImmune",type="FLAG",value=true}},nil}
57025698
c["Immune to Shock"]={{[1]={flags=0,keywordFlags=0,name="ShockImmune",type="FLAG",value=true}},nil}
57035699
c["Immune to Shock if a majority of your Socketed Support Gems are Green"]={{[1]={[1]={type="Condition",var="MajorityGreenSocketedSupports"},flags=0,keywordFlags=0,name="ShockImmune",type="FLAG",value=true}},nil}
5704-
c["Immune to Shock while affected by an Archon Buff"]={nil,"Immune to Shock while affected by an Archon Buff "}
5700+
c["Immune to Shock while affected by an Archon Buff"]={{},"Shock while affected by an Archon Buff "}
57055701
c["Increases Movement Speed by 25%, plus 1% per 500 Evasion Rating, up to a maximum of 75%"]={nil,"Increases Movement Speed by 25%, plus 1% per 500 Evasion Rating, up to a maximum of 75% "}
57065702
c["Increases Movement Speed by 25%, plus 1% per 500 Evasion Rating, up to a maximum of 75% Other Modifiers to Movement Speed except for Sprinting do not apply"]={nil,"Increases Movement Speed by 25%, plus 1% per 500 Evasion Rating, up to a maximum of 75% Other Modifiers to Movement Speed except for Sprinting do not apply "}
57075703
c["Increases Movement Speed by 25%, plus 1% per 600 Evasion Rating, up to a maximum of 75%"]={nil,"Increases Movement Speed by 25%, plus 1% per 600 Evasion Rating, up to a maximum of 75% "}

src/Modules/ModParser.lua

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,10 @@ local formList = {
151151
["is doubled"] = "DOUBLED",
152152
["doubles?"] = "DOUBLED",
153153
["causes? double"] = "DOUBLED",
154+
["^immunity to "] = "IMMUNE",
155+
["^immune to being "] = "IMMUNE",
156+
["^immune to "] = "IMMUNE",
157+
["^cannot be "] = "IMMUNE",
154158
}
155159

156160
-- Map of modifier names
@@ -6182,6 +6186,30 @@ local specialModList = {
61826186
for _, name in pairs(data.keystones) do
61836187
specialModList[name:lower()] = { mod("Keystone", "LIST", name) }
61846188
end
6189+
--[[ -- Conditional Immunities
6190+
-- NOTE: conditional mods with "Immune to ..." cannot be handled for PoE2 as they no longer start with "You are..." or similar prefixes that trigger a "FLAG" mod
6191+
specialModList["immune to (.-) w?h?i[lf]e? (.*)"] = = function(_, debuff, cond)
6192+
-- NOTE: this only handles cases for which unconditional immunity mods exist to avoid false positives that don't actually get calculated
6193+
6194+
-- look for static or dynamically phrased base immunity mod
6195+
local searchPrefix1 = "immun[ei]t?y? to " .. ailment and string.lower(debuff)
6196+
local searchPrefix2 = "immune to " .. ailment and string.lower(debuff)
6197+
local lowerAilment = ailment and string.lower(ailment) or ""
6198+
local validDebuff = (specialModList[searchPrefix1 .. lowerAilment] or specialModList[searchPrefix2 .. lowerAilment]) and true or false
6199+
6200+
-- look if condition exists
6201+
-- todo make more dynamic
6202+
local tagKey = (validDebuff and cond) and "while " .. string.lower(cond)
6203+
6204+
local condTag = tagKey and (modTagList[tagKey] or modFlagList) or nil
6205+
if condTag then
6206+
6207+
return { flag(firstToUpper(ailment) .. "Immune", condTag.tag) }
6208+
else
6209+
return nil
6210+
end
6211+
end
6212+
]]
61856213
local oldList = specialModList
61866214
specialModList = { }
61876215
for k, v in pairs(oldList) do
@@ -6323,6 +6351,29 @@ local flagTypes = {
63236351
["malediction"] = "HasMalediction",
63246352
}
63256353

6354+
-- Table to map "status" like "Bleeding" to correct effect like "BleedImmune"
6355+
local statusToEffectMap = {
6356+
["bleeding"] = "Bleed",
6357+
["blinded"] = "Blind",
6358+
["chilled"] = "Chill",
6359+
["cursed"] = "Curse",
6360+
["curses"] = "Curse",
6361+
["elemental ailments"] = "ElementalAilment",
6362+
["frozen"] = "Freeze",
6363+
["hindered"] = "Hinder",
6364+
["ignited"] = "Ignite",
6365+
["light stunned"] = "Stun",
6366+
["maimed"] = "Maim",
6367+
["poisoned"] = "Poison",
6368+
["shocked"] = "Shock",
6369+
-- NOTE: the following are possible, but not yet processed as of 2026-06-09
6370+
--["armour broken"] = "ArmourBreak",
6371+
--["critically hit"] = "Crit",
6372+
--["electrocuted"] = "Electrocute",
6373+
--["heavy stunned"] = "HeavyStun",
6374+
--["pinned"] = "Pin",
6375+
}
6376+
63266377
-- Build active skill name lookup
63276378
local skillNameList = {
63286379
[" corpse cremation " ] = { tag = { type = "SkillName", skillName = "Cremation", includeTransfigured = true }}, -- Sigh.
@@ -6642,6 +6693,54 @@ local function parseMod(line, order)
66426693
modName = type(modValue) == "table" and modValue.name or modValue
66436694
modType = type(modValue) == "table" and modValue.type or "FLAG"
66446695
modValue = type(modValue) == "table" and modValue.value or true
6696+
elseif modForm == "IMMUNE" then
6697+
local effectLine = line:gsub("%s+$","") -- remove trailing spaces
6698+
local _, numWords = effectLine:gsub("%S+", "")
6699+
local multiEffect = effectLine:find(" and ")
6700+
6701+
local function getEffectFromStatus(statusString)
6702+
return statusToEffectMap[statusString:lower()] or statusString
6703+
end
6704+
6705+
-- Check number of words, as 99% of effects consist of only one or two words
6706+
-- NOTE: needs exception for wordings like "freeze and chill"
6707+
if multiEffect then
6708+
if numWords > 3 then
6709+
local preEff, postEff = effectLine:match("^(.-) and (.*)")
6710+
local _, preWordNum = preEff:gsub("%S+", "")
6711+
local _, postWordNum = postEff:gsub("%S+", "")
6712+
if preWordNum > 2 or postWordNum > 2 then
6713+
return { }, line -- more than 2 effects, likely a false positive
6714+
end
6715+
end
6716+
elseif (numWords < 1) or (numWords > 2) then
6717+
return { }, line -- no words or more than 2 unlikely for single effect
6718+
end
6719+
6720+
-- Process effect strings to valid mod names
6721+
local effect
6722+
if multiEffect then
6723+
effect = { }
6724+
effect[1], effect[2] = effectLine:match("^(.-) and (.*)") -- fuzzy match is fine here as length is already checked before
6725+
for i, _ in pairs(effect) do
6726+
effect[i] = getEffectFromStatus(effect[i])
6727+
effect[i] = combineToUpper(effect[i])
6728+
end
6729+
else
6730+
effect = getEffectFromStatus(effectLine)
6731+
effect = combineToUpper(effect)
6732+
end
6733+
6734+
if type(effect) == "table" then
6735+
modName = { effect[1] .. "Immune", effect[2] .. "Immune" }
6736+
modType = { type(modValue) == "table" and modValue.type or "FLAG", type(modValue) == "table" and modValue.type or "FLAG" }
6737+
modValue = { type(modValue) == "table" and modValue.value or true, type(modValue) == "table" and modValue.value or true }
6738+
else
6739+
modName = effect .. "Immune"
6740+
modType = type(modValue) == "table" and modValue.type or "FLAG"
6741+
modValue = type(modValue) == "table" and modValue.value or true
6742+
end
6743+
line = "" -- rest of the line already processed at this stage
66456744
elseif modForm == "OVERRIDE" then
66466745
modType = "OVERRIDE"
66476746
elseif modForm == "DOUBLED" then

0 commit comments

Comments
 (0)