From 04aa69a9ee8ee1cdd2d95da97148312258e1199b Mon Sep 17 00:00:00 2001 From: EtherealCarnivore <42915554+EtherealCarnivore@users.noreply.github.com> Date: Mon, 2 Mar 2026 23:29:35 +0200 Subject: [PATCH 1/8] Rename Otherworldly Appendages to Cryogenesis, add new stats 3.28 replaces the old graft damage reduction stats with: - Int single highest attribute: all added damage treated as Lightning - Dex single highest attribute: all added damage treated as Cold Updated tree data (all 4 variants), added ModParser entries, single-highest-attribute conditions in CalcPerform, and flat added damage redirection in CalcOffence. --- src/Modules/CalcOffence.lua | 25 +++++++++++++++++-- src/Modules/CalcPerform.lua | 4 +++ src/Modules/ModParser.lua | 6 +++++ src/TreeData/3_27/tree.lua | 9 +++---- src/TreeData/3_27_alternate/tree.lua | 9 +++---- src/TreeData/3_27_ruthless/tree.lua | 9 +++---- src/TreeData/3_27_ruthless_alternate/tree.lua | 9 +++---- 7 files changed, 45 insertions(+), 26 deletions(-) diff --git a/src/Modules/CalcOffence.lua b/src/Modules/CalcOffence.lua index c89a4200fd..157399ab62 100644 --- a/src/Modules/CalcOffence.lua +++ b/src/Modules/CalcOffence.lua @@ -3047,14 +3047,35 @@ function calcs.offence(env, actor, activeSkill) runSkillFunc("postCritFunc") + -- Check for added damage redirection (Cryogenesis) + local addedDamageRedirectType = nil + if skillModList:Flag(cfg, "AllAddedDamageAsLightning") then + addedDamageRedirectType = "Lightning" + elseif skillModList:Flag(cfg, "AllAddedDamageAsCold") then + addedDamageRedirectType = "Cold" + end + -- Calculate base hit damage for _, damageType in ipairs(dmgTypeList) do local damageTypeMin = damageType.."Min" local damageTypeMax = damageType.."Max" local baseMultiplier = activeSkill.activeEffect.grantedEffectLevel.baseMultiplier or skillData.baseMultiplier or 1 local damageEffectiveness = activeSkill.activeEffect.grantedEffectLevel.damageEffectiveness or skillData.damageEffectiveness or 1 - local addedMin = skillModList:Sum("BASE", cfg, damageTypeMin) + enemyDB:Sum("BASE", cfg, "Self"..damageTypeMin) - local addedMax = skillModList:Sum("BASE", cfg, damageTypeMax) + enemyDB:Sum("BASE", cfg, "Self"..damageTypeMax) + local addedMin, addedMax + if addedDamageRedirectType then + if damageType == addedDamageRedirectType then + addedMin, addedMax = 0, 0 + for _, srcType in ipairs(dmgTypeList) do + addedMin = addedMin + skillModList:Sum("BASE", cfg, srcType.."Min") + enemyDB:Sum("BASE", cfg, "Self"..srcType.."Min") + addedMax = addedMax + skillModList:Sum("BASE", cfg, srcType.."Max") + enemyDB:Sum("BASE", cfg, "Self"..srcType.."Max") + end + else + addedMin, addedMax = 0, 0 + end + else + addedMin = skillModList:Sum("BASE", cfg, damageTypeMin) + enemyDB:Sum("BASE", cfg, "Self"..damageTypeMin) + addedMax = skillModList:Sum("BASE", cfg, damageTypeMax) + enemyDB:Sum("BASE", cfg, "Self"..damageTypeMax) + end local addedMult = calcLib.mod(skillModList, cfg, "Added"..damageType.."Damage", "AddedDamage") local baseMin = ((source[damageTypeMin] or 0) + (source[damageType.."BonusMin"] or 0)) * baseMultiplier + addedMin * damageEffectiveness * addedMult local baseMax = ((source[damageTypeMax] or 0) + (source[damageType.."BonusMax"] or 0)) * baseMultiplier + addedMax * damageEffectiveness * addedMult diff --git a/src/Modules/CalcPerform.lua b/src/Modules/CalcPerform.lua index 05b2d161b5..a4a7745fd1 100644 --- a/src/Modules/CalcPerform.lua +++ b/src/Modules/CalcPerform.lua @@ -395,6 +395,8 @@ local function doActorAttribsConditions(env, actor) condList["StrHighestAttribute"] = output.Str >= output.Dex and output.Str >= output.Int condList["IntHighestAttribute"] = output.Int >= output.Str and output.Int >= output.Dex condList["DexHighestAttribute"] = output.Dex >= output.Str and output.Dex >= output.Int + condList["IntSingleHighestAttribute"] = output.Int > output.Str and output.Int > output.Dex + condList["DexSingleHighestAttribute"] = output.Dex > output.Str and output.Dex > output.Int end end @@ -457,6 +459,8 @@ local function doActorAttribsConditions(env, actor) condList["StrHighestAttribute"] = output.Str >= output.Dex and output.Str >= output.Int condList["IntHighestAttribute"] = output.Int >= output.Str and output.Int >= output.Dex condList["DexHighestAttribute"] = output.Dex >= output.Str and output.Dex >= output.Int + condList["IntSingleHighestAttribute"] = output.Int > output.Str and output.Int > output.Dex + condList["DexSingleHighestAttribute"] = output.Dex > output.Str and output.Dex > output.Int end end diff --git a/src/Modules/ModParser.lua b/src/Modules/ModParser.lua index fd91130b3e..16062c62f1 100644 --- a/src/Modules/ModParser.lua +++ b/src/Modules/ModParser.lua @@ -5258,6 +5258,12 @@ local specialModList = { ["consecrated path and purifying flame create profane ground instead of consecrated ground"] = { flag("Condition:CreateProfaneGround"), }, + ["if intelligence is your single highest attribute, all added damage is treated as added lightning damage"] = { + flag("AllAddedDamageAsLightning", { type = "Condition", var = "IntSingleHighestAttribute" }), + }, + ["if dexterity is your single highest attribute, all added damage is treated as added cold damage"] = { + flag("AllAddedDamageAsCold", { type = "Condition", var = "DexSingleHighestAttribute" }), + }, ["you have consecrated ground around you while stationary if strength is your highest attribute"] = { flag("Condition:OnConsecratedGround", { type = "Condition", var = "StrHighestAttribute" }, { type = "Condition", var = "Stationary" }), }, diff --git a/src/TreeData/3_27/tree.lua b/src/TreeData/3_27/tree.lua index 6f3f9332e3..e9216e971e 100644 --- a/src/TreeData/3_27/tree.lua +++ b/src/TreeData/3_27/tree.lua @@ -14450,17 +14450,14 @@ return { }, [52855]= { ["skill"]= 52855, - ["name"]= "Otherworldly Appendages", + ["name"]= "Cryogenesis", ["icon"]= "Art/2DArt/SkillIcons/passives/AtlasTrees/BreachNotable4.png", ["isNotable"]= true, ["ascendancyName"]= "Breachlord", ["isBloodline"]= true, ["stats"]= { - "Take 15% less Lightning Damage with at least one Eshgraft grafted to you", - "Take 15% less Cold Damage with at least one Tulgraft grafted to you", - "Take 15% less Physical Damage with at least one Uulgraft grafted to you", - "Take 15% less Fire Damage with at least one Xophgraft grafted to you", - "Nearby Enemies take 100% increased Damage from Graft Skills" + "If Intelligence is your single highest Attribute, all added Damage is treated as added Lightning Damage", + "If Dexterity is your single highest Attribute, all added Damage is treated as added Cold Damage" }, ["group"]= 26, ["orbit"]= 3, diff --git a/src/TreeData/3_27_alternate/tree.lua b/src/TreeData/3_27_alternate/tree.lua index 3589c96d75..7f8d2335d8 100644 --- a/src/TreeData/3_27_alternate/tree.lua +++ b/src/TreeData/3_27_alternate/tree.lua @@ -15057,17 +15057,14 @@ return { }, [52855]= { ["skill"]= 52855, - ["name"]= "Otherworldly Appendages", + ["name"]= "Cryogenesis", ["icon"]= "Art/2DArt/SkillIcons/passives/AtlasTrees/BreachNotable4.png", ["isNotable"]= true, ["ascendancyName"]= "Breachlord", ["isBloodline"]= true, ["stats"]= { - "Take 15% less Lightning Damage with at least one Eshgraft grafted to you", - "Take 15% less Cold Damage with at least one Tulgraft grafted to you", - "Take 15% less Physical Damage with at least one Uulgraft grafted to you", - "Take 15% less Fire Damage with at least one Xophgraft grafted to you", - "Nearby Enemies take 100% increased Damage from Graft Skills" + "If Intelligence is your single highest Attribute, all added Damage is treated as added Lightning Damage", + "If Dexterity is your single highest Attribute, all added Damage is treated as added Cold Damage" }, ["group"]= 31, ["orbit"]= 3, diff --git a/src/TreeData/3_27_ruthless/tree.lua b/src/TreeData/3_27_ruthless/tree.lua index 6ea05e0d86..b8b41268d6 100644 --- a/src/TreeData/3_27_ruthless/tree.lua +++ b/src/TreeData/3_27_ruthless/tree.lua @@ -14438,17 +14438,14 @@ return { }, [52855]= { ["skill"]= 52855, - ["name"]= "Otherworldly Appendages", + ["name"]= "Cryogenesis", ["icon"]= "Art/2DArt/SkillIcons/passives/AtlasTrees/BreachNotable4.png", ["isNotable"]= true, ["ascendancyName"]= "Breachlord", ["isBloodline"]= true, ["stats"]= { - "Take 15% less Lightning Damage with at least one Eshgraft grafted to you", - "Take 15% less Cold Damage with at least one Tulgraft grafted to you", - "Take 15% less Physical Damage with at least one Uulgraft grafted to you", - "Take 15% less Fire Damage with at least one Xophgraft grafted to you", - "Nearby Enemies take 100% increased Damage from Graft Skills" + "If Intelligence is your single highest Attribute, all added Damage is treated as added Lightning Damage", + "If Dexterity is your single highest Attribute, all added Damage is treated as added Cold Damage" }, ["group"]= 26, ["orbit"]= 3, diff --git a/src/TreeData/3_27_ruthless_alternate/tree.lua b/src/TreeData/3_27_ruthless_alternate/tree.lua index 1d126324c5..42a4b7938f 100644 --- a/src/TreeData/3_27_ruthless_alternate/tree.lua +++ b/src/TreeData/3_27_ruthless_alternate/tree.lua @@ -15058,17 +15058,14 @@ return { }, [52855]= { ["skill"]= 52855, - ["name"]= "Otherworldly Appendages", + ["name"]= "Cryogenesis", ["icon"]= "Art/2DArt/SkillIcons/passives/AtlasTrees/BreachNotable4.png", ["isNotable"]= true, ["ascendancyName"]= "Breachlord", ["isBloodline"]= true, ["stats"]= { - "Take 15% less Lightning Damage with at least one Eshgraft grafted to you", - "Take 15% less Cold Damage with at least one Tulgraft grafted to you", - "Take 15% less Physical Damage with at least one Uulgraft grafted to you", - "Take 15% less Fire Damage with at least one Xophgraft grafted to you", - "Nearby Enemies take 100% increased Damage from Graft Skills" + "If Intelligence is your single highest Attribute, all added Damage is treated as added Lightning Damage", + "If Dexterity is your single highest Attribute, all added Damage is treated as added Cold Damage" }, ["group"]= 31, ["orbit"]= 3, From 377d6579de91eb04ba5bf5b456823900810979dc Mon Sep 17 00:00:00 2001 From: LocalIdentity Date: Fri, 6 Mar 2026 06:18:34 +1100 Subject: [PATCH 2/8] Revert tree changes --- src/TreeData/3_27/tree.lua | 9 ++++++--- src/TreeData/3_27_alternate/tree.lua | 9 ++++++--- src/TreeData/3_27_ruthless/tree.lua | 9 ++++++--- src/TreeData/3_27_ruthless_alternate/tree.lua | 9 ++++++--- 4 files changed, 24 insertions(+), 12 deletions(-) diff --git a/src/TreeData/3_27/tree.lua b/src/TreeData/3_27/tree.lua index e9216e971e..6f3f9332e3 100644 --- a/src/TreeData/3_27/tree.lua +++ b/src/TreeData/3_27/tree.lua @@ -14450,14 +14450,17 @@ return { }, [52855]= { ["skill"]= 52855, - ["name"]= "Cryogenesis", + ["name"]= "Otherworldly Appendages", ["icon"]= "Art/2DArt/SkillIcons/passives/AtlasTrees/BreachNotable4.png", ["isNotable"]= true, ["ascendancyName"]= "Breachlord", ["isBloodline"]= true, ["stats"]= { - "If Intelligence is your single highest Attribute, all added Damage is treated as added Lightning Damage", - "If Dexterity is your single highest Attribute, all added Damage is treated as added Cold Damage" + "Take 15% less Lightning Damage with at least one Eshgraft grafted to you", + "Take 15% less Cold Damage with at least one Tulgraft grafted to you", + "Take 15% less Physical Damage with at least one Uulgraft grafted to you", + "Take 15% less Fire Damage with at least one Xophgraft grafted to you", + "Nearby Enemies take 100% increased Damage from Graft Skills" }, ["group"]= 26, ["orbit"]= 3, diff --git a/src/TreeData/3_27_alternate/tree.lua b/src/TreeData/3_27_alternate/tree.lua index 7f8d2335d8..3589c96d75 100644 --- a/src/TreeData/3_27_alternate/tree.lua +++ b/src/TreeData/3_27_alternate/tree.lua @@ -15057,14 +15057,17 @@ return { }, [52855]= { ["skill"]= 52855, - ["name"]= "Cryogenesis", + ["name"]= "Otherworldly Appendages", ["icon"]= "Art/2DArt/SkillIcons/passives/AtlasTrees/BreachNotable4.png", ["isNotable"]= true, ["ascendancyName"]= "Breachlord", ["isBloodline"]= true, ["stats"]= { - "If Intelligence is your single highest Attribute, all added Damage is treated as added Lightning Damage", - "If Dexterity is your single highest Attribute, all added Damage is treated as added Cold Damage" + "Take 15% less Lightning Damage with at least one Eshgraft grafted to you", + "Take 15% less Cold Damage with at least one Tulgraft grafted to you", + "Take 15% less Physical Damage with at least one Uulgraft grafted to you", + "Take 15% less Fire Damage with at least one Xophgraft grafted to you", + "Nearby Enemies take 100% increased Damage from Graft Skills" }, ["group"]= 31, ["orbit"]= 3, diff --git a/src/TreeData/3_27_ruthless/tree.lua b/src/TreeData/3_27_ruthless/tree.lua index b8b41268d6..6ea05e0d86 100644 --- a/src/TreeData/3_27_ruthless/tree.lua +++ b/src/TreeData/3_27_ruthless/tree.lua @@ -14438,14 +14438,17 @@ return { }, [52855]= { ["skill"]= 52855, - ["name"]= "Cryogenesis", + ["name"]= "Otherworldly Appendages", ["icon"]= "Art/2DArt/SkillIcons/passives/AtlasTrees/BreachNotable4.png", ["isNotable"]= true, ["ascendancyName"]= "Breachlord", ["isBloodline"]= true, ["stats"]= { - "If Intelligence is your single highest Attribute, all added Damage is treated as added Lightning Damage", - "If Dexterity is your single highest Attribute, all added Damage is treated as added Cold Damage" + "Take 15% less Lightning Damage with at least one Eshgraft grafted to you", + "Take 15% less Cold Damage with at least one Tulgraft grafted to you", + "Take 15% less Physical Damage with at least one Uulgraft grafted to you", + "Take 15% less Fire Damage with at least one Xophgraft grafted to you", + "Nearby Enemies take 100% increased Damage from Graft Skills" }, ["group"]= 26, ["orbit"]= 3, diff --git a/src/TreeData/3_27_ruthless_alternate/tree.lua b/src/TreeData/3_27_ruthless_alternate/tree.lua index 42a4b7938f..1d126324c5 100644 --- a/src/TreeData/3_27_ruthless_alternate/tree.lua +++ b/src/TreeData/3_27_ruthless_alternate/tree.lua @@ -15058,14 +15058,17 @@ return { }, [52855]= { ["skill"]= 52855, - ["name"]= "Cryogenesis", + ["name"]= "Otherworldly Appendages", ["icon"]= "Art/2DArt/SkillIcons/passives/AtlasTrees/BreachNotable4.png", ["isNotable"]= true, ["ascendancyName"]= "Breachlord", ["isBloodline"]= true, ["stats"]= { - "If Intelligence is your single highest Attribute, all added Damage is treated as added Lightning Damage", - "If Dexterity is your single highest Attribute, all added Damage is treated as added Cold Damage" + "Take 15% less Lightning Damage with at least one Eshgraft grafted to you", + "Take 15% less Cold Damage with at least one Tulgraft grafted to you", + "Take 15% less Physical Damage with at least one Uulgraft grafted to you", + "Take 15% less Fire Damage with at least one Xophgraft grafted to you", + "Nearby Enemies take 100% increased Damage from Graft Skills" }, ["group"]= 31, ["orbit"]= 3, From 2289e9fe3a17dfedf58aa4e2132d6efeff7dd93d Mon Sep 17 00:00:00 2001 From: EtherealCarnivore <42915554+EtherealCarnivore@users.noreply.github.com> Date: Thu, 5 Mar 2026 23:52:50 +0200 Subject: [PATCH 3/8] Add ConvertMod to ModStore/ModList/ModDB Like ReplaceMod but matches by oldName instead of the new mod's name, so it can change a mod's identity (e.g. FireMin -> ColdMin) rather than just updating its value. ModDB moves the mod between name buckets. --- src/Classes/ModDB.lua | 40 ++++++++++++++++++++++++++++++++++++++++ src/Classes/ModList.lua | 21 +++++++++++++++++++++ src/Classes/ModStore.lua | 13 +++++++++++++ 3 files changed, 74 insertions(+) diff --git a/src/Classes/ModDB.lua b/src/Classes/ModDB.lua index f60ac59e49..3b7ae60922 100644 --- a/src/Classes/ModDB.lua +++ b/src/Classes/ModDB.lua @@ -7,6 +7,7 @@ local ipairs = ipairs local pairs = pairs local select = select local t_insert = table.insert +local t_remove = table.remove local m_floor = math.floor local m_min = math.min local m_max = math.max @@ -65,6 +66,45 @@ function ModDBClass:ReplaceModInternal(mod) return false end +---ConvertModInternal +--- Converts an existing mod with oldName to a new mod with a different name. +--- Moves the mod from the old name's bucket to the new name's bucket. +--- If no matching mod exists, then the function returns false +---@param oldName string @The name of the existing mod to find +---@param mod table @The new mod to replace it with +---@return boolean @Whether any mod was converted +function ModDBClass:ConvertModInternal(oldName, mod) + if not self.mods[oldName] then + if self.parent then + return self.parent:ConvertModInternal(oldName, mod) + end + return false + end + + local oldList = self.mods[oldName] + for i = 1, #oldList do + local curMod = oldList[i] + if oldName == curMod.name and mod.type == curMod.type and mod.flags == curMod.flags and mod.keywordFlags == curMod.keywordFlags and mod.source == curMod.source and not curMod.converted then + -- Remove from old name's bucket + t_remove(oldList, i) + -- Add to new name's bucket + local newName = mod.name + if not self.mods[newName] then + self.mods[newName] = { } + end + mod.converted = true + t_insert(self.mods[newName], mod) + return true + end + end + + if self.parent then + return self.parent:ConvertModInternal(oldName, mod) + end + + return false +end + function ModDBClass:AddList(modList) local mods = self.mods for i, mod in ipairs(modList) do diff --git a/src/Classes/ModList.lua b/src/Classes/ModList.lua index b27393279a..7bb6e2aba8 100644 --- a/src/Classes/ModList.lua +++ b/src/Classes/ModList.lua @@ -45,6 +45,27 @@ function ModListClass:ReplaceModInternal(mod) return false end +---ConvertModInternal +--- Converts an existing mod with oldName to a new mod with a different name. +--- If no matching mod exists, then the function returns false +---@param oldName string @The name of the existing mod to find +---@param mod table @The new mod to replace it with +---@return boolean @Whether any mod was converted +function ModListClass:ConvertModInternal(oldName, mod) + for i, curMod in ipairs(self) do + if oldName == curMod.name and mod.type == curMod.type and mod.flags == curMod.flags and mod.keywordFlags == curMod.keywordFlags and mod.source == curMod.source then + self[i] = mod + return true + end + end + + if self.parent then + return self.parent:ConvertModInternal(oldName, mod) + end + + return false +end + function ModListClass:MergeMod(mod, skipNonAdditive) if mod.type == "BASE" or mod.type == "INC" or mod.type == "MORE" then for i = 1, #self do diff --git a/src/Classes/ModStore.lua b/src/Classes/ModStore.lua index ff0130c481..ec61927e90 100644 --- a/src/Classes/ModStore.lua +++ b/src/Classes/ModStore.lua @@ -110,6 +110,19 @@ function ModStoreClass:ReplaceMod(...) end end +---ConvertMod +--- Converts an existing mod to a new name, replacing it in the store. +--- Finds a mod matching oldName with the same type, flags, keywordFlags, and source as the new mod. +--- If no matching mod exists, the new mod is added instead. +---@param oldName string @The name of the existing mod to convert +---@param ... any @Parameters to be passed along to the modLib.createMod function (new name, type, value, source, ...) +function ModStoreClass:ConvertMod(oldName, ...) + local mod = mod_createMod(...) + if not self:ConvertModInternal(oldName, mod) then + self:AddMod(mod) + end +end + function ModStoreClass:Combine(modType, cfg, ...) if modType == "MORE" then return self:More(cfg, ...) From c5591733cbeedbf79ee4021b118af1e3dd28302c Mon Sep 17 00:00:00 2001 From: EtherealCarnivore <42915554+EtherealCarnivore@users.noreply.github.com> Date: Fri, 6 Mar 2026 01:19:41 +0200 Subject: [PATCH 4/8] Rework Cryogenesis to use ConvertMod, match 3.28 tree wording ModParser entries now match the actual 3.28 tree text instead of guessed wording. Handles the "Lighting" typo with a pattern. CalcOffence uses ConvertMod to redirect added damage mods before the damage loop so breakdowns show the source properly. Base Elemental Hit is excluded per the node text. Added ConvertMod to ModStore/ModList/ModDB - like ReplaceMod but matches by oldName so it can change a mod's identity. --- src/Modules/CalcOffence.lua | 39 ++++++++++++++++++++++--------------- src/Modules/ModParser.lua | 9 +++++---- 2 files changed, 28 insertions(+), 20 deletions(-) diff --git a/src/Modules/CalcOffence.lua b/src/Modules/CalcOffence.lua index 18ec301474..9d93508259 100644 --- a/src/Modules/CalcOffence.lua +++ b/src/Modules/CalcOffence.lua @@ -3048,13 +3048,33 @@ function calcs.offence(env, actor, activeSkill) runSkillFunc("postCritFunc") - -- Check for added damage redirection (Cryogenesis) + -- Added damage redirection (Cryogenesis) + -- Convert all added damage mods to the target type before the damage loop + -- so breakdowns show the redirected source correctly. + -- Base Elemental Hit is excluded per the node text. local addedDamageRedirectType = nil if skillModList:Flag(cfg, "AllAddedDamageAsLightning") then addedDamageRedirectType = "Lightning" elseif skillModList:Flag(cfg, "AllAddedDamageAsCold") then addedDamageRedirectType = "Cold" end + if addedDamageRedirectType then + local skipRedirect = activeSkill.activeEffect.grantedEffect.name == "Elemental Hit" + if not skipRedirect then + for _, damageType in ipairs(dmgTypeList) do + if damageType ~= addedDamageRedirectType then + for _, value in ipairs(skillModList:Tabulate("BASE", cfg, damageType.."Min")) do + local mod = value.mod + skillModList:ConvertMod(damageType.."Min", addedDamageRedirectType.."Min", "BASE", mod.value, mod.source, mod.flags, mod.keywordFlags, unpack(mod)) + end + for _, value in ipairs(skillModList:Tabulate("BASE", cfg, damageType.."Max")) do + local mod = value.mod + skillModList:ConvertMod(damageType.."Max", addedDamageRedirectType.."Max", "BASE", mod.value, mod.source, mod.flags, mod.keywordFlags, unpack(mod)) + end + end + end + end + end -- Calculate base hit damage for _, damageType in ipairs(dmgTypeList) do @@ -3062,21 +3082,8 @@ function calcs.offence(env, actor, activeSkill) local damageTypeMax = damageType.."Max" local baseMultiplier = activeSkill.activeEffect.grantedEffectLevel.baseMultiplier or skillData.baseMultiplier or 1 local damageEffectiveness = activeSkill.activeEffect.grantedEffectLevel.damageEffectiveness or skillData.damageEffectiveness or 1 - local addedMin, addedMax - if addedDamageRedirectType then - if damageType == addedDamageRedirectType then - addedMin, addedMax = 0, 0 - for _, srcType in ipairs(dmgTypeList) do - addedMin = addedMin + skillModList:Sum("BASE", cfg, srcType.."Min") + enemyDB:Sum("BASE", cfg, "Self"..srcType.."Min") - addedMax = addedMax + skillModList:Sum("BASE", cfg, srcType.."Max") + enemyDB:Sum("BASE", cfg, "Self"..srcType.."Max") - end - else - addedMin, addedMax = 0, 0 - end - else - addedMin = skillModList:Sum("BASE", cfg, damageTypeMin) + enemyDB:Sum("BASE", cfg, "Self"..damageTypeMin) - addedMax = skillModList:Sum("BASE", cfg, damageTypeMax) + enemyDB:Sum("BASE", cfg, "Self"..damageTypeMax) - end + local addedMin = skillModList:Sum("BASE", cfg, damageTypeMin) + enemyDB:Sum("BASE", cfg, "Self"..damageTypeMin) + local addedMax = skillModList:Sum("BASE", cfg, damageTypeMax) + enemyDB:Sum("BASE", cfg, "Self"..damageTypeMax) local addedMult = calcLib.mod(skillModList, cfg, "Added"..damageType.."Damage", "AddedDamage") local baseMin = ((source[damageTypeMin] or 0) + (source[damageType.."BonusMin"] or 0)) * baseMultiplier + addedMin * damageEffectiveness * addedMult local baseMax = ((source[damageTypeMax] or 0) + (source[damageType.."BonusMax"] or 0)) * baseMultiplier + addedMax * damageEffectiveness * addedMult diff --git a/src/Modules/ModParser.lua b/src/Modules/ModParser.lua index 7fabb65490..44d8098e48 100644 --- a/src/Modules/ModParser.lua +++ b/src/Modules/ModParser.lua @@ -5286,12 +5286,13 @@ local specialModList = { ["consecrated path and purifying flame create profane ground instead of consecrated ground"] = { flag("Condition:CreateProfaneGround"), }, - ["if intelligence is your single highest attribute, all added damage is treated as added lightning damage"] = { - flag("AllAddedDamageAsLightning", { type = "Condition", var = "IntSingleHighestAttribute" }), - }, - ["if dexterity is your single highest attribute, all added damage is treated as added cold damage"] = { + ["you gain added cold damage instead of added damage of other types if dexterity exceeds both other attributes"] = { flag("AllAddedDamageAsCold", { type = "Condition", var = "DexSingleHighestAttribute" }), }, + ["you gain added lightn?ing damage instead of added damage of other types if intelligence exceeds both other attributes"] = { + flag("AllAddedDamageAsLightning", { type = "Condition", var = "IntSingleHighestAttribute" }), + }, + ["elemental hit's added damage cannot be replaced this way"] = { }, ["you have consecrated ground around you while stationary if strength is your highest attribute"] = { flag("Condition:OnConsecratedGround", { type = "Condition", var = "StrHighestAttribute" }, { type = "Condition", var = "Stationary" }), }, From b1766ee040ea85ec2755113d1afb9963b3729717 Mon Sep 17 00:00:00 2001 From: EtherealCarnivore <42915554+EtherealCarnivore@users.noreply.github.com> Date: Fri, 6 Mar 2026 01:46:22 +0200 Subject: [PATCH 5/8] Update ModCache entries for Cryogenesis ascendancy mods The old cache had these 3.28 tree lines stored as parse failures from before the ModParser entries existed. Since the cache uses exact-case text as the key, the tree's mixed-case text always hit the stale entry and returned "unsupported" without reaching the new specialModList patterns. Replaced 3 individual entries with correct parsed results and removed 3 concatenated multi-line entries that were never going to match anyway. --- src/Data/ModCache.lua | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/Data/ModCache.lua b/src/Data/ModCache.lua index b807a765e8..dbabd5d4cf 100755 --- a/src/Data/ModCache.lua +++ b/src/Data/ModCache.lua @@ -8413,7 +8413,7 @@ c["Elemental Ailments you inflict are Reflected to you Elemental Damage with Hit c["Elemental Damage with Hits is Lucky while you are Shocked"]={{[1]={[1]={type="Condition",var="Shocked"},flags=0,keywordFlags=0,name="ElementalLuckHits",type="FLAG",value=true}},nil} c["Elemental Damage you Deal with Hits is Resisted by lowest Elemental Resistance instead"]={{[1]={flags=0,keywordFlags=0,name="ElementalDamageUsesLowestResistance",type="FLAG",value=true}},nil} c["Elemental Equilibrium"]={{[1]={flags=0,keywordFlags=0,name="Keystone",type="LIST",value="Elemental Equilibrium"}},nil} -c["Elemental Hit's Added Damage cannot be replaced this way"]={nil,"Elemental Hit's Added Damage cannot be replaced this way "} +c["Elemental Hit's Added Damage cannot be replaced this way"]={{},nil} c["Elemental Overload"]={{[1]={flags=0,keywordFlags=0,name="Keystone",type="LIST",value="Elemental Overload"}},nil} c["Elemental Resistance values as inverted"]={nil,"Elemental Resistance values as inverted "} c["Elemental Resistance values as inverted Limited to 1 Runegraft of the Gauche"]={nil,"Elemental Resistance values as inverted Limited to 1 Runegraft of the Gauche "} @@ -12581,11 +12581,8 @@ c["You count as on Low Life while you are Cursed with Vulnerability"]={{[1]={[1] c["You do not inherently take less Damage for having Fortification"]={{[1]={flags=0,keywordFlags=0,name="Condition:NoFortificationMitigation",type="FLAG",value=true}},nil} c["You gain 3 Grasping Vines when you take a Critical Strike"]={{}," Grasping Vines when you take a Critical Strike "} c["You gain 3 Grasping Vines when you take a Critical Strike Nearby stationary Enemies gain a Grasping Vine every 0.5 seconds"]={{}," Grasping Vines when you take a Critical Strike Nearby stationary Enemies gain a Grasping Vine every 0.5 seconds "} -c["You gain Added Cold Damage instead of Added Damage of other types if Dexterity exceeds both other Attributes"]={nil,"Added Cold Damage instead of Added Damage of other types if Dexterity exceeds both other Attributes "} -c["You gain Added Cold Damage instead of Added Damage of other types if Dexterity exceeds both other Attributes You gain Added Lighting Damage instead of Added Damage of other types if Intelligence exceeds both other Attributes"]={nil,"Added Cold Damage instead of Added Damage of other types if Dexterity exceeds both other Attributes You gain Added Lighting Damage instead of Added Damage of other types if Intelligence exceeds both other Attributes "} -c["You gain Added Cold Damage instead of Added Damage of other types if Dexterity exceeds both other Attributes You gain Added Lighting Damage instead of Added Damage of other types if Intelligence exceeds both other Attributes Elemental Hit's Added Damage cannot be replaced this way"]={nil,"Added Cold Damage instead of Added Damage of other types if Dexterity exceeds both other Attributes You gain Added Lighting Damage instead of Added Damage of other types if Intelligence exceeds both other Attributes Elemental Hit's Added Damage cannot be replaced this way "} -c["You gain Added Lighting Damage instead of Added Damage of other types if Intelligence exceeds both other Attributes"]={nil,"Added Lighting Damage instead of Added Damage of other types if Intelligence exceeds both other Attributes "} -c["You gain Added Lighting Damage instead of Added Damage of other types if Intelligence exceeds both other Attributes Elemental Hit's Added Damage cannot be replaced this way"]={nil,"Added Lighting Damage instead of Added Damage of other types if Intelligence exceeds both other Attributes Elemental Hit's Added Damage cannot be replaced this way "} +c["You gain Added Cold Damage instead of Added Damage of other types if Dexterity exceeds both other Attributes"]={{[1]={[1]={type="Condition",var="DexSingleHighestAttribute"},flags=0,keywordFlags=0,name="AllAddedDamageAsCold",type="FLAG",value=true}},nil} +c["You gain Added Lighting Damage instead of Added Damage of other types if Intelligence exceeds both other Attributes"]={{[1]={[1]={type="Condition",var="IntSingleHighestAttribute"},flags=0,keywordFlags=0,name="AllAddedDamageAsLightning",type="FLAG",value=true}},nil} c["You gain Divinity for 10 seconds on reaching maximum Divine Charges"]={{[1]={[1]={type="Condition",var="Divinity"},flags=0,keywordFlags=0,name="ElementalDamage",type="MORE",value=75},[2]={[1]={type="Condition",var="Divinity"},flags=0,keywordFlags=0,name="ElementalDamageTaken",type="MORE",value=-25}},nil} c["You gain Onslaught for 1 seconds on Killing Taunted Enemies"]={{[1]={[1]={type="Condition",var="KilledTauntedEnemyRecently"},flags=0,keywordFlags=0,name="Condition:Onslaught",type="FLAG",value=true}},nil} c["You gain Onslaught for 1 seconds per Endurance Charge when Hit"]={{[1]={[1]={type="Multiplier",var="EnduranceCharge"},flags=0,keywordFlags=0,name="Condition:Onslaught",type="FLAG",value=true}}," when Hit "} From baee98d7daeb01f1edd3a754b7928a9a7ebe6414 Mon Sep 17 00:00:00 2001 From: EtherealCarnivore <42915554+EtherealCarnivore@users.noreply.github.com> Date: Fri, 6 Mar 2026 03:51:02 +0200 Subject: [PATCH 6/8] Use offset mods for Cryogenesis redirect, label source Replaced ConvertMod approach with sum-and-offset: for each non-target damage type, add a negative mod to cancel the original and a positive mod on the target type. Both labeled "Cryogenesis Conversion" so the breakdown popup shows where the redirected damage comes from. Removed ConvertMod/ConvertModInternal from ModStore/ModList/ModDB since nothing uses them anymore. --- src/Classes/ModDB.lua | 38 ------------------------------------- src/Classes/ModList.lua | 20 ------------------- src/Classes/ModStore.lua | 12 ------------ src/Modules/CalcOffence.lua | 14 ++++++++------ 4 files changed, 8 insertions(+), 76 deletions(-) diff --git a/src/Classes/ModDB.lua b/src/Classes/ModDB.lua index 3b7ae60922..62260fd575 100644 --- a/src/Classes/ModDB.lua +++ b/src/Classes/ModDB.lua @@ -66,44 +66,6 @@ function ModDBClass:ReplaceModInternal(mod) return false end ----ConvertModInternal ---- Converts an existing mod with oldName to a new mod with a different name. ---- Moves the mod from the old name's bucket to the new name's bucket. ---- If no matching mod exists, then the function returns false ----@param oldName string @The name of the existing mod to find ----@param mod table @The new mod to replace it with ----@return boolean @Whether any mod was converted -function ModDBClass:ConvertModInternal(oldName, mod) - if not self.mods[oldName] then - if self.parent then - return self.parent:ConvertModInternal(oldName, mod) - end - return false - end - - local oldList = self.mods[oldName] - for i = 1, #oldList do - local curMod = oldList[i] - if oldName == curMod.name and mod.type == curMod.type and mod.flags == curMod.flags and mod.keywordFlags == curMod.keywordFlags and mod.source == curMod.source and not curMod.converted then - -- Remove from old name's bucket - t_remove(oldList, i) - -- Add to new name's bucket - local newName = mod.name - if not self.mods[newName] then - self.mods[newName] = { } - end - mod.converted = true - t_insert(self.mods[newName], mod) - return true - end - end - - if self.parent then - return self.parent:ConvertModInternal(oldName, mod) - end - - return false -end function ModDBClass:AddList(modList) local mods = self.mods diff --git a/src/Classes/ModList.lua b/src/Classes/ModList.lua index 7bb6e2aba8..36de750efb 100644 --- a/src/Classes/ModList.lua +++ b/src/Classes/ModList.lua @@ -45,26 +45,6 @@ function ModListClass:ReplaceModInternal(mod) return false end ----ConvertModInternal ---- Converts an existing mod with oldName to a new mod with a different name. ---- If no matching mod exists, then the function returns false ----@param oldName string @The name of the existing mod to find ----@param mod table @The new mod to replace it with ----@return boolean @Whether any mod was converted -function ModListClass:ConvertModInternal(oldName, mod) - for i, curMod in ipairs(self) do - if oldName == curMod.name and mod.type == curMod.type and mod.flags == curMod.flags and mod.keywordFlags == curMod.keywordFlags and mod.source == curMod.source then - self[i] = mod - return true - end - end - - if self.parent then - return self.parent:ConvertModInternal(oldName, mod) - end - - return false -end function ModListClass:MergeMod(mod, skipNonAdditive) if mod.type == "BASE" or mod.type == "INC" or mod.type == "MORE" then diff --git a/src/Classes/ModStore.lua b/src/Classes/ModStore.lua index ec61927e90..45f8dded15 100644 --- a/src/Classes/ModStore.lua +++ b/src/Classes/ModStore.lua @@ -110,18 +110,6 @@ function ModStoreClass:ReplaceMod(...) end end ----ConvertMod ---- Converts an existing mod to a new name, replacing it in the store. ---- Finds a mod matching oldName with the same type, flags, keywordFlags, and source as the new mod. ---- If no matching mod exists, the new mod is added instead. ----@param oldName string @The name of the existing mod to convert ----@param ... any @Parameters to be passed along to the modLib.createMod function (new name, type, value, source, ...) -function ModStoreClass:ConvertMod(oldName, ...) - local mod = mod_createMod(...) - if not self:ConvertModInternal(oldName, mod) then - self:AddMod(mod) - end -end function ModStoreClass:Combine(modType, cfg, ...) if modType == "MORE" then diff --git a/src/Modules/CalcOffence.lua b/src/Modules/CalcOffence.lua index 9d93508259..843f4affd7 100644 --- a/src/Modules/CalcOffence.lua +++ b/src/Modules/CalcOffence.lua @@ -3063,13 +3063,15 @@ function calcs.offence(env, actor, activeSkill) if not skipRedirect then for _, damageType in ipairs(dmgTypeList) do if damageType ~= addedDamageRedirectType then - for _, value in ipairs(skillModList:Tabulate("BASE", cfg, damageType.."Min")) do - local mod = value.mod - skillModList:ConvertMod(damageType.."Min", addedDamageRedirectType.."Min", "BASE", mod.value, mod.source, mod.flags, mod.keywordFlags, unpack(mod)) + local addedMin = skillModList:Sum("BASE", cfg, damageType.."Min") + local addedMax = skillModList:Sum("BASE", cfg, damageType.."Max") + if addedMin ~= 0 then + skillModList:NewMod(damageType.."Min", "BASE", -addedMin, "Cryogenesis Conversion") + skillModList:NewMod(addedDamageRedirectType.."Min", "BASE", addedMin, "Cryogenesis Conversion") end - for _, value in ipairs(skillModList:Tabulate("BASE", cfg, damageType.."Max")) do - local mod = value.mod - skillModList:ConvertMod(damageType.."Max", addedDamageRedirectType.."Max", "BASE", mod.value, mod.source, mod.flags, mod.keywordFlags, unpack(mod)) + if addedMax ~= 0 then + skillModList:NewMod(damageType.."Max", "BASE", -addedMax, "Cryogenesis Conversion") + skillModList:NewMod(addedDamageRedirectType.."Max", "BASE", addedMax, "Cryogenesis Conversion") end end end From 161d3c4f70e20bd8d767cae6c56eb709baff00e4 Mon Sep 17 00:00:00 2001 From: LocalIdentity Date: Fri, 6 Mar 2026 20:27:09 +1100 Subject: [PATCH 7/8] Revert "Use offset mods for Cryogenesis redirect, label source" This reverts commit baee98d7daeb01f1edd3a754b7928a9a7ebe6414. --- src/Classes/ModDB.lua | 38 +++++++++++++++++++++++++++++++++++++ src/Classes/ModList.lua | 20 +++++++++++++++++++ src/Classes/ModStore.lua | 12 ++++++++++++ src/Modules/CalcOffence.lua | 14 ++++++-------- 4 files changed, 76 insertions(+), 8 deletions(-) diff --git a/src/Classes/ModDB.lua b/src/Classes/ModDB.lua index 62260fd575..3b7ae60922 100644 --- a/src/Classes/ModDB.lua +++ b/src/Classes/ModDB.lua @@ -66,6 +66,44 @@ function ModDBClass:ReplaceModInternal(mod) return false end +---ConvertModInternal +--- Converts an existing mod with oldName to a new mod with a different name. +--- Moves the mod from the old name's bucket to the new name's bucket. +--- If no matching mod exists, then the function returns false +---@param oldName string @The name of the existing mod to find +---@param mod table @The new mod to replace it with +---@return boolean @Whether any mod was converted +function ModDBClass:ConvertModInternal(oldName, mod) + if not self.mods[oldName] then + if self.parent then + return self.parent:ConvertModInternal(oldName, mod) + end + return false + end + + local oldList = self.mods[oldName] + for i = 1, #oldList do + local curMod = oldList[i] + if oldName == curMod.name and mod.type == curMod.type and mod.flags == curMod.flags and mod.keywordFlags == curMod.keywordFlags and mod.source == curMod.source and not curMod.converted then + -- Remove from old name's bucket + t_remove(oldList, i) + -- Add to new name's bucket + local newName = mod.name + if not self.mods[newName] then + self.mods[newName] = { } + end + mod.converted = true + t_insert(self.mods[newName], mod) + return true + end + end + + if self.parent then + return self.parent:ConvertModInternal(oldName, mod) + end + + return false +end function ModDBClass:AddList(modList) local mods = self.mods diff --git a/src/Classes/ModList.lua b/src/Classes/ModList.lua index 36de750efb..7bb6e2aba8 100644 --- a/src/Classes/ModList.lua +++ b/src/Classes/ModList.lua @@ -45,6 +45,26 @@ function ModListClass:ReplaceModInternal(mod) return false end +---ConvertModInternal +--- Converts an existing mod with oldName to a new mod with a different name. +--- If no matching mod exists, then the function returns false +---@param oldName string @The name of the existing mod to find +---@param mod table @The new mod to replace it with +---@return boolean @Whether any mod was converted +function ModListClass:ConvertModInternal(oldName, mod) + for i, curMod in ipairs(self) do + if oldName == curMod.name and mod.type == curMod.type and mod.flags == curMod.flags and mod.keywordFlags == curMod.keywordFlags and mod.source == curMod.source then + self[i] = mod + return true + end + end + + if self.parent then + return self.parent:ConvertModInternal(oldName, mod) + end + + return false +end function ModListClass:MergeMod(mod, skipNonAdditive) if mod.type == "BASE" or mod.type == "INC" or mod.type == "MORE" then diff --git a/src/Classes/ModStore.lua b/src/Classes/ModStore.lua index 45f8dded15..ec61927e90 100644 --- a/src/Classes/ModStore.lua +++ b/src/Classes/ModStore.lua @@ -110,6 +110,18 @@ function ModStoreClass:ReplaceMod(...) end end +---ConvertMod +--- Converts an existing mod to a new name, replacing it in the store. +--- Finds a mod matching oldName with the same type, flags, keywordFlags, and source as the new mod. +--- If no matching mod exists, the new mod is added instead. +---@param oldName string @The name of the existing mod to convert +---@param ... any @Parameters to be passed along to the modLib.createMod function (new name, type, value, source, ...) +function ModStoreClass:ConvertMod(oldName, ...) + local mod = mod_createMod(...) + if not self:ConvertModInternal(oldName, mod) then + self:AddMod(mod) + end +end function ModStoreClass:Combine(modType, cfg, ...) if modType == "MORE" then diff --git a/src/Modules/CalcOffence.lua b/src/Modules/CalcOffence.lua index 843f4affd7..9d93508259 100644 --- a/src/Modules/CalcOffence.lua +++ b/src/Modules/CalcOffence.lua @@ -3063,15 +3063,13 @@ function calcs.offence(env, actor, activeSkill) if not skipRedirect then for _, damageType in ipairs(dmgTypeList) do if damageType ~= addedDamageRedirectType then - local addedMin = skillModList:Sum("BASE", cfg, damageType.."Min") - local addedMax = skillModList:Sum("BASE", cfg, damageType.."Max") - if addedMin ~= 0 then - skillModList:NewMod(damageType.."Min", "BASE", -addedMin, "Cryogenesis Conversion") - skillModList:NewMod(addedDamageRedirectType.."Min", "BASE", addedMin, "Cryogenesis Conversion") + for _, value in ipairs(skillModList:Tabulate("BASE", cfg, damageType.."Min")) do + local mod = value.mod + skillModList:ConvertMod(damageType.."Min", addedDamageRedirectType.."Min", "BASE", mod.value, mod.source, mod.flags, mod.keywordFlags, unpack(mod)) end - if addedMax ~= 0 then - skillModList:NewMod(damageType.."Max", "BASE", -addedMax, "Cryogenesis Conversion") - skillModList:NewMod(addedDamageRedirectType.."Max", "BASE", addedMax, "Cryogenesis Conversion") + for _, value in ipairs(skillModList:Tabulate("BASE", cfg, damageType.."Max")) do + local mod = value.mod + skillModList:ConvertMod(damageType.."Max", addedDamageRedirectType.."Max", "BASE", mod.value, mod.source, mod.flags, mod.keywordFlags, unpack(mod)) end end end From a20c100901b29f920900fbc56a07dc9d34661e26 Mon Sep 17 00:00:00 2001 From: LocalIdentity Date: Fri, 6 Mar 2026 20:28:39 +1100 Subject: [PATCH 8/8] Fix Ele Hit handling and breakdown Only the base damage granted via ele hit is not meant to be converted. Mods from gear are meant to work still The breakdown now shows a note on the mods that are converted that the cryogenesis node is what is changing them --- src/Modules/CalcOffence.lua | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/src/Modules/CalcOffence.lua b/src/Modules/CalcOffence.lua index 9d93508259..65265f745c 100644 --- a/src/Modules/CalcOffence.lua +++ b/src/Modules/CalcOffence.lua @@ -3059,17 +3059,18 @@ function calcs.offence(env, actor, activeSkill) addedDamageRedirectType = "Cold" end if addedDamageRedirectType then - local skipRedirect = activeSkill.activeEffect.grantedEffect.name == "Elemental Hit" - if not skipRedirect then - for _, damageType in ipairs(dmgTypeList) do - if damageType ~= addedDamageRedirectType then - for _, value in ipairs(skillModList:Tabulate("BASE", cfg, damageType.."Min")) do - local mod = value.mod - skillModList:ConvertMod(damageType.."Min", addedDamageRedirectType.."Min", "BASE", mod.value, mod.source, mod.flags, mod.keywordFlags, unpack(mod)) + for _, damageType in ipairs(dmgTypeList) do + if damageType ~= addedDamageRedirectType then + for _, value in ipairs(skillModList:Tabulate("BASE", cfg, damageType.."Min")) do + local mod = value.mod + if mod.source ~= "Skill:ElementalHit" then + skillModList:ConvertMod(damageType.."Min", addedDamageRedirectType.."Min", "BASE", mod.value, mod.source, mod.flags, mod.keywordFlags, { type = "Cryogenesis Added Damage" }, unpack(mod)) end - for _, value in ipairs(skillModList:Tabulate("BASE", cfg, damageType.."Max")) do - local mod = value.mod - skillModList:ConvertMod(damageType.."Max", addedDamageRedirectType.."Max", "BASE", mod.value, mod.source, mod.flags, mod.keywordFlags, unpack(mod)) + end + for _, value in ipairs(skillModList:Tabulate("BASE", cfg, damageType.."Max")) do + local mod = value.mod + if mod.source ~= "Skill:ElementalHit" then + skillModList:ConvertMod(damageType.."Max", addedDamageRedirectType.."Max", "BASE", mod.value, mod.source, mod.flags, mod.keywordFlags, { type = "Cryogenesis Added Damage" }, unpack(mod)) end end end