From 8c2d59b870d1c18d9619417c19dd12e756f8cf60 Mon Sep 17 00:00:00 2001 From: vaisest <4550061+vaisest@users.noreply.github.com> Date: Sat, 12 Sep 2026 21:59:41 +0300 Subject: [PATCH 1/3] Show red text for unsupported rune mods --- src/Classes/ItemsTab.lua | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/Classes/ItemsTab.lua b/src/Classes/ItemsTab.lua index 1ee55268d2..b92c803cf7 100644 --- a/src/Classes/ItemsTab.lua +++ b/src/Classes/ItemsTab.lua @@ -813,7 +813,11 @@ holding Shift will put it in the second.]]) tooltip:AddLine(14, "^7" .. s_format("Requires: Level %d", value.req)) end for _, line in ipairs(value.lines) do - tooltip:AddLine(14, colorCodes.MAGIC .. line) + -- rune mod lines don't need a range applied to them + local stripped = line:gsub("Bonded: ", "") + local modList, extra = modLib.parseMod(stripped) + local colour = ((not not modList) and not extra) and colorCodes.MAGIC or colorCodes.UNSUPPORTED + tooltip:AddLine(14, colour .. line) end -- Adding Comparison local compLines = { type = "Rune" } From af5408e17ff5990efba3b3b619d96245231a8618 Mon Sep 17 00:00:00 2001 From: LocalIdentity <31035929+LocalIdentity@users.noreply.github.com> Date: Tue, 29 Sep 2026 19:02:34 +1000 Subject: [PATCH 2/3] Fix multiline rune modifier tooltip colours --- spec/System/TestItemsTab_spec.lua | 25 +++++++++++++++++++++++++ src/Classes/ItemsTab.lua | 29 ++++++++++++++++++++++++----- 2 files changed, 49 insertions(+), 5 deletions(-) diff --git a/spec/System/TestItemsTab_spec.lua b/spec/System/TestItemsTab_spec.lua index a5dd31920e..1e193465b4 100644 --- a/spec/System/TestItemsTab_spec.lua +++ b/spec/System/TestItemsTab_spec.lua @@ -16,6 +16,31 @@ describe("TestItemsTab", function() runCallback("OnFrame") end) + it("colours complete multiline rune modifiers without joining normal and Bonded lines", function() + local firstLine = "Increases and Reductions to Spell Damage also" + local continuation = " apply to Attacks" + local supported = "+10 to maximum Life" + local blue, red = colorCodes.MAGIC, colorCodes.UNSUPPORTED + local cases = { + { lines = { firstLine, continuation, supported }, colours = { blue, blue, blue } }, + { lines = { "Bonded: " .. firstLine, "Bonded: " .. continuation, supported }, colours = { blue, blue, blue } }, + { lines = { firstLine, "Bonded: " .. continuation }, colours = { red, red } }, + { lines = { "Bonded: " .. firstLine, continuation }, colours = { red, red } }, + { lines = { firstLine, supported, "Bonded: " .. supported, continuation }, colours = { red, blue, blue, red } }, + } + local comparison = stub(build.itemsTab, "AddModComparisonTooltip") + for _, case in ipairs(cases) do + local tooltip = new("Tooltip"):Tooltip() + local value = { name = "Test Rune", req = 1, lines = case.lines } + build.itemsTab.controls.displayItemRune1.tooltipFunc(tooltip, "HOVER", 1, value) + assert.are.equal(#case.lines + 1, #tooltip.lines) + for index, line in ipairs(case.lines) do + assert.are.equal(case.colours[index] .. line, tooltip.lines[index + 1].text) + end + end + comparison:revert() + end) + it("compares weapons in the skill's assigned set, with Both following the Items tab", function() build.skillsTab:PasteSocketGroup("Spark 20/0 1") local group = build.skillsTab.displayGroup diff --git a/src/Classes/ItemsTab.lua b/src/Classes/ItemsTab.lua index b92c803cf7..3b05bb4473 100644 --- a/src/Classes/ItemsTab.lua +++ b/src/Classes/ItemsTab.lua @@ -812,12 +812,31 @@ holding Shift will put it in the second.]]) if value.req > 1 then tooltip:AddLine(14, "^7" .. s_format("Requires: Level %d", value.req)) end - for _, line in ipairs(value.lines) do - -- rune mod lines don't need a range applied to them - local stripped = line:gsub("Bonded: ", "") + local function stripRuneLine(line) + local stripped, bonded = line:gsub("^Bonded:%s*", "") + return stripped:match("^%s*(.-)%s*$"), bonded + end + local lineIndex = 1 + while lineIndex <= #value.lines do + -- Match item parsing: retry unsupported text with its continuation line. + local stripped, bonded = stripRuneLine(value.lines[lineIndex]) local modList, extra = modLib.parseMod(stripped) - local colour = ((not not modList) and not extra) and colorCodes.MAGIC or colorCodes.UNSUPPORTED - tooltip:AddLine(14, colour .. line) + local lineCount = 1 + if (not modList or extra) and value.lines[lineIndex + 1] then + local nextLine, nextBonded = stripRuneLine(value.lines[lineIndex + 1]) + if bonded == nextBonded then + local combinedMods, combinedExtra = modLib.parseMod(stripped .. " " .. nextLine, true) + if combinedMods and not combinedExtra then + modList, extra = combinedMods, combinedExtra + lineCount = 2 + end + end + end + local colour = (modList and not extra) and colorCodes.MAGIC or colorCodes.UNSUPPORTED + for index = lineIndex, lineIndex + lineCount - 1 do + tooltip:AddLine(14, colour .. value.lines[index]) + end + lineIndex = lineIndex + lineCount end -- Adding Comparison local compLines = { type = "Rune" } From e0e89df93b32aad614011cf19ffbecf182ab5a1e Mon Sep 17 00:00:00 2001 From: LocalIdentity Date: Tue, 29 Sep 2026 19:08:35 +1000 Subject: [PATCH 3/3] Handle bonded lines better --- spec/System/TestItemsTab_spec.lua | 41 ++++++++++++++++++++++++------- src/Classes/ItemsTab.lua | 31 ++++++++++++----------- 2 files changed, 49 insertions(+), 23 deletions(-) diff --git a/spec/System/TestItemsTab_spec.lua b/spec/System/TestItemsTab_spec.lua index 1e193465b4..4ef9bfe091 100644 --- a/spec/System/TestItemsTab_spec.lua +++ b/spec/System/TestItemsTab_spec.lua @@ -17,24 +17,32 @@ describe("TestItemsTab", function() end) it("colours complete multiline rune modifiers without joining normal and Bonded lines", function() - local firstLine = "Increases and Reductions to Spell Damage also" - local continuation = " apply to Attacks" - local supported = "+10 to maximum Life" + local firstLine = { line = "Increases and Reductions to Spell Damage also", bonded = false } + local continuation = { line = " apply to Attacks", bonded = false } + local supported = { line = "+10 to maximum Life", bonded = false } + local function bonded(modLine) + return { line = modLine.line, bonded = true } + end local blue, red = colorCodes.MAGIC, colorCodes.UNSUPPORTED local cases = { { lines = { firstLine, continuation, supported }, colours = { blue, blue, blue } }, - { lines = { "Bonded: " .. firstLine, "Bonded: " .. continuation, supported }, colours = { blue, blue, blue } }, - { lines = { firstLine, "Bonded: " .. continuation }, colours = { red, red } }, - { lines = { "Bonded: " .. firstLine, continuation }, colours = { red, red } }, - { lines = { firstLine, supported, "Bonded: " .. supported, continuation }, colours = { red, blue, blue, red } }, + { lines = { firstLine, continuation }, displayLines = { "First display line", "Continuation display line" }, colours = { blue, blue } }, + { lines = { bonded(firstLine), bonded(continuation), supported }, colours = { blue, blue, blue } }, + { lines = { firstLine, bonded(continuation) }, colours = { red, red } }, + { lines = { bonded(firstLine), continuation }, colours = { red, red } }, + { lines = { firstLine, supported, bonded(supported), continuation }, colours = { red, blue, blue, red } }, } local comparison = stub(build.itemsTab, "AddModComparisonTooltip") for _, case in ipairs(cases) do local tooltip = new("Tooltip"):Tooltip() - local value = { name = "Test Rune", req = 1, lines = case.lines } + local value = { name = "Test Rune", req = 1, lines = { }, modLines = case.lines } + for _, modLine in ipairs(case.lines) do + table.insert(value.lines, (modLine.bonded and "Bonded: " or "") .. modLine.line) + end + value.lines = case.displayLines or value.lines build.itemsTab.controls.displayItemRune1.tooltipFunc(tooltip, "HOVER", 1, value) assert.are.equal(#case.lines + 1, #tooltip.lines) - for index, line in ipairs(case.lines) do + for index, line in ipairs(value.lines) do assert.are.equal(case.colours[index] .. line, tooltip.lines[index + 1].text) end end @@ -896,6 +904,10 @@ Ruby]]) assert.are.equals(2, #ticabaRune.lines) assert.are.equals("Hits against you have 50% reduced Critical Damage Bonus", ticabaRune.lines[1]) assert.are.equals("Hits against you have 50% reduced Critical Damage Bonus", ticabaRune.lines[2]) + assert.are.same({ + { line = ticabaRune.lines[1], bonded = false }, + { line = ticabaRune.lines[2], bonded = false }, + }, ticabaRune.modLines) end) it("keeps pure Bonded slot entries and uses the regular rune mod as the dropdown label", function () @@ -911,6 +923,17 @@ Ruby]]) for _, rune in ipairs(build.itemsTab:GetValidRunesForItem(item)) do if rune.name == "Perfect Resolve Rune" then assert.are.equals("+15 to Intelligence", rune.label) + assert.are.equal(#rune.lines, #rune.modLines) + local bondedLines = { } + for index, modLine in ipairs(rune.modLines) do + assert.are.equal((modLine.bonded and "Bonded: " or "") .. modLine.line, rune.lines[index]) + if modLine.bonded then + table.insert(bondedLines, modLine.line) + else + assert.are.equal("+15 to Intelligence", modLine.line) + end + end + assert.are.same({ "+50 to maximum Energy Shield" }, bondedLines) return end end diff --git a/src/Classes/ItemsTab.lua b/src/Classes/ItemsTab.lua index 3b05bb4473..6ae64b84ad 100644 --- a/src/Classes/ItemsTab.lua +++ b/src/Classes/ItemsTab.lua @@ -812,20 +812,18 @@ holding Shift will put it in the second.]]) if value.req > 1 then tooltip:AddLine(14, "^7" .. s_format("Requires: Level %d", value.req)) end - local function stripRuneLine(line) - local stripped, bonded = line:gsub("^Bonded:%s*", "") - return stripped:match("^%s*(.-)%s*$"), bonded - end local lineIndex = 1 - while lineIndex <= #value.lines do + while lineIndex <= #value.modLines do -- Match item parsing: retry unsupported text with its continuation line. - local stripped, bonded = stripRuneLine(value.lines[lineIndex]) - local modList, extra = modLib.parseMod(stripped) + local modLine = value.modLines[lineIndex] + local line = modLine.line:match("^%s*(.-)%s*$") + local modList, extra = modLib.parseMod(line) local lineCount = 1 - if (not modList or extra) and value.lines[lineIndex + 1] then - local nextLine, nextBonded = stripRuneLine(value.lines[lineIndex + 1]) - if bonded == nextBonded then - local combinedMods, combinedExtra = modLib.parseMod(stripped .. " " .. nextLine, true) + local nextModLine = value.modLines[lineIndex + 1] + if (not modList or extra) and nextModLine then + if modLine.bonded == nextModLine.bonded then + local nextLine = nextModLine.line:match("^%s*(.-)%s*$") + local combinedMods, combinedExtra = modLib.parseMod(line .. " " .. nextLine, true) if combinedMods and not combinedExtra then modList, extra = combinedMods, combinedExtra lineCount = 2 @@ -2246,18 +2244,21 @@ function ItemsTabClass:UpdateAffixControls() self:UpdateCustomControls() end -runeModLines = { { name = "None", label = "None", lines = { "None" }, mods = { }, req = 1, order = -1, slot = "None", group = -1, isSocketBound = false } } +runeModLines = { { name = "None", label = "None", lines = { "None" }, modLines = { }, mods = { }, req = 1, order = -1, slot = "None", group = -1, isSocketBound = false } } for name, runeMods in pairs(data.itemMods.Runes) do -- Some runes have multiple mod lines; insert each as separate entry for slotType, runeMod in pairs(runeMods) do -- Bonded stats are stored separately for calculation, but remain part of the -- visible rune description and are prefixed only at this presentation boundary. local lines = { } + local modLines = { } for _, line in ipairs(runeMod) do t_insert(lines, line) + t_insert(modLines, { line = line, bonded = false }) end for _, line in ipairs(runeMod.bonded or { }) do t_insert(lines, "Bonded: " .. line) + t_insert(modLines, { line = line, bonded = true }) end local mods = { } for _, line in ipairs(runeMod) do @@ -2267,7 +2268,7 @@ for name, runeMods in pairs(data.itemMods.Runes) do end end local order = (runeMod.statOrder and runeMod.statOrder[1]) or (runeMod.bonded and runeMod.bonded.statOrder and runeMod.bonded.statOrder[1]) or 0 - t_insert(runeModLines, { name = name, label = runeMod[1], lines = lines, mods = mods, req = runeMod.levelReq, order = order, slot = slotType, type = runeMod.type, group = #lines, isSocketBound = runeMod.isSocketBound, localMod = runeMod.localMod, limit = runeMod.limit, canSocketInChakraSlots = runeMod.canSocketInChakraSlots, canSocketInUniqueItems = runeMod.canSocketInUniqueItems, canSocketInJewellery = runeMod.canSocketInJewellery }) + t_insert(runeModLines, { name = name, label = runeMod[1], lines = lines, modLines = modLines, mods = mods, req = runeMod.levelReq, order = order, slot = slotType, type = runeMod.type, group = #lines, isSocketBound = runeMod.isSocketBound, localMod = runeMod.localMod, limit = runeMod.limit, canSocketInChakraSlots = runeMod.canSocketInChakraSlots, canSocketInUniqueItems = runeMod.canSocketInUniqueItems, canSocketInJewellery = runeMod.canSocketInJewellery }) end end table.sort(runeModLines, function(a, b) @@ -2303,12 +2304,14 @@ function ItemsTabClass:GetValidRunesForItem(item) if not addedRune then addedRune = copyTable(rune, true) addedRune.lines = { } + addedRune.modLines = { } t_insert(runes, addedRune) addedRunes[rune.name] = addedRune end addedRune.label = addedRune.label or rune.label - for _, line in ipairs(rune.lines) do + for index, line in ipairs(rune.lines) do t_insert(addedRune.lines, line) + t_insert(addedRune.modLines, rune.modLines[index]) end end end