From ce84f8e4c23b33f76c4fb48e080871e3f1a807cb Mon Sep 17 00:00:00 2001 From: Mickael Cagnion Date: Fri, 2 Oct 2026 08:46:05 +0200 Subject: [PATCH] Fix saved rolls moving to other mod lines when loading builds Since PR 10039, ParseRaw re-sorts explicit mod lines into stat order whenever advancedCopy is set. Any item text PoB saves with a {range:} tag or a ranged line sets it, so saved items are re-sorted on load. ItemsTab:Load then applies the saved ModRange entries by line position, and builds saved before that change load with rolls on the wrong lines, for example the spell block roll of Rathpith Globe moving onto its lightning resistance line. Only re-sort text that carries the in-game advanced copy headers. --- spec/System/TestItemParse_spec.lua | 26 ++++++++++++++++++++++++++ src/Classes/Item.lua | 8 ++++++-- 2 files changed, 32 insertions(+), 2 deletions(-) diff --git a/spec/System/TestItemParse_spec.lua b/spec/System/TestItemParse_spec.lua index 66bc1cdb394..9018545932e 100644 --- a/spec/System/TestItemParse_spec.lua +++ b/spec/System/TestItemParse_spec.lua @@ -930,6 +930,32 @@ describe("TestAdvancedItemParse #item", function() }, lines(item.explicitModLines)) end) + it("keeps saved rolls on their lines for a unique saved out of stat order", function() + loadBuildFromXML([[ + + + + +Rarity: UNIQUE +Geofri's Sanctuary +Elegant Ringmail +Implicits: 0 +{range:0.2}+(60-70) to maximum Life +{range:0.9}(50-75)% increased Armour and Energy Shield + + + + + +]]) + + local ranges = { } + for _, modLine in ipairs(build.itemsTab.items[1].explicitModLines) do + ranges[modLine.line] = modLine.range + end + assert.are.equals(0.9, ranges["(50-75)% increased Armour and Energy Shield"]) + end) + it("keeps the selected value from advanced-copy enum ranges", function() local item = new("Item"):Item([[ Rarity: Unique diff --git a/src/Classes/Item.lua b/src/Classes/Item.lua index 1618e93e018..edaabe4bb1a 100644 --- a/src/Classes/Item.lua +++ b/src/Classes/Item.lua @@ -501,6 +501,9 @@ function ItemClass:ParseRaw(raw, rarity, highQuality) -- assume an item to be an advanced copy format if either has mod roll information, a modifier -- line with a range, or advanced copy lines self.advancedCopy = false + -- Only in-game advanced copy text (with "{ ... }" modifier headers) is re-sorted into stat order. + -- Text written by PoB keeps its line order, which the ModRange entries of saved builds refer to. + local hasModifierHeaders = false self.modMagnitudeMods = {} local implicitLines = 0 self.variantList = nil @@ -574,6 +577,7 @@ function ItemClass:ParseRaw(raw, rarity, highQuality) elseif line:match("^{ ") then -- We're parsing advanced copy/paste format self.advancedCopy = true + hasModifierHeaders = true linePrefix = "" linePostfix = "" self.crafted = true @@ -1357,7 +1361,7 @@ function ItemClass:ParseRaw(raw, rarity, highQuality) ::continue:: l = l + 1 end - if self.advancedCopy and (self.rarity == "UNIQUE" or self.rarity == "RELIC") then + if hasModifierHeaders and (self.rarity == "UNIQUE" or self.rarity == "RELIC") then if not uniqueModStatOrder then uniqueModStatOrder = { exact = { }, normalised = { } } for _, mod in pairs(data.itemMods.ItemExclusive) do @@ -1468,7 +1472,7 @@ function ItemClass:ParseRaw(raw, rarity, highQuality) end end end - if self.advancedCopy and #self.explicitModLines > 1 then + if hasModifierHeaders and #self.explicitModLines > 1 then sortCraftedModLines(self.explicitModLines) end self.affixLimit = 0