From a971266b26d86429970d0677ff02a7e07a4c02cc Mon Sep 17 00:00:00 2001 From: Eucelia Date: Wed, 3 Jun 2026 00:51:47 -0700 Subject: [PATCH 1/2] Add support for Way of the Mountain Martial Artist Ascendency node --- src/Classes/CompareTab.lua | 2 +- src/Classes/ConfigTab.lua | 8 ++++++++ src/Data/ModCache.lua | 5 +---- src/Modules/ConfigOptions.lua | 3 +++ src/Modules/ModParser.lua | 9 ++++++++- 5 files changed, 21 insertions(+), 6 deletions(-) diff --git a/src/Classes/CompareTab.lua b/src/Classes/CompareTab.lua index f10b9f6790..82813ec7c0 100644 --- a/src/Classes/CompareTab.lua +++ b/src/Classes/CompareTab.lua @@ -1270,7 +1270,7 @@ function CompareTabClass:RebuildConfigControls(compareEntry) end local hasAnyCondition = varData.ifCond or varData.ifOption or varData.ifSkill or varData.ifSkillFlag or varData.ifSkillData or varData.ifSkillList - or varData.ifNode or varData.ifMod or varData.ifMult + or varData.ifNode or varData.ifAscendClass or varData.ifMod or varData.ifMult or varData.ifEnemyStat or varData.ifEnemyCond or varData.legacy local ctrlInfo = { diff --git a/src/Classes/ConfigTab.lua b/src/Classes/ConfigTab.lua index e3cc9ec6cf..14e9a76141 100644 --- a/src/Classes/ConfigTab.lua +++ b/src/Classes/ConfigTab.lua @@ -255,6 +255,14 @@ local ConfigTabClass = newClass("ConfigTab", "UndoHandler", "ControlHost", "Cont return "This option is specific to '"..self.build.spec.nodes[ifOption].dn.."'." end)) end + if varData.ifAscendClass then + t_insert(shownFuncs, listOrSingleIfOption(varData.ifAscendClass, function(ifOption) + return self.build.spec.curAscendClassBaseName == ifOption + end)) + t_insert(tooltipFuncs, listOrSingleIfTooltip(varData.ifAscendClass, function(ifOption) + return "This option is specific to the "..ifOption.." Ascendancy Class." + end)) + end if varData.ifOption then t_insert(shownFuncs, listOrSingleIfOption(varData.ifOption, function(ifOption) return self.configSets[self.activeConfigSetId].input[ifOption] diff --git a/src/Data/ModCache.lua b/src/Data/ModCache.lua index 524bfaca78..9ffa5fcce2 100644 --- a/src/Data/ModCache.lua +++ b/src/Data/ModCache.lua @@ -1254,7 +1254,7 @@ c["10% reduced maximum Mana"]={{[1]={flags=0,keywordFlags=0,name="Mana",type="IN c["100 Passive Skill Points become Weapon Set Skill Points"]={{[1]={flags=0,keywordFlags=0,name="PassivePointsToWeaponSetPoints",type="BASE",value=100}},nil} c["100% Surpassing chance per enemy Power to gain"]={{},"% Surpassing chance per enemy Power to gain "} c["100% Surpassing chance per enemy Power to gain Mountain's Teachings on Immobilising an enemy if"]={{},"% Surpassing chance per enemy Power to gain Mountain's Teachings on Immobilising an enemy if "} -c["100% Surpassing chance per enemy Power to gain Mountain's Teachings on Immobilising an enemy if you have the Way of the Mountain Ascendancy Passive Skill"]={{},"% Surpassing chance per enemy Power to gain Mountain's Teachings on Immobilising an enemy if you have the Way of the Mountain Ascendancy Passive Skill "} +c["100% Surpassing chance per enemy Power to gain Mountain's Teachings on Immobilising an enemy if you have the Way of the Mountain Ascendancy Passive Skill"]={{[1]={[1]={type="Condition",var="MountainsTeachings"},[2]={neg=true,skillType=37,type="SkillType"},[3]={neg=true,skillType=6,type="SkillType"},flags=1,keywordFlags=0,name="Damage",source="Mountain's Teachings",type="MORE",value=15},[2]={[1]={type="Condition",var="MountainsTeachings"},flags=0,keywordFlags=0,name="StunThreshold",source="Mountain's Teachings",type="MORE",value=50}},nil} c["100% chance to Daze Enemies whose Hits you Block with a raised Shield"]={{[1]={flags=0,keywordFlags=0,name="DazeChance",type="BASE",value=100}}," Enemies whose Hits you Block with a raised Shield "} c["100% chance to Pierce an Enemy"]={{[1]={flags=0,keywordFlags=0,name="PierceChance",type="BASE",value=100}},nil} c["100% faster start of Energy Shield Recharge"]={{[1]={flags=0,keywordFlags=0,name="EnergyShieldRechargeFaster",type="INC",value=100}},nil} @@ -5939,8 +5939,6 @@ c["Modifiers to Fire Resistance also grant Cold and Lightning Resistance at 50% c["Modifiers to Maximum Block Chance instead apply to Maximum Resistances"]={{[1]={flags=0,keywordFlags=0,name="MaxBlockChanceModsApplyMaxResist",type="FLAG",value=true}},nil} c["Modifiers to Maximum Fire Resistance also grant Maximum Cold and Lightning Resistance"]={{[1]={flags=0,keywordFlags=0,name="FireMaxResConvertToCold",type="BASE",value=100},[2]={flags=0,keywordFlags=0,name="FireMaxResConvertToLightning",type="BASE",value=100}},nil} c["Modifiers to Stun Buildup apply to Freeze Buildup instead for Parry"]={{[1]={[1]={includeTransfigured=true,skillName="Parry",type="SkillName"},flags=0,keywordFlags=0,name="FreezeBuildupInsteadOfStunBuildup",type="FLAG",value=true},[2]={[1]={includeTransfigured=true,skillName="Parry",type="SkillName"},flags=0,keywordFlags=0,name="CannotStun",type="FLAG",value=true},[3]={[1]={includeTransfigured=true,skillName="Parry",type="SkillName"},flags=0,keywordFlags=0,name="CannotHeavyStun",type="FLAG",value=true}},nil} -c["Mountain's Teachings on Immobilising an enemy if"]={nil,"Mountain's Teachings on Immobilising an enemy if "} -c["Mountain's Teachings on Immobilising an enemy if you have the Way of the Mountain Ascendancy Passive Skill"]={nil,"Mountain's Teachings on Immobilising an enemy if you have the Way of the Mountain Ascendancy Passive Skill "} c["Moving while Bleeding doesn't cause you to take extra damage"]={nil,"Moving while Bleeding doesn't cause you to take extra damage "} c["Nearby Allies and Enemies Share Charges with you"]={nil,"Nearby Allies and Enemies Share Charges with you "} c["Nearby Allies and Enemies Share Charges with you Enemies Hitting you have 10% chance to gain an Endurance, "]={nil,"Nearby Allies and Enemies Share Charges with you Enemies Hitting you have 10% chance to gain an Endurance, "} @@ -6601,7 +6599,6 @@ c["until you take no Damage to Life for 5 seconds"]={nil,"until you take no Dama c["until you take no Damage to Life for 5 seconds Life that would be lost by taking Damage is instead Reserved"]={nil,"until you take no Damage to Life for 5 seconds Life that would be lost by taking Damage is instead Reserved "} c["you Shapeshift to an Animal form"]={nil,"you Shapeshift to an Animal form "} c["you Shapeshift to an Animal form Modifiers gained this way are lost after 30 seconds or when you next Shapeshift"]={nil,"you Shapeshift to an Animal form Modifiers gained this way are lost after 30 seconds or when you next Shapeshift "} -c["you have the Way of the Mountain Ascendancy Passive Skill"]={nil,"the Way of the Mountain Ascendancy Passive Skill "} c["your maximum Life as Physical damage per second"]={nil,"your maximum Life as Physical damage per second "} c["your maximum number of Power Charges"]={nil,"your maximum number of Power Charges "} c["your maximum number of Power Charges +1 to Maximum Power Charges"]={nil,"your maximum number of Power Charges +1 to Maximum Power Charges "} diff --git a/src/Modules/ConfigOptions.lua b/src/Modules/ConfigOptions.lua index c1b133a63c..86ab33fcd1 100644 --- a/src/Modules/ConfigOptions.lua +++ b/src/Modules/ConfigOptions.lua @@ -924,6 +924,9 @@ Huge sets the radius to 11. { var = "multiplierTailwind", type = "count", label = "# of Tailwind Stacks:", ifFlag = "Condition:CanHaveTailwind", tooltip = "Tailwind grants the following, up to a base of 10 stacks:\n\t1% increased movement speed\n\t3% increased Skill Speed\n\t15% increased Evasion Rating", apply = function(val, modList, enemyModList) modList:NewMod("Multiplier:Tailwind", "BASE", val, "Config", { type = "Condition", var = "Combat" }) end }, + { var = "mountainsTeachingsEnabled", type = "check", label = "Mountain's Teachings:", ifAscendClass = "Martial Artist", ifNode = 51546, defaultState = true, tooltip = "While active (requires Way of the Mountain):\n\tAttacks you use yourself and Attacks granted by the Martial Artist Ascendancy Class deal 15% more damage\n\tEnemy Hits you take that would deal damage less than or equal to 30% of your maximum Life (after Armour and Resistances, but before other modifiers to damage taken) deal 40% less damage (NOT REFLECTED IN POB)\n\tYou have 50% more Stun Threshold", apply = function(val, modList, enemyModList) + modList:NewMod("Condition:MountainsTeachings", "FLAG", true, "Config", { type = "Condition", var = "Combat" }) + end }, { var = "buffAdrenaline", type = "check", label = "Do you have Adrenaline?", tooltip = "This will enable the Adrenaline buff, which grants:\n\t100% increased Damage\n\t25% increased Attack, Cast and Movement Speed\n\t10% additional Physical Damage Reduction", apply = function(val, modList, enemyModList) modList:NewMod("Condition:Adrenaline", "FLAG", true, "Config", { type = "Condition", var = "Combat" }) end }, diff --git a/src/Modules/ModParser.lua b/src/Modules/ModParser.lua index 301db66c28..464cd31f65 100644 --- a/src/Modules/ModParser.lua +++ b/src/Modules/ModParser.lua @@ -6137,7 +6137,14 @@ local specialModList = { } end, ["you can socket an additional copy of each lineage support gem, in different skills"] = { mod("MaxLineageCount", "BASE", 1) }, ["you can socket (%d+) additional copies of each lineage support gem, in different skills"] = function(num) return { mod("MaxLineageCount", "BASE", num) } end, - ["can be modified while corrupted"] = {} + ["can be modified while corrupted"] = {}, + ["(%d+)%% surpassing chance per enemy power to gain mountain's teachings on immobilising an enemy if you have the way of the mountain ascendancy passive skill"] = function() + local mtTag = { type = "Condition", var = "MountainsTeachings" } + return { + mod("Damage", "MORE", 15, "Mountain's Teachings", ModFlag.Attack, mtTag, { type = "SkillType", skillType = SkillType.Triggered, neg = true }, { type = "SkillType", skillType = SkillType.Minion, neg = true }), + mod("StunThreshold", "MORE", 50, "Mountain's Teachings", mtTag), + } + end, } for _, name in pairs(data.keystones) do specialModList[name:lower()] = { mod("Keystone", "LIST", name) } From 21368d54b7f2e277478b2647e39346b557b2e5bb Mon Sep 17 00:00:00 2001 From: Eucelia Date: Thu, 4 Jun 2026 14:27:59 -0700 Subject: [PATCH 2/2] add a test --- spec/System/TestWayOfTheMountain_spec.lua | 119 ++++++++++++++++++++++ 1 file changed, 119 insertions(+) create mode 100644 spec/System/TestWayOfTheMountain_spec.lua diff --git a/spec/System/TestWayOfTheMountain_spec.lua b/spec/System/TestWayOfTheMountain_spec.lua new file mode 100644 index 0000000000..c5c952eb0b --- /dev/null +++ b/spec/System/TestWayOfTheMountain_spec.lua @@ -0,0 +1,119 @@ +describe("Way of the Mountain", function() + local WAY_OF_THE_MOUNTAIN_NODE_ID = 51546 + local FULL_MOD_LINE = "100% Surpassing chance per enemy Power to gain Mountain's Teachings on Immobilising an enemy if you have the Way of the Mountain Ascendancy Passive Skill" + + local function expectedMountainsTeachingsMods() + local mtTag = { type = "Condition", var = "MountainsTeachings" } + return { + damage = modLib.createMod("Damage", "MORE", 15, "Mountain's Teachings", ModFlag.Attack, mtTag, + { type = "SkillType", skillType = SkillType.Triggered, neg = true }, + { type = "SkillType", skillType = SkillType.Minion, neg = true }), + stun = modLib.createMod("StunThreshold", "MORE", 50, "Mountain's Teachings", mtTag), + } + end + + local function allocateNode(nodeId) + local spec = build.spec + local node = spec.nodes[nodeId] + node.alloc = true + spec.allocNodes[node.id] = node + end + + local function setupMonkMartialArtist(allocWayOfMountain) + local ascendInfo = build.spec.tree.ascendNameMap["Martial Artist"] + build.spec:SelectClass(ascendInfo.classId) + build.spec:SelectAscendClass(ascendInfo.ascendClassId) + if allocWayOfMountain then + allocateNode(WAY_OF_THE_MOUNTAIN_NODE_ID) + end + build.buildFlag = true + runCallback("OnFrame") + end + + local function findParsedMod(parsed, name) + for _, mod in ipairs(parsed) do + if mod.name == name then + return mod + end + end + end + + before_each(function() + newBuild() + end) + + describe("ModParser", function() + it("parses the Way of the Mountain passive line into Mountain's Teachings modifiers", function() + local expected = expectedMountainsTeachingsMods() + local parsed, unrecognized = modLib.parseMod(FULL_MOD_LINE) + assert.is_nil(unrecognized) + assert.are.equals(2, #parsed) + assert.True(modLib.compareModParams(findParsedMod(parsed, "Damage"), expected.damage)) + assert.True(modLib.compareModParams(findParsedMod(parsed, "StunThreshold"), expected.stun)) + end) + + it("matches the cached ModCache entry", function() + local expected = expectedMountainsTeachingsMods() + local cached = modLib.parseModCache[FULL_MOD_LINE] + assert.is_not_nil(cached) + assert.are.equals(2, #cached[1]) + assert.True(modLib.compareModParams(cached[1][1], expected.damage)) + assert.True(modLib.compareModParams(cached[1][2], expected.stun)) + end) + end) + + describe("Mountain's Teachings config", function() + it("is hidden without Martial Artist or Way of the Mountain", function() + runCallback("OnFrame") + local control = build.configTab.varControls.mountainsTeachingsEnabled + assert.True(control ~= nil) + assert.False(control:shown()) + + setupMonkMartialArtist(false) + assert.False(control:shown()) + end) + + it("is shown for Martial Artist with Way of the Mountain allocated", function() + setupMonkMartialArtist(true) + assert.are.equals("Martial Artist", build.spec.curAscendClassBaseName) + assert.is_not_nil(build.spec.allocNodes[WAY_OF_THE_MOUNTAIN_NODE_ID]) + local control = build.configTab.varControls.mountainsTeachingsEnabled + assert.True(control:shown()) + assert.is_true(build.configTab.input.mountainsTeachingsEnabled) + end) + end) + + describe("Mountain's Teachings bonuses", function() + local function setupAttackLoadout(mountainsTeachingsEnabled) + setupMonkMartialArtist(true) + build.configTab.input.mountainsTeachingsEnabled = mountainsTeachingsEnabled + build.configTab:BuildModList() + build.itemsTab:CreateDisplayItemFromRaw([[ + New Item + Razor Quarterstaff + Quality: 0 + ]]) + build.itemsTab:AddDisplayItem() + build.skillsTab:PasteSocketGroup("Quarterstaff Strike 1/0 1") + runCallback("OnFrame") + build.calcsTab:BuildOutput() + runCallback("OnFrame") + end + + it("grants 15% more attack damage and 50% more stun threshold while active", function() + setupAttackLoadout(true) + local skill = build.calcsTab.mainEnv.player.activeSkillList[1] + local attackDamageMore = skill.skillModList:More(skill.skillCfg, "Damage") + assert.are.equals(1.15, attackDamageMore) + assert.are.equals(1.5, build.calcsTab.mainEnv.player.modDB:More(nil, "StunThreshold")) + end) + + it("does not grant bonuses when Mountain's Teachings is disabled", function() + setupAttackLoadout(false) + local skill = build.calcsTab.mainEnv.player.activeSkillList[1] + local attackDamageMore = skill.skillModList:More(skill.skillCfg, "Damage") + assert.are.equals(1, attackDamageMore) + assert.are.equals(1, build.calcsTab.mainEnv.player.modDB:More(nil, "StunThreshold")) + end) + end) +end)