Skip to content

Commit cedccb9

Browse files
Rewrite Foulborn generation with proper mod replacement logic
Instead of appending mutation mods as variants to the original's base mods, the new logic properly replaces specific original mods with their Foulborn mutations -- matching the actual in-game behavior. Key changes: - Add ModFoulbornPairs.lua: mutation slot pairing table (239 uniques) extracted from PR #9432's static item data, defining which original mods get removed and what replaces them per mutation slot - Rewrite Generated.lua Foulborn block: for each unique, enumerate all non-empty subsets of mutation slots (2^n - 1 items per unique), remove matched mods and add mutated replacements with "(mutated)" suffix - Add warning system: ConPrintf flags uniques in ModFoulbornMap that lack pairing data in ModFoulbornPairs (22 uniques currently) - Update tests: 14 tests covering item count (437), per-unique counts, mod replacement verification, implicit handling, Skin of the Lords - Remove dumpModEquivalencies.lua (no longer needed) Total: 437 generated Foulborn items with correct replacement semantics.
1 parent 39bba5e commit cedccb9

5 files changed

Lines changed: 2572 additions & 194 deletions

File tree

spec/System/TestFoulborn_spec.lua

Lines changed: 80 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,56 +1,106 @@
11
describe("Foulborn generation", function()
2-
it("generates Foulborn items from foulbornMap", function()
2+
it("generates correct number of Foulborn items", function()
33
local foulbornCount = 0
44
for _, raw in ipairs(data.uniques.generated) do
55
if raw:match("^Foulborn ") then
66
foulbornCount = foulbornCount + 1
77
end
88
end
9-
assert.is_true(foulbornCount > 200, "Expected 200+ Foulborn items, got " .. foulbornCount)
9+
-- PR #9432 has 440 items total; Skin of the Lords is tree-dependent (separate function)
10+
-- so we expect ~439 non-tree-dependent items
11+
assert.is_true(foulbornCount >= 430, "Expected 430+ Foulborn items, got " .. foulbornCount)
1012
print(" Foulborn items generated: " .. foulbornCount)
1113
end)
1214

13-
it("generates valid Foulborn Alpha's Howl", function()
15+
it("generates correct number of Headhunter items (2^3-1=7)", function()
16+
local count = 0
17+
for _, raw in ipairs(data.uniques.generated) do
18+
if raw:match("^Foulborn Headhunter %d+\n") then
19+
count = count + 1
20+
end
21+
end
22+
assert.are.equals(7, count, "Headhunter should have 7 Foulborn items (3 slots)")
23+
end)
24+
25+
it("generates correct number of Alpha's Howl items (2^2-1=3)", function()
26+
local count = 0
27+
for _, raw in ipairs(data.uniques.generated) do
28+
if raw:match("^Foulborn Alpha's Howl %d+\n") then
29+
count = count + 1
30+
end
31+
end
32+
assert.are.equals(3, count, "Alpha's Howl should have 3 Foulborn items (2 slots)")
33+
end)
34+
35+
it("verifies mod replacement in Headhunter 1", function()
1436
local raw
1537
for _, r in ipairs(data.uniques.generated) do
16-
if r:match("^Foulborn Alpha") then raw = r; break end
38+
if r:match("^Foulborn Headhunter 1\n") then raw = r; break end
1739
end
18-
assert.is_truthy(raw, "Foulborn Alpha's Howl not found")
40+
assert.is_truthy(raw, "Foulborn Headhunter 1 not found")
41+
-- Should have Culling Strike (mutated) as the added mod
42+
assert.truthy(raw:match("Culling Strike %(mutated%)"), "Missing added mod 'Culling Strike (mutated)'")
43+
-- Should NOT have the removed mod
44+
assert.is_falsy(raw:match("increased Damage with Hits against Rare monsters"), "Removed mod should not be present")
45+
-- Should still have other original mods
46+
assert.truthy(raw:match("When you Kill a Rare monster"), "Missing original mod that should be preserved")
47+
assert.truthy(raw:match("Leather Belt"), "Missing base type")
48+
end)
49+
50+
it("verifies Headhunter 7 has all three mutations", function()
51+
local raw
52+
for _, r in ipairs(data.uniques.generated) do
53+
if r:match("^Foulborn Headhunter 7\n") then raw = r; break end
54+
end
55+
assert.is_truthy(raw, "Foulborn Headhunter 7 not found")
56+
assert.truthy(raw:match("Culling Strike %(mutated%)"), "Missing Culling Strike")
57+
assert.truthy(raw:match("Eat a Soul.-%(mutated%)"), "Missing Eat a Soul")
58+
assert.truthy(raw:match("Minimap Icons.-%(mutated%)"), "Missing Minimap Icons")
59+
-- All three original mods should be removed
60+
assert.is_falsy(raw:match("increased Damage with Hits against Rare monsters"), "Removed mod still present")
61+
assert.is_falsy(raw:match("When you Kill a Rare monster, you gain its Modifiers"), "Removed mod still present")
62+
end)
63+
64+
it("generates valid Foulborn Alpha's Howl 1", function()
65+
local raw
66+
for _, r in ipairs(data.uniques.generated) do
67+
if r:match("^Foulborn Alpha's Howl 1\n") then raw = r; break end
68+
end
69+
assert.is_truthy(raw, "Foulborn Alpha's Howl 1 not found")
1970
assert.truthy(raw:match("Sinner Tricorne"), "Missing base type")
20-
assert.truthy(raw:match("Variant:"), "Missing variant declarations")
21-
assert.truthy(raw:match("{variant:1}"), "Missing variant-tagged mod")
2271
assert.truthy(raw:match("Requires Level"), "Missing requires line")
72+
-- Should have added mod and NOT have removed mod
73+
assert.truthy(raw:match("Life Reservation Efficiency.-%(mutated%)"), "Missing added mutation mod")
74+
assert.is_falsy(raw:match("16%% increased Mana Reservation Efficiency"), "Removed mod should not be present")
2375
end)
2476

2577
it("parses Foulborn item with foulborn flag", function()
2678
newBuild()
2779
local raw
2880
for _, r in ipairs(data.uniques.generated) do
29-
if r:match("^Foulborn Alpha") then raw = r; break end
81+
if r:match("^Foulborn Alpha's Howl 1\n") then raw = r; break end
3082
end
3183
local item = new("Item", raw)
3284
assert.is_true(item.foulborn, "foulborn flag not set")
3385
assert.are.equals("Sinner Tricorne", item.baseName)
34-
assert.are.equals("Foulborn Alpha's Howl", item.title)
35-
assert.is_true(#item.variantList > 0, "No variants parsed")
36-
print(" Variants: " .. #item.variantList)
86+
assert.are.equals("Foulborn Alpha's Howl 1", item.title)
3787
end)
3888

3989
it("generates Foulborn item with existing variants correctly", function()
4090
local raw
4191
for _, r in ipairs(data.uniques.generated) do
42-
if r:match("^Foulborn The Formless Flame") then raw = r; break end
92+
if r:match("^Foulborn The Formless Flame %d+\n") then raw = r; break end
4393
end
4494
assert.is_truthy(raw, "Foulborn The Formless Flame not found")
45-
-- Should use Current variant base (Royal Burgonet, not Siege Helmet)
46-
assert.truthy(raw:match("Royal Burgonet"), "Wrong base type - should be Royal Burgonet (Current variant)")
95+
-- Should use current variant base (Royal Burgonet, not Siege Helmet)
96+
assert.truthy(raw:match("Royal Burgonet"), "Wrong base type - should be Royal Burgonet (current variant)")
4797
assert.is_falsy(raw:match("Siege Helmet"), "Should not contain old variant base")
4898
end)
4999

50100
it("generates Foulborn item with implicits", function()
51101
local raw
52102
for _, r in ipairs(data.uniques.generated) do
53-
if r:match("^Foulborn Call of the Brotherhood") then raw = r; break end
103+
if r:match("^Foulborn Call of the Brotherhood %d+\n") then raw = r; break end
54104
end
55105
assert.is_truthy(raw, "Foulborn Call of the Brotherhood not found")
56106
assert.truthy(raw:match("Implicits: 1"), "Missing implicits declaration")
@@ -61,7 +111,7 @@ describe("Foulborn generation", function()
61111
newBuild()
62112
local raw
63113
for _, r in ipairs(data.uniques.generated) do
64-
if r:match("^Foulborn Call of the Brotherhood") then raw = r; break end
114+
if r:match("^Foulborn Call of the Brotherhood %d+\n") then raw = r; break end
65115
end
66116
assert.is_truthy(raw, "Foulborn Call of the Brotherhood not found")
67117
local item = new("Item", raw)
@@ -117,4 +167,18 @@ describe("Foulborn generation", function()
117167
assert.is_true(#item.variantList > 30, "Not enough variants parsed")
118168
print(" Parsed variants: " .. #item.variantList .. ", hasAltVariant: " .. tostring(item.hasAltVariant))
119169
end)
170+
171+
it("generates items with (mutated) tag on all added mods", function()
172+
local issues = {}
173+
for _, raw in ipairs(data.uniques.generated) do
174+
if raw:match("^Foulborn ") and not raw:match("^Foulborn Skin of the Lords") then
175+
local hasMutated = raw:match("%(mutated%)")
176+
if not hasMutated then
177+
local name = raw:match("^(.-)\n")
178+
table.insert(issues, name)
179+
end
180+
end
181+
end
182+
assert.are.equals(0, #issues, "Items without (mutated) tag: " .. table.concat(issues, ", "))
183+
end)
120184
end)

0 commit comments

Comments
 (0)