Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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
46 changes: 46 additions & 0 deletions src/Classes/PassiveSpec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -663,6 +663,52 @@ function PassiveSpecClass:IsClassConnected(classId)
return false
end

-- Find and allocate the shortest path to connect to a target class's starting node
function PassiveSpecClass:ConnectToClass(classId)
local targetStartNode = self.nodes[self.tree.classes[classId].startNodeId]
if not targetStartNode then
return false
end

-- Find all currently allocated nodes
local allocatedNodes = {}
for nodeId, node in pairs(self.allocNodes) do
if node.type ~= "ClassStart" and node.type ~= "AscendClassStart" then
t_insert(allocatedNodes, node)
end
end

-- Find the shortest path from any allocated node to any node connected to the target start
local shortestPath = nil
local shortestLength = 999999

for _, allocNode in ipairs(allocatedNodes) do
-- Check each node connected to the target class start
for _, targetNode in ipairs(targetStartNode.linked) do
if not targetNode.alloc and targetNode.path then
-- Calculate distance from allocated node to this target node
local pathLength = #targetNode.path
if pathLength < shortestLength then
shortestLength = pathLength
shortestPath = targetNode.path
end
end
end
end

-- Allocate the shortest path found
if shortestPath and #shortestPath > 0 then
for _, node in ipairs(shortestPath) do
if not node.alloc then
self:AllocNode(node)
end
end
return true
end

return false
end

