From a835d72ced3ec3bf87d1d64a77463522fcaf69c2 Mon Sep 17 00:00:00 2001 From: Mickael Cagnion Date: Sat, 7 Nov 2026 23:33:48 +0100 Subject: [PATCH] Add support for four Abyss timeless jewel node lines Spiteful Allies, The Loyal Few, From Below and Sanguine Bargain had lines that the parser left unsupported: - Minions Impale on Hit - Maximum number of raised Spectres, Skeletons and Zombies is 1 - Cannot recover Mana - Attack Skills cost Life instead of Mana Map each to the modifier already used by the closest existing wording, and update the four matching ModCache entries. --- spec/System/TestAbyssTimelessJewel_spec.lua | 37 +++++++++++++++++++++ src/Data/ModCache.lua | 8 ++--- src/Modules/ModParser.lua | 8 +++++ 3 files changed, 49 insertions(+), 4 deletions(-) diff --git a/spec/System/TestAbyssTimelessJewel_spec.lua b/spec/System/TestAbyssTimelessJewel_spec.lua index 1f64037c529..5a9a1b0de5a 100644 --- a/spec/System/TestAbyssTimelessJewel_spec.lua +++ b/spec/System/TestAbyssTimelessJewel_spec.lua @@ -475,6 +475,43 @@ describe("Abyss timeless jewels", function() assert.is_true(hasComparison) end) + -- Each node line is fed as a Config custom modifier with the node's exact text + describe("calculates Abyss node lines", function() + local function calculate(skill, customMods) + build.skillsTab:PasteSocketGroup(skill) + build.configTab.input.customMods = customMods + build.configTab:BuildModList() + runCallback("OnFrame") + return build.calcsTab.mainOutput + end + + it("Spiteful Allies makes minions Impale on Hit", function() + local output = calculate("Raise Zombie 20/0 1", "Minions Impale on Hit") + assert.are.equal(100, output.Minion.ImpaleChance) + end) + + it("The Loyal Few limits raised minions to one", function() + build.skillsTab:PasteSocketGroup("Summon Skeletons 20/0 1") + local output = calculate("Raise Zombie 20/0 1", "Maximum number of raised Spectres, Skeletons and Zombies is 1") + assert.are.same({ 1, 1 }, { output.ActiveSkeletonLimit, output.ActiveZombieLimit }) + -- Raise Spectre without a selected monster has no minion, so read the spectre limit from the modifier store + assert.are.equal(1, build.calcsTab.mainEnv.modDB:Override(nil, "ActiveSpectreLimit")) + end) + + it("From Below stops Mana regeneration", function() + local output = calculate("Fireball 20/0 1", "Cannot recover Mana") + assert.are.equal(0, output.ManaRegen) + end) + + it("Sanguine Bargain makes Attack Skills cost Life", function() + build.itemsTab:CreateDisplayItemFromRaw("New Item\nRusted Sword") + build.itemsTab:AddDisplayItem() + local output = calculate("Double Strike 20/0 1", "Attack Skills cost Life instead of Mana") + assert.are.equal(0, output.ManaCost) + assert.is_true(output.LifeCost > 0) + end) + end) + it("applies Zorath seed 4050 to the selected Chieftain notable", function() local spec = build.spec spec:SelectClass(spec.tree.classNameMap.Marauder) diff --git a/src/Data/ModCache.lua b/src/Data/ModCache.lua index c0ba5d23632..55f46ae3953 100755 --- a/src/Data/ModCache.lua +++ b/src/Data/ModCache.lua @@ -14096,7 +14096,7 @@ c["Attack Projectiles Return to you"]={nil,"Attack Projectiles Return to you "} c["Attack Projectiles always inflict Bleeding and Maim, and Knock Back Enemies"]={{[1]={flags=1025,keywordFlags=0,name="BleedChance",type="BASE",value=100},[2]={flags=1025,keywordFlags=0,name="EnemyKnockbackChance",type="BASE",value=100}},nil} c["Attack Skills Cost Life instead of 10% of Mana Cost"]={{[1]={flags=1,keywordFlags=0,name="HybridManaAndLifeCost_Life",type="BASE",value=10}},nil} c["Attack Skills Cost Life instead of 20% of Mana Cost"]={{[1]={flags=1,keywordFlags=0,name="HybridManaAndLifeCost_Life",type="BASE",value=20}},nil} -c["Attack Skills cost Life instead of Mana"]={nil,"Attack Skills cost Life instead of Mana "} +c["Attack Skills cost Life instead of Mana"]={{[1]={flags=1,keywordFlags=0,name="CostLifeInsteadOfMana",type="FLAG",value=true}},nil} c["Attack Skills deal 10% increased Damage while Dual Wielding"]={{[1]={[1]={type="Condition",var="DualWielding"},flags=0,keywordFlags=65536,name="Damage",type="INC",value=10}},nil} c["Attack Skills deal 10% increased Damage while holding a Shield"]={{[1]={[1]={type="Condition",varList={[1]="UsingShield"}},flags=0,keywordFlags=65536,name="Damage",type="INC",value=10}},nil} c["Attack Skills deal 10% increased Damage with Ailments while Dual Wielding"]={{[1]={[1]={type="Condition",var="DualWielding"},flags=2048,keywordFlags=65536,name="Damage",type="INC",value=10}},nil} @@ -14646,7 +14646,7 @@ c["Cannot inflict Ignite"]={{[1]={flags=0,keywordFlags=0,name="CannotIgnite",typ c["Cannot inflict Shock"]={{[1]={flags=0,keywordFlags=0,name="CannotShock",type="FLAG",value=true}},nil} c["Cannot lose Crab Barriers if you have lost Crab Barriers Recently"]={nil,"Cannot lose Crab Barriers if you have lost Crab Barriers Recently "} c["Cannot lose Crab Barriers if you have lost Crab Barriers Recently +3% Chance to Block Attack Damage while you have at least 5 Crab Barriers"]={nil,"Cannot lose Crab Barriers if you have lost Crab Barriers Recently +3% Chance to Block Attack Damage while you have at least 5 Crab Barriers "} -c["Cannot recover Mana"]={nil,"Cannot recover Mana "} +c["Cannot recover Mana"]={{[1]={flags=0,keywordFlags=0,name="CannotGainMana",type="FLAG",value=true}},nil} c["Cannot roll Caster Modifiers"]={{},nil} c["Cannot roll Modifiers of Non-Chaos Damage Types"]={{},nil} c["Cannot roll Modifiers of Non-Cold Damage Types"]={{},nil} @@ -17661,7 +17661,7 @@ c["Maximum number of Summoned Raging Spirits is Doubled"]={{[1]={[1]={globalLimi c["Maximum number of Summoned Reapers is Doubled"]={{[1]={[1]={globalLimit=100,globalLimitKey="ActiveReaperLimitDoubledLimit",type="Multiplier",var="ActiveReaperLimitDoubled"},flags=0,keywordFlags=0,name="ActiveReaperLimit",type="MORE",value=100},[2]={flags=0,keywordFlags=0,name="Multiplier:ActiveReaperLimitDoubled",type="OVERRIDE",value=1}},nil} c["Maximum number of Summoned Skeletons is Doubled"]={{[1]={[1]={globalLimit=100,globalLimitKey="ActiveSkeletonLimitDoubledLimit",type="Multiplier",var="ActiveSkeletonLimitDoubled"},flags=0,keywordFlags=0,name="ActiveSkeletonLimit",type="MORE",value=100},[2]={flags=0,keywordFlags=0,name="Multiplier:ActiveSkeletonLimitDoubled",type="OVERRIDE",value=1}},nil} c["Maximum number of Summoned Spectral Wolves is Doubled"]={{[1]={[1]={globalLimit=100,globalLimitKey="ActiveWolfLimitDoubledLimit",type="Multiplier",var="ActiveWolfLimitDoubled"},flags=0,keywordFlags=0,name="ActiveWolfLimit",type="MORE",value=100},[2]={flags=0,keywordFlags=0,name="Multiplier:ActiveWolfLimitDoubled",type="OVERRIDE",value=1}},nil} -c["Maximum number of raised Spectres, Skeletons and Zombies is 1"]={{[1]={[1]={includeTransfigured=true,skillName="Summon Skeletons",type="SkillName"},flags=0,keywordFlags=0,name="MinionModifier",type="LIST",value={mod={flags=0,keywordFlags=0,name="ActiveSpectreLimit",type="OVERRIDE",value=1}}}},", s and Zombies "} +c["Maximum number of raised Spectres, Skeletons and Zombies is 1"]={{[1]={flags=0,keywordFlags=0,name="ActiveSpectreLimit",type="OVERRIDE",value=1},[2]={flags=0,keywordFlags=0,name="ActiveSkeletonLimit",type="OVERRIDE",value=1},[3]={flags=0,keywordFlags=0,name="ActiveZombieLimit",type="OVERRIDE",value=1}},nil} c["Maximum total Energy Shield Recovery per second from Leech is doubled"]={{[1]={[1]={globalLimit=100,globalLimitKey="MaxEnergyShieldLeechRateDoubledLimit",type="Multiplier",var="MaxEnergyShieldLeechRateDoubled"},flags=0,keywordFlags=0,name="MaxEnergyShieldLeechRate",type="MORE",value=100},[2]={flags=0,keywordFlags=0,name="Multiplier:MaxEnergyShieldLeechRateDoubled",type="OVERRIDE",value=1}},nil} c["Melee Attacks Poison on Hit"]={{[1]={flags=256,keywordFlags=0,name="PoisonChance",type="BASE",value=100}},nil} c["Melee Attacks cause Bleeding"]={{[1]={flags=256,keywordFlags=0,name="BleedChance",type="BASE",value=100}},nil} @@ -17753,7 +17753,7 @@ c["Minion Spells Triggered by this Item have a 0.25 second Cooldown with 5 Uses c["Minions Convert 2% of their Maximum Life to Maximum Energy Shield per 1% Chaos Resistance they have"]={nil,"Convert 2% of their Maximum Life to Maximum Energy Shield per 1% Chaos Resistance they have "} c["Minions Explode when reduced to Low Life, dealing 33% of their Life as Fire Damage to surrounding Enemies"]={{[1]={flags=0,keywordFlags=0,name="ExtraMinionSkill",type="LIST",value={skillId="MinionInstability"}}},nil} c["Minions Hits have 50% chance to ignore Enemy Physical Damage Reduction"]={{[1]={flags=0,keywordFlags=0,name="MinionModifier",type="LIST",value={mod={flags=0,keywordFlags=0,name="ChanceToIgnoreEnemyPhysicalDamageReduction",type="BASE",value=50}}}},nil} -c["Minions Impale on Hit"]={nil,"Impale on Hit "} +c["Minions Impale on Hit"]={{[1]={flags=0,keywordFlags=0,name="MinionModifier",type="LIST",value={mod={flags=0,keywordFlags=0,name="ImpaleChance",type="BASE",value=100}}}},nil} c["Minions Leech 0.25% of Damage as Life"]={{[1]={flags=0,keywordFlags=0,name="MinionModifier",type="LIST",value={mod={flags=0,keywordFlags=0,name="DamageLifeLeech",type="BASE",value=0.25}}}},nil} c["Minions Leech 0.4% of Damage as Life"]={{[1]={flags=0,keywordFlags=0,name="MinionModifier",type="LIST",value={mod={flags=0,keywordFlags=0,name="DamageLifeLeech",type="BASE",value=0.4}}}},nil} c["Minions Leech 0.45% of Damage as Life"]={{[1]={flags=0,keywordFlags=0,name="MinionModifier",type="LIST",value={mod={flags=0,keywordFlags=0,name="DamageLifeLeech",type="BASE",value=0.45}}}},nil} diff --git a/src/Modules/ModParser.lua b/src/Modules/ModParser.lua index 9d5d1db3a05..27f7c865716 100644 --- a/src/Modules/ModParser.lua +++ b/src/Modules/ModParser.lua @@ -4476,6 +4476,11 @@ local specialModList = { ["raised zombies take ([%d%.]+)%% of their maximum life per second as fire damage"] = function(num) return { mod("MinionModifier", "LIST", { mod = mod("FireDegen", "BASE", 1, { type = "PercentStat", stat = "Life", percent = num }) }, { type = "SkillName", skillName = "Raise Zombie", includeTransfigured = true }) } end, ["maximum number of summoned raging spirits is (%d+)"] = function(num) return { mod("ActiveRagingSpiritLimit", "OVERRIDE", num) } end, ["maximum number of summoned phantasms is (%d+)"] = function(num) return { mod("ActivePhantasmLimit", "OVERRIDE", num) } end, + ["maximum number of raised spectres, skeletons and zombies is (%d+)"] = function(num) return { + mod("ActiveSpectreLimit", "OVERRIDE", num), + mod("ActiveSkeletonLimit", "OVERRIDE", num), + mod("ActiveZombieLimit", "OVERRIDE", num), + } end, ["summoned raging spirits have diamond shrine and massive shrine buffs"] = { mod("MinionModifier", "LIST", { mod = flag("Condition:DiamondShrine") }, { type = "SkillName", skillName = "Summon Raging Spirit", includeTransfigured = true }), mod("MinionModifier", "LIST", { mod = flag("Condition:MassiveShrine") }, { type = "SkillName", skillName = "Summon Raging Spirit", includeTransfigured = true }), @@ -4512,6 +4517,7 @@ local specialModList = { end end, ["minions have a (%d+)%% chance to impale on hit with attacks"] = function(num) return { mod("MinionModifier", "LIST", { mod = mod("ImpaleChance", "BASE", num ) }) } end, + ["minions impale on hit"] = { mod("MinionModifier", "LIST", { mod = mod("ImpaleChance", "BASE", 100) }) }, ["minions have (%d+)%% chance to impale on attack hit per socketed ghastly eye jewel"] = function(num) return { mod("MinionModifier", "LIST", { mod = mod("ImpaleChance", "BASE", num ) }, { type = "Multiplier", var = "GhastlyEyeJewelIn{SlotName}" }) } end, ["minions from herald skills deal (%d+)%% more damage"] = function(num) return { mod("MinionModifier", "LIST", { mod = mod("Damage", "MORE", num) }, { type = "SkillType", skillType = SkillType.Herald }) } end, ["minions have (%d+)%% increased movement speed for each herald affecting you"] = function(num) return { mod("MinionModifier", "LIST", { mod = mod("MovementSpeed", "INC", num, { type = "Multiplier", var = "Herald", actor = "parent" }) }) } end, @@ -4797,6 +4803,7 @@ local specialModList = { ["cannot gain energy shield"] = { flag("CannotGainEnergyShield") }, ["cannot gain life"] = { flag("CannotGainLife") }, ["cannot gain mana"] = { flag("CannotGainMana") }, + ["cannot recover mana"] = { flag("CannotGainMana") }, ["cannot recover life other than from leech"] = { flag("CannotRecoverLifeOutsideLeech"), flag("NoLifeRegen"), @@ -5320,6 +5327,7 @@ local specialModList = { ["spell skills deal no damage"] = { flag("DealNoDamage", { type = "SkillType", skillType = SkillType.Spell }) }, ["attacks have blood magic"] = { flag("CostLifeInsteadOfMana", nil, ModFlag.Attack) }, ["attacks cost life instead of mana"] = { flag("CostLifeInsteadOfMana", nil, ModFlag.Attack) }, + ["attack skills cost life instead of mana"] = { flag("CostLifeInsteadOfMana", nil, ModFlag.Attack) }, ["attack skills cost life instead of (%d+)%% of mana cost"] = function(num) return { mod("HybridManaAndLifeCost_Life", "BASE", num, nil, ModFlag.Attack) } end,