diff --git a/src/Data/ModCache.lua b/src/Data/ModCache.lua index b846e2a14..a1317d0f1 100644 --- a/src/Data/ModCache.lua +++ b/src/Data/ModCache.lua @@ -4742,12 +4742,12 @@ c["Cannot Recover Life other than from Leech"]={{[1]={flags=0,keywordFlags=0,nam 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} 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} 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} -c["Cannot be Critically Hit while Parrying"]={nil,"Cannot be Critically Hit while Parrying "} +c["Cannot be Critically Hit while Parrying"]={{},"Critically Hit while Parrying "} c["Cannot be Heavy Stunned while Sprinting"]={{[1]={[1]={type="Condition",var="Sprinting"},flags=0,keywordFlags=0,name="StunImmune",type="FLAG",value=true}},nil} c["Cannot be Ignited"]={{[1]={flags=0,keywordFlags=0,name="IgniteImmune",type="FLAG",value=true}},nil} c["Cannot be Light Stunned"]={{[1]={flags=0,keywordFlags=0,name="StunImmune",type="FLAG",value=true}},nil} -c["Cannot be Light Stunned by Deflected Hits"]={nil,"Cannot be Light Stunned by Deflected Hits "} -c["Cannot be Light Stunned if you haven't been Hit Recently"]={nil,"Cannot be Light Stunned if you haven't been Hit Recently "} +c["Cannot be Light Stunned by Deflected Hits"]={{},"Light Stunned by Deflected Hits "} +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} c["Cannot be Poisoned"]={{[1]={flags=0,keywordFlags=0,name="PoisonImmune",type="FLAG",value=true}},nil} c["Cannot be Shocked"]={{[1]={flags=0,keywordFlags=0,name="ShockImmune",type="FLAG",value=true}},nil} c["Cannot be Stunned"]={{[1]={flags=0,keywordFlags=0,name="StunImmune",type="FLAG",value=true}},nil} @@ -5637,29 +5637,25 @@ c["Ignore Warcry Cooldowns"]={{[1]={[1]={skillType=63,type="SkillType"},flags=0, c["Ignore all Movement Penalties from Armour"]={{[1]={flags=0,keywordFlags=0,name="Condition:IgnoreMovementPenalties",type="FLAG",value=true}},nil} 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} 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} -c["Immune to Bleeding while Shapeshifted"]={nil,"Immune to Bleeding while Shapeshifted "} -c["Immune to Bleeding while Shapeshifted Immune to Maim while Shapeshifted"]={nil,"Immune to Bleeding while Shapeshifted Immune to Maim while Shapeshifted "} -c["Immune to Bleeding while affected by an Archon Buff"]={nil,"Immune to Bleeding while affected by an Archon Buff "} +c["Immune to Bleeding while Shapeshifted"]={{[1]={[1]={type="Condition",var="Shapeshifted"},flags=0,keywordFlags=0,name="BleedImmune",type="FLAG",value=true}},nil} +c["Immune to Bleeding while affected by an Archon Buff"]={{},"Bleeding while affected by an Archon Buff "} 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} 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} -c["Immune to Corrupted Blood"]={nil,"Immune to Corrupted Blood "} -c["Immune to Corrupted Blood 40% reduced Duration of Bleeding on You"]={nil,"Immune to Corrupted Blood 40% reduced Duration of Bleeding on You "} +c["Immune to Corrupted Blood"]={{[1]={flags=0,keywordFlags=0,name="CorruptedBloodImmune",type="FLAG",value=true}},nil} 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} -c["Immune to Exposure"]={nil,"Immune to Exposure "} -c["Immune to Exposure Unaffected by Elemental Weakness"]={nil,"Immune to Exposure Unaffected by Elemental Weakness "} +c["Immune to Exposure"]={{[1]={flags=0,keywordFlags=0,name="ExposureImmune",type="FLAG",value=true}},nil} c["Immune to Freeze"]={{[1]={flags=0,keywordFlags=0,name="FreezeImmune",type="FLAG",value=true}},nil} -c["Immune to Freeze and Chill while affected by an Archon Buff"]={nil,"Immune to Freeze and Chill while affected by an Archon Buff "} -c["Immune to Hinder"]={nil,"Immune to Hinder "} -c["Immune to Hinder Immune to Maim"]={nil,"Immune to Hinder Immune to Maim "} +c["Immune to Freeze and Chill while affected by an Archon Buff"]={{},"Freeze and Chill while affected by an Archon Buff "} +c["Immune to Hinder"]={{[1]={flags=0,keywordFlags=0,name="HinderImmune",type="FLAG",value=true}},nil} c["Immune to Ignite"]={{[1]={flags=0,keywordFlags=0,name="IgniteImmune",type="FLAG",value=true}},nil} 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} -c["Immune to Maim"]={nil,"Immune to Maim "} -c["Immune to Maim while Shapeshifted"]={nil,"Immune to Maim while Shapeshifted "} +c["Immune to Maim"]={{[1]={flags=0,keywordFlags=0,name="MaimImmune",type="FLAG",value=true}},nil} +c["Immune to Maim while Shapeshifted"]={{[1]={[1]={type="Condition",var="Shapeshifted"},flags=0,keywordFlags=0,name="MaimImmune",type="FLAG",value=true}},nil} c["Immune to Poison"]={{[1]={flags=0,keywordFlags=0,name="PoisonImmune",type="FLAG",value=true}},nil} 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} c["Immune to Shock"]={{[1]={flags=0,keywordFlags=0,name="ShockImmune",type="FLAG",value=true}},nil} 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} -c["Immune to Shock while affected by an Archon Buff"]={nil,"Immune to Shock while affected by an Archon Buff "} +c["Immune to Shock while affected by an Archon Buff"]={{},"Shock while affected by an Archon Buff "} 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% "} 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 "} 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% "} diff --git a/src/Modules/ModParser.lua b/src/Modules/ModParser.lua index 4dc7443fd..84389d769 100644 --- a/src/Modules/ModParser.lua +++ b/src/Modules/ModParser.lua @@ -151,6 +151,10 @@ local formList = { ["is doubled"] = "DOUBLED", ["doubles?"] = "DOUBLED", ["causes? double"] = "DOUBLED", + ["^immunity to "] = "IMMUNE", + ["^immune to being "] = "IMMUNE", + ["^immune to "] = "IMMUNE", + ["^cannot be "] = "IMMUNE", } -- Map of modifier names @@ -6168,6 +6172,30 @@ local specialModList = { for _, name in pairs(data.keystones) do specialModList[name:lower()] = { mod("Keystone", "LIST", name) } end +--[[ -- Conditional Immunities +-- 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 +specialModList["immune to (.-) w?h?i[lf]e? (.*)"] = = function(_, debuff, cond) + -- NOTE: this only handles cases for which unconditional immunity mods exist to avoid false positives that don't actually get calculated + + -- look for static or dynamically phrased base immunity mod + local searchPrefix1 = "immun[ei]t?y? to " .. ailment and string.lower(debuff) + local searchPrefix2 = "immune to " .. ailment and string.lower(debuff) + local lowerAilment = ailment and string.lower(ailment) or "" + local validDebuff = (specialModList[searchPrefix1 .. lowerAilment] or specialModList[searchPrefix2 .. lowerAilment]) and true or false + + -- look if condition exists + -- todo make more dynamic + local tagKey = (validDebuff and cond) and "while " .. string.lower(cond) + + local condTag = tagKey and (modTagList[tagKey] or modFlagList) or nil + if condTag then + + return { flag(firstToUpper(ailment) .. "Immune", condTag.tag) } + else + return nil + end +end + ]] local oldList = specialModList specialModList = { } for k, v in pairs(oldList) do @@ -6309,6 +6337,29 @@ local flagTypes = { ["malediction"] = "HasMalediction", } +-- Table to map "status" like "Bleeding" to correct effect like "BleedImmune" +local statusToEffectMap = { + ["bleeding"] = "Bleed", + ["blinded"] = "Blind", + ["chilled"] = "Chill", + ["cursed"] = "Curse", + ["curses"] = "Curse", + ["elemental ailments"] = "ElementalAilment", + ["frozen"] = "Freeze", + ["hindered"] = "Hinder", + ["ignited"] = "Ignite", + ["light stunned"] = "Stun", + ["maimed"] = "Maim", + ["poisoned"] = "Poison", + ["shocked"] = "Shock", + -- NOTE: the following are possible, but not yet processed as of 2026-06-09 + --["armour broken"] = "ArmourBreak", + --["critically hit"] = "Crit", + --["electrocuted"] = "Electrocute", + --["heavy stunned"] = "HeavyStun", + --["pinned"] = "Pin", +} + -- Build active skill name lookup local skillNameList = { [" corpse cremation " ] = { tag = { type = "SkillName", skillName = "Cremation", includeTransfigured = true }}, -- Sigh. @@ -6628,6 +6679,54 @@ local function parseMod(line, order) modName = type(modValue) == "table" and modValue.name or modValue modType = type(modValue) == "table" and modValue.type or "FLAG" modValue = type(modValue) == "table" and modValue.value or true + elseif modForm == "IMMUNE" then + local effectLine = line:gsub("%s+$","") -- remove trailing spaces + local _, numWords = effectLine:gsub("%S+", "") + local multiEffect = effectLine:find(" and ") + + local function getEffectFromStatus(statusString) + return statusToEffectMap[statusString:lower()] or statusString + end + + -- Check number of words, as 99% of effects consist of only one or two words + -- NOTE: needs exception for wordings like "freeze and chill" + if multiEffect then + if numWords > 3 then + local preEff, postEff = effectLine:match("^(.-) and (.*)") + local _, preWordNum = preEff:gsub("%S+", "") + local _, postWordNum = postEff:gsub("%S+", "") + if preWordNum > 2 or postWordNum > 2 then + return { }, line -- more than 2 effects, likely a false positive + end + end + elseif (numWords < 1) or (numWords > 2) then + return { }, line -- no words or more than 2 unlikely for single effect + end + + -- Process effect strings to valid mod names + local effect + if multiEffect then + effect = { } + effect[1], effect[2] = effectLine:match("^(.-) and (.*)") -- fuzzy match is fine here as length is already checked before + for i, _ in pairs(effect) do + effect[i] = getEffectFromStatus(effect[i]) + effect[i] = combineToUpper(effect[i]) + end + else + effect = getEffectFromStatus(effectLine) + effect = combineToUpper(effect) + end + + if type(effect) == "table" then + modName = { effect[1] .. "Immune", effect[2] .. "Immune" } + modType = { type(modValue) == "table" and modValue.type or "FLAG", type(modValue) == "table" and modValue.type or "FLAG" } + modValue = { type(modValue) == "table" and modValue.value or true, type(modValue) == "table" and modValue.value or true } + else + modName = effect .. "Immune" + modType = type(modValue) == "table" and modValue.type or "FLAG" + modValue = type(modValue) == "table" and modValue.value or true + end + line = "" -- rest of the line already processed at this stage elseif modForm == "OVERRIDE" then modType = "OVERRIDE" elseif modForm == "DOUBLED" then