diff --git a/spec/System/TestItemParse_spec.lua b/spec/System/TestItemParse_spec.lua index 66bc1cdb39..1a348a0085 100644 --- a/spec/System/TestItemParse_spec.lua +++ b/spec/System/TestItemParse_spec.lua @@ -843,6 +843,40 @@ describe("TestAdvancedItemParse #item", function() assert.are.equals("Adds 14 to 285 Lightning Damage", item.explicitModLines[2].line) end) + it("preserves corrupted modifier identity and requirement through crafting", function() + local item = new("Item"):Item([[ + Rarity: Rare + Test Ring + Gold Ring + Crafted: true + Prefix: None + Prefix: None + Prefix: None + Suffix: None + Suffix: None + Suffix: None + LevelReq: 1 + Implicits: 1 + {modGroup:IncreasedCastSpeedCorrupted}(4-6)% increased Cast Speed + Corrupted + ]]) + + assert.are.equals("IncreasedCastSpeedCorrupted", item.implicitModLines[1].modGroup) + item:BuildAndParseRaw() + assert.are.equals("IncreasedCastSpeedCorrupted", item.implicitModLines[1].modGroup) + + item:Craft() + assert.are.equals(1, item.requirements.level) + + item.suffixes[1] = { modId = "Strength8", range = 0.5 } + item:Craft() + assert.are.equals(59, item.requirements.level) + + item.suffixes[1] = { modId = "None" } + item:Craft() + assert.are.equals(1, item.requirements.level) + end) + it("filters flask base properties and parses fixed-value advanced rolls", function() local item = new("Item"):Item([[ Rarity: Unique diff --git a/spec/System/TestItemTools_spec.lua b/spec/System/TestItemTools_spec.lua index 58b8aa818e..517a89c868 100644 --- a/spec/System/TestItemTools_spec.lua +++ b/spec/System/TestItemTools_spec.lua @@ -119,4 +119,58 @@ Can be Anointed assert.are.equals(0, fakeItemsTab:getMissingAnointCount(item)) end) + + it("calculates corrupted requirements from generated modifier levels", function() + local item = setmetatable({ + crafted = true, + rarity = "RARE", + base = { req = { level = 20 } }, + affixes = { }, + prefixes = { }, + suffixes = { }, + requirements = { level = 20 }, + }, common.classes.Item) + + assert.are.equals(60, item:CalculateCorruptedRequirement({ { level = 75 } }, 20)) + assert.are.equals(64, item:CalculateCorruptedRequirement({ { level = 75 }, { level = 80 } }, 20)) + end) + + it("composes structured affix and conservative requirement floors", function() + local item = setmetatable({ + crafted = true, + rarity = "RARE", + base = { req = { level = 20 } }, + affixes = { HighAffix = { level = 85 } }, + prefixes = { { modId = "HighAffix" } }, + suffixes = { }, + requirements = { level = 72 }, + }, common.classes.Item) + + assert.are.equals(68, item:CalculateCorruptedRequirement({ { level = 75 } }, 72)) + + item.prefixes[1].modId = "MissingAffix" + assert.are.equals(72, item:CalculateCorruptedRequirement({ { level = 75 } }, 72)) + + item.prefixes = { } + item.base.weapon = { } + item.base.req.level = 70 + assert.are.equals(70, item:CalculateCorruptedRequirement({ { level = 75 } }, 72)) + end) + + it("does not lower unique or non-crafted item requirements", function() + local item = setmetatable({ + crafted = true, + rarity = "UNIQUE", + base = { req = { level = 20 } }, + affixes = { }, + prefixes = { }, + suffixes = { }, + requirements = { level = 70 }, + }, common.classes.Item) + + assert.are.equals(70, item:CalculateCorruptedRequirement({ { level = 75 } }, 70)) + item.rarity = "RARE" + item.crafted = false + assert.are.equals(70, item:CalculateCorruptedRequirement({ { level = 75 } }, 70)) + end) end) diff --git a/src/Classes/Item.lua b/src/Classes/Item.lua index 1618e93e01..d3be745d38 100644 --- a/src/Classes/Item.lua +++ b/src/Classes/Item.lua @@ -2051,8 +2051,61 @@ function ItemClass:BuildAndParseRaw() self:ParseRaw(raw) end +function ItemClass:GetCorruptedImplicitMods() + local mods = { } + local seen = { } + for _, modLine in ipairs(self.implicitModLines) do + local modId = modLine.modGroup + if modId and not seen[modId] then + local mod = data.itemMods.Corrupted[modId] + if mod and mod.type == "Corrupted" then + seen[modId] = true + t_insert(mods, mod) + end + end + end + return mods +end + +function ItemClass:CalculateCorruptedRequirement(corruptedMods, existingRequirement) + local requirement + local canRecalculate = self.crafted and self.rarity ~= "UNIQUE" and self.rarity ~= "RELIC" + if canRecalculate then + if self.base.weapon or self.base.armour or self.base.flask then + requirement = self.base.req.level or 1 + else + -- Other bases have a pre-implicit requirement of 1 in the base exporter. + requirement = 1 + end + for _, list in ipairs({ self.prefixes, self.suffixes }) do + for _, affix in ipairs(list) do + if affix.modId ~= "None" then + local mod = affix.modId and self.affixes[affix.modId] + if not mod then + canRecalculate = false + break + end + requirement = m_max(requirement, m_floor(mod.level * 0.8)) + end + end + if not canRecalculate then + break + end + end + end + if not canRecalculate then + requirement = existingRequirement or self.requirements.level or self.base.req.level or 1 + end + for _, mod in ipairs(corruptedMods) do + requirement = m_max(requirement, m_floor(mod.level * 0.8)) + end + return requirement +end + -- Rebuild explicit modifiers using the item's affixes function ItemClass:Craft() + local existingLevelRequirement = self.requirements.level + local corruptedImplicitMods = self:GetCorruptedImplicitMods() -- Save off any crafted or custom mods so they can be re-added at the end local savedMods = {} for _, mod in ipairs(self.explicitModLines) do @@ -2113,6 +2166,13 @@ function ItemClass:Craft() if #self.explicitModLines > 1 then sortCraftedModLines(self.explicitModLines) end + if self.corrupted then + if #corruptedImplicitMods > 0 then + self.requirements.level = self:CalculateCorruptedRequirement(corruptedImplicitMods, existingLevelRequirement) + else + self.requirements.level = m_max(self.requirements.level or 0, existingLevelRequirement or 0) + end + end self:BuildAndParseRaw() end diff --git a/src/Classes/ItemsTab.lua b/src/Classes/ItemsTab.lua index 0b3852aba5..cdba4c3bdc 100644 --- a/src/Classes/ItemsTab.lua +++ b/src/Classes/ItemsTab.lua @@ -3150,13 +3150,13 @@ function ItemsTabClass:CorruptDisplayItem() if modType == "Glimpse of Chaos" then for modId, mod in pairs(data.itemMods.ItemExclusive) do if modId:find("^UniqueSpecialCorruption") then - t_insert(implicitList[modType], { mod = mod }) + t_insert(implicitList[modType], { modId = modId, mod = mod }) end end else for modId, mod in pairs(self.displayItem.affixes) do if mod.type == modType and self.displayItem:GetModSpawnWeight(mod) > 0 then - t_insert(implicitList[modType], { mod = mod }) + t_insert(implicitList[modType], { modId = modId, mod = mod }) end end end @@ -3182,7 +3182,7 @@ function ItemsTabClass:CorruptDisplayItem() for _, entry in ipairs(implicitList[modType]) do local mod = entry.mod if not otherMod or mod.group ~= otherMod.group then - t_insert(control.list, { label = table.concat(mod, "/"), mod = mod }) + t_insert(control.list, { label = table.concat(mod, "/"), modId = entry.modId, mod = mod }) end end control:SelByValue(selfMod, "mod") @@ -3206,7 +3206,7 @@ function ItemsTabClass:CorruptDisplayItem() for _, entry in ipairs(implicitList[modType]) do local mod = entry.mod if not selectedGroups[mod.group] or selectedGroups[mod.group].idx == i then - t_insert(control.list, { label = table.concat(mod, "/"), mod = mod }) + t_insert(control.list, { label = table.concat(mod, "/"), modId = entry.modId, mod = mod }) end end end @@ -3306,16 +3306,21 @@ function ItemsTabClass:CorruptDisplayItem() -- implicits, like in pob2 if addingImplicits then local newImplicit = {} + local selectedCorruptionMods = { } for i = 1, maxImplicitNum do local control = controls[string.format("implicit%d", i)] if control.selIndex > 1 then - local mod = control.list[control.selIndex].mod + local entry = control.list[control.selIndex] + local mod = entry.mod + if currentModType == "Corrupted" then + t_insert(selectedCorruptionMods, mod) + end for _, modLine in ipairs(mod) do modLine = (currentModType == "ScourgeUpside" and "{scourge}" or "") .. modLine if mod.modTags[1] then - t_insert(newImplicit, { line = "{tags:" .. table.concat(mod.modTags, ",") .. "}" .. modLine }) + t_insert(newImplicit, { line = "{tags:" .. table.concat(mod.modTags, ",") .. "}" .. modLine, modGroup = currentModType == "Corrupted" and entry.modId or nil }) else - t_insert(newImplicit, { line = modLine }) + t_insert(newImplicit, { line = modLine, modGroup = currentModType == "Corrupted" and entry.modId or nil }) end end end @@ -3329,6 +3334,11 @@ function ItemsTabClass:CorruptDisplayItem() t_insert(currentModType ~= "ScourgeUpside" and item.implicitModLines or item.scourgeModLines, implicit) end end + item:BuildAndParseRaw() + if #selectedCorruptionMods > 0 then + item.requirements.level = item:CalculateCorruptedRequirement(selectedCorruptionMods, self.displayItem.requirements.level) + item:BuildAndParseRaw() + end end if not addingImplicits then for i, modLine in ipairs(item.explicitModLines) do @@ -3338,7 +3348,9 @@ function ItemsTabClass:CorruptDisplayItem() end end end - item:BuildAndParseRaw() + if not addingImplicits then + item:BuildAndParseRaw() + end return item end if self.displayItem.rarity == "UNIQUE" or self.displayItem.rarity == "RELIC" then