From e773b33735024c91125a42c3e1b0fd81186603f3 Mon Sep 17 00:00:00 2001 From: AdamZ Date: Wed, 9 Sep 2026 14:51:28 -0700 Subject: [PATCH 1/2] Show local tincture effect in item previews and refresh modifier ranges --- spec/System/TestItemMods_spec.lua | 111 ++++++++++++++++++++++++++++++ src/Classes/Item.lua | 7 ++ src/Classes/ItemsTab.lua | 7 +- src/Modules/CalcPerform.lua | 4 +- src/Modules/ItemTools.lua | 19 ++++- 5 files changed, 141 insertions(+), 7 deletions(-) diff --git a/spec/System/TestItemMods_spec.lua b/spec/System/TestItemMods_spec.lua index 3aa079e67b9..564ed9c77b3 100644 --- a/spec/System/TestItemMods_spec.lua +++ b/spec/System/TestItemMods_spec.lua @@ -7,6 +7,117 @@ describe("TetsItemMods", function() -- newBuild() takes care of resetting everything in setup() end) + it("refreshes imported modifier ranges after crafting an affix", function() + local raw = [[ + Item Class: Tinctures + Rarity: Magic + Potent Prismatic Tincture of Overpowering + -------- + Quality: +24% (augmented) + Inflicts Mana Burn every 0.39 (augmented) Seconds + 8 Second Cooldown when Deactivated + Intangibility: 5% + -------- + Requirements: + Level: 68 + -------- + Item Level: 85 + -------- + { Implicit Modifier — Damage, Elemental, Attack — 67% Increased } + 100(70-100)% increased Elemental Damage with Melee Weapons + -------- + { Prefix Modifier "Potent" (Tier: 3) — 67% Increased } + 35% increased effect — Unscalable Value + 47(47-51)% increased Mana Burn rate + { Suffix Modifier "of Overpowering" (Tier: 1) — Damage, Elemental, Attack — 67% Increased } + Melee Weapon Damage Penetrates 19(17-19)% Elemental Resistances + -------- + Right click to activate. Only one Tincture in your belt can be active at a time. Mana Burn causes you to lose 1% of your maximum Mana per stack per second. Can be deactivated manually, or will automatically deactivate when you reach 0 Mana. + ]] + for _, edit in ipairs({ "slider", "dropdown" }) do + for _, hasImplicit in ipairs({ true, false }) do + local tab = build.itemsTab + tab:CreateDisplayItemFromRaw(raw) + if not hasImplicit then + wipeTable(tab.displayItem.implicitModLines) + end + -- Saved items can retain the separately ranged line from advanced copy. + tab:CreateDisplayItemFromRaw(tab.displayItem:BuildRaw()) + local ranges = tab.controls.displayItemRangeLine + local count = hasImplicit and 2 or 1 + assert.are.equals(count, #ranges.list) + local affix = tab.controls.displayItemAffix1 + if edit == "slider" then + affix.slider:SetVal(0.1) + else + affix:SetSel(1) + end + assert.are.equals(count - 1, #tab.displayItem.rangeLineList) + assert.are.equals(count - 1, #ranges.list) + ranges:SetSel(2) + if hasImplicit then + assert.are.equals(1, ranges.selIndex) + tab.controls.displayItemRangeSlider:SetVal(0) + assert.are.equals(0, tab.displayItem.rangeLineList[1].range) + else + assert.falsy(ranges:IsShown()) + end + end + end + end) + + it("shows local tincture effect without changing base rolls or character scaling", function() + local tab = build.itemsTab + tab:CreateDisplayItemFromRaw([[ + Rarity: Magic + Prismatic Tincture + Crafted: true + Prefix: {range:0}TinctureEffectFasterToxicity1 + Suffix: {range:1}TinctureElementalPenetration5 + Quality: 24 + Implicits: 1 + {range:1}(70-100)% increased Elemental Damage with Melee Weapons + ]]) + local item = tab.displayItem + item:Craft() + tab:SetDisplayItem(item) + for _, roll in ipairs({ { 0, 17, 28 }, { 0.5, 18, 30 }, { 1, 19, 31 } }) do + item.suffixes[1].range = roll[1] + item:Craft() + local raw = item:BuildRaw() + local tooltip = new("Tooltip"):Tooltip() + tab:AddItemTooltip(tooltip, item) + local text = { } + for _, line in ipairs(tooltip.lines) do + table.insert(text, line.text or "") + end + text = table.concat(text, "\n") + assert.is_truthy(text:find("Melee Weapon Damage Penetrates " .. roll[3] .. "%% Elemental Resistances")) + assert.is_truthy(text:find("167%% increased Elemental Damage with Melee Weapons")) + assert.is_truthy(text:find("35%% increased effect")) + assert.are.equals(raw, item:BuildRaw()) + assert.is_truthy(raw:find("Melee Weapon Damage Penetrates " .. roll[2] .. "%% Elemental Resistances")) + end + tab:AddDisplayItem() + tab.slots["Flask 1"].active = true + runCallback("OnFrame") + assert.are.equals(31, build.calcsTab.mainEnv.player.modDB.mods.ElementalPenetration[1].value) + build.configTab.input.customMods = "Tinctures applied to you have 20% increased effect" + build.configTab:BuildModList() + runCallback("OnFrame") + assert.are.equals(36, build.calcsTab.mainEnv.player.modDB.mods.ElementalPenetration[1].value) + local reloaded = new("Item"):Item(item:BuildRaw()) + assert.are.equals(1.67, reloaded:GetTinctureEffect()) + assert.are.equals(1.24, new("Item"):Item("Rarity: Magic\nPrismatic Tincture\nQuality: 24"):GetTinctureEffect()) + assert.are.equals(1.35, new("Item"):Item("Rarity: Magic\nPrismatic Tincture\nQuality: 0\n35% increased effect"):GetTinctureEffect()) + assert.are.equals(1.92, reloaded:GetTinctureEffect(20)) + local penLine = reloaded.explicitModLines[3] + assert.is_truthy(itemLib.formatModLine(penLine):find("19%% Elemental Resistances")) + assert.is_truthy(itemLib.formatModLine(penLine, false, reloaded:GetTinctureEffect()):find("31%% Elemental Resistances")) + penLine.unscalable = true + assert.is_truthy(itemLib.formatModLine(penLine, false, reloaded:GetTinctureEffect()):find("19%% Elemental Resistances")) + end) + it("shows versioned reusable variant groups", function() build.itemsTab:CreateDisplayItemFromRaw([[ Rarity: Unique diff --git a/src/Classes/Item.lua b/src/Classes/Item.lua index 1618e93e018..95bf3bc97b6 100644 --- a/src/Classes/Item.lua +++ b/src/Classes/Item.lua @@ -2046,6 +2046,13 @@ function ItemClass:BuildRaw() return table.concat(rawLines, "\n") end +-- Local quality/effect also applies to the item preview; character effect is optional. +---@param effectInc number? Character tincture effect, in percent +---@return number +function ItemClass:GetTinctureEffect(effectInc) + return m_floor((1 + (self.tinctureData.effectInc + (effectInc or 0)) / 100) * (1 + (self.quality or 0) / 100) * 100) / 100 +end + function ItemClass:BuildAndParseRaw() local raw = self:BuildRaw() self:ParseRaw(raw) diff --git a/src/Classes/ItemsTab.lua b/src/Classes/ItemsTab.lua index 0b3852aba58..2f75f830664 100644 --- a/src/Classes/ItemsTab.lua +++ b/src/Classes/ItemsTab.lua @@ -811,6 +811,7 @@ holding Shift will put it in the second.]]) self.displayItem:Craft() self:UpdateDisplayItemTooltip() self:UpdateAffixControls() + self:UpdateDisplayItemRangeLines() end) drop.y = function() return i == 1 and 0 or 24 + (prev.slider:IsShown() and 18 or 0) @@ -939,6 +940,7 @@ holding Shift will put it in the second.]]) affix.range = verifyRange(range, index, drop) self.displayItem:Craft() self:UpdateDisplayItemTooltip() + self:UpdateDisplayItemRangeLines() end) slider.width = function() return slider.divCount and 300 or 100 @@ -2478,8 +2480,8 @@ end -- Updates the range line dropdown and range slider for the current display item function ItemsTabClass:UpdateDisplayItemRangeLines() + wipeTable(self.controls.displayItemRangeLine.list) if self.displayItem and self.displayItem.rangeLineList[1] then - wipeTable(self.controls.displayItemRangeLine.list) for _, modLine in ipairs(self.displayItem.rangeLineList) do if (modLine.modId and modLine.newModId) or modLine.range then t_insert(self.controls.displayItemRangeLine.list, { modLine = modLine, label = modLine.line }) @@ -4468,12 +4470,13 @@ function ItemsTabClass:AddItemTooltip(tooltip, item, slot, dbMode, maxWidth) item.requirements.str or 0, item.requirements.dex or 0, item.requirements.int or 0) -- Modifiers + local effectMod = base.tincture and item:GetTinctureEffect() for _, modList in ipairs{item.enchantModLines, item.scourgeModLines, item.implicitModLines, item.explicitModLines, item.crucibleModLines} do if modList[1] then for _, modLine in ipairs(modList) do local variantCount = item:GetModLineVariantCount(modLine) if variantCount > 0 then - local formattedModLine = itemLib.formatModLine(modLine, dbMode) + local formattedModLine = itemLib.formatModLine(modLine, dbMode, effectMod) if formattedModLine then for _ = 1, variantCount do tooltip:AddLine(fontSizeBig, formattedModLine, "FONTIN SC", modLine) diff --git a/src/Modules/CalcPerform.lua b/src/Modules/CalcPerform.lua index c7f75eda6c4..dd82ee9357b 100644 --- a/src/Modules/CalcPerform.lua +++ b/src/Modules/CalcPerform.lua @@ -1870,13 +1870,13 @@ function calcs.perform(env, skipEHP) local tinctureBuffsPerBase = {} local function calcTinctureMods(item, baseName, buffModList, modList) - local tinctureEffectInc = effectInc + item.tinctureData.effectInc + local tinctureEffectInc = effectInc if item.rarity == "MAGIC" then tinctureEffectInc = tinctureEffectInc + effectIncMagic end -- Compute tincture effect multiplier. -- Tincture effect multiplier is rounded to 2 decimal places before applying it. - local effectMod = math.floor((1 + (tinctureEffectInc) / 100) * (1 + (item.quality or 0) / 100) * 100) / 100 + local effectMod = item:GetTinctureEffect(tinctureEffectInc) -- same deal as flasks, go look at the comment there if buffModList[1] then diff --git a/src/Modules/ItemTools.lua b/src/Modules/ItemTools.lua index 2f21380cce1..7a406988709 100644 --- a/src/Modules/ItemTools.lua +++ b/src/Modules/ItemTools.lua @@ -358,10 +358,23 @@ function itemLib.isModLineScalable(line, range, valueScalar) return itemLib.applyRange(line, range, valueScalar, 1) ~= itemLib.applyRange(line, range, valueScalar, 2) end -function itemLib.formatModLine(modLine, dbMode) - local shouldApplyRange = not dbMode and (modLine.range or modLine.corruptedRange) +---@param effectMod number? Display-only effect multiplier; leaves stored rolls unchanged +function itemLib.formatModLine(modLine, dbMode, effectMod) + local valueScalar = modLine.valueScalar + if effectMod and not dbMode and not modLine.unscalable and not modLine.extra then + -- The effect modifier itself must not be amplified, including crafted lines + -- which do not carry advanced-copy's unscalable marker. + for _, mod in ipairs(modLine.modList) do + if mod.name == "LocalEffect" or mod.name == "TinctureEffect" then + effectMod = 1 + break + end + end + valueScalar = (valueScalar or 1) * effectMod + end + local shouldApplyRange = not dbMode and (modLine.range or modLine.corruptedRange or (effectMod and valueScalar)) local line = shouldApplyRange and itemLib.applyRange(modLine.line, modLine.range or main.defaultItemAffixQuality, - modLine.valueScalar, modLine.corruptedRange) or modLine.line + valueScalar, modLine.corruptedRange) or modLine.line if line:match("^%+?0%%? ") or (line:match(" %+?0%%? ") and not line:match("0 to [1-9]")) or line:match(" 0%-0 ") or line:match(" 0 to 0 ") then -- Hack to hide 0-value modifiers return end From 76b49aecff22f9ae26c7e2b5d02310224b69f3d7 Mon Sep 17 00:00:00 2001 From: AdamZ Date: Wed, 9 Sep 2026 15:01:33 -0700 Subject: [PATCH 2/2] Clarify local and character tincture effect comment --- src/Classes/Item.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Classes/Item.lua b/src/Classes/Item.lua index 95bf3bc97b6..a48abcbf709 100644 --- a/src/Classes/Item.lua +++ b/src/Classes/Item.lua @@ -2046,7 +2046,7 @@ function ItemClass:BuildRaw() return table.concat(rawLines, "\n") end --- Local quality/effect also applies to the item preview; character effect is optional. +-- The item preview uses local bonuses only; calculations also pass in character bonuses. ---@param effectInc number? Character tincture effect, in percent ---@return number function ItemClass:GetTinctureEffect(effectInc)