Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion spec/System/TestItemMods_spec.lua
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
describe("TetsItemMods", function()
describe("TestItemMods", function()
before_each(function()
newBuild()
end)
Expand Down
189 changes: 189 additions & 0 deletions spec/System/TestItemParse_spec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -1257,4 +1257,193 @@ describe("TestAdvancedItemParse #item", function()
Note: ~b/o 2 chaos
]])
end)
describe("mod magnitude scaling", function()
before_each(function()
newBuild()
runCallback("onFrame")
end)
local function chaosDamageInc()
return build.calcsTab.mainEnv.modDB:Sum("INC", nil, "ChaosDamage")
end

local function chaosResist()
return build.calcsTab.mainEnv.modDB:Sum("BASE", nil, "ChaosResist")
end

local function spellCrit()
return build.calcsTab.mainEnv.modDB:Sum("INC", { flags = ModFlag.Spell }, "CritChance")
end

local function spellDamage()
return build.calcsTab.mainEnv.modDB:Sum("INC", { flags = ModFlag.Spell }, "Damage")
end

it("scales matching desecrated mods by modifier magnitude", function()
-- 130% * 1.7 = 221
build.itemsTab:CreateDisplayItemFromRaw([[
Rarity: RARE
Test Subject
Ravenous Staff
LevelReq: 60
Implicits: 0
{desecrated}{range:0.5}(100-160)% increased Chaos Damage
{range:0.5}70% increased Desecrated Modifier magnitudes
]])
local item = build.itemsTab.displayItem
assert.is_true(item.advancedCopy)
build.itemsTab:AddDisplayItem()
runCallback("OnFrame")
assert.are.equals(221, chaosDamageInc())
end)

it("does not rescale old format (baked) copies", function()
-- magnitude already baked in, so no rescale
build.itemsTab:CreateDisplayItemFromRaw([[
Rarity: RARE
Baked Subject
Ravenous Staff
LevelReq: 60
Implicits: 0
130% increased Chaos Damage
70% increased Desecrated Modifier magnitudes
]])
local item = build.itemsTab.displayItem
assert.is_false(item.advancedCopy)
build.itemsTab:AddDisplayItem()
runCallback("OnFrame")
assert.are.equals(130, chaosDamageInc())
end)

it("only scales mods that share the magnitude mod's tags", function()
build.itemsTab:CreateDisplayItemFromRaw([[
Rarity: RARE
Test Subject
Sapphire Ring
LevelReq: 20
Implicits: 0
{tags:chaos,damage}{range:0.5}(100-160)% increased Chaos Damage
{tags:resistance}{range:0.5}+(20-40)% to Chaos Resistance
{range:0.5}100% increased resistance modifier magnitudes
]])
build.itemsTab:AddDisplayItem()
runCallback("OnFrame")
assert.are.equals(60, chaosResist())
assert.are.equals(130, chaosDamageInc())
end)

it("only scales the modifier type named by the magnitude mod", function()
build.itemsTab:CreateDisplayItemFromRaw([[
Rarity: RARE
Test Subject
Sapphire Ring
LevelReq: 20
Implicits: 1
{range:0.5}(100-160)% increased Chaos Damage
{range:0.5}+(20-40)% to Chaos Resistance
{range:0.5}100% increased explicit modifier magnitudes
]])
build.itemsTab:AddDisplayItem()
runCallback("OnFrame")
assert.are.equals(130, chaosDamageInc())
assert.are.equals(60, chaosResist())
end)

it("scales only prefixes for increased effect of prefixes", function()
build.itemsTab:CreateDisplayItemFromRaw([[
Rarity: RARE
Test Subject
Sapphire Ring
LevelReq: 20
Implicits: 0
{prefix}{range:0.5}(100-160)% increased Chaos Damage
{suffix}{range:0.5}+(20-40)% to Chaos Resistance
{range:0.5}50% increased effect of prefixes
]])
build.itemsTab:AddDisplayItem()
runCallback("OnFrame")
assert.are.equals(195, chaosDamageInc())
assert.are.equals(30, chaosResist())
end)

-- actually a ring so we don't have to allocate a socket
local realJewel = [[
Rarity: Rare
Pandemonium Desire
Ruby Ring
--------
Quality (Caster Modifiers): +20% (augmented)
--------
Item Level: 80
--------
{ Corruption Enhancement — Elemental, Cold, Resistance }
+7(5-10)% to Cold Resistance
{ Corruption Enhancement — Attribute }
+6(4-6) to Intelligence
--------
{ Fractured Crafted Prefix Modifier "" }
60(40-60)% increased Effect of Suffixes — Unscalable Value
{ Prefix Modifier "Mystic" (Tier: 1) — Damage, Caster — 20% Increased }
7(5-15)% increased Spell Damage
{ Suffix Modifier "of Unmaking" (Tier: 1) — Damage, Caster, Critical — 80% Increased }
20(10-20)% increased Critical Spell Damage Bonus
{ Desecrated Suffix Modifier "of Annihilating" (Tier: 1) — Caster, Critical — 80% Increased }
15(5-15)% increased Critical Hit Chance for Spells
{ Suffix Modifier "of Potency" (Tier: 1) — Damage, Critical — 60% Increased }
20(10-20)% increased Critical Damage Bonus
--------
Place into an allocated Jewel Socket on the Passive Skill Tree. Right click to remove from the Socket.
--------
Twice Corrupted
--------
Fractured Item
--------
Note: ~b/o 1 mirror
]]
it("scales only prefixes for increased effect of prefixes for advanced copy format", function()
assert.equal(0, spellCrit())
local item = new("Item", realJewel)
build.itemsTab:AddItem(item)
build.itemsTab:EquipItemInSet(item, build.itemsTab.activeItemSetId)
runCallback("OnFrame")
assert.equal(26, spellCrit())
assert.equal(8, spellDamage())
end)

it("does not apply scaling twice when saving and loading", function()
local item = new("Item", new("Item", realJewel):BuildRaw())
build.itemsTab:AddItem(item)
build.itemsTab:EquipItemInSet(item, build.itemsTab.activeItemSetId)
runCallback("OnFrame")
assert.equal(26, spellCrit())
assert.equal(8, spellDamage())
end)

it("The Unborn Lich scales its desecrated mods", function()
local raw
for _, itemStr in ipairs(data.uniques.staff) do
if itemStr:find("Unborn Lich") then
raw = itemStr
end
end
if not raw then
error("Couldn't find unborn lich")
end
build.itemsTab:CreateDisplayItemFromRaw(raw)
build.itemsTab:AddDisplayItem()
runCallback("OnFrame")
assert.are.equals(221, chaosDamageInc())

-- the tooltip advertises the same scaled value the calculation uses
local tooltip = new("Tooltip")
build.itemsTab:AddItemTooltip(tooltip, new("Item", raw))
local found = false
for _, section in ipairs(tooltip.lines) do
if section.text and section.text:find("221% increased Chaos Damage", 1, true) then
found = true
break
end
end
assert.is_true(found)
end)
end)
end)
108 changes: 103 additions & 5 deletions src/Classes/Item.lua
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ local function getRangedModList(item, modLine)
if not modLine.range or not modLine.line:find("%((%-?%d+%.?%d*)%-(%-?%d+%.?%d*)%)") then
return
end
local line = itemLib.applyRange(modLine.line:gsub("\n", " "), modLine.range, getCatalystScalar(item.catalyst, modLine, item.catalystQuality), modLine.corruptedRange)
local line = itemLib.applyRange(modLine.line:gsub("\n", " "), modLine.range, modLine.valueScalar, modLine.corruptedRange)
local list, extra = modLib.parseMod(line)
if itemLib.isZeroValueLine(line) then
return { }
Expand All @@ -75,7 +75,7 @@ local ItemClass = newClass("Item", function(self, raw, rarity, highQuality)
end)

local lineFlags = {
["custom"] = true, ["crafted"] = true, ["fractured"] = true, ["desecrated"] = true, ["mutated"] = true, ["enchant"] = true, ["implicit"] = true, ["rune"] = true, ["unscalable"] = true
["custom"] = true, ["crafted"] = true, ["fractured"] = true, ["desecrated"] = true, ["mutated"] = true, ["enchant"] = true, ["implicit"] = true, ["rune"] = true, ["unscalable"] = true, ["prefix"] = true, ["suffix"] = true
}

local function baseHasImplicitLine(base, line)
Expand Down Expand Up @@ -401,6 +401,12 @@ function ItemClass:ParseRaw(raw, rarity, highQuality)
self.socketedSoulCoreTypes = { }
self.implicitModLines = { }
self.explicitModLines = { }
-- old items or trade-sourced items have increases to modifiers baked in to the item text, which
-- means that we can't add e.g. quality or mod magnitude effect to them during parsing. we will
-- 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
self.modMagnitudeMods = {}
local implicitLines = 0
self.variantList = nil
self.prefixes = { }
Expand Down Expand Up @@ -451,6 +457,7 @@ function ItemClass:ParseRaw(raw, rarity, highQuality)
end
elseif line:match("^{ ") then
-- We're parsing advanced copy/paste format
self.advancedCopy = true
linePrefix = ""
linePostfix = ""
self.crafted = true
Expand Down Expand Up @@ -737,6 +744,7 @@ function ItemClass:ParseRaw(raw, rarity, highQuality)
t_insert(modLine.modTags, tag)
end
elseif k == "range" then
self.advancedCopy = true
modLine.range = tonumber(val)
elseif k == "corruptedRange" then
modLine.corruptedRange = tonumber(val)
Expand Down Expand Up @@ -777,6 +785,10 @@ function ItemClass:ParseRaw(raw, rarity, highQuality)
if modLine.fractured then
self.fractured = true
end
-- items containing (#-#) lines are marked since the values won't have catalyst or mod magnitudes applied
if line:find("%((%-?%d+%.?%d*)%-(%-?%d+%.?%d*)%)") then
self.advancedCopy = true
end
local baseName
if not self.base and (self.rarity == "NORMAL" or self.rarity == "MAGIC") then
-- Exact match (affix-less magic and normal items)
Expand Down Expand Up @@ -960,6 +972,10 @@ function ItemClass:ParseRaw(raw, rarity, highQuality)
end

local lineLower = line:lower()
-- \d+% increased/decreased explicit/implicit/ *tags* modifier magnitudes
local modMagnitudePattern = { "(%d+)%% ([id][ne]creased) ?([%a%s]*) modifier magnitudes",
-- \d+% increased/decreased effect of suffixes/prefixes
"(%d+)%% ([id][ne]creased) effect of ([sp][ur][fe]fix)es" }
if lineLower == "implicit modifiers cannot be changed" then
self.implicitsCannotBeChanged = true
elseif lineLower:match(" prefix modifiers? allowed") then
Expand All @@ -978,6 +994,28 @@ function ItemClass:ParseRaw(raw, rarity, highQuality)
self.canHaveThreeEnchants = true
self.canHaveFourEnchants = true
end
for _, pattern in ipairs(modMagnitudePattern) do
if rangedLine:lower():find(pattern) then
local rangedLine = itemLib.applyRange(line, modLine.range or main.defaultItemAffixQuality or 1, catalystScalar, modLine.corruptedRange)
local amount, increaseOrDecrease, modTagsString = rangedLine:lower():match(pattern)
if amount and modTagsString and (increaseOrDecrease == "increased" or increaseOrDecrease == "decreased") then
local modTags = {}
local modType
-- explicit elemental damage -> tags = {elemental, damage}, modType = explicit
for word in (modTagsString .. " "):gmatch("%S+") do
word = word:lower()
if word == "implicit" or word == "explicit" or word == "enchant" then
modType = word
else
table.insert(modTags, word)
end
end
local quality = increaseOrDecrease == "increased" and tonumber(amount) or -tonumber(amount)
table.insert(self.modMagnitudeMods, { tags = modTags, quality = quality, modType = modType })
break
end
end
end
modLine.socketedAugmentTypeOverride = lineLower:match("^this item gains bonuses from socketed items as though it was a? ?(.+)$")
modLine.socketedSoulCoreType = lineLower:match("^this item gains bonuses from socketed soul cores as though it was also a? ?(.+)$")

Expand Down Expand Up @@ -1287,6 +1325,50 @@ function ItemClass:ParseRaw(raw, rarity, highQuality)
self.requirements.level = self.requirements.level or self.requirements.naturalLevel
self.requirements.level = m_max(self.requirements.level, self.requirements.naturalLevel, self.requirements.runeLevel)
end
if self.advancedCopy then
-- apply mod magnitude boost to matching mods
if #self.modMagnitudeMods > 0 then
for _, modMagnitudeMod in ipairs(self.modMagnitudeMods) do
local modLists
if modMagnitudeMod.modType then
modLists = { self[modMagnitudeMod.modType .. "ModLines"] }
else
modLists = { self.implicitModLines, self.explicitModLines, self.enchantModLines }
end
for _, mods in ipairs(modLists) do
for _, mod in ipairs(mods or {}) do
-- Create a fast lookup table for all provided tags
local tagLookup = {}
for _, curTag in ipairs(mod.modTags) do
tagLookup[curTag] = true;
end
-- these aren't actual mod tags but do appear in mod magnitude mods
for _, lineFlag in ipairs({ "desecrated", "prefix", "suffix" }) do
if mod[lineFlag] then
tagLookup[lineFlag] = true
end
end
local match = true
for _, magnitudeTag in ipairs(modMagnitudeMod.tags) do
if not tagLookup[magnitudeTag] then
match = false
end
end
if match then
mod.valueScalar = (mod.valueScalar or 1) + (modMagnitudeMod.quality / 100)
end
if mod.valueScalar and mod.valueScalar ~= 1 then
local rangedLine = itemLib.applyRange(mod.line, mod.range, mod.valueScalar, 1)
local modList, extra = modLib.parseMod(rangedLine)
mod.displayValueScalar = 1
mod.modList = modList
mod.extra = extra
end
end
end
end
end
end
self.affixLimit = 0
if self.crafted then
if not self.affixes then
Expand Down Expand Up @@ -1457,8 +1539,13 @@ function ItemClass:BuildRaw()
t_insert(rawLines, "Item Level: " .. self.itemLevel)
end
local function writeModLine(modLine)
local displayValueScalar = modLine.displayValueScalar and (modLine.valueScalar or 1) * modLine.displayValueScalar
local line = displayValueScalar and itemLib.applyRange(modLine.line, modLine.range or main.defaultItemAffixQuality, displayValueScalar, modLine.corruptedRange) or modLine.line
local line
if modLine.rune then
local displayValueScalar = modLine.displayValueScalar and (modLine.valueScalar or 1) * modLine.displayValueScalar
line = displayValueScalar and itemLib.applyRange(modLine.line, modLine.range or main.defaultItemAffixQuality, displayValueScalar, modLine.corruptedRange) or modLine.line
else
line = modLine.line
end
if modLine.range and line:match("%(%-?[%d%.]+%-%-?[%d%.]+%)") then
line = "{range:" .. round(modLine.range, 3) .. "}" .. line
end
Expand Down Expand Up @@ -1489,6 +1576,12 @@ function ItemClass:BuildRaw()
if modLine.unscalable then
line = "{unscalable}" .. line
end
if modLine.prefix then
line = "{prefix}" .. line
end
if modLine.suffix then
line = "{suffix}" .. line
end
if modLine.variantList then
local varSpec
for varId in pairs(modLine.variantList) do
Expand Down Expand Up @@ -1733,7 +1826,12 @@ function ItemClass:Craft()
return tonumber(num) + tonumber(other)
end)
else
local modLine = { line = line, order = order }
local modLine = { line = line, order = order, type = mod.type }
if mod.type == "Prefix" then
modLine.prefix = true
elseif mod.type == "Suffix" then
modLine.suffix = true
end
for l = 1, #self.explicitModLines + 1 do
if not self.explicitModLines[l] or self.explicitModLines[l].order > order then
t_insert(self.explicitModLines, l, modLine)
Expand Down
Loading
Loading