Skip to content

Commit 0fc3283

Browse files
ksnyder9801LocalIdentity
andauthored
Add masteries to power report (#9604)
* add masteries to power report * Dedupe code * Dedupe CalcsTab * Cleanup * Fix test crash --------- Co-authored-by: LocalIdentity <localidentity2@gmail.com>
1 parent f1f3f0d commit 0fc3283

4 files changed

Lines changed: 247 additions & 86 deletions

File tree

spec/System/TestTreeTab_spec.lua

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
describe("TreeTab", function()
2+
local originalClusterNodeMap
3+
local originalMasteryEffects
4+
5+
before_each(function()
6+
newBuild()
7+
originalClusterNodeMap = build.spec.tree.clusterNodeMap
8+
originalMasteryEffects = build.spec.tree.masteryEffects
9+
end)
10+
11+
after_each(function()
12+
build.spec.tree.clusterNodeMap = originalClusterNodeMap
13+
build.spec.tree.masteryEffects = originalMasteryEffects
14+
end)
15+
16+
it("adds separate power report entries for mastery effects", function()
17+
local treeTab = build.treeTab
18+
local parentNode = { id = 2 }
19+
local masteryNode = {
20+
id = 1,
21+
type = "Mastery",
22+
dn = "Two Hand Mastery",
23+
power = {
24+
masteryEffects = {
25+
[101] = { singleStat = 10, pathPower = 10 },
26+
[102] = { singleStat = 20, pathPower = 20 },
27+
},
28+
},
29+
masteryEffects = {
30+
{ effect = 101 },
31+
{ effect = 102 },
32+
},
33+
path = { parentNode, false },
34+
x = 10,
35+
y = 20,
36+
}
37+
masteryNode.path[2] = masteryNode
38+
39+
treeTab.build.displayStats = {
40+
{ stat = "Damage", label = "Damage", fmt = ".1f" },
41+
}
42+
treeTab.build.spec.nodes = {
43+
[masteryNode.id] = masteryNode,
44+
}
45+
treeTab.build.spec.masterySelections = { }
46+
treeTab.build.spec.tree.clusterNodeMap = { }
47+
treeTab.build.spec.tree.masteryEffects = {
48+
[101] = { id = 101, sd = { "Gain 10 Damage" }, stats = { "Gain 10 Damage" } },
49+
[102] = { id = 102, sd = { "Gain 20 Damage" }, stats = { "Gain 20 Damage" } },
50+
}
51+
treeTab.build.calcsTab.mainEnv = { grantedPassives = { } }
52+
53+
local report = treeTab:BuildPowerReportList({ stat = "Damage", label = "Damage" })
54+
55+
assert.are.same(2, #report)
56+
assert.are.same("Mastery", report[1].type)
57+
assert.are.same("Two Hand Mastery: Gain 20 Damage", report[1].name)
58+
assert.are.same(20, report[1].power)
59+
assert.are.same(2, report[1].pathDist)
60+
assert.are.same(10, report[2].power)
61+
assert.are.same("Two Hand Mastery: Gain 10 Damage", report[2].name)
62+
end)
63+
end)

src/Classes/CalcsTab.lua

Lines changed: 124 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -479,6 +479,7 @@ function CalcsTabClass:PowerBuilder()
479479
local cache = { }
480480
local distanceMap = { }
481481
local distanceList = { }
482+
local masteryNodeList = { }
482483
local newPowerMax = {
483484
singleStat = 0,
484485
offence = 0,
@@ -492,18 +493,73 @@ function CalcsTabClass:PowerBuilder()
492493
if coroutine.running() then
493494
coroutine.yield()
494495
end
496+
497+
local function buildMasteryEffectNode(node, effect)
498+
local effectNode = {
499+
id = node.id,
500+
type = node.type,
501+
name = node.name,
502+
sd = { },
503+
}
504+
for i, sd in ipairs(effect.sd or { }) do
505+
effectNode.sd[i] = sd
506+
end
507+
self.build.spec.tree:ProcessStats(effectNode)
508+
return effectNode
509+
end
510+
511+
local function masteryEffectCanBeAssignedToNode(node, masteryEffect)
512+
local assignedNodeId = isValueInTable(self.build.spec.masterySelections, masteryEffect.effect)
513+
return not assignedNodeId or assignedNodeId == node.id
514+
end
515+
516+
local function calculateAddNodePower(power, node, output, buildPathNodes)
517+
if self.powerStat and self.powerStat.stat and not self.powerStat.ignoreForNodes then
518+
power.singleStat = self:CalculatePowerStat(self.powerStat, output, calcBase)
519+
if node.path and not node.ascendancyName then
520+
newPowerMax.singleStat = m_max(newPowerMax.singleStat, power.singleStat)
521+
power.pathPower = power.singleStat
522+
if node.pathDist > 1 then
523+
power.pathPower = self:CalculatePowerStat(self.powerStat, calcFunc({ addNodes = buildPathNodes() }, useFullDPS), calcBase)
524+
end
525+
end
526+
elseif not self.powerStat or not self.powerStat.ignoreForNodes then
527+
power.offence, power.defence = self:CalculateCombinedOffDefStat(output, calcBase)
528+
power.singleStat = power.offence
529+
if node.path and not node.ascendancyName then
530+
newPowerMax.offence = m_max(newPowerMax.offence, power.offence)
531+
newPowerMax.defence = m_max(newPowerMax.defence, power.defence)
532+
newPowerMax.offencePerPoint = m_max(newPowerMax.offencePerPoint, power.offence / node.pathDist)
533+
newPowerMax.defencePerPoint = m_max(newPowerMax.defencePerPoint, power.defence / node.pathDist)
534+
end
535+
end
536+
end
495537

496538
local start = GetTime()
497539
local nodeIndex = 0
498540
local total = 0
499541

500542
for nodeId, node in pairs(self.build.spec.nodes) do
501543
wipeTable(node.power)
544+
if node.type == "Mastery" then
545+
node.power.masteryEffects = { }
546+
end
502547
if node.modKey ~= "" and not self.mainEnv.grantedPassives[nodeId] then
503-
distanceMap[node.pathDist or 1000] = distanceMap[node.pathDist or 1000] or { }
504-
distanceMap[node.pathDist or 1000][nodeId] = node
505-
if not (self.nodePowerMaxDepth and self.nodePowerMaxDepth < node.pathDist) then
506-
total = total + 1
548+
if node.type == "Mastery" and node.allMasteryOptions then
549+
if not (self.nodePowerMaxDepth and self.nodePowerMaxDepth < node.pathDist) then
550+
t_insert(masteryNodeList, node)
551+
for _, masteryEffect in ipairs(node.masteryEffects or { }) do
552+
if masteryEffectCanBeAssignedToNode(node, masteryEffect) then
553+
total = total + 1
554+
end
555+
end
556+
end
557+
else
558+
distanceMap[node.pathDist or 1000] = distanceMap[node.pathDist or 1000] or { }
559+
distanceMap[node.pathDist or 1000][nodeId] = node
560+
if not (self.nodePowerMaxDepth and self.nodePowerMaxDepth < node.pathDist) then
561+
total = total + 1
562+
end
507563
end
508564
end
509565
end
@@ -530,29 +586,13 @@ function CalcsTabClass:PowerBuilder()
530586
cache[node.modKey] = calcFunc({ addNodes = { [node] = true } }, useFullDPS)
531587
end
532588
local output = cache[node.modKey]
533-
if self.powerStat and self.powerStat.stat and not self.powerStat.ignoreForNodes then
534-
node.power.singleStat = self:CalculatePowerStat(self.powerStat, output, calcBase)
535-
if node.path and not node.ascendancyName then
536-
newPowerMax.singleStat = m_max(newPowerMax.singleStat, node.power.singleStat)
537-
node.power.pathPower = node.power.singleStat
538-
local pathNodes = { }
539-
for _, node in pairs(node.path) do
540-
pathNodes[node] = true
541-
end
542-
if node.pathDist > 1 then
543-
node.power.pathPower = self:CalculatePowerStat(self.powerStat, calcFunc({ addNodes = pathNodes }, useFullDPS), calcBase)
544-
end
545-
end
546-
elseif not self.powerStat or not self.powerStat.ignoreForNodes then
547-
node.power.offence, node.power.defence = self:CalculateCombinedOffDefStat(output, calcBase)
548-
node.power.singleStat = node.power.offence
549-
if node.path and not node.ascendancyName then
550-
newPowerMax.offence = m_max(newPowerMax.offence, node.power.offence)
551-
newPowerMax.defence = m_max(newPowerMax.defence, node.power.defence)
552-
newPowerMax.offencePerPoint = m_max(newPowerMax.offencePerPoint, node.power.offence / node.pathDist)
553-
newPowerMax.defencePerPoint = m_max(newPowerMax.defencePerPoint, node.power.defence / node.pathDist)
589+
calculateAddNodePower(node.power, node, output, function()
590+
local pathNodes = { }
591+
for _, pathNode in pairs(node.path) do
592+
pathNodes[pathNode] = true
554593
end
555-
end
594+
return pathNodes
595+
end)
556596
elseif node.alloc and node.modKey ~= "" and not self.mainEnv.grantedPassives[nodeId] then
557597
if not cache[node.modKey.."_remove"] then
558598
cache[node.modKey.."_remove"] = calcFunc({ removeNodes = { [node] = true } }, useFullDPS)
@@ -572,6 +612,17 @@ function CalcsTabClass:PowerBuilder()
572612
end
573613
end
574614
end
615+
if node.type == "Mastery" then
616+
local selectedEffectId = self.build.spec.masterySelections[node.id]
617+
if selectedEffectId then
618+
node.power.masteryEffects[selectedEffectId] = {
619+
singleStat = node.power.singleStat,
620+
pathPower = node.power.pathPower,
621+
offence = node.power.offence,
622+
defence = node.power.defence,
623+
}
624+
end
625+
end
575626
nodeIndex = nodeIndex + 1
576627
if coroutine.running() and GetTime() - start > 100 then
577628
if self.build.powerBuilderProgressCallback then
@@ -583,6 +634,53 @@ function CalcsTabClass:PowerBuilder()
583634
end
584635
end
585636

637+
for _, node in ipairs(masteryNodeList) do
638+
for _, masteryEffect in ipairs(node.masteryEffects or { }) do
639+
if masteryEffectCanBeAssignedToNode(node, masteryEffect) then
640+
local effect = self.build.spec.tree.masteryEffects[masteryEffect.effect]
641+
if effect then
642+
local effectNode = buildMasteryEffectNode(node, effect)
643+
if effectNode.modKey ~= "" then
644+
if not cache[effectNode.modKey] then
645+
cache[effectNode.modKey] = calcFunc({ addNodes = { [effectNode] = true } }, useFullDPS)
646+
end
647+
local output = cache[effectNode.modKey]
648+
node.power.masteryEffects[effect.id] = { }
649+
local effectPower = node.power.masteryEffects[effect.id]
650+
calculateAddNodePower(effectPower, node, output, function()
651+
local pathNodes = {
652+
[effectNode] = true
653+
}
654+
for _, pathNode in pairs(node.path) do
655+
if pathNode ~= node then
656+
pathNodes[pathNode] = true
657+
end
658+
end
659+
return pathNodes
660+
end)
661+
if self.powerStat and self.powerStat.stat and not self.powerStat.ignoreForNodes then
662+
effectPower.pathPower = effectPower.pathPower or effectPower.singleStat
663+
node.power.singleStat = m_max(node.power.singleStat or 0, effectPower.singleStat)
664+
node.power.pathPower = m_max(node.power.pathPower or 0, effectPower.pathPower)
665+
elseif not self.powerStat or not self.powerStat.ignoreForNodes then
666+
node.power.offence = m_max(node.power.offence or 0, effectPower.offence)
667+
node.power.defence = m_max(node.power.defence or 0, effectPower.defence)
668+
node.power.singleStat = m_max(node.power.singleStat or 0, effectPower.singleStat)
669+
end
670+
end
671+
nodeIndex = nodeIndex + 1
672+
if coroutine.running() and GetTime() - start > 100 then
673+
if self.build.powerBuilderProgressCallback then
674+
self.build.powerBuilderProgressCallback(m_floor(nodeIndex/total*100))
675+
end
676+
coroutine.yield()
677+
start = GetTime()
678+
end
679+
end
680+
end
681+
end
682+
end
683+
586684
-- Calculate the impact of every cluster notable
587685
-- used for the power report screen
588686
for nodeName, node in pairs(self.build.spec.tree.clusterNodeMap) do

src/Classes/PowerReportListControl.lua

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ local PowerReportListClass = newClass("PowerReportListControl", "ListControl", f
2323
self.colLabels = true
2424
self.nodeSelectCallback = nodeSelectCallback
2525
self.showClusters = false
26+
self.showMasteries = true
2627
self.allocated = false
2728
self.label = "Building Tree..."
2829

@@ -34,6 +35,11 @@ local PowerReportListClass = newClass("PowerReportListControl", "ListControl", f
3435
self:ReList()
3536
self:ReSort(3) -- Sort by power
3637
end)
38+
self.controls.masteryCheck = new("CheckBoxControl", {"RIGHT", self.controls.filterSelect, "LEFT"}, {-120, 0, 18}, "Show Masteries:", function(state)
39+
self.showMasteries = state
40+
self:ReList()
41+
self:ReSort(3) -- Sort by power
42+
end, nil, true)
3743
end)
3844

3945
function PowerReportListClass:SetReport(stat, report)
@@ -103,6 +109,9 @@ function PowerReportListClass:ReList()
103109
if self.allocated then
104110
insert = item.allocated
105111
end
112+
if not self.showMasteries and item.type == "Mastery" then
113+
insert = false
114+
end
106115

107116
if insert then
108117
t_insert(self.list, item)

0 commit comments

Comments
 (0)