From 7a4d7406a9c550abaef281cdab629b1e06b1008e Mon Sep 17 00:00:00 2001 From: Alexander Karrer Date: Thu, 29 Oct 2026 15:03:56 +0100 Subject: [PATCH 1/3] Fix party Wither and exposure stacking --- src/Classes/PartyTab.lua | 9 ++++++ src/Modules/CalcPerform.lua | 61 ++++++++++++++++++++++++++++++++++--- 2 files changed, 66 insertions(+), 4 deletions(-) diff --git a/src/Classes/PartyTab.lua b/src/Classes/PartyTab.lua index 96cc74508e..58dcd7ad19 100644 --- a/src/Classes/PartyTab.lua +++ b/src/Classes/PartyTab.lua @@ -739,6 +739,15 @@ function PartyTabClass:ParseBuffs(list, buf, buffType, label) local mod = modLib.parseFormattedSourceMod(line) if mod then mod.source = "Party"..mod.source + -- Keep existing support exports compatible while comparing Wither instead of adding it. + if mod.name == "ChaosDamageTaken" and mod.type == "INC" and mod.source == "PartyWithered" then + mod.name = "WitheredEffect" + mod.type = "MAX" + end + local exposureElement = mod.name:match("^(%a+)Resist$") + if exposureElement and mod.type == "BASE" and mod.source:match("^PartyExposure:") then + mod.name = exposureElement.."ExposureFromParty" + end list:AddMod(mod) if label then t_insert(enemyModList[currentName], {mod.value, mod.type}) diff --git a/src/Modules/CalcPerform.lua b/src/Modules/CalcPerform.lua index d683aab3ce..a7669067bf 100644 --- a/src/Modules/CalcPerform.lua +++ b/src/Modules/CalcPerform.lua @@ -845,7 +845,7 @@ local function doActorMisc(env, actor) if modDB:Max(nil, "WitherEffectStack") then modDB:NewMod("Condition:CanWither", "FLAG", true, "Config") local effect = modDB:Max(nil, "WitherEffectStack") - enemyDB:NewMod("ChaosDamageTaken", "INC", effect, "Withered", { type = "Multiplier", var = "WitheredStack", limit = 15 } ) + enemyDB:NewMod("WitheredEffect", "MAX", effect, "Withered", { type = "Multiplier", var = "WitheredStack", limit = 15 } ) end if modDB:Flag(nil, "Condition:CanBeWithered") then local effect = 6 * (100 + modDB:Sum("INC", nil, "WitherEffectOnSelf")) / 100 * modDB:More(nil, "WitherEffectOnSelf") @@ -2231,6 +2231,8 @@ function calcs.perform(env, skipEHP) local linkSkills = { } local allyBuffs = env.partyMembers["Aura"] local buffExports = { Aura = {}, Curse = {}, Warcry = {}, Link = {}, EnemyMods = {}, EnemyConditions = {}, PlayerMods = {} } + local localDebuffMods = { } + local appliedDebuffMods = { } local hasActiveSpectreSkill = false local activeSpectreList = { } for _, activeSkill in ipairs(env.player.activeSkillList) do @@ -3520,6 +3522,31 @@ function calcs.perform(env, skipEHP) doActorMisc(env, env.minion) end + -- Wither from players and party members shares the same stack limit; only apply the strongest effect. + local strongestWither + local strongestLocalWither + for _, value in ipairs(enemyDB:Tabulate("MAX", nil, "WitheredEffect")) do + if not strongestWither or value.value > strongestWither.value then + strongestWither = value + end + if value.mod.source == "Withered" and (not strongestLocalWither or value.value > strongestLocalWither.value) then + strongestLocalWither = value + end + end + if partyTabEnableExportBuffs and strongestLocalWither then + local mod = copyTable(strongestLocalWither.mod, true) + mod.name = "ChaosDamageTaken" + mod.type = "INC" + t_insert(localDebuffMods, mod) + end + if strongestWither then + local mod = copyTable(strongestWither.mod, true) + mod.name = "ChaosDamageTaken" + mod.type = "INC" + enemyDB:AddMod(mod) + appliedDebuffMods[mod] = true + end + -- Calculate maximum and apply the strongest non-damaging ailments local ailmentData = data.nonDamagingAilment local ailments = { @@ -3677,10 +3704,26 @@ function calcs.perform(env, skipEHP) if exposureEffectInc ~= 0 then min = min * (1 + exposureEffectInc / 100) end - enemyDB:NewMod("Condition:Has"..element.."Exposure", "FLAG", true, "") - enemyDB:NewMod(element.."Resist", "BASE", m_min(min, modDB:Override(nil, "ExposureMin")), source) + min = m_min(min, modDB:Override(nil, "ExposureMin")) + if partyTabEnableExportBuffs then + -- Mark exposure only in the export so Calcs retains the original modifier source. + t_insert(localDebuffMods, modLib.createMod(element.."Resist", "BASE", min, "Exposure:"..source)) + end modDB:NewMod("Condition:AppliedExposureRecently", "FLAG", true, "") end + -- Imported exposure already includes the support's modifiers; compare it without scaling it again. + for _, mod in ipairs(enemyDB:Tabulate("BASE", nil, element.."ExposureFromParty")) do + if mod.value < min then + min = mod.value + source = mod.mod.source + end + end + if min ~= math.huge then + enemyDB:NewMod("Condition:Has"..element.."Exposure", "FLAG", true, "") + local mod = modLib.createMod(element.."Resist", "BASE", min, source) + enemyDB:AddMod(mod) + appliedDebuffMods[mod] = true + end end end @@ -3836,7 +3879,17 @@ function calcs.perform(env, skipEHP) -- Export modifiers to enemy conditions and stats for party tab if partyTabEnableExportBuffs then - for k, mod in pairs(enemyDB.mods) do + -- Export the character's own debuffs even when a stronger party effect is applied locally. + local exportDB = new("ModDB"):ModDB() + for _, mods in pairs(enemyDB.mods) do + for _, mod in ipairs(mods) do + if not appliedDebuffMods[mod] then + exportDB:AddMod(mod) + end + end + end + exportDB:AddList(localDebuffMods) + for k, mod in pairs(exportDB.mods) do if k:find("Condition") and not k:find("Party") then buffExports["EnemyConditions"][k] = true elseif (k:find("Resist") and not k:find("Totem") and not k:find("Max")) or k:find("Damage") or k:find("ActionSpeed") or k:find("SelfCrit") or (k:find("Multiplier") and not k:find("Max") and not k:find("Impale")) then From 0e84709b811d61f683d3af1d473ffd06ec7b2d96 Mon Sep 17 00:00:00 2001 From: Alexander Karrer Date: Thu, 29 Oct 2026 15:34:57 +0100 Subject: [PATCH 2/3] Export party Wither with the support's configured stacks --- src/Classes/PartyTab.lua | 1 + src/Modules/CalcPerform.lua | 6 ++---- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/Classes/PartyTab.lua b/src/Classes/PartyTab.lua index 58dcd7ad19..b9d5cc72f2 100644 --- a/src/Classes/PartyTab.lua +++ b/src/Classes/PartyTab.lua @@ -743,6 +743,7 @@ function PartyTabClass:ParseBuffs(list, buf, buffType, label) if mod.name == "ChaosDamageTaken" and mod.type == "INC" and mod.source == "PartyWithered" then mod.name = "WitheredEffect" mod.type = "MAX" + t_insert(mod, { type = "Condition", var = "Effective" }) end local exposureElement = mod.name:match("^(%a+)Resist$") if exposureElement and mod.type == "BASE" and mod.source:match("^PartyExposure:") then diff --git a/src/Modules/CalcPerform.lua b/src/Modules/CalcPerform.lua index a7669067bf..10213701fd 100644 --- a/src/Modules/CalcPerform.lua +++ b/src/Modules/CalcPerform.lua @@ -3534,10 +3534,8 @@ function calcs.perform(env, skipEHP) end end if partyTabEnableExportBuffs and strongestLocalWither then - local mod = copyTable(strongestLocalWither.mod, true) - mod.name = "ChaosDamageTaken" - mod.type = "INC" - t_insert(localDebuffMods, mod) + -- Export the support's configured stacks, without depending on the recipient's stack count. + t_insert(localDebuffMods, modLib.createMod("ChaosDamageTaken", "INC", strongestLocalWither.value, "Withered")) end if strongestWither then local mod = copyTable(strongestWither.mod, true) From ae1e3b89276bac5b0144f9d58c4f9fc46628e9bc Mon Sep 17 00:00:00 2001 From: Alexander Karrer Date: Thu, 29 Oct 2026 15:53:19 +0100 Subject: [PATCH 3/3] Combine player and party Wither stacks up to the shared cap --- src/Classes/PartyTab.lua | 5 +++-- src/Modules/CalcPerform.lua | 41 ++++++++++++++++++++++++++----------- 2 files changed, 32 insertions(+), 14 deletions(-) diff --git a/src/Classes/PartyTab.lua b/src/Classes/PartyTab.lua index b9d5cc72f2..4b6f6800f7 100644 --- a/src/Classes/PartyTab.lua +++ b/src/Classes/PartyTab.lua @@ -739,10 +739,11 @@ function PartyTabClass:ParseBuffs(list, buf, buffType, label) local mod = modLib.parseFormattedSourceMod(line) if mod then mod.source = "Party"..mod.source - -- Keep existing support exports compatible while comparing Wither instead of adding it. - if mod.name == "ChaosDamageTaken" and mod.type == "INC" and mod.source == "PartyWithered" then + local witherStacks = mod.source:match("^PartyWithered:(%d+)$") + if mod.name == "ChaosDamageTaken" and mod.type == "INC" and (mod.source == "PartyWithered" or witherStacks) then mod.name = "WitheredEffect" mod.type = "MAX" + mod.witherStackCount = tonumber(witherStacks) t_insert(mod, { type = "Condition", var = "Effective" }) end local exposureElement = mod.name:match("^(%a+)Resist$") diff --git a/src/Modules/CalcPerform.lua b/src/Modules/CalcPerform.lua index 10213701fd..c59ce6af83 100644 --- a/src/Modules/CalcPerform.lua +++ b/src/Modules/CalcPerform.lua @@ -3522,27 +3522,44 @@ function calcs.perform(env, skipEHP) doActorMisc(env, env.minion) end - -- Wither from players and party members shares the same stack limit; only apply the strongest effect. - local strongestWither + -- Combine Wither sources, keeping the strongest 15 individual stacks. + local witherSources = { } local strongestLocalWither + local localWitherStacks = m_min(15, m_max(0, m_floor(enemyDB:GetMultiplier("WitheredStack")))) for _, value in ipairs(enemyDB:Tabulate("MAX", nil, "WitheredEffect")) do - if not strongestWither or value.value > strongestWither.value then - strongestWither = value - end if value.mod.source == "Withered" and (not strongestLocalWither or value.value > strongestLocalWither.value) then strongestLocalWither = value + elseif value.mod.source ~= "Withered" then + -- Older flat exports lack a count; preserve their total as a full set of stacks. + local stacks = value.mod.witherStackCount or 15 + for _, tag in ipairs(value.mod) do + if tag.type == "Multiplier" and tag.var == "WitheredStack" then + stacks = localWitherStacks -- Legacy exports used the recipient's multiplier. + end + end + if stacks > 0 then + t_insert(witherSources, { effect = value.value / stacks, stacks = stacks, source = value.mod.source }) + end end end - if partyTabEnableExportBuffs and strongestLocalWither then - -- Export the support's configured stacks, without depending on the recipient's stack count. - t_insert(localDebuffMods, modLib.createMod("ChaosDamageTaken", "INC", strongestLocalWither.value, "Withered")) + if strongestLocalWither and localWitherStacks > 0 then + t_insert(witherSources, { effect = strongestLocalWither.value / localWitherStacks, stacks = localWitherStacks, source = "Withered" }) + if partyTabEnableExportBuffs then + -- Retain the count so recipients can combine this contribution with their own stacks. + t_insert(localDebuffMods, modLib.createMod("ChaosDamageTaken", "INC", strongestLocalWither.value, "Withered:"..localWitherStacks)) + end end - if strongestWither then - local mod = copyTable(strongestWither.mod, true) - mod.name = "ChaosDamageTaken" - mod.type = "INC" + table.sort(witherSources, function(a, b) return a.effect > b.effect end) + local remainingWitherStacks = 15 + for _, wither in ipairs(witherSources) do + local stacks = m_min(wither.stacks, remainingWitherStacks) + if stacks <= 0 then + break + end + local mod = modLib.createMod("ChaosDamageTaken", "INC", wither.effect * stacks, wither.source) enemyDB:AddMod(mod) appliedDebuffMods[mod] = true + remainingWitherStacks = remainingWitherStacks - stacks end -- Calculate maximum and apply the strongest non-damaging ailments