Skip to content

Commit 9fc0abe

Browse files
author
LocalIdentity
committed
Add support for Idolatry and change how Bonded lines are parsed
Export Bonded modifiers into separate rune data sub-tables instead of adding "Bonded:" as a line prefix. Fix export of some Bonded mods that had separate stat entries that were not being combined. e.g. Perfect Resolve Rune did not add the bonded mods for armour, weapons, wands and staves Rebuild rune mods from rune data, keeping Bonded lines unparsed until they are enabled. Add support for the global `CanUseBonded` flag and the item-local Idol-only unlock, including socketed augment effect scaling. Add tests for many of the new changes. This now fixes all the mods that didn't parse correctly before because they were not a piece-meal mod and required explicit "Bonded:" ModParser lines
1 parent ce8bffa commit 9fc0abe

11 files changed

Lines changed: 2914 additions & 1045 deletions

File tree

spec/System/TestIdolatry_spec.lua

Lines changed: 81 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ describe("TestIdolatry", function()
5959
end)
6060

6161
-- Parsing: the three stat lines must resolve to mods that scale against those multipliers.
62-
it("parses the three Idolatry stat lines", function()
62+
it("parses Idolatry and bonded-idol stat lines", function()
6363
local parseMod = LoadModule("Modules/ModParser")
6464

6565
-- Helper to find the Multiplier tag on a mod (tags are stored as array entries)
@@ -98,5 +98,85 @@ describe("TestIdolatry", function()
9898
assert.are.equals("BASE", resist[1].type)
9999
assert.are.equals(-4, resist[1].value)
100100
assert.are.equals("NonIdolAugmentsInEquipment", multiplierTag(resist[1]).var)
101+
102+
-- 4) Fox Idol's unlock is an item-local flag, not a global condition.
103+
local localUnlock = parseMod("Idols socketed in this item gain the benefits of their Bonded modifiers")
104+
assert.are.equals(1, #localUnlock)
105+
assert.are.equals("SocketedIdolsUseBondedModifiers", localUnlock[1].name)
106+
assert.are.equals("FLAG", localUnlock[1].type)
107+
108+
-- 5) The ascendancy unlock is a flag consumed by calc setup, not a condition on each bonded mod.
109+
local globalUnlock = parseMod("Gain the benefits of Bonded modifiers on Runes and Idols")
110+
assert.are.equals(1, #globalUnlock)
111+
assert.are.equals("CanUseBonded", globalUnlock[1].name)
112+
assert.are.equals("FLAG", globalUnlock[1].type)
113+
end)
114+
115+
it("enables only Idol Bonded modifiers from Fox Idol locally", function()
116+
local item = new("Item", [[
117+
Test Body
118+
Rusted Cuirass
119+
]])
120+
item.itemSocketCount = 2
121+
item.runes = { "Fox Idol", "Lesser Body Rune" }
122+
item:UpdateRunes()
123+
item:BuildAndParseRaw()
124+
assert.is_true(item.socketedIdolsUseBondedModifiers)
125+
126+
local foxBondedLine
127+
local bodyRuneBondedLife
128+
for _, modLine in ipairs(item.runeModLines) do
129+
if modLine.line == "Bonded: +5% to Quality of all Skills" then
130+
foxBondedLine = modLine
131+
end
132+
if modLine.line == "Bonded: +20 to maximum Life" then
133+
bodyRuneBondedLife = modLine
134+
end
135+
end
136+
assert.is_not_nil(foxBondedLine)
137+
assert.are.equals(0, #foxBondedLine.modList)
138+
assert.is_not_nil(bodyRuneBondedLife)
139+
assert.are.equals(0, #bodyRuneBondedLife.modList)
140+
141+
build.itemsTab:AddItem(item)
142+
build.buildFlag = true
143+
runCallback("OnFrame")
144+
145+
local modDB = build.calcsTab.mainEnv.itemModDB
146+
local foxBondedQuality
147+
for _, mod in ipairs(modDB.mods.GemProperty or { }) do
148+
if mod.value.value == 5 and mod.source == item.modSource then
149+
foxBondedQuality = mod
150+
break
151+
end
152+
end
153+
assert.is_not_nil(foxBondedQuality)
154+
assert.is_not_nil(foxBondedLine.bondedModList)
155+
assert.is_nil(bodyRuneBondedLife.bondedModList)
156+
157+
for _, mod in ipairs(modDB.mods.Life or { }) do
158+
assert.is_false(mod.type == "BASE" and mod.value == 20 and mod.source == item.modSource)
159+
end
160+
end)
161+
162+
it("enables and scales Bonded modifiers from the ascendancy flag", function()
163+
build.spec.allocNodes[42253] = build.spec.nodes[42253] -- Wisdom of the Maji
164+
165+
local item = new("Item", [[
166+
Test Body
167+
Rusted Cuirass
168+
200% increased effect of Socketed Runes
169+
]])
170+
item.itemSocketCount = 1
171+
item.runes = { "Lesser Body Rune" }
172+
item:UpdateRunes()
173+
item:BuildAndParseRaw()
174+
build.itemsTab:AddItem(item)
175+
build.buildFlag = true
176+
runCallback("OnFrame")
177+
178+
local modDB = build.calcsTab.mainEnv.itemModDB
179+
assert.are.equals(150, modDB:Sum("BASE", nil, "Life"))
180+
assert.are.equals(60, modDB:Sum("BASE", nil, "Mana"))
101181
end)
102182
end)

spec/System/TestItemParse_spec.lua

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -659,10 +659,15 @@ describe("TestItemParse", function()
659659

660660
assert.are.equals(3, item.itemSocketCount)
661661
assert.are.same({ "Greater Glacial Rune", "Lesser Body Rune" }, item.runes)
662-
assert.are.equals(1, item.runeModLines[1].runeCount)
663-
assert.are.equals(1, item.runeModLines[2].runeCount)
664-
assert.is_nil(item.runeModLines[3].runeCount)
665-
assert.is_nil(item.runeModLines[4].runeCount)
662+
local runeLines = { }
663+
for _, modLine in ipairs(item.runeModLines) do
664+
runeLines[modLine.line] = true
665+
end
666+
assert.are.equals(4, #item.runeModLines)
667+
assert.is_true(runeLines["Adds 9 to 15 Cold Damage"])
668+
assert.is_true(runeLines["Leeches 3% of Physical Damage as Life"])
669+
assert.is_true(runeLines["Bonded: 5% increased maximum Life"])
670+
assert.is_true(runeLines["Bonded: 30% increased Freeze Buildup"])
666671
for _, rune in ipairs(item.runes) do
667672
assert.are_not.equals("Lesser Glacial Rune", rune)
668673
end
@@ -682,6 +687,8 @@ describe("TestItemParse", function()
682687
assert.are.equals("+30 to maximum Life", item.runeModLines[1].line)
683688
assert.are.equals("Bonded: +20 to maximum Life", item.runeModLines[2].line)
684689
assert.are.equals("Bonded: +20 to maximum Mana", item.runeModLines[3].line)
690+
assert.are.equals(0, #item.runeModLines[2].modList)
691+
assert.are.equals(0, #item.runeModLines[3].modList)
685692
end)
686693

687694
it("applies increased effect of socketed runes", function()

spec/System/TestItemsTab_spec.lua

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -722,6 +722,25 @@ describe("TestItemsTab", function()
722722
assert.are.equals("Hits against you have 20% reduced Critical Damage Bonus", ticabaRune.lines[1])
723723
assert.are.equals("Hits against you have 20% reduced Critical Damage Bonus", ticabaRune.lines[2])
724724
end)
725+
726+
it("uses the regular rune mod as the dropdown label", function ()
727+
local weaponMods = data.itemMods.Runes["Perfect Robust Rune"].weapon
728+
assert.are.equals("+15 to Strength", weaponMods[1])
729+
assert.are.same({ "Adds 6 to 10 Physical Damage to Attacks", "Adds 6 to 10 Fire damage to Attacks" }, { unpack(weaponMods.bonded) })
730+
731+
local item = new("Item", [[
732+
Test Wand
733+
Runic Fork
734+
]])
735+
736+
for _, rune in ipairs(build.itemsTab:GetValidRunesForItem(item)) do
737+
if rune.name == "Perfect Robust Rune" then
738+
assert.are.equals("+15 to Strength", rune.label)
739+
return
740+
end
741+
end
742+
assert.fail("Perfect Robust Rune was not valid for a wand")
743+
end)
725744
end)
726745

727746
it("does nothing when no matching item is equipped", function ()

spec/System/TestSocketables_spec.lua

Lines changed: 17 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,19 @@ describe("TestSocketables", function()
66
-- Item Tab display Tests
77
-- Also checks slot type runes
88

9-
local extractNamesFromModRunes = function(slotType)
9+
local extractNamesFromModRunes = function(item)
1010
local modRunes = LoadModule("../src/Data/ModRunes")
1111
local names = { }
12+
local baseType, specificType = item:GetSocketedAugmentTypes()
1213
for name, rune in pairs(modRunes) do
13-
for runeSlotType, mods in pairs(rune) do
14-
if runeSlotType == slotType then
15-
table.insert(names, name)
14+
if rune[baseType] or rune[specificType] then
15+
names[name] = true
16+
else
17+
for soulCoreType in pairs(item.socketedSoulCoreTypes) do
18+
if rune[soulCoreType] and rune[soulCoreType].type == "SoulCore" then
19+
names[name] = true
20+
break
21+
end
1622
end
1723
end
1824
end
@@ -23,36 +29,23 @@ describe("TestSocketables", function()
2329
-- ConPrintf("Testing: %s", slotType)
2430
local itemRaw = "Test\n" .. itemBase .. "\nSockets: S"
2531

26-
local modRunes = extractNamesFromModRunes(slotType)
27-
2832
-- Create an ItemTab and add a socketable item to it
2933
local item = new("Item", itemRaw)
34+
local modRunes = extractNamesFromModRunes(item)
3035

3136
build.itemsTab:AddItem(item)
3237
build.itemsTab:SetDisplayItem(item)
3338
runCallback("OnFrame")
3439

35-
-- Extract the proper slot type runes from the list
40+
-- The dropdown combines broad and specific slot types, then deduplicates by name.
41+
-- Compare that exact union so both missing and incorrectly included runes fail.
3642
local itemTabRunes = { }
3743
for _, rune in ipairs(build.itemsTab.controls["displayItemRune1"].list) do
38-
if rune.slot == slotType then
39-
table.insert(itemTabRunes, rune.name)
40-
end
41-
end
42-
-- To keep the test fast, only check that the lengths match
43-
-- This should also catch issues with multi-mod line runes since the rune name will appear
44-
-- for the number of mod lines that the rune has.
45-
if #itemTabRunes ~= #modRunes then
46-
ConPrintf("Item Tab Runes for slot type '%s':", slotType)
47-
for _, name in ipairs(itemTabRunes) do
48-
ConPrintf(" %s", name)
49-
end
50-
ConPrintf("Mod Runes for slot type '%s':", slotType)
51-
for _, name in ipairs(modRunes) do
52-
ConPrintf(" %s", name)
44+
if rune.name ~= "None" then
45+
itemTabRunes[rune.name] = true
5346
end
5447
end
55-
assert.are.equals(#itemTabRunes, #modRunes, "Mismatch in number of runes for slot type: " .. slotType)
48+
assert.are.same(modRunes, itemTabRunes, "Rune list mismatch for slot type: " .. slotType)
5649
end
5750

5851
-- Note: Except for weapon/armour/caster,
@@ -113,4 +106,4 @@ describe("TestSocketables", function()
113106

114107
-- it("'Dagger' runes appear in Items tab", slotTypeTest("dagger", ""))
115108

116-
end)
109+
end)

0 commit comments

Comments
 (0)