Skip to content

Commit 4ca48e5

Browse files
github-actions[bot]obokingLocalIdentity
authored
Fix comparing builds that used a tree with Radius Jewels (#9874)
* Apply changes from PathOfBuildingCommunity/PathOfBuilding-PoE2#1932 * Fix merge issues --------- Co-authored-by: oboking <oboking@users.noreply.github.com> Co-authored-by: LocalIdentity <localidentity2@gmail.com>
1 parent fd95863 commit 4ca48e5

2 files changed

Lines changed: 222 additions & 85 deletions

File tree

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
describe("PassiveTreeView jewel comparison", function()
2+
local viewer
3+
4+
local function newJewel(name, raw)
5+
return {
6+
name = name,
7+
BuildRaw = function()
8+
return raw
9+
end,
10+
}
11+
end
12+
13+
local function setCompareJewel(jewel, allocated)
14+
local itemsTab = {
15+
items = { [2] = jewel },
16+
sockets = { [100] = { selItemId = jewel and 2 or 0 } },
17+
}
18+
viewer.compareSpec = {
19+
allocNodes = { [100] = allocated },
20+
build = { itemsTab = itemsTab },
21+
}
22+
return itemsTab
23+
end
24+
25+
before_each(function()
26+
newBuild()
27+
viewer = build.treeTab.viewer
28+
end)
29+
30+
it("matches jewels by their complete raw representation", function()
31+
local primaryJewel = newJewel("Controller Metamorphosis", "Radius: Small")
32+
local compareJewel = newJewel("Controller Metamorphosis", "Radius: Small")
33+
setCompareJewel(compareJewel, true)
34+
35+
local color = viewer:GetCompareNodeColor(
36+
{ id = 100, type = "Socket", alloc = true },
37+
{ alloc = true },
38+
{ jewels = { [100] = 1 } },
39+
{ itemsTab = { items = { [1] = primaryJewel } } },
40+
"^xFFFFFF"
41+
)
42+
43+
assert.are.equals("^xFFFFFF", color)
44+
end)
45+
46+
it("marks same-name jewels with different variants as changed", function()
47+
local primaryJewel = newJewel("Controller Metamorphosis", "Radius: Small")
48+
local compareJewel = newJewel("Controller Metamorphosis", "Radius: Large")
49+
setCompareJewel(compareJewel, true)
50+
51+
local red, green, blue = viewer:GetCompareNodeColor(
52+
{ id = 100, type = "Socket", alloc = true },
53+
{ alloc = true },
54+
{ jewels = { [100] = 1 } },
55+
{ itemsTab = { items = { [1] = primaryJewel } } },
56+
"^xFFFFFF"
57+
)
58+
59+
assert.are.same({ 0, 0, 1 }, { red, green, blue })
60+
end)
61+
62+
it("shows both full jewel tooltips when allocated socket jewels differ", function()
63+
local primaryJewel = newJewel("Controller Metamorphosis", "Radius: Small")
64+
local compareJewel = newJewel("Controller Metamorphosis", "Radius: Large")
65+
local compareItemsTab = setCompareJewel(compareJewel, true)
66+
local primaryTooltipItems = { }
67+
local compareTooltipItems = { }
68+
local socket = { IsEnabled = function() return false end }
69+
local tooltip = {
70+
AddLine = function() end,
71+
AddSeparator = function() end,
72+
}
73+
local primaryBuild = {
74+
itemsTab = {
75+
GetSocketAndJewelForNodeID = function()
76+
return socket, primaryJewel
77+
end,
78+
AddItemTooltip = function(_, _, item)
79+
table.insert(primaryTooltipItems, item)
80+
end,
81+
},
82+
}
83+
compareItemsTab.AddItemTooltip = function(_, _, item)
84+
table.insert(compareTooltipItems, item)
85+
end
86+
87+
viewer:AddNodeTooltip(tooltip, { id = 100, type = "Socket", alloc = true }, primaryBuild)
88+
89+
assert.are.same({ primaryJewel }, primaryTooltipItems)
90+
assert.are.same({ compareJewel }, compareTooltipItems)
91+
end)
92+
93+
it("shows the compared jewel tooltip when only the compared socket is allocated", function()
94+
local compareJewel = newJewel("Controller Metamorphosis", "Radius: Large")
95+
local compareItemsTab = setCompareJewel(compareJewel, true)
96+
local compareTooltipItems = { }
97+
local tooltip = {
98+
AddLine = function() end,
99+
AddSeparator = function() end,
100+
}
101+
local primaryBuild = {
102+
itemsTab = {
103+
GetSocketAndJewelForNodeID = function()
104+
return { }, nil
105+
end,
106+
},
107+
}
108+
compareItemsTab.AddItemTooltip = function(_, _, item)
109+
table.insert(compareTooltipItems, item)
110+
end
111+
112+
viewer:AddNodeTooltip(tooltip, { id = 100, type = "Socket", alloc = false }, primaryBuild)
113+
114+
assert.are.same({ compareJewel }, compareTooltipItems)
115+
end)
116+
end)

src/Classes/PassiveTreeView.lua

Lines changed: 106 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,10 @@ local m_floor = math.floor
1414
local band = bit.band
1515
local b_rshift = bit.rshift
1616

17+
local JEWEL_RADIUS_TINT_NEUTRAL = { 1, 1, 1, 0.7 }
18+
local JEWEL_RADIUS_TINT_PRIMARY_ONLY = { 1, 0, 0, 0.7 }
19+
local JEWEL_RADIUS_TINT_COMPARE_ONLY = { 0, 1, 0, 0.7 }
20+
1721
local gemTooltip = LoadModule("Classes/GemTooltip")
1822
local PassiveTreeViewClass = newClass("PassiveTreeView", function(self)
1923
self.ring = NewImageHandle()
@@ -144,6 +148,13 @@ function PassiveTreeViewClass:GetJewelSocketOverlay(jewel, isExpansion)
144148
end
145149
end
146150

151+
local function compareJewelsEqual(a, b)
152+
if not a or not b then
153+
return a == b
154+
end
155+
return a:BuildRaw() == b:BuildRaw()
156+
end
157+
147158
-- Returns the draw color for a node when compare overlay is active.
148159
-- Handles diff coloring for allocated/unallocated, mastery changes, and jewel socket differences.
149160
function PassiveTreeViewClass:GetCompareNodeColor(node, compareNode, spec, build, nodeDefaultColor)
@@ -160,9 +171,7 @@ function PassiveTreeViewClass:GetCompareNodeColor(node, compareNode, spec, build
160171
local pJewelId = spec.jewels[node.id]
161172
local pJewel = pJewelId and build.itemsTab.items[pJewelId]
162173
local cJewel = self:GetCompareJewel(node.id)
163-
local pName = pJewel and pJewel.name or ""
164-
local cName = cJewel and cJewel.name or ""
165-
if pName ~= cName then
174+
if not compareJewelsEqual(pJewel, cJewel) then
166175
return 0, 0, 1
167176
end
168177
end
@@ -1115,80 +1124,89 @@ function PassiveTreeViewClass:Draw(build, viewPort, inputEvents)
11151124
end
11161125

11171126
-- Draw ring overlays for jewel sockets
1127+
local function drawJewelRadius(jewel, scrX, scrY, tint)
1128+
local radData = build.data.jewelRadius[jewel.jewelRadiusIndex]
1129+
local outerSize = radData.outer * scale
1130+
local innerSize = radData.inner * scale * 1.06
1131+
SetDrawColor(tint[1], tint[2], tint[3], tint[4])
1132+
if jewel.title:match("Impossible Escape") then
1133+
-- Impossible Escape ring shows on the allocated Keystone
1134+
for keystoneName, _ in pairs(jewel.jewelData.impossibleEscapeKeystones) do
1135+
local keystone = spec.tree.keystoneMap[keystoneName]
1136+
if keystone and keystone.x and keystone.y then
1137+
innerSize = 150 * scale
1138+
local keyX, keyY = treeToScreen(keystone.x, keystone.y)
1139+
self:DrawImageRotated(self.jewelShadedOuterRing, keyX, keyY, outerSize * 2, outerSize * 2, -0.8)
1140+
self:DrawImageRotated(self.jewelShadedOuterRingFlipped, keyX, keyY, outerSize * 2, outerSize * 2, 1)
1141+
self:DrawImageRotated(self.jewelShadedInnerRing, keyX, keyY, innerSize * 2, innerSize * 2, -1.2)
1142+
self:DrawImageRotated(self.jewelShadedInnerRingFlipped, keyX, keyY, innerSize * 2, innerSize * 2, 1.0)
1143+
end
1144+
end
1145+
elseif jewel.title:match("^Brutal Restraint") then
1146+
self:DrawImageRotated(self.maraketh1, scrX, scrY, outerSize * 2, outerSize * 2, -0.7)
1147+
self:DrawImageRotated(self.maraketh2, scrX, scrY, outerSize * 2, outerSize * 2, 0.7)
1148+
elseif jewel.title:match("^Elegant Hubris") then
1149+
self:DrawImageRotated(self.eternal1, scrX, scrY, outerSize * 2, outerSize * 2, -0.7)
1150+
self:DrawImageRotated(self.eternal2, scrX, scrY, outerSize * 2, outerSize * 2, 0.7)
1151+
elseif jewel.title:match("^Glorious Vanity") then
1152+
self:DrawImageRotated(self.vaal1, scrX, scrY, outerSize * 2, outerSize * 2, -0.7)
1153+
self:DrawImageRotated(self.vaal2, scrX, scrY, outerSize * 2, outerSize * 2, 0.7)
1154+
elseif jewel.title:match("^Lethal Pride") then
1155+
self:DrawImageRotated(self.karui1, scrX, scrY, outerSize * 2, outerSize * 2, -0.7)
1156+
self:DrawImageRotated(self.karui2, scrX, scrY, outerSize * 2, outerSize * 2, 0.7)
1157+
elseif jewel.title:match("^Militant Faith") then
1158+
self:DrawImageRotated(self.templar1, scrX, scrY, outerSize * 2, outerSize * 2, -0.7)
1159+
self:DrawImageRotated(self.templar2, scrX, scrY, outerSize * 2, outerSize * 2, 0.7)
1160+
elseif jewel.title:match("^Heroic Tragedy") then
1161+
self:DrawImageRotated(self.kalguur1, scrX, scrY, outerSize * 2, outerSize * 2, -0.7)
1162+
self:DrawImageRotated(self.kalguur2, scrX, scrY, outerSize * 2, outerSize * 2, 0.7)
1163+
else
1164+
self:DrawImageRotated(self.jewelShadedOuterRing, scrX, scrY, outerSize * 2, outerSize * 2, -0.7)
1165+
self:DrawImageRotated(self.jewelShadedOuterRingFlipped, scrX, scrY, outerSize * 2, outerSize * 2, 0.7)
1166+
self:DrawImageRotated(self.jewelShadedInnerRing, scrX, scrY, innerSize * 2, innerSize * 2, -0.7)
1167+
self:DrawImageRotated(self.jewelShadedInnerRingFlipped, scrX, scrY, innerSize * 2, innerSize * 2, 0.7)
1168+
end
1169+
end
11181170
SetDrawLayer(nil, 25)
11191171
for nodeId in pairs(tree.sockets) do
11201172
local node = spec.nodes[nodeId]
11211173
if node and node.name ~= "Charm Socket" and (not node.expansionJewel or node.expansionJewel.size == 2) then
11221174
local scrX, scrY = treeToScreen(node.x, node.y)
11231175
local socket, jewel = build.itemsTab:GetSocketAndJewelForNodeID(nodeId)
1176+
local compareNode = self.compareSpec and self.compareSpec.nodes[nodeId] or nil
1177+
local cJewel = self.compareSpec and self:GetCompareJewel(nodeId) or nil
11241178
if node == hoverNode then
1125-
local isThreadOfHope = jewel and jewel.jewelRadiusLabel == "Variable"
1126-
if isThreadOfHope then
1127-
for _, radData in ipairs(build.data.jewelRadius) do
1128-
local outerSize = radData.outer * scale
1129-
local innerSize = radData.inner * scale
1130-
-- Jewel in socket is Thread of Hope or similar, draw it's annulus
1179+
local effectiveJewel = jewel or cJewel
1180+
local isThreadOfHope = effectiveJewel and effectiveJewel.jewelRadiusLabel == "Variable"
1181+
for _, radData in ipairs(build.data.jewelRadius) do
1182+
local outerSize = radData.outer * scale
1183+
local innerSize = radData.inner * scale
1184+
if isThreadOfHope then
1185+
-- Thread of Hope-like: draw the annulus (only radii with a non-zero inner)
11311186
if innerSize ~= 0 then
11321187
SetDrawColor(radData.col)
11331188
DrawImage(self.ring, scrX - outerSize, scrY - outerSize, outerSize * 2, outerSize * 2)
11341189
DrawImage(self.ring, scrX - innerSize, scrY - innerSize, innerSize * 2, innerSize * 2)
11351190
end
1136-
end
1137-
else
1138-
for _, radData in ipairs(build.data.jewelRadius) do
1139-
local outerSize = radData.outer * scale
1140-
local innerSize = radData.inner * scale
1141-
-- Jewel in socket is not Thread of Hope or similar, draw normal jewel radius
1191+
else
1192+
-- Standard jewel: draw the full-disc radii (inner == 0)
11421193
if innerSize == 0 then
11431194
SetDrawColor(radData.col)
11441195
DrawImage(self.ring, scrX - outerSize, scrY - outerSize, outerSize * 2, outerSize * 2)
11451196
end
11461197
end
11471198
end
1148-
elseif node.alloc then
1149-
if jewel and jewel.jewelRadiusIndex then
1150-
-- Draw only the selected jewel radius
1151-
local radData = build.data.jewelRadius[jewel.jewelRadiusIndex]
1152-
local outerSize = radData.outer * scale
1153-
local innerSize = radData.inner * scale * 1.06
1154-
SetDrawColor(1,1,1,0.7)
1155-
if jewel.title:match("Impossible Escape") then
1156-
-- Impossible Escape ring shows on the allocated Keystone
1157-
for keystoneName, _ in pairs(jewel.jewelData.impossibleEscapeKeystones) do
1158-
local keystone = spec.tree.keystoneMap[keystoneName]
1159-
if keystone and keystone.x and keystone.y then
1160-
innerSize = 150 * scale
1161-
local keyX, keyY = treeToScreen(keystone.x, keystone.y)
1162-
self:DrawImageRotated(self.jewelShadedOuterRing, keyX, keyY, outerSize * 2, outerSize * 2, -0.8)
1163-
self:DrawImageRotated(self.jewelShadedOuterRingFlipped, keyX, keyY, outerSize * 2, outerSize * 2, 1)
1164-
self:DrawImageRotated(self.jewelShadedInnerRing, keyX, keyY, innerSize * 2, innerSize * 2, -1.2)
1165-
self:DrawImageRotated(self.jewelShadedInnerRingFlipped, keyX, keyY, innerSize * 2, innerSize * 2, 1.0)
1166-
end
1167-
end
1168-
elseif jewel.title:match("^Brutal Restraint") then
1169-
self:DrawImageRotated(self.maraketh1, scrX, scrY, outerSize * 2, outerSize * 2, -0.7)
1170-
self:DrawImageRotated(self.maraketh2, scrX, scrY, outerSize * 2, outerSize * 2, 0.7)
1171-
elseif jewel.title:match("^Elegant Hubris") then
1172-
self:DrawImageRotated(self.eternal1, scrX, scrY, outerSize * 2, outerSize * 2, -0.7)
1173-
self:DrawImageRotated(self.eternal2, scrX, scrY, outerSize * 2, outerSize * 2, 0.7)
1174-
elseif jewel.title:match("^Glorious Vanity") then
1175-
self:DrawImageRotated(self.vaal1, scrX, scrY, outerSize * 2, outerSize * 2, -0.7)
1176-
self:DrawImageRotated(self.vaal2, scrX, scrY, outerSize * 2, outerSize * 2, 0.7)
1177-
elseif jewel.title:match("^Lethal Pride") then
1178-
self:DrawImageRotated(self.karui1, scrX, scrY, outerSize * 2, outerSize * 2, -0.7)
1179-
self:DrawImageRotated(self.karui2, scrX, scrY, outerSize * 2, outerSize * 2, 0.7)
1180-
elseif jewel.title:match("^Militant Faith") then
1181-
self:DrawImageRotated(self.templar1, scrX, scrY, outerSize * 2, outerSize * 2, -0.7)
1182-
self:DrawImageRotated(self.templar2, scrX, scrY, outerSize * 2, outerSize * 2, 0.7)
1183-
elseif jewel.title:match("^Heroic Tragedy") then
1184-
self:DrawImageRotated(self.kalguur1, scrX, scrY, outerSize * 2, outerSize * 2, -0.7)
1185-
self:DrawImageRotated(self.kalguur2, scrX, scrY, outerSize * 2, outerSize * 2, 0.7)
1186-
else
1187-
self:DrawImageRotated(self.jewelShadedOuterRing, scrX, scrY, outerSize * 2, outerSize * 2, -0.7)
1188-
self:DrawImageRotated(self.jewelShadedOuterRingFlipped, scrX, scrY, outerSize * 2, outerSize * 2, 0.7)
1189-
self:DrawImageRotated(self.jewelShadedInnerRing, scrX, scrY, innerSize * 2, innerSize * 2, -0.7)
1190-
self:DrawImageRotated(self.jewelShadedInnerRingFlipped, scrX, scrY, innerSize * 2, innerSize * 2, 0.7)
1191-
end
1199+
end
1200+
if node.alloc or (compareNode and compareNode.alloc) then
1201+
local pHasRadius = jewel and jewel.jewelRadiusIndex
1202+
local cHasRadius = cJewel and cJewel.jewelRadiusIndex
1203+
local sameJewel = compareJewelsEqual(jewel, cJewel)
1204+
if pHasRadius then
1205+
local tint = (not self.compareSpec or sameJewel) and JEWEL_RADIUS_TINT_NEUTRAL or JEWEL_RADIUS_TINT_PRIMARY_ONLY
1206+
drawJewelRadius(jewel, scrX, scrY, tint)
1207+
end
1208+
if cHasRadius and not sameJewel then
1209+
drawJewelRadius(cJewel, scrX, scrY, JEWEL_RADIUS_TINT_COMPARE_ONLY)
11921210
end
11931211
end
11941212
end
@@ -1407,30 +1425,40 @@ function PassiveTreeViewClass:AddNodeTooltip(tooltip, node, build)
14071425
self.skillTooltip:Clear()
14081426
tooltip.center = true
14091427
tooltip.maxWidth = 800
1428+
-- Appends the compare spec's jewel tooltip if it has a jewel in this allocated socket.
1429+
local function addCompareJewelSection(socket, withLabel)
1430+
local cJewel = self.compareSpec and self:GetCompareJewel(node.id)
1431+
local cAllocated = self.compareSpec and self.compareSpec.allocNodes and self.compareSpec.allocNodes[node.id]
1432+
if not cJewel or not cAllocated then
1433+
return false
1434+
end
1435+
if withLabel then
1436+
tooltip:AddSeparator(14)
1437+
tooltip:AddLine(14, colorCodes.DEXTERITY .. "Compared build:")
1438+
end
1439+
self.compareSpec.build.itemsTab:AddItemTooltip(tooltip, cJewel, socket)
1440+
return true
1441+
end
1442+
14101443
-- Special case for sockets
14111444
if node.type == "Socket" and node.alloc then
14121445
local socket, jewel = build.itemsTab:GetSocketAndJewelForNodeID(node.id)
1446+
local cJewel = self.compareSpec and self:GetCompareJewel(node.id) or nil
14131447
if jewel then
14141448
build.itemsTab:AddItemTooltip(tooltip, jewel, socket)
1415-
if node.distanceToClassStart and node.distanceToClassStart > 0 then
1416-
tooltip:AddSeparator(14)
1417-
tooltip:AddLine(16, string.format("^7Distance to start: %d", node.distanceToClassStart))
1449+
if not compareJewelsEqual(jewel, cJewel) then
1450+
addCompareJewelSection(socket, true)
14181451
end
1419-
else
1452+
elseif not addCompareJewelSection(socket, false) then
14201453
self:AddNodeName(tooltip, node, build)
14211454
end
1422-
-- Show compare build's jewel info when in overlay compare mode
1423-
if self.compareSpec then
1424-
local cJewel = self:GetCompareJewel(node.id)
1425-
local cAllocated = self.compareSpec.allocNodes and self.compareSpec.allocNodes[node.id]
1426-
if cJewel then
1427-
tooltip:AddSeparator(14)
1428-
tooltip:AddLine(16, colorCodes.WARNING .. "Compared build jewel:")
1429-
tooltip:AddLine(16, (cJewel.rarity == "UNIQUE" and colorCodes.UNIQUE or cJewel.rarity == "RARE" and colorCodes.RARE or cJewel.rarity == "MAGIC" and colorCodes.MAGIC or "^7") .. cJewel.name)
1430-
elseif cAllocated then
1431-
tooltip:AddSeparator(14)
1432-
tooltip:AddLine(16, colorCodes.WARNING .. "Compared build: ^7(empty socket)")
1433-
end
1455+
if self.compareSpec and self.compareSpec.allocNodes and self.compareSpec.allocNodes[node.id] and not cJewel then
1456+
tooltip:AddSeparator(14)
1457+
tooltip:AddLine(16, colorCodes.WARNING .. "Compared build: ^7(empty socket)")
1458+
end
1459+
if node.distanceToClassStart and node.distanceToClassStart > 0 then
1460+
tooltip:AddSeparator(14)
1461+
tooltip:AddLine(16, string.format("^7Distance to start: %d", node.distanceToClassStart))
14341462
end
14351463
tooltip:AddSeparator(14)
14361464
if socket:IsEnabled() then
@@ -1441,16 +1469,9 @@ function PassiveTreeViewClass:AddNodeTooltip(tooltip, node, build)
14411469
end
14421470

14431471
-- For unallocated sockets, show compare build's jewel if it has one
1444-
if node.type == "Socket" and not node.alloc and self.compareSpec then
1445-
local cJewel = self:GetCompareJewel(node.id)
1446-
local cItemsTab = self.compareSpec.build and self.compareSpec.build.itemsTab
1447-
local cAllocated = self.compareSpec.allocNodes and self.compareSpec.allocNodes[node.id]
1448-
if cJewel and cAllocated then
1449-
-- Show the compare build's jewel tooltip instead of generic socket info
1450-
local socket = build.itemsTab:GetSocketAndJewelForNodeID(node.id)
1451-
cItemsTab:AddItemTooltip(tooltip, cJewel, socket)
1452-
tooltip:AddSeparator(14)
1453-
tooltip:AddLine(14, colorCodes.DEXTERITY .. "Jewel from compared build")
1472+
if node.type == "Socket" and not node.alloc then
1473+
local socket = build.itemsTab:GetSocketAndJewelForNodeID(node.id)
1474+
if addCompareJewelSection(socket, false) then
14541475
tooltip:AddLine(14, colorCodes.TIP.."Tip: Hold Shift or Ctrl to hide this tooltip.")
14551476
return
14561477
end

0 commit comments

Comments
 (0)