From 2e538251a7fcf587ac3a6540dfbfce50ba0ae317 Mon Sep 17 00:00:00 2001 From: cupkax Date: Sat, 15 Aug 2026 13:55:47 +1000 Subject: [PATCH 1/4] Add option to allow auto-socketing timeless jewel --- src/Classes/TimelessJewelListControl.lua | 21 +++++++++++++++++++-- src/Classes/TreeTab.lua | 13 +++++++++++++ 2 files changed, 32 insertions(+), 2 deletions(-) diff --git a/src/Classes/TimelessJewelListControl.lua b/src/Classes/TimelessJewelListControl.lua index eb494dfe63..67aea0a0c0 100644 --- a/src/Classes/TimelessJewelListControl.lua +++ b/src/Classes/TimelessJewelListControl.lua @@ -298,6 +298,13 @@ function TimelessJewelListControlClass:AddValueTooltip(tooltip, index, data) if not self.noTooltip then if self.list[index].label:match("B2B2B2") == nil then tooltip:AddLine(16, "^7Double click to add this jewel to your build.") + if self.build.timelessData.socketAllocate then + if socket and self.build.spec.allocNodes[socketId] then + tooltip:AddLine(16, "^7It will be socketed into " .. (data.socketLabel or self.sharedList.socket.label or socketId) .. ".") + else + tooltip:AddLine(16, colorCodes.WARNING .. "That jewel socket is not allocated, so the jewel will only be added to your items.") + end + end else tooltip:AddLine(16, "^7" .. self.sharedList.type.label .. " " .. data.seed .. " was successfully added to your build.") end @@ -328,8 +335,18 @@ end function TimelessJewelListControlClass:OnSelClick(index, data, doubleClick) if doubleClick and self.list[index].label:match("B2B2B2") == nil then local item = self:GetJewelItem(data) - self.build.itemsTab:AddItem(item, true) - self.build.itemsTab:PopulateSlots() + local itemsTab = self.build.itemsTab + itemsTab:AddItem(item, true) + if self.build.timelessData.socketAllocate then + local socketId = data.socketId or self.sharedList.socket.id + local socketControl = socketId ~= -1 and itemsTab.sockets[socketId] + if socketControl and self.build.spec.allocNodes[socketId] and itemsTab:IsItemValidForSlot(item, socketControl.slotName) then + socketControl:SetSelItemId(item.id) + itemsTab:AddUndoState() + self.build.buildFlag = true + end + end + itemsTab:PopulateSlots() self.list[index].label = "^xB2B2B2" .. self.list[index].label end end diff --git a/src/Classes/TreeTab.lua b/src/Classes/TreeTab.lua index 06f66f38df..5d04e342b6 100644 --- a/src/Classes/TreeTab.lua +++ b/src/Classes/TreeTab.lua @@ -1670,6 +1670,19 @@ function TreeTabClass:FindTimelessJewel() end controls.socketFilter.state = timelessData.socketFilter + -- sits on the same row as the socket filter, right of the node distance slider it can show + controls.socketAllocate = new("CheckBoxControl"):CheckBoxControl({"LEFT", controls.socketFilter, "RIGHT"}, {165, 0, rowHeight}, nil, function(value) + timelessData.socketAllocate = value + end) + controls.socketAllocateLabel = new("LabelControl"):LabelControl({"RIGHT", controls.socketAllocate, "LEFT"}, {-labelSpacing, 0, 0, labelHeight}, "^7Socket Jewel:") + controls.socketAllocate.tooltipFunc = function(tooltip, mode, index, value) + tooltip:Clear() + tooltip:AddLine(16, "^7Double clicking a result also equips the jewel in its jewel socket.") + tooltip:AddLine(16, "^7The socket must be allocated on your current tree; if it isn't, the jewel is only added to your item list.") + tooltip:AddLine(16, "^7A jewel already in that socket is replaced.") + end + controls.socketAllocate.state = timelessData.socketAllocate + -- Protect notables that must not be replaced by Militant Faith or Reclaimed Malevolence. controls.protectAllocatedLabel = new("LabelControl"):LabelControl({ "TOPLEFT", nil, "TOPLEFT" }, { 15, From 82040c3af760621f5a378c6b3430266bc298614c Mon Sep 17 00:00:00 2001 From: cupkax Date: Mon, 24 Aug 2026 09:54:56 +1000 Subject: [PATCH 2/4] Persist timeless jewel auto-socket option Save socketAllocate to the TimelessData element and restore it on load, and mark the build modified when it is toggled, matching socketFilter. The attribute is only written when the option is enabled. --- .../System/TestTimelessJewelSettings_spec.lua | 25 +++++++++++++++++++ src/Classes/TreeTab.lua | 1 + src/Modules/Build.lua | 2 ++ 3 files changed, 28 insertions(+) create mode 100644 spec/System/TestTimelessJewelSettings_spec.lua diff --git a/spec/System/TestTimelessJewelSettings_spec.lua b/spec/System/TestTimelessJewelSettings_spec.lua new file mode 100644 index 0000000000..e3caf916d0 --- /dev/null +++ b/spec/System/TestTimelessJewelSettings_spec.lua @@ -0,0 +1,25 @@ +describe("TestTimelessJewelSettings", function() + before_each(function() + newBuild() + end) + + -- The finder's "Socket Jewel" toggle lives in timelessData, which only reaches the + -- build XML through the TimelessData element, so cover the save/load round trip. + it("round trips socketAllocate through the build XML", function() + build.timelessData.socketAllocate = true + local xmlText = build:SaveDB("code") + assert.is_truthy(xmlText:match('socketAllocate="true"')) + + loadBuildFromXML(xmlText) + assert.is_true(build.timelessData.socketAllocate) + end) + + it("omits socketAllocate from the build XML while unticked", function() + build.timelessData.socketAllocate = false + local xmlText = build:SaveDB("code") + assert.is_nil(xmlText:match("socketAllocate")) + + loadBuildFromXML(xmlText) + assert.is_false(build.timelessData.socketAllocate) + end) +end) diff --git a/src/Classes/TreeTab.lua b/src/Classes/TreeTab.lua index 5d04e342b6..2282347b4d 100644 --- a/src/Classes/TreeTab.lua +++ b/src/Classes/TreeTab.lua @@ -1673,6 +1673,7 @@ function TreeTabClass:FindTimelessJewel() -- sits on the same row as the socket filter, right of the node distance slider it can show controls.socketAllocate = new("CheckBoxControl"):CheckBoxControl({"LEFT", controls.socketFilter, "RIGHT"}, {165, 0, rowHeight}, nil, function(value) timelessData.socketAllocate = value + self.build.modFlag = true end) controls.socketAllocateLabel = new("LabelControl"):LabelControl({"RIGHT", controls.socketAllocate, "LEFT"}, {-labelSpacing, 0, 0, labelHeight}, "^7Socket Jewel:") controls.socketAllocate.tooltipFunc = function(tooltip, mode, index, value) diff --git a/src/Modules/Build.lua b/src/Modules/Build.lua index d080d84c90..5bde4c868a 100644 --- a/src/Modules/Build.lua +++ b/src/Modules/Build.lua @@ -1005,6 +1005,7 @@ function buildMode:Load(xml, fileName) idx = tonumber(child.attrib.fallbackWeightModeIdx) } self.timelessData.socketFilter = child.attrib.socketFilter == "true" + self.timelessData.socketAllocate = child.attrib.socketAllocate == "true" self.timelessData.socketFilterDistance = tonumber(child.attrib.socketFilterDistance) or 0 self.timelessData.searchList = child.attrib.searchList self.timelessData.searchListFallback = child.attrib.searchListFallback @@ -1086,6 +1087,7 @@ function buildMode:Save(xml) jewelSocketId = next(self.timelessData.jewelSocket) and tostring(self.timelessData.jewelSocket.id), fallbackWeightModeIdx = next(self.timelessData.fallbackWeightMode) and tostring(self.timelessData.fallbackWeightMode.idx), socketFilter = self.timelessData.socketFilter and "true", + socketAllocate = self.timelessData.socketAllocate and "true", socketFilterDistance = self.timelessData.socketFilterDistance and tostring(self.timelessData.socketFilterDistance), searchList = self.timelessData.searchList and tostring(self.timelessData.searchList), searchListFallback = self.timelessData.searchListFallback and tostring(self.timelessData.searchListFallback) From 52850486a498d3ef13c0c9ebc73a426c726fd091 Mon Sep 17 00:00:00 2001 From: cupkax Date: Mon, 24 Aug 2026 09:55:25 +1000 Subject: [PATCH 3/4] Give the auto-socket option its own row in the jewel finder --- src/Classes/TreeTab.lua | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/Classes/TreeTab.lua b/src/Classes/TreeTab.lua index 2282347b4d..842b3b4e09 100644 --- a/src/Classes/TreeTab.lua +++ b/src/Classes/TreeTab.lua @@ -1670,8 +1670,8 @@ function TreeTabClass:FindTimelessJewel() end controls.socketFilter.state = timelessData.socketFilter - -- sits on the same row as the socket filter, right of the node distance slider it can show - controls.socketAllocate = new("CheckBoxControl"):CheckBoxControl({"LEFT", controls.socketFilter, "RIGHT"}, {165, 0, rowHeight}, nil, function(value) + -- own row under the socket filter, so it never collides with the node distance slider + controls.socketAllocate = new("CheckBoxControl"):CheckBoxControl({"TOPLEFT", controls.socketFilter, "BOTTOMLEFT"}, {0, rowSpacing, rowHeight}, nil, function(value) timelessData.socketAllocate = value self.build.modFlag = true end) @@ -1764,7 +1764,7 @@ function TreeTabClass:FindTimelessJewel() local scrollWheelSpeedTbl2 = { ["SHIFT"] = 0.2, ["CTRL"] = 0.002, ["DEFAULT"] = 0.02 } local nodeSliderStatLabel = "None" - controls.nodeSlider = new("SliderControl"):SliderControl({"TOPLEFT", controls.socketFilter, "BOTTOMLEFT"}, {0, rowSpacing, 200, rowHeight}, function(value) + controls.nodeSlider = new("SliderControl"):SliderControl({"TOPLEFT", controls.socketAllocate, "BOTTOMLEFT"}, {0, rowSpacing, 200, rowHeight}, function(value) controls.nodeSliderValue.label = s_format("^7%.3f", value * 10) parseSearchList(1, controls.searchListFallback and controls.searchListFallback.shown or false) end, scrollWheelSpeedTbl) @@ -2881,6 +2881,8 @@ function TreeTabClass:FindTimelessJewel() end end) - local panelHeight = 565 + -- the settings column is top anchored and the results/trade block bottom anchored, + -- so the panel grows by a row for every row the settings column gains + local panelHeight = 565 + rowSpacing + rowHeight main:OpenPopup(panelWidth, panelHeight, "Find a Timeless Jewel", controls) end From b66f0145f59004cf436968e02314aa6bde9a661c Mon Sep 17 00:00:00 2001 From: LocalIdentity Date: Wed, 26 Aug 2026 21:46:46 +1000 Subject: [PATCH 4/4] Fix undo state --- .../System/TestTimelessJewelSettings_spec.lua | 27 +++++++++++++++++++ src/Classes/TimelessJewelListControl.lua | 2 +- 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/spec/System/TestTimelessJewelSettings_spec.lua b/spec/System/TestTimelessJewelSettings_spec.lua index e3caf916d0..50858ed40b 100644 --- a/spec/System/TestTimelessJewelSettings_spec.lua +++ b/spec/System/TestTimelessJewelSettings_spec.lua @@ -22,4 +22,31 @@ describe("TestTimelessJewelSettings", function() loadBuildFromXML(xmlText) assert.is_false(build.timelessData.socketAllocate) end) + + it("records the item addition when the target socket is unallocated", function() + local socketId, socketControl = next(build.itemsTab.sockets) + local result = { label = "10000:", seed = 10000, total = 1 } + build.spec.allocNodes[socketId] = nil + build.timelessData.socketAllocate = true + build.timelessData.sharedResults = { + type = { id = 2, label = "Lethal Pride" }, + conqueror = { id = 1 }, + socket = { id = socketId, label = "Socket" }, + desiredNodes = { }, + } + build.timelessData.searchResults = { result } + local control = new("TimelessJewelListControl"):TimelessJewelListControl(nil, { 0, 0, 300, 100 }, build) + local initialItemCount = #build.itemsTab.itemOrderList + build.itemsTab:ResetUndo() + build.itemsTab.modFlag = false + + control:OnSelClick(1, result, true) + + assert.are.equal(initialItemCount + 1, #build.itemsTab.itemOrderList) + assert.are.equal(0, socketControl.selItemId) + assert.is_true(build.itemsTab.modFlag) + + build.itemsTab:Undo() + assert.are.equal(initialItemCount, #build.itemsTab.itemOrderList) + end) end) diff --git a/src/Classes/TimelessJewelListControl.lua b/src/Classes/TimelessJewelListControl.lua index 67aea0a0c0..8b82e3b2d9 100644 --- a/src/Classes/TimelessJewelListControl.lua +++ b/src/Classes/TimelessJewelListControl.lua @@ -342,11 +342,11 @@ function TimelessJewelListControlClass:OnSelClick(index, data, doubleClick) local socketControl = socketId ~= -1 and itemsTab.sockets[socketId] if socketControl and self.build.spec.allocNodes[socketId] and itemsTab:IsItemValidForSlot(item, socketControl.slotName) then socketControl:SetSelItemId(item.id) - itemsTab:AddUndoState() self.build.buildFlag = true end end itemsTab:PopulateSlots() + itemsTab:AddUndoState() self.list[index].label = "^xB2B2B2" .. self.list[index].label end end