Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
136 changes: 136 additions & 0 deletions spec/System/TestClusterJewelPreview_spec.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
describe("Cluster jewel notable preview", function()
local function makeCluster(names, size, count, sockets)
size = size or "Large"
local raw = "Rarity: RARE\nPreview\n"..size.." Cluster Jewel\nImplicits: 2\nAdds "..(count or 8).." Passive Skills\n"..(sockets or 2).." Added Passive Skills are Jewel Sockets\n"
for _, name in ipairs(names) do
raw = raw.."1 Added Passive Skill is "..name.."\n"
end
return new("Item"):Item(raw)
end

local function positions(item)
return itemLib.getClusterJewelNotablePositions(item, data.clusterJewels.notableSortOrder)
end

before_each(function()
newBuild()
end)

it("places a single large-cluster notable at the back", function()
assert.are.same({ { name = "Renewal", index = 6, position = "Back" } }, positions(makeCluster({ "Renewal" })))
end)

it("moves the desired pair to the front when a middle-ordered notable is added", function()
local names = { "Feasting Fiends", "Renewal" }
local item = makeCluster(names)
assert.are.same({
{ name = "Renewal", index = 6, position = "Back" },
{ name = "Feasting Fiends", index = 10, position = "Front" },
}, positions(item))
table.insert(names, "Call to the Slaughter")
assert.are.same({
{ name = "Renewal", index = 2, position = "Front" },
{ name = "Call to the Slaughter", index = 6, position = "Back" },
{ name = "Feasting Fiends", index = 10, position = "Front" },
}, positions(makeCluster(names)))
assert.are.same({ "Feasting Fiends", "Renewal" }, item.jewelData.clusterJewelNotables)
end)

it("handles the legacy single socket and socket-count override", function()
local item = makeCluster({ "Renewal", "Feasting Fiends" }, "Large", 8, 1)
assert.are.same({ 4, 8 }, itemLib.getClusterJewelNotableIndices(item.clusterJewel, item.jewelData, 2))
item.jewelData.clusterJewelSocketCountOverride = 2
assert.are.same({ 6, 10 }, itemLib.getClusterJewelNotableIndices(item.clusterJewel, item.jewelData, 2))
end)

it("uses the special four-passive medium-cluster positions", function()
local item = makeCluster({ "Flow of Life", "Brush with Death" }, "Medium", 4, 1)
local preview = positions(item)
assert.are.equal(3, preview[1].index)
assert.are.equal(9, preview[2].index)
assert.are.equal("Front", preview[1].position)
assert.are.equal("Front", preview[2].position)
item.jewelData.clusterJewelSocketCountOverride = 0
assert.are.same({ 4, 8 }, itemLib.getClusterJewelNotableIndices(item.clusterJewel, item.jewelData, 2))
end)

it("labels a small cluster's terminal notable as End", function()
local preview = positions(makeCluster({ "Fettle" }, "Small", 3, 0))
assert.are.equal("End", preview[1].position)
end)

it("returns no positions for ordinary items, empty clusters, or keystones", function()
assert.are.same({ }, positions(nil))
assert.are.same({ }, positions(new("Item"):Item("Rarity: NORMAL\nIron Ring")))
assert.are.same({ }, positions(makeCluster({ })))
local item = makeCluster({ "Renewal" })
item.jewelData.clusterJewelKeystone = "Hollow Palm Technique"
assert.are.same({ }, positions(item))
end)

it("hides the editor readout for ordinary medium and small clusters", function()
for _, item in ipairs({
makeCluster({ "Flow of Life", "Brush with Death" }, "Medium", 4, 1),
makeCluster({ "Fettle" }, "Small", 3, 0),
}) do
build.itemsTab:SetDisplayItem(item)
item.crafted = true
local controls = build.itemsTab.controls
assert.is_falsy(controls.displayItemClusterJewelPositions:IsShown())
assert.are.equal(0, controls.displayItemSectionClusterJewelPositions:GetProperty("height"))
end
end)

it("shows Megalomaniac positions without requiring crafting controls", function()
local item = new("Item"):Item([[Rarity: UNIQUE
Megalomaniac
Medium Cluster Jewel
Implicits: 2
Adds 4 Passive Skills
Added Small Passive Skills grant Nothing
1 Added Passive Skill is Feasting Fiends
1 Added Passive Skill is Renewal
1 Added Passive Skill is Call to the Slaughter]])
build.itemsTab:SetDisplayItem(item)
local controls = build.itemsTab.controls
assert.is_falsy(controls.displayItemClusterJewelSkill:IsShown())
assert.is_true(controls.displayItemClusterJewelPosition3:IsShown())
assert.are.same({
{ name = "Renewal", index = 3, position = "Front" },
{ name = "Call to the Slaughter", index = 6, position = "Back" },
{ name = "Feasting Fiends", index = 9, position = "Front" },
}, build.itemsTab.clusterJewelNotablePositions)
assert.are.equal(78, controls.displayItemSectionClusterJewelPositions:GetProperty("height"))
end)

it("updates the editor readout and collapses it when the last notable is removed", function()
local tab = build.itemsTab
local item = makeCluster({ "Renewal" })
-- Exercise the editor controls without turning this parsed fixture into a crafted item.
tab:SetDisplayItem(item)
item.crafted = true
local controls = tab.controls
assert.is_true(controls.displayItemClusterJewelPosition1:IsShown())
assert.is_truthy(controls.displayItemClusterJewelPosition1:GetProperty("label"):find("Back: %^7Renewal"))
assert.are.equal(52, controls.displayItemSectionClusterJewel:GetProperty("height"))
assert.are.equal(42, controls.displayItemSectionClusterJewelPositions:GetProperty("height"))
item.jewelData.clusterJewelNotables = { "Feasting Fiends", "Call to the Slaughter", "Renewal" }
tab:UpdateDisplayItemTooltip()
assert.is_truthy(controls.displayItemClusterJewelPosition1:GetProperty("label"):find("Front: %^7Renewal"))
assert.is_true(controls.displayItemClusterJewelPosition3:IsShown())
local _, lastLabelY = controls.displayItemClusterJewelPosition3:GetPos()
local _, headerY = controls.displayItemClusterJewelPositions:GetPos()
local _, addModifierY = controls.displayItemAddCustom:GetPos()
local _, rangeY = controls.displayItemSectionRange:GetPos()
assert.is_true(headerY >= addModifierY + 28)
assert.is_true(rangeY >= lastLabelY + 18)
item.jewelData.clusterJewelNotables = { }
tab:UpdateDisplayItemTooltip()
assert.is_false(controls.displayItemClusterJewelPositions:IsShown())
assert.are.equal(0, controls.displayItemSectionClusterJewelPositions:GetProperty("height"))
assert.are.equal(52, controls.displayItemSectionClusterJewel:GetProperty("height"))
tab:SetDisplayItem(new("Item"):Item("Rarity: NORMAL\nIron Ring"))
assert.is_falsy(controls.displayItemClusterJewelPosition1:IsShown())
assert.are.equal(0, controls.displayItemSectionClusterJewel:GetProperty("height"))
end)
end)
30 changes: 29 additions & 1 deletion src/Classes/ItemsTab.lua
Original file line number Diff line number Diff line change
Expand Up @@ -1009,9 +1009,36 @@ holding Shift will put it in the second.]])
return self.displayItem and (self.displayItem:GetPrimarySlot() == "Weapon 1" or self.displayItem.type == "Shield" or self.displayItem.canHaveShieldCrucibleTree)
end

