Skip to content
Merged
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
161 changes: 161 additions & 0 deletions spec/System/TestSkills_spec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,167 @@ describe("TestSkills", function()
-- newBuild() takes care of resetting everything in setup()
end)

local function gemTooltipText(gem)
local tooltip = new("Tooltip"):Tooltip()
require("Classes.GemTooltip").AddGemTooltip(tooltip, build, gem)
local lines = {}
for _, line in ipairs(tooltip.lines) do
table.insert(lines, line.text)
end
return table.concat(lines, "\n")
end

it("separates socketed and global modifiers from the same item on a support gem", function()
build.itemsTab:CreateDisplayItemFromRaw("Rarity: RARE\nTest Helmet\nHubris Circlet\nSockets: B-B\nImplicits: 0\n+2 to Level of Socketed Gems\n+10% to Quality of Socketed Gems\n+1 to Level of all Support Gems\n+3% to Quality of all Support Gems")
build.itemsTab:AddDisplayItem()
build.skillsTab:PasteSocketGroup("Slot: Helmet\nFireball 20/0 1\nControlled Destruction 20/5 1\n")
runCallback("OnFrame")

local gem = build.skillsTab.socketGroupList[1].gemList[2]
assert.are.equals(23, gem.displayEffect.level)
assert.are.equals(28, gem.displayEffect.quality)
local text = gemTooltipText(gem)
assert.is_truthy(text:find("+2 Levels from Item", 1, true))
assert.is_truthy(text:find("+1 Levels from Global Modifiers", 1, true))
assert.is_truthy(text:find("+10% Quality from Item", 1, true))
assert.is_truthy(text:find("+3% Quality from Global Modifiers", 1, true))
assert.is_truthy(text:find("+10% Quality from Socket Colour", 1, true))
assert.is_nil(text:find("from Supports", 1, true))
end)

it("includes negative item and global modifiers in gem tooltips and breakdowns", function()
build.itemsTab:CreateDisplayItemFromRaw("Rarity: RARE\nTest Helmet\nHubris Circlet\nSockets: G\nImplicits: 0\n-5 to Level of Socketed Gems\n-10% to Quality of Socketed Gems")
build.itemsTab:AddDisplayItem()
build.skillsTab:PasteSocketGroup("Slot: Helmet\nFireball 20/20 1\n")
build.configTab.input.customMods = "-1 to Level of all Spell Skill Gems\n-2% to Quality of all Skill Gems"
build.configTab:BuildModList()
runCallback("OnFrame")

assert.are.equals(14, build.calcsTab.mainOutput.GemLevel)
assert.are.equals(8, build.calcsTab.mainOutput.GemQuality)
local text = gemTooltipText(build.skillsTab.socketGroupList[1].gemList[1])
assert.is_truthy(text:find("-5 Levels from Item", 1, true))
assert.is_truthy(text:find("-1 Levels from Global Modifiers", 1, true))
assert.is_truthy(text:find("-10% Quality from Item", 1, true))
assert.is_truthy(text:find("-2% Quality from Global Modifiers", 1, true))
local breakdown = build.calcsTab.calcsEnv.player.breakdown
assert.is_truthy(table.concat(breakdown.GemLevel, "\n"):find("- 5 ^8(level from items)", 1, true))
assert.is_truthy(table.concat(breakdown.GemLevel, "\n"):find("- 1 ^8(level from global modifiers)", 1, true))
assert.is_truthy(table.concat(breakdown.GemQuality, "\n"):find("- 10 ^8(quality from items)", 1, true))
assert.is_truthy(table.concat(breakdown.GemQuality, "\n"):find("- 2 ^8(quality from global modifiers)", 1, true))
end)

it("shows the applied level cap without losing the source bonuses", function()
build.skillsTab:PasteSocketGroup("Fireball 20/0 1\n")
build.configTab.input.customMods = "+30 to Level of all Spell Skill Gems"
build.configTab:BuildModList()
runCallback("OnFrame")

assert.are.equals(40, build.calcsTab.mainEnv.player.mainSkill.activeEffect.level)
assert.are.equals(40, build.calcsTab.mainOutput.GemLevel)
local text = gemTooltipText(build.skillsTab.socketGroupList[1].gemList[1])
assert.is_truthy(text:find("+30 Levels from Global Modifiers", 1, true))
assert.is_truthy(text:find("Limited to Level 40", 1, true))
local breakdown = table.concat(build.calcsTab.calcsEnv.player.breakdown.GemLevel, "\n")
assert.is_truthy(breakdown:find("+ 30 ^8(level from global modifiers)", 1, true))
assert.is_truthy(breakdown:find("Limited to Level 40", 1, true))
assert.is_truthy(breakdown:find("= 40", 1, true))
end)

it("includes item and global bonuses in gem totals alongside supports and socket quality", function()
build.itemsTab:CreateDisplayItemFromRaw("Rarity: RARE\nTest Robe\nSage's Robe\nSockets: B-R-G\nImplicits: 0\n+2 to Level of Socketed Fire Gems\n+3 to Level of Socketed Projectile Gems\n+10% to Quality of Socketed Gems\n+4% to Quality of Socketed Fire Gems")
build.itemsTab:AddDisplayItem()
build.skillsTab:PasteSocketGroup("Slot: Body Armour\nFireball 20/5 1\nEmpower 3/0 1\nEnhance 3/0 1\n")
build.configTab.input.customMods = "+1 to Level of all Spell Skill Gems\n+2 to Level of all Fire Skill Gems\n+5% to Quality of all Skill Gems\n+3% to Quality of all Fire Skill Gems"
build.configTab:BuildModList()
runCallback("OnFrame")

assert.are.equals(30, build.calcsTab.mainEnv.player.mainSkill.activeEffect.level)
assert.are.equals(53, build.calcsTab.mainEnv.player.mainSkill.activeEffect.quality)
assert.are.equals(30, build.calcsTab.mainOutput.GemLevel)
assert.are.equals(53, build.calcsTab.mainOutput.GemQuality)

local text = gemTooltipText(build.skillsTab.socketGroupList[1].gemList[1])
assert.is_truthy(text:find("20 Levels from Gem (Max)", 1, true))
assert.is_truthy(text:find("+5 Levels from Item", 1, true))
assert.is_truthy(text:find("+3 Levels from Global Modifiers", 1, true))
assert.is_truthy(text:find("+2 Levels from Supports", 1, true))
assert.is_truthy(text:find("+14% Quality from Item", 1, true))
assert.is_truthy(text:find("+8% Quality from Global Modifiers", 1, true))
assert.is_truthy(text:find("+16% Quality from Support", 1, true))
assert.is_truthy(text:find("+10% Quality from Socket Colour", 1, true))

local levelBreakdown = table.concat(build.calcsTab.calcsEnv.player.breakdown.GemLevel, "\n")
assert.is_truthy(levelBreakdown:find("+ 5 ^8(level from items)", 1, true))
assert.is_truthy(levelBreakdown:find("+ 3 ^8(level from global modifiers)", 1, true))
assert.is_truthy(levelBreakdown:find("+ 2 ^8(level from support)", 1, true))
assert.is_truthy(levelBreakdown:find("= 30", 1, true))
local qualityBreakdown = table.concat(build.calcsTab.calcsEnv.player.breakdown.GemQuality, "\n")
assert.is_truthy(qualityBreakdown:find("+ 14 ^8(quality from items)", 1, true))
assert.is_truthy(qualityBreakdown:find("+ 8 ^8(quality from global modifiers)", 1, true))
assert.is_truthy(qualityBreakdown:find("+ 16 ^8(quality from support)", 1, true))
assert.is_truthy(qualityBreakdown:find("+ 10 ^8(quality from socket colour)", 1, true))
assert.is_truthy(qualityBreakdown:find("= 53", 1, true))

-- Exercise the visible breakdown tables, not just the calculation summary.
local checkedBreakdowns = 0
for _, calcSection in ipairs(build.calcsTab.sectionList) do
if calcSection.id == "SkillTypeStats" then
for _, row in ipairs(calcSection.subSection[1].data) do
if row.label == "Gem Level" or row.label == "Gem Quality" then
checkedBreakdowns = checkedBreakdowns + 1
local breakdown = build.calcsTab.controls.breakdown
breakdown:SetBreakdownData(row[1])
local groups = {}
for _, section in ipairs(breakdown.sectionList) do
if section.type == "TABLE" then
local name = section.rowList[1].mod.name
groups[name] = 0
for _, modifier in ipairs(section.rowList) do
assert.are.equals(name, modifier.mod.name, "Each gem bonus category needs its own table")
groups[name] = groups[name] + modifier.value
end
end
end
if row.label == "Gem Level" then
assert.same({ GemLevel = 20, GemItemLevel = 5, GemGlobalLevel = 3, GemSupportLevel = 2 }, groups)
else
assert.same({ GemQuality = 5, GemItemQuality = 14, GemGlobalQuality = 8, GemSupportQuality = 16, GemSocketQuality = 10 }, groups)
end
end
end
end
end
assert.are.equals(2, checkedBreakdowns)
end)

it("shows socketed gem levels from a helmet as item bonuses in the tooltip", function()
build.itemsTab:CreateDisplayItemFromRaw("Rarity: RARE\nTest Helmet\nHubris Circlet\nSockets: G\nImplicits: 0\n+6 to Level of Socketed Gems")
build.itemsTab:AddDisplayItem()
build.skillsTab:PasteSocketGroup("Slot: Helmet\nTornado 18/0 1\n")
runCallback("OnFrame")

local text = gemTooltipText(build.skillsTab.socketGroupList[1].gemList[1])
assert.is_truthy(text:find("Level: " .. colorCodes.MAGIC .. "24", 1, true))
assert.is_truthy(text:find("18 Levels from Gem", 1, true))
assert.is_truthy(text:find("+6 Levels from Item", 1, true))
assert.is_nil(text:find("Levels from Global Modifiers", 1, true))
assert.is_nil(text:find("Levels from Supports", 1, true))
end)

it("breaks down Tornado's gem level and global levels in its tooltip", function()
build.skillsTab:PasteSocketGroup("Tornado 18/0 1\n")
build.configTab.input.customMods = "+4 to Level of all Spell Skill Gems"
build.configTab:BuildModList()
runCallback("OnFrame")

local text = gemTooltipText(build.skillsTab.socketGroupList[1].gemList[1])
assert.is_truthy(text:find("Level: " .. colorCodes.MAGIC .. "22", 1, true))
assert.is_truthy(text:find("18 Levels from Gem", 1, true))
assert.is_truthy(text:find("+4 Levels from Global Modifiers", 1, true))
assert.is_nil(text:find("Levels from Supports", 1, true))
end)

it("adds envy, ensures +1 level keeps level 25 Envy", function()
build.itemsTab:CreateDisplayItemFromRaw("New Item\nAssassin Bow\nGrants Level 1 Summon Raging Spirit\nGrants Level 25 Envy Skill")
build.itemsTab:AddDisplayItem()
Expand Down
48 changes: 27 additions & 21 deletions src/Classes/GemTooltip.lua
Original file line number Diff line number Diff line change
Expand Up @@ -32,13 +32,24 @@ local function addCommonGemInfo(tooltip, build, gemInstance, grantedEffect, addL
local displayInstance = gemInstance.displayEffect or gemInstance
local grantedEffectLevel = grantedEffect.levels[displayInstance.level] or {}
if addLevel then
tooltip:AddLine(fontSizeBig, string.format("^x7F7F7FLevel: ^7%d%s%s",
gemInstance.level,
((displayInstance.level > gemInstance.level) and " (" .. colorCodes.MAGIC .. "+" .. (displayInstance.level - gemInstance.level) .. "^7)") or
((displayInstance.level < gemInstance.level) and " (" .. colorCodes.WARNING .. "-" .. (gemInstance.level - displayInstance.level) .. "^7)") or
"",
(gemInstance.level >= (gemInstance.gemData.naturalMaxLevel or math.huge)) and " (Max)" or ""
), "FONTIN SC")
local globalLevels = displayInstance.globalLevel or 0
local itemLevels = displayInstance.itemLevel or 0
local supportLevels = displayInstance.supportLevel or 0
local maxLevel = gemInstance.level >= (gemInstance.gemData.naturalMaxLevel or math.huge) and " (Max)" or ""
if itemLevels ~= 0 or globalLevels ~= 0 or supportLevels ~= 0 then
tooltip:AddLine(fontSizeBig, "^x7F7F7FLevel: " .. colorCodes.MAGIC .. displayInstance.level, "FONTIN SC")
tooltip:AddLine(fontSizeBig, string.format("^7%d Levels from Gem%s", gemInstance.level, maxLevel), "FONTIN SC")
for _, source in ipairs({ { itemLevels, "Item" }, { globalLevels, "Global Modifiers" }, { supportLevels, "Supports" } }) do
if source[1] ~= 0 then
tooltip:AddLine(fontSizeBig, (source[1] > 0 and colorCodes.MAGIC or colorCodes.WARNING) .. string.format("%+d Levels from %s", source[1], source[2]), "FONTIN SC")
end
end
if displayInstance.level ~= gemInstance.level + itemLevels + globalLevels + supportLevels then
tooltip:AddLine(fontSizeBig, "^x7F7F7FLimited to Level " .. displayInstance.level, "FONTIN SC")
end
else
tooltip:AddLine(fontSizeBig, string.format("^x7F7F7FLevel: ^7%d%s", gemInstance.level, maxLevel), "FONTIN SC")
end
end
if grantedEffect.support then
if grantedEffectLevel.manaMultiplier then
Expand Down Expand Up @@ -141,20 +152,15 @@ local function addCommonGemInfo(tooltip, build, gemInstance, grantedEffect, addL
if gemInstance.quality > 0 then
tooltip:AddLine(fontSizeBig, string.format("^x7F7F7FQuality: +%s%d%%", colorCodes.MAGIC, gemInstance.quality), "FONTIN SC")
end
local function formatQuality(number, suffix)
return colorCodes.MAGIC .. string.format("+%d%% Quality from %s", number, suffix)
end
if (displayInstance.itemQuality or 0) > 0 then
tooltip:AddLine(fontSizeBig, formatQuality(displayInstance.itemQuality, "Item"), "FONTIN SC")
end
if (displayInstance.supportQuality or 0) > 0 then
tooltip:AddLine(fontSizeBig, formatQuality(displayInstance.supportQuality, "Support"), "FONTIN SC")
end
if (displayInstance.globalQuality or 0) > 0 then
tooltip:AddLine(fontSizeBig, formatQuality(displayInstance.globalQuality, "Global Modifiers"), "FONTIN SC")
end
if (displayInstance.socketQuality or 0) > 0 then
tooltip:AddLine(fontSizeBig, formatQuality(displayInstance.socketQuality, "Socket Colour"), "FONTIN SC")
for _, source in ipairs({
{ displayInstance.itemQuality or 0, "Item" },
{ displayInstance.globalQuality or 0, "Global Modifiers" },
{ displayInstance.supportQuality or 0, "Supports" },
{ displayInstance.socketQuality or 0, "Socket Colour" },
}) do
if source[1] ~= 0 then
tooltip:AddLine(fontSizeBig, (source[1] > 0 and colorCodes.MAGIC or colorCodes.WARNING) .. string.format("%+d%% Quality from %s", source[1], source[2]), "FONTIN SC")
end
end
end
tooltip:AddSeparator(10)
Expand Down
6 changes: 4 additions & 2 deletions src/Modules/CalcActiveSkill.lua
Original file line number Diff line number Diff line change
Expand Up @@ -559,9 +559,11 @@ function calcs.buildActiveSkillModList(env, activeSkill)
for _, supportProperty in ipairs(skillModList:Tabulate("LIST", activeSkill.skillCfg, "SupportedGemProperty")) do
local value = supportProperty.value
if value.keyword == "grants_active_skill" and activeSkill.activeEffect.gemData and not activeSkill.activeEffect.gemData.tags.support then
-- save quality increases for use in tooltips
-- Save level and quality increases for use in tooltips
if value.key == "quality" then
activeEffect.supportQuality = (activeEffect.supportQuality or 0) + value.value
elseif value.key == "level" then
activeEffect.supportLevel = (activeEffect.supportLevel or 0) + value.value
end
activeEffect[value.key] = activeEffect[value.key] + value.value
skillModList:NewMod("GemSupport".. value.key:gsub("^%l", string.upper), "BASE", value.value, supportProperty.mod.source, #supportProperty.mod > 0 and supportProperty.mod[1] or nil)
Expand All @@ -570,7 +572,7 @@ function calcs.buildActiveSkillModList(env, activeSkill)

for _, gemProperty in ipairs((activeSkill.activeEffect.gemPropertyInfo or {})) do
local value = gemProperty.value
skillModList:NewMod("GemItem".. value.key:gsub("^%l", string.upper), "BASE", value.value, gemProperty.mod.source, #gemProperty.mod > 0 and gemProperty.mod[1] or nil)
skillModList:NewMod("Gem" .. gemProperty.sourceType .. value.key:gsub("^%l", string.upper), "BASE", value.value, gemProperty.mod.source, #gemProperty.mod > 0 and gemProperty.mod[1] or nil)
end

-- Add active gem modifiers
Expand Down
28 changes: 14 additions & 14 deletions src/Modules/CalcPerform.lua
Original file line number Diff line number Diff line change
Expand Up @@ -3940,19 +3940,22 @@ function calcs.perform(env, skipEHP)
if mainSkill.activeEffect.level then
local baseLevel = mainSkill.skillModList:Sum("BASE", mainSkill.skillCfg, "GemLevel")
local totalItemLevel = mainSkill.skillModList:Sum("BASE", mainSkill.skillCfg, "GemItemLevel")
local totalGlobalLevel = mainSkill.skillModList:Sum("BASE", mainSkill.skillCfg, "GemGlobalLevel")
local totalSupportLevel = mainSkill.skillModList:Sum("BASE", mainSkill.skillCfg, "GemSupportLevel")

output.GemHasLevel = true
output.GemLevel = baseLevel + totalSupportLevel + totalItemLevel
output.GemLevel = mainSkill.activeEffect.level

if env.player.breakdown then
env.player.breakdown.GemLevel = {}
t_insert(env.player.breakdown.GemLevel, s_format("%d ^8(level from gem)", baseLevel))
if totalSupportLevel > 0 then
t_insert(env.player.breakdown.GemLevel, s_format("+ %d ^8(level from support)", totalSupportLevel))
for _, source in ipairs({ { totalItemLevel, "items" }, { totalGlobalLevel, "global modifiers" }, { totalSupportLevel, "support" } }) do
if source[1] ~= 0 then
t_insert(env.player.breakdown.GemLevel, s_format("%s %d ^8(level from %s)", source[1] > 0 and "+" or "-", math.abs(source[1]), source[2]))
end
end
if totalItemLevel > 0 then
t_insert(env.player.breakdown.GemLevel, s_format("+ %d ^8(level from items)", totalItemLevel))
if output.GemLevel ~= baseLevel + totalItemLevel + totalGlobalLevel + totalSupportLevel then
t_insert(env.player.breakdown.GemLevel, s_format("^8Limited to Level %d", output.GemLevel))
end
t_insert(env.player.breakdown.GemLevel, s_format("= %d", output.GemLevel))
end
Expand All @@ -3962,23 +3965,20 @@ function calcs.perform(env, skipEHP)
if mainSkill.activeEffect.quality then
local baseQuality = mainSkill.skillModList:Sum("BASE", mainSkill.skillCfg, "GemQuality")
local totalItemQuality = mainSkill.skillModList:Sum("BASE", mainSkill.skillCfg, "GemItemQuality")
local totalGlobalQuality = mainSkill.skillModList:Sum("BASE", mainSkill.skillCfg, "GemGlobalQuality")
local totalSupportQuality = mainSkill.skillModList:Sum("BASE", mainSkill.skillCfg, "GemSupportQuality")
local socketQuality = mainSkill.skillModList:Sum("BASE", mainSkill.skillCfg, "GemSocketQuality")

output.GemHasQuality = true
output.GemQuality = baseQuality + totalSupportQuality + totalItemQuality + socketQuality
output.GemQuality = baseQuality + totalSupportQuality + totalItemQuality + totalGlobalQuality + socketQuality

if env.player.breakdown then
env.player.breakdown.GemQuality = {}
t_insert(env.player.breakdown.GemQuality, s_format("%d ^8(quality from gem)", baseQuality))
if totalSupportQuality > 0 then
t_insert(env.player.breakdown.GemQuality, s_format("+ %d ^8(quality from support)", totalSupportQuality))
end
if totalItemQuality > 0 then
t_insert(env.player.breakdown.GemQuality, s_format("+ %d ^8(quality from items)", totalItemQuality))
end
if socketQuality > 0 then
t_insert(env.player.breakdown.GemQuality, s_format("+ %d ^8(quality from socket)", socketQuality))
for _, source in ipairs({ { totalItemQuality, "items" }, { totalGlobalQuality, "global modifiers" }, { totalSupportQuality, "support" }, { socketQuality, "socket colour" } }) do
if source[1] ~= 0 then
t_insert(env.player.breakdown.GemQuality, s_format("%s %d ^8(quality from %s)", source[1] > 0 and "+" or "-", math.abs(source[1]), source[2]))
end
end
t_insert(env.player.breakdown.GemQuality, s_format("= %d", output.GemQuality))
end
Expand Down
Loading
Loading