diff --git a/spec/System/TestSkills_spec.lua b/spec/System/TestSkills_spec.lua index 9609d08349..76806b1902 100644 --- a/spec/System/TestSkills_spec.lua +++ b/spec/System/TestSkills_spec.lua @@ -28,6 +28,41 @@ describe("TestSkills", function() end end + local function assertGemSupportLevel(gemName, expectedLevel, expectedCount) + local count = 0 + for _, activeSkill in ipairs(build.calcsTab.calcsEnv.player.activeSkillList) do + if activeSkill.activeEffect.gemData and activeSkill.activeEffect.gemData.name == gemName then + count = count + 1 + assert.are.equals(expectedLevel, activeSkill.skillModList:Sum("BASE", activeSkill.skillCfg, "GemSupportLevel")) + end + end + assert.are.equals(expectedCount, count) + end + + it("evaluates GemTag mod tags against active skill gem tags", function() + local modDB = build.calcsTab.mainEnv.modDB + + modDB:NewMod("Damage", "INC", 10, "Test Fire GemTag", { type = "GemTag", gemTag = "Fire" }) + modDB:NewMod("Damage", "INC", 20, "Test Elemental GemTagList", { type = "GemTag", gemTagList = { "Cold", "Lightning" } }) + modDB:NewMod("Damage", "INC", 40, "Test Not Minion GemTag", { type = "GemTag", gemTag = "Minion", neg = true }) + + assert.are.equals(50, modDB:Sum("INC", { skillGem = { tags = { fire = true } } }, "Damage")) + assert.are.equals(60, modDB:Sum("INC", { skillGem = { tags = { cold = true } } }, "Damage")) + assert.are.equals(0, modDB:Sum("INC", { skillGem = { tags = { minion = true } } }, "Damage")) + end) + + it("applies Fire Mastery level to Apocalypse through the source gem tag", function() + build.skillsTab:PasteSocketGroup("Apocalypse 20/0 1\nFire Mastery 1/0 1") + runCallback("OnFrame") + assertGemSupportLevel("Apocalypse", 1, 4) + end) + + it("evaluates conditional gem levels using the source gem support count", function() + build.skillsTab:PasteSocketGroup("Apocalypse 20/0 1\nFire Mastery 1/0 1\nUhtred's Omen 1/0 1") + runCallback("OnFrame") + assertGemSupportLevel("Apocalypse", 3, 4) + end) + it("uses granted effect minion list when active skill minion list is missing", function() local srcInstance = { statSet = { }, skillPart = { }, nameSpec = "Spectre: Test" } diff --git a/src/Classes/CalcBreakdownControl.lua b/src/Classes/CalcBreakdownControl.lua index c78f279adc..c9bac2b4a6 100644 --- a/src/Classes/CalcBreakdownControl.lua +++ b/src/Classes/CalcBreakdownControl.lua @@ -481,6 +481,8 @@ function CalcBreakdownClass:AddModSection(sectionData, modList) else desc = "Skill type: "..(tag.neg and "Not " or "")..self:FormatModName(SkillTypeName[tag.skillType]) end + elseif tag.type == "GemTag" then + desc = "Gem tag: "..(tag.neg and "Not " or "")..self:FormatVarNameOrList(tag.gemTag, tag.gemTagList) elseif tag.type == "BaseFlag" then desc = "Base flag: "..(tag.neg and "Not " or "")..self:FormatModName(tostring(tag.baseFlag)) elseif tag.type == "SlotNumber" then diff --git a/src/Classes/ModStore.lua b/src/Classes/ModStore.lua index b1e496349e..d02a5ecc69 100644 --- a/src/Classes/ModStore.lua +++ b/src/Classes/ModStore.lua @@ -827,6 +827,25 @@ function ModStoreClass:EvalMod(mod, cfg, globalLimits) if not match then return end + elseif tag.type == "GemTag" then + local match = false + local gemTags = cfg and cfg.skillGem and cfg.skillGem.tags + if tag.gemTagList then + for _, gemTag in pairs(tag.gemTagList) do + if gemTags and gemTags[gemTag:lower()] then + match = true + break + end + end + else + match = gemTags and gemTags[tag.gemTag:lower()] + end + if tag.neg then + match = not match + end + if not match then + return + end elseif tag.type == "BaseFlag" then local match = false if cfg and cfg.skillGem and cfg.skillGem.grantedEffect and cfg.skillGem.grantedEffect.statSets and cfg.skillGem.grantedEffect.statSets[1] then diff --git a/src/Data/SkillStatMap.lua b/src/Data/SkillStatMap.lua index f25bb4f0f7..35e74316e1 100644 --- a/src/Data/SkillStatMap.lua +++ b/src/Data/SkillStatMap.lua @@ -3017,23 +3017,23 @@ return { -- --Fire ["supported_fire_skill_gem_level_+"] = { - mod("SupportedGemProperty", "LIST", { keyword = "grants_active_skill", key = "level", value = nil }, 0, 0, { type = "SkillType", skillType = SkillType.Fire }), + mod("SupportedGemProperty", "LIST", { keyword = "grants_active_skill", key = "level", value = nil }, 0, 0, { type = "GemTag", gemTag = "Fire" }), }, --Cold ["supported_cold_skill_gem_level_+"] = { - mod("SupportedGemProperty", "LIST", { keyword = "grants_active_skill", key = "level", value = nil }, 0, 0, { type = "SkillType", skillType = SkillType.Cold }), + mod("SupportedGemProperty", "LIST", { keyword = "grants_active_skill", key = "level", value = nil }, 0, 0, { type = "GemTag", gemTag = "Cold" }), }, --Lightning ["supported_lightning_skill_gem_level_+"] = { - mod("SupportedGemProperty", "LIST", { keyword = "grants_active_skill", key = "level", value = nil }, 0, 0, { type = "SkillType", skillType = SkillType.Lightning }), + mod("SupportedGemProperty", "LIST", { keyword = "grants_active_skill", key = "level", value = nil }, 0, 0, { type = "GemTag", gemTag = "Lightning" }), }, --Chaos ["supported_chaos_skill_gem_level_+"] = { - mod("SupportedGemProperty", "LIST", { keyword = "grants_active_skill", key = "level", value = nil }, 0, 0, { type = "SkillType", skillType = SkillType.Chaos }), + mod("SupportedGemProperty", "LIST", { keyword = "grants_active_skill", key = "level", value = nil }, 0, 0, { type = "GemTag", gemTag = "Chaos" }), }, --Physical ["supported_physical_skill_gem_level_+"] = { - mod("SupportedGemProperty", "LIST", { keyword = "grants_active_skill", key = "level", value = nil }, 0, 0, { type = "SkillType", skillType = SkillType.Physical }), + mod("SupportedGemProperty", "LIST", { keyword = "grants_active_skill", key = "level", value = nil }, 0, 0, { type = "GemTag", gemTag = "Physical" }), }, --Active ["supported_active_skill_gem_level_+"] = { @@ -3044,23 +3044,23 @@ return { }, --Aura ["supported_aura_skill_gem_level_+"] = { - mod("SupportedGemProperty", "LIST", { keyword = "grants_active_skill", key = "level", value = nil }, 0, 0, { type = "SkillType", skillType = SkillType.Aura }), + mod("SupportedGemProperty", "LIST", { keyword = "grants_active_skill", key = "level", value = nil }, 0, 0, { type = "GemTag", gemTag = "Aura" }), }, --Curse ["supported_curse_skill_gem_level_+"] = { - mod("SupportedGemProperty", "LIST", { keyword = "grants_active_skill", key = "level", value = nil }, 0, KeywordFlag.Curse), + mod("SupportedGemProperty", "LIST", { keyword = "grants_active_skill", key = "level", value = nil }, 0, 0, { type = "GemTag", gemTag = "Curse" }), }, --Strike ["supported_strike_skill_gem_level_+"] = { - mod("SupportedGemProperty", "LIST", { keyword = "grants_active_skill", key = "level", value = nil }, 0, 0, { type = "SkillType", skillType = SkillType.MeleeSingleTarget }), + mod("SupportedGemProperty", "LIST", { keyword = "grants_active_skill", key = "level", value = nil }, 0, 0, { type = "GemTag", gemTag = "Strike" }), }, --Elemental ["supported_elemental_skill_gem_level_+"] = { - mod("SupportedGemProperty", "LIST", { keyword = "grants_active_skill", key = "level", value = nil }, 0, OR64(KeywordFlag.Lightning, KeywordFlag.Cold, KeywordFlag.Fire)), + mod("SupportedGemProperty", "LIST", { keyword = "grants_active_skill", key = "level", value = nil }, 0, 0, { type = "GemTag", gemTagList = { "Lightning", "Cold", "Fire" } }), }, --Minion ["supported_minion_skill_gem_level_+"] = { - mod("SupportedGemProperty", "LIST", { keyword = "grants_active_skill", key = "level", value = nil }, 0, 0, { type = "SkillType", skillType = SkillType.Minion }), + mod("SupportedGemProperty", "LIST", { keyword = "grants_active_skill", key = "level", value = nil }, 0, 0, { type = "GemTag", gemTag = "Minion" }), }, -- Remnant stats ["remnant_effect_+%"] = { diff --git a/src/Modules/CalcActiveSkill.lua b/src/Modules/CalcActiveSkill.lua index 90af8a6c68..01e3ea95c9 100644 --- a/src/Modules/CalcActiveSkill.lua +++ b/src/Modules/CalcActiveSkill.lua @@ -230,6 +230,35 @@ function calcs.createActiveSkill(activeEffect, supportList, env, actor, socketGr return activeSkill end +local function getSourceGemPropertyInfo(env, activeSkill) + local activeEffect = activeSkill.activeEffect + local sourceGem = activeEffect.srcInstance + if not sourceGem or not activeEffect.gemData or activeEffect.gemData.tags.support then + return { } + end + + env.sourceGemPropertyInfo = env.sourceGemPropertyInfo or { } + if not env.sourceGemPropertyInfo[sourceGem] then + local modList = new("ModList", activeSkill.actor.modDB) + local supportCount = 0 + for _, supportEffect in ipairs(activeSkill.supportList) do + if supportEffect.isSupporting and supportEffect.isSupporting[sourceGem] then + calcs.mergeSkillInstanceMods(env, modList, supportEffect) + if not supportEffect.grantedEffect.hidden then + supportCount = supportCount + 1 + end + end + end + modList:NewMod("Multiplier:SupportCount", "BASE", supportCount, "Support Count") + env.sourceGemPropertyInfo[sourceGem] = modList:Tabulate("LIST", { + skillName = activeEffect.gemData.name, + skillGem = activeEffect.gemData, + slotName = activeSkill.slotName, + }, "SupportedGemProperty") + end + return env.sourceGemPropertyInfo[sourceGem] +end + function calcs.getActiveSkillDisplayName(activeSkill) local skillName = activeSkill.activeEffect.grantedEffect.name local skillMinion = activeSkill.minion @@ -741,11 +770,13 @@ function calcs.buildActiveSkillModList(env, activeSkill) 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") end - for _, supportProperty in ipairs(skillModList:Tabulate("LIST", activeSkill.skillCfg, "SupportedGemProperty")) do + for _, supportProperty in ipairs(getSourceGemPropertyInfo(env, activeSkill)) do local value = supportProperty.value - if value.keyword == "grants_active_skill" and activeSkill.activeEffect.gemData and not activeSkill.activeEffect.gemData.tags.support then + if value.keyword == "grants_active_skill" then activeEffect[value.key] = activeEffect[value.key] + value.value - skillModList:NewMod("GemSupport".. value.key:gsub("^%l", string.upper), "BASE", value.value, supportProperty.mod.source, #supportProperty.mod > 0 and supportProperty.mod[1] or nil) + local gemTag = supportProperty.mod[1] + gemTag = gemTag and gemTag.type == "GemTag" and gemTag or nil + skillModList:NewMod("GemSupport".. value.key:gsub("^%l", string.upper), "BASE", value.value, supportProperty.mod.source, gemTag) end end diff --git a/src/Modules/CalcSetup.lua b/src/Modules/CalcSetup.lua index be72984edd..4e74315dc4 100644 --- a/src/Modules/CalcSetup.lua +++ b/src/Modules/CalcSetup.lua @@ -395,6 +395,7 @@ function wipeEnv(env, accelerate) if not accelerate.skills then -- Player Active Skills generation wipeTable(env.player.activeSkillList) + env.sourceGemPropertyInfo = { } -- Enhances Active Skills with skill ModFlags, KeywordFlags -- and modifiers that affect skill scaling (e.g., global buffs/effects) @@ -1678,6 +1679,7 @@ function calcs.initEnv(build, mode, override, specEnv) level = value.level, quality = 0, enabled = true, + isSupporting = { }, }) end end