Skip to content

Commit 5a16f1e

Browse files
github-actions[bot]arthurweryLocalIdentity
authored
[pob1-port] Add ascendancy click switching with connect-path option (#1602)
* Apply changes from PathOfBuildingCommunity/PathOfBuilding#9292 * Fix merge issues --------- Co-authored-by: arthurwery <arthurwery@users.noreply.github.com> Co-authored-by: LocalIdentity <localidentity2@gmail.com>
1 parent 5a6dc39 commit 5a16f1e

4 files changed

Lines changed: 223 additions & 21 deletions

File tree

src/Classes/PassiveSpec.lua

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -680,6 +680,80 @@ function PassiveSpecClass:IsClassConnected(classId)
680680
return false
681681
end
682682

683+
-- Find and allocate the shortest path to connect to a target class's starting node
684+
function PassiveSpecClass:ConnectToClass(classId)
685+
local classData = self.tree.classes[classId]
686+
if not classData then
687+
return false
688+
end
689+
local targetStartNode = self.nodes[classData.startNodeId]
690+
if not targetStartNode then
691+
return false
692+
end
693+
694+
local function isMainTreeNode(node)
695+
return node
696+
and not node.isProxy
697+
and not node.ascendancyName
698+
and node.type ~= "ClassStart"
699+
and node.type ~= "AscendClassStart"
700+
end
701+
702+
local visited = {}
703+
local prev = {}
704+
local queue = { targetStartNode }
705+
visited[targetStartNode] = true
706+
local head = 1
707+
local foundNode = nil
708+
709+
while queue[head] and not foundNode do
710+
local node = queue[head]
711+
head = head + 1
712+
713+
if node ~= targetStartNode and node.alloc and node.connectedToStart and node.type ~= "ClassStart" and node.type ~= "AscendClassStart" then
714+
foundNode = node
715+
break
716+
end
717+
718+
for _, linked in ipairs(node.linked) do
719+
if isMainTreeNode(linked) and not visited[linked] then
720+
visited[linked] = true
721+
prev[linked] = node
722+
queue[#queue + 1] = linked
723+
end
724+
end
725+
end
726+
727+
if not foundNode then
728+
return false
729+
end
730+
731+
local pathBack = {}
732+
local current = foundNode
733+
while current do
734+
t_insert(pathBack, current)
735+
if current == targetStartNode then
736+
break
737+
end
738+
current = prev[current]
739+
end
740+
741+
if pathBack[#pathBack] ~= targetStartNode then
742+
return false
743+
end
744+
745+
local altPath = { pathBack[1] }
746+
for idx = 2, #pathBack - 1 do
747+
altPath[idx] = pathBack[idx]
748+
local node = pathBack[idx]
749+
if not node.alloc then
750+
self:AllocNode(node, altPath)
751+
end
752+
end
753+
754+
return true
755+
end
756+
683757
-- Clear the allocated status of all non-class-start nodes
684758
function PassiveSpecClass:ResetNodes()
685759
for id, node in pairs(self.nodes) do

src/Classes/PassiveTreeView.lua

Lines changed: 103 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -323,19 +323,110 @@ function PassiveTreeViewClass:Draw(build, viewPort, inputEvents)
323323
end
324324
spec:AddUndoState()
325325
build.buildFlag = true
326-
elseif hoverNode.path and not shouldBlockGlobalNodeAllocation(hoverNode) then
327-
-- Handle allocation of unallocated nodes
328-
if hoverNode.isAttribute and not hotkeyPressed then
329-
build.treeTab:ModifyAttributePopup(hoverNode)
330-
else
331-
-- the odd conditional here is so the popup only calls AllocNode inside and to avoid duplicating some code
332-
-- same flow for hotkey attribute and non attribute nodes
333-
if hotkeyPressed then
334-
processAttributeHotkeys(hoverNode.isAttribute)
326+
else
327+
-- Check if the node belongs to a different ascendancy
328+
if hoverNode.ascendancyName then
329+
local isDifferentAscendancy = false
330+
local targetAscendClassId = nil
331+
local targetBaseClassId = nil
332+
local targetBaseClass = nil
333+
334+
-- Check if it's different from current primary or secondary ascendancy
335+
if spec.curAscendClassId == 0 or hoverNode.ascendancyName ~= spec.curAscendClassBaseName then
336+
if not (spec.curSecondaryAscendClass and hoverNode.ascendancyName == spec.curSecondaryAscendClass.id) then
337+
isDifferentAscendancy = true
338+
end
339+
end
340+
341+
if isDifferentAscendancy then
342+
-- First, check if it's in the current class (same-class switching)
343+
for ascendClassId, ascendClass in pairs(spec.curClass.classes) do
344+
if ascendClass.id == hoverNode.ascendancyName then
345+
targetAscendClassId = ascendClassId
346+
break
347+
end
348+
end
349+
350+
if targetAscendClassId then
351+
-- Same-class switching - always allowed
352+
spec:SelectAscendClass(targetAscendClassId)
353+
spec:AddUndoState()
354+
spec:SetWindowTitleWithBuildClass()
355+
build.buildFlag = true
356+
else
357+
-- Cross-class switching - search all classes
358+
for classId, classData in pairs(spec.tree.classes) do
359+
for ascendClassId, ascendClass in pairs(classData.classes) do
360+
if ascendClass.id == hoverNode.ascendancyName then
361+
targetBaseClassId = classId
362+
targetBaseClass = classData
363+
targetAscendClassId = ascendClassId
364+
break
365+
end
366+
end
367+
if targetBaseClassId then break end
368+
end
369+
370+
if targetBaseClassId then
371+
local used = spec:CountAllocNodes()
372+
local clickedAscendNodeId = hoverNode and hoverNode.id
373+
local function allocateClickedAscendancy()
374+
if not clickedAscendNodeId then
375+
return
376+
end
377+
local targetNode = spec.nodes[clickedAscendNodeId]
378+
if targetNode and not targetNode.alloc then
379+
spec:AllocNode(targetNode)
380+
end
381+
end
382+
383+
-- Allow cross-class switching if: no regular points allocated OR tree is connected to target class
384+
if used == 0 or spec:IsClassConnected(targetBaseClassId) then
385+
spec:SelectClass(targetBaseClassId)
386+
spec:SelectAscendClass(targetAscendClassId)
387+
allocateClickedAscendancy()
388+
spec:AddUndoState()
389+
spec:SetWindowTitleWithBuildClass()
390+
build.buildFlag = true
391+
else
392+
-- Tree has points but isn't connected to target class
393+
main:OpenConfirmPopup("Class Change", "Changing class to "..targetBaseClass.name.." will reset your passive tree.\nThis can be avoided by connecting one of the "..targetBaseClass.name.." starting nodes to your tree.", "Continue", function()
394+
spec:SelectClass(targetBaseClassId)
395+
spec:SelectAscendClass(targetAscendClassId)
396+
allocateClickedAscendancy()
397+
spec:AddUndoState()
398+
spec:SetWindowTitleWithBuildClass()
399+
build.buildFlag = true
400+
end, "Connect Path", function()
401+
if spec:ConnectToClass(targetBaseClassId) then
402+
spec:SelectClass(targetBaseClassId)
403+
spec:SelectAscendClass(targetAscendClassId)
404+
allocateClickedAscendancy()
405+
spec:AddUndoState()
406+
spec:SetWindowTitleWithBuildClass()
407+
build.buildFlag = true
408+
end
409+
end)
410+
return
411+
end
412+
end
413+
end
414+
end
415+
end
416+
if hoverNode.path and not shouldBlockGlobalNodeAllocation(hoverNode) then
417+
-- Handle allocation of unallocated nodes
418+
if hoverNode.isAttribute and not hotkeyPressed then
419+
build.treeTab:ModifyAttributePopup(hoverNode)
420+
else
421+
-- the odd conditional here is so the popup only calls AllocNode inside and to avoid duplicating some code
422+
-- same flow for hotkey attribute and non attribute nodes
423+
if hotkeyPressed then
424+
processAttributeHotkeys(hoverNode.isAttribute)
425+
end
426+
spec:AllocNode(hoverNode, self.tracePath and hoverNode == self.tracePath[#self.tracePath] and self.tracePath)
427+
spec:AddUndoState()
428+
build.buildFlag = true
335429
end
336-
spec:AllocNode(hoverNode, self.tracePath and hoverNode == self.tracePath[#self.tracePath] and self.tracePath)
337-
spec:AddUndoState()
338-
build.buildFlag = true
339430
end
340431
end
341432
end

src/Modules/Build.lua

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -241,6 +241,14 @@ function buildMode:Init(dbFileName, buildName, buildXML, convertBuild, importLin
241241
self.spec:SetWindowTitleWithBuildClass()
242242
self.buildFlag = true
243243
self.treeTab.viewer.searchNeedsForceUpdate = true
244+
end, "Connect Path", function()
245+
if self.spec:ConnectToClass(value.classId) then
246+
self.spec:SelectClass(value.classId)
247+
self.spec:AddUndoState()
248+
self.spec:SetWindowTitleWithBuildClass()
249+
self.buildFlag = true
250+
self.treeTab.viewer.searchNeedsForceUpdate = true
251+
end
244252
end)
245253
end
246254
end

src/Modules/Main.lua

Lines changed: 38 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1553,22 +1553,51 @@ function main:OpenMessagePopup(title, msg)
15531553
return self:OpenPopup(m_max(DrawStringWidth(16, "VAR", msg) + 30, 190), 70 + numMsgLines * 16, title, controls, "close")
15541554
end
15551555

1556-
function main:OpenConfirmPopup(title, msg, confirmLabel, onConfirm)
1556+
function main:OpenConfirmPopup(title, msg, confirmLabel, onConfirm, extraLabel, onExtra)
15571557
local controls = { }
15581558
local numMsgLines = 0
15591559
for line in string.gmatch(msg .. "\n", "([^\n]*)\n") do
15601560
t_insert(controls, new("LabelControl", nil, {0, 20 + numMsgLines * 16, 0, 16}, line))
15611561
numMsgLines = numMsgLines + 1
15621562
end
15631563
local confirmWidth = m_max(80, DrawStringWidth(16, "VAR", confirmLabel) + 10)
1564-
controls.confirm = new("ButtonControl", nil, {-5 - m_ceil(confirmWidth/2), 40 + numMsgLines * 16, confirmWidth, 20}, confirmLabel, function()
1565-
main:ClosePopup()
1566-
onConfirm()
1567-
end)
1568-
t_insert(controls, new("ButtonControl", nil, {5 + m_ceil(confirmWidth/2), 40 + numMsgLines * 16, confirmWidth, 20}, "Cancel", function()
1569-
main:ClosePopup()
1570-
end))
1571-
return self:OpenPopup(m_max(DrawStringWidth(16, "VAR", msg) + 30, 190), 70 + numMsgLines * 16, title, controls, "confirm")
1564+
1565+
if extraLabel and onExtra then
1566+
-- Three button layout: Continue (left), Connect Path (center), Cancel (right)
1567+
local extraWidth = m_max(80, DrawStringWidth(16, "VAR", extraLabel) + 10)
1568+
local cancelWidth = 80
1569+
local spacing = 10
1570+
local totalWidth = confirmWidth + extraWidth + cancelWidth + (spacing * 2)
1571+
local leftEdge = -totalWidth / 2
1572+
local buttonY = 40 + numMsgLines * 16
1573+
local function placeButton(width, label, onClick, isConfirm)
1574+
local centerX = leftEdge + width / 2
1575+
local ctrl = new("ButtonControl", nil, {centerX, buttonY, width, 20}, label, function()
1576+
main:ClosePopup()
1577+
onClick()
1578+
end)
1579+
if isConfirm then
1580+
controls.confirm = ctrl
1581+
else
1582+
t_insert(controls, ctrl)
1583+
end
1584+
leftEdge = leftEdge + width + spacing
1585+
end
1586+
placeButton(confirmWidth, confirmLabel, onConfirm, true)
1587+
placeButton(extraWidth, extraLabel, onExtra)
1588+
placeButton(cancelWidth, "Cancel", function() end)
1589+
return self:OpenPopup(m_max(DrawStringWidth(16, "VAR", msg) + 30, totalWidth + 40), 70 + numMsgLines * 16, title, controls, "confirm")
1590+
else
1591+
-- Two button layout (original)
1592+
controls.confirm = new("ButtonControl", nil, {-5 - m_ceil(confirmWidth/2), 40 + numMsgLines * 16, confirmWidth, 20}, confirmLabel, function()
1593+
main:ClosePopup()
1594+
onConfirm()
1595+
end)
1596+
t_insert(controls, new("ButtonControl", nil, {5 + m_ceil(confirmWidth/2), 40 + numMsgLines * 16, confirmWidth, 20}, "Cancel", function()
1597+
main:ClosePopup()
1598+
end))
1599+
return self:OpenPopup(m_max(DrawStringWidth(16, "VAR", msg) + 30, 190), 70 + numMsgLines * 16, title, controls, "confirm")
1600+
end
15721601
end
15731602

15741603
function main:OpenNewFolderPopup(path, onClose)

0 commit comments

Comments
 (0)