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
106 changes: 106 additions & 0 deletions spec/System/TestBuildDisplayStats_spec.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
describe("Build display stats", function()
local originalCompactValues

before_each(function()
originalCompactValues = main.useCompactValues
newBuild()
end)

after_each(function()
main.useCompactValues = originalCompactValues
end)

local function getSidebarLine(label)
local suffix = label .. ":"
for _, stat in ipairs(build.controls.statBox.list) do
if stat[1] and stat[1]:sub(-#suffix) == suffix then
return stat
end
end
end

it("only underlines sidebar stats with a visible breakdown", function()
build.skillsTab:PasteSocketGroup("Fireball 20/0 1")
runCallback("OnFrame")

for _, line in ipairs(build.controls.statBox.list) do
if line.underline and line.underline[2] then
build:SetDisplayStat({ line = line, x = 0, y = 0, width = 300 }, false)
assert.is_true(build.controls.breakdown.shown, line[1])
build:ClearDisplayStat()
end
end
end)

it("uses aggregate breakdowns for dual-wield attacks", function()
build.skillsTab:PasteSocketGroup("skillId:MeleeMaceMacePlayer Mace Strike 20/0 1")
build.itemsTab:CreateDisplayItemFromRaw("New Item\nMarauding Mace\nQuality: 0\n20% increased Attack Speed")
build.itemsTab:AddDisplayItem()
build.itemsTab:CreateDisplayItemFromRaw("New Item\nMarauding Mace\nQuality: 0")
build.itemsTab:AddDisplayItem()
runCallback("OnFrame")

local actor = build.calcsTab.mainEnv.player
assert.is_true(actor.mainSkill.activeEffect.statSet.skillFlags.bothWeaponAttack)
assert.matches("Simultaneous hits from each weapon", table.concat(actor.breakdown.Speed, "\n"), nil, true)
assert.matches("Both weapons", table.concat(actor.breakdown.PreEffectiveCritChance, "\n"), nil, true)
assert.matches("Both weapons", table.concat(actor.breakdown.CritChance, "\n"), nil, true)
assert.not_matches("Crit confirmation roll", table.concat(actor.breakdown.PreEffectiveCritChance, "\n"), nil, true)
assert.matches("Crit confirmation roll", table.concat(actor.breakdown.CritChance, "\n"), nil, true)
assert.not_matches("Effective Crit Chance:", table.concat(actor.breakdown.CritChance, "\n"), nil, true)
assert.matches("Both weapons", table.concat(actor.breakdown.HitChance, "\n"), nil, true)

local critLine = getSidebarLine("Crit Chance")
local effectiveCritLine = getSidebarLine("Effective Crit Chance")
assert.are.equal("PreEffectiveCritChance", critLine.breakdown)
assert.are.equal("CritChance", effectiveCritLine.breakdown)

local displayData = build:GetSidebarBreakdown(critLine.breakdown, critLine.modNames, critLine.ignoredSections, "player")
local breakdownCount = 0
local hasMainHandModifiers = false
for _, section in ipairs(displayData) do
breakdownCount = breakdownCount + (section.breakdown and 1 or 0)
hasMainHandModifiers = hasMainHandModifiers or section.cfg == "weapon1"
end
assert.are.equal(1, breakdownCount)
assert.is_true(hasMainHandModifiers)
end)

it("uses off-hand breakdowns for shield attacks", function()
build.itemsTab:CreateDisplayItemFromRaw("New Item\nShortsword\nQuality: 0")
build.itemsTab:AddDisplayItem()
build.itemsTab:CreateDisplayItemFromRaw("New Item\nSplintered Tower Shield\nQuality: 0")
build.itemsTab:AddDisplayItem()
build.skillsTab:PasteSocketGroup("Shield Wall 20/0 1")
build.configTab.input.enemyEvasion = 10000
build.configTab:BuildModList()
runCallback("OnFrame")

local actor = build.calcsTab.mainEnv.player
assert.is_falsy(actor.mainSkill.activeEffect.statSet.skillFlags.weapon1Attack)
assert.is_true(actor.mainSkill.activeEffect.statSet.skillFlags.weapon2Attack)
assert.are.equal(actor.breakdown.OffHand.Speed, actor.breakdown.Speed)
assert.are.equal(actor.breakdown.OffHand.AccuracyHitChance, actor.breakdown.HitChance)
assert.are.equal(actor.breakdown.OffHand.PreEffectiveCritChance, actor.breakdown.PreEffectiveCritChance)
assert.are.equal(actor.breakdown.OffHand.CritChance, actor.breakdown.CritChance)

local critLine = getSidebarLine("Crit Chance")
local effectiveCritLine = getSidebarLine("Effective Crit Chance")
assert.are.equal("PreEffectiveCritChance", critLine.breakdown)
assert.are.equal("OffHand.CritChance", actor.breakdown.PreEffectiveCritChance.breakdownSource)
assert.are.equal("CritChance", effectiveCritLine.breakdown)

local displayData = build:GetSidebarBreakdown(critLine.breakdown, critLine.modNames, critLine.ignoredSections, "player")
local hasModifierSection = false
for _, section in ipairs(displayData) do
hasModifierSection = hasModifierSection or section.modName ~= nil
end
assert.is_true(hasModifierSection)

build.configTab.input.enemyBlockChance = 25
build.configTab:BuildModList()
runCallback("OnFrame")
actor = build.calcsTab.mainEnv.player
assert.are.equal(actor.breakdown.OffHand.HitChance, actor.breakdown.HitChance)
end)
end)
52 changes: 40 additions & 12 deletions src/Classes/CalcBreakdownControl.lua
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,13 @@ function CalcBreakdownClass:CalcBreakdownControl(calcsTab)
self.rangeGuide:Load("Assets/range_guide.png")
self.uiOverlay = NewImageHandle()
self.uiOverlay:Load("Assets/game_ui_small.png")
self.borderThickness = 2
self.controls.scrollBar = new("ScrollBarControl"):ScrollBarControl({ "RIGHT", self, "RIGHT" }, { -2, 0, 18, 0 }, 80, "VERTICAL", true)
self.controls.scrollBar.x = function()
return -self.borderThickness
end
self.pinnedColour = { 0.25, 1, 0.25 }
self.borderColour = { 0.33, 0.66, 0.33 }
return self
end

Expand All @@ -38,21 +44,35 @@ function CalcBreakdownClass:IsMouseOver()
return self:IsMouseInBounds() or self:GetMouseOverControl()
end

function CalcBreakdownClass:SetBreakdownData(displayData, pinned)
function CalcBreakdownClass:GetActor()
local env = self.calcsTab[self.envName or "calcsEnv"]
local actor = self.calcsTab.input.showMinion and env.minion or env.player
if self.forceActor then
actor = env[self.forceActor]
end
return actor, env
end

---@param displayData any
---@param pinned any
---@param forceActor "player"|"minion"|nil
function CalcBreakdownClass:SetBreakdownData(displayData, pinned, forceActor)
self.pinned = pinned
if displayData == self.sourceData then
return
end
self.sourceData = displayData
self.forceActor = forceActor
self.shown = false
if not displayData then
return
end

-- Build list of sections
local actor, env = self:GetActor()
self.sectionList = wipeTable(self.sectionList)
for _, sectionData in ipairs(displayData) do
if self.calcsTab:CheckFlag(sectionData) then
if self.calcsTab:CheckFlag(sectionData, actor, env.player) then
if sectionData.breakdown then
self:AddBreakdownSection(sectionData)
elseif sectionData.modName then
Expand All @@ -61,7 +81,11 @@ function CalcBreakdownClass:SetBreakdownData(displayData, pinned)
end
end
if #self.sectionList == 0 then
self.calcsTab:ClearDisplayStat()
if self.clearDisplayFunc then
self.clearDisplayFunc()
else
self.calcsTab:ClearDisplayStat()
end
return
end

Expand Down Expand Up @@ -120,7 +144,7 @@ end

-- Add sections based on the breakdown data generated by the Calcs module
function CalcBreakdownClass:AddBreakdownSection(sectionData)
local actor = self.calcsTab.input.showMinion and self.calcsTab.calcsEnv.minion or self.calcsTab.calcsEnv.player
local actor = self:GetActor()
local breakdown
local ns, name = sectionData.breakdown:match("^(%a+)%.(%a+)$")
if ns then
Expand Down Expand Up @@ -273,7 +297,7 @@ end

-- Add a table section showing a list of modifiers
function CalcBreakdownClass:AddModSection(sectionData, modList)
local actor = self.calcsTab.input.showMinion and self.calcsTab.calcsEnv.minion or self.calcsTab.calcsEnv.player
local actor = self:GetActor()
local build = self.calcsTab.build

-- Build list of modifiers to display
Expand Down Expand Up @@ -678,12 +702,13 @@ function CalcBreakdownClass:Draw(viewPort)
local scrollBar = self.controls.scrollBar
local width = self.contentWidth
local height = self.contentHeight
local borderThickness = self.borderThickness
if self.contentHeight > viewPort.height then
-- Content won't fit the screen height, so set the scrollbar
width = self.contentWidth + scrollBar.width
height = viewPort.height
scrollBar.height = height - 4
scrollBar:SetContentDimension(self.contentHeight - 4, viewPort.height - 4)
scrollBar.height = height - borderThickness * 2
scrollBar:SetContentDimension(self.contentHeight - borderThickness * 2, viewPort.height - borderThickness * 2)
else
scrollBar:SetContentDimension(0, 0)
end
Expand All @@ -700,15 +725,14 @@ function CalcBreakdownClass:Draw(viewPort)
-- Draw background
SetDrawLayer(nil, 10)
SetDrawColor(0, 0, 0, 0.9)
DrawImage(nil, x + 2, y + 2, width - 4, height - 4)
DrawImage(nil, x + borderThickness, y + borderThickness, width - borderThickness * 2, height - borderThickness * 2)
-- Draw border (this is put in sub layer 11 so it draws over the contents, in case they don't fit the screen)
SetDrawLayer(nil, 11)
if self.pinned then
SetDrawColor(0.25, 1, 0.25)
SetDrawColor(unpack(self.pinnedColour))
else
SetDrawColor(0.33, 0.66, 0.33)
SetDrawColor(unpack(self.borderColour))
end
local borderThickness = 2
DrawImage(nil, x, y, width, borderThickness)
DrawImage(nil, x, y + height - borderThickness, width, borderThickness)
DrawImage(nil, x, y, borderThickness, height)
Expand Down Expand Up @@ -750,7 +774,11 @@ function CalcBreakdownClass:OnKeyDown(key, doubleClick)
if key:match("BUTTON") then
if not mOver then
-- Mouse click outside the control, hide the breakdown
self.calcsTab:ClearDisplayStat()
if self.clearDisplayFunc then
self.clearDisplayFunc()
else
self.calcsTab:ClearDisplayStat()
end
self.shown = false
return
end
Expand Down
10 changes: 6 additions & 4 deletions src/Classes/CalcsTab.lua
Original file line number Diff line number Diff line change
Expand Up @@ -415,9 +415,10 @@ function CalcsTabClass:SetDisplayStat(displayData, pin)
self.controls.breakdown:SetBreakdownData(displayData, pin)
end

function CalcsTabClass:CheckFlag(obj)
local actor = self.input.showMinion and self.calcsEnv.minion or self.calcsEnv.player
local skillFlags = actor.mainSkill.activeEffect.statSetCalcs.skillFlags
function CalcsTabClass:CheckFlag(obj, actor, player)
actor = actor or (self.input.showMinion and self.calcsEnv.minion or self.calcsEnv.player)
local activeEffect = actor.mainSkill.activeEffect
local skillFlags = (activeEffect.statSetCalcs or activeEffect.statSet).skillFlags or {}
local skillData = actor.mainSkill.skillData
if obj.flag and not skillFlags[obj.flag] then
return
Expand All @@ -432,7 +433,8 @@ function CalcsTabClass:CheckFlag(obj)
end
end
end
if obj.playerFlag and not self.calcsEnv.player.mainSkill.activeEffect.statSetCalcs.skillFlags[obj.playerFlag] then
local playerActiveEffect = (player or self.calcsEnv.player).mainSkill.activeEffect
if obj.playerFlag and not (playerActiveEffect.statSetCalcs or playerActiveEffect.statSet).skillFlags[obj.playerFlag] then
return
end
if obj.notFlag and skillFlags[obj.notFlag] then
Expand Down
31 changes: 30 additions & 1 deletion src/Classes/TextListControl.lua
Original file line number Diff line number Diff line change
Expand Up @@ -46,18 +46,47 @@ function TextListClass:Draw(viewPort)
local lineY = -scrollBar.offset
for _, lineInfo in ipairs(self.list) do
if lineInfo[colIndex] then
DrawString(lineInfo.x or colInfo.x, lineY, lineInfo.align or colInfo.align, lineInfo.height, lineInfo.font or "VAR", lineInfo[colIndex])
local textX = lineInfo.x or colInfo.x
local align = lineInfo.align or colInfo.align
DrawString(textX, lineY, align, lineInfo.height, lineInfo.font or "VAR", lineInfo[colIndex])
if lineInfo.underline and lineInfo.underline[colIndex] then
local width = DrawStringWidth(lineInfo.height, "VAR", StripEscapes(lineInfo[colIndex]))
-- note: not fully handled. this is currently only used for
-- the side bar stats
if align == "RIGHT_X" then
textX = textX - width
end
SetDrawColor(0.5, 0.5, 0.5)
DrawImage(nil, textX, lineY + lineInfo.height, width, 1)
end
end
lineY = lineY + lineInfo.height
end
end
-- determine which line the user is hovering over
self.hoveredLine = nil
local cursorX, cursorY = GetCursorPos()
if cursorX >= x + 2 and cursorX < x + width - 18 and cursorY >= y + 2 and cursorY < y + height - 2 then
local rowY = y - scrollBar.offset + 2
-- suboptimal. should do binary search if this causes performance problems
for _, lineInfo in ipairs(self.list) do
if cursorY >= rowY and cursorY < rowY + lineInfo.height then
self.hoveredLine = { line = lineInfo, x = x, y = rowY, width = width }
break
end
rowY = rowY + lineInfo.height
end
end
SetViewport()
end

function TextListClass:OnKeyDown(key, doubleClick)
if not self:IsShown() or not self:IsEnabled() then
return
end
if key == "LEFTBUTTON" and self.onClick then
self.onClick(self.hoveredLine)
end
local mOverControl = self:GetMouseOverControl()
if mOverControl and mOverControl.OnKeyDown then
return mOverControl:OnKeyDown(key)
Expand Down
3 changes: 2 additions & 1 deletion src/Data/Global.lua
Original file line number Diff line number Diff line change
Expand Up @@ -94,8 +94,9 @@ function updateColorCode(code, color)
end

function hexToRGB(hex)
hex = hex:gsub("%^x", "") -- Remove "^x" prefix
hex = hex:gsub("0x", "") -- Remove "0x" prefix
hex = hex:gsub("#","") -- Remove '#' if present
hex = hex:gsub("#", "") -- Remove '#' if present
if #hex ~= 6 then
return nil
end
Expand Down
Loading
Loading