Add ascendancy click switching with connect-path option (#9292)
* Add click-to-switch functionality for ascendancies and bloodlines - Click any ascendancy node to automatically switch to that ascendancy - Same-class switching (e.g., Juggernaut → Berserker): always allowed - Cross-class switching: allowed if no regular points allocated or tree is connected to target class - Bloodline switching: always allowed (independent of base class and tree) - Show warning when trying to cross-class switch with points but no connection * Optimizations and auto pathing -Optimizations -Connect path option when trying to swap to unconnected class. * fix: keep existing allocations when auto-connecting classes * Fix pathing + Title update The pathing code didn't check to make sure it was linked to a node that was connected to the start Also changing Ascendancies was not updating the window title with the new ascendancy --------- Co-authored-by: LocalIdentity <localidentity2@gmail.com>
This commit is contained in:
@@ -663,6 +663,80 @@ 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 classData = self.tree.classes[classId]
|
||||
if not classData then
|
||||
return false
|
||||
end
|
||||
local targetStartNode = self.nodes[classData.startNodeId]
|
||||
if not targetStartNode then
|
||||
return false
|
||||
end
|
||||
|
||||
local function isMainTreeNode(node)
|
||||
return node
|
||||
and not node.isProxy
|
||||
and not node.ascendancyName
|
||||
and node.type ~= "ClassStart"
|
||||
and node.type ~= "AscendClassStart"
|
||||
end
|
||||
|
||||
local visited = {}
|
||||
local prev = {}
|
||||
local queue = { targetStartNode }
|
||||
visited[targetStartNode] = true
|
||||
local head = 1
|
||||
local foundNode = nil
|
||||
|
||||
while queue[head] and not foundNode do
|
||||
local node = queue[head]
|
||||
head = head + 1
|
||||
|
||||
if node ~= targetStartNode and node.alloc and node.connectedToStart and node.type ~= "ClassStart" and node.type ~= "AscendClassStart" then
|
||||
foundNode = node
|
||||
break
|
||||
end
|
||||
|
||||
for _, linked in ipairs(node.linked) do
|
||||
if isMainTreeNode(linked) and not visited[linked] then
|
||||
visited[linked] = true
|
||||
prev[linked] = node
|
||||
queue[#queue + 1] = linked
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if not foundNode then
|
||||
return false
|
||||
end
|
||||
|
||||
local pathBack = {}
|
||||
local current = foundNode
|
||||
while current do
|
||||
t_insert(pathBack, current)
|
||||
if current == targetStartNode then
|
||||
break
|
||||
end
|
||||
current = prev[current]
|
||||
end
|
||||
|
||||
if pathBack[#pathBack] ~= targetStartNode then
|
||||
return false
|
||||
end
|
||||
|
||||
local altPath = { pathBack[1] }
|
||||
for idx = 2, #pathBack - 1 do
|
||||
altPath[idx] = pathBack[idx]
|
||||
local node = pathBack[idx]
|
||||
if not node.alloc then
|
||||
self:AllocNode(node, altPath)
|
||||
end
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
-- Clear the allocated status of all non-class-start nodes
|
||||
function PassiveSpecClass:ResetNodes()
|
||||
for id, node in pairs(self.nodes) do
|
||||
|
||||
@@ -277,14 +277,125 @@ 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()
|
||||
spec:SetWindowTitleWithBuildClass()
|
||||
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()
|
||||
spec:SetWindowTitleWithBuildClass()
|
||||
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()
|
||||
local clickedAscendNodeId = hoverNode and hoverNode.id
|
||||
local function allocateClickedAscendancy()
|
||||
if not clickedAscendNodeId then
|
||||
return
|
||||
end
|
||||
local targetNode = spec.nodes[clickedAscendNodeId]
|
||||
if targetNode and not targetNode.alloc then
|
||||
spec:AllocNode(targetNode)
|
||||
end
|
||||
end
|
||||
|
||||
-- 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)
|
||||
allocateClickedAscendancy()
|
||||
spec:AddUndoState()
|
||||
spec:SetWindowTitleWithBuildClass()
|
||||
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)
|
||||
allocateClickedAscendancy()
|
||||
spec:AddUndoState()
|
||||
spec:SetWindowTitleWithBuildClass()
|
||||
build.buildFlag = true
|
||||
end, "Connect Path", function()
|
||||
if spec:ConnectToClass(targetBaseClassId) then
|
||||
spec:SelectClass(targetBaseClassId)
|
||||
spec:SelectAscendClass(targetAscendClassId)
|
||||
allocateClickedAscendancy()
|
||||
spec:AddUndoState()
|
||||
spec:SetWindowTitleWithBuildClass()
|
||||
build.buildFlag = true
|
||||
end
|
||||
end)
|
||||
return
|
||||
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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1538,7 +1538,7 @@ 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
|
||||
@@ -1546,14 +1546,43 @@ function main:OpenConfirmPopup(title, msg, confirmLabel, onConfirm)
|
||||
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)
|
||||
|
||||
Reference in New Issue
Block a user