From 54a43e578cd79ea750918d0a52e5ba237ace7d7e Mon Sep 17 00:00:00 2001 From: Andrew Devlin Date: Sun, 1 Mar 2026 15:09:05 -0800 Subject: [PATCH 1/2] Adds Split Personality path connector coloring and an option menu toggle to disable it. Signed-off-by: Andrew Devlin --- src/Classes/PassiveSpec.lua | 45 +++++++++++++++++++++++++++++++++ src/Classes/PassiveTreeView.lua | 44 ++++++++++++++++++++++++++------ src/Data/Global.lua | 3 ++- src/Modules/Main.lua | 13 ++++++++++ 4 files changed, 96 insertions(+), 9 deletions(-) diff --git a/src/Classes/PassiveSpec.lua b/src/Classes/PassiveSpec.lua index 937be5065f..f9e9ee8b3e 100644 --- a/src/Classes/PassiveSpec.lua +++ b/src/Classes/PassiveSpec.lua @@ -950,6 +950,51 @@ function PassiveSpecClass:SetNodeDistanceToClassStart(root) end end +-- Determine the shortest path from the given node to the class' start +-- Only allocated nodes can be traversed +function PassiveSpecClass:GetShortestPathToClassStart(rootId) + local root = self.nodes[rootId] + if not root or not root.alloc or not root.connectedToStart then + return nil + end + + -- Stop once the current class' starting node is reached + local targetNodeId = self.curClass.startNodeId + + local parent = { } + parent[root.id] = nil + + local queue = { root } + local o, i = 1, 2 -- Out, in + while o < i do + local node = queue[o] + o = o + 1 + -- Iterate through all nodes that are connected to this one + for _, other in ipairs(node.linked) do + -- If this connected node is the correct class start node, then construct and return the path + if other.id == targetNodeId then + local path = { [root.id] = true, [other.id] = true } + local cur = node + while cur do + path[cur.id] = true + cur = parent[cur.id] + end + return path + end + + -- Otherwise, record the parent of this node if it hasn't already been visited + if other.alloc and node.type ~= "Mastery" and other.type ~= "ClassStart" and other.type ~= "AscendClassStart" and not parent[other.id] and other.id ~= root.id then + parent[other.id] = node + + -- Add the other node to the end of the queue + queue[i] = other + i = i + 1 + end + end + end + return nil +end + function PassiveSpecClass:AddMasteryEffectOptionsToNode(node) node.sd = {} if node.masteryEffects ~= nil and #node.masteryEffects > 0 then diff --git a/src/Classes/PassiveTreeView.lua b/src/Classes/PassiveTreeView.lua index 77c9f21fc3..2809b731f0 100644 --- a/src/Classes/PassiveTreeView.lua +++ b/src/Classes/PassiveTreeView.lua @@ -269,6 +269,22 @@ function PassiveTreeViewClass:Draw(build, viewPort, inputEvents) end end + -- Split Personality highlight + local splitPersonalityPath = { } + if main.showSplitPersonalityPath then + for nodeId, itemId in pairs(spec.jewels) do + local item = build.itemsTab.items[itemId] + if item and item.jewelData and item.jewelData.jewelIncEffectFromClassStart then + local path = spec:GetShortestPathToClassStart(nodeId) + if path then + for id in pairs(path) do + splitPersonalityPath[id] = true + end + end + end + end + end + if treeClick == "LEFT" then if hoverNode then -- User left-clicked on a node @@ -503,7 +519,13 @@ function PassiveTreeViewClass:Draw(build, viewPort, inputEvents) end local function renderConnector(connector) local node1, node2 = spec.nodes[connector.nodeId1], spec.nodes[connector.nodeId2] - setConnectorColor(1, 1, 1) + local connectorDefaultColor = "^xFFFFFF" + + if splitPersonalityPath[node1.id] and splitPersonalityPath[node2.id] then + connectorDefaultColor = colorCodes.SPLITPERSONALITY + end + + setConnectorColor(connectorDefaultColor) local state = getState(node1, node2) local baseState = state if self.compareSpec then @@ -589,6 +611,12 @@ function PassiveTreeViewClass:Draw(build, viewPort, inputEvents) local base, overlay, effect local isAlloc = node.alloc or build.calcsTab.mainEnv.grantedPassives[nodeId] or (compareNode and compareNode.alloc) + local nodeDefaultColor = "^xFFFFFF" + + if splitPersonalityPath[node.id] then + nodeDefaultColor = colorCodes.SPLITPERSONALITY + end + SetDrawLayer(nil, 25) if node.type == "ClassStart" then overlay = isAlloc and node.startArt or "PSStartNodeBackgroundInactive" @@ -734,11 +762,11 @@ function PassiveTreeViewClass:Draw(build, viewPort, inputEvents) -- Node is a mastery, both have it allocated, but mastery changed, color it blue SetDrawColor(0, 0, 1) else - -- Both have or both have not, use white - SetDrawColor(1, 1, 1) + -- Both have or both have not + SetDrawColor(nodeDefaultColor) end else - SetDrawColor(1, 1, 1) + SetDrawColor(nodeDefaultColor) end end elseif launch.devModeAlt then @@ -762,11 +790,11 @@ function PassiveTreeViewClass:Draw(build, viewPort, inputEvents) -- Node is a mastery, both have it allocated, but mastery changed, color it blue SetDrawColor(0, 0, 1) else - -- Both have or both have not, use white - SetDrawColor(1, 1, 1) - end + -- Both have or both have not + SetDrawColor(nodeDefaultColor) + end else - SetDrawColor(1, 1, 1) + SetDrawColor(nodeDefaultColor) end end diff --git a/src/Data/Global.lua b/src/Data/Global.lua index a264f13dfa..a8cc44731a 100644 --- a/src/Data/Global.lua +++ b/src/Data/Global.lua @@ -56,7 +56,8 @@ colorCodes = { BRITTLEBG = "^x00122b", SAPBG = "^x261500", SCOURGE = "^xFF6E25", - CRUCIBLE = "^xFFA500" + CRUCIBLE = "^xFFA500", + SPLITPERSONALITY = "^xFFD62A" } colorCodes.STRENGTH = colorCodes.MARAUDER colorCodes.DEXTERITY = colorCodes.RANGER diff --git a/src/Modules/Main.lua b/src/Modules/Main.lua index c329acc5ec..693d73b87a 100644 --- a/src/Modules/Main.lua +++ b/src/Modules/Main.lua @@ -115,6 +115,7 @@ function main:Init() self.showAnimations = true self.showAllItemAffixes = true self.errorReadingSettings = false + self.showSplitPersonalityPath = true if not SetDPIScaleOverridePercent then SetDPIScaleOverridePercent = function(scale) end end @@ -660,6 +661,9 @@ function main:LoadSettings(ignoreBuild) self.dpiScaleOverridePercent = tonumber(node.attrib.dpiScaleOverridePercent) or 0 SetDPIScaleOverridePercent(self.dpiScaleOverridePercent) end + if node.attrib.showSplitPersonalityPath then + self.showSplitPersonalityPath = node.attrib.showSplitPersonalityPath == "true" + end end end end @@ -791,6 +795,7 @@ function main:SaveSettings() showAnimations = tostring(self.showAnimations), showAllItemAffixes = tostring(self.showAllItemAffixes), dpiScaleOverridePercent = tostring(self.dpiScaleOverridePercent), + showSplitPersonalityPath = tostring(self.showSplitPersonalityPath), } }) local res, errMsg = common.xml.SaveXMLFile(setXML, self.userPath.."Settings.xml") if not res then @@ -1079,6 +1084,12 @@ function main:OpenOptionsPopup() controls.invertSliderScrollDirection.tooltipText = "Default scroll direction is:\nScroll Up = Move right\nScroll Down = Move left" controls.invertSliderScrollDirection.state = self.invertSliderScrollDirection + nextRow() + controls.showSplitPersonalityPath = new("CheckBoxControl", { "TOPLEFT", nil, "TOPLEFT" }, { defaultLabelPlacementX, currentY, 20 }, "^7Show Split Personality paths:", function(state) + self.showSplitPersonalityPath = state + end) + controls.showSplitPersonalityPath.state = self.showSplitPersonalityPath + if launch.devMode then nextRow() controls.disableDevAutoSave = new("CheckBoxControl", { "TOPLEFT", nil, "TOPLEFT" }, { defaultLabelPlacementX, currentY, 20 }, "^7Disable Dev AutoSave:", function(state) @@ -1118,6 +1129,7 @@ function main:OpenOptionsPopup() local initialShowAnimations = self.showAnimations local initialShowAllItemAffixes = self.showAllItemAffixes local initialDpiScaleOverridePercent = self.dpiScaleOverridePercent + local initialShowSplitPersonalityPath = self.showSplitPersonalityPath -- last line with buttons has more spacing nextRow(1.5) @@ -1175,6 +1187,7 @@ function main:OpenOptionsPopup() self.showAllItemAffixes = initialShowAllItemAffixes self.dpiScaleOverridePercent = initialDpiScaleOverridePercent SetDPIScaleOverridePercent(self.dpiScaleOverridePercent) + self.showSplitPersonalityPath = initialShowSplitPersonalityPath main:ClosePopup() end) nextRow(1.5) From 37f93e8054a9f35af4e431f3cb9f7980cbad575e Mon Sep 17 00:00:00 2001 From: LocalIdentity Date: Tue, 10 Mar 2026 02:58:06 +1100 Subject: [PATCH 2/2] Remove config and cache path Removed the config as I don't see a reason for it to exists The function was previously called on every frame in PassiveTreeView but is now cached after each tree change and that cache is used instead --- src/Classes/PassiveSpec.lua | 21 +++++++++++++++++++++ src/Classes/PassiveTreeView.lua | 15 +-------------- src/Modules/Main.lua | 13 ------------- 3 files changed, 22 insertions(+), 27 deletions(-) diff --git a/src/Classes/PassiveSpec.lua b/src/Classes/PassiveSpec.lua index ac6050d2a0..c67b7ac27e 100644 --- a/src/Classes/PassiveSpec.lua +++ b/src/Classes/PassiveSpec.lua @@ -81,6 +81,9 @@ function PassiveSpecClass:Init(treeVersion, convert) -- Keys are node IDs, values are the replacement node self.hashOverrides = { } + + -- Cached highlight path for Split Personality jewels + self.splitPersonalityPath = { } end function PassiveSpecClass:Load(xml, dbFileName) @@ -995,6 +998,22 @@ function PassiveSpecClass:GetShortestPathToClassStart(rootId) return nil end +function PassiveSpecClass:BuildSplitPersonalityPath() + local splitPersonalityPath = { } + for nodeId, itemId in pairs(self.jewels) do + local item = self.build.itemsTab.items[itemId] + if item and item.jewelData and item.jewelData.jewelIncEffectFromClassStart then + local path = self:GetShortestPathToClassStart(nodeId) + if path then + for id in pairs(path) do + splitPersonalityPath[id] = true + end + end + end + end + self.splitPersonalityPath = splitPersonalityPath +end + function PassiveSpecClass:AddMasteryEffectOptionsToNode(node) node.sd = {} if node.masteryEffects ~= nil and #node.masteryEffects > 0 then @@ -1515,6 +1534,8 @@ function PassiveSpecClass:BuildAllDependsAndPaths() end end end + + self:BuildSplitPersonalityPath() end function PassiveSpecClass:ReplaceNode(old, newNode) diff --git a/src/Classes/PassiveTreeView.lua b/src/Classes/PassiveTreeView.lua index 450e4a41a2..3370a436e2 100644 --- a/src/Classes/PassiveTreeView.lua +++ b/src/Classes/PassiveTreeView.lua @@ -274,20 +274,7 @@ function PassiveTreeViewClass:Draw(build, viewPort, inputEvents) end -- Split Personality highlight - local splitPersonalityPath = { } - if main.showSplitPersonalityPath then - for nodeId, itemId in pairs(spec.jewels) do - local item = build.itemsTab.items[itemId] - if item and item.jewelData and item.jewelData.jewelIncEffectFromClassStart then - local path = spec:GetShortestPathToClassStart(nodeId) - if path then - for id in pairs(path) do - splitPersonalityPath[id] = true - end - end - end - end - end + local splitPersonalityPath = spec.splitPersonalityPath or { } if treeClick == "LEFT" then if hoverNode then diff --git a/src/Modules/Main.lua b/src/Modules/Main.lua index 386f4e9c49..49f78e6bbc 100644 --- a/src/Modules/Main.lua +++ b/src/Modules/Main.lua @@ -119,7 +119,6 @@ function main:Init() self.showAnimations = true self.showAllItemAffixes = true self.errorReadingSettings = false - self.showSplitPersonalityPath = true if not SetDPIScaleOverridePercent then SetDPIScaleOverridePercent = function(scale) end end @@ -630,9 +629,6 @@ function main:LoadSettings(ignoreBuild) self.dpiScaleOverridePercent = tonumber(node.attrib.dpiScaleOverridePercent) or 0 SetDPIScaleOverridePercent(self.dpiScaleOverridePercent) end - if node.attrib.showSplitPersonalityPath then - self.showSplitPersonalityPath = node.attrib.showSplitPersonalityPath == "true" - end end end end @@ -765,7 +761,6 @@ function main:SaveSettings() showAnimations = tostring(self.showAnimations), showAllItemAffixes = tostring(self.showAllItemAffixes), dpiScaleOverridePercent = tostring(self.dpiScaleOverridePercent), - showSplitPersonalityPath = tostring(self.showSplitPersonalityPath), } }) local res, errMsg = common.xml.SaveXMLFile(setXML, self.userPath.."Settings.xml") if not res then @@ -1061,12 +1056,6 @@ function main:OpenOptionsPopup() controls.invertSliderScrollDirection.tooltipText = "Default scroll direction is:\nScroll Up = Move right\nScroll Down = Move left" controls.invertSliderScrollDirection.state = self.invertSliderScrollDirection - nextRow() - controls.showSplitPersonalityPath = new("CheckBoxControl", { "TOPLEFT", nil, "TOPLEFT" }, { defaultLabelPlacementX, currentY, 20 }, "^7Show Split Personality paths:", function(state) - self.showSplitPersonalityPath = state - end) - controls.showSplitPersonalityPath.state = self.showSplitPersonalityPath - if launch.devMode then nextRow() controls.disableDevAutoSave = new("CheckBoxControl", { "TOPLEFT", nil, "TOPLEFT" }, { defaultLabelPlacementX, currentY, 20 }, "^7Disable Dev AutoSave:", function(state) @@ -1107,7 +1096,6 @@ function main:OpenOptionsPopup() local initialShowAnimations = self.showAnimations local initialShowAllItemAffixes = self.showAllItemAffixes local initialDpiScaleOverridePercent = self.dpiScaleOverridePercent - local initialShowSplitPersonalityPath = self.showSplitPersonalityPath -- last line with buttons has more spacing nextRow(1.5) @@ -1166,7 +1154,6 @@ function main:OpenOptionsPopup() self.showAllItemAffixes = initialShowAllItemAffixes self.dpiScaleOverridePercent = initialDpiScaleOverridePercent SetDPIScaleOverridePercent(self.dpiScaleOverridePercent) - self.showSplitPersonalityPath = initialShowSplitPersonalityPath main:ClosePopup() end) nextRow(1.5)