diff --git a/src/Classes/PassiveSpec.lua b/src/Classes/PassiveSpec.lua index 920d439d..b4f959a4 100644 --- a/src/Classes/PassiveSpec.lua +++ b/src/Classes/PassiveSpec.lua @@ -669,7 +669,7 @@ function PassiveSpecClass:AllocNode(node, altPath) end -- Allocate all nodes along the path - if node.dependsOnIntuitiveLeapLike then + if #node.intuitiveLeapLikesAffecting > 0 then node.alloc = true self.allocNodes[node.id] = node else @@ -788,7 +788,7 @@ function PassiveSpecClass:BuildPathFromNode(root) -- All nodes that are 1 node away from the root will be processed first, then all nodes that are 2 nodes away, etc local node = queue[o] o = o + 1 - local curDist = node.pathDist + 1 + local curDist = node.pathDist -- Iterate through all nodes that are connected to this one for _, other in ipairs(node.linked) do -- Paths must obey these rules: @@ -803,6 +803,9 @@ function PassiveSpecClass:BuildPathFromNode(root) if node.type ~= "Mastery" and other.type ~= "ClassStart" and other.type ~= "AscendClassStart" and other.pathDist > curDist and (node.ascendancyName == other.ascendancyName or (curDist == 1 and not other.ascendancyName)) then -- The shortest path to the other node is through the current node other.pathDist = curDist + if not other.alloc then + other.pathDist = other.pathDist + 1 + end other.path = wipeTable(other.path) other.path[1] = other for i, n in ipairs(node.path) do @@ -820,7 +823,7 @@ end -- Only allocated nodes can be traversed function PassiveSpecClass:SetNodeDistanceToClassStart(root) root.distanceToClassStart = 0 - if not root.alloc or root.dependsOnIntuitiveLeapLike then + if not root.alloc or not root.connectedToStart then return end @@ -876,6 +879,36 @@ function PassiveSpecClass:AddMasteryEffectOptionsToNode(node) node.allMasteryOptions = true end +function PassiveSpecClass:NodesInIntuitiveLeapLikeRadius(node) + local result = { } + if self.jewels[node.id] and self.jewels[node.id] > 0 then + local item = self.build.itemsTab.items[self.jewels[node.id]] + local radiusIndex = item.jewelRadiusIndex + if item and item.jewelData and item.jewelData.intuitiveLeapLike then + local inRadius = self.nodes[node.id].nodesInRadius and self.nodes[node.id].nodesInRadius[radiusIndex] + for affectedNodeId, affectedNode in pairs(inRadius) do + if self.nodes[affectedNodeId].alloc then + t_insert(result, self.nodes[affectedNodeId]) + end + end + end + + if item.jewelData and item.jewelData.impossibleEscapeKeystone then + for keyName, keyNode in pairs(item.jewelData.impossibleEscapeKeystones) do + if self.tree.keystoneMap[keyName].nodesInRadius then + for affectedNodeId in pairs(self.tree.keystoneMap[keyName].nodesInRadius[radiusIndex]) do + if self.nodes[affectedNodeId].alloc then + t_insert(result, self.nodes[affectedNodeId]) + end + end + end + end + end + end + + return result +end + -- Rebuilds dependencies and paths for all nodes function PassiveSpecClass:BuildAllDependsAndPaths() -- This table will keep track of which nodes have been visited during each path-finding attempt @@ -884,7 +917,7 @@ function PassiveSpecClass:BuildAllDependsAndPaths() -- Check all nodes for other nodes which depend on them (i.e. are only connected to the tree through that node) for id, node in pairs(self.nodes) do node.depends = wipeTable(node.depends) - node.dependsOnIntuitiveLeapLike = false + node.intuitiveLeapLikesAffecting = { } node.conqueredBy = nil -- ignore cluster jewel nodes that don't have an id in the tree @@ -902,10 +935,9 @@ function PassiveSpecClass:BuildAllDependsAndPaths() if item.jewelData.intuitiveLeapLike then -- This node depends on Intuitive Leap-like behaviour -- This flag: - -- 1. Prevents generation of paths from this node - -- 2. Prevents this node from being deallocted via dependency - -- 3. Prevents allocation of path nodes when this node is being allocated - node.dependsOnIntuitiveLeapLike = true + -- 1. Prevents generation of paths from this node unless it's also connected to the start + -- 2. Prevents allocation of path nodes when this node is being allocated + t_insert(node.intuitiveLeapLikesAffecting, self.nodes[nodeId]) end if item.jewelData.conqueredBy then node.conqueredBy = item.jewelData.conqueredBy @@ -915,9 +947,9 @@ function PassiveSpecClass:BuildAllDependsAndPaths() if item.jewelData and item.jewelData.impossibleEscapeKeystone then for keyName, keyNode in pairs(self.tree.keystoneMap) do - if item.jewelData.impossibleEscapeKeystones[keyName:lower()] and keyNode.nodesInRadius then + if item.jewelData.impossibleEscapeKeystones[keyName] and keyNode.nodesInRadius then if keyNode.nodesInRadius[radiusIndex][node.id] then - node.dependsOnIntuitiveLeapLike = true + t_insert(node.intuitiveLeapLikesAffecting, self.nodes[nodeId]) end end end @@ -1158,8 +1190,11 @@ function PassiveSpecClass:BuildAllDependsAndPaths() end end + local potentialDeps = { } + local intuitiveLeaps = { } for id, node in pairs(self.allocNodes) do node.visited = true + node.connectedToStart = false local anyStartFound = (node.type == "ClassStart" or node.type == "AscendClassStart") for _, other in ipairs(node.linked) do if other.alloc and not isValueInArray(node.depends, other) then @@ -1167,9 +1202,11 @@ function PassiveSpecClass:BuildAllDependsAndPaths() if other.type == "ClassStart" or other.type == "AscendClassStart" then -- Well that was easy! anyStartFound = true + node.connectedToStart = true elseif self:FindStartFromNode(other, visited) then -- We found a path through the other node, therefore the other node cannot be dependent on this node anyStartFound = true + node.connectedToStart = true for i, n in ipairs(visited) do n.visited = false visited[i] = nil @@ -1179,33 +1216,50 @@ function PassiveSpecClass:BuildAllDependsAndPaths() -- except for mastery nodes that have linked allocated nodes that weren't visited local depIds = { } for _, n in ipairs(visited) do - if not n.dependsOnIntuitiveLeapLike then + if #n.intuitiveLeapLikesAffecting == 0 then depIds[n.id] = true end end for i, n in ipairs(visited) do - if not n.dependsOnIntuitiveLeapLike then - if n.type == "Mastery" then - local otherPath = false - local allocatedLinkCount = 0 + if n.type == "Mastery" then + local otherPath = false + local allocatedLinkCount = 0 + for _, linkedNode in ipairs(n.linked) do + if linkedNode.alloc then + allocatedLinkCount = allocatedLinkCount + 1 + end + end + if allocatedLinkCount > 1 then for _, linkedNode in ipairs(n.linked) do - if linkedNode.alloc then - allocatedLinkCount = allocatedLinkCount + 1 + if linkedNode.alloc and not depIds[linkedNode.id] then + otherPath = true end end - if allocatedLinkCount > 1 then - for _, linkedNode in ipairs(n.linked) do - if linkedNode.alloc and not depIds[linkedNode.id] then - otherPath = true - end - end - end - if not otherPath then - t_insert(node.depends, n) + end + if not otherPath then + t_insert(node.depends, n) + end + else + -- If n is dependent on intuitive leap then it may be dependent on this node + if #n.intuitiveLeapLikesAffecting > 0 then + if not potentialDeps[n.id] then + potentialDeps[n.id] = { } end + + t_insert(potentialDeps[n.id], node) else t_insert(node.depends, n) end + + -- If n is a jewel socket containing an intuitive leap-like jewel, nodes in its radius (or the radius of the keystone) + -- may be dependent on this node if they're found to be unconnected to the start + if not intuitiveLeaps[node.id] then + intuitiveLeaps[node.id] = self:NodesInIntuitiveLeapLikeRadius(n) + else + for _, affectedNode in ipairs(self:NodesInIntuitiveLeapLikeRadius(n)) do + t_insert(intuitiveLeaps[node.id], affectedNode) + end + end end n.visited = false visited[i] = nil @@ -1236,7 +1290,10 @@ function PassiveSpecClass:BuildAllDependsAndPaths() ) then -- Hold off on the pruning; this node could be supported by Intuitive Leap-like jewel prune = false - t_insert(self.nodes[nodeId].depends, depNode) + if not intuitiveLeaps[nodeId] then + intuitiveLeaps[nodeId] = { } + end + t_insert(intuitiveLeaps[nodeId], depNode) break end end @@ -1248,16 +1305,57 @@ function PassiveSpecClass:BuildAllDependsAndPaths() end end + -- All other dependencies resolved, add dependencies to nodes affected by intuitive leap-like jewels + -- Nodes that are unconnected to the start depend on all nodes that connect the jewel socket to the start + -- Nodes that are connected to the start depend on all nodes that connect them *and* the jewel socket to the start + -- In both cases, the nodes can be affected by multiple jewels at the same time + for id, deps in pairs(potentialDeps) do + local potentialNode = self.nodes[id] + local seen = { } + for _, node in ipairs(deps) do + local allDep = true + for _, intuitiveLeapLikeProvider in pairs(potentialNode.intuitiveLeapLikesAffecting) do + if not isValueInArray(node.depends, intuitiveLeapLikeProvider) then + allDep = false + end + end + if allDep and not seen[node.id] then + t_insert(node.depends, potentialNode) + seen[node.id] = true + end + end + end + + for id, deps in pairs(intuitiveLeaps) do + local node = self.nodes[id] + local seen = { } + for _, dep in ipairs(deps) do + if not dep.connectedToStart then + local allDep = true + for _, intuitiveDep in ipairs(dep.intuitiveLeapLikesAffecting) do + if not isValueInArray(node.depends, intuitiveDep) then + allDep = false + break + end + end + if allDep and not seen[dep.id] then + t_insert(node.depends, dep) + seen[dep.id] = true + end + end + end + end + -- Reset and rebuild all node paths for id, node in pairs(self.nodes) do - node.pathDist = (node.alloc and not node.dependsOnIntuitiveLeapLike) and 0 or 1000 + node.pathDist = (node.alloc and #node.intuitiveLeapLikesAffecting == 0) and 0 or 1000 node.path = nil if node.isJewelSocket or node.expansionJewel then node.distanceToClassStart = 0 end end for id, node in pairs(self.allocNodes) do - if not node.dependsOnIntuitiveLeapLike then + if #node.intuitiveLeapLikesAffecting == 0 or node.connectedToStart then self:BuildPathFromNode(node) if node.isJewelSocket or node.expansionJewel then self:SetNodeDistanceToClassStart(node) diff --git a/src/Classes/PassiveTreeView.lua b/src/Classes/PassiveTreeView.lua index 18e30c05..14f41333 100644 --- a/src/Classes/PassiveTreeView.lua +++ b/src/Classes/PassiveTreeView.lua @@ -252,7 +252,7 @@ function PassiveTreeViewClass:Draw(build, viewPort, inputEvents) elseif hoverNode and hoverNode.path then -- Use the node's own path and dependence list hoverPath = { } - if not hoverNode.dependsOnIntuitiveLeapLike then + if #hoverNode.intuitiveLeapLikesAffecting == 0 then for _, pathNode in pairs(hoverNode.path) do hoverPath[pathNode] = true end @@ -632,7 +632,13 @@ function PassiveTreeViewClass:Draw(build, viewPort, inputEvents) -- Draw base artwork if base then - self:DrawAsset(base, scrX, scrY, scale) + if node.type == "Socket" and hoverDep and hoverDep[node] then + SetDrawColor(1, 0, 0); + self:DrawAsset(base, scrX, scrY, scale) + SetDrawColor(1, 1, 1); + else + self:DrawAsset(base, scrX, scrY, scale) + end end if overlay then @@ -1076,7 +1082,7 @@ function PassiveTreeViewClass:AddNodeTooltip(tooltip, node, build) elseif node.alloc then -- Calculate the differences caused by deallocating this node and its dependent nodes nodeOutput = calcFunc({ removeNodes = { [node] = true } }) - if not node.dependsOnIntuitiveLeapLike and pathLength > 1 then + if pathLength > 1 then pathOutput = calcFunc({ removeNodes = pathNodes }) end elseif isGranted then @@ -1090,19 +1096,19 @@ function PassiveTreeViewClass:AddNodeTooltip(tooltip, node, build) else nodeOutput = calcFunc({ addNodes = { [node] = true } }) end - if not node.dependsOnIntuitiveLeapLike and pathLength > 1 then + if pathLength > 1 then pathOutput = calcFunc({ addNodes = pathNodes }) end end local count = build:AddStatComparesToTooltip(tooltip, calcBase, nodeOutput, realloc and "^7Reallocating this node will give you:" or node.alloc and "^7Unallocating this node will give you:" or isGranted and "^7This node is granted by an item. Removing it will give you:" or "^7Allocating this node will give you:") - if not node.dependsOnIntuitiveLeapLike and pathLength > 1 and not isGranted then + if pathLength > 1 and not isGranted then count = count + build:AddStatComparesToTooltip(tooltip, calcBase, pathOutput, node.alloc and "^7Unallocating this node and all nodes depending on it will give you:" or "^7Allocating this node and all nodes leading to it will give you:", pathLength) end if count == 0 then if isGranted then tooltip:AddLine(14, string.format("^7This node is granted by an item. Removing it will cause no changes")) else - tooltip:AddLine(14, string.format("^7No changes from %s this node%s.", node.alloc and "unallocating" or "allocating", not node.dependsOnIntuitiveLeapLike and pathLength > 1 and " or the nodes leading to it" or "")) + tooltip:AddLine(14, string.format("^7No changes from %s this node%s.", node.alloc and "unallocating" or "allocating", node.intuitiveLeapLikesAffecting == 0 and pathLength > 1 and " or the nodes leading to it" or "")) end end tooltip:AddLine(14, colorCodes.TIP.."Tip: Press Ctrl+D to disable the display of stat differences.") @@ -1118,7 +1124,7 @@ function PassiveTreeViewClass:AddNodeTooltip(tooltip, node, build) tooltip:AddLine(14, "^7"..#self.tracePath .. " nodes in trace path") tooltip:AddLine(14, colorCodes.TIP) else - tooltip:AddLine(14, "^7"..#node.path .. " points to node" .. (node.dependsOnIntuitiveLeapLike and " ^8(Can be allocated without pathing to it)" or "")) + tooltip:AddLine(14, "^7"..node.pathDist .. " points to node" .. (#node.intuitiveLeapLikesAffecting > 0 and " ^8(Can be allocated without pathing to it)" or "")) tooltip:AddLine(14, colorCodes.TIP) if #node.path > 1 then -- Handy hint!