From d855e01cec13e7e75be84a38046012f7a63bf52c Mon Sep 17 00:00:00 2001 From: Leonel Togniolli Date: Tue, 9 Jun 2026 11:42:09 -0300 Subject: [PATCH 1/2] @ Implement Idolatry (Spirit Walker) ascendancy mods Adds support for the three Idolatry notable mods: - Companions deal 10% increased damage per Idol in your Equipment - 2% increased Reservation Efficiency of Skills per Idol in your Equipment - -4% to all Elemental Resistances per non-Idol Augment in your Equipment CalcSetup now tallies socketed augments by type into IdolsInEquipment and NonIdolAugmentsInEquipment multipliers (only counting known augments, never empty sockets). ModParser gains the two "per idol/non-idol augment in your equipment" multiplier suffixes plus a companion-damage special case that reads the player count via actor = "parent". ModCache regenerated accordingly. @ --- spec/System/TestIdolatry_spec.lua | 102 ++++++++++++++++++++++++++++++ src/Data/ModCache.lua | 9 +-- src/Modules/CalcSetup.lua | 21 +++++- src/Modules/ModParser.lua | 2 + 4 files changed, 126 insertions(+), 8 deletions(-) create mode 100644 spec/System/TestIdolatry_spec.lua diff --git a/spec/System/TestIdolatry_spec.lua b/spec/System/TestIdolatry_spec.lua new file mode 100644 index 000000000..3eb4f62ee --- /dev/null +++ b/spec/System/TestIdolatry_spec.lua @@ -0,0 +1,102 @@ +describe("TestIdolatry", function() + before_each(function() + newBuild() + end) + + -- The Spirit Walker "Idolatry" notable grants three mods that scale with the + -- number of Idols / non-Idol augments (Runes + Soul Cores) socketed across equipped items. + + -- Counting: CalcSetup tallies socketed augments by type into the IdolsInEquipment and + -- NonIdolAugmentsInEquipment multipliers, which the three Idolatry mods scale against. + it("counts Idols and non-Idol augments across equipped items", function() + -- Gloves with 2 Idols socketed + build.itemsTab:CreateDisplayItemFromRaw([[ + Rarity: MAGIC + Idolatry Test Gloves + Vaal Gloves + Sockets: S S + Rune: Idol of Sirrius + Rune: Idol of Sirrius + Implicits: 0 + ]]) + build.itemsTab:AddDisplayItem() + + -- Quarterstaff with 3 Soul Cores socketed (non-Idol augments) + build.itemsTab:CreateDisplayItemFromRaw([[ + Rarity: MAGIC + Idolatry Test Staff + Aegis Quarterstaff + Sockets: S S S + Rune: Soul Core of Cholotl + Rune: Soul Core of Zantipi + Rune: Soul Core of Atmohua + Implicits: 0 + ]]) + build.itemsTab:AddDisplayItem() + runCallback("OnFrame") + + local modDB = build.calcsTab.mainEnv.modDB + assert.are.equals(2, modDB.multipliers.IdolsInEquipment) + assert.are.equals(3, modDB.multipliers.NonIdolAugmentsInEquipment) + end) + + -- Empty sockets (itemSocketCount populated while item.runes has no entry for the slot, e.g. a + -- freshly created base item) must not be counted as augments. + it("does not count empty sockets as augments", function() + build.itemsTab:CreateDisplayItemFromRaw([[ + Rarity: MAGIC + Empty Socket Test Gloves + Vaal Gloves + Sockets: S S + Implicits: 0 + ]]) + build.itemsTab:AddDisplayItem() + runCallback("OnFrame") + + local modDB = build.calcsTab.mainEnv.modDB + assert.is_nil(modDB.multipliers.IdolsInEquipment) + assert.is_nil(modDB.multipliers.NonIdolAugmentsInEquipment) + end) + + -- Parsing: the three stat lines must resolve to mods that scale against those multipliers. + it("parses the three Idolatry stat lines", function() + local parseMod = LoadModule("Modules/ModParser") + + -- Helper to find the Multiplier tag on a mod (tags are stored as array entries) + local function multiplierTag(mod) + for _, tag in ipairs(mod) do + if tag.type == "Multiplier" then return tag end + end + end + + -- 1) Companion damage scales by the player's Idol count (read via actor = "player" + -- since the mod is evaluated in the companion's own modDB). + local companion = parseMod("Companions deal 10% increased damage per Idol in your Equipment") + assert.are.equals(1, #companion) + assert.are.equals("MinionModifier", companion[1].name) + local inner = companion[1].value.mod + assert.are.equals("Damage", inner.name) + assert.are.equals("INC", inner.type) + assert.are.equals(10, inner.value) + local companionTag = multiplierTag(inner) + assert.is_not_nil(companionTag) + assert.are.equals("IdolsInEquipment", companionTag.var) + assert.are.equals("player", companionTag.actor) + + -- 2) Reservation Efficiency scales by the Idol count (player context). + local reservation = parseMod("2% increased Reservation Efficiency of Skills per Idol in your Equipment") + assert.are.equals(1, #reservation) + assert.are.equals("ReservationEfficiency", reservation[1].name) + assert.are.equals("INC", reservation[1].type) + assert.are.equals(2, reservation[1].value) + assert.are.equals("IdolsInEquipment", multiplierTag(reservation[1]).var) + + -- 3) Elemental Resistance penalty scales by the non-Idol augment count (player context). + local resist = parseMod("-4% to all Elemental Resistances per non-Idol Augment in your Equipment") + assert.are.equals(1, #resist) + assert.are.equals("ElementalResist", resist[1].name) + assert.are.equals("BASE", resist[1].type) + assert.are.equals(-4, resist[1].value) + assert.are.equals("NonIdolAugmentsInEquipment", multiplierTag(resist[1]).var) + end) +end) diff --git a/src/Data/ModCache.lua b/src/Data/ModCache.lua index 43d71c776..ad66ddcda 100644 --- a/src/Data/ModCache.lua +++ b/src/Data/ModCache.lua @@ -945,7 +945,7 @@ c["-30% to Lightning Resistance"]={{[1]={flags=0,keywordFlags=0,name="LightningR c["-30% to all Elemental Resistances"]={{[1]={flags=0,keywordFlags=0,name="ElementalResist",type="BASE",value=-30}},nil} c["-35% to Lightning Resistance"]={{[1]={flags=0,keywordFlags=0,name="LightningResist",type="BASE",value=-35}},nil} c["-4 Physical Damage taken from Attack Hits"]={{[1]={flags=0,keywordFlags=0,name="PhysicalDamageTakenFromAttacks",type="BASE",value=-4}},nil} -c["-4% to all Elemental Resistances per non-Idol Augment in your Equipment"]={{[1]={flags=0,keywordFlags=0,name="ElementalResist",type="BASE",value=-4}}," per non-Idol Augment in your Equipment "} +c["-4% to all Elemental Resistances per non-Idol Augment in your Equipment"]={{[1]={[1]={actor="player",type="Multiplier",var="NonIdolAugmentsInEquipment"},flags=0,keywordFlags=0,name="ElementalResist",type="BASE",value=-4}},nil} c["-5% to all Elemental Resistances"]={{[1]={flags=0,keywordFlags=0,name="ElementalResist",type="BASE",value=-5}},nil} c["-5% to amount of Damage Prevented by Deflection"]={{[1]={flags=0,keywordFlags=0,name="DeflectEffect",type="BASE",value=-5}},nil} c["-6% to amount of Damage Prevented by Deflection"]={{[1]={flags=0,keywordFlags=0,name="DeflectEffect",type="BASE",value=-6}},nil} @@ -1819,8 +1819,7 @@ c["2% increased Maximum Life per socketed Grand Spectrum"]={{[1]={[1]={type="Mul c["2% increased Movement Speed"]={{[1]={flags=0,keywordFlags=0,name="MovementSpeed",type="INC",value=2}},nil} c["2% increased Movement Speed while Sprinting"]={{[1]={[1]={type="Condition",var="Sprinting"},flags=0,keywordFlags=0,name="MovementSpeed",type="INC",value=2}},nil} c["2% increased Parried Debuff Duration per 10 Tribute"]={{[1]={[1]={actor="parent",div=10,stat="Tribute",type="PerStat"},flags=0,keywordFlags=0,name="ParryDebuffDuration",type="INC",value=2}},nil} -c["2% increased Reservation Efficiency of Skills per Idol in your Equipment"]={{[1]={flags=0,keywordFlags=0,name="ReservationEfficiency",type="INC",value=2}}," per Idol in your Equipment "} -c["2% increased Reservation Efficiency of Skills per Idol in your Equipment -4% to all Elemental Resistances per non-Idol Augment in your Equipment"]={{[1]={flags=0,keywordFlags=0,name="ReservationEfficiency",type="INC",value=2}}," per Idol in your Equipment -4% to all Elemental Resistances per non-Idol Augment in your Equipment "} +c["2% increased Reservation Efficiency of Skills per Idol in your Equipment"]={{[1]={[1]={actor="player",type="Multiplier",var="IdolsInEquipment"},flags=0,keywordFlags=0,name="ReservationEfficiency",type="INC",value=2}},nil} c["2% increased Skill Speed"]={{[1]={flags=0,keywordFlags=0,name="Speed",type="INC",value=2},[2]={flags=0,keywordFlags=0,name="WarcrySpeed",type="INC",value=2},[3]={flags=0,keywordFlags=0,name="TotemPlacementSpeed",type="INC",value=2}},nil} c["2% increased Skill Speed with Channelling Skills"]={{[1]={[1]={skillType=48,type="SkillType"},flags=0,keywordFlags=0,name="Speed",type="INC",value=2},[2]={[1]={skillType=48,type="SkillType"},flags=0,keywordFlags=0,name="WarcrySpeed",type="INC",value=2},[3]={[1]={skillType=48,type="SkillType"},flags=0,keywordFlags=0,name="TotemPlacementSpeed",type="INC",value=2}},nil} c["2% increased Spell Damage per 10 Intelligence"]={{[1]={[1]={div=10,stat="Int",type="PerStat"},flags=2,keywordFlags=0,name="Damage",type="INC",value=2}},nil} @@ -4836,9 +4835,7 @@ c["Charms use no Charges"]={{[1]={flags=0,keywordFlags=0,name="CharmsUseNoCharge c["Cold Damage from Hits Contributes to Flammability and Ignite Magnitudes instead of Chill Magnitude or Freeze Buildup"]={{[1]={flags=0,keywordFlags=0,name="ColdCanIgnite",type="FLAG",value=true},[2]={flags=0,keywordFlags=0,name="ColdCannotChill",type="FLAG",value=true},[3]={flags=0,keywordFlags=0,name="ColdCannotFreeze",type="FLAG",value=true}},nil} c["Cold Resistance is unaffected by Area Penalties"]={nil,"Cold Resistance is unaffected by Area Penalties "} c["Companions deal 10% increased Damage"]={{[1]={[1]={skillType=219,type="SkillType"},flags=0,keywordFlags=0,name="MinionModifier",type="LIST",value={mod={flags=0,keywordFlags=0,name="Damage",type="INC",value=10}}}},nil} -c["Companions deal 10% increased damage per Idol in your Equipment"]={{[1]={[1]={skillType=219,type="SkillType"},flags=0,keywordFlags=0,name="MinionModifier",type="LIST",value={mod={flags=0,keywordFlags=0,name="Damage",type="INC",value=10}}}}," per Idol in your Equipment "} -c["Companions deal 10% increased damage per Idol in your Equipment 2% increased Reservation Efficiency of Skills per Idol in your Equipment"]={{[1]={[1]={skillType=219,type="SkillType"},flags=0,keywordFlags=0,name="MinionModifier",type="LIST",value={mod={flags=0,keywordFlags=0,name="Damage",type="INC",value=10}}}}," per Idol in your Equipment 2% increased Reservation Efficiency of Skills per Idol in your Equipment "} -c["Companions deal 10% increased damage per Idol in your Equipment 2% increased Reservation Efficiency of Skills per Idol in your Equipment -4% to all Elemental Resistances per non-Idol Augment in your Equipment"]={{[1]={[1]={skillType=219,type="SkillType"},flags=0,keywordFlags=0,name="MinionModifier",type="LIST",value={mod={flags=0,keywordFlags=0,name="Damage",type="INC",value=10}}}}," per Idol in your Equipment 2% increased Reservation Efficiency of Skills per Idol in your Equipment -4% to all Elemental Resistances per non-Idol Augment in your Equipment "} +c["Companions deal 10% increased damage per Idol in your Equipment"]={{[1]={[1]={skillType=219,type="SkillType"},flags=0,keywordFlags=0,name="MinionModifier",type="LIST",value={mod={[1]={actor="player",type="Multiplier",var="IdolsInEquipment"},flags=0,keywordFlags=0,name="Damage",type="INC",value=10}}}},nil} c["Companions deal 12% increased Damage"]={{[1]={[1]={skillType=219,type="SkillType"},flags=0,keywordFlags=0,name="MinionModifier",type="LIST",value={mod={flags=0,keywordFlags=0,name="Damage",type="INC",value=12}}}},nil} c["Companions deal 15% increased Damage"]={{[1]={[1]={skillType=219,type="SkillType"},flags=0,keywordFlags=0,name="MinionModifier",type="LIST",value={mod={flags=0,keywordFlags=0,name="Damage",type="INC",value=15}}}},nil} c["Companions deal 60% increased damage against Immobilised enemies"]={{[1]={[1]={skillType=219,type="SkillType"},flags=0,keywordFlags=0,name="MinionModifier",type="LIST",value={mod={[1]={actor="enemy",type="ActorCondition",var="Immobilised"},flags=0,keywordFlags=0,name="Damage",type="INC",value=60}}}},nil} diff --git a/src/Modules/CalcSetup.lua b/src/Modules/CalcSetup.lua index 02c55fe4d..6d96cc48f 100644 --- a/src/Modules/CalcSetup.lua +++ b/src/Modules/CalcSetup.lua @@ -1090,11 +1090,28 @@ function calcs.initEnv(build, mode, override, specEnv) Int = item.requirements.intMod, }) end - -- Rune / Soul Core Sockets + -- Rune / Soul Core / Idol Sockets local socketed = 0 for i = 1, item.itemSocketCount do - if item.runes[i] ~= "None" then + local runeName = item.runes[i] + if runeName and runeName ~= "None" then socketed = socketed + 1 + -- Track Idols vs non-Idol augments (Runes + Soul Cores) across all equipment + local runeData = data.itemMods.Runes[runeName] + local augType + if runeData then + for _, baseEntry in pairs(runeData) do + if type(baseEntry) == "table" and baseEntry.type then + augType = baseEntry.type + break + end + end + end + if augType == "Idol" then + env.itemModDB.multipliers["IdolsInEquipment"] = (env.itemModDB.multipliers["IdolsInEquipment"] or 0) + 1 + elseif augType then + env.itemModDB.multipliers["NonIdolAugmentsInEquipment"] = (env.itemModDB.multipliers["NonIdolAugmentsInEquipment"] or 0) + 1 + end end end env.itemModDB.multipliers["RunesSocketedIn"..slotName] = socketed diff --git a/src/Modules/ModParser.lua b/src/Modules/ModParser.lua index b98ce01c0..f5dce983a 100644 --- a/src/Modules/ModParser.lua +++ b/src/Modules/ModParser.lua @@ -1462,6 +1462,8 @@ local modTagList = { ["per (%d+)%% (%a+) effect on enemy"] = function(num, _, effectName) return { tag = { type = "Multiplier", var = firstToUpper(effectName) .. "Effect", div = num, actor = "enemy" } } end, ["per socketed rune or soul core"] = { tag = { type = "Multiplier", var = "RunesSocketedIn{SlotName}" } }, ["per socket filled"] = { tag = { type = "Multiplier", var = "RunesSocketedIn{SlotName}" } }, + ["per idol in your equipment"] = { tag = { type = "Multiplier", var = "IdolsInEquipment", actor = "player" } }, + ["per non%-idol augment in your equipment"] = { tag = { type = "Multiplier", var = "NonIdolAugmentsInEquipment", actor = "player" } }, ["per (%d+) (%a+) support gems socketed"] = function(num, _, color) return { tag = { type = "Multiplier", var = firstToUpper(color) .. "SupportGems", div = num } } end, ["per socketed (%a+) support gem"] = function(color) return { tag = { type = "Multiplier", var = firstToUpper(color) .. "SupportGems" } } end, ["for each equipped normal item"] = { tag = { type = "Multiplier", var = "NormalItem" } }, From 1b3f0d6b2a16c3de94958cc56a6e660642a9ab86 Mon Sep 17 00:00:00 2001 From: Leonel Togniolli Date: Tue, 9 Jun 2026 13:31:32 -0300 Subject: [PATCH 2/2] @ Add Carved (Ancient Augment) Idols to socketable data Carved Cunning/Majesty/Mischief/Tenacity were never recognized because soulcore.txt had no #baseMatch for the Carved metadata prefix, so they were absent from ModRunes.lua. Add the directive and regenerate the data; these Idols now carry type = "Idol" with per-slot mod entries. @ --- src/Data/ModRunes.lua | 105 ++++++++++++++++++++++++++++++++++ src/Export/Bases/soulcore.txt | 1 + 2 files changed, 106 insertions(+) diff --git a/src/Data/ModRunes.lua b/src/Data/ModRunes.lua index 9ad5cdf2f..01b52f040 100644 --- a/src/Data/ModRunes.lua +++ b/src/Data/ModRunes.lua @@ -4994,6 +4994,111 @@ return { rank = { 50 }, }, }, + ["Carved Cunning"] = { + ["helmet"] = { + type = "Idol", + "Enemies which are on Full Life cannot Evade your Hits", + "Bonded: 30% increased Accuracy Rating", + statOrder = { 5293, 1331 }, + tradeHashes = { [4111745607] = { "Enemies which are on Full Life cannot Evade your Hits" }, }, + rank = { 60 }, + }, + ["body armour"] = { + type = "Idol", + "Prevent +5% of Damage from Deflected Hits if you've", + "Deflected no Hits Recently", + "Bonded: 8% increased Deflection Rating", + statOrder = { 4668, 4668.1, 6106 }, + tradeHashes = { [967155385] = { "Prevent +5% of Damage from Deflected Hits if you've", "Deflected no Hits Recently" }, }, + rank = { 60 }, + }, + ["boots"] = { + type = "Idol", + "Gain Onslaught for 4 seconds when your Marks Activate", + "Bonded: Buffs on you expire 10% slower", + statOrder = { 6805, 5228 }, + tradeHashes = { [1811977226] = { "Gain Onslaught for 4 seconds when your Marks Activate" }, }, + rank = { 60 }, + }, + }, + ["Carved Majesty"] = { + ["body armour"] = { + type = "Idol", + "+3 to Spirit per Idol socketed in your Equipment", + "Bonded: 5% increased Spirit", + statOrder = { 4742, 1416 }, + tradeHashes = { [1073847159] = { "+3 to Spirit per Idol socketed in your Equipment" }, }, + rank = { 60 }, + }, + ["gloves"] = { + type = "Idol", + "Companions gain Onslaught for 4 seconds on Hitting your Marked targets", + "Bonded: Companions deal 30% increased Damage", + statOrder = { 5720, 5709 }, + tradeHashes = { [226999623] = { "Companions gain Onslaught for 4 seconds on Hitting your Marked targets" }, }, + rank = { 60 }, + }, + ["boots"] = { + type = "Idol", + "1% increased Movement Speed while Sprinting per Persistent Minion", + "Bonded: Minions have 12% increased maximum Life", + statOrder = { 10042, 1025 }, + tradeHashes = { [3639405795] = { "1% increased Movement Speed while Sprinting per Persistent Minion" }, }, + rank = { 60 }, + }, + }, + ["Carved Mischief"] = { + ["helmet"] = { + type = "Idol", + "Gain Guard equal to 10% of maximum Life for 4 seconds on taking Savage Hit", + "Bonded: Buffs on you expire 10% slower", + statOrder = { 6783, 5228 }, + tradeHashes = { [3863682550] = { "Gain Guard equal to 10% of maximum Life for 4 seconds on taking Savage Hit" }, }, + rank = { 60 }, + }, + ["gloves"] = { + type = "Idol", + "+5% to maximum Block chance if you've Blocked with a raised Shield Recently", + "Bonded: 20% increased Block chance", + statOrder = { 4197, 1132 }, + tradeHashes = { [3617372509] = { "+5% to maximum Block chance if you've Blocked with a raised Shield Recently" }, }, + rank = { 60 }, + }, + ["body armour"] = { + type = "Idol", + "200% increased Stun Threshold if you've been Stunned Recently", + "Bonded: 25% increased Stun Threshold", + statOrder = { 10104, 2981 }, + tradeHashes = { [751944209] = { "200% increased Stun Threshold if you've been Stunned Recently" }, }, + rank = { 60 }, + }, + }, + ["Carved Tenacity"] = { + ["helmet"] = { + type = "Idol", + "Enemies have no Critical Damage Bonus for 4 seconds after you Blind them", + "Bonded: 20% increased Blind Effect", + statOrder = { 6368, 4916 }, + tradeHashes = { [25786091] = { "Enemies have no Critical Damage Bonus for 4 seconds after you Blind them" }, }, + rank = { 60 }, + }, + ["gloves"] = { + type = "Idol", + "Enemies you Critically Hit get 100% reduced Life Regeneration Rate for 4 seconds", + "Bonded: 15% increased Critical Hit Chance", + statOrder = { 5809, 975 }, + tradeHashes = { [3370077792] = { "Enemies you Critically Hit get 100% reduced Life Regeneration Rate for 4 seconds" }, }, + rank = { 60 }, + }, + ["boots"] = { + type = "Idol", + "Your speed is Unaffected by Slows while Sprinting", + "Bonded: 8% increased Movement Speed while Sprinting", + statOrder = { 9911, 10041 }, + tradeHashes = { [3128773415] = { "Your speed is Unaffected by Slows while Sprinting" }, }, + rank = { 60 }, + }, + }, ["Raven-Touched Shard"] = { ["helmet"] = { type = "CongealedMist", diff --git a/src/Export/Bases/soulcore.txt b/src/Export/Bases/soulcore.txt index 4e569b627..cb0817b9d 100644 --- a/src/Export/Bases/soulcore.txt +++ b/src/Export/Bases/soulcore.txt @@ -18,6 +18,7 @@ local itemBases = ... #type Idol #baseMatch Metadata/Items/SoulCores/Talisman #baseMatch Metadata/Items/SoulCores/Idol +#baseMatch Metadata/Items/SoulCores/Carved #type CongealedMist #baseMatch Metadata/Items/SoulCores/AugmentAnoint \ No newline at end of file