@@ -168,11 +168,11 @@ function PassiveTreeViewClass:Draw(build, viewPort, inputEvents)
168168 local offsetY = self .zoomY + viewPort .y + viewPort .height / 2
169169 local function treeToScreen (x , y )
170170 return x * scale + offsetX ,
171- y * scale + offsetY
171+ y * scale + offsetY
172172 end
173173 local function screenToTree (x , y )
174174 return (x - offsetX ) / scale ,
175- (y - offsetY ) / scale
175+ (y - offsetY ) / scale
176176 end
177177
178178 if IsKeyDown (" SHIFT" ) then
@@ -271,42 +271,43 @@ function PassiveTreeViewClass:Draw(build, viewPort, inputEvents)
271271
272272 local hotkeyPressed = IsKeyDown (" 1" ) or IsKeyDown (" I" ) or IsKeyDown (" 2" ) or IsKeyDown (" S" ) or IsKeyDown (" 3" ) or IsKeyDown (" D" )
273273
274- -- Helper function to determine if global node allocation should be blocked
275- local function shouldBlockGlobalNodeAllocation (node )
276- local isGlobalNode = node .type == " Keystone" or node .type == " Socket" or node .containJewelSocket
277-
278- if not isGlobalNode or node .alloc or not node .path then
274+ -- Unified helper function to determine if node allocation should be blocked
275+ local function shouldBlockNodeAllocation (node )
276+ if not node .path or node .alloc then
279277 return false
280278 end
281279
282- local weaponSetMode = spec .allocMode > 0
283- local connectedToWeaponSetNodes = self :IsConnectedToWeaponSetNodes (node )
284-
285- -- Only allow allocation from main tree AND node must not be connected to weapon set nodes
286- local shouldBlock = weaponSetMode or connectedToWeaponSetNodes
280+ local isGlobalNode = node .type == " Keystone" or node .type == " Socket" or node .containJewelSocket
287281
288- return shouldBlock
282+ if isGlobalNode then
283+ local weaponSetMode = spec .allocMode > 0
284+ local connectedToWeaponSetNodes = self :IsConnectedToWeaponSetNodes (node )
285+ return weaponSetMode or connectedToWeaponSetNodes
286+ else
287+ local _ , canAllocate = self :GetNodeBlockingReason (node , spec )
288+ return not canAllocate
289+ end
289290 end
290291
291- -- Helper function to determine if global node deallocation should be blocked
292- local function shouldBlockGlobalNodeDeallocation (node )
293- local isGlobalNode = node .type == " Keystone" or node .type == " Socket" or node .containJewelSocket
294-
295- if not isGlobalNode or not node .alloc then
292+ -- Unified helper function to determine if node deallocation should be blocked
293+ local function shouldBlockNodeDeallocation (node )
294+ if not node .alloc then
296295 return false
297296 end
298297
299- -- Main-tree global nodes can only be deallocated from main tree
300- -- Legacy weapon-set global nodes can be deallocated from any mode
301- local shouldBlock = node .allocMode == 0 and spec .allocMode > 0
298+ local isGlobalNode = node .type == " Keystone" or node .type == " Socket" or node .containJewelSocket
302299
303- return shouldBlock
300+ if isGlobalNode then
301+ return node .allocMode == 0 and spec .allocMode > 0
302+ else
303+ return node .allocMode ~= spec .allocMode and node .allocMode ~= 0
304+ end
304305 end
305306
306307 if treeClick == " LEFT" then
307308 if hoverNode then
308309 -- User left-clicked on a node
309- if hoverNode .alloc and not shouldBlockGlobalNodeDeallocation (hoverNode ) then
310+ if hoverNode .alloc and not shouldBlockNodeDeallocation (hoverNode ) then
310311 -- Handle deallocation of allocated nodes
311312 if hoverNode .isAttribute then
312313 -- change to other attribute without needing to deallocate
@@ -323,7 +324,7 @@ function PassiveTreeViewClass:Draw(build, viewPort, inputEvents)
323324 end
324325 spec :AddUndoState ()
325326 build .buildFlag = true
326- elseif hoverNode .path and not shouldBlockGlobalNodeAllocation (hoverNode ) then
327+ elseif hoverNode .path and not shouldBlockNodeAllocation (hoverNode ) then
327328 -- Handle allocation of unallocated nodes
328329 if hoverNode .isAttribute and not hotkeyPressed then
329330 build .treeTab :ModifyAttributePopup (hoverNode )
@@ -350,7 +351,8 @@ function PassiveTreeViewClass:Draw(build, viewPort, inputEvents)
350351 build .itemsTab :SelectControl (slot )
351352 build .viewMode = " ITEMS"
352353 end
353- else
354+ elseif not hoverNode .alloc and not shouldBlockNodeAllocation (hoverNode ) then
355+ -- Only proceed with allocation if it's not blocked
354356 -- a way for us to bypass the popup when allocating attribute nodes, last used hotkey + RMB
355357 -- RMB + non attribute node logic
356358 -- RMB hot-swap logic
@@ -1183,7 +1185,7 @@ function PassiveTreeViewClass:AddNodeTooltip(tooltip, node, build, incSmallPassi
11831185 tooltip :AddLine (14 , colorCodes .TIP .. " Tip: Right click this socket to go to the items page and choose the jewel for this socket." )
11841186 end
11851187
1186- self :AddGlobalNodeWarningsToTooltip (tooltip , node , build )
1188+ self :AddNodeWarningsToTooltip (tooltip , node , build )
11871189
11881190 tooltip :AddLine (14 , colorCodes .TIP .. " Tip: Hold Shift or Ctrl to hide this tooltip." )
11891191 return
@@ -1448,7 +1450,7 @@ function PassiveTreeViewClass:AddNodeTooltip(tooltip, node, build, incSmallPassi
14481450 tooltip :AddLine (14 , colorCodes .TIP )
14491451 end
14501452
1451- self :AddGlobalNodeWarningsToTooltip (tooltip , node , build )
1453+ self :AddNodeWarningsToTooltip (tooltip , node , build )
14521454
14531455 if node .type == " Socket" then
14541456 tooltip :AddLine (14 , colorCodes .TIP .. " Tip: Hold Shift or Ctrl to hide this tooltip." )
@@ -1484,31 +1486,96 @@ function PassiveTreeViewClass:IsConnectedToWeaponSetNodes(node)
14841486 return false
14851487end
14861488
1487- -- Helper function to add warnings in the tooltip for global nodes (keystones/jewel sockets)
1488- function PassiveTreeViewClass :AddGlobalNodeWarningsToTooltip (tooltip , node , build )
1489+ -- Helper function to get detailed blocking reason for a node
1490+ -- Returns: reason, canAllocate
1491+ -- reason: "global_in_weapon_set", "path_through_global", "weapon_set_crossing", nil
1492+ function PassiveTreeViewClass :GetNodeBlockingReason (node , spec )
1493+ if not node or node .alloc or not node .path then
1494+ return nil , true
1495+ end
1496+
14891497 local isGlobalNode = node .type == " Keystone" or node .type == " Socket" or node .containJewelSocket
1498+ if isGlobalNode and spec .allocMode > 0 then
1499+ return " global_in_weapon_set" , false
1500+ end
14901501
1491- if not isGlobalNode then
1492- return -- No warning needed for non-global nodes
1502+ -- Check if a node conflicts with the current weapon set
1503+ local function isWeaponSetCompatible (allocatedNode )
1504+ return allocatedNode .allocMode == 0 or allocatedNode .allocMode == spec .allocMode
14931505 end
14941506
1495- local nodeTypeText = node .type == " Keystone" and " keystones" or " jewel sockets"
1507+ -- Check path or direct node for conflicts
1508+ local nodesToCheck = node .path and # node .path > 1 and node .path or { node }
1509+ for i , currentNode in ipairs (nodesToCheck ) do
1510+ -- Skip target node for path-specific checks if part of a multi-node path
1511+ if i > 1 or # nodesToCheck == 1 then
1512+ -- Block path through unallocated global nodes in weapon set mode
1513+ if spec .allocMode > 0 and not currentNode .alloc then
1514+ local isGlobalNode = currentNode .type == " Keystone" or currentNode .type == " Socket" or currentNode .containJewelSocket
1515+ if isGlobalNode then
1516+ return " path_through_global" , false
1517+ end
1518+ end
1519+
1520+ -- Check for weapon set conflicts with allocated nodes
1521+ if currentNode .alloc and not isWeaponSetCompatible (currentNode ) then
1522+ return " weapon_set_crossing" , false
1523+ end
1524+ end
1525+
1526+ -- Check linked nodes for conflicts, excluding those in our path
1527+ if currentNode .linked then
1528+ for _ , linkedNode in ipairs (currentNode .linked ) do
1529+ if not (i == 1 and node .path and linkedNode == node ) and linkedNode .alloc and not isWeaponSetCompatible (linkedNode ) then
1530+ return " weapon_set_crossing" , false
1531+ end
1532+ end
1533+ end
1534+ end
1535+
1536+ return nil , true
1537+ end
1538+
1539+ -- Helper function to add warnings in the tooltip for nodes affected by weapon set rules
1540+ function PassiveTreeViewClass :AddNodeWarningsToTooltip (tooltip , node , build )
1541+ local isGlobalNode = node .type == " Keystone" or node .type == " Socket" or node .containJewelSocket
1542+ local spec = build .spec
1543+
14961544 local warningText = " "
14971545 local tipText = " "
14981546
14991547 if not node .alloc and node .path then
1500- -- Unallocated global node - check allocation conditions
1501- if build .spec .allocMode > 0 then
1502- warningText = " Cannot allocate " .. nodeTypeText .. " while weapon set " .. build .spec .allocMode .. " is selected"
1503- tipText = " Tip: Switch to main tree (Alt+scroll) to allocate " .. nodeTypeText
1504- elseif self :IsConnectedToWeaponSetNodes (node ) then
1548+ -- Get detailed blocking reason
1549+ local blockingReason , canAllocate = self :GetNodeBlockingReason (node , spec )
1550+
1551+ if not canAllocate then
1552+ if blockingReason == " global_in_weapon_set" then
1553+ local nodeTypeText = node .type == " Keystone" and " keystones" or " jewel sockets"
1554+ warningText = " Cannot allocate " .. nodeTypeText .. " while weapon set " .. spec .allocMode .. " is selected"
1555+ tipText = " Tip: Switch to main tree (Alt+scroll) to allocate " .. nodeTypeText
1556+ elseif blockingReason == " path_through_global" then
1557+ warningText = " Cannot allocate while weapon set " .. spec .allocMode .. " is selected - path goes through global nodes"
1558+ tipText = " Tip: Switch to main tree (Alt+scroll) or find an alternative path"
1559+ elseif blockingReason == " weapon_set_crossing" then
1560+ warningText = " Cannot allocate - path crosses between different weapon sets"
1561+ tipText = " Tip: Deallocate conflicting weapon set nodes in the path first"
1562+ end
1563+ elseif isGlobalNode and self :IsConnectedToWeaponSetNodes (node ) then
1564+ -- Special case: global node connected to weapon set nodes (existing logic)
1565+ local nodeTypeText = node .type == " Keystone" and " keystones" or " jewel sockets"
15051566 warningText = " Cannot allocate " .. nodeTypeText .. " - connected to weapon set nodes"
15061567 tipText = " Tip: Deallocate weapon set nodes in the connection path to allow allocation"
15071568 end
1508- elseif node .alloc and node .allocMode == 0 and build .spec .allocMode > 0 then
1509- -- Allocated main-tree global node viewed from weapon set
1510- warningText = " Cannot deallocate global " .. nodeTypeText .. " from weapon set " .. build .spec .allocMode
1511- tipText = " Tip: Switch to main tree (Alt+scroll) to deallocate " .. nodeTypeText
1569+ elseif node .alloc then
1570+ -- Allocated node warnings
1571+ if isGlobalNode and node .allocMode == 0 and spec .allocMode > 0 then
1572+ local nodeTypeText = node .type == " Keystone" and " keystones" or " jewel sockets"
1573+ warningText = " Cannot deallocate global " .. nodeTypeText .. " from weapon set " .. spec .allocMode
1574+ tipText = " Tip: Switch to main tree (Alt+scroll) to deallocate " .. nodeTypeText
1575+ elseif (node .isAttribute or not isGlobalNode ) and node .allocMode ~= spec .allocMode and node .allocMode ~= 0 then
1576+ warningText = " Cannot deallocate - node was allocated by weapon set " .. node .allocMode
1577+ tipText = " Tip: Switch to weapon set " .. node .allocMode .. " (Alt+scroll) to deallocate this node"
1578+ end
15121579 end
15131580
15141581 if warningText ~= " " then
0 commit comments