From 18b04fdf0f6a5366ab79ff02398e60803eaae563 Mon Sep 17 00:00:00 2001 From: Wires77 Date: Tue, 24 Mar 2026 10:58:55 -0500 Subject: [PATCH 1/7] Add initial test items to feel out the format --- spec/System/TestItemParse_spec.lua | 148 +++++++++++++++++++++++++++++ 1 file changed, 148 insertions(+) diff --git a/spec/System/TestItemParse_spec.lua b/spec/System/TestItemParse_spec.lua index 4890207cea..ba8b50939f 100644 --- a/spec/System/TestItemParse_spec.lua +++ b/spec/System/TestItemParse_spec.lua @@ -465,3 +465,151 @@ describe("TestItemParse", function() assert.are.equal("+1500 to Armour", item.buffModLines[1].line) end) end) + +describe("TestAdvancedItemParse", function() + it("parses item", function() + local advancedItem = new("Item", [[ + Item Class: Belts + Rarity: Rare + Beast Snare + Cord Belt + -------- + Requirements: + Level: 51 + -------- + Item Level: 83 + -------- + Allocates Surveillance (enchant) + -------- + { Implicit Modifier } + Can be Anointed + -------- + { Fractured Prefix Modifier "Thorny" (Tier: 2) — Damage, Physical } + Reflects 3(1-4) Physical Damage to Melee Attackers + { Prefix Modifier "Fecund" (Tier: 1) — Life } + +142(130-144) to maximum Life + { Prefix Modifier "Glowing" (Tier: 9) — Defences, Energy Shield } + +15(13-15) to maximum Energy Shield + { Suffix Modifier "of the Tempest" (Tier: 4) — Elemental, Lightning, Resistance } + +34(30-35)% to Lightning Resistance + { Master Crafted Suffix Modifier "of Craft" (Rank: 3) — Elemental, Cold, Resistance } + +35(29-35)% to Cold Resistance + -------- + Fractured Item + ]]) + + local equivalentCraftItem = new("Item", [[ + Beast Snare + Cord Belt + Crafted: true + Prefix: {range:0.599}AttackerTakesDamage1 + Prefix: {range:0.859}IncreasedLife9 + Prefix: {range:0.845}IncreasedEnergyShield4 + Suffix: {range:0.732}LightningResist5 + Suffix: None + Suffix: None + LevelReq: 51 + Implicits: 1 + {crafted}Allocates Surveillance + Can be Anointed + +15 to maximum Energy Shield + +142 to maximum Life + +34% to Lightning Resistance + Reflects 3 Physical Damage to Melee Attackers + {tags:elemental,cold,resistance}{crafted}{range:1}+(29-35)% to Cold Resistance + ]]) + + local catalyst = new("Item", [[ + Item Class: Amulets + Rarity: Unique + Astramentis + Onyx Amulet + -------- + Quality (Attribute Modifiers): +20% (augmented) + -------- + Requirements: + Level: 20 + -------- + Item Level: 80 + -------- + Allocates Weathered Hunter (enchant) + -------- + { Implicit Modifier — Attribute — 20% Increased } + +16(10-16) to all Attributes + (Attributes are Strength, Dexterity, and Intelligence) + -------- + { Unique Modifier — Attribute — 20% Increased } + +86(80-100) to all Attributes + (Attributes are Strength, Dexterity, and Intelligence) + { Unique Modifier — Physical, Attack } + -4 Physical Damage taken from Attack Hits + -------- + Mindless rage will shake the world, + Cunning lies will bend it. + Reckless haste will break the world, + And into darkness send it. + -------- + Note: ~b/o 50 chaos + ]]) + local godTestItem = new("Item", [[ + Item Class: Sceptres + Rarity: Unique + Nebulis + Synthesised Void Sceptre + -------- + Sceptre + Physical Damage: 50-76 + Critical Strike Chance: 7.30% + Attacks per Second: 1.25 + Weapon Range: 1.1 metres + Memory Strands: 58 + -------- + Requirements: + Level: 68 + Str: 104 + Int: 122 + -------- + Sockets: B R + -------- + Item Level: 87 + -------- + +30% to Fire Resistance (scourge) + 22% reduced Global Defences (scourge) + (Armour, Evasion Rating and Energy Shield are the standard Defences) (scourge) + -------- + 8% increased Explicit Cold Modifier magnitudes (enchant) + Has 1 White Socket (enchant) + -------- + { Searing Exarch Implicit Modifier (Lesser) } + Tempest Shield has 15(15-17)% increased Buff Effect + { Implicit Modifier — Damage, Critical — 106% Increased } + +15(15-17)% to Global Critical Strike Multiplier + -------- + { Prefix Modifier "Freezing" (Tier: 5) — Damage, Elemental, Cold, Caster — 8% Increased } + Adds 17(16-20) to 35(30-36) Cold Damage to Spells + { Unique Modifier } + 106(60-120)% increased Implicit Modifier magnitudes — Unscalable Value + (Implicit Modifiers are those that come from an item's type, rather than its random properties) + { Master Crafted Suffix Modifier "of Craft" (Rank: 3) — Elemental, Cold, Resistance } + +35(29-35)% to Cold Resistance + { Fractured Prefix Modifier "Thorny" (Tier: 2) — Damage, Physical } + Reflects 3(1-4) Physical Damage to Melee Attackers + { Prefix Modifier "Veiled" } + Veiled Prefix + Searing Exarch Item + -------- + { Allocated Crucible Passive Skill (Tier: 2) } + Adds 2 to 6 Physical Damage to Spells + -------- + Synthesised Item + -------- + Corrupted + -------- + Scourged + -------- + Hinekora's Lock + -------- + Note: ~b/o 2 chaos + ]]) + end) +end) \ No newline at end of file From d04aee28c57467c9ea687860c537cb78b5beae9e Mon Sep 17 00:00:00 2001 From: Wires77 Date: Sun, 19 Apr 2026 07:23:06 -0500 Subject: [PATCH 2/7] Add actual test --- spec/System/TestItemParse_spec.lua | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/spec/System/TestItemParse_spec.lua b/spec/System/TestItemParse_spec.lua index ba8b50939f..f5cf416881 100644 --- a/spec/System/TestItemParse_spec.lua +++ b/spec/System/TestItemParse_spec.lua @@ -519,6 +519,8 @@ describe("TestAdvancedItemParse", function() {tags:elemental,cold,resistance}{crafted}{range:1}+(29-35)% to Cold Resistance ]]) + assert.are.equals(advancedItem:BuildRaw(), equivalentCraftItem:BuildRaw()) + local catalyst = new("Item", [[ Item Class: Amulets Rarity: Unique @@ -587,6 +589,12 @@ describe("TestAdvancedItemParse", function() -------- { Prefix Modifier "Freezing" (Tier: 5) — Damage, Elemental, Cold, Caster — 8% Increased } Adds 17(16-20) to 35(30-36) Cold Damage to Spells + { Prefix Modifier "Beetle's" (Tier: 6) — Defences, Armour } + 9(6-13)% increased Armour + 7(6-7)% increased Stun and Block Recovery + { Master Crafted Prefix Modifier "Upgraded" — Life, Defences, Armour } + 21(18-21)% increased Armour + +18(17-19) to maximum Life { Unique Modifier } 106(60-120)% increased Implicit Modifier magnitudes — Unscalable Value (Implicit Modifiers are those that come from an item's type, rather than its random properties) From 4e3e1d75957f73f008974babc68dd671963a0cbd Mon Sep 17 00:00:00 2001 From: Wires77 Date: Mon, 4 May 2026 00:10:08 -0500 Subject: [PATCH 3/7] Initial pass at parsing advanced copy/paste. Still need to handle unscaled values --- src/Classes/Item.lua | 54 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/src/Classes/Item.lua b/src/Classes/Item.lua index 4597dde52b..86ecb3f7da 100644 --- a/src/Classes/Item.lua +++ b/src/Classes/Item.lua @@ -369,6 +369,7 @@ function ItemClass:ParseRaw(raw, rarity, highQuality) local deferJewelRadiusIndexAssignment local gameModeStage = "FINDIMPLICIT" local foundExplicit, foundImplicit + local linePrefix = "" while self.rawLines[l] do local line = self.rawLines[l] @@ -398,7 +399,42 @@ function ItemClass:ParseRaw(raw, rarity, highQuality) self[influenceItemMap[line]] = true elseif line == "Requirements:" then -- nothing to do + elseif line:match("^{ ") then + -- We're parsing advanced copy/paste format + linePrefix = "" + self.crafted = true + local fullModName, modTags, increasedAmt = line:match("^{ (.-) %- (.-) %- (%d*).*}$") + if not fullModName then + fullModName, modTags = line:match("^{ (.-) %- (.-) }$") + end + if not fullModName then + fullModName = line:match("^{ (.-) }$") + end + local modName = fullModName:match("^.*Modifier \"(.*)\"") + if modName and modName ~= "" then + for modId, modData in pairs(self.affixes) do + if modData.affix == modName then + if modData.type == "Prefix" then + pendingAffix = { modId = modId, table = self.prefixes } + elseif modData.type == "Suffix" then + pendingAffix = { modId = modId, table = self.suffixes } + end + end + end + end + local possibleLineFlags = fullModName:match("(.*)Modifier.*") + if possibleLineFlags then + for flag in possibleLineFlags:gmatch("%a+") do + if lineFlags[flag:lower()] then + linePrefix = linePrefix .. "{" .. flag:lower() .. "}" + end + end + end + if modTags and modTags ~= "" then + linePrefix = linePrefix .. "{tags:" .. modTags:lower():gsub("%s+", "") .. "}" + end else + line = linePrefix .. line if self.checkSection then if gameModeStage == "IMPLICIT" then if foundImplicit then @@ -733,6 +769,24 @@ function ItemClass:ParseRaw(raw, rarity, highQuality) gameModeStage = "IMPLICIT" end local catalystScalar = getCatalystScalar(self.catalyst, modLine.modTags, self.catalystQuality) + for value, range in line:gmatch("(%d+)%((%d+%-%d+)%)") do + -- Find advanced copy paste format: 45(40-50) + if pendingAffix then + local min, max = range:match("(%d+)%-(%d+)") + local numRange = round((value - min) / (tonumber(max) - min), 3) + line = line:gsub(value .. "%(" .. range:gsub("%-", "%%-") .. "%)", value) + t_insert(pendingAffix.table, { + modId = pendingAffix.modId, + range = tonumber(numRange), + }) + pendingAffix = nil + else + local min, max = range:match("(%d+)%-(%d+)") + local numRange = round((value - min) / (tonumber(max) - min), 3) + modLine.range = tonumber(numRange) + line = line:gsub(value .. "%(" .. range:gsub("%-", "%%-") .. "%)", "(" .. range .. ")") + end + end local rangedLine = itemLib.applyRange(line, 1, catalystScalar) local modList, extra = modLib.parseMod(rangedLine) if (not modList or extra) and self.rawLines[l+1] then From c28b0d692761d72e18bba720c2dcc8402684aa8b Mon Sep 17 00:00:00 2001 From: Wires77 Date: Thu, 14 May 2026 13:09:44 -0500 Subject: [PATCH 4/7] Parse advanced unique item with catalyst --- spec/System/TestItemParse_spec.lua | 38 ++++++++++++++++++++++-------- src/Classes/Item.lua | 13 +++++++++- 2 files changed, 40 insertions(+), 11 deletions(-) diff --git a/spec/System/TestItemParse_spec.lua b/spec/System/TestItemParse_spec.lua index f5cf416881..61aa172f7a 100644 --- a/spec/System/TestItemParse_spec.lua +++ b/spec/System/TestItemParse_spec.lua @@ -466,7 +466,7 @@ describe("TestItemParse", function() end) end) -describe("TestAdvancedItemParse", function() +describe("TestAdvancedItemParse #item", function() it("parses item", function() local advancedItem = new("Item", [[ Item Class: Belts @@ -502,21 +502,23 @@ describe("TestAdvancedItemParse", function() Beast Snare Cord Belt Crafted: true - Prefix: {range:0.599}AttackerTakesDamage1 - Prefix: {range:0.859}IncreasedLife9 - Prefix: {range:0.845}IncreasedEnergyShield4 - Suffix: {range:0.732}LightningResist5 + Prefix: {range:0.667}AttackerTakesDamage1 + Prefix: {range:0.857}IncreasedLife9 + Prefix: {range:1}IncreasedEnergyShield4 + Suffix: {range:0.8}LightningResist5 Suffix: None Suffix: None + Item Level: 83 LevelReq: 51 - Implicits: 1 + Implicits: 2 {crafted}Allocates Surveillance Can be Anointed - +15 to maximum Energy Shield - +142 to maximum Life - +34% to Lightning Resistance - Reflects 3 Physical Damage to Melee Attackers + {tags:damage,physical}{fractured}Reflects 3 Physical Damage to Melee Attackers + {tags:life}+142 to maximum Life + {tags:defences,energyshield}+15 to maximum Energy Shield + {tags:elemental,lightning,resistance}+34% to Lightning Resistance {tags:elemental,cold,resistance}{crafted}{range:1}+(29-35)% to Cold Resistance + Fractured Item ]]) assert.are.equals(advancedItem:BuildRaw(), equivalentCraftItem:BuildRaw()) @@ -553,6 +555,22 @@ describe("TestAdvancedItemParse", function() -------- Note: ~b/o 50 chaos ]]) + + local equivalentCatalystItem = new("Item", [[ + Astramentis + Onyx Amulet + Catalyst: Intrinsic + CatalystQuality: 20 + Item Level: 80 + LevelReq: 20 + Implicits: 2 + {crafted}Allocates Weathered Hunter + {tags:attribute}{range:1}+(10-16) to all Attributes + {tags:attribute}{range:0.3}+(80-100) to all Attributes + {tags:physical,attack}-4 Physical Damage taken from Attack Hits + ]]) + + assert.are.equals(catalyst:BuildRaw(), equivalentCatalystItem:BuildRaw()) local godTestItem = new("Item", [[ Item Class: Sceptres Rarity: Unique diff --git a/src/Classes/Item.lua b/src/Classes/Item.lua index 86ecb3f7da..214113ebe2 100644 --- a/src/Classes/Item.lua +++ b/src/Classes/Item.lua @@ -12,6 +12,7 @@ local m_floor = math.floor local dmgTypeList = {"Physical", "Lightning", "Cold", "Fire", "Chaos"} local catalystList = {"Abrasive", "Accelerating", "Fertile", "Imbued", "Intrinsic", "Noxious", "Prismatic", "Tempering", "Turbulent", "Unstable"} +local catalystDescriptorList = {"Attack", "Speed", "Life and Mana", "Caster", "Attribute", "Physical and Chaos", "Resistance", "Defense", "Elemental", "Critical"} local catalystTags = { { "attack" }, { "speed" }, @@ -399,6 +400,8 @@ function ItemClass:ParseRaw(raw, rarity, highQuality) self[influenceItemMap[line]] = true elseif line == "Requirements:" then -- nothing to do + elseif line:match("^%(") then + -- Reminder text, nothing to parse elseif line:match("^{ ") then -- We're parsing advanced copy/paste format linePrefix = "" @@ -453,7 +456,7 @@ function ItemClass:ParseRaw(raw, rarity, highQuality) end self.checkSection = false end - local specName, specVal = line:match("^([%a ]+:?): (.+)$") + local specName, specVal = line:match("^([%a %(%)]+:?): (.+)$") if specName then if specName == "Class:" then specName = "Requires Class" @@ -468,6 +471,13 @@ function ItemClass:ParseRaw(raw, rarity, highQuality) self.itemLevel = specToNumber(specVal) elseif specName == "Requires Class" then self.classRestriction = specVal + elseif specName:match("Quality %(%a+ Modifiers%)") then + self.catalystQuality = specToNumber(specVal:match("(%d+)%%")) + for i=1, #catalystDescriptorList do + if specName:match("Quality %(([%a%s]+) Modifiers%)") == catalystDescriptorList[i] then + self.catalyst = i + end + end elseif specName == "Quality" then self.quality = specToNumber(specVal) elseif specName == "Sockets" then @@ -784,6 +794,7 @@ function ItemClass:ParseRaw(raw, rarity, highQuality) local min, max = range:match("(%d+)%-(%d+)") local numRange = round((value - min) / (tonumber(max) - min), 3) modLine.range = tonumber(numRange) + --ConPrintf(modLine.range) line = line:gsub(value .. "%(" .. range:gsub("%-", "%%-") .. "%)", "(" .. range .. ")") end end From d8011d0d88f9ce7ee405879be9723459461b3ba0 Mon Sep 17 00:00:00 2001 From: Wires77 Date: Sat, 16 May 2026 21:57:21 -0500 Subject: [PATCH 5/7] Parse unscalable mods, increase mod precision, remove invalid mods from consideration, add more tests --- spec/System/TestItemParse_spec.lua | 242 +++++++++++++++++++++-------- src/Classes/Item.lua | 91 ++++++++--- src/Classes/ItemsTab.lua | 23 +-- 3 files changed, 254 insertions(+), 102 deletions(-) diff --git a/spec/System/TestItemParse_spec.lua b/spec/System/TestItemParse_spec.lua index 61aa172f7a..9d976153fc 100644 --- a/spec/System/TestItemParse_spec.lua +++ b/spec/System/TestItemParse_spec.lua @@ -523,37 +523,37 @@ describe("TestAdvancedItemParse #item", function() assert.are.equals(advancedItem:BuildRaw(), equivalentCraftItem:BuildRaw()) - local catalyst = new("Item", [[ + local vaaledCatalyst = new("Item", [[ Item Class: Amulets Rarity: Unique Astramentis Onyx Amulet -------- - Quality (Attribute Modifiers): +20% (augmented) + Quality (Attribute Modifiers): +19% (augmented) -------- Requirements: Level: 20 -------- - Item Level: 80 - -------- - Allocates Weathered Hunter (enchant) + Item Level: 84 -------- - { Implicit Modifier — Attribute — 20% Increased } + { Implicit Modifier — Attribute — 19% Increased } +16(10-16) to all Attributes (Attributes are Strength, Dexterity, and Intelligence) -------- - { Unique Modifier — Attribute — 20% Increased } - +86(80-100) to all Attributes + { Unique Modifier — Attribute — 19% Increased } + +120(80-100) to all Attributes (Attributes are Strength, Dexterity, and Intelligence) { Unique Modifier — Physical, Attack } - -4 Physical Damage taken from Attack Hits + -3(-4) Physical Damage taken from Attack Hits -------- Mindless rage will shake the world, Cunning lies will bend it. Reckless haste will break the world, And into darkness send it. -------- - Note: ~b/o 50 chaos + Corrupted + -------- + Note: ~b/o 1 mirror ]]) local equivalentCatalystItem = new("Item", [[ @@ -566,76 +566,196 @@ describe("TestAdvancedItemParse #item", function() Implicits: 2 {crafted}Allocates Weathered Hunter {tags:attribute}{range:1}+(10-16) to all Attributes - {tags:attribute}{range:0.3}+(80-100) to all Attributes + {tags:attribute}+120 to all Attributes {tags:physical,attack}-4 Physical Damage taken from Attack Hits ]]) assert.are.equals(catalyst:BuildRaw(), equivalentCatalystItem:BuildRaw()) - local godTestItem = new("Item", [[ - Item Class: Sceptres - Rarity: Unique - Nebulis - Synthesised Void Sceptre + + local craftedWeapon = new("Item", [[ + Item Class: Staves + Rarity: Rare + Grim Beam + Royal Staff -------- - Sceptre - Physical Damage: 50-76 - Critical Strike Chance: 7.30% - Attacks per Second: 1.25 - Weapon Range: 1.1 metres - Memory Strands: 58 + Staff + Physical Damage: 44-133 (augmented) + Critical Strike Chance: 8.50% + Attacks per Second: 1.15 + Weapon Range: 1.3 metres -------- Requirements: - Level: 68 - Str: 104 - Int: 122 + Level: 51 + Str: 51 + Int: 51 -------- - Sockets: B R + Sockets: G -------- - Item Level: 87 + Item Level: 68 -------- - +30% to Fire Resistance (scourge) - 22% reduced Global Defences (scourge) - (Armour, Evasion Rating and Energy Shield are the standard Defences) (scourge) + { Implicit Modifier } + +20% Chance to Block Spell Damage while wielding a Staff + (Warstaves are considered Staves) -------- - 8% increased Explicit Cold Modifier magnitudes (enchant) - Has 1 White Socket (enchant) + { Prefix Modifier "Warlock's" (Tier: 4) — Mana, Damage, Caster } + 32(30-37)% increased Spell Damage + +46(42-47) to maximum Mana + { Prefix Modifier "Freezing" (Tier: 5) — Damage, Elemental, Cold, Caster } + Adds 30(24-30) to 48(45-53) Cold Damage to Spells + { Prefix Modifier "Serrated" (Tier: 7) — Damage, Physical, Attack } + 64(50-64)% increased Physical Damage + { Suffix Modifier "of Anger" (Tier: 5) — Damage, Critical } + +18(15-19)% to Global Critical Strike Multiplier + { Suffix Modifier "of Bliss" (Tier: 3) — Mana } + 71(59-72)% increased Mana Regeneration Rate + { Suffix Modifier "of Discharge" (Tier: 2) — Damage, Elemental, Lightning } + 40(40-44)% increased Lightning Damage -------- - { Searing Exarch Implicit Modifier (Lesser) } - Tempest Shield has 15(15-17)% increased Buff Effect - { Implicit Modifier — Damage, Critical — 106% Increased } - +15(15-17)% to Global Critical Strike Multiplier + Corrupted -------- - { Prefix Modifier "Freezing" (Tier: 5) — Damage, Elemental, Cold, Caster — 8% Increased } - Adds 17(16-20) to 35(30-36) Cold Damage to Spells - { Prefix Modifier "Beetle's" (Tier: 6) — Defences, Armour } - 9(6-13)% increased Armour - 7(6-7)% increased Stun and Block Recovery - { Master Crafted Prefix Modifier "Upgraded" — Life, Defences, Armour } - 21(18-21)% increased Armour - +18(17-19) to maximum Life - { Unique Modifier } - 106(60-120)% increased Implicit Modifier magnitudes — Unscalable Value - (Implicit Modifiers are those that come from an item's type, rather than its random properties) - { Master Crafted Suffix Modifier "of Craft" (Rank: 3) — Elemental, Cold, Resistance } - +35(29-35)% to Cold Resistance - { Fractured Prefix Modifier "Thorny" (Tier: 2) — Damage, Physical } - Reflects 3(1-4) Physical Damage to Melee Attackers - { Prefix Modifier "Veiled" } - Veiled Prefix - Searing Exarch Item + Note: ~b/o 1 alch + ]]) + + -- This gives a range to maximum mana, because GGG rolls multi-line mods separately, while we don't (for now?) + local equivCraftedWeapon = new("Item", [[ + Grim Beam + Royal Staff + Crafted: true + Prefix: {range:0.286}SpellDamageAndManaOnTwoHandWeapon4 + Prefix: {range:0.375}LocalAddedColdDamageTwoHand5 + Prefix: {range:1}LocalIncreasedPhysicalDamagePercent2 + Suffix: {range:0.75}LocalCriticalMultiplier2 + Suffix: {range:0.923}ManaRegenerationTwoHand4 + Suffix: {range:0}LightningDamagePercentTwoHand5 + Item Level: 68 + Quality: 0 + Sockets: G + LevelReq: 28 + Implicits: 1 + +20% Chance to Block Spell Damage while wielding a Staff + {tags:mana,damage,caster}32% increased Spell Damage + {tags:mana,damage,caster}{range:0.8}+(42-47) to maximum Mana + {tags:damage,elemental,cold,caster}Adds 30 to 48 Cold Damage to Spells + {tags:damage,physical,attack}64% increased Physical Damage + {tags:damage,critical}+18% to Global Critical Strike Multiplier + {tags:mana}71% increased Mana Regeneration Rate + {tags:damage,elemental,lightning}40% increased Lightning Damage + Corrupted + ]]) + + assert.are.equals(craftedWeapon:BuildRaw(), equivCraftedWeapon:BuildRaw()) + + local badUnique = new("Item", [[ + Item Class: Amulets + Rarity: Unique + Astramentis + Onyx Amulet -------- - { Allocated Crucible Passive Skill (Tier: 2) } - Adds 2 to 6 Physical Damage to Spells + Requirements: + Level: 20 -------- - Synthesised Item + Item Level: 84 -------- - Corrupted + { Implicit Modifier — Attribute } + +12(10-16) to all Attributes + (Attributes are Strength, Dexterity, and Intelligence) -------- - Scourged + { Unique Modifier — Attribute } + +69(80-100) to all Attributes + (Attributes are Strength, Dexterity, and Intelligence) + { Unique Modifier — Physical, Attack } + -4 Physical Damage taken from Attack Hits -------- - Hinekora's Lock + Mindless rage will shake the world, + Cunning lies will bend it. + Reckless haste will break the world, + And into darkness send it. -------- - Note: ~b/o 2 chaos + Corrupted + -------- + Note: ~b/o 69 mirror +]]) + + local equivBadUnique = new("Item", [[ + Astramentis + Onyx Amulet + Item Level: 84 + LevelReq: 20 + Implicits: 1 + {tags:attribute}{range:0.333}+(10-16) to all Attributes + {tags:attribute}+69 to all Attributes + {tags:physical,attack}-4 Physical Damage taken from Attack Hits + Corrupted + ]]) + + assert.are.equals(badUnique:BuildRaw(), equivBadUnique:BuildRaw()) + + + local godTestItem = new("Item", [[ +Item Class: Sceptres +Rarity: Unique +Nebulis +Synthesised Void Sceptre +-------- +Sceptre +Physical Damage: 50-76 +Critical Strike Chance: 7.30% +Attacks per Second: 1.25 +Weapon Range: 1.1 metres +Memory Strands: 58 +-------- +Requirements: +Level: 68 +Str: 104 +Int: 122 +-------- +Sockets: B R +-------- +Item Level: 87 +-------- ++30% to Fire Resistance (scourge) +22% reduced Global Defences (scourge) +(Armour, Evasion Rating and Energy Shield are the standard Defences) (scourge) +-------- +8% increased Explicit Cold Modifier magnitudes (enchant) +Has 1 White Socket (enchant) +-------- +{ Searing Exarch Implicit Modifier (Lesser) } +Tempest Shield has 15(15-17)% increased Buff Effect +{ Implicit Modifier — Damage, Critical — 106% Increased } ++15(15-17)% to Global Critical Strike Multiplier +-------- +{ Prefix Modifier "Freezing" (Tier: 5) — Damage, Elemental, Cold, Caster — 8% Increased } +Adds 17(16-20) to 35(30-36) Cold Damage to Spells +{ Prefix Modifier "Beetle's" (Tier: 6) — Defences, Armour } +9(6-13)% increased Armour +7(6-7)% increased Stun and Block Recovery +{ Master Crafted Prefix Modifier "Upgraded" — Life, Defences, Armour } +21(18-21)% increased Armour ++18(17-19) to maximum Life +{ Unique Modifier } +106(60-120)% increased Implicit Modifier magnitudes — Unscalable Value +(Implicit Modifiers are those that come from an item's type, rather than its random properties) +{ Master Crafted Suffix Modifier "of Craft" (Rank: 3) — Elemental, Cold, Resistance } ++35(29-35)% to Cold Resistance +{ Fractured Prefix Modifier "Thorny" (Tier: 2) — Damage, Physical } +Reflects 3(1-4) Physical Damage to Melee Attackers +{ Prefix Modifier "Veiled" } +Veiled Prefix +Searing Exarch Item +-------- +{ Allocated Crucible Passive Skill (Tier: 2) } +Adds 2 to 6 Physical Damage to Spells +-------- +Synthesised Item +-------- +Corrupted +-------- +Scourged +-------- +Hinekora's Lock +-------- +Note: ~b/o 2 chaos ]]) end) end) \ No newline at end of file diff --git a/src/Classes/Item.lua b/src/Classes/Item.lua index 6d2fe78c09..7f3324d697 100644 --- a/src/Classes/Item.lua +++ b/src/Classes/Item.lua @@ -29,6 +29,9 @@ local catalystTags = { } local function getCatalystScalar(catalystId, mod, quality) + if mod.unscalable then + return 1 + end local tags = mod.modTags local affixType = mod.type if not catalystId or type(catalystId) ~= "number" or not catalystTags[catalystId] or not tags or type(tags) ~= "table" or #tags == 0 then @@ -79,7 +82,7 @@ end local lineFlags = { ["crafted"] = true, ["crucible"] = true, ["custom"] = true, ["eater"] = true, ["enchant"] = true, ["exarch"] = true, ["fractured"] = true, ["implicit"] = true, ["scourge"] = true, ["synthesis"] = true, - ["mutated"] = true + ["mutated"] = true, ["unscalable"] = true } -- Special function to store unique instances of modifier on specific item slots @@ -423,11 +426,11 @@ function ItemClass:ParseRaw(raw, rarity, highQuality) local modName = fullModName:match("^.*Modifier \"(.*)\"") if modName and modName ~= "" then for modId, modData in pairs(self.affixes) do - if modData.affix == modName then + if modData.affix == modName and self:CanHaveMod(modData) then if modData.type == "Prefix" then - pendingAffix = { modId = modId, table = self.prefixes } + self.pendingAffix = { modId = modId, table = self.prefixes } elseif modData.type == "Suffix" then - pendingAffix = { modId = modId, table = self.suffixes } + self.pendingAffix = { modId = modId, table = self.suffixes } end end end @@ -785,24 +788,49 @@ function ItemClass:ParseRaw(raw, rarity, highQuality) foundImplicit = true gameModeStage = "IMPLICIT" end - local catalystScalar = getCatalystScalar(self.catalyst, modLine, self.catalystQuality) - for value, range in line:gmatch("(%d+)%((%d+%-%d+)%)") do - -- Find advanced copy paste format: 45(40-50) - if pendingAffix then + local catalystScalar = 1 + if line:match(" %- Unscalable Value$") then + line = line:gsub(" %- Unscalable Value$", "") + modLine.unscalable = true + else + catalystScalar = getCatalystScalar(self.catalyst, modLine, self.catalystQuality) + end + if self.pendingAffix then + local bestPrecisionDelta = 0 + local bestPrecisionRange = 0 + for value, range in line:gmatch("(%d+)%((%d+%-%d+)%)") do + -- Find advanced copy paste format: 45(40-50) local min, max = range:match("(%d+)%-(%d+)") - local numRange = round((value - min) / (tonumber(max) - min), 3) + local delta = tonumber(max) - min line = line:gsub(value .. "%(" .. range:gsub("%-", "%%-") .. "%)", value) - t_insert(pendingAffix.table, { - modId = pendingAffix.modId, - range = tonumber(numRange), - }) - pendingAffix = nil - else + if delta > bestPrecisionDelta then + bestPrecisionRange = round((value - min) / delta, 3) + bestPrecisionDelta = delta + end + end + t_insert(self.pendingAffix.table, { + modId = self.pendingAffix.modId, + range = tonumber(bestPrecisionRange), + }) + self.pendingAffix = nil + else + local bestPrecisionDelta = 0 + local bestPrecisionRange = 0 + for value, range in line:gmatch("(%d+)%((%d+%-%d+)%)") do local min, max = range:match("(%d+)%-(%d+)") - local numRange = round((value - min) / (tonumber(max) - min), 3) - modLine.range = tonumber(numRange) - --ConPrintf(modLine.range) - line = line:gsub(value .. "%(" .. range:gsub("%-", "%%-") .. "%)", "(" .. range .. ")") + local delta = tonumber(max) - min + if delta > bestPrecisionDelta then + bestPrecisionRange = round((value - min) / delta, 3) + bestPrecisionDelta = delta + end + if bestPrecisionRange > 1 or bestPrecisionRange < 0 then + line = line:gsub(value .. "%(" .. range:gsub("%-", "%%-") .. "%)", value) + else + line = line:gsub(value .. "%(" .. range:gsub("%-", "%%-") .. "%)", "(" .. range .. ")") + end + end + if bestPrecisionRange < 1 and bestPrecisionRange > 0 then + modLine.range = tonumber(bestPrecisionRange) end end local rangedLine = itemLib.applyRange(line, 1, catalystScalar) @@ -1206,6 +1234,9 @@ function ItemClass:BuildRaw() if modLine.synthesis then line = "{synthesis}" .. line end + if modLine.unscalable then + line = "{unscalable}" .. line + end if modLine.variantList then local varSpec for varId in pairs(modLine.variantList) do @@ -1902,3 +1933,25 @@ function ItemClass:BuildModList() self.modList = self:BuildModListForSlotNum(baseList) end end + +function ItemClass:CanHaveMod(mod) + local keyMap, includeTags = { }, { } + for index, key in ipairs(mod.weightKey) do + keyMap[key] = index + end + -- check for uniques with off-tag mods + if data.casterTagCrucibleUniques[self.title] then + includeTags["caster_unique_weapon"] = true + end + if data.minionTagCrucibleUniques[self.title] then + includeTags["minion_unique_weapon"] = true + end + if self.canHaveOnlySupportSkillsCrucibleTree then + return keyMap["crucible_unique_staff"] and mod.weightVal[keyMap["crucible_unique_staff"]] ~= 0 + elseif self.canHaveShieldCrucibleTree then + return self:GetModSpawnWeight(mod, { ["crucible_unique_helmet"] = true, ["shield"] = true }) > 0 + elseif self.canHaveTwoHandedSwordCrucibleTree then + return self:GetModSpawnWeight(mod, { ["two_hand_weapon"] = true }, { ["one_hand_weapon"] = true }) > 0 + end + return self:GetModSpawnWeight(mod, includeTags) > 0 +end diff --git a/src/Classes/ItemsTab.lua b/src/Classes/ItemsTab.lua index 24a9f8b32c..7f4f1f89f3 100644 --- a/src/Classes/ItemsTab.lua +++ b/src/Classes/ItemsTab.lua @@ -3039,30 +3039,9 @@ function ItemsTabClass:AddCrucibleModifierToDisplayItem() end return table.concat(label, "/") end - local function itemCanHaveMod(mod) - local keyMap, includeTags = { }, { } - for index, key in ipairs(mod.weightKey) do - keyMap[key] = index - end - -- check for uniques with off-tag mods - if data.casterTagCrucibleUniques[self.displayItem.title] then - includeTags["caster_unique_weapon"] = true - end - if data.minionTagCrucibleUniques[self.displayItem.title] then - includeTags["minion_unique_weapon"] = true - end - if self.displayItem.canHaveOnlySupportSkillsCrucibleTree then - return keyMap["crucible_unique_staff"] and mod.weightVal[keyMap["crucible_unique_staff"]] ~= 0 - elseif self.displayItem.canHaveShieldCrucibleTree then - return self.displayItem:GetModSpawnWeight(mod, { ["crucible_unique_helmet"] = true, ["shield"] = true }) > 0 - elseif self.displayItem.canHaveTwoHandedSwordCrucibleTree then - return self.displayItem:GetModSpawnWeight(mod, { ["two_hand_weapon"] = true }, { ["one_hand_weapon"] = true }) > 0 - end - return self.displayItem:GetModSpawnWeight(mod, includeTags) > 0 - end local function buildCrucibleMods() for i, mod in pairs(self.build.data.crucible) do - if itemCanHaveMod(mod) then + if self.displayItem:CanHaveMod(mod) then -- item mod must match the whole mod, whether that's one line or two if itemModMap[checkLineForAllocates(mod[1], self.build.spec.nodes)] and ((mod[2] and itemModMap[checkLineForAllocates(mod[2], self.build.spec.nodes)]) or not mod[2]) then -- for multi nodes, if the first location is taken, use second From 1c8bd47e8d9f6ebd2c953b53e2fc9eb59fd691b7 Mon Sep 17 00:00:00 2001 From: Wires77 Date: Sat, 16 May 2026 23:14:05 -0500 Subject: [PATCH 6/7] Simplify tests, since BuildRaw doesn't do what I'd expect --- spec/System/TestItemParse_spec.lua | 379 +++++++++-------------------- 1 file changed, 119 insertions(+), 260 deletions(-) diff --git a/spec/System/TestItemParse_spec.lua b/spec/System/TestItemParse_spec.lua index 9d976153fc..6e04a8c2c8 100644 --- a/spec/System/TestItemParse_spec.lua +++ b/spec/System/TestItemParse_spec.lua @@ -392,6 +392,13 @@ describe("TestItemParse", function() assert.truthy(item.explicitModLines[1].synthesis) end) + it("unscalable", function() + local item = new("Item", raw("{unscalable}+8 to Strength")) + assert.truthy(item.explicitModLines[1].unscalable) + item = new("Item", raw("+8 to Strength - Unscalable Value")) + assert.truthy(item.explicitModLines[1].unscalable) + end) + it("multiple bases", function() local item = new("Item", [[ Ashcaller @@ -467,295 +474,147 @@ describe("TestItemParse", function() end) describe("TestAdvancedItemParse #item", function() - it("parses item", function() - local advancedItem = new("Item", [[ - Item Class: Belts - Rarity: Rare - Beast Snare - Cord Belt - -------- - Requirements: - Level: 51 - -------- - Item Level: 83 - -------- - Allocates Surveillance (enchant) - -------- - { Implicit Modifier } - Can be Anointed - -------- - { Fractured Prefix Modifier "Thorny" (Tier: 2) — Damage, Physical } - Reflects 3(1-4) Physical Damage to Melee Attackers + local function raw(s, base) + base = base or "Plate Vest" + return "Rarity: Rare\nName\n"..base.."\n"..s + end + + it("parses to craft", function() + local item = new("Item", raw([[ { Prefix Modifier "Fecund" (Tier: 1) — Life } +142(130-144) to maximum Life - { Prefix Modifier "Glowing" (Tier: 9) — Defences, Energy Shield } - +15(13-15) to maximum Energy Shield - { Suffix Modifier "of the Tempest" (Tier: 4) — Elemental, Lightning, Resistance } - +34(30-35)% to Lightning Resistance + ]], "Cord Belt")) + assert.are.equals("IncreasedLife9", item.prefixes[1].modId) + assert.are.equals(0.857, item.prefixes[1].range) + assert.are.equals("life", item.explicitModLines[1].modTags[1]) + item = new("Item", raw([[ { Master Crafted Suffix Modifier "of Craft" (Rank: 3) — Elemental, Cold, Resistance } +35(29-35)% to Cold Resistance - -------- - Fractured Item - ]]) + ]], "Cord Belt")) + assert.truthy(item.explicitModLines[1].crafted) + end) - local equivalentCraftItem = new("Item", [[ - Beast Snare - Cord Belt - Crafted: true - Prefix: {range:0.667}AttackerTakesDamage1 - Prefix: {range:0.857}IncreasedLife9 - Prefix: {range:1}IncreasedEnergyShield4 - Suffix: {range:0.8}LightningResist5 - Suffix: None - Suffix: None - Item Level: 83 - LevelReq: 51 - Implicits: 2 - {crafted}Allocates Surveillance - Can be Anointed - {tags:damage,physical}{fractured}Reflects 3 Physical Damage to Melee Attackers - {tags:life}+142 to maximum Life - {tags:defences,energyshield}+15 to maximum Energy Shield - {tags:elemental,lightning,resistance}+34% to Lightning Resistance - {tags:elemental,cold,resistance}{crafted}{range:1}+(29-35)% to Cold Resistance - Fractured Item - ]]) + it("parses correct range", function() + local item = new("Item", raw([[ + { Prefix Modifier "Freezing" (Tier: 5) — Damage, Elemental, Cold, Caster — 8% Increased } + Adds 17(16-20) to 35(30-36) Cold Damage to Spells + ]], "Void Sceptre")) + assert.are.equals("Adds 17 to 35 Cold Damage to Spells", item.explicitModLines[1].line) + end) - assert.are.equals(advancedItem:BuildRaw(), equivalentCraftItem:BuildRaw()) + -- GGG scales each mod line separately here, but PoB scales them both together, so this parsing is a bit wonky + it("parses multi-line mod", function() + local item = new("Item", raw([[ + { Prefix Modifier "Warlock's" (Tier: 4) — Mana, Damage, Caster } + 32(30-37)% increased Spell Damage + +46(42-47) to maximum Mana + ]], "Royal Staff")) + assert.are.equals("SpellDamageAndManaOnTwoHandWeapon4", item.prefixes[1].modId) + assert.are.equals(0.286, item.prefixes[1].range) + assert.are.equals(0.8, item.explicitModLines[2].range) + end) - local vaaledCatalyst = new("Item", [[ - Item Class: Amulets - Rarity: Unique - Astramentis - Onyx Amulet - -------- + it("parses vaaled catalyst", function() + local item = new("Item", raw([[ Quality (Attribute Modifiers): +19% (augmented) - -------- - Requirements: - Level: 20 - -------- - Item Level: 84 - -------- - { Implicit Modifier — Attribute — 19% Increased } - +16(10-16) to all Attributes - (Attributes are Strength, Dexterity, and Intelligence) - -------- { Unique Modifier — Attribute — 19% Increased } +120(80-100) to all Attributes (Attributes are Strength, Dexterity, and Intelligence) - { Unique Modifier — Physical, Attack } - -3(-4) Physical Damage taken from Attack Hits - -------- - Mindless rage will shake the world, - Cunning lies will bend it. - Reckless haste will break the world, - And into darkness send it. - -------- - Corrupted - -------- - Note: ~b/o 1 mirror - ]]) + ]], "Onyx Amulet")) + assert.are.equals(142, item.baseModList[1].value) + -- assert.falsy(item.explicitModLines[1].range) -- Not sure why this is returning 0.5 + assert.are.equals(6, item.catalyst) + assert.are.equals(19, item.catalystQuality) + end) - local equivalentCatalystItem = new("Item", [[ - Astramentis - Onyx Amulet - Catalyst: Intrinsic - CatalystQuality: 20 - Item Level: 80 - LevelReq: 20 - Implicits: 2 - {crafted}Allocates Weathered Hunter - {tags:attribute}{range:1}+(10-16) to all Attributes - {tags:attribute}+120 to all Attributes - {tags:physical,attack}-4 Physical Damage taken from Attack Hits - ]]) + it("parses vaaled catalyst within range", function() + local item = new("Item", raw([[ + Quality (Attribute Modifiers): +19% (augmented) + { Unique Modifier — Attribute — 19% Increased } + +95(80-100) to all Attributes + (Attributes are Strength, Dexterity, and Intelligence) + ]], "Onyx Amulet")) + assert.are.equals(113, item.baseModList[1].value) + assert.are.equals(0.75, item.explicitModLines[1].range) -- Not sure why this is returning 0.5 + assert.are.equals(6, item.catalyst) + assert.are.equals(19, item.catalystQuality) + end) - assert.are.equals(catalyst:BuildRaw(), equivalentCatalystItem:BuildRaw()) + it("doesn't scale unscalable", function() + local item = new("Item", raw([[ + Quality (Life and Mana Modifiers): +20% (augmented) + { Unique Modifier — Life, Defences, Energy Shield, Minion, Gem } + Socketed Golem Skills gain 20% of Maximum Life as Extra Maximum Energy Shield — Unscalable Value + ]])) + assert.are.equals(20, item.baseModList[1].value.mod.value) + end) - local craftedWeapon = new("Item", [[ - Item Class: Staves - Rarity: Rare - Grim Beam - Royal Staff + it("parses junk", function() + local godTestItem = new("Item", [[ + Item Class: Sceptres + Rarity: Unique + Nebulis + Synthesised Void Sceptre -------- - Staff - Physical Damage: 44-133 (augmented) - Critical Strike Chance: 8.50% - Attacks per Second: 1.15 - Weapon Range: 1.3 metres + Sceptre + Physical Damage: 50-76 + Critical Strike Chance: 7.30% + Attacks per Second: 1.25 + Weapon Range: 1.1 metres + Memory Strands: 58 -------- Requirements: - Level: 51 - Str: 51 - Int: 51 - -------- - Sockets: G + Level: 68 + Str: 104 + Int: 122 -------- - Item Level: 68 + Sockets: B R -------- - { Implicit Modifier } - +20% Chance to Block Spell Damage while wielding a Staff - (Warstaves are considered Staves) + Item Level: 87 -------- - { Prefix Modifier "Warlock's" (Tier: 4) — Mana, Damage, Caster } - 32(30-37)% increased Spell Damage - +46(42-47) to maximum Mana - { Prefix Modifier "Freezing" (Tier: 5) — Damage, Elemental, Cold, Caster } - Adds 30(24-30) to 48(45-53) Cold Damage to Spells - { Prefix Modifier "Serrated" (Tier: 7) — Damage, Physical, Attack } - 64(50-64)% increased Physical Damage - { Suffix Modifier "of Anger" (Tier: 5) — Damage, Critical } - +18(15-19)% to Global Critical Strike Multiplier - { Suffix Modifier "of Bliss" (Tier: 3) — Mana } - 71(59-72)% increased Mana Regeneration Rate - { Suffix Modifier "of Discharge" (Tier: 2) — Damage, Elemental, Lightning } - 40(40-44)% increased Lightning Damage + +30% to Fire Resistance (scourge) + 22% reduced Global Defences (scourge) + (Armour, Evasion Rating and Energy Shield are the standard Defences) (scourge) -------- - Corrupted + 8% increased Explicit Cold Modifier magnitudes (enchant) + Has 1 White Socket (enchant) -------- - Note: ~b/o 1 alch - ]]) - - -- This gives a range to maximum mana, because GGG rolls multi-line mods separately, while we don't (for now?) - local equivCraftedWeapon = new("Item", [[ - Grim Beam - Royal Staff - Crafted: true - Prefix: {range:0.286}SpellDamageAndManaOnTwoHandWeapon4 - Prefix: {range:0.375}LocalAddedColdDamageTwoHand5 - Prefix: {range:1}LocalIncreasedPhysicalDamagePercent2 - Suffix: {range:0.75}LocalCriticalMultiplier2 - Suffix: {range:0.923}ManaRegenerationTwoHand4 - Suffix: {range:0}LightningDamagePercentTwoHand5 - Item Level: 68 - Quality: 0 - Sockets: G - LevelReq: 28 - Implicits: 1 - +20% Chance to Block Spell Damage while wielding a Staff - {tags:mana,damage,caster}32% increased Spell Damage - {tags:mana,damage,caster}{range:0.8}+(42-47) to maximum Mana - {tags:damage,elemental,cold,caster}Adds 30 to 48 Cold Damage to Spells - {tags:damage,physical,attack}64% increased Physical Damage - {tags:damage,critical}+18% to Global Critical Strike Multiplier - {tags:mana}71% increased Mana Regeneration Rate - {tags:damage,elemental,lightning}40% increased Lightning Damage - Corrupted - ]]) - - assert.are.equals(craftedWeapon:BuildRaw(), equivCraftedWeapon:BuildRaw()) - - local badUnique = new("Item", [[ - Item Class: Amulets - Rarity: Unique - Astramentis - Onyx Amulet + { Searing Exarch Implicit Modifier (Lesser) } + Tempest Shield has 15(15-17)% increased Buff Effect + { Implicit Modifier — Damage, Critical — 106% Increased } + +15(15-17)% to Global Critical Strike Multiplier -------- - Requirements: - Level: 20 + { Prefix Modifier "Freezing" (Tier: 5) — Damage, Elemental, Cold, Caster — 8% Increased } + Adds 17(16-20) to 35(30-36) Cold Damage to Spells + { Prefix Modifier "Beetle's" (Tier: 6) — Defences, Armour } + 9(6-13)% increased Armour + 7(6-7)% increased Stun and Block Recovery + { Master Crafted Prefix Modifier "Upgraded" — Life, Defences, Armour } + 21(18-21)% increased Armour + +18(17-19) to maximum Life + { Unique Modifier } + 106(60-120)% increased Implicit Modifier magnitudes — Unscalable Value + (Implicit Modifiers are those that come from an item's type, rather than its random properties) + { Master Crafted Suffix Modifier "of Craft" (Rank: 3) — Elemental, Cold, Resistance } + +35(29-35)% to Cold Resistance + { Fractured Prefix Modifier "Thorny" (Tier: 2) — Damage, Physical } + Reflects 3(1-4) Physical Damage to Melee Attackers + { Prefix Modifier "Veiled" } + Veiled Prefix + Searing Exarch Item -------- - Item Level: 84 + { Allocated Crucible Passive Skill (Tier: 2) } + Adds 2 to 6 Physical Damage to Spells -------- - { Implicit Modifier — Attribute } - +12(10-16) to all Attributes - (Attributes are Strength, Dexterity, and Intelligence) + Synthesised Item -------- - { Unique Modifier — Attribute } - +69(80-100) to all Attributes - (Attributes are Strength, Dexterity, and Intelligence) - { Unique Modifier — Physical, Attack } - -4 Physical Damage taken from Attack Hits + Corrupted -------- - Mindless rage will shake the world, - Cunning lies will bend it. - Reckless haste will break the world, - And into darkness send it. + Scourged -------- - Corrupted + Hinekora's Lock -------- - Note: ~b/o 69 mirror -]]) - - local equivBadUnique = new("Item", [[ - Astramentis - Onyx Amulet - Item Level: 84 - LevelReq: 20 - Implicits: 1 - {tags:attribute}{range:0.333}+(10-16) to all Attributes - {tags:attribute}+69 to all Attributes - {tags:physical,attack}-4 Physical Damage taken from Attack Hits - Corrupted - ]]) - - assert.are.equals(badUnique:BuildRaw(), equivBadUnique:BuildRaw()) - - - local godTestItem = new("Item", [[ -Item Class: Sceptres -Rarity: Unique -Nebulis -Synthesised Void Sceptre --------- -Sceptre -Physical Damage: 50-76 -Critical Strike Chance: 7.30% -Attacks per Second: 1.25 -Weapon Range: 1.1 metres -Memory Strands: 58 --------- -Requirements: -Level: 68 -Str: 104 -Int: 122 --------- -Sockets: B R --------- -Item Level: 87 --------- -+30% to Fire Resistance (scourge) -22% reduced Global Defences (scourge) -(Armour, Evasion Rating and Energy Shield are the standard Defences) (scourge) --------- -8% increased Explicit Cold Modifier magnitudes (enchant) -Has 1 White Socket (enchant) --------- -{ Searing Exarch Implicit Modifier (Lesser) } -Tempest Shield has 15(15-17)% increased Buff Effect -{ Implicit Modifier — Damage, Critical — 106% Increased } -+15(15-17)% to Global Critical Strike Multiplier --------- -{ Prefix Modifier "Freezing" (Tier: 5) — Damage, Elemental, Cold, Caster — 8% Increased } -Adds 17(16-20) to 35(30-36) Cold Damage to Spells -{ Prefix Modifier "Beetle's" (Tier: 6) — Defences, Armour } -9(6-13)% increased Armour -7(6-7)% increased Stun and Block Recovery -{ Master Crafted Prefix Modifier "Upgraded" — Life, Defences, Armour } -21(18-21)% increased Armour -+18(17-19) to maximum Life -{ Unique Modifier } -106(60-120)% increased Implicit Modifier magnitudes — Unscalable Value -(Implicit Modifiers are those that come from an item's type, rather than its random properties) -{ Master Crafted Suffix Modifier "of Craft" (Rank: 3) — Elemental, Cold, Resistance } -+35(29-35)% to Cold Resistance -{ Fractured Prefix Modifier "Thorny" (Tier: 2) — Damage, Physical } -Reflects 3(1-4) Physical Damage to Melee Attackers -{ Prefix Modifier "Veiled" } -Veiled Prefix -Searing Exarch Item --------- -{ Allocated Crucible Passive Skill (Tier: 2) } -Adds 2 to 6 Physical Damage to Spells --------- -Synthesised Item --------- -Corrupted --------- -Scourged --------- -Hinekora's Lock --------- -Note: ~b/o 2 chaos + Note: ~b/o 2 chaos ]]) end) end) \ No newline at end of file From d71f08923e5ffc3f952a5ad02d932cf545b5a9d0 Mon Sep 17 00:00:00 2001 From: Wires77 Date: Sat, 16 May 2026 23:21:28 -0500 Subject: [PATCH 7/7] Fix linePrefix bug --- spec/System/TestItemParse_spec.lua | 13 ++++++++++++- src/Classes/Item.lua | 1 + 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/spec/System/TestItemParse_spec.lua b/spec/System/TestItemParse_spec.lua index 6e04a8c2c8..089198bcb4 100644 --- a/spec/System/TestItemParse_spec.lua +++ b/spec/System/TestItemParse_spec.lua @@ -514,6 +514,17 @@ describe("TestAdvancedItemParse #item", function() assert.are.equals(0.8, item.explicitModLines[2].range) end) + it("resets linePrefix", function() + local item = new("Item", raw([[ + { Prefix Modifier "Warlock's" (Tier: 4) — Mana, Damage, Caster } + 32(30-37)% increased Spell Damage + +46(42-47) to maximum Mana + -------- + +15 to maximum life + ]], "Royal Staff")) + assert.are_not.equals("mana", item.explicitModLines[3].modTags[1]) + end) + it("parses vaaled catalyst", function() local item = new("Item", raw([[ Quality (Attribute Modifiers): +19% (augmented) @@ -535,7 +546,7 @@ describe("TestAdvancedItemParse #item", function() (Attributes are Strength, Dexterity, and Intelligence) ]], "Onyx Amulet")) assert.are.equals(113, item.baseModList[1].value) - assert.are.equals(0.75, item.explicitModLines[1].range) -- Not sure why this is returning 0.5 + assert.are.equals(0.75, item.explicitModLines[1].range) assert.are.equals(6, item.catalyst) assert.are.equals(19, item.catalystQuality) end) diff --git a/src/Classes/Item.lua b/src/Classes/Item.lua index 7f3324d697..e4d7818c33 100644 --- a/src/Classes/Item.lua +++ b/src/Classes/Item.lua @@ -392,6 +392,7 @@ function ItemClass:ParseRaw(raw, rarity, highQuality) elseif tinctureBuffLines and tinctureBuffLines[line] then tinctureBuffLines[line] = nil elseif line == "--------" then + linePrefix = "" self.checkSection = true elseif line == "Split" then self.split = true