From 86145caefd90e51291da1d9598cc5a3f58727353 Mon Sep 17 00:00:00 2001 From: vaisest <4550061+vaisest@users.noreply.github.com> Date: Thu, 10 Sep 2026 19:19:11 +0300 Subject: [PATCH 1/2] Fix gem level behaviour of support granted skills --- src/Modules/CalcActiveSkill.lua | 14 ++++++++++---- src/Modules/CalcPerform.lua | 26 +++++++++++++++----------- src/Modules/CalcSections.lua | 2 +- src/Modules/CalcSetup.lua | 10 ++++++++++ 4 files changed, 36 insertions(+), 16 deletions(-) diff --git a/src/Modules/CalcActiveSkill.lua b/src/Modules/CalcActiveSkill.lua index 8526eeff9e..0a5506f571 100644 --- a/src/Modules/CalcActiveSkill.lua +++ b/src/Modules/CalcActiveSkill.lua @@ -437,10 +437,14 @@ function calcs.buildActiveSkillModList(env, activeSkill) skillFlags = activeEffect.statSet.skillFlags end -- Active skills granted by support gems inherit the level of the skill that support applied to. + local supportGrantedInheritedLevel if activeEffect.gemData and activeEffect.gemData.grantedEffect.support then - for _, supportEffect in ipairs(activeSkill.supportList) do - if supportEffect.srcInstance == activeEffect.srcInstance and supportEffect.activeSkillLevel then - activeEffect.level = supportEffect.activeSkillLevel + for _, skill in ipairs(env.player.activeSkillList) do + local effect = skill.activeEffect + if effect ~= activeEffect and effect.level and skill.socketGroup == activeSkill.socketGroup + and not (effect.gemData and effect.gemData.grantedEffect.support) then + supportGrantedInheritedLevel = effect.level + activeEffect.level = effect.level break end end @@ -770,7 +774,9 @@ function calcs.buildActiveSkillModList(env, activeSkill) end -- Apply gem/quality modifiers from support gems - skillModList:NewMod("GemLevel", "BASE", activeSkill.activeEffect.srcInstance and activeSkill.activeEffect.srcInstance.level or activeSkill.activeEffect.level, "Max Level") + local gemMaxLevel = supportGrantedInheritedLevel or (activeSkill.activeEffect.srcInstance and activeSkill.activeEffect.srcInstance.level) or activeSkill.activeEffect.level + local gemMaxLevelSource = supportGrantedInheritedLevel and "Inherited Max Level" or "Max Level" + skillModList:NewMod("GemLevel", "BASE", gemMaxLevel, gemMaxLevelSource) if activeSkill.activeEffect.srcInstance and activeSkill.activeEffect.srcInstance.corrupted and not (activeSkill.activeEffect.srcInstance.fromItem or activeSkill.activeEffect.srcInstance.fromTree or activeSkill.activeEffect.grantedEffect.fromItem or activeSkill.activeEffect.grantedEffect.fromTree) then skillModList:NewMod("GemCorruptionLevel", "BASE", activeSkill.activeEffect.srcInstance.corruptLevel, "Corruption") activeSkill.skillCfg.skillCond["GemCorrupted"] = true diff --git a/src/Modules/CalcPerform.lua b/src/Modules/CalcPerform.lua index b2f044f345..9ebbe43c7b 100644 --- a/src/Modules/CalcPerform.lua +++ b/src/Modules/CalcPerform.lua @@ -3658,22 +3658,26 @@ function calcs.perform(env, skipEHP) local totalSupportLevel = mainSkill.skillModList:Sum("BASE", mainSkill.skillCfg, "GemSupportLevel") local totalCorruptionLevel = mainSkill.skillModList:Sum("BASE", mainSkill.skillCfg, "GemCorruptionLevel") + local inheritsLevel = mainSkill.skillData.inheritsGemLevel output.GemHasLevel = true - output.GemLevel = m_max(baseLevel + totalSupportLevel + totalItemLevel + totalCorruptionLevel, 1) + output.GemLevel = m_max(inheritsLevel and baseLevel or (baseLevel + totalSupportLevel + totalItemLevel + totalCorruptionLevel), 1) if env.player.breakdown then env.player.breakdown.GemLevel = {} t_insert(env.player.breakdown.GemLevel, s_format("%d ^8(level from gem)", baseLevel)) - if totalSupportLevel > 0 then - t_insert(env.player.breakdown.GemLevel, s_format("+ %d ^8(level from support)", totalSupportLevel)) - end - if totalItemLevel > 0 then - t_insert(env.player.breakdown.GemLevel, s_format("+ %d ^8(level from items)", totalItemLevel)) - end - if totalCorruptionLevel > 0 then - t_insert(env.player.breakdown.GemLevel, s_format("+ %d ^8(level from corruption)", totalCorruptionLevel)) - elseif totalCorruptionLevel < 0 then - t_insert(env.player.breakdown.GemLevel, s_format("%d ^8(level from corruption)", totalCorruptionLevel)) + -- support gems inherit levels from the parent gem, which means showing level modifiers is pointless + if not inheritsLevel then + if totalSupportLevel > 0 then + t_insert(env.player.breakdown.GemLevel, s_format("+ %d ^8(level from support)", totalSupportLevel)) + end + if totalItemLevel > 0 then + t_insert(env.player.breakdown.GemLevel, s_format("+ %d ^8(level from items)", totalItemLevel)) + end + if totalCorruptionLevel > 0 then + t_insert(env.player.breakdown.GemLevel, s_format("+ %d ^8(level from corruption)", totalCorruptionLevel)) + elseif totalCorruptionLevel < 0 then + t_insert(env.player.breakdown.GemLevel, s_format("%d ^8(level from corruption)", totalCorruptionLevel)) + end end t_insert(env.player.breakdown.GemLevel, s_format("= %d", output.GemLevel)) end diff --git a/src/Modules/CalcSections.lua b/src/Modules/CalcSections.lua index e5604958c8..7027656ee8 100644 --- a/src/Modules/CalcSections.lua +++ b/src/Modules/CalcSections.lua @@ -698,7 +698,7 @@ return { } } } }, { 1, "SkillTypeStats", 1, colorCodes.OFFENCE, {{ defaultCollapsed = false, label = "Skill type-specific Stats", data = { - { label = "Gem Level", notFlag = "thorns", haveOutput = "GemHasLevel", { format = "{0:output:GemLevel}", { breakdown = "GemLevel" }, { modName = { "GemLevel" }, cfg = "skill" },{ modName = { "GemSupportLevel" }, cfg = "skill" }, { modName = { "GemItemLevel" }, cfg = "skill" }, { modName = { "GemCorruptionLevel" }, cfg = "skill" } }, }, + { label = "Gem Level", notFlag = "thorns", haveOutput = "GemHasLevel", { format = "{0:output:GemLevel}", { breakdown = "GemLevel" }, { modName = { "GemLevel" }, cfg = "skill" }, { modName = { "GemSupportLevel" }, cfg = "skill", notSkillData = "inheritsGemLevel" }, { modName = { "GemItemLevel" }, cfg = "skill", notSkillData = "inheritsGemLevel" }, { modName = { "GemCorruptionLevel" }, cfg = "skill", notSkillData = "inheritsGemLevel" } }, }, { label = "Spirit Cost", color = colorCodes.SPIRIT, haveOutput = "SpiritHasCost", { format = "{0:output:SpiritCost}", { breakdown = "SpiritCost" }, { modName = { "SpiritCost", "Cost", "SpiritCostNoMult" }, cfg = "skill" }, }, }, { label = "Spirit % Cost", color = colorCodes.SPIRIT, haveOutput = "SpiritPercentHasCost", { format = "{0:output:SpiritPercentCost}", { breakdown = "SpiritPercentCost" }, { modName = { "SpiritCost", "Cost", "SpiritCostNoMult" }, cfg = "skill" }, }, }, { label = "Mana Cost", color = colorCodes.MANA, haveOutput = "ManaHasCost", { format = "{0:output:ManaCost}", { breakdown = "ManaCost" }, { modName = { "ManaCost", "Cost", "ManaCostNoMult" }, cfg = "skill" }, }, }, diff --git a/src/Modules/CalcSetup.lua b/src/Modules/CalcSetup.lua index 76c1bd37ba..1f63c0925e 100644 --- a/src/Modules/CalcSetup.lua +++ b/src/Modules/CalcSetup.lua @@ -2454,8 +2454,18 @@ function calcs.initEnv(build, mode, override, specEnv) t_insert(env.player.activeSkillList, env.player.mainSkill) end + -- some active skills, like those granted by support gems depend on the stats of other gems, + -- which means we have to do them last. + local deferredActiveSkills = {} -- Build skill modifier lists for _, activeSkill in pairs(env.player.activeSkillList) do + if activeSkill.activeEffect.gemData and activeSkill.activeEffect.gemData.grantedEffect.support then + table.insert(deferredActiveSkills, activeSkill) + else + calcs.buildActiveSkillModList(env, activeSkill) + end + end + for _, activeSkill in ipairs(deferredActiveSkills) do calcs.buildActiveSkillModList(env, activeSkill) end From 224038417505e9c61c904a023f40edf1d2e3cd4f Mon Sep 17 00:00:00 2001 From: vaisest <4550061+vaisest@users.noreply.github.com> Date: Thu, 10 Sep 2026 19:27:05 +0300 Subject: [PATCH 2/2] Add a test --- spec/System/TestSkills_spec.lua | 32 +++++++++++++++++++++++++++++++- src/Modules/CalcActiveSkill.lua | 1 + 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/spec/System/TestSkills_spec.lua b/spec/System/TestSkills_spec.lua index c9d8f1e6f2..37b28f86b8 100644 --- a/spec/System/TestSkills_spec.lua +++ b/spec/System/TestSkills_spec.lua @@ -909,6 +909,36 @@ describe("TestSkills", function() assert.are.equals(warcryFirstDps, supportFirstDps) end) + it("support-granted active skills inherit tree gem levels from the linked skill", function() + build.skillsTab:PasteSocketGroup("Despair 20/0 1\nDoedre's Undoing 1/0 1") + local socketGroup = build.skillsTab.socketGroupList[#build.skillsTab.socketGroupList] + recalculate() + local darkConsequences = selectActiveSkillById(socketGroup, "ChaosFrogExplosionPlayer") + assert.is_not_nil(darkConsequences) + assert.are.equals(20, darkConsequences.activeEffect.level) + assert.are.equals(20, build.calcsTab.mainOutput.GemLevel) + local baseDamage = build.calcsTab.mainOutput.AverageDamage + + local chaosMasteryNode = build.spec.nodes[63074] + assert.are.equals("Dark Entries", chaosMasteryNode.dn) + assert.are.equals("+1 to Level of all Chaos Skills", chaosMasteryNode.sd[1]) + chaosMasteryNode.alloc = true + build.spec.allocNodes[chaosMasteryNode.id] = chaosMasteryNode + recalculate() + + darkConsequences = selectActiveSkillById(socketGroup, "ChaosFrogExplosionPlayer") + -- the support gem also matches "all Chaos Skills", but its own gem level modifiers + -- must not stack on top of the level inherited from Despair + assert.are.equals(21, darkConsequences.activeEffect.level) + assert.are.equals(21, build.calcsTab.mainOutput.GemLevel) + assert.True(build.calcsTab.mainOutput.AverageDamage > baseDamage) + + local calcFunc, calcBase = build.calcsTab:GetMiscCalculator() + local withoutNode = calcFunc({ removeNodes = { [chaosMasteryNode] = true } }) + assert.are.equals(21, calcBase.GemLevel) + assert.are.equals(20, withoutNode.GemLevel) + assert.True(withoutNode.AverageDamage < calcBase.AverageDamage) + end) it("Flame Breath attack speed scales DPS and is not capped by its channel cooldown", function() build.itemsTab:CreateDisplayItemFromRaw([[ New Item @@ -2228,5 +2258,5 @@ describe("TestSkills", function() local noParrySpellDmg = build.calcsTab.mainOutput.AverageDamage assert.equals(withParrySpellDmg, noParrySpellDmg, "Parry should not affect spell damage") end) - + end) diff --git a/src/Modules/CalcActiveSkill.lua b/src/Modules/CalcActiveSkill.lua index 0a5506f571..b4435d93a5 100644 --- a/src/Modules/CalcActiveSkill.lua +++ b/src/Modules/CalcActiveSkill.lua @@ -445,6 +445,7 @@ function calcs.buildActiveSkillModList(env, activeSkill) and not (effect.gemData and effect.gemData.grantedEffect.support) then supportGrantedInheritedLevel = effect.level activeEffect.level = effect.level + activeSkill.skillData.inheritsGemLevel = true break end end