-- Clear the allocated status of all non-class-start nodes
function PassiveSpecClass:ResetNodes()
for id, node in pairs(self.nodes) do
Expand Down
108 changes: 100 additions & 8 deletions src/Classes/PassiveTreeView.lua
Original file line number Diff line number Diff line change
Expand Up @@ -277,14 +277,106 @@ function PassiveTreeViewClass:Draw(build, viewPort, inputEvents)
spec:DeallocNode(hoverNode)
spec:AddUndoState()
build.buildFlag = true
elseif hoverNode.path then
-- Node is unallocated and can be allocated, so allocate it
if hoverNode.type == "Mastery" and hoverNode.masteryEffects then
build.treeTab:OpenMasteryPopup(hoverNode, viewPort)
else
spec:AllocNode(hoverNode, self.tracePath and hoverNode == self.tracePath[#self.tracePath] and self.tracePath)
spec:AddUndoState()
build.buildFlag = true
else
-- Check if the node belongs to a different ascendancy
if hoverNode.ascendancyName then
local isDifferentAscendancy = false
local targetAscendClassId = nil
local targetBaseClassId = nil
local targetBaseClass = nil

-- Check if this is a bloodline (secondary ascendancy) node
if hoverNode.isBloodline and spec.tree.alternate_ascendancies then
local isDifferentBloodline = not spec.curSecondaryAscendClass or hoverNode.ascendancyName ~= spec.curSecondaryAscendClass.id

if isDifferentBloodline then
-- Find the bloodline in alternate_ascendancies
for bloodlineId, bloodlineData in pairs(spec.tree.alternate_ascendancies) do
if bloodlineData.id == hoverNode.ascendancyName then
spec:SelectSecondaryAscendClass(bloodlineId)
spec:AddUndoState()
build.buildFlag = true
break
end
end
end
else
-- Regular ascendancy node (not bloodline)
-- Check if it's different from current primary or secondary ascendancy
if spec.curAscendClassId == 0 or hoverNode.ascendancyName ~= spec.curAscendClassBaseName then
if not (spec.curSecondaryAscendClass and hoverNode.ascendancyName == spec.curSecondaryAscendClass.id) then
isDifferentAscendancy = true
end
end

if isDifferentAscendancy then
-- First, check if it's in the current class (same-class switching)
for ascendClassId, ascendClass in pairs(spec.curClass.classes) do
if ascendClass.id == hoverNode.ascendancyName then
targetAscendClassId = ascendClassId
break
end
end

if targetAscendClassId then
-- Same-class switching - always allowed
spec:SelectAscendClass(targetAscendClassId)
spec:AddUndoState()
build.buildFlag = true
else
-- Cross-class switching - search all classes
for classId, classData in pairs(spec.tree.classes) do
for ascendClassId, ascendClass in pairs(classData.classes) do
if ascendClass.id == hoverNode.ascendancyName then
targetBaseClassId = classId
targetBaseClass = classData
targetAscendClassId = ascendClassId
break
end
end
if targetBaseClassId then break end
end

if targetBaseClassId then
local used = spec:CountAllocNodes()

-- Allow cross-class switching if: no regular points allocated OR tree is connected to target class
if used == 0 or spec:IsClassConnected(targetBaseClassId) then
spec:SelectClass(targetBaseClassId)
spec:SelectAscendClass(targetAscendClassId)
spec:AddUndoState()
build.buildFlag = true
else
-- Tree has points but isn't connected to target class
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()
spec:SelectClass(targetBaseClassId)
spec:SelectAscendClass(targetAscendClassId)
spec:AddUndoState()
build.buildFlag = true
end, "Connect Path", function()
if spec:ConnectToClass(targetBaseClassId) then
spec:SelectClass(targetBaseClassId)
spec:SelectAscendClass(targetAscendClassId)
spec:AddUndoState()
build.buildFlag = true
end
end)
end
end
end
end
end
end

-- Normal node allocation (non-ascendancy or same ascendancy)
if hoverNode.path and not hoverNode.alloc then
if hoverNode.type == "Mastery" and hoverNode.masteryEffects then
build.treeTab:OpenMasteryPopup(hoverNode, viewPort)
else
spec:AllocNode(hoverNode, self.tracePath and hoverNode == self.tracePath[#self.tracePath] and self.tracePath)
spec:AddUndoState()
build.buildFlag = true
end
end
end
end
Expand Down
7 changes: 7 additions & 0 deletions src/Modules/Build.lua
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,13 @@ function buildMode:Init(dbFileName, buildName, buildXML, convertBuild, importLin
self.spec:AddUndoState()
self.spec:SetWindowTitleWithBuildClass()
self.buildFlag = true
end, "Connect Path", function()
if self.spec:ConnectToClass(value.classId) then
self.spec:SelectClass(value.classId)
self.spec:AddUndoState()
self.spec:SetWindowTitleWithBuildClass()
self.buildFlag = true
end
end)
end
end
Expand Down
47 changes: 38 additions & 9 deletions src/Modules/Main.lua
Original file line number Diff line number Diff line change
Expand Up @@ -1507,22 +1507,51 @@ function main:OpenMessagePopup(title, msg)
return self:OpenPopup(m_max(DrawStringWidth(16, "VAR", msg) + 30, 190), 70 + numMsgLines * 16, title, controls, "close")
end

function main:OpenConfirmPopup(title, msg, confirmLabel, onConfirm)
function main:OpenConfirmPopup(title, msg, confirmLabel, onConfirm, extraLabel, onExtra)
local controls = { }
local numMsgLines = 0
for line in string.gmatch(msg .. "\n", "([^\n]*)\n") do
t_insert(controls, new("LabelControl", nil, {0, 20 + numMsgLines * 16, 0, 16}, line))
numMsgLines = numMsgLines + 1
end
local confirmWidth = m_max(80, DrawStringWidth(16, "VAR", confirmLabel) + 10)
controls.confirm = new("ButtonControl", nil, {-5 - m_ceil(confirmWidth/2), 40 + numMsgLines * 16, confirmWidth, 20}, confirmLabel, function()
main:ClosePopup()
onConfirm()
end)
t_insert(controls, new("ButtonControl", nil, {5 + m_ceil(confirmWidth/2), 40 + numMsgLines * 16, confirmWidth, 20}, "Cancel", function()
main:ClosePopup()
end))
return self:OpenPopup(m_max(DrawStringWidth(16, "VAR", msg) + 30, 190), 70 + numMsgLines * 16, title, controls, "confirm")

if extraLabel and onExtra then
-- Three button layout: Continue (left), Connect Path (center), Cancel (right)
local extraWidth = m_max(80, DrawStringWidth(16, "VAR", extraLabel) + 10)
local cancelWidth = 80
local spacing = 10
local totalWidth = confirmWidth + extraWidth + cancelWidth + (spacing * 2)
local leftEdge = -totalWidth / 2
local buttonY = 40 + numMsgLines * 16
local function placeButton(width, label, onClick, isConfirm)
local centerX = leftEdge + width / 2
local ctrl = new("ButtonControl", nil, {centerX, buttonY, width, 20}, label, function()
main:ClosePopup()
onClick()
end)
if isConfirm then
controls.confirm = ctrl
else
t_insert(controls, ctrl)
end
leftEdge = leftEdge + width + spacing
end
placeButton(confirmWidth, confirmLabel, onConfirm, true)
placeButton(extraWidth, extraLabel, onExtra)
placeButton(cancelWidth, "Cancel", function() end)
return self:OpenPopup(m_max(DrawStringWidth(16, "VAR", msg) + 30, totalWidth + 40), 70 + numMsgLines * 16, title, controls, "confirm")
else
-- Two button layout (original)
controls.confirm = new("ButtonControl", nil, {-5 - m_ceil(confirmWidth/2), 40 + numMsgLines * 16, confirmWidth, 20}, confirmLabel, function()
main:ClosePopup()
onConfirm()
end)
t_insert(controls, new("ButtonControl", nil, {5 + m_ceil(confirmWidth/2), 40 + numMsgLines * 16, confirmWidth, 20}, "Cancel", function()
main:ClosePopup()
end))
return self:OpenPopup(m_max(DrawStringWidth(16, "VAR", msg) + 30, 190), 70 + numMsgLines * 16, title, controls, "confirm")
end
end

function main:OpenNewFolderPopup(path, onClose)
Expand Down
Loading