-- Section: Cluster jewel notable positions
self.controls.displayItemSectionClusterJewelPositions = new("Control"):Control({"TOPLEFT",self.controls.displayItemSectionCustom,"BOTTOMLEFT"}, {0, 0, 0, function()
if not self.controls.displayItemClusterJewelPositions:IsShown() then
return 0
end
return 24 + #self.clusterJewelNotablePositions * 18
end})
self.controls.displayItemClusterJewelPositions = new("LabelControl"):LabelControl({"TOPLEFT",self.controls.displayItemSectionClusterJewelPositions,"TOPLEFT"}, {0, 0, 0, 14}, "^7Notable positions (relative to entrance):")
self.controls.displayItemClusterJewelPositions.shown = function()
local item = self.displayItem
return item and item.clusterJewel and (item.clusterJewel.size == "Large" and item.crafted or item.title == "Megalomaniac")
and #(self.clusterJewelNotablePositions or { }) > 0
end
for i = 1, 4 do
self.controls["displayItemClusterJewelPosition"..i] = new("LabelControl"):LabelControl({"TOPLEFT",self.controls.displayItemClusterJewelPositions,"TOPLEFT"}, {0, i * 18, 0, 14}, function()
local notable = self.clusterJewelNotablePositions and self.clusterJewelNotablePositions[i]
if not notable then
return ""
end
local color = notable.position == "Front" and colorCodes.POSITIVE or colorCodes.NORMAL
return color..notable.position..": ^7"..notable.name
end)
self.controls["displayItemClusterJewelPosition"..i].shown = function()
return self.controls.displayItemClusterJewelPositions:IsShown() and self.clusterJewelNotablePositions[i] ~= nil
end
end

