Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,6 @@ src/Data/TimelessJewelData/*.bin
# Simplegraphic Debugging
runtime/imgui.ini
runtime/SimpleGraphic/SimpleGraphic.log

# Local-only documentation notes (never commit upstream)
.notes/
5 changes: 5 additions & 0 deletions src/Classes/ItemsTab.lua
Original file line number Diff line number Diff line change
Expand Up @@ -3564,6 +3564,11 @@ function ItemsTabClass:AddItemTooltip(tooltip, item, slot, dbMode)
end
if item.jewelRadiusLabel then
tooltip:AddLine(fontSizeBig, "^x7F7F7FRadius: ^7"..item.jewelRadiusLabel, "FONTIN SC")
if item.jewelRadiusLabel == "Variable" then
-- Display-only hint, not added to rawLines, so item import/export is unaffected.
tooltip:AddSeparator(4)
tooltip:AddLine(fontSizeBig, colorCodes.MAGIC.."Tip: Press ^x7F7F7FT"..colorCodes.MAGIC.." in the tree view to toggle all five ring sizes.", "FONTIN")
end
end
if item.jewelRadiusData and slot and item.jewelRadiusData[slot.nodeId] then
local radiusData = item.jewelRadiusData[slot.nodeId]
Expand Down
78 changes: 50 additions & 28 deletions src/Classes/PassiveSpec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -1031,16 +1031,32 @@ function PassiveSpecClass:AddMasteryEffectOptionsToNode(node)
node.allMasteryOptions = true
end

-- Returns jewelRadius indices to consider for this item. When the tree-view planning
-- toggle is on for a Variable-radius jewel, all 5 Variable rings are treated as candidate
-- radii so dependency/path calculations cover every possible socketed roll.
function PassiveSpecClass:GetEffectiveRadiusIndices(item)
local toHMode = self.build.treeTab and self.build.treeTab.viewer
and self.build.treeTab.viewer.toHRingMode
if toHMode and item.jewelRadiusLabel == "Variable" then
return { 6, 7, 8, 9, 10 }
end
return { item.jewelRadiusIndex }
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 or {}) do
if self.nodes[affectedNodeId].alloc then
t_insert(result, self.nodes[affectedNodeId])
local seen = { }
for _, idx in ipairs(self:GetEffectiveRadiusIndices(item)) do
local inRadius = self.nodes[node.id].nodesInRadius and self.nodes[node.id].nodesInRadius[idx]
for affectedNodeId, affectedNode in pairs(inRadius or {}) do
if self.nodes[affectedNodeId].alloc and not seen[affectedNodeId] then
seen[affectedNodeId] = true
t_insert(result, self.nodes[affectedNodeId])
end
end
end
end
Expand Down Expand Up @@ -1082,18 +1098,21 @@ function PassiveSpecClass:BuildAllDependsAndPaths()
local item = self.build.itemsTab.items[itemId]
if item and item.jewelRadiusIndex and self.allocNodes[nodeId] and item.jewelData and not item.jewelData.limitDisabled then
local radiusIndex = item.jewelRadiusIndex
if self.nodes[nodeId].nodesInRadius and self.nodes[nodeId].nodesInRadius[radiusIndex][node.id] then
if itemId ~= 0 then
if item.jewelData.intuitiveLeapLike and not (item.jewelData.intuitiveLeapKeystoneOnly and node.type ~= "Keystone") then
-- This node depends on Intuitive Leap-like behaviour
-- This flag:
-- 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
for _, idx in ipairs(self:GetEffectiveRadiusIndices(item)) do
if self.nodes[nodeId].nodesInRadius and self.nodes[nodeId].nodesInRadius[idx][node.id] then
if itemId ~= 0 then
if item.jewelData.intuitiveLeapLike and not (item.jewelData.intuitiveLeapKeystoneOnly and node.type ~= "Keystone") then
-- This node depends on Intuitive Leap-like behaviour
-- This flag:
-- 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
end
end
break
end
end

Expand Down Expand Up @@ -1447,19 +1466,22 @@ function PassiveSpecClass:BuildAllDependsAndPaths()
local prune = true
for nodeId, itemId in pairs(self.jewels) do
if self.allocNodes[nodeId] then
if itemId ~= 0 and (
self.build.itemsTab.items[itemId] and (
self.build.itemsTab.items[itemId].jewelData
and self.build.itemsTab.items[itemId].jewelData.intuitiveLeapLike
and self.build.itemsTab.items[itemId].jewelRadiusIndex
and self.nodes[nodeId].nodesInRadius
and self.nodes[nodeId].nodesInRadius[self.build.itemsTab.items[itemId].jewelRadiusIndex][depNode.id]
) or (
self.build.itemsTab.items[itemId].jewelData
and self.build.itemsTab.items[itemId].jewelData.impossibleEscapeKeystones
and self:NodeInKeystoneRadius(self.build.itemsTab.items[itemId].jewelData.impossibleEscapeKeystones, depNode.id, self.build.itemsTab.items[itemId].jewelRadiusIndex)
)
) then
local item = self.build.itemsTab.items[itemId]
local socketNode = self.nodes[nodeId]
local leapHit = false
if itemId ~= 0 and item and item.jewelData and item.jewelData.intuitiveLeapLike
and item.jewelRadiusIndex and socketNode.nodesInRadius then
for _, idx in ipairs(self:GetEffectiveRadiusIndices(item)) do
if socketNode.nodesInRadius[idx] and socketNode.nodesInRadius[idx][depNode.id] then
leapHit = true
break
end
end
end
local keyHit = itemId ~= 0 and item and item.jewelData
and item.jewelData.impossibleEscapeKeystones
and self:NodeInKeystoneRadius(item.jewelData.impossibleEscapeKeystones, depNode.id, item.jewelRadiusIndex)
if leapHit or keyHit then
-- Hold off on the pruning; this node could be supported by Intuitive Leap-like jewel
prune = false
if not intuitiveLeaps[nodeId] then
Expand Down
68 changes: 67 additions & 1 deletion src/Classes/PassiveTreeView.lua
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ local PassiveTreeViewClass = newClass("PassiveTreeView", function(self)
self.searchStrCached = ""
self.searchStrResults = {}
self.showStatDifferences = true
self.toHRingMode = nil -- nil | true (planning toggle: show all Variable rings)
self.hoverNode = nil
end)

Expand Down Expand Up @@ -116,6 +117,15 @@ function PassiveTreeViewClass:Draw(build, viewPort, inputEvents)
end
elseif event.key == "p" then
self.showHeatMap = not self.showHeatMap
elseif event.key == "t" then
-- Toggle Thread of Hope all-rings planning view
self.toHRingMode = not self.toHRingMode or nil
if not main.toHHintDismissed then
main.toHHintDismissed = true
main:SaveSettings()
end
build.spec:BuildAllDependsAndPaths()
build.buildFlag = true
elseif event.key == "d" and IsKeyDown("CTRL") then
self.showStatDifferences = not self.showStatDifferences
elseif event.key == "c" and IsKeyDown("CTRL") and self.hoverNode and self.hoverNode.type ~= "Socket" then
Expand Down Expand Up @@ -635,6 +645,35 @@ function PassiveTreeViewClass:Draw(build, viewPort, inputEvents)
end
end

-- One pass over allocated jewel sockets: detects whether any Variable-radius jewel is
-- socketed (used to gate the first-time hint), and when the planning toggle is on, builds
-- a nodeId -> ring colour tint map. First-seen wins when sockets overlap; rings within one
-- socket are non-overlapping so per-socket order is deterministic.
local toHTintMap = self.toHRingMode and { } or nil
local hasVariableJewel = false
for socketNodeId, itemId in pairs(spec.jewels) do
if itemId ~= 0 and spec.allocNodes[socketNodeId] then
local item = build.itemsTab.items[itemId]
local socketNode = spec.nodes[socketNodeId]
if item and item.jewelRadiusLabel == "Variable" then
hasVariableJewel = true
if toHTintMap and socketNode and socketNode.nodesInRadius then
for ringIdx = 6, 10 do
local nodesInRing = socketNode.nodesInRadius[ringIdx]
local radData = build.data.jewelRadius[ringIdx]
if nodesInRing and radData then
for nId in pairs(nodesInRing) do
if not toHTintMap[nId] then
toHTintMap[nId] = radData.col
end
end
end
end
end
end
end
end

-- Draw the nodes
for nodeId, node in pairs(spec.nodes) do
-- Determine the base and overlay images for this node based on type and state
Expand Down Expand Up @@ -850,11 +889,13 @@ function PassiveTreeViewClass:Draw(build, viewPort, inputEvents)
if overlay then
-- Draw overlay
if node.type ~= "ClassStart" and node.type ~= "AscendClassStart" then
local hoverTinted = false
if hoverNode and hoverNode ~= node then
-- Mouse is hovering over a different node
if hoverDep and hoverDep[node] then
-- This node depends on the hover node, turn it red
SetDrawColor(1, 0, 0)
hoverTinted = true
elseif hoverNode.type == "Socket" and hoverNode.nodesInRadius then
-- Hover node is a socket, check if this node falls within its radius and color it accordingly
local socket, jewel = build.itemsTab:GetSocketAndJewelForNodeID(hoverNode.id)
Expand All @@ -866,6 +907,7 @@ function PassiveTreeViewClass:Draw(build, viewPort, inputEvents)
-- Draw Thread of Hope's annuli
if data.inner ~= 0 then
SetDrawColor(data.col)
hoverTinted = true
break
end
end
Expand All @@ -877,13 +919,18 @@ function PassiveTreeViewClass:Draw(build, viewPort, inputEvents)
-- Draw normal jewel radii
if data.inner == 0 then
SetDrawColor(data.col)
hoverTinted = true
break
end
end
end
end
end
end
if not hoverTinted and toHTintMap and toHTintMap[nodeId] then
-- Planning toggle is on: tint nodes by the Thread of Hope ring they sit in
SetDrawColor(toHTintMap[nodeId])
end
end
self:DrawAsset(tree.assets[overlay], scrX, scrY, scale)
SetDrawColor(1, 1, 1)
Expand Down Expand Up @@ -959,7 +1006,18 @@ function PassiveTreeViewClass:Draw(build, viewPort, inputEvents)
end
end
elseif node.alloc then
if jewel and jewel.jewelRadiusIndex then
if self.toHRingMode and jewel and jewel.jewelRadiusLabel == "Variable" then
-- Planning toggle: draw all five Variable-radius annuli around the socket
for _, radData in ipairs(build.data.jewelRadius) do
local outerSize = radData.outer * scale
local innerSize = radData.inner * scale
if innerSize ~= 0 then
SetDrawColor(radData.col)
DrawImage(self.ring, scrX - outerSize, scrY - outerSize, outerSize * 2, outerSize * 2)
DrawImage(self.ring, scrX - innerSize, scrY - innerSize, innerSize * 2, innerSize * 2)
end
end
elseif jewel and jewel.jewelRadiusIndex then
-- Draw only the selected jewel radius
local radData = build.data.jewelRadius[jewel.jewelRadiusIndex]
local outerSize = radData.outer * scale
Expand Down Expand Up @@ -1006,6 +1064,14 @@ function PassiveTreeViewClass:Draw(build, viewPort, inputEvents)
end
end
end

-- First-time hint: shown only while the toggle is off, the user has never pressed T,
-- and the build has at least one allocated Variable-radius jewel.
if hasVariableJewel and not self.toHRingMode and not main.toHHintDismissed then
SetDrawLayer(nil, 100)
DrawString(viewPort.x + 12, viewPort.y + 12, "LEFT", 18, "FONTIN",
"^xFFCC00Tip: ^7Press ^xFFCC00T^7 to view all Thread of Hope ring sizes.")
end
end
function PassiveTreeViewClass:DrawImageRotated(handle, x, y, width, height, angle, ...)
if main.showAnimations == false then
Expand Down
4 changes: 2 additions & 2 deletions src/Modules/Data.lua
Original file line number Diff line number Diff line change
Expand Up @@ -540,13 +540,13 @@ data.jewelRadii = {
{ inner = 0, outer = 1440, col = "^x66FFCC", label = "Medium" },
{ inner = 0, outer = 1800, col = "^x2222CC", label = "Large" },
{ inner = 0, outer = 2400, col = "^xC100FF", label = "Very Large" },
{ inner = 0, outer = 2880, col = "^x0B9300", label = "Massive" },
{ inner = 0, outer = 2880, col = "^xFFD700", label = "Massive" },

{ inner = 960, outer = 1320, col = "^xD35400", label = "Variable" },
{ inner = 1320, outer = 1680, col = "^x66FFCC", label = "Variable" },
{ inner = 1680, outer = 2040, col = "^x2222CC", label = "Variable" },
{ inner = 2040, outer = 2400, col = "^xC100FF", label = "Variable" },
{ inner = 2400, outer = 2880, col = "^x0B9300", label = "Variable" },
{ inner = 2400, outer = 2880, col = "^xFFD700", label = "Variable" },
}
}

Expand Down
4 changes: 4 additions & 0 deletions src/Modules/Main.lua
Original file line number Diff line number Diff line change
Expand Up @@ -580,6 +580,9 @@ function main:LoadSettings(ignoreBuild)
if node.attrib.edgeSearchHighlight then
self.edgeSearchHighlight = node.attrib.edgeSearchHighlight == "true"
end
if node.attrib.toHHintDismissed then
self.toHHintDismissed = node.attrib.toHHintDismissed == "true"
end
if node.attrib.defaultGemQuality then
self.defaultGemQuality = m_min(tonumber(node.attrib.defaultGemQuality) or 0, 23)
end
Expand Down Expand Up @@ -745,6 +748,7 @@ function main:SaveSettings()
showTitlebarName = tostring(self.showTitlebarName),
betaTest = tostring(self.betaTest),
edgeSearchHighlight = tostring(self.edgeSearchHighlight),
toHHintDismissed = tostring(self.toHHintDismissed or false),
defaultGemQuality = tostring(self.defaultGemQuality or 0),
defaultCharLevel = tostring(self.defaultCharLevel or 1),
defaultItemAffixQuality = tostring(self.defaultItemAffixQuality or 0.5),
Expand Down
Loading