Skip to content

Commit 735652d

Browse files
committed
AI fix for Tincture mods being applied twice when imported from character
1 parent 2ea45ef commit 735652d

3 files changed

Lines changed: 247 additions & 1 deletion

File tree

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
describe("TestTinctureImport", function()
2+
local function newImportedTincture(baseName, quality, implicitLines, explicitLines)
3+
local item = new("Item")
4+
item.baseName = baseName
5+
item.base = data.itemBases[baseName]
6+
item.type = "Tincture"
7+
item.name = baseName
8+
item.rarity = "MAGIC"
9+
item.quality = quality
10+
item.implicitModLines = { }
11+
item.explicitModLines = { }
12+
item.enchantModLines = { }
13+
item.scourgeModLines = { }
14+
item.classRequirementModLines = { }
15+
item.crucibleModLines = { }
16+
for _, line in ipairs(implicitLines or { }) do
17+
table.insert(item.implicitModLines, { line = line })
18+
end
19+
for _, line in ipairs(explicitLines or { }) do
20+
table.insert(item.explicitModLines, { line = line })
21+
end
22+
return item
23+
end
24+
25+
it("normalises Rosethorn import lines back to base values", function()
26+
local item = newImportedTincture("Rosethorn Tincture", 20, {
27+
"216% increased Critical Strike Chance with Melee Weapons",
28+
}, {
29+
"20% increased effect",
30+
"36% increased Melee Weapon Attack Speed",
31+
})
32+
33+
item:NormaliseImportedTinctureModLines()
34+
item:BuildAndParseRaw()
35+
36+
assert.are.equal("150% increased Critical Strike Chance with Melee Weapons", item.implicitModLines[1].line)
37+
assert.are.equal("20% increased effect", item.explicitModLines[1].line)
38+
assert.are.equal("25% increased Melee Weapon Attack Speed", item.explicitModLines[2].line)
39+
end)
40+
41+
it("preserves non-scaled Mana Burn lines while normalising effect-scaled tincture lines", function()
42+
local item = newImportedTincture("Prismatic Tincture", 20, {
43+
"162% increased Elemental Damage with Melee Weapons",
44+
}, {
45+
"35% increased effect",
46+
"37% increased Mana Burn rate",
47+
})
48+
49+
item:NormaliseImportedTinctureModLines()
50+
item:BuildAndParseRaw()
51+
52+
assert.are.equal("100% increased Elemental Damage with Melee Weapons", item.implicitModLines[1].line)
53+
assert.are.equal("35% increased effect", item.explicitModLines[1].line)
54+
assert.are.equal("37% increased Mana Burn rate", item.explicitModLines[2].line)
55+
end)
56+
57+
it("can recover the base local effect roll when the imported effect line is already quality-scaled", function()
58+
local item = newImportedTincture("Prismatic Tincture", 20, {
59+
"162% increased Elemental Damage with Melee Weapons",
60+
}, {
61+
"42% increased effect",
62+
"37% increased Mana Burn rate",
63+
})
64+
65+
item:NormaliseImportedTinctureModLines()
66+
item:BuildAndParseRaw()
67+
68+
assert.are.equal("100% increased Elemental Damage with Melee Weapons", item.implicitModLines[1].line)
69+
assert.are.equal("35% increased effect", item.explicitModLines[1].line)
70+
assert.are.equal("37% increased Mana Burn rate", item.explicitModLines[2].line)
71+
end)
72+
end)

src/Classes/ImportTab.lua

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1083,6 +1083,10 @@ function ImportTabClass:ImportItem(itemData, slotName)
10831083
item.foilType = foilVariants[itemData.foilVariation] or "Rainbow"
10841084
end
10851085

1086+
if item.base and item.base.tincture then
1087+
item:NormaliseImportedTinctureModLines()
1088+
end
1089+
10861090
-- Add and equip the new item
10871091
item:BuildAndParseRaw()
10881092
--ConPrintf("%s", item.raw)
@@ -1231,4 +1235,4 @@ function ImportTabClass:SetPredefinedBuildName()
12311235
local charData = charSelect.list[charSelect.selIndex].char
12321236
local charName = charData.name
12331237
main.predefinedBuildName = accountName.." - "..charName
1234-
end
1238+
end

src/Classes/Item.lua

Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
local ipairs = ipairs
77
local t_insert = table.insert
88
local t_remove = table.remove
9+
local m_abs = math.abs
910
local m_min = math.min
1011
local m_max = math.max
1112
local m_floor = math.floor
@@ -962,6 +963,175 @@ function ItemClass:NormaliseQuality()
962963
end
963964
end
964965

966+
local tinctureLocalModNames = {
967+
CooldownRecovery = true,
968+
LocalEffect = true,
969+
TinctureCooldownRecovery = true,
970+
TinctureEffect = true,
971+
TinctureManaBurnRate = true,
972+
}
973+
974+
local function getTinctureModLineParse(line)
975+
if not line then
976+
return
977+
end
978+
return modLib.parseMod(line:gsub("\n", " "))
979+
end
980+
981+
local function getTinctureModLineSignature(modList)
982+
local signature = { }
983+
for _, mod in ipairs(modList or { }) do
984+
t_insert(signature, modLib.formatMod(mod))
985+
end
986+
table.sort(signature)
987+
return table.concat(signature, "\n")
988+
end
989+
990+
local function tinctureModLineShouldScale(modList)
991+
for _, mod in ipairs(modList or { }) do
992+
local scaledMod = (type(mod.value) == "table" and mod.value.mod) or mod
993+
if tinctureLocalModNames[scaledMod.name] then
994+
return false
995+
end
996+
end
997+
return true
998+
end
999+
1000+
local function tinctureModLineHasLocalEffect(modList)
1001+
for _, mod in ipairs(modList or { }) do
1002+
local scaledMod = (type(mod.value) == "table" and mod.value.mod) or mod
1003+
if scaledMod.name == "LocalEffect" or scaledMod.name == "TinctureEffect" then
1004+
return true
1005+
end
1006+
end
1007+
return false
1008+
end
1009+
1010+
local function getTinctureRangeSteps(templateLine)
1011+
local maxSteps = 0
1012+
for min, max in templateLine:gmatch("%((%-?%d+%.?%d*)%-(%-?%d+%.?%d*)%)") do
1013+
local minStr, maxStr = tostring(min), tostring(max)
1014+
local minPrecision = minStr:match("%.(%d+)") and #minStr:match("%.(%d+)") or 0
1015+
local maxPrecision = maxStr:match("%.(%d+)") and #maxStr:match("%.(%d+)") or 0
1016+
local power = 10 ^ m_max(minPrecision, maxPrecision)
1017+
local steps = m_floor(m_abs((tonumber(max) - tonumber(min)) * power) + 0.5)
1018+
maxSteps = m_max(maxSteps, steps)
1019+
end
1020+
return maxSteps
1021+
end
1022+
1023+
local function getTinctureScaledModList(modList, scale)
1024+
if scale == 1 then
1025+
return modList
1026+
end
1027+
local scaledList = new("ModList")
1028+
scaledList:ScaleAddList(modList, scale)
1029+
return { unpack(scaledList) }
1030+
end
1031+
1032+
local function matchTinctureModLine(targetLine, templateLines, scale, forceScale)
1033+
local targetModList, targetExtra = getTinctureModLineParse(targetLine)
1034+
if not targetModList or targetExtra or not templateLines then
1035+
return
1036+
end
1037+
local targetShouldScale = forceScale or tinctureModLineShouldScale(targetModList)
1038+
local targetSignature = getTinctureModLineSignature(targetModList)
1039+
for _, templateLine in ipairs(templateLines) do
1040+
local steps = getTinctureRangeSteps(templateLine)
1041+
local seen = { }
1042+
for step = 0, m_max(steps, 0) do
1043+
local candidateLine = steps > 0 and itemLib.applyRange(templateLine, step / steps) or templateLine
1044+
if not seen[candidateLine] then
1045+
seen[candidateLine] = true
1046+
local candidateModList, candidateExtra = getTinctureModLineParse(candidateLine)
1047+
if candidateModList and not candidateExtra then
1048+
if targetShouldScale and scale ~= 1 then
1049+
candidateModList = getTinctureScaledModList(candidateModList, scale)
1050+
end
1051+
if getTinctureModLineSignature(candidateModList) == targetSignature then
1052+
return candidateLine
1053+
end
1054+
end
1055+
end
1056+
end
1057+
end
1058+
end
1059+
1060+
function ItemClass:NormaliseImportedTinctureModLines()
1061+
if not self.base or not self.base.tincture then
1062+
return
1063+
end
1064+
1065+
local explicitTemplateLines = { }
1066+
if self.rarity == "UNIQUE" or self.rarity == "RELIC" then
1067+
local uniqueItem = main and main.uniqueDB and main.uniqueDB.list and main.uniqueDB.list[self.name]
1068+
if uniqueItem then
1069+
for _, modLine in ipairs(uniqueItem.explicitModLines) do
1070+
t_insert(explicitTemplateLines, modLine.line)
1071+
end
1072+
end
1073+
else
1074+
local affixes = self.affixes
1075+
or (self.base.subType and data.itemMods[self.base.type .. self.base.subType])
1076+
or data.itemMods[self.base.type]
1077+
or data.itemMods.Item
1078+
for _, affix in pairs(affixes or { }) do
1079+
for _, line in ipairs(affix) do
1080+
t_insert(explicitTemplateLines, line)
1081+
end
1082+
end
1083+
end
1084+
1085+
local localEffectInc = 0
1086+
for _, modLine in ipairs(self.explicitModLines) do
1087+
local modList = getTinctureModLineParse(modLine.line)
1088+
local matchedLine
1089+
if tinctureModLineHasLocalEffect(modList) then
1090+
matchedLine = matchTinctureModLine(modLine.line, explicitTemplateLines, 1)
1091+
if not matchedLine and (self.quality or 0) > 0 then
1092+
matchedLine = matchTinctureModLine(modLine.line, explicitTemplateLines, 1 + (self.quality or 0) / 100, true)
1093+
end
1094+
end
1095+
if matchedLine then
1096+
modLine.line = matchedLine
1097+
end
1098+
modList = getTinctureModLineParse(modLine.line)
1099+
for _, mod in ipairs(modList or { }) do
1100+
local scaledMod = (type(mod.value) == "table" and mod.value.mod) or mod
1101+
if scaledMod.type == "INC" and (scaledMod.name == "LocalEffect" or scaledMod.name == "TinctureEffect") then
1102+
localEffectInc = localEffectInc + scaledMod.value
1103+
end
1104+
end
1105+
end
1106+
1107+
local effectMod = m_floor((1 + localEffectInc / 100) * (1 + (self.quality or 0) / 100) * 100 + 0.0001) / 100
1108+
if effectMod == 1 then
1109+
return
1110+
end
1111+
1112+
local implicitTemplateLines = { }
1113+
if self.base.implicit then
1114+
for line in self.base.implicit:gmatch("[^\n]+") do
1115+
t_insert(implicitTemplateLines, line)
1116+
end
1117+
end
1118+
1119+
for index, modLine in ipairs(self.implicitModLines) do
1120+
local templateLines = implicitTemplateLines[index] and { implicitTemplateLines[index] } or implicitTemplateLines
1121+
local matchedLine = matchTinctureModLine(modLine.line, templateLines, effectMod)
1122+
if matchedLine then
1123+
modLine.line = matchedLine
1124+
end
1125+
end
1126+
1127+
for _, modLine in ipairs(self.explicitModLines) do
1128+
local matchedLine = matchTinctureModLine(modLine.line, explicitTemplateLines, effectMod)
1129+
if matchedLine then
1130+
modLine.line = matchedLine
1131+
end
1132+
end
1133+
end
1134+
9651135
function ItemClass:GetModSpawnWeight(mod, includeTags, excludeTags)
9661136
local weight = 0
9671137
if self.base then

0 commit comments

Comments
 (0)