-- Section: Modifier Range
local labelFontSize = 14
self.controls.displayItemSectionRange = new("Control"):Control({ "TOPLEFT", self.controls.displayItemSectionCustom, "BOTTOMLEFT" }, { 0, 0, 0, function()
self.controls.displayItemSectionRange = new("Control"):Control({ "TOPLEFT", self.controls.displayItemSectionClusterJewelPositions, "BOTTOMLEFT" }, { 0, 0, 0, function()
if not self.displayItem or not self.displayItem.rangeLineList[1] then
return 0
end
Expand Down Expand Up @@ -2115,6 +2142,7 @@ function ItemsTabClass:SetDisplayItem(item)
end

function ItemsTabClass:UpdateDisplayItemTooltip()
self.clusterJewelNotablePositions = itemLib.getClusterJewelNotablePositions(self.displayItem, self.build.data.clusterJewels.notableSortOrder)
self.displayItemTooltip:Clear()
self:AddItemTooltip(self.displayItemTooltip, self.displayItem)
self.displayItemTooltip.center = true
Expand Down
28 changes: 1 addition & 27 deletions src/Classes/PassiveSpec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -2195,33 +2195,7 @@ function PassiveSpecClass:BuildSubgraph(jewel, parentSocket, id, upSize, importe
-- Second pass: notables

-- Gather notable indicies
local notableIndexList = { }
for _, nodeIndex in ipairs(clusterJewel.notableIndicies) do
if #notableIndexList == notableCount then
break
end
if clusterJewel.size == "Medium" then
if socketCount == 0 and notableCount == 2 then
-- Special rule for two notables in a Medium cluster
if nodeIndex == 6 then
nodeIndex = 4
elseif nodeIndex == 10 then
nodeIndex = 8
end
elseif nodeCount == 4 then
-- Special rule for notables in a 4-node Medium cluster
if nodeIndex == 10 then
nodeIndex = 9
elseif nodeIndex == 2 then
nodeIndex = 3
end
end
end
if not indicies[nodeIndex] then
t_insert(notableIndexList, nodeIndex)
end
end
table.sort(notableIndexList)
local notableIndexList = itemLib.getClusterJewelNotableIndices(clusterJewel, jewelData, notableCount)

-- Create the notables
for index, baseNode in ipairs(notableList) do
Expand Down
72 changes: 71 additions & 1 deletion src/Modules/ItemTools.lua
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,76 @@ local m_floor = math.floor

itemLib = { }

-- Cluster-template positions, shared by the passive tree and the item editor.
-- Index 0 is the entrance; half of totalIndicies is the back of the cluster.
function itemLib.getClusterJewelNotableIndices(clusterJewel, jewelData, notableCount)
local socketCount = jewelData.clusterJewelSocketCountOverride or jewelData.clusterJewelSocketCount or 0
local nodeCount = jewelData.clusterJewelNodeCount or (socketCount + notableCount + (jewelData.clusterJewelNothingnessCount or 0))
local sockets = { }
if clusterJewel.size == "Large" and socketCount == 1 then
sockets[6] = true
else
for i = 1, socketCount do
local nodeIndex = clusterJewel.socketIndicies[i]
if nodeIndex then
sockets[nodeIndex] = true
end
end
end
local indices = { }
for _, nodeIndex in ipairs(clusterJewel.notableIndicies) do
if #indices == notableCount then
break
end
if clusterJewel.size == "Medium" then
if socketCount == 0 and notableCount == 2 then
if nodeIndex == 6 then
nodeIndex = 4
elseif nodeIndex == 10 then
nodeIndex = 8
end
elseif nodeCount == 4 then
if nodeIndex == 10 then
nodeIndex = 9
elseif nodeIndex == 2 then
nodeIndex = 3
end
end
end
if not sockets[nodeIndex] then
t_insert(indices, nodeIndex)
end
end
table.sort(indices)
return indices
end

function itemLib.getClusterJewelNotablePositions(item, sortOrder)
local positions = { }
if not item or not item.clusterJewel or not item.jewelData or item.jewelData.clusterJewelKeystone then
return positions
end
local names = { }
for _, name in ipairs(item.jewelData.clusterJewelNotables or { }) do
if not sortOrder[name] then
return positions
end
t_insert(names, name)
end
table.sort(names, function(a, b) return sortOrder[a] < sortOrder[b] end)
local indices = itemLib.getClusterJewelNotableIndices(item.clusterJewel, item.jewelData, #names)
for i, nodeIndex in ipairs(indices) do
local position = "Front"
if item.clusterJewel.size == "Small" then
position = "End"
elseif nodeIndex > item.clusterJewel.totalIndicies / 4 and nodeIndex < item.clusterJewel.totalIndicies * 3 / 4 then
position = "Back"
end
t_insert(positions, { name = names[i], index = nodeIndex, position = position })
end
return positions
end

-- Info table for all types of item influence
itemLib.influenceInfo = {
["all"] = {
Expand Down Expand Up @@ -411,4 +481,4 @@ itemLib.wiki = {
return key == itemLib.wiki.key
end,
triggered = false
}
}
Loading