Skip to content

Commit 681eb64

Browse files
author
LocalIdentity
committed
Merge branch 'dev' into add-atziris-communion-support
2 parents a9b1dfa + 1bc1685 commit 681eb64

28 files changed

Lines changed: 295 additions & 76 deletions

spec/System/TestImportReimport_spec.lua

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,39 @@ Fireball 20/0 1
187187
assert.is_false(groupsByGem.Fireball.enabled)
188188
end)
189189

190+
it("imports item socketed jewels using jewel socket order instead of raw socket index", function()
191+
build.importTab.controls.charImportItemsClearItems.state = true
192+
build.importTab.controls.charImportItemsClearSkills.state = true
193+
194+
local gloves = makeImportItem("Linen Wraps", "Gloves", "test-import-gloves")
195+
gloves.sockets = {
196+
{ type = "rune" },
197+
{ type = "jewel" },
198+
}
199+
gloves.socketedItems = {
200+
{ baseType = "Greater Rune of Nobility" },
201+
{
202+
id = "test-import-jewel",
203+
frameType = 2,
204+
name = "Vivid Ornament",
205+
typeLine = "Sapphire",
206+
baseType = "Sapphire",
207+
inventoryId = "PassiveJewels",
208+
ilvl = DEFAULT_ITEM_LEVEL,
209+
properties = {},
210+
socket = 1,
211+
},
212+
}
213+
214+
build.importTab:ImportItemsAndSkills(buildImportPayload({ gloves }, {}))
215+
runCallback("OnFrame")
216+
217+
local socketedJewel = build.itemsTab.items[build.itemsTab.slots["Gloves Jewel Socket 1"].selItemId]
218+
assert.is_not_nil(socketedJewel)
219+
assert.are.equal("test-import-jewel", socketedJewel.uniqueID)
220+
assert.are.equal(0, build.itemsTab.slots["Gloves Jewel Socket 2"].selItemId)
221+
end)
222+
190223
it("preserves skill part selection when reimporting items and skills", function()
191224
assertReimportPreservesSkillSubstate("Twig Focus", "Offhand", "Dark Effigy", "skillPart", 2)
192225
end)

spec/System/TestItemMods_spec.lua

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,20 @@ describe("TetsItemMods", function()
3333
assert.are.equals(2, legacyLines)
3434
end)
3535

36+
it("shows a fallback tooltip when an item's base is no longer supported", function()
37+
local item = new("Item", [[
38+
Rarity: Unique
39+
Legacy Item
40+
Removed Base
41+
]])
42+
local tooltip = new("Tooltip")
43+
44+
assert.has_no.errors(function()
45+
build.itemsTab:AddItemTooltip(tooltip, item)
46+
end)
47+
assert.is_truthy(tooltip.lines[#tooltip.lines].text:find("Item base is not supported", 1, true))
48+
end)
49+
3650
it("aggregates matching ring item rarity lines before applying ring bonus effect", function()
3751
build.configTab.input.customMods = "30% increased bonuses gained from left Equipped Ring"
3852
build.configTab:BuildModList()

spec/System/TestItemParse_spec.lua

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -591,6 +591,28 @@ describe("TestItemParse", function()
591591
assert.is_not_nil(item:BuildRaw():match("{enchant}{rune}Gain 18%% of Damage as Extra Fire Damage"))
592592
end)
593593

594+
it("applies increased effect of socketed augment items", function()
595+
local item = new("Item", [[
596+
Test Wand
597+
Runic Fork
598+
Sockets: S
599+
Rune: Lesser Desert Rune
600+
Implicits: 1
601+
{enchant}{rune}Gain 6% of Damage as Extra Fire Damage
602+
100% increased effect of Socketed Augment Items
603+
]])
604+
item:BuildAndParseRaw()
605+
606+
local damageGainAsFire = 0
607+
for _, mod in ipairs(item.slotModList[1]) do
608+
if mod.name == "DamageGainAsFire" and mod.type == "BASE" then
609+
damageGainAsFire = damageGainAsFire + mod.value
610+
end
611+
end
612+
assert.are.equals(12, damageGainAsFire)
613+
assert.is_not_nil(item:BuildRaw():match("{enchant}{rune}Gain 12%% of Damage as Extra Fire Damage"))
614+
end)
615+
594616
it("does not double-scale imported socketed rune text", function()
595617
local item = new("Item", [[
596618
Runeseeker's Call

spec/System/TestSkills_spec.lua

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,41 @@ describe("TestSkills", function()
2828
end
2929
end
3030

31+
local function assertGemSupportLevel(gemName, expectedLevel, expectedCount)
32+
local count = 0
33+
for _, activeSkill in ipairs(build.calcsTab.calcsEnv.player.activeSkillList) do
34+
if activeSkill.activeEffect.gemData and activeSkill.activeEffect.gemData.name == gemName then
35+
count = count + 1
36+
assert.are.equals(expectedLevel, activeSkill.skillModList:Sum("BASE", activeSkill.skillCfg, "GemSupportLevel"))
37+
end
38+
end
39+
assert.are.equals(expectedCount, count)
40+
end
41+
42+
it("evaluates GemTag mod tags against active skill gem tags", function()
43+
local modDB = build.calcsTab.mainEnv.modDB
44+
45+
modDB:NewMod("Damage", "INC", 10, "Test Fire GemTag", { type = "GemTag", gemTag = "Fire" })
46+
modDB:NewMod("Damage", "INC", 20, "Test Elemental GemTagList", { type = "GemTag", gemTagList = { "Cold", "Lightning" } })
47+
modDB:NewMod("Damage", "INC", 40, "Test Not Minion GemTag", { type = "GemTag", gemTag = "Minion", neg = true })
48+
49+
assert.are.equals(50, modDB:Sum("INC", { skillGem = { tags = { fire = true } } }, "Damage"))
50+
assert.are.equals(60, modDB:Sum("INC", { skillGem = { tags = { cold = true } } }, "Damage"))
51+
assert.are.equals(0, modDB:Sum("INC", { skillGem = { tags = { minion = true } } }, "Damage"))
52+
end)
53+
54+
it("applies Fire Mastery level to Apocalypse through the source gem tag", function()
55+
build.skillsTab:PasteSocketGroup("Apocalypse 20/0 1\nFire Mastery 1/0 1")
56+
runCallback("OnFrame")
57+
assertGemSupportLevel("Apocalypse", 1, 4)
58+
end)
59+
60+
it("evaluates conditional gem levels using the source gem support count", function()
61+
build.skillsTab:PasteSocketGroup("Apocalypse 20/0 1\nFire Mastery 1/0 1\nUhtred's Omen 1/0 1")
62+
runCallback("OnFrame")
63+
assertGemSupportLevel("Apocalypse", 3, 4)
64+
end)
65+
3166

3267
it("uses granted effect minion list when active skill minion list is missing", function()
3368
local srcInstance = { statSet = { }, skillPart = { }, nameSpec = "Spectre: Test" }
@@ -111,6 +146,17 @@ describe("TestSkills", function()
111146
assert.are.equals(20, build.calcsTab.mainOutput.LifeReservedPercent)
112147
end)
113148

149+
it("rounds Blasphemy curse magnitudes to the nearest integer", function()
150+
build.configTab.input.customMods = "79% increased Curse Magnitudes"
151+
build.configTab.input.enemyIsBoss = "None"
152+
build.configTab:BuildModList()
153+
build.skillsTab:PasteSocketGroup("Blasphemy 10/0 1\nDespair 12/0 1\n")
154+
155+
runCallback("OnFrame")
156+
157+
assert.are.equals(-42, build.calcsTab.mainEnv.enemyDB:Sum("BASE", nil, "ChaosResist"))
158+
end)
159+
114160
it("applies active skill reservation multiplier to linked buff spirit reservation", function()
115161
build.skillsTab:PasteSocketGroup("Purity of Fire 20/0 1\nVitality II 1/0 1\n")
116162
runCallback("OnFrame")

src/Classes/CalcBreakdownControl.lua

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -481,6 +481,8 @@ function CalcBreakdownClass:AddModSection(sectionData, modList)
481481
else
482482
desc = "Skill type: "..(tag.neg and "Not " or "")..self:FormatModName(SkillTypeName[tag.skillType])
483483
end
484+
elseif tag.type == "GemTag" then
485+
desc = "Gem tag: "..(tag.neg and "Not " or "")..self:FormatVarNameOrList(tag.gemTag, tag.gemTagList)
484486
elseif tag.type == "BaseFlag" then
485487
desc = "Base flag: "..(tag.neg and "Not " or "")..self:FormatModName(tostring(tag.baseFlag))
486488
elseif tag.type == "SlotNumber" then

src/Classes/ImportTab.lua

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -328,8 +328,9 @@ local ImportTabClass = newClass("ImportTab", "ControlHost", "Control", function(
328328
end
329329

330330
if self.importCodeJson then
331-
self:ImportItemsAndSkills(self.importCodeJson)
332331
self:ImportPassiveTreeAndJewels(self.importCodeJson)
332+
self.build.calcsTab:BuildOutput()
333+
self:ImportItemsAndSkills(self.importCodeJson)
333334
return
334335
end
335336

@@ -1286,7 +1287,7 @@ function ImportTabClass:ImportItem(itemData, slotName)
12861287

12871288
item.runes = { }
12881289
if itemData.socketedItems then
1289-
self:ImportSocketedItems(item, itemData.socketedItems, slotName)
1290+
self:ImportSocketedItems(item, itemData.socketedItems, slotName, itemData.sockets)
12901291
end
12911292
if itemData.requirements and (not itemData.socketedItems or not itemData.socketedItems[1]) then
12921293
-- Requirements cannot be trusted if there are socketed gems, as they may override the item's natural requirements
@@ -1437,11 +1438,19 @@ function ImportTabClass:ImportItem(itemData, slotName)
14371438
end
14381439
end
14391440

1440-
function ImportTabClass:ImportSocketedItems(item, socketedItems, slotName)
1441+
function ImportTabClass:ImportSocketedItems(item, socketedItems, slotName, sockets)
14411442
-- Build socket group list
14421443
for _, socketedItem in ipairs(socketedItems) do
14431444
if isValueInTable({ "Diamond", "Emerald", "Ruby", "Sapphire" }, socketedItem.baseType) then
1444-
self:ImportItem(socketedItem, slotName .. " Jewel Socket "..socketedItem.socket + 1)
1445+
local jewelSocketIndex = socketedItem.socket + 1
1446+
if sockets then
1447+
for index = 1, socketedItem.socket + 1 do
1448+
if sockets[index] and sockets[index].type ~= "jewel" then
1449+
jewelSocketIndex = jewelSocketIndex - 1
1450+
end
1451+
end
1452+
end
1453+
self:ImportItem(socketedItem, slotName .. " Jewel Socket "..jewelSocketIndex)
14451454
else
14461455
t_insert(item.runes, socketedItem.baseType)
14471456
end

src/Classes/Item.lua

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1140,13 +1140,13 @@ function ItemClass:ParseRaw(raw, rarity, highQuality)
11401140
table.sort(runes, function(a, b) return compareRuneValueSets(a.values, b.values) end)
11411141
end
11421142

1143-
local gameSocketedRuneEffectModifier = 0
1143+
local gameSocketedAugmentEffectModifier = 0
11441144
if mode == "GAME" and shouldFixRunesOnItem then
11451145
for _, modLines in ipairs({ self.enchantModLines, self.implicitModLines, self.explicitModLines }) do
11461146
for _, effectModLine in ipairs(modLines) do
11471147
for _, mod in ipairs(effectModLine.modList or { }) do
1148-
if mod.name == "SocketedRuneEffect" and mod.type == "INC" then
1149-
gameSocketedRuneEffectModifier = gameSocketedRuneEffectModifier + mod.value / 100
1148+
if (mod.name == "SocketedRuneEffect" or mod.name == "SocketedAugmentItemEffect") and mod.type == "INC" then
1149+
gameSocketedAugmentEffectModifier = gameSocketedAugmentEffectModifier + mod.value / 100
11501150
end
11511151
end
11521152
end
@@ -1160,10 +1160,10 @@ function ItemClass:ParseRaw(raw, rarity, highQuality)
11601160
if groupedRunes and not modLine.bonded then -- found the rune category with the relevant stat.
11611161
local result, numRunes
11621162
local socketedRuneEffectAlreadyApplied
1163-
if gameSocketedRuneEffectModifier ~= 0 then
1163+
if gameSocketedAugmentEffectModifier ~= 0 then
11641164
local unscaledTargetValues = { }
11651165
for valueIndex, value in ipairs(targetValues) do
1166-
unscaledTargetValues[valueIndex] = value / (1 + gameSocketedRuneEffectModifier)
1166+
unscaledTargetValues[valueIndex] = value / (1 + gameSocketedAugmentEffectModifier)
11671167
end
11681168
result, numRunes = findRuneCombination(groupedRunes, unscaledTargetValues, remainingRunes)
11691169
socketedRuneEffectAlreadyApplied = result ~= nil
@@ -1604,8 +1604,12 @@ end
16041604

16051605
function ItemClass:ApplySocketedRuneDisplayScalars()
16061606
for _, modLine in ipairs(self.runeModLines or { }) do
1607-
local effectModifier = modLine.augmentType == "SoulCore" and (self.socketedSoulCoreEffectModifier or 0)
1608-
or modLine.augmentType == "Rune" and (self.socketedRuneEffectModifier or 0)
1607+
local effectModifier = self.socketedAugmentItemEffectModifier or 0
1608+
if modLine.augmentType == "SoulCore" then
1609+
effectModifier = effectModifier + (self.socketedSoulCoreEffectModifier or 0)
1610+
elseif modLine.augmentType == "Rune" then
1611+
effectModifier = effectModifier + (self.socketedRuneEffectModifier or 0)
1612+
end
16091613
if effectModifier and effectModifier ~= 0 and not modLine.socketedRuneEffectAlreadyApplied then
16101614
modLine.displayValueScalar = 1 + effectModifier
16111615
else
@@ -2105,12 +2109,17 @@ function ItemClass:BuildModList()
21052109
end
21062110
self.socketedSoulCoreEffectModifier = calcLocal(baseList, "SocketedSoulCoreEffect", "INC", 0) / 100
21072111
self.socketedRuneEffectModifier = calcLocal(baseList, "SocketedRuneEffect", "INC", 0) / 100
2112+
self.socketedAugmentItemEffectModifier = calcLocal(baseList, "SocketedAugmentItemEffect", "INC", 0) / 100
21082113
if self.runeModLines[1] then
21092114
self:ApplySocketedRuneDisplayScalars()
21102115
end
21112116
for _, modLine in ipairs(self.runeModLines) do
2112-
local effectModifier = modLine.augmentType == "SoulCore" and self.socketedSoulCoreEffectModifier
2113-
or modLine.augmentType == "Rune" and self.socketedRuneEffectModifier
2117+
local effectModifier = self.socketedAugmentItemEffectModifier or 0
2118+
if modLine.augmentType == "SoulCore" then
2119+
effectModifier = effectModifier + self.socketedSoulCoreEffectModifier
2120+
elseif modLine.augmentType == "Rune" then
2121+
effectModifier = effectModifier + self.socketedRuneEffectModifier
2122+
end
21142123
if effectModifier and effectModifier ~= 0 and self:CheckModLineVariant(modLine) and not modLine.extra and not modLine.socketedRuneEffectAlreadyApplied then
21152124
for _, mod in ipairs(modLine.modList) do
21162125
baseList:ScaleAddMod(mod, effectModifier)
@@ -2226,4 +2235,4 @@ function ItemClass:BuildModList()
22262235
else
22272236
self.modList = self:BuildModListForSlotNum(baseList)
22282237
end
2229-
end
2238+
end

src/Classes/ItemsTab.lua

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3232,6 +3232,13 @@ function ItemsTabClass:AddItemTooltip(tooltip, item, slot, dbMode, maxWidth)
32323232
tooltip.tooltipHeader = item.rarity
32333233
tooltip.center = true
32343234
tooltip.color = rarityCode
3235+
-- Shared items can use old base names that no longer exist. Add a tooltip so they can be copied or removed without causing a crash.
3236+
if not item.base or not item.baseName then
3237+
tooltip:AddLine(fontSizeTitle, rarityCode..(item.title or item.name or "Unknown Item"), "FONTIN SC")
3238+
tooltip:AddSeparator(30)
3239+
tooltip:AddLine(fontSizeTitle, colorCodes.NEGATIVE.."Item base is not supported by the current version.", "FONTIN SC")
3240+
return
3241+
end
32353242
-- Item name
32363243
if item.title then
32373244
tooltip:AddLine(fontSizeTitle, rarityCode..item.title, "FONTIN SC")

src/Classes/ModStore.lua

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,8 @@ local function getActor(self, actorType)
4242
end
4343
end
4444

45-
function ModStoreClass:ScaleAddMod(mod, scale)
45+
-- roundToNearest is reserved for effects that the game rounds instead of truncates.
46+
function ModStoreClass:ScaleAddMod(mod, scale, roundToNearest)
4647
local unscalable = false
4748
for _, effects in ipairs(mod) do
4849
if effects.unscalable then
@@ -71,6 +72,8 @@ function ModStoreClass:ScaleAddMod(mod, scale)
7172
if precision then
7273
local power = 10 ^ precision
7374
subMod.value = m_floor(subMod.value * scale * power) / power
75+
elseif roundToNearest then
76+
subMod.value = roundSymmetric(subMod.value * scale)
7477
else
7578
subMod.value = m_modf(round(subMod.value * scale, 2))
7679
end
@@ -85,12 +88,12 @@ function ModStoreClass:CopyList(modList)
8588
end
8689
end
8790

88-
function ModStoreClass:ScaleAddList(modList, scale)
91+
function ModStoreClass:ScaleAddList(modList, scale, roundToNearest)
8992
if scale == 1 then
9093
self:AddList(modList)
9194
else
9295
for i = 1, #modList do
93-
self:ScaleAddMod(modList[i], scale)
96+
self:ScaleAddMod(modList[i], scale, roundToNearest)
9497
end
9598
end
9699
end
@@ -557,8 +560,8 @@ function ModStoreClass:EvalMod(mod, cfg, globalLimits)
557560
local stat
558561
if tag.statList then
559562
stat = 0
560-
for _, stat in ipairs(tag.statList) do
561-
stat = stat + GetStat(self, stat, cfg)
563+
for _, statName in ipairs(tag.statList) do
564+
stat = stat + GetStat(self, statName, cfg)
562565
end
563566
else
564567
stat = GetStat(self, tag.stat, cfg)
@@ -827,6 +830,25 @@ function ModStoreClass:EvalMod(mod, cfg, globalLimits)
827830
if not match then
828831
return
829832
end
833+
elseif tag.type == "GemTag" then
834+
local match = false
835+
local gemTags = cfg and cfg.skillGem and cfg.skillGem.tags
836+
if tag.gemTagList then
837+
for _, gemTag in pairs(tag.gemTagList) do
838+
if gemTags and gemTags[gemTag:lower()] then
839+
match = true
840+
break
841+
end
842+
end
843+
else
844+
match = gemTags and gemTags[tag.gemTag:lower()]
845+
end
846+
if tag.neg then
847+
match = not match
848+
end
849+
if not match then
850+
return
851+
end
830852
elseif tag.type == "BaseFlag" then
831853
local match = false
832854
if cfg and cfg.skillGem and cfg.skillGem.grantedEffect and cfg.skillGem.grantedEffect.statSets and cfg.skillGem.grantedEffect.statSets[1] then

0 commit comments

Comments
 (0)