local HIDE_INACTIVE_NODE = false; local _, addon = ... local LoadingBarUtil = addon.TalentTreeLoadingBarUtil; local ClassTalentTooltipUtil = addon.ClassTalentTooltipUtil; local DataProvider = addon.TalentTreeDataProvider; local OnEnterDelay = addon.TalentTreeOnEnterDelay; local After = C_Timer.After; local C_Traits = C_Traits; local C_ClassTalents = C_ClassTalents; local C_SpecializationInfo = C_SpecializationInfo; local GetNodeInfo = C_Traits and C_Traits.GetNodeInfo; local GetEntryInfo = C_Traits and C_Traits.GetEntryInfo; local C_PvP = C_PvP; local GetSpecializationInfoByID = GetSpecializationInfoByID; local GetInspectSpecialization = GetInspectSpecialization; local UnitClass = UnitClass; local UnitSex = UnitSex; --local INSPECT_TRAIT_CONFIG_ID = -1; local sqrt = math.sqrt; local atan2 = math.atan2; local ipairs = ipairs; local floor = math.floor; local BUTTON_PIXEL_SIZE = 32; local ICON_PIXEL_SIZE = 24; local DISTANCE_UNIT = 300; --600 --neighboring node distance 600 local PADDING = 1; local HEADER_SIZE; local SECTOR_WIDTH = 11; --the width of each tab (spec talent, class talent). unit is button wdith. local BUTTON_SIZE = 32; local BUTTON_SIZE_HALF = BUTTON_SIZE * 0.5; local BRANCH_WEIGHT = 1; local DISTANCE_RATIO = 0.05; local ICON_SIZE = 28; local FONT_HEIGHT = 16; local PIXEL = 1; local MainFrame; local Nodes = {}; local Branches = {}; local EntryInfoCache = {}; local NodeInfoCache = {}; local NodeIDxNode = {}; function DataProvider:WipeNodeCache() EntryInfoCache = {}; NodeInfoCache = {}; end local LoadoutUtil = {}; local LayoutUtil = {}; function LayoutUtil:Reset() self.leftMinX = 69; self.leftMaxX = 0; self.leftMinY = 69; self.rightMinX = 69; self.rightMaxX = 1; self.rightMinY = 69; end function LayoutUtil:UpdateFrameSize() local pvpFrameWidth = (MainFrame.PvPTalentFrame:IsShown() and 3*BUTTON_SIZE) or 0; MainFrame:SetSize(BUTTON_SIZE * 2 * SECTOR_WIDTH + pvpFrameWidth, BUTTON_SIZE * (10 + 2*PADDING) + HEADER_SIZE); end function LayoutUtil:UpdateNodePosition() local tileSize = BUTTON_SIZE; local container = MainFrame; local leftMinY = self.leftMinY; local leftMinX = self.leftMinX; local leftMaxX = self.leftMaxX; local leftSpanX = leftMaxX - leftMinX + 1; local rightMinY = self.rightMinY; local rightMinX = self.rightMinX; local rightMaxX = self.rightMaxX; local rightSpanX = rightMaxX - rightMinX + 1; local leftFromOffsetX = (SECTOR_WIDTH - leftSpanX)*0.5*tileSize; local rightFromOffsetX = ((SECTOR_WIDTH - rightSpanX)*0.5 + SECTOR_WIDTH) *tileSize; local fromOffsetY = - HEADER_SIZE - tileSize; local node, x, y; for i = 1, MainFrame.numAcitveNodes do node = Nodes[i]; node:ClearAllPoints(); if node.isLeft then x = (node.iX - leftMinX) * tileSize + leftFromOffsetX; y = -(node.iY - leftMinY) * tileSize + fromOffsetY; --node.Order:SetText(node.iX - leftMinX) else x = (node.iX - rightMinX) * tileSize + rightFromOffsetX; y = -(node.iY - rightMinY) * tileSize + fromOffsetY; node:SetPoint("TOPLEFT", container, "TOPLEFT", x, y); --node.Order:SetText(node.iX - rightMinX) end node:SetPoint("TOPLEFT", container, "TOPLEFT", x, y); node.x, node.y = x, y; end end function LayoutUtil:SetNodeTileIndex(node, isLeft, iX, iY) node.iX = iX; node.iY = iY; node.isLeft = isLeft or nil; if isLeft then if iX > self.leftMaxX then self.leftMaxX = iX; end if iX < self.leftMinX then self.leftMinX = iX; end if iY < self.leftMinY then self.leftMinY = iY; end else if iX > self.rightMaxX then self.rightMaxX = iX; end if iX < self.rightMinX then self.rightMinX = iX; end if iY < self.rightMinY then self.rightMinY = iY; end end end local function CalculateNormalizedPosition(posX, posY) posX = posX - 1800; posY = 1200 - posY; posX = floor(posX/DISTANCE_UNIT + 0.5); posY = floor(posY/DISTANCE_UNIT + 0.5); posY = -posY; posX = posX * 0.5; posY = posY * 0.5; local isLeftSide; if posX >= 12.5 then posX = posX - 2 else isLeftSide = true end return posX, posY, isLeftSide end local function SetBranchColorYellow(branch, isActive) if isActive then branch:SetVertexColor(0.72, 0.6, 0); else branch:SetVertexColor(0.200, 0.200, 0.200); end end local function SetBranchColorCyan(branch, isActive) if isActive then branch:SetVertexColor(0, 0.53, 0.65); else branch:SetVertexColor(0.200, 0.200, 0.200); end end local SetBranchColor = SetBranchColorYellow; NarciMiniTalentTreeMixin = {}; function NarciMiniTalentTreeMixin:OnLoad() MainFrame = self; local px = NarciAPI.GetPixelForWidget(self, 1); PIXEL = px; BRANCH_WEIGHT = NarciAPI.GetPixelForWidget(self, 2); BUTTON_SIZE = NarciAPI.GetPixelForWidget(self, BUTTON_PIXEL_SIZE); ICON_SIZE = NarciAPI.GetPixelForWidget(self, ICON_PIXEL_SIZE); DISTANCE_RATIO = BUTTON_SIZE / 600; BUTTON_SIZE_HALF = BUTTON_SIZE * 0.5; FONT_HEIGHT = 16 * px; HEADER_SIZE = FONT_HEIGHT + BUTTON_SIZE; LayoutUtil.fromOffsetX = -3 * 600*DISTANCE_RATIO; LayoutUtil.fromOffsetY = 2 * 600*DISTANCE_RATIO; LayoutUtil:UpdateFrameSize(); self.ClassName:ClearAllPoints(); self.ClassName:SetPoint("TOP", self, "TOPLEFT", (4.5 + PADDING) * BUTTON_SIZE, -PADDING*BUTTON_SIZE); self.SpecName:ClearAllPoints(); self.SpecName:SetPoint("TOP", self, "TOPLEFT", (15.5 + PADDING) * BUTTON_SIZE, -PADDING*BUTTON_SIZE); local font, height, flag = self.LoadoutToggle.ButtonText:GetFont(); self.ClassName:SetFont(font, BUTTON_SIZE, flag); self.SpecName:SetFont(font, BUTTON_SIZE, flag); local hitrectCompensation = (16*px - 16)/2; if hitrectCompensation > 0 then hitrectCompensation = 0; end local function LoadoutToggle_OnEnter(f) f.ButtonText:SetTextColor(0.92, 0.92, 0.92); f.Arrow:SetVertexColor(0.92, 0.92, 0.92); end local function LoadoutToggle_OnLeave(f) f.ButtonText:SetTextColor(0.67, 0.67, 0.67); f.Arrow:SetVertexColor(0.67, 0.67, 0.67); end self.LoadoutToggle.ButtonText:SetFont(font, FONT_HEIGHT, ""); self.LoadoutToggle:ClearAllPoints(); self.LoadoutToggle:SetPoint("TOP", self, "TOPLEFT", SECTOR_WIDTH * BUTTON_SIZE, -BUTTON_SIZE); self.LoadoutToggle:SetHeight(FONT_HEIGHT); self.LoadoutToggle:SetHitRectInsets(0, 0, hitrectCompensation, hitrectCompensation); self.LoadoutToggle.Arrow:SetSize(FONT_HEIGHT, FONT_HEIGHT); self.LoadoutToggle:SetScript("OnClick", function() LoadoutUtil:ToggleList(); end); self.LoadoutToggle:SetScript("OnEnter", LoadoutToggle_OnEnter); self.LoadoutToggle:SetScript("OnLeave", LoadoutToggle_OnLeave); self.HeaderLight:ClearAllPoints(); self.HeaderLight:SetPoint("TOP", self, "TOPLEFT", SECTOR_WIDTH * BUTTON_SIZE, 0); self.HeaderLight:SetSize(SECTOR_WIDTH * BUTTON_SIZE * 2 , SECTOR_WIDTH * BUTTON_SIZE * 0.5, 0); self.SpecTabToggle:SetHeight(FONT_HEIGHT); self.SpecTabToggle:SetHitRectInsets(-4, 0, hitrectCompensation, hitrectCompensation); self.SpecTabToggle:SetPoint("TOPLEFT", self, "TOPLEFT", BUTTON_SIZE, -BUTTON_SIZE); self.SpecTabToggle.ButtonText:SetFont(font, FONT_HEIGHT, ""); self.SpecTabToggle.ButtonText:SetPoint("LEFT", 12*px, 0); self.SpecTabToggle.Arrow:SetSize(FONT_HEIGHT, FONT_HEIGHT); self.SpecTabToggle.Arrow:SetPoint("LEFT", 0, -px); local function SpecTabToggle_OnEnter(f) f.ButtonText:SetTextColor(0.8, 0.8, 0.8); f.Arrow:SetVertexColor(0.8, 0.8, 0.8); end local function SpecTabToggle_OnLeave(f) f.ButtonText:SetTextColor(0.5, 0.5, 0.5); f.Arrow:SetVertexColor(0.5, 0.5, 0.5); end local function SpecTabToggle_OnClick(f) MainFrame.SpecSelect:ShowFrame(); end self.SpecTabToggle:SetScript("OnEnter", SpecTabToggle_OnEnter); self.SpecTabToggle:SetScript("OnLeave", SpecTabToggle_OnLeave); self.SpecTabToggle:SetScript("OnClick", SpecTabToggle_OnClick); self.PvPTalentToggle:SetHeight(FONT_HEIGHT); self.PvPTalentToggle:SetHitRectInsets(0, -8, hitrectCompensation, hitrectCompensation); self.PvPTalentToggle:SetPoint("TOPRIGHT", self, "TOPRIGHT", -BUTTON_SIZE, -BUTTON_SIZE); self.PvPTalentToggle.ButtonText:SetFont(font, FONT_HEIGHT, ""); self.PvPTalentToggle.ButtonText:SetPoint("RIGHT", -12*px, 0); self.PvPTalentToggle.Arrow:SetSize(FONT_HEIGHT/2, FONT_HEIGHT); self.PvPTalentToggle.Arrow:SetPoint("RIGHT", 0, -px); self.PvPTalentToggle:SetScript("OnEnter", SpecTabToggle_OnEnter); self.PvPTalentToggle:SetScript("OnLeave", SpecTabToggle_OnLeave); self.PvPTalentToggle:SetScript("OnClick", function(f) MainFrame.PvPTalentFrame:Toggle(); end); self.Divider:ClearAllPoints(); self.Divider:SetPoint("TOPLEFT", self, "TOPLEFT", SECTOR_WIDTH * BUTTON_SIZE, -BUTTON_SIZE/2 -HEADER_SIZE); self.Divider:SetPoint("BOTTOMLEFT", self, "BOTTOM", 0, BUTTON_SIZE/2); end function NarciMiniTalentTreeMixin:Init() if self.PvPTalentFrame:IsWarModeActive() then self.PvPTalentFrame:Toggle(); end self.Init = nil; end function NarciMiniTalentTreeMixin:ShowActiveBuild() local configID = C_ClassTalents.GetActiveConfigID(); self:ShowConfig(configID); end function NarciMiniTalentTreeMixin:ShowConfig(configID, isPreviewing) if configID ~= self.configID then DataProvider:WipeNodeCache(); else return end self:ReleaseAllNodes(); local specID = DataProvider:GetCurrentSpecID(); local configInfo = C_Traits.GetConfigInfo(configID); local treeID = configInfo.treeIDs[1] local nodeIDs = C_Traits.GetTreeNodes(treeID); self.configID = configID; self.treeID = treeID; self.nodeIDs = nodeIDs; local isInspecting = self:IsInspecting(); for i, nodeID in ipairs(nodeIDs) do self:InstantiateTalentButton(nodeID, nil, isInspecting); end LayoutUtil:UpdateNodePosition(); --After(0, function() self:CreateBranches() --end) if not isPreviewing and not isInspecting then self.LoadoutToggle.ButtonText:SetText(DataProvider:GetActiveLoadoutName()); self.SpecSelect:SetSelectedSpec(specID); LoadoutUtil:SetActiveConfigID(DataProvider:GetSelecetdConfigID()); LoadingBarUtil:HideBar(); end end function NarciMiniTalentTreeMixin:ShowInspecting(inspectUnit) self.inspectUnit = inspectUnit; self:SetInspectionMode(true); self:ShowConfig(-1); end function NarciMiniTalentTreeMixin:SetInspectionMode(state) if state then self.SpecTabToggle:Hide(); self.LoadoutToggle:Disable(); self.LoadoutToggle.Arrow:Hide(); --self.LoadoutToggle.ButtonText:SetText(TALENTS_INSPECT_FORMAT:format(UnitName(self.inspectUnit))); LoadingBarUtil:HideBar(); if self.SpecSelect:IsShown() then self.SpecSelect:CloseFrame(true); end if self.PvPTalentFrame:IsShown() then self.PvPTalentFrame:Update(); end local loadoutName; local unit = self:GetInspectUnit(); local playerName = UnitName(unit); local specID = GetInspectSpecialization(unit); local classDisplayName, class = UnitClass(unit); if specID then local sex = UnitSex(unit); local _, specName = GetSpecializationInfoByID(specID, sex); loadoutName = specName.." "..classDisplayName.." - "..playerName; else loadoutName = TALENTS_INSPECT_FORMAT:format(playerName); end self.LoadoutToggle.ButtonText:SetText(loadoutName); SetBranchColor = SetBranchColorCyan; elseif self:IsInspecting() then self.inspectUnit = nil; self.SpecTabToggle:Show(); self.LoadoutToggle:Enable(); self.LoadoutToggle.Arrow:Show(); self.LoadoutToggle.ButtonText:SetText(DataProvider:GetActiveLoadoutName()); if self.PvPTalentFrame:IsShown() then self.PvPTalentFrame:Update(); end SetBranchColor = SetBranchColorYellow; end end local PRINTED_INDEX = { [118] = true, [119] = true, [103] = true, [112] = true, [102] = true, } local function SetNodePosition(node, relativeTo, x, y) y = y - HEADER_SIZE; --node:SetPoint("TOPLEFT", relativeTo, "TOPLEFT", x, y); node.x = x; node.y = y; end function NarciMiniTalentTreeMixin:InstantiateTalentButton(nodeID, nodeInfo, isInspecting) nodeInfo = nodeInfo or self:GetAndCacheNodeInfo(nodeID); if not nodeInfo.isVisible then return nil; end local activeEntryID = nodeInfo.activeEntry and nodeInfo.activeEntry.entryID or nil; local entryInfo = (activeEntryID ~= nil) and self:GetAndCacheEntryInfo(activeEntryID) or nil; local talentType = (entryInfo ~= nil) and entryInfo.type or nil; if nodeInfo.posY < 0 then --Button #1 is out-of-bound for some reason return end local node = self:AcquireNode(); node:SetBorderColor(isInspecting); local iX, iY, isLeftSide = CalculateNormalizedPosition(nodeInfo.posX, nodeInfo.posY); if (nodeInfo.ranksPurchased > 0) or (nodeInfo.activeRank > 0) or (DataProvider:IsAutoGrantedTalent(nodeID)) then --ranksPurchased is 0 for freely-granted talent (some talent in the first row) node.active = true; if nodeInfo.ranksPurchased == 0 then node.currentRank = 1; else node.currentRank = nodeInfo.ranksPurchased; end else if HIDE_INACTIVE_NODE then node:Hide(); end node.active = nil; node.currentRank = 0; end node.maxRanks = nodeInfo.maxRanks; LayoutUtil:SetNodeTileIndex(node, isLeftSide, iX, iY); NodeIDxNode[nodeID] = node; if nodeInfo.type == 2 then node.entryIDs = nodeInfo.entryIDs; if activeEntryID == nodeInfo.entryIDs[1] then node:SetNodeType(2, 1); elseif activeEntryID == nodeInfo.entryIDs[2] then node:SetNodeType(2, 2); else node:SetNodeType(2, 0); end --[[ if nodeInfo and (nodeInfo.type == Enum.TraitNodeType.Selection) then if FlagsUtil.IsSet(nodeInfo.flags, Enum.TraitNodeFlag.ShowMultipleIcons) then return "ClassTalentButtonChoiceTemplate"; end end --]] else node.entryIDs = nil; if talentType == 0 then --*Warrior Why do some passive traits use this type? node:SetNodeType(1, 0); elseif talentType == 1 then --square node:SetNodeType(0); elseif talentType == 2 then --circle if nodeInfo.type == 0 then if nodeInfo.maxRanks == 1 then --1/1 node:SetNodeType(1, 0); elseif nodeInfo.maxRanks == 2 then if nodeInfo.ranksPurchased == 1 then --1/2 node:SetNodeType(1, 1); else --2/2 node:SetNodeType(1, 0); end elseif nodeInfo.maxRanks == 3 then if nodeInfo.ranksPurchased == 1 then --1/3 node:SetNodeType(1, 1); elseif nodeInfo.ranksPurchased == 2 then --2/3 node:SetNodeType(1, 2); else --3/3 node:SetNodeType(1, 0); end else --0/4? node.Symbol:SetVertexColor(1, 0, 0); node:SetNodeType(1, 0); end else node:Hide(); end else --nil is unselected octagon node.Symbol:SetVertexColor(1, 0, 0); print(talentType, "Unknown type") end end if node.active then node.Symbol:SetVertexColor(0.67, 0.67, 0.67); node:SetActive(true); else node.Symbol:SetVertexColor(0.160, 0.160, 0.160); node:SetActive(false); end if entryInfo then node:SetDefinitionID(entryInfo.definitionID); end node.nodeID = nodeID; node.entryID = activeEntryID; node.rank = nodeInfo.ranksPurchased; end function NarciMiniTalentTreeMixin:ReleaseAllNodes() for i = 1, #Nodes do Nodes[i]:Hide(); Nodes[i]:ClearAllPoints(); end self.numAcitveNodes = 0; LayoutUtil:Reset(); end function NarciMiniTalentTreeMixin:AcquireNode() self.numAcitveNodes = self.numAcitveNodes + 1; if not Nodes[self.numAcitveNodes] then Nodes[self.numAcitveNodes] = CreateFrame("Frame", nil, self, "NarciTalentTreeNodeTemplate"); Nodes[self.numAcitveNodes].Symbol:SetVertexColor(0.67, 0.67, 0.67); Nodes[self.numAcitveNodes].Icon:SetSize(ICON_SIZE, ICON_SIZE); Nodes[self.numAcitveNodes]:SetSize(BUTTON_SIZE, BUTTON_SIZE); end Nodes[self.numAcitveNodes]:Show(); return Nodes[self.numAcitveNodes]; end function NarciMiniTalentTreeMixin:GetAndCacheEntryInfo(entryID) if not EntryInfoCache[entryID] then EntryInfoCache[entryID] = GetEntryInfo(self.configID, entryID); end return EntryInfoCache[entryID]; end function NarciMiniTalentTreeMixin:GetAndCacheNodeInfo(nodeID) if not NodeInfoCache[nodeID] then NodeInfoCache[nodeID] = GetNodeInfo(self.configID, nodeID); end return NodeInfoCache[nodeID]; end local BranchUpdater = CreateFrame("Frame"); local MAX_PROCESS_PER_FRAME = 400; --20 local function BranchUpdater_OnUpdate(self, elapsed) local processedThisFrame = 0; local nodeID; local nodeInfo; local fromNode, targetNode; local b; local x1, y1, x2, y2, d, rd; local bchs = Branches; local SZH = BUTTON_SIZE_HALF; local notFound = true; local main = MainFrame; while self.fromNodeIndex <= self.numNodes do if self.lastEdgeID and self.lastNodeInfo then fromNode = self.lastFromNode; for j = self.lastEdgeID, #self.lastNodeInfo.visibleEdges do targetNode = NodeIDxNode[self.lastNodeInfo.visibleEdges[j].targetNode]; if targetNode then processedThisFrame = processedThisFrame + 1; if processedThisFrame > MAX_PROCESS_PER_FRAME then self.lastEdgeID = j; self.lastNodeInfo = nodeInfo; return end self.numBranches = self.numBranches + 1; b = bchs[self.numBranches]; if not b then b = MainFrame:CreateTexture(nil, "OVERLAY"); bchs[self.numBranches] = b; b:SetHeight(BRANCH_WEIGHT); b:SetTexture("Interface\\AddOns\\Narcissus\\Art\\Modules\\TalentTree\\Branch"); end x1, y1 = fromNode.x + SZH, fromNode.y - SZH; x2, y2 = targetNode.x + SZH, targetNode.y - SZH; d = sqrt( (x2-x1)*(x2-x1) + (y2-y1)*(y2-y1) ); rd = atan2(y2 - y1, x2 - x1); b:SetWidth(d); b:ClearAllPoints(); if fromNode.isLeft then b:SetPoint("CENTER", main, "TOPLEFT", (x1+x2)*0.5, (y1+y2)*0.5); else b:SetPoint("CENTER", main, "TOPLEFT", (x1+x2)*0.5, (y1+y2)*0.5); end b:SetRotation(rd); b:Show(); SetBranchColor(b, fromNode.active and targetNode.active); end end self.fromNodeIndex = self.fromNodeIndex + 1; self.lastEdgeID = nil; self.lastNodeInfo = nil; self.lastFromNode = nil; if self.fromNodeIndex > self.numNodes then break end end nodeID = MainFrame.nodeIDs[self.fromNodeIndex]; nodeInfo = MainFrame:GetAndCacheNodeInfo(nodeID); if nodeInfo then fromNode = NodeIDxNode[nodeID]; if fromNode then --print(nodeID) --notFound = false; for j, edgeVisualInfo in ipairs(nodeInfo.visibleEdges) do targetNode = NodeIDxNode[edgeVisualInfo.targetNode]; if targetNode then notFound = false; processedThisFrame = processedThisFrame + 1; if processedThisFrame > MAX_PROCESS_PER_FRAME then self.lastEdgeID = j; self.lastNodeInfo = nodeInfo; self.lastFromNode = fromNode; return end self.numBranches = self.numBranches + 1; b = bchs[self.numBranches]; if not b then b = MainFrame:CreateTexture(nil, "OVERLAY"); bchs[self.numBranches] = b; b:SetHeight(BRANCH_WEIGHT); b:SetTexture("Interface\\AddOns\\Narcissus\\Art\\Modules\\TalentTree\\Branch"); end x1, y1 = fromNode.x + SZH, fromNode.y - SZH; x2, y2 = targetNode.x + SZH, targetNode.y - SZH; d = sqrt( (x2-x1)*(x2-x1) + (y2-y1)*(y2-y1) ); rd = atan2(y2 - y1, x2 - x1); b:SetWidth(d); b:ClearAllPoints(); if fromNode.isLeft then b:SetPoint("CENTER", main, "TOPLEFT", (x1+x2)*0.5, (y1+y2)*0.5); else b:SetPoint("CENTER", main, "TOPLEFT", (x1+x2)*0.5, (y1+y2)*0.5); end b:SetRotation(rd); b:Show(); SetBranchColor(b, fromNode.active and targetNode.active); end end end end self.fromNodeIndex = self.fromNodeIndex + 1; end if notFound then self:SetScript("OnUpdate", nil); --print("Branches: "..self.numBranches); end end local function BranchUpdater_OnUpdate_OneFrame(self, elapsed) for i = 1, #Branches do Branches[i]:Hide(); Branches[i]:ClearAllPoints(); end local nodeID; local nodeInfo; local fromNode, targetNode; local b; local x1, y1, x2, y2, d, rd; local bchs = Branches; local SZH = BUTTON_SIZE_HALF; local main = MainFrame; while self.fromNodeIndex <= self.numNodes do nodeID = MainFrame.nodeIDs[self.fromNodeIndex]; nodeInfo = MainFrame:GetAndCacheNodeInfo(nodeID); if nodeInfo then fromNode = NodeIDxNode[nodeID]; if fromNode then for j, edgeVisualInfo in ipairs(nodeInfo.visibleEdges) do targetNode = NodeIDxNode[edgeVisualInfo.targetNode]; if targetNode then self.numBranches = self.numBranches + 1; b = bchs[self.numBranches]; if not b then b = MainFrame:CreateTexture(nil, "OVERLAY"); bchs[self.numBranches] = b; b:SetHeight(BRANCH_WEIGHT); b:SetTexture("Interface\\AddOns\\Narcissus\\Art\\Modules\\TalentTree\\Branch"); end x1, y1 = fromNode.x + SZH, fromNode.y - SZH; x2, y2 = targetNode.x + SZH, targetNode.y - SZH; d = sqrt( (x2-x1)*(x2-x1) + (y2-y1)*(y2-y1) ); rd = atan2(y2 - y1, x2 - x1); b:SetWidth(d); b:ClearAllPoints(); if fromNode.isLeft then b:SetPoint("CENTER", main, "TOPLEFT", (x1+x2)*0.5, (y1+y2)*0.5); else b:SetPoint("CENTER", main, "TOPLEFT", (x1+x2)*0.5, (y1+y2)*0.5); end b:SetRotation(rd); b:Show(); SetBranchColor(b, fromNode.active and targetNode.active); end end end end self.fromNodeIndex = self.fromNodeIndex + 1; end self:SetScript("OnUpdate", nil); end function BranchUpdater:StartUpdating() self.numBranches = 0; self.fromNodeIndex = 1; self.numNodes = #MainFrame.nodeIDs; self:SetScript("OnUpdate", BranchUpdater_OnUpdate_OneFrame); end function NarciMiniTalentTreeMixin:CreateBranches() if true then BranchUpdater:StartUpdating(); return end local nodeIDs = self.nodeIDs or C_Traits.GetTreeNodes(self.treeID); local configID = self.configID; local nodeInfo; local fromNode, targetNode; local total = 0; local bchs = Branches; local x1, y1, x2, y2, d, rd; local b; local sqrt = sqrt; local atan2 = atan2; local SZH = BUTTON_SIZE_HALF; for i, nodeID in ipairs(nodeIDs) do nodeInfo = self:GetAndCacheNodeInfo(nodeID); if nodeInfo then fromNode = NodeIDxNode[nodeID]; if fromNode then for j, edgeVisualInfo in ipairs(nodeInfo.visibleEdges) do targetNode = NodeIDxNode[edgeVisualInfo.targetNode]; if targetNode then --self:LinkNode(fromNode, targetNode); total = total + 1; b = bchs[total]; if not b then b = self:CreateTexture(nil, "OVERLAY"); bchs[total] = b; b:SetHeight(BRANCH_WEIGHT); b:SetTexture("Interface\\AddOns\\Narcissus\\Art\\Modules\\TalentTree\\Branch") end x1, y1 = fromNode.x + SZH, fromNode.y - SZH; x2, y2 = targetNode.x + SZH, targetNode.y - SZH; d = sqrt( (x2-x1)*(x2-x1) + (y2-y1)*(y2-y1) ); rd = atan2(y2 - y1, x2 - x1); b:SetWidth(d); b:SetPoint("CENTER", self, "TOPLEFT", (x1+x2)*0.5, (y1+y2)*0.5); b:SetRotation(rd); b:Show(); if fromNode.active and targetNode.active then b:SetVertexColor(0.4, 0.4, 0.4); else b:SetVertexColor(0.160, 0.160, 0.160); end end end end end end end local Events = { "TRAIT_TREE_CHANGED", "TRAIT_NODE_CHANGED", "TRAIT_NODE_CHANGED_PARTIAL", "TRAIT_NODE_ENTRY_UPDATED", "TRAIT_CONFIG_UPDATED", "ACTIVE_PLAYER_SPECIALIZATION_CHANGED", --TRAIT_NODE_CHANGED: Fires multiple times when cancel switching talent --TRAIT_TREE_CHANGED: After clicking a loadout --TRAIT_CONFIG_UPDATED: After successfully changing loadout --ACTIVE_PLAYER_SPECIALIZATION_CHANGED: followed by TRAIT_CONFIG_UPDATED } function NarciMiniTalentTreeMixin:OnShow() for i, event in ipairs(Events) do self:RegisterEvent(event); end if self.Init then self:Init(); end end function NarciMiniTalentTreeMixin:OnEvent(event, ...) if event == "TRAIT_TREE_CHANGED" then elseif event == "TRAIT_NODE_CHANGED" then elseif event == "TRAIT_NODE_CHANGED_PARTIAL" then elseif event == "TRAIT_NODE_ENTRY_UPDATED" then elseif event == "TRAIT_CONFIG_UPDATED" then After(0, function() self:ShowActiveBuild(); --if ClassTalentFrame then -- ClassTalentFrame:OnEvent("ACTIVE_PLAYER_SPECIALIZATION_CHANGED"); --end end) elseif event == "ACTIVE_PLAYER_SPECIALIZATION_CHANGED" then if self.SpecSelect:IsShown() then self.SpecSelect:CloseFrame(); end DataProvider:UpdateSpecInfo(); self:ShowActiveBuild(); end end function NarciMiniTalentTreeMixin:IsInspecting() return self.inspectUnit ~= nil end function NarciMiniTalentTreeMixin:GetInspectUnit() return self.inspectUnit; end function NarciMiniTalentTreeMixin:AnchorToInspectFrame() self.anchor = "inspectframe"; self:ClearAllPoints(); self:SetPoint("TOPLEFT", InspectFrame, "TOPRIGHT", 4, 0); end function NarciMiniTalentTreeMixin:AnchorToPaperDoll() self.anchor = "paperdoll"; self:ClearAllPoints(); self:SetPoint("TOPLEFT", PaperDollFrame, "TOPRIGHT", 4, 0); end --[[ function ClassTalentTalentsTabMixin:GetSpecID() if self:IsInspecting() then return GetInspectSpecialization(self:GetInspectUnit()); end return PlayerUtil.GetCurrentSpecID(); end ]] NarciTalentTreeLoadoutButtonMixin = {}; function NarciTalentTreeLoadoutButtonMixin:OnEnter() if not self.selected then self.ButtonText:SetTextColor(0.92, 0.92, 0.92); end if self.configID then OnEnterDelay:WatchButton(self); end end function NarciTalentTreeLoadoutButtonMixin:OnEnterCallback() if self.configID then MainFrame:ShowConfig(self.configID, true); end end function NarciTalentTreeLoadoutButtonMixin:OnLeave() if not self.selected then self.ButtonText:SetTextColor(0.5, 0.5, 0.5); end OnEnterDelay:ClearButton(); end function NarciTalentTreeLoadoutButtonMixin:OnClick() if self.selected then else if self.configID then LoadingBarUtil:SetFromLoadoutToggle(MainFrame.LoadoutToggle); local autoApply = true; local result = C_ClassTalents.LoadConfig(self.configID, autoApply); if result ~= 0 then local currentSpecID = DataProvider:GetCurrentSpecID(); if currentSpecID then C_ClassTalents.UpdateLastSelectedSavedConfigID(currentSpecID, self.configID); end end end end LoadoutUtil:HideList(); end function NarciTalentTreeLoadoutButtonMixin:SetConfigID(configID) self.configID = configID; local info = C_Traits.GetConfigInfo(configID); if info and info.name then self.ButtonText:SetText(info.name); self.Underline:SetWidth(self.ButtonText:GetWrappedWidth()); else end end function LoadoutUtil:IsFocused() if MainFrame.LoadoutToggle:IsMouseOver(12, -12, 0, 0) then return true end for i = 1, #self.buttons do if self.buttons[i]:IsShown() then if self.buttons[i]:IsMouseOver() then return true end else return end end end function LoadoutUtil:Init() self.buttons = {}; local function LoadoutDropdown_OnShow(f) f:RegisterEvent("GLOBAL_MOUSE_DOWN"); end local function LoadoutDropdown_OnEvent(f, event, ...) if not self:IsFocused() then f:UnregisterEvent("GLOBAL_MOUSE_DOWN"); self:HideList(); end end self.container = MainFrame.LoadoutDropdown; self.container:SetScript("OnShow", LoadoutDropdown_OnShow); self.container:SetScript("OnEvent", LoadoutDropdown_OnEvent); self.font = MainFrame.LoadoutToggle.ButtonText:GetFont(); self.buttonHeight = 32*PIXEL; self.Init = nil; end function LoadoutUtil:UpdateList() for i = 1, #self.buttons do self.buttons[i]:Hide(); end local specID = DataProvider:GetCurrentSpecID(); local configs = C_ClassTalents.GetConfigIDsBySpecID(specID); local button; for i = 1, #configs do button = self.buttons[i]; if not button then button = CreateFrame("Button", nil, self.container, "NarciTalentTreeLoadoutButtonTemplate"); self.buttons[i] = button; button.ButtonText:SetFont(self.font, FONT_HEIGHT, ""); button:SetPoint("TOP", MainFrame.LoadoutToggle, "TOP", 0, self.buttonHeight*( - i)); button.Underline:SetHeight(2*PIXEL); button.Underline:SetPoint("TOPLEFT", button.ButtonText, "BOTTOMLEFT", 0, -2*PIXEL); end button:SetConfigID(configs[i]); button:Show(); end if button then local top = self.container:GetTop(); local bottom = button:GetBottom(); self.container:SetHeight(top - bottom + 8); if self.activeConfigID then self:SetActiveConfigID(self.activeConfigID); end else self.container:SetHeight(64); end end function LoadoutUtil.ShowFrame_OnUpdate(f, elapsed) f.alpha = f.alpha + elapsed * 4; if f.alpha > 1 then f.alpha = 1; f:SetScript("OnUpdate", nil); end f:SetAlpha(f.alpha); end function LoadoutUtil:ShowList() if self.Init then self:Init(); end self:UpdateList(); if self.activeButton then self.activeButton.Underline.AnimIn:Stop(); self.activeButton.Underline.AnimIn:Play(); end self.container:Show(); self.container.alpha = 0; self.container:SetScript("OnUpdate", self.ShowFrame_OnUpdate); MainFrame.LoadoutToggle.ButtonText:SetText("Loadout"); MainFrame.LoadoutToggle.Arrow:SetRotation(math.pi); end function LoadoutUtil:HideList() self.container:Hide(); MainFrame.LoadoutToggle.Arrow:SetRotation(0); if not LoadingBarUtil:IsBarVisible() then --MainFrame.LoadoutToggle.ButtonText:SetText(DataProvider:GetActiveLoadoutName()); MainFrame:ShowActiveBuild(); end end function LoadoutUtil:ToggleList() if self.container and self.container:IsShown() then self:HideList(); else self:ShowList(); end end function LoadoutUtil:SetActiveConfigID(configID) self.activeButton = nil; self.activeConfigID = configID; if self.buttons then for i, b in ipairs(self.buttons) do if b.configID == configID then b.Underline:Show(); b.selected = true; b.ButtonText:SetTextColor(1, 0.82, 0); self.activeButton = b; else b.Underline:Hide(); b.selected = false; b:OnLeave(); end end end end NarciTalentTreePvPFrameMixin = {}; function NarciTalentTreePvPFrameMixin:OnShow() self:RegisterEvent("PLAYER_PVP_TALENT_UPDATE"); self:RegisterEvent("PLAYER_SPECIALIZATION_CHANGED"); self:Update(); end function NarciTalentTreePvPFrameMixin:OnEvent() self:Update(); end function NarciTalentTreePvPFrameMixin:IsWarModeActive() return C_PvP.IsWarModeDesired() or C_PvP.IsWarModeActive() end function NarciTalentTreePvPFrameMixin:Update() self:Init(); local talentID, talentInfo; if MainFrame:IsInspecting() then local unit = MainFrame:GetInspectUnit(); for i = 1, 3 do talentID = C_SpecializationInfo.GetInspectSelectedPvpTalent(unit, i); self.slots[i]:SetPvPTalent(talentID, unit); self.slots[i]:Show(); self.slots[i].isInspecting = true; self.slots[i]:SetBorderColor(true); end for i = 4, 6 do talentInfo = C_SpecializationInfo.GetPvpTalentSlotInfo(i - 3); self.slots[i]:SetPvPTalent((talentInfo ~= nil and talentInfo.selectedTalentID) or nil); self.slots[i]:Show(); self.slots[i].isInspecting = false; end else for i = 4, 6 do self.slots[i]:Hide(); self.slots[i].isInspecting = false; end for i = 1, 3 do talentInfo = C_SpecializationInfo.GetPvpTalentSlotInfo(i); self.slots[i]:SetPvPTalent((talentInfo ~= nil and talentInfo.selectedTalentID) or nil); self.slots[i]:Show(); self.slots[i].isInspecting = false; self.slots[i]:SetBorderColor(false); end end end function NarciTalentTreePvPFrameMixin:Init() if not self.slots then self.slots = {}; local slot; for i = 1, 6 do -- 4,5,6 are for inspection (1-3 for the inspectee, 4-6 for me) slot = CreateFrame("Button", nil, self, "NarciTalentTreeNodeTemplate"); self.slots[i] = slot; slot.Icon:SetSize(ICON_SIZE, ICON_SIZE); slot:SetSize(BUTTON_SIZE, BUTTON_SIZE); slot:SetNodeType(0, 1); slot.Icon:SetTexCoord(0.0625, 0.9375, 0.0625, 0.9375); slot.Symbol:SetVertexColor(0.160, 0.160, 0.160); slot:SetScript("OnEnter", ClassTalentTooltipUtil.SetFromPvPButton); if i <= 3 then slot:SetPoint("TOP", self, "TOP", 0, -HEADER_SIZE -(i + 1) * BUTTON_SIZE); slot.index = i; else slot:SetPoint("TOP", self, "TOP", 0, -HEADER_SIZE -(i + 3) * BUTTON_SIZE); slot.index = i - 3; end end self:SetWidth(BUTTON_SIZE * 3); self.Divider:ClearAllPoints(); self.Divider:SetPoint("TOPLEFT", self, "TOPLEFT", 0, -BUTTON_SIZE/2 -HEADER_SIZE); self.Divider:SetPoint("BOTTOMLEFT", self, "BOTTOMLEFT", 0, BUTTON_SIZE/2); end end function NarciTalentTreePvPFrameMixin:Toggle() if self:IsShown() then self:Hide(); MainFrame.PvPTalentToggle.Arrow:SetTexCoord(0, 0.25, 0.5, 1); else self:Show(); MainFrame.PvPTalentToggle.Arrow:SetTexCoord(0.25, 0, 0.5, 1); end LayoutUtil:UpdateFrameSize(); end if not addon.IsDragonflight() then return end; local ENABLE_INSPECT = false; local ENABLE_PAPERDOLL = false; local HookUtil = {}; function HookUtil:HookInpsectFrame() if self.inspectFrameHooked then return end; local InspectFrame = _G["InspectFrame"]; if InspectFrame then self.inspectFrameHooked = true; InspectFrame:HookScript("OnShow", function() if ENABLE_INSPECT and InspectFrame.unit then MainFrame:AnchorToInspectFrame(); MainFrame:Show(); MainFrame:ShowInspecting(InspectFrame.unit); end end); InspectFrame:HookScript("OnHide", function() if ENABLE_INSPECT then MainFrame:Hide(); end end); else if self.inspectFuncHooked then return end; self.inspectFuncHooked = true; hooksecurefunc("InspectUnit", function() if not self.inspectFrameHooked then self.inspectFrameHooked = true; InspectFrame = _G["InspectFrame"]; if not InspectFrame then return end; InspectFrame:HookScript("OnShow", function() if ENABLE_INSPECT and InspectFrame.unit then MainFrame:AnchorToInspectFrame(); MainFrame:Show(); MainFrame:ShowInspecting(InspectFrame.unit); end end); InspectFrame:HookScript("OnHide", function() if ENABLE_INSPECT then MainFrame:Hide(); end end); end end); end end function HookUtil:HookPaperDoll() if self.paperdollHooked then return end; self.paperdollHooked = true; local PaperDoll = _G["PaperDollFrame"]; PaperDoll:HookScript("OnShow", function() if ENABLE_PAPERDOLL then MainFrame:AnchorToPaperDoll(); MainFrame:SetInspectionMode(false); MainFrame:Show(); MainFrame:ShowActiveBuild(); end end); PaperDoll:HookScript("OnHide", function() if ENABLE_PAPERDOLL then MainFrame:Hide(); end end); end do local SettingFunctions = addon.SettingFunctions; function SettingFunctions.ShowMiniTalentTreeForInspection(state, db) if state == nil then state = db["TalentTreeForInspection"]; end if state then ENABLE_INSPECT = true; HookUtil:HookInpsectFrame(); else ENABLE_INSPECT = false; if MainFrame.anchor == "inpsectframe" then MainFrame:Hide(); end end end function SettingFunctions.ShowMiniTalentTreeForPaperDoll(state, db) if state == nil then state = db["TalentTreeForPaperDoll"]; end if state then ENABLE_PAPERDOLL = true; HookUtil:HookPaperDoll(); else ENABLE_PAPERDOLL = false; if MainFrame.anchor == "paperdoll" then MainFrame:Hide(); end end end end