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 <Wires77@users.noreply.github.com>
This commit is contained in:
Paliak
2025-06-13 03:39:38 +02:00
committed by GitHub
parent 9e1acfdc61
commit b2b3749b90
4 changed files with 115 additions and 26 deletions

View File

@@ -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

View File

@@ -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

View File

@@ -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)

View File

@@ -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),