From b2b3749b90cceacb7d7e8e0226e7d84b67dc8c86 Mon Sep 17 00:00:00 2001 From: Paliak <91493239+Paliak@users.noreply.github.com> Date: Fri, 13 Jun 2025 03:39:38 +0200 Subject: [PATCH] Fix globalLimit being sensitive to tag order (#8652) * FEAT(items): Add showcased uniques to New.lua * FEAT(mods): add handling for new mods on Scornflux * FEAT(mods): add handling for "Take X Fire Damage when you use a Skill" * FEAT(mods): add handling for Damage penetrates "Fire Resistance equal to your overcapped Fire Resistance" * FEAT(mods): add handling for "Warcries have an additional Life Cost equal to 15% of your Maximum Life" * FEAT(mods): add handling for "Warcry Skills have X increased Area of Effect" * FEAT(mods): add handling for `Gain no armour from equipped body armour` * FEAT(mods): port generalized "doubled" mod handling from POB2 https://github.com/PathOfBuildingCommunity/PathOfBuilding-PoE2/pull/1095 * Update spelling and ModCache * FIX(mods): globalLimit style mods did not work sometimes When a mod gots pice wise parsed the tags can be added to the mod table in unpredictable order. The inital implementation was simply checking the first tag which caused the global limit functionality to not work sometimes. This commit moves the logic into EvalMod. * FEAT(mods): add support for "you have no Str/Dex" mods * FIX(spelling): un-rake-able --------- Co-authored-by: Wires77 --- spec/System/TestDefence_spec.lua | 95 ++++++++++++++++++++++++++++++++ src/Classes/ModDB.lua | 28 +--------- src/Classes/ModStore.lua | 12 +++- src/Modules/ModParser.lua | 6 ++ 4 files changed, 115 insertions(+), 26 deletions(-) diff --git a/spec/System/TestDefence_spec.lua b/spec/System/TestDefence_spec.lua index 81f743b3..ad151fbb 100644 --- a/spec/System/TestDefence_spec.lua +++ b/spec/System/TestDefence_spec.lua @@ -844,7 +844,102 @@ describe("TestDefence", function() assert.are.equals(0, floor(poolsRemaining.Life)) assert.are.equals(0, floor(poolsRemaining.OverkillDamage)) end) + + it("Unbreakable + Iron Reflexes", function() + build.configTab.input.customMods = [[ + you have no dexterity + ]] + build.configTab:BuildModList() + runCallback("OnFrame") + -- Get the base to make this test more adaptable to changes + local baseArmour = build.calcsTab.mainOutput.Armour + local baseEvasion = build.calcsTab.mainOutput.Evasion + + build.configTab.input.customMods = [[ + Converts all Evasion Rating to Armour. Dexterity provides no bonus to Evasion Rating + you have no dexterity + ]] + build.itemsTab:CreateDisplayItemFromRaw([[ + New Item + Shabby Jerkin + Quality: 20 + ]]) + build.itemsTab:AddDisplayItem() + build.configTab:BuildModList() + runCallback("OnFrame") + + -- Get the base + Shabby Jerkin to make this test more adaptable to changes + local ironReflexesArmour = build.calcsTab.mainOutput.Armour - baseArmour - baseEvasion + + print("build.calcsTab.mainOutput.Armour:" .. build.calcsTab.mainOutput.Armour) + + build.configTab.input.customMods = [[ + Armour from Equipped Body Armour is doubled + Converts all Evasion Rating to Armour. Dexterity provides no bonus to Evasion Rating + you have no dexterity + ]] + build.configTab:BuildModList() + runCallback("OnFrame") + + -- Evasion from Body Armour is converted to Armour before being doubled + assert.are.equals(2*ironReflexesArmour + baseArmour + baseEvasion, build.calcsTab.mainOutput.Armour) + + build.configTab.input.customMods = [[ + Armour from Equipped Body Armour is doubled + Converts all Evasion Rating to Armour. Dexterity provides no bonus to Evasion Rating + Gain no armour from equipped body armour + you have no dexterity + ]] + build.configTab:BuildModList() + runCallback("OnFrame") + + -- Only the base armour from the chest is affected. + -- Armour converted with Iron Reflexes still applies + assert.are.equals(2*ironReflexesArmour + baseArmour + baseEvasion, build.calcsTab.mainOutput.Armour) + build.configTab.input.customMods = [[ + Armour from Equipped Body Armour is doubled + Converts all Evasion Rating to Armour. Dexterity provides no bonus to Evasion Rating + Gain no armour from equipped body armour + defences from equipped body armour are doubled if it has no socketed gems + you have no dexterity + ]] + build.configTab:BuildModList() + runCallback("OnFrame") + + -- Oath Of Maji double defences stack with Unbreakable + assert.are.equals(2*2*ironReflexesArmour + baseArmour + baseEvasion, build.calcsTab.mainOutput.Armour) + + build.configTab.input.customMods = [[ + Armour from Equipped Body Armour is doubled + Armour from Equipped Body Armour is doubled + Converts all Evasion Rating to Armour. Dexterity provides no bonus to Evasion Rating + Gain no armour from equipped body armour + defences from equipped body armour are doubled if it has no socketed gems + you have no dexterity + ]] + build.configTab:BuildModList() + runCallback("OnFrame") + + -- Mod form unbreakable should apply only once + assert.are.equals(2*2*ironReflexesArmour + baseArmour + baseEvasion, build.calcsTab.mainOutput.Armour) + + build.configTab.input.customMods = [[ + Armour from Equipped Body Armour is doubled + Armour from Equipped Body Armour is doubled + Converts all Evasion Rating to Armour. Dexterity provides no bonus to Evasion Rating + Gain no armour from equipped body armour + defences from equipped body armour are doubled if it has no socketed gems + defences from equipped body armour are doubled if it has no socketed gems + you have no dexterity + ]] + build.configTab:BuildModList() + runCallback("OnFrame") + + -- Oath Of Maji should apply only once + assert.are.equals(2*2*ironReflexesArmour + baseArmour + baseEvasion, build.calcsTab.mainOutput.Armour) + end) + it("MoM + EB", function() build.configTab.input.enemyIsBoss = "None" -- enough mana and es, 0% and 100% bypass diff --git a/src/Classes/ModDB.lua b/src/Classes/ModDB.lua index f3c1ddc8..971bfbed 100644 --- a/src/Classes/ModDB.lua +++ b/src/Classes/ModDB.lua @@ -98,14 +98,7 @@ function ModDBClass:SumInternal(context, modType, cfg, flags, keywordFlags, sour local mod = modList[i] if mod.type == modType and band(flags, mod.flags) == mod.flags and MatchKeywordFlags(keywordFlags, mod.keywordFlags) and (not source or ( mod.source and mod.source:match("[^:]+") == source )) then if mod[1] then - local value = context:EvalMod(mod, cfg) or 0 - if mod[1].globalLimit and mod[1].globalLimitKey then - globalLimits[mod[1].globalLimitKey] = globalLimits[mod[1].globalLimitKey] or 0 - if globalLimits[mod[1].globalLimitKey] + value > mod[1].globalLimit then - value = mod[1].globalLimit - globalLimits[mod[1].globalLimitKey] - end - globalLimits[mod[1].globalLimitKey] = globalLimits[mod[1].globalLimitKey] + value - end + local value = context:EvalMod(mod, cfg, globalLimits) or 0 result = result + value else result = result + mod.value @@ -133,14 +126,7 @@ function ModDBClass:MoreInternal(context, cfg, flags, keywordFlags, source, ...) if mod.type == "MORE" and band(flags, mod.flags) == mod.flags and MatchKeywordFlags(keywordFlags, mod.keywordFlags) and (not source or mod.source:match("[^:]+") == source) then local value if mod[1] then - value = context:EvalMod(mod, cfg) or 0 - if mod[1].globalLimit and mod[1].globalLimitKey then - globalLimits[mod[1].globalLimitKey] = globalLimits[mod[1].globalLimitKey] or 0 - if globalLimits[mod[1].globalLimitKey] + value > mod[1].globalLimit then - value = mod[1].globalLimit - globalLimits[mod[1].globalLimitKey] - end - globalLimits[mod[1].globalLimitKey] = globalLimits[mod[1].globalLimitKey] + value - end + value = context:EvalMod(mod, cfg, globalLimits) or 0 else value = mod.value or 0 end @@ -249,15 +235,7 @@ function ModDBClass:TabulateInternal(context, result, modType, cfg, flags, keywo if (mod.type == modType or not modType) and band(flags, mod.flags) == mod.flags and MatchKeywordFlags(keywordFlags, mod.keywordFlags) and (not source or mod.source:match("[^:]+") == source) then local value if mod[1] then - value = context:EvalMod(mod, cfg) - if mod[1].globalLimit and mod[1].globalLimitKey then - value = value or 0 - globalLimits[mod[1].globalLimitKey] = globalLimits[mod[1].globalLimitKey] or 0 - if globalLimits[mod[1].globalLimitKey] + value > mod[1].globalLimit then - value = mod[1].globalLimit - globalLimits[mod[1].globalLimitKey] - end - globalLimits[mod[1].globalLimitKey] = globalLimits[mod[1].globalLimitKey] + value - end + value = context:EvalMod(mod, cfg, globalLimits) else value = mod.value end diff --git a/src/Classes/ModStore.lua b/src/Classes/ModStore.lua index d6ca744a..d258d51a 100644 --- a/src/Classes/ModStore.lua +++ b/src/Classes/ModStore.lua @@ -260,12 +260,22 @@ function ModStoreClass:GetStat(stat, cfg) end end -function ModStoreClass:EvalMod(mod, cfg) +function ModStoreClass:EvalMod(mod, cfg, globalLimits) local value = mod.value for _, tag in ipairs(mod) do if tag.type == "Multiplier" then local target = self local limitTarget = self + + if globalLimits and tag.globalLimit and tag.globalLimitKey then + value = value or 0 + globalLimits[tag.globalLimitKey] = globalLimits[tag.globalLimitKey] or 0 + if globalLimits[tag.globalLimitKey] + value > tag.globalLimit then + value = tag.globalLimit - globalLimits[tag.globalLimitKey] + end + globalLimits[tag.globalLimitKey] = globalLimits[tag.globalLimitKey] + value + end + -- Allow limiting a self multiplier on a parent multiplier (eg. Agony Crawler on player virulence) -- This explicit target is necessary because even though the GetMultiplier method does call self.parent.GetMultiplier, it does so with noMod = true, -- disabling the summation (3rd part): (not noMod and self:Sum("BASE", cfg, multiplierName[var]) or 0) diff --git a/src/Modules/ModParser.lua b/src/Modules/ModParser.lua index fe68915d..6c01c494 100644 --- a/src/Modules/ModParser.lua +++ b/src/Modules/ModParser.lua @@ -4254,6 +4254,12 @@ local specialModList = { ["you have no intelligence"] = { mod("Int", "MORE", -100), }, + ["you have no dexterity"] = { + mod("Dex", "MORE", -100), + }, + ["you have no strength"] = { + mod("Str", "MORE", -100), + }, ["elemental resistances are zero"] = { mod("FireResist", "OVERRIDE", 0), mod("ColdResist", "OVERRIDE", 0),