-
Notifications
You must be signed in to change notification settings - Fork 2.3k
Add masteries to power report #9604
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: dev
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,57 @@ | ||
| describe("TreeTab", function() | ||
| before_each(function() | ||
| newBuild() | ||
| end) | ||
|
|
||
| teardown(function() | ||
| -- newBuild() resets the shared build state for the next test. | ||
| end) | ||
|
|
||
| it("adds separate power report entries for mastery effects", function() | ||
| local treeTab = build.treeTab | ||
| local parentNode = { id = 2 } | ||
| local masteryNode = { | ||
| id = 1, | ||
| type = "Mastery", | ||
| dn = "Two Hand Mastery", | ||
| power = { | ||
| masteryEffects = { | ||
| [101] = { singleStat = 10, pathPower = 10 }, | ||
| [102] = { singleStat = 20, pathPower = 20 }, | ||
| }, | ||
| }, | ||
| masteryEffects = { | ||
| { effect = 101 }, | ||
| { effect = 102 }, | ||
| }, | ||
| path = { parentNode, false }, | ||
| x = 10, | ||
| y = 20, | ||
| } | ||
| masteryNode.path[2] = masteryNode | ||
|
|
||
| treeTab.build.displayStats = { | ||
| { stat = "Damage", label = "Damage", fmt = ".1f" }, | ||
| } | ||
| treeTab.build.spec.nodes = { | ||
| [masteryNode.id] = masteryNode, | ||
| } | ||
| treeTab.build.spec.masterySelections = { } | ||
| treeTab.build.spec.tree.clusterNodeMap = { } | ||
| treeTab.build.spec.tree.masteryEffects = { | ||
| [101] = { id = 101, sd = { "Gain 10 Damage" }, stats = { "Gain 10 Damage" } }, | ||
| [102] = { id = 102, sd = { "Gain 20 Damage" }, stats = { "Gain 20 Damage" } }, | ||
| } | ||
| treeTab.build.calcsTab.mainEnv = { grantedPassives = { } } | ||
|
|
||
| local report = treeTab:BuildPowerReportList({ stat = "Damage", label = "Damage" }) | ||
|
|
||
| assert.are.same(2, #report) | ||
| assert.are.same("Mastery", report[1].type) | ||
| assert.are.same("Two Hand Mastery: Gain 20 Damage", report[1].name) | ||
| assert.are.same(20, report[1].power) | ||
| assert.are.same(2, report[1].pathDist) | ||
| assert.are.same(10, report[2].power) | ||
| assert.are.same("Two Hand Mastery: Gain 10 Damage", report[2].name) | ||
| end) | ||
| end) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -492,18 +492,46 @@ function CalcsTabClass:PowerBuilder() | |
| if coroutine.running() then | ||
| coroutine.yield() | ||
| end | ||
|
|
||
| local function buildMasteryEffectNode(node, effect) | ||
| local effectNode = { | ||
| id = node.id, | ||
| type = node.type, | ||
| name = node.name, | ||
| sd = { }, | ||
| } | ||
| for i, sd in ipairs(effect.sd or { }) do | ||
| effectNode.sd[i] = sd | ||
| end | ||
| self.build.spec.tree:ProcessStats(effectNode) | ||
| return effectNode | ||
| end | ||
|
|
||
| local start = GetTime() | ||
| local nodeIndex = 0 | ||
| local total = 0 | ||
|
|
||
| for nodeId, node in pairs(self.build.spec.nodes) do | ||
| wipeTable(node.power) | ||
| if node.type == "Mastery" then | ||
| node.power.masteryEffects = { } | ||
| end | ||
| if node.modKey ~= "" and not self.mainEnv.grantedPassives[nodeId] then | ||
| distanceMap[node.pathDist or 1000] = distanceMap[node.pathDist or 1000] or { } | ||
| distanceMap[node.pathDist or 1000][nodeId] = node | ||
| if not (self.nodePowerMaxDepth and self.nodePowerMaxDepth < node.pathDist) then | ||
| total = total + 1 | ||
| if node.type == "Mastery" and node.allMasteryOptions then | ||
| if not (self.nodePowerMaxDepth and self.nodePowerMaxDepth < node.pathDist) then | ||
| for _, masteryEffect in ipairs(node.masteryEffects or { }) do | ||
| local assignedNodeId = isValueInTable(self.build.spec.masterySelections, masteryEffect.effect) | ||
| if not assignedNodeId or assignedNodeId == node.id then | ||
| total = total + 1 | ||
| end | ||
| end | ||
| end | ||
| else | ||
| distanceMap[node.pathDist or 1000] = distanceMap[node.pathDist or 1000] or { } | ||
| distanceMap[node.pathDist or 1000][nodeId] = node | ||
| if not (self.nodePowerMaxDepth and self.nodePowerMaxDepth < node.pathDist) then | ||
| total = total + 1 | ||
| end | ||
| end | ||
| end | ||
| end | ||
|
|
@@ -572,6 +600,17 @@ function CalcsTabClass:PowerBuilder() | |
| end | ||
| end | ||
| end | ||
| if node.type == "Mastery" then | ||
| local selectedEffectId = self.build.spec.masterySelections[node.id] | ||
| if selectedEffectId then | ||
| node.power.masteryEffects[selectedEffectId] = { | ||
| singleStat = node.power.singleStat, | ||
| pathPower = node.power.pathPower, | ||
| offence = node.power.offence, | ||
| defence = node.power.defence, | ||
| } | ||
| end | ||
| end | ||
| nodeIndex = nodeIndex + 1 | ||
| if coroutine.running() and GetTime() - start > 100 then | ||
| if self.build.powerBuilderProgressCallback then | ||
|
|
@@ -583,6 +622,69 @@ function CalcsTabClass:PowerBuilder() | |
| end | ||
| end | ||
|
|
||
| for nodeId, node in pairs(self.build.spec.nodes) do | ||
| if node.type == "Mastery" and node.allMasteryOptions and node.modKey ~= "" and not self.mainEnv.grantedPassives[nodeId] then | ||
| if not (self.nodePowerMaxDepth and self.nodePowerMaxDepth < node.pathDist) then | ||
| for _, masteryEffect in ipairs(node.masteryEffects or { }) do | ||
| local assignedNodeId = isValueInTable(self.build.spec.masterySelections, masteryEffect.effect) | ||
| if not assignedNodeId or assignedNodeId == node.id then | ||
| local effect = self.build.spec.tree.masteryEffects[masteryEffect.effect] | ||
| if effect then | ||
| local effectNode = buildMasteryEffectNode(node, effect) | ||
| if effectNode.modKey ~= "" then | ||
| if not cache[effectNode.modKey] then | ||
| cache[effectNode.modKey] = calcFunc({ addNodes = { [effectNode] = true } }, useFullDPS) | ||
| end | ||
| local output = cache[effectNode.modKey] | ||
| node.power.masteryEffects[effect.id] = { } | ||
| if self.powerStat and self.powerStat.stat and not self.powerStat.ignoreForNodes then | ||
| node.power.masteryEffects[effect.id].singleStat = self:CalculatePowerStat(self.powerStat, output, calcBase) | ||
| node.power.masteryEffects[effect.id].pathPower = node.power.masteryEffects[effect.id].singleStat | ||
| if node.path and not node.ascendancyName then | ||
| newPowerMax.singleStat = m_max(newPowerMax.singleStat, node.power.masteryEffects[effect.id].singleStat) | ||
| local pathNodes = { | ||
| [effectNode] = true | ||
| } | ||
| for _, pathNode in pairs(node.path) do | ||
| if pathNode ~= node then | ||
| pathNodes[pathNode] = true | ||
| end | ||
| end | ||
| if node.pathDist > 1 then | ||
| node.power.masteryEffects[effect.id].pathPower = self:CalculatePowerStat(self.powerStat, calcFunc({ addNodes = pathNodes }, useFullDPS), calcBase) | ||
| end | ||
| end | ||
| node.power.singleStat = m_max(node.power.singleStat or 0, node.power.masteryEffects[effect.id].singleStat) | ||
| node.power.pathPower = m_max(node.power.pathPower or 0, node.power.masteryEffects[effect.id].pathPower) | ||
| elseif not self.powerStat or not self.powerStat.ignoreForNodes then | ||
| node.power.masteryEffects[effect.id].offence, node.power.masteryEffects[effect.id].defence = self:CalculateCombinedOffDefStat(output, calcBase) | ||
| node.power.masteryEffects[effect.id].singleStat = node.power.masteryEffects[effect.id].offence | ||
| if node.path and not node.ascendancyName then | ||
| newPowerMax.offence = m_max(newPowerMax.offence, node.power.masteryEffects[effect.id].offence) | ||
| newPowerMax.defence = m_max(newPowerMax.defence, node.power.masteryEffects[effect.id].defence) | ||
| newPowerMax.offencePerPoint = m_max(newPowerMax.offencePerPoint, node.power.masteryEffects[effect.id].offence / node.pathDist) | ||
| newPowerMax.defencePerPoint = m_max(newPowerMax.defencePerPoint, node.power.masteryEffects[effect.id].defence / node.pathDist) | ||
| end | ||
| node.power.offence = m_max(node.power.offence or 0, node.power.masteryEffects[effect.id].offence) | ||
| node.power.defence = m_max(node.power.defence or 0, node.power.masteryEffects[effect.id].defence) | ||
| node.power.singleStat = m_max(node.power.singleStat or 0, node.power.masteryEffects[effect.id].singleStat) | ||
| end | ||
| end | ||
| nodeIndex = nodeIndex + 1 | ||
| if coroutine.running() and GetTime() - start > 100 then | ||
| if self.build.powerBuilderProgressCallback then | ||
| self.build.powerBuilderProgressCallback(m_floor(nodeIndex/total*100)) | ||
| end | ||
| coroutine.yield() | ||
| start = GetTime() | ||
| end | ||
| end | ||
| end | ||
| end | ||
| end | ||
| end | ||
| end | ||
|
|
||
|
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. opted to duplicate the power calc in the loop here instead of a bigger refactor for readability
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd prefer to not duplicate this logic, actually. |
||
| -- Calculate the impact of every cluster notable | ||
| -- used for the power report screen | ||
| for nodeName, node in pairs(self.build.spec.tree.clusterNodeMap) do | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -1093,6 +1093,53 @@ function TreeTabClass:BuildPowerReportList(currentStat) | |
| type = node.type, | ||
| pathDist = pathDist | ||
| }) | ||
| elseif node.type == "Mastery" and node.power.masteryEffects and not node.ascendancyName then | ||
| local pathDist | ||
| if isAlloc then | ||
| pathDist = #(node.depends or { }) == 0 and 1 or #node.depends | ||
| else | ||
| pathDist = #(node.path or { }) == 0 and 1 or #node.path | ||
| end | ||
|
|
||
| for _, masteryEffect in ipairs(node.masteryEffects or { }) do | ||
| local effect = self.build.spec.tree.masteryEffects[masteryEffect.effect] | ||
| local effectPower = node.power.masteryEffects[masteryEffect.effect] | ||
| if effect and effectPower then | ||
| local nodePower = (effectPower.singleStat or 0) * ((displayStat.pc or displayStat.mod) and 100 or 1) | ||
| local pathPower = ((effectPower.pathPower or effectPower.singleStat or 0) / pathDist) * ((displayStat.pc or displayStat.mod) and 100 or 1) | ||
| local nodePowerStr = s_format("%"..displayStat.fmt, nodePower) | ||
| local pathPowerStr = s_format("%"..displayStat.fmt, pathPower) | ||
|
|
||
| nodePowerStr = formatNumSep(nodePowerStr) | ||
| pathPowerStr = formatNumSep(pathPowerStr) | ||
|
|
||
| if (nodePower > 0 and not displayStat.lowerIsBetter) or (nodePower < 0 and displayStat.lowerIsBetter) then | ||
| nodePowerStr = colorCodes.POSITIVE .. nodePowerStr | ||
| elseif (nodePower < 0 and not displayStat.lowerIsBetter) or (nodePower > 0 and displayStat.lowerIsBetter) then | ||
| nodePowerStr = colorCodes.NEGATIVE .. nodePowerStr | ||
| end | ||
| if (pathPower > 0 and not displayStat.lowerIsBetter) or (pathPower < 0 and displayStat.lowerIsBetter) then | ||
| pathPowerStr = colorCodes.POSITIVE .. pathPowerStr | ||
| elseif (pathPower < 0 and not displayStat.lowerIsBetter) or (pathPower > 0 and displayStat.lowerIsBetter) then | ||
| pathPowerStr = colorCodes.NEGATIVE .. pathPowerStr | ||
| end | ||
|
|
||
| local effectLabelParts = isAlloc and not node.allMasteryOptions and node.sd or effect.stats or effect.sd | ||
|
Comment on lines
+1108
to
+1127
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is all the same code as the section above, I think we can combine these sections. At least have a function that they share. |
||
| t_insert(report, { | ||
| name = effectLabelParts and node.dn..": "..t_concat(effectLabelParts, " / ") or node.dn, | ||
| power = nodePower, | ||
| powerStr = nodePowerStr, | ||
| pathPower = pathPower, | ||
| pathPowerStr = pathPowerStr, | ||
| allocated = isAlloc, | ||
| id = node.id, | ||
| x = node.x, | ||
| y = node.y, | ||
| type = node.type, | ||
| pathDist = pathDist | ||
| }) | ||
| end | ||
| end | ||
| end | ||
| end | ||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
NB: adding a new table to node.power since a single power per node can't represent unallocated mastery options