From 773ec4342db930590d29a751a2cc55b1c188a7b5 Mon Sep 17 00:00:00 2001 From: Tercio Jose Date: Wed, 3 May 2023 23:33:31 -0300 Subject: [PATCH] Round of fixes of the new breakdown --- Definitions.lua | 2 +- Libs/DF/fw.lua | 2 +- Libs/DF/header.lua | 19 ++- classes/class_damage.lua | 2 +- classes/class_spelltable.lua | 18 ++- frames/window_playerbreakdown.lua | 20 +++ frames/window_playerbreakdown_spells.lua | 166 ++++++++++++++--------- images/arrow_face_down.tga | Bin 0 -> 16428 bytes 8 files changed, 150 insertions(+), 79 deletions(-) create mode 100644 images/arrow_face_down.tga diff --git a/Definitions.lua b/Definitions.lua index 3d8f59c6..31b0318d 100644 --- a/Definitions.lua +++ b/Definitions.lua @@ -51,6 +51,7 @@ ---@class point : string @string(topleft, topright, bottomleft, bottomright, top, bottom, left, right, center) is a string that represents a point on a frame. Points are used to position frames relative to each other. ---@class uiobject +---@field GetObjectType fun(self: uiobject) : string ---@field Show fun(self: uiobject) make the object be shown on the user screen ---@field Hide fun(self: uiobject) make the object be hidden from the user screen ---@field SetShown fun(self: uiobject, state: boolean) show or hide the object @@ -142,7 +143,6 @@ ---@field GetNumPoints fun(self: frame) : number ---@field GetNumRegions fun(self: frame) : number ---@field GetName fun(self: frame) : string ----@field GetObjectType fun(self: frame) : string ---@field GetChildren fun(self: frame) : frame[] ---@field GetRegions fun(self: frame) : region[] ---@field CreateTexture fun(self: frame, name: string|nil, layer: "background"|"border"|"artwork"|"overlay"|"highlight", inherits: string|nil, subLayer: number|nil) : texture diff --git a/Libs/DF/fw.lua b/Libs/DF/fw.lua index 1245843e..e4bfe072 100644 --- a/Libs/DF/fw.lua +++ b/Libs/DF/fw.lua @@ -1,6 +1,6 @@ -local dversion = 423 +local dversion = 424 local major, minor = "DetailsFramework-1.0", dversion local DF, oldminor = LibStub:NewLibrary(major, minor) diff --git a/Libs/DF/header.lua b/Libs/DF/header.lua index eceab551..5ccf1cb8 100644 --- a/Libs/DF/header.lua +++ b/Libs/DF/header.lua @@ -93,8 +93,9 @@ detailsFramework.HeaderFunctions = { ---@cast self df_headerchild for i = 1, #self.FramesToAlign do - local frame = self.FramesToAlign[i] - frame:ClearAllPoints() + ---@type uiobject + local uiObject = self.FramesToAlign[i] + uiObject:ClearAllPoints() ---@type df_headercolumnframe local columnHeader = columnHeaderFrames[i] @@ -103,12 +104,20 @@ detailsFramework.HeaderFunctions = { if (columnHeader.columnAlign == "right") then offset = columnHeader:GetWidth() - if (frame:GetObjectType() == "FontString") then - frame:SetJustifyH("right") + end + + if (uiObject:GetObjectType() == "FontString") then + ---@cast uiObject fontstring + if (columnHeader.columnAlign == "right") then + uiObject:SetJustifyH("right") + elseif (columnHeader.columnAlign == "left") then + uiObject:SetJustifyH("left") + elseif (columnHeader.columnAlign == "center") then + uiObject:SetJustifyH("center") end end - frame:SetPoint(columnHeader.columnAlign, self, anchor, columnHeader.XPosition + columnHeader.columnOffset + offset, 0) + uiObject:SetPoint(columnHeader.columnAlign, self, anchor, columnHeader.XPosition + columnHeader.columnOffset + offset, 0) end end end, diff --git a/classes/class_damage.lua b/classes/class_damage.lua index 9de38b11..dbfdf44b 100644 --- a/classes/class_damage.lua +++ b/classes/class_damage.lua @@ -1104,7 +1104,7 @@ end thisLine.icone_classe:SetTexture(spellIcon) thisLine.icone_classe:SetTexCoord(0.078125, 0.921875, 0.078125, 0.921875) thisLine.icone_classe:SetVertexColor(1, 1, 1) - if(thisLine.mouse_over and not instancia.baseframe.isMoving) then + if(thisLine.mouse_over and not instance.baseframe.isMoving) then local classIcon = thisLine:GetClassIcon() thisLine.iconHighlight:SetTexture(classIcon:GetTexture()) thisLine.iconHighlight:SetTexCoord(classIcon:GetTexCoord()) diff --git a/classes/class_spelltable.lua b/classes/class_spelltable.lua index 286a008c..7fbbbaf7 100644 --- a/classes/class_spelltable.lua +++ b/classes/class_spelltable.lua @@ -28,6 +28,11 @@ local spellTable_FieldsToSum = { ["absorbed"] = true, ["overheal"] = true, ["totaldenied"] = true, + ["e_amt"] = true, + ["e_dmg"] = true, + ["e_heal"] = true, + ["e_lvl"] = true, + ["e_total"] = true, } ---@class spelltablemixin @@ -90,16 +95,21 @@ Details.SpellTableMixin = { local spellTable = spellTables[i] if (spellTable) then for key, value in pairs(spellTable) do - ---@cast key string - ---@cast value number if (spellTable_FieldsToSum[key]) then - targetTable[key] = (targetTable[key] or 0) + value + --evoker empowerment levels + if (key == "e_lvl" or key == "e_heal" or key == "e_dmg") then + targetTable[key] = targetTable[key] or {} + for level, amount in pairs(value) do + targetTable[key][level] = (targetTable[key][level] or 0) + amount + end + else + targetTable[key] = (targetTable[key] or 0) + value + end end end end end end, - } --detailsFramework:Mixin(Details, Details.SpellTableMixin) \ No newline at end of file diff --git a/frames/window_playerbreakdown.lua b/frames/window_playerbreakdown.lua index ba065f13..217659cb 100644 --- a/frames/window_playerbreakdown.lua +++ b/frames/window_playerbreakdown.lua @@ -94,6 +94,7 @@ function Details:OpenBreakdownWindow(instanceObject, actorObject, bFromAttribute --create the player list frame in the left side of the window Details.PlayerBreakdown.CreatePlayerListFrame() + Details.PlayerBreakdown.CreateDumpDataFrame() if (not Details.row_singleclick_overwrite[mainAttribute] or not Details.row_singleclick_overwrite[mainAttribute][subAttribute]) then Details:CloseBreakdownWindow() @@ -337,6 +338,25 @@ function Details:CloseBreakdownWindow() end end +function Details.PlayerBreakdown.CreateDumpDataFrame() + breakdownWindow.dumpDataFrame = CreateFrame("frame", "$parentDumpTableFrame", DetailsBreakdownWindowPlayerScrollBox, "BackdropTemplate") + breakdownWindow.dumpDataFrame:SetPoint("topleft", DetailsBreakdownWindowPlayerScrollBox, "topleft", 0, 0) + breakdownWindow.dumpDataFrame:SetPoint("bottomright", DetailsBreakdownWindowPlayerScrollBox, "bottomright", 0, 0) + breakdownWindow.dumpDataFrame:SetFrameLevel(DetailsBreakdownWindowPlayerScrollBox:GetFrameLevel() + 10) + detailsFramework:ApplyStandardBackdrop(breakdownWindow.dumpDataFrame, true) + breakdownWindow.dumpDataFrame:Hide() + + --create a details framework special lua editor + breakdownWindow.dumpDataFrame.luaEditor = detailsFramework:NewSpecialLuaEditorEntry(breakdownWindow.dumpDataFrame, 1, 1, "text", "$parentCodeEditorWindow") + breakdownWindow.dumpDataFrame.luaEditor:SetPoint("topleft", breakdownWindow.dumpDataFrame, "topleft", 2, -2) + breakdownWindow.dumpDataFrame.luaEditor:SetPoint("bottomright", breakdownWindow.dumpDataFrame, "bottomright", -2, 2) + breakdownWindow.dumpDataFrame.luaEditor:SetFrameLevel(breakdownWindow.dumpDataFrame:GetFrameLevel()+1) + breakdownWindow.dumpDataFrame.luaEditor:SetBackdrop({}) + + --hide the scroll bar + DetailsBreakdownWindowPlayerScrollBoxDumpTableFrameCodeEditorWindowScrollBar:Hide() +end + function breakdownWindow:CreateRightSideBar() --not enabled breakdownWindow.RightSideBar = CreateFrame("frame", nil, breakdownWindow, "BackdropTemplate") breakdownWindow.RightSideBar:SetWidth(20) diff --git a/frames/window_playerbreakdown_spells.lua b/frames/window_playerbreakdown_spells.lua index 41f5ecf8..ca2dde4c 100644 --- a/frames/window_playerbreakdown_spells.lua +++ b/frames/window_playerbreakdown_spells.lua @@ -130,8 +130,8 @@ local spellContainerColumnData = { --the align seems to be bugged as the left is aligning in the center and center is on the left side {name = "icon", width = 22, label = "", align = "left", enabled = true, offset = columnOffset}, {name = "target", width = 22, label = "", align = "left", enabled = true, offset = columnOffset}, - {name = "rank", label = "#", width = 16, align = "left", enabled = true, offset = columnOffset, dataType = "number"}, - {name = "expand", label = "^", width = 16, align = "center", enabled = true, offset = columnOffset}, + {name = "rank", label = "#", width = 16, align = "center", enabled = true, offset = 6, dataType = "number"}, + {name = "expand", label = "^", width = 16, align = "left", enabled = true, offset = -4}, --maybe -3 {name = "name", label = "spell name", width = 246, align = "left", enabled = true, offset = columnOffset}, {name = "amount", label = "total", key = "total", selected = true, width = 50, align = "left", enabled = true, canSort = true, sortKey = "total", dataType = "number", order = "DESC", offset = columnOffset}, {name = "persecond", label = "ps", key = "total", width = 50, align = "left", enabled = false, canSort = true, sortKey = "ps", offset = columnOffset, order = "DESC", dataType = "number"}, @@ -503,10 +503,27 @@ local onEnterSpellBar = function(spellBar, motion) --parei aqui: precisa por nom if (IsShiftKeyDown()) then if (type(spellId) == "number") then GameCooltip:Preset(2) - GameCooltip:SetOwner(spellBar, "ANCHOR_TOPRIGHT") + GameCooltip:SetOwner(spellBar) GameCooltip:AddLine(Loc ["ABILITY_ID"] .. ": " .. spellBar.spellId) GameCooltip:Show() + + local t = combatObject:GetActor(1, actorName).spells._ActorTable[spellId] + + local textToEditor = "" + for key, value in pairs(t) do + if (type(value) ~= "function" and type(value) ~= "table") then + textToEditor = textToEditor .. key .. " = " .. tostring(value) .. "\n" + end + end + + breakdownWindow.dumpDataFrame:Show() + breakdownWindow.dumpDataFrame.luaEditor:SetText(textToEditor) + --hide the scroll bar + DetailsBreakdownWindowPlayerScrollBoxDumpTableFrameCodeEditorWindowScrollBar:Hide() end + + elseif (breakdownWindow.dumpDataFrame:IsShown()) then + breakdownWindow.dumpDataFrame:Hide() end if (spellId == 98021) then --spirit link totem @@ -562,6 +579,69 @@ local onEnterSpellBar = function(spellBar, motion) --parei aqui: precisa por nom blockLine3.rightText:SetText(Loc ["STRING_DPS"] .. ": " .. Details:CommaValue(spellBar.perSecond)) --dps end + local emporwerSpell = spellTable.e_total + if (emporwerSpell) then + local empowerLevelSum = spellTable.e_total --total sum of empower levels + local empowerAmount = spellTable.e_amt --amount of casts with empower + local empowerAmountPerLevel = spellTable.e_lvl --{[1] = 4; [2] = 9; [3] = 15} + local empowerDamagePerLevel = spellTable.e_dmg --{[1] = 54548745, [2] = 74548745} + + ---@type breakdownspellblock + local empowerBlock = spellBlockContainer:GetBlock(blockIndex) + blockIndex = blockIndex + 1 + + local level1AverageDamage = "0" + local level2AverageDamage = "0" + local level3AverageDamage = "0" + local level4AverageDamage = "0" + local level5AverageDamage = "0" + + if (empowerDamagePerLevel[1]) then + level1AverageDamage = Details:Format(empowerDamagePerLevel[1] / empowerAmountPerLevel[1]) + end + if (empowerDamagePerLevel[2]) then + level2AverageDamage = Details:Format(empowerDamagePerLevel[2] / empowerAmountPerLevel[2]) + end + if (empowerDamagePerLevel[3]) then + level3AverageDamage = Details:Format(empowerDamagePerLevel[3] / empowerAmountPerLevel[3]) + end + if (empowerDamagePerLevel[4]) then + level4AverageDamage = Details:Format(empowerDamagePerLevel[4] / empowerAmountPerLevel[4]) + end + if (empowerDamagePerLevel[5]) then + level5AverageDamage = Details:Format(empowerDamagePerLevel[5] / empowerAmountPerLevel[5]) + end + + empowerBlock:Show() + empowerBlock:SetValue(100) + + empowerBlock.sparkTexture:SetPoint("left", empowerBlock, "left", empowerBlock:GetWidth() + spellBreakdownSettings.blockspell_spark_offset, 0) + empowerBlock:SetColor(0.200, 0.576, 0.498, 0.6) + + local blockLine1, blockLine2, blockLine3 = empowerBlock:GetLines() + blockLine1.leftText:SetText("Spell Empower Average Level: " .. string.format("%.2f", empowerLevelSum / empowerAmount)) + + if (level1AverageDamage ~= "0") then + blockLine2.leftText:SetText("Level 1 Avg: " .. level1AverageDamage .. " (" .. (empowerAmountPerLevel[1] or 0) .. ")") + end + + if (level2AverageDamage ~= "0") then + blockLine2.centerText:SetText("Level 2 Avg: " .. level2AverageDamage .. " (" .. (empowerAmountPerLevel[2] or 0) .. ")") + end + + if (level3AverageDamage ~= "0") then + blockLine2.rightText:SetText("Level 3 Avg: " .. level3AverageDamage .. " (" .. (empowerAmountPerLevel[3] or 0) .. ")") + end + + if (level4AverageDamage ~= "0") then + blockLine3.leftText:SetText("Level 4 Avg: " .. level4AverageDamage .. " (" .. (empowerAmountPerLevel[4] or 0) .. ")") + end + + if (level5AverageDamage ~= "0") then + blockLine3.rightText:SetText("Level 5 Avg: " .. level5AverageDamage .. " (" .. (empowerAmountPerLevel[5] or 0) .. ")") + end + end + --check if there's normal hits and build the block ---@type number local normalHitsAmt = spellTable.n_amt @@ -620,68 +700,6 @@ local onEnterSpellBar = function(spellBar, motion) --parei aqui: precisa por nom blockLine3.rightText:SetText(Loc ["STRING_DPS"] .. ": " .. Details:CommaValue(spellTable.c_total / critTempoPercent)) end - local emporwerSpell = spellTable.e_total - if (emporwerSpell) then - local empowerLevelSum = spellTable.e_total --total sum of empower levels - local empowerAmount = spellTable.e_amt --amount of casts with empower - local empowerAmountPerLevel = spellTable.e_lvl --{[1] = 4; [2] = 9; [3] = 15} - local empowerDamagePerLevel = spellTable.e_dmg --{[1] = 54548745, [2] = 74548745} - - ---@type breakdownspellblock - local empowerBlock = spellBlockContainer:GetBlock(blockIndex) - empowerBlock:Show() - blockIndex = blockIndex + 1 - - local level1AverageDamage = "0" - local level2AverageDamage = "0" - local level3AverageDamage = "0" - local level4AverageDamage = "0" - local level5AverageDamage = "0" - - if (empowerDamagePerLevel[1]) then - level1AverageDamage = Details:Format(empowerDamagePerLevel[1] / empowerAmountPerLevel[1]) - end - if (empowerDamagePerLevel[2]) then - level2AverageDamage = Details:Format(empowerDamagePerLevel[2] / empowerAmountPerLevel[2]) - end - if (empowerDamagePerLevel[3]) then - level3AverageDamage = Details:Format(empowerDamagePerLevel[3] / empowerAmountPerLevel[3]) - end - if (empowerDamagePerLevel[4]) then - level4AverageDamage = Details:Format(empowerDamagePerLevel[4] / empowerAmountPerLevel[4]) - end - if (empowerDamagePerLevel[5]) then - level5AverageDamage = Details:Format(empowerDamagePerLevel[5] / empowerAmountPerLevel[5]) - end - - empowerBlock:SetValue(100) - empowerBlock.sparkTexture:SetPoint("left", empowerBlock, "left", empowerBlock:GetWidth() + spellBreakdownSettings.blockspell_spark_offset, 0) - empowerBlock:SetColor(0.200, 0.576, 0.498, 0.6) - - local blockLine1, blockLine2, blockLine3 = empowerBlock:GetLines() - blockLine1.leftText:SetText("Spell Empower Average Level: " .. string.format("%.2f", empowerLevelSum / empowerAmount)) - - if (level1AverageDamage ~= "0") then - blockLine2.leftText:SetText("Level 1 Avg: " .. level1AverageDamage .. " (" .. (empowerAmountPerLevel[1] or 0) .. ")") - end - - if (level2AverageDamage ~= "0") then - blockLine2.centerText:SetText("Level 2 Avg: " .. level2AverageDamage .. " (" .. (empowerAmountPerLevel[2] or 0) .. ")") - end - - if (level3AverageDamage ~= "0") then - blockLine2.rightText:SetText("Level 3 Avg: " .. level3AverageDamage .. " (" .. (empowerAmountPerLevel[3] or 0) .. ")") - end - - if (level4AverageDamage ~= "0") then - blockLine3.leftText:SetText("Level 4 Avg: " .. level4AverageDamage .. " (" .. (empowerAmountPerLevel[4] or 0) .. ")") - end - - if (level5AverageDamage ~= "0") then - blockLine3.rightText:SetText("Level 5 Avg: " .. level5AverageDamage .. " (" .. (empowerAmountPerLevel[5] or 0) .. ")") - end - end - if (trinketData[spellId]) then ---@type trinketdata local trinketInfo = trinketData[spellId] @@ -838,6 +856,10 @@ local onLeaveSpellBar = function(spellBar) if (not spellsTab.HasSelectedSpellBar()) then spellsTab.GetSpellBlockFrame():ClearBlocks() end + + if (breakdownWindow.dumpDataFrame:IsShown()) then + breakdownWindow.dumpDataFrame:Hide() + end end ---on mouse down a breakdownspellbar in the breakdown window @@ -1619,7 +1641,7 @@ local updateSpellBar = function(spellBar, index, actorName, combatObject, scroll --statusbar color by school local r, g, b = Details:GetSpellSchoolColor(spellTable.spellschool or 1) - spellBar.statusBar:SetStatusBarColor(r, g, b, 1) + spellBar.statusBar:SetStatusBarColor(r, g, b, 0.963) spellBar.average = value / spellTable.counter spellBar.combatTime = combatTime @@ -1631,6 +1653,7 @@ local updateSpellBar = function(spellBar, index, actorName, combatObject, scroll if (header.name == "icon") then --ok spellBar.spellIcon:Show() spellBar.spellIcon:SetTexture(spellIcon) + spellBar.spellIcon:SetAlpha(0.92) spellBar:AddFrameToHeaderAlignment(spellBar.spellIconFrame) elseif (header.name == "target") then --the tab does not have knownledge about the targets of the spell, it must be passed over @@ -1663,7 +1686,16 @@ local updateSpellBar = function(spellBar, index, actorName, combatObject, scroll spellBar.expandButton:SetScript("OnClick", onClickExpandButton) --update the texture taking the state of the expanded value - spellBar.expandButton.texture:SetTexture(bIsSpellExpaded and [[Interface\BUTTONS\Arrow-Up-Down]] or [[Interface\BUTTONS\Arrow-Down-Down]]) + if (bIsSpellExpaded) then + spellBar.expandButton.texture:SetTexture([[Interface\AddOns\Details\images\arrow_face_down]]) + spellBar.expandButton.texture:SetTexCoord(0, 1, 1, 0) + else + spellBar.expandButton.texture:SetTexture([[Interface\AddOns\Details\images\arrow_face_down]]) + spellBar.expandButton.texture:SetTexCoord(0, 1, 0, 1) + end + + spellBar.expandButton.texture:SetAlpha(0.7) + spellBar.expandButton.texture:SetSize(16, 16) end elseif (header.name == "name") then --ok diff --git a/images/arrow_face_down.tga b/images/arrow_face_down.tga new file mode 100644 index 0000000000000000000000000000000000000000..7ba32929078bbc07f2938b255d7dae56abec0a4f GIT binary patch literal 16428 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 qAq7GTgcJxV5K