From 02e062d66be196ac595207641222af27c3b59053 Mon Sep 17 00:00:00 2001 From: Torsten Brendgen Date: Sat, 25 Apr 2026 17:33:32 +0200 Subject: [PATCH] delted old debug window, added new version notice window, added new features to tracker module, updated locales, and updated main addon files. --- Core/DebugWindow.lua | 486 ----------------------- Core/VersionNoticeWindow.lua | 18 +- HailMaryGuildTools.lua | 305 +++++++------- HailMaryGuildTools.toc | 3 +- Locales/deDE.lua | 3 +- Locales/enUS.lua | 3 +- Modules/RaidTimeline/RaidTimelineDBM.lua | 8 - Modules/Tracker/TrackerBridge.lua | 5 + Modules/Tracker/TrackerCore.lua | 174 +++++++- Modules/Tracker/TrackerDetection.lua | 2 +- Modules/Tracker/TrackerOptions.lua | 59 ++- Modules/Tracker/TrackerSync.lua | 16 +- 12 files changed, 401 insertions(+), 681 deletions(-) delete mode 100644 Core/DebugWindow.lua delete mode 100644 Modules/RaidTimeline/RaidTimelineDBM.lua diff --git a/Core/DebugWindow.lua b/Core/DebugWindow.lua deleted file mode 100644 index 59e7a77..0000000 --- a/Core/DebugWindow.lua +++ /dev/null @@ -1,486 +0,0 @@ -local ADDON_NAME = "HailMaryGuildTools" -local HMGT = _G[ADDON_NAME] -if not HMGT then return end - -local L = HMGT.L or LibStub("AceLocale-3.0"):GetLocale(ADDON_NAME) -local AceGUI = LibStub("AceGUI-3.0", true) - -local function GetOrderedDebugLevels() - return { "error", "info", "verbose" } -end - -local function GetOrderedDebugScopes() - local values = HMGT:GetDebugScopeOptions() or {} - local names = { "ALL" } - for scope in pairs(values) do - if scope ~= "ALL" then - names[#names + 1] = scope - end - end - table.sort(names, function(a, b) - if a == "ALL" then return true end - if b == "ALL" then return false end - return tostring(values[a] or a) < tostring(values[b] or b) - end) - return names, values -end - -local function SetFilterButtonText(buttonWidget, prefix, valueLabel) - if not buttonWidget then - return - end - buttonWidget:SetText(string.format("%s: %s", tostring(prefix or ""), tostring(valueLabel or ""))) -end - -local function AdvanceDebugLevel(step) - local levels = GetOrderedDebugLevels() - local current = HMGT:GetConfiguredDebugLevel() - local nextIndex = 1 - for index, value in ipairs(levels) do - if value == current then - nextIndex = index + (step or 1) - break - end - end - if nextIndex < 1 then - nextIndex = #levels - elseif nextIndex > #levels then - nextIndex = 1 - end - HMGT.db.profile.debugLevel = levels[nextIndex] - HMGT:RefreshDebugWindow() -end - -local function AdvanceDebugScope(step) - local scopes, labels = GetOrderedDebugScopes() - local current = (HMGT.db and HMGT.db.profile and HMGT.db.profile.debugScope) or "ALL" - local nextIndex = 1 - for index, value in ipairs(scopes) do - if value == current then - nextIndex = index + (step or 1) - break - end - end - if nextIndex < 1 then - nextIndex = #scopes - elseif nextIndex > #scopes then - nextIndex = 1 - end - HMGT.db.profile.debugScope = scopes[nextIndex] - HMGT:RefreshDebugWindow() -end - -function HMGT:SetDebugWindowMinimized(minimized) - local frame = self.debugWindow - if not frame then - return - end - - minimized = minimized and true or false - self.debugWindowStatus = self.debugWindowStatus or { - width = 860, - height = 340, - } - self.debugWindowStatus.minimized = minimized - - local collapsedHeight = 64 - if minimized then - self.debugWindowStatus.restoreHeight = self.debugWindowStatus.height or frame:GetHeight() or 340 - end - - local targetHeight = minimized - and collapsedHeight - or (self.debugWindowStatus.restoreHeight or self.debugWindowStatus.height or 340) - - if frame.aceWidget then - frame.aceWidget:EnableResize(not minimized) - frame.aceWidget:SetHeight(targetHeight) - else - frame:SetHeight(targetHeight) - end - - if frame.minimizeButton then - frame.minimizeButton:SetText(minimized and "+" or "-") - end - if frame.clearButton then - local buttonFrame = frame.clearButton.frame or frame.clearButton - buttonFrame:SetShown(not minimized) - end - if frame.selectButton then - local buttonFrame = frame.selectButton.frame or frame.selectButton - buttonFrame:SetShown(not minimized) - end - if frame.levelFilter then - local filterFrame = frame.levelFilter.frame or frame.levelFilter - filterFrame:SetShown(not minimized) - end - if frame.scopeFilter then - local filterFrame = frame.scopeFilter.frame or frame.scopeFilter - filterFrame:SetShown(not minimized) - end - if frame.logWidget then - frame.logWidget.frame:SetShown(not minimized) - end - if frame.scrollBG then - frame.scrollBG:SetShown(not minimized) - end - - if not minimized then - self:RefreshDebugWindow() - end -end - -function HMGT:ToggleDebugWindowMinimized() - self:SetDebugWindowMinimized(not (self.debugWindowStatus and self.debugWindowStatus.minimized)) -end - -function HMGT:EnsureDebugWindow() - if self.debugWindow then - return self.debugWindow - end - - local frameWidget - if AceGUI then - frameWidget = AceGUI:Create("Frame") - self.debugWindowStatus = self.debugWindowStatus or { - width = 860, - height = 340, - } - frameWidget:SetTitle(L["DEBUG_WINDOW_TITLE"] or "HMGT Debug Console") - frameWidget:SetStatusText(L["DEBUG_WINDOW_HINT"] or "Mouse wheel scrolls, Ctrl+A selects all, Ctrl+C copies selected text") - frameWidget:SetStatusTable(self.debugWindowStatus) - frameWidget:SetWidth(self.debugWindowStatus.width or 860) - frameWidget:SetHeight(self.debugWindowStatus.height or 340) - frameWidget:EnableResize(true) - frameWidget.frame:SetClampedToScreen(true) - frameWidget.frame:SetToplevel(true) - frameWidget.frame:SetFrameStrata("FULLSCREEN_DIALOG") - frameWidget:Hide() - end - - local frame = frameWidget and frameWidget.frame or CreateFrame("Frame", "HMGT_DebugWindow", UIParent, "BackdropTemplate") - if not frameWidget then - frame:SetSize(860, 340) - frame:SetPoint("CENTER", UIParent, "CENTER", 0, 0) - frame:SetFrameStrata("DIALOG") - frame:SetClampedToScreen(true) - frame:SetMovable(true) - frame:EnableMouse(true) - frame:RegisterForDrag("LeftButton") - frame:SetScript("OnDragStart", function(selfFrame) selfFrame:StartMoving() end) - frame:SetScript("OnDragStop", function(selfFrame) selfFrame:StopMovingOrSizing() end) - frame:SetBackdrop({ - bgFile = "Interface\\DialogFrame\\UI-DialogBox-Background-Dark", - edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border", - edgeSize = 12, - insets = { left = 3, right = 3, top = 3, bottom = 3 }, - }) - frame:SetBackdropColor(0.05, 0.05, 0.06, 0.95) - frame:SetBackdropBorderColor(0.35, 0.55, 0.85, 1) - frame:Hide() - - local title = frame:CreateFontString(nil, "OVERLAY", "GameFontNormalLarge") - title:SetPoint("TOPLEFT", frame, "TOPLEFT", 14, -12) - title:SetText(L["DEBUG_WINDOW_TITLE"] or "HMGT Debug Console") - frame.title = title - - local closeButton = CreateFrame("Button", nil, frame, "UIPanelCloseButton") - closeButton:SetPoint("TOPRIGHT", frame, "TOPRIGHT", -5, -5) - frame.closeButton = closeButton - - local minimizeButton = CreateFrame("Button", nil, frame, "UIPanelButtonTemplate") - minimizeButton:SetSize(22, 20) - minimizeButton:SetPoint("TOPRIGHT", closeButton, "TOPLEFT", -2, 0) - minimizeButton:SetText((self.debugWindowStatus and self.debugWindowStatus.minimized) and "+" or "-") - minimizeButton:SetScript("OnClick", function() - HMGT:ToggleDebugWindowMinimized() - end) - frame.minimizeButton = minimizeButton - end - - frame.aceWidget = frameWidget - - if frameWidget and AceGUI then - local content = frameWidget.content - - local minimizeButton = AceGUI:Create("Button") - minimizeButton:SetText((self.debugWindowStatus and self.debugWindowStatus.minimized) and "+" or "-") - minimizeButton:SetWidth(24) - minimizeButton:SetCallback("OnClick", function() - HMGT:ToggleDebugWindowMinimized() - end) - minimizeButton.frame:SetParent(frame) - minimizeButton.frame:ClearAllPoints() - minimizeButton.frame:SetPoint("TOPRIGHT", frame, "TOPRIGHT", -34, -4) - minimizeButton.frame:SetHeight(20) - minimizeButton.frame:Show() - frame.minimizeButton = minimizeButton - - local clearButton = AceGUI:Create("Button") - clearButton:SetText(L["OPT_DEBUG_CLEAR"] or "Clear log") - clearButton:SetWidth(120) - clearButton:SetCallback("OnClick", function() - HMGT:ClearDebugLog() - end) - clearButton.frame:SetParent(content) - clearButton.frame:ClearAllPoints() - clearButton.frame:SetPoint("TOPRIGHT", content, "TOPRIGHT", 0, -2) - clearButton.frame:Show() - frame.clearButton = clearButton - - local selectButton = AceGUI:Create("Button") - selectButton:SetText(L["OPT_DEBUG_SELECT_ALL"] or "Select all") - selectButton:SetWidth(120) - selectButton:SetCallback("OnClick", function() - if frame.editBox then - frame.editBox:SetFocus() - frame.editBox:HighlightText(0) - end - end) - selectButton.frame:SetParent(content) - selectButton.frame:ClearAllPoints() - selectButton.frame:SetPoint("TOPRIGHT", clearButton.frame, "TOPLEFT", -6, 0) - selectButton.frame:Show() - frame.selectButton = selectButton - - local levelFilter = AceGUI:Create("Button") - levelFilter:SetWidth(150) - levelFilter:SetCallback("OnClick", function() - AdvanceDebugLevel(1) - end) - levelFilter.frame:SetParent(content) - levelFilter.frame:ClearAllPoints() - levelFilter.frame:SetPoint("TOPLEFT", content, "TOPLEFT", 0, 0) - levelFilter.frame:Show() - frame.levelFilter = levelFilter - - local scopeFilter = AceGUI:Create("Button") - scopeFilter:SetWidth(180) - scopeFilter:SetCallback("OnClick", function() - AdvanceDebugScope(1) - end) - scopeFilter.frame:SetParent(content) - scopeFilter.frame:ClearAllPoints() - scopeFilter.frame:SetPoint("TOPLEFT", levelFilter.frame, "TOPRIGHT", 8, 0) - scopeFilter.frame:Show() - frame.scopeFilter = scopeFilter - - local logWidget = AceGUI:Create("MultiLineEditBox") - logWidget:SetLabel("") - logWidget:DisableButton(true) - logWidget:SetNumLines(18) - logWidget:SetText("") - logWidget.frame:SetParent(content) - logWidget.frame:ClearAllPoints() - logWidget.frame:SetPoint("TOPLEFT", content, "TOPLEFT", 0, -54) - logWidget.frame:SetPoint("BOTTOMRIGHT", content, "BOTTOMRIGHT", 0, 0) - logWidget.frame:Show() - logWidget:SetCallback("OnTextChanged", function() - HMGT:RefreshDebugWindow() - end) - logWidget.editBox:SetScript("OnKeyDown", function(selfBox, key) - if IsControlKeyDown() and (key == "A" or key == "a") then - selfBox:HighlightText(0) - end - end) - frame.logWidget = logWidget - frame.editBox = logWidget.editBox - frame.scrollFrame = logWidget.scrollFrame - - self.debugWindow = frame - self:SetDebugWindowMinimized(self.debugWindowStatus and self.debugWindowStatus.minimized) - return frame - end - - local clearButton = CreateFrame("Button", nil, frame, "UIPanelButtonTemplate") - clearButton:SetSize(90, 22) - clearButton:SetPoint("TOPRIGHT", frame, "TOPRIGHT", -30, -6) - clearButton:SetText(L["OPT_DEBUG_CLEAR"] or "Clear log") - clearButton:SetScript("OnClick", function() - HMGT:ClearDebugLog() - end) - frame.clearButton = clearButton - - local selectButton = CreateFrame("Button", nil, frame, "UIPanelButtonTemplate") - selectButton:SetSize(90, 22) - selectButton:SetPoint("TOPRIGHT", clearButton, "TOPLEFT", -6, 0) - selectButton:SetText(L["OPT_DEBUG_SELECT_ALL"] or "Select all") - selectButton:SetScript("OnClick", function() - if frame.editBox then - frame.editBox:SetFocus() - frame.editBox:HighlightText(0) - end - end) - frame.selectButton = selectButton - - local scopeFilter = CreateFrame("Frame", nil, frame) - scopeFilter:SetSize(170, 22) - scopeFilter:SetPoint("TOPLEFT", frame, "TOPLEFT", 16, -8) - frame.scopeFilter = scopeFilter - - local scrollBG = CreateFrame("Frame", nil, frame, "BackdropTemplate") - scrollBG:SetBackdrop({ - bgFile = "Interface\\Tooltips\\UI-Tooltip-Background", - edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border", - edgeSize = 16, - insets = { left = 4, right = 3, top = 4, bottom = 3 }, - }) - scrollBG:SetBackdropColor(0, 0, 0, 0.95) - scrollBG:SetBackdropBorderColor(0.4, 0.4, 0.4, 1) - scrollBG:SetPoint("TOPLEFT", frame, "TOPLEFT", 14, -36) - scrollBG:SetPoint("BOTTOMRIGHT", frame, "BOTTOMRIGHT", -30, 14) - frame.scrollBG = scrollBG - - local scrollFrame = CreateFrame("ScrollFrame", nil, scrollBG, "UIPanelScrollFrameTemplate") - scrollFrame:SetPoint("TOPLEFT", scrollBG, "TOPLEFT", 6, -6) - scrollFrame:SetPoint("BOTTOMRIGHT", scrollBG, "BOTTOMRIGHT", -27, 4) - scrollFrame:EnableMouseWheel(true) - scrollFrame:SetScript("OnMouseWheel", function(selfMsg, delta) - if delta > 0 then - selfMsg:SetVerticalScroll(math.max(0, selfMsg:GetVerticalScroll() - 42)) - else - selfMsg:SetVerticalScroll(selfMsg:GetVerticalScroll() + 42) - end - end) - frame.scrollFrame = scrollFrame - - local editBox = CreateFrame("EditBox", nil, scrollFrame) - editBox:SetMultiLine(true) - editBox:SetAutoFocus(false) - editBox:SetFontObject(ChatFontNormal) - editBox:SetWidth(780) - editBox:SetTextInsets(6, 6, 6, 6) - editBox:EnableMouse(true) - editBox:SetScript("OnEscapePressed", function(selfBox) - selfBox:ClearFocus() - end) - editBox:SetScript("OnKeyDown", function(selfBox, key) - if IsControlKeyDown() and (key == "A" or key == "a") then - selfBox:HighlightText(0) - end - end) - editBox:SetScript("OnTextChanged", function(selfBox, userInput) - if userInput then - HMGT:RefreshDebugWindow() - else - selfBox:SetCursorPosition(selfBox:GetNumLetters()) - selfBox:SetHeight(math.max(scrollFrame:GetHeight(), HMGT:GetDebugWindowTextHeight(frame, selfBox:GetText()) + 16)) - scrollFrame:UpdateScrollChildRect() - end - end) - editBox:SetScript("OnMouseUp", function(selfBox) - selfBox:SetFocus() - end) - scrollFrame:SetScrollChild(editBox) - frame.editBox = editBox - - local measureText = frame:CreateFontString(nil, "ARTWORK", "ChatFontNormal") - measureText:SetJustifyH("LEFT") - measureText:SetJustifyV("TOP") - if measureText.SetSpacing then - measureText:SetSpacing(2) - end - measureText:SetWidth(768) - frame.measureText = measureText - - self.debugWindow = frame - self:SetDebugWindowMinimized(self.debugWindowStatus and self.debugWindowStatus.minimized) - return frame -end - -function HMGT:GetDebugWindowTextHeight(frame, text) - if not frame or not frame.measureText then - return 0 - end - - local width = 768 - if frame.editBox then - width = math.max(1, (frame.editBox:GetWidth() or width) - 12) - end - frame.measureText:SetWidth(width) - frame.measureText:SetText(text or "") - return frame.measureText:GetStringHeight() -end - -function HMGT:RefreshDebugWindow() - local frame = self:EnsureDebugWindow() - if not frame then - return - end - - local filtered = self:GetFilteredDebugBuffer() or self.debugBuffer or {} - local text = table.concat(filtered, "\n") - if frame.logWidget and frame.editBox then - if frame.levelFilter then - local levelOptions = self:GetDebugLevelOptions() - SetFilterButtonText(frame.levelFilter, L["OPT_DEBUG_LEVEL"] or "Level", levelOptions[self:GetConfiguredDebugLevel()]) - end - if frame.scopeFilter then - local scopeOptions = self:GetDebugScopeOptions() - local currentScope = (self.db and self.db.profile and self.db.profile.debugScope) or "ALL" - SetFilterButtonText(frame.scopeFilter, L["OPT_DEBUG_SCOPE"] or "Module", scopeOptions[currentScope] or currentScope) - end - frame.logWidget:SetText(text) - frame.editBox:SetCursorPosition(frame.editBox:GetNumLetters()) - return - end - - if not frame.editBox then - return - end - - frame.editBox:SetText(text) - frame.editBox:SetCursorPosition(#text) - frame.editBox:SetHeight(math.max(frame.scrollFrame:GetHeight(), self:GetDebugWindowTextHeight(frame, text) + 16)) - frame.scrollFrame:SetVerticalScroll(math.max(0, frame.editBox:GetHeight() - frame.scrollFrame:GetHeight())) -end - -function HMGT:UpdateDebugWindowVisibility() - if self.db and self.db.profile then - self.db.profile.debug = false - end - local frame = self.debugWindow - if not frame then - return - end - local widget = frame.aceWidget - if widget then - widget:Hide() - else - frame:Hide() - end -end - -function HMGT:ClearDebugLog() - wipe(self.debugBuffer) - if self.debugWindow and self.debugWindow.logWidget then - self.debugWindow.logWidget:SetText("") - self.debugWindow.editBox:SetCursorPosition(0) - self.debugWindow.scrollFrame:SetVerticalScroll(0) - return - end - if self.debugWindow and self.debugWindow.editBox then - self.debugWindow.editBox:SetText("") - self.debugWindow.scrollFrame:SetVerticalScroll(0) - end -end - -function HMGT:ToggleDebugWindowShortcut() - if self.db and self.db.profile then - self.db.profile.debug = false - end - local frame = self.debugWindow - if not frame then - return - end - local widget = frame.aceWidget - if widget then - widget:Hide() - else - frame:Hide() - end -end - -function HMGT:DumpDebugLog(maxLines) - return -end diff --git a/Core/VersionNoticeWindow.lua b/Core/VersionNoticeWindow.lua index cb86ed4..e495d63 100644 --- a/Core/VersionNoticeWindow.lua +++ b/Core/VersionNoticeWindow.lua @@ -68,12 +68,14 @@ local function GetPlayerVersionText(name) return tostring(HMGT.ADDON_VERSION or "dev"), tonumber(HMGT.PROTOCOL_VERSION) or 0, true end - local version = HMGT.peerVersions and HMGT.peerVersions[normalized] or nil - local protocol = HMGT.GetPeerProtocolVersion and HMGT:GetPeerProtocolVersion(normalized) or 0 - if version and version ~= "" then - return tostring(version), tonumber(protocol) or 0, true + local addonStatus = HMGT.GetPlayerAddonStatus and HMGT:GetPlayerAddonStatus(normalized) or nil + if addonStatus and addonStatus.mode == "hmgt" and addonStatus.version and addonStatus.version ~= "" then + return tostring(addonStatus.version), tonumber(addonStatus.protocol) or 0, true end - return nil, tonumber(protocol) or 0, false + if addonStatus and addonStatus.mode == "bridge" then + return L["VERSION_WINDOW_BRIDGE_MODE"] or "Bridge Mode", 0, true + end + return nil, tonumber(addonStatus and addonStatus.protocol) or 0, false end local function ApplyClassIcon(texture, classTag) @@ -167,7 +169,11 @@ function HMGT:RefreshVersionNoticeWindow() local versionText, protocol, hasAddon = GetPlayerVersionText(info.name) if hasAddon then row.versionText:SetText(versionText or "?") - row.versionText:SetTextColor(0.9, 0.9, 0.9, 1) + if versionText == (L["VERSION_WINDOW_BRIDGE_MODE"] or "Bridge Mode") then + row.versionText:SetTextColor(0.55, 0.82, 1, 1) + else + row.versionText:SetTextColor(0.9, 0.9, 0.9, 1) + end row.protocolText:SetText(protocol > 0 and tostring(protocol) or "-") row.protocolText:SetTextColor(0.75, 0.75, 0.75, 1) else diff --git a/HailMaryGuildTools.lua b/HailMaryGuildTools.lua index 37eec2f..1c62c4d 100644 --- a/HailMaryGuildTools.lua +++ b/HailMaryGuildTools.lua @@ -288,16 +288,20 @@ HMGT.powerTracking = { } HMGT.pendingSpellPowerCosts = {} HMGT.demoModeData = {} -HMGT.peerVersions = {} HMGT.versionWarnings = {} HMGT.versionWhisperWarnings = {} +HMGT.playerStatus = {} HMGT.debugBuffer = {} HMGT.debugBufferMax = 500 HMGT.enabledDebugScopes = { General = true, Debug = true, Comm = true, - TrackedSpells = true, + TrackerCore = true, + TrackerSync = true, + TrackerUI = true, + TrackerBridge = true, + TrackerState = true, PowerSpend = true, } HMGT.pendingReliableMessages = HMGT.pendingReliableMessages or {} @@ -311,7 +315,11 @@ local DEBUG_SCOPE_LABELS = { General = "General", Debug = "Debug", Comm = "Communication", - TrackedSpells = "Tracked Spells", + TrackerCore = "Tracker Core", + TrackerSync = "Tracker Sync", + TrackerUI = "Tracker UI", + TrackerBridge = "Tracker Bridge", + TrackerState = "Tracker State", PowerSpend = "Power Spend", RaidTimeline = "Raid Timeline", Notes = "Notes", @@ -340,8 +348,12 @@ end function HMGT:GetTrackerDebugScope(tracker) local trackerName = nil + local trackerId = nil + local trackerType = nil if type(tracker) == "table" then trackerName = tracker.name + trackerId = tonumber(tracker.id) + trackerType = tracker.trackerType if (not trackerName or trackerName == "") and tracker.id then trackerName = string.format("Tracker %s", tostring(tracker.id)) end @@ -353,7 +365,106 @@ function HMGT:GetTrackerDebugScope(tracker) if trackerName == "" then trackerName = "Tracker" end - return "Tracker: " .. trackerName + local prefix = "Tracker" + if trackerType == "group" then + prefix = "Tracker Group" + elseif trackerType == "normal" then + prefix = "Tracker Normal" + end + if trackerId then + return string.format("%s #%d: %s", prefix, trackerId, trackerName) + end + return prefix .. ": " .. trackerName +end + +function HMGT:GetPlayerStatus(playerName, create) + local normalizedName = self:NormalizePlayerName(playerName) + if not normalizedName or normalizedName == "" then + return nil + end + self.playerStatus = self.playerStatus or {} + if create then + self.playerStatus[normalizedName] = self.playerStatus[normalizedName] or {} + end + return self.playerStatus[normalizedName] +end + +function HMGT:SetPlayerVersionStatus(playerName, version, protocol, sourceTag) + local status = self:GetPlayerStatus(playerName, true) + if not status then + return nil + end + if version and version ~= "" then + status.version = tostring(version) + end + if tonumber(protocol) then + status.protocol = tonumber(protocol) + end + if sourceTag and sourceTag ~= "" then + status.versionSource = tostring(sourceTag) + end + status.mode = "hmgt" + return status +end + +function HMGT:SetPlayerBridgeStatus(playerName, sourceName) + local source = tostring(sourceName or "") + if source == "" then + return nil + end + local status = self:GetPlayerStatus(playerName, true) + if not status then + return nil + end + status.bridgeSource = source + if not status.version or status.version == "" then + status.mode = "bridge" + end + return status +end + +function HMGT:GetPlayerAddonStatus(playerName) + local status = self:GetPlayerStatus(playerName, false) + if not status then + return { + mode = "missing", + version = nil, + protocol = 0, + bridgeSource = nil, + } + end + + local version = status.version + local protocol = tonumber(status.protocol) or 0 + local bridgeSource = status.bridgeSource + local mode = status.mode + + if version and version ~= "" then + mode = "hmgt" + elseif bridgeSource and bridgeSource ~= "" then + mode = "bridge" + else + mode = "missing" + end + + return { + mode = mode, + version = version, + protocol = protocol, + bridgeSource = bridgeSource, + } +end + +function HMGT:ClearPlayerStatus(playerName) + local normalizedName = self:NormalizePlayerName(playerName) + if not normalizedName or not self.playerStatus then + return false + end + if self.playerStatus[normalizedName] then + self.playerStatus[normalizedName] = nil + return true + end + return false end function HMGT:GetStaticDebugScopeOptions() @@ -456,6 +567,10 @@ function HMGT:IsReliableCommType(msgType) end function HMGT:GetPeerProtocolVersion(playerName) + local status = self:GetPlayerStatus(playerName, false) + if status and tonumber(status.protocol) then + return tonumber(status.protocol) or 0 + end local normalizedName = self:NormalizePlayerName(playerName) local peerProtocols = self.peerProtocols or {} return tonumber(normalizedName and peerProtocols[normalizedName]) or 0 @@ -469,6 +584,7 @@ function HMGT:RememberPeerProtocolVersion(playerName, protocol) end self.peerProtocols = self.peerProtocols or {} self.peerProtocols[normalizedName] = numeric + self:SetPlayerVersionStatus(normalizedName, nil, numeric, nil) end local function ParseVersionTokens(version) @@ -747,7 +863,32 @@ function HMGT:SendDirectMessage(payload, target, prio) end function HMGT:DebugScoped(level, scope, fmt, ...) - return + local normalizedLevel = tostring(level or "info"):lower() + if not DEBUG_LEVELS[normalizedLevel] then + normalizedLevel = "info" + end + + local normalizedScope = tostring(scope or "General"):match("^%s*(.-)%s*$") + if normalizedScope == "" then + normalizedScope = "General" + end + + local ok, message = pcall(string.format, tostring(fmt or ""), ...) + if not ok then + message = tostring(fmt or "") + end + local line = string.format("%s [%s][%s] %s", date("%H:%M:%S"), string.upper(normalizedLevel), normalizedScope, tostring(message or "")) + + self.debugBuffer = self.debugBuffer or {} + self.debugBuffer[#self.debugBuffer + 1] = line + local maxLines = tonumber(self.debugBufferMax) or 500 + while #self.debugBuffer > maxLines do + table.remove(self.debugBuffer, 1) + end + + if self.debugWindow and self.debugWindow.IsShown and self.debugWindow:IsShown() and self.RefreshDebugWindow then + self:RefreshDebugWindow() + end end function HMGT:Debug(fmt, ...) @@ -764,7 +905,7 @@ end function HMGT:RegisterPeerVersion(playerName, version, protocol, sourceTag) if not playerName then return end - self.peerVersions[playerName] = version + self:SetPlayerVersionStatus(playerName, version, protocol, sourceTag) self:RememberPeerProtocolVersion(playerName, protocol) if self.versionNoticeWindow and self.versionNoticeWindow.IsShown and self.versionNoticeWindow:IsShown() and self.RefreshVersionNoticeWindow then self:RefreshVersionNoticeWindow() @@ -798,7 +939,7 @@ function HMGT:RegisterPeerVersion(playerName, version, protocol, sourceTag) tostring(playerName), table.concat(details, " | ")) self:Print("|cffff5555HMGT|r " .. text) self:ShowVersionMismatchPopup(playerName, table.concat(details, " | "), sourceTag) - self:Debug("info", "Version mismatch %s via=%s %s", tostring(playerName), tostring(sourceTag or "?"), table.concat(details, " | ")) + self:DebugScoped("info", "TrackerCore", "Version mismatch %s via=%s %s", tostring(playerName), tostring(sourceTag or "?"), table.concat(details, " | ")) end end @@ -1091,7 +1232,7 @@ function HMGT:LogTrackedSpellCast(playerName, spellEntry, details) self:DebugScoped( "verbose", - "TrackedSpells", + "TrackerCore", "%s -> %s von %s, %s", GetTrackedSpellCategoryLabel(spellEntry), GetSpellDebugLabel(spellEntry.spellId), @@ -1699,22 +1840,9 @@ function HMGT:MigrateProfileSettings() if #p.trackers == 0 and p.trackerModelVersion ~= TRACKER_MODEL_VERSION then p.trackers = { - self:CreateTrackerConfig(1, CopyTrackerFields({ - name = L["IT_NAME"] or "Interrupts", - trackerType = "normal", - categories = { "interrupt" }, - }, p.interruptTracker or {})), - self:CreateTrackerConfig(2, CopyTrackerFields({ - name = L["RCD_NAME"] or "Raid Cooldowns", - trackerType = "normal", - categories = { "raid" }, - }, p.raidCooldownTracker or {})), - self:CreateTrackerConfig(3, CopyTrackerFields({ - name = L["GCD_NAME"] or "Cooldowns", - trackerType = "group", - categories = { "defensive", "offensive", "tank", "healing", "utility", "cc", "lust" }, - showChargesOnIcon = true, - }, p.groupCooldownTracker or {})), + self:BuildTrackerConfigFromPreset("interruptTracker", 1, CopyTrackerFields({}, p.interruptTracker or {})), + self:BuildTrackerConfigFromPreset("raidCooldownTracker", 2, CopyTrackerFields({}, p.raidCooldownTracker or {})), + self:BuildTrackerConfigFromPreset("groupCooldownTracker", 3, CopyTrackerFields({}, p.groupCooldownTracker or {})), } end @@ -1732,11 +1860,7 @@ function HMGT:MigrateProfileSettings() end end if #normalizedTrackers == 0 then - normalizedTrackers[1] = self:CreateTrackerConfig(1, { - name = L["IT_NAME"] or "Interrupts", - trackerType = "normal", - categories = { "interrupt" }, - }) + normalizedTrackers[1] = self:BuildTrackerConfigFromPreset("interruptTracker", 1) end p.trackers = normalizedTrackers p.trackerModelVersion = TRACKER_MODEL_VERSION @@ -2648,7 +2772,7 @@ function HMGT:OnGroupRosterUpdate() if not validPlayers[name] then self.playerData[name] = nil self:ClearTrackerStateForPlayer(name) - self.peerVersions[name] = nil + self:ClearPlayerStatus(name) self.versionWarnings[name] = nil if self.peerProtocols then self.peerProtocols[name] = nil @@ -3040,127 +3164,6 @@ function HMGT:TestMode() self:Print(L["TEST_MODE_ACTIVE"]) end -function HMGT:GetDemoEntries(trackerKey, database, settings) - local pool = {} - local poolByClass = {} - for _, entry in ipairs(database) do - if settings.enabledSpells[entry.spellId] ~= false then - pool[#pool + 1] = entry - for _, cls in ipairs(entry.classes or {}) do - poolByClass[cls] = poolByClass[cls] or {} - poolByClass[cls][#poolByClass[cls] + 1] = entry - end - end - end - if #pool == 0 then return {} end - - local classKeys = {} - for cls in pairs(poolByClass) do - classKeys[#classKeys + 1] = cls - end - if #classKeys == 0 then classKeys[1] = "WARRIOR" end - - local count = settings.showBar and math.min(8, #pool) or math.min(12, #pool) - local names = { "Alice", "Bob", "Clara", "Duke", "Elli", "Fynn", "Gina", "Hektor", "Ivo", "Jana", "Kira", "Lio" } - - local spellIds = {} - for _, e in ipairs(pool) do spellIds[#spellIds + 1] = tostring(e.spellId) end - table.sort(spellIds) - local signature = table.concat(spellIds, ",") .. "|" .. tostring(settings.showBar and 1 or 0) .. "|" .. tostring(count) - - local now = GetTime() - local cache = self.demoModeData[trackerKey] - if (not cache) or (cache.signature ~= signature) or (not cache.entries) or (#cache.entries ~= count) then - local entries = {} - for i = 1, count do - local cls = classKeys[math.random(1, #classKeys)] - local classPool = poolByClass[cls] - local spell = (classPool and classPool[math.random(1, #classPool)]) or pool[math.random(1, #pool)] - local duration = math.max( - 1, - tonumber(HMGT_SpellData.GetBaseCooldown and HMGT_SpellData.GetBaseCooldown(spell)) or tonumber(spell.cooldown) or 60 - ) - local playerName = names[((i - 1) % #names) + 1] - -- start offset so demo entries do not all tick in sync - local offset = math.random() * math.min(duration * 0.85, duration - 0.1) - entries[#entries + 1] = { - playerName = playerName, - class = cls or ((spell.classes and spell.classes[1]) or "WARRIOR"), - spellEntry = spell, - total = duration, - cycleStart = now - offset, - currentCharges = nil, - maxCharges = nil, - } - end - cache = { signature = signature, entries = entries } - self.demoModeData[trackerKey] = cache - end - - local out = {} - for _, e in ipairs(cache.entries) do - local total = math.max(1, tonumber(e.total) or 1) - local elapsed = math.max(0, now - (e.cycleStart or now)) - local phase = math.fmod(elapsed, total) - local rem = total - phase - -- show zero briefly at cycle boundary, then restart immediately - if elapsed > 0 and phase < 0.05 then rem = 0 end - out[#out + 1] = { - playerName = e.playerName, - class = e.class, - spellEntry = e.spellEntry, - remaining = rem, - total = total, - currentCharges = e.currentCharges, - maxCharges = e.maxCharges, - } - end - - return out -end - -function HMGT:GetOwnTestEntries(database, settings, cooldownInfoOpts) - local entries = {} - local enabledSpells = settings and settings.enabledSpells or {} - local playerName = self:NormalizePlayerName(UnitName("player")) or "Player" - local classToken = select(2, UnitClass("player")) - if not classToken then - return entries, playerName - end - - local specIdx = GetSpecialization() - local lookupSpec = (specIdx and specIdx > 0) and specIdx or 0 - local talents = (self.playerData[playerName] and self.playerData[playerName].talents) or {} - local spells = HMGT_SpellData.GetSpellsForSpec(classToken, lookupSpec, database or {}) - - for _, spellEntry in ipairs(spells) do - if enabledSpells[spellEntry.spellId] ~= false then - local remaining, total, curCharges, maxCharges = self:GetCooldownInfo(playerName, spellEntry.spellId, cooldownInfoOpts) - local effectiveCd = HMGT_SpellData.GetEffectiveCooldown(spellEntry, talents) - local isAvailabilitySpell = self:IsAvailabilitySpell(spellEntry) - local spellKnown = self:IsTrackedSpellKnownForPlayer(playerName, spellEntry.spellId) - local hasPartialCharges = (tonumber(maxCharges) or 0) > 0 - and (tonumber(curCharges) or tonumber(maxCharges) or 0) < (tonumber(maxCharges) or 0) - local hasActiveCd = ((remaining or 0) > 0) or hasPartialCharges - local hasAvailabilityState = isAvailabilitySpell and self:HasAvailabilityState(playerName, spellEntry.spellId) - - if spellKnown or hasActiveCd or hasAvailabilityState then - entries[#entries + 1] = { - playerName = playerName, - class = classToken, - spellEntry = spellEntry, - remaining = remaining, - total = total > 0 and total or effectiveCd, - currentCharges = curCharges, - maxCharges = maxCharges, - } - end - end - end - - return entries, playerName -end - -- ═══════════════════════════════════════════════════════════════ -- HILFSFUNKTIONEN -- ═══════════════════════════════════════════════════════════════ diff --git a/HailMaryGuildTools.toc b/HailMaryGuildTools.toc index 5d46a58..bef1e5e 100644 --- a/HailMaryGuildTools.toc +++ b/HailMaryGuildTools.toc @@ -44,7 +44,7 @@ Modules\Tracker\RaidCooldownTracker\RaidCooldownTracker.lua Modules\Tracker\GroupCooldownTracker\GroupCooldownTracker.lua Modules\Tracker\InterruptTracker\InterruptSpellDatabase.lua -Modules\Tracker\RaidcooldownTracker\RaidCooldownSpellDatabase.lua +Modules\Tracker\RaidCooldownTracker\RaidCooldownSpellDatabase.lua Modules\Tracker\GroupCooldownTracker\GroupCooldownSpellDatabase.lua Modules\Tracker\TrackerManager.lua Modules\Tracker\NormalTrackerFrames.lua @@ -65,5 +65,4 @@ Modules\MapOverlay\MapOverlay.xml Modules\RaidTimeline\RaidTimelineBossAbilityData.lua Modules\RaidTimeline\RaidTimeline.lua Modules\RaidTimeline\RaidTimelineBigWigs.lua -Modules\RaidTimeline\RaidTimelineDBM.lua Modules\RaidTimeline\RaidTimelineOptions.lua diff --git a/Locales/deDE.lua b/Locales/deDE.lua index db24be8..5679e7f 100644 --- a/Locales/deDE.lua +++ b/Locales/deDE.lua @@ -22,11 +22,12 @@ L["VERSION_WINDOW_MESSAGE"] = "Hail Mary Guild Tools Versionen in deiner aktuell L["VERSION_WINDOW_DETAIL"] = "Erkannt ueber %s von %s.\n%s" L["VERSION_WINDOW_NO_MISMATCH"] = "In deiner aktuellen Gruppe wurde keine neuere HMGT-Version erkannt." L["VERSION_WINDOW_CURRENT"] = "Aktuelle Version: %s | Protokoll: %s" -L["VERSION_WINDOW_STATUS"] = "HMGT bei %d/%d Spielern erkannt" +L["VERSION_WINDOW_STATUS"] = "Addon oder Bridge bei %d/%d Spielern erkannt" L["VERSION_WINDOW_REFRESH"] = "Aktualisieren" L["VERSION_WINDOW_COLUMN_PLAYER"] = "Spieler" L["VERSION_WINDOW_COLUMN_VERSION"] = "Version" L["VERSION_WINDOW_COLUMN_PROTOCOL"] = "Protokoll" +L["VERSION_WINDOW_BRIDGE_MODE"] = "Bridge Mode" L["VERSION_WINDOW_MISSING_ADDON"] = "Addon nicht vorhanden" L["VERSION_WINDOW_LEADER_TAG"] = "(Leiter)" L["VERSION_WINDOW_ASSISTANT_TAG"] = "(Assist)" diff --git a/Locales/enUS.lua b/Locales/enUS.lua index 89da42b..068ce49 100644 --- a/Locales/enUS.lua +++ b/Locales/enUS.lua @@ -22,11 +22,12 @@ L["VERSION_WINDOW_MESSAGE"] = "Hail Mary Guild Tools versions in your current gr L["VERSION_WINDOW_DETAIL"] = "Detected via %s from %s.\n%s" L["VERSION_WINDOW_NO_MISMATCH"] = "No newer HMGT version has been detected in your current group." L["VERSION_WINDOW_CURRENT"] = "Current version: %s | Protocol: %s" -L["VERSION_WINDOW_STATUS"] = "Detected HMGT on %d/%d players" +L["VERSION_WINDOW_STATUS"] = "Detected addon or bridge on %d/%d players" L["VERSION_WINDOW_REFRESH"] = "Refresh" L["VERSION_WINDOW_COLUMN_PLAYER"] = "Player" L["VERSION_WINDOW_COLUMN_VERSION"] = "Version" L["VERSION_WINDOW_COLUMN_PROTOCOL"] = "Protocol" +L["VERSION_WINDOW_BRIDGE_MODE"] = "Bridge Mode" L["VERSION_WINDOW_MISSING_ADDON"] = "Addon not installed" L["VERSION_WINDOW_LEADER_TAG"] = "(Leader)" L["VERSION_WINDOW_ASSISTANT_TAG"] = "(Assist)" diff --git a/Modules/RaidTimeline/RaidTimelineDBM.lua b/Modules/RaidTimeline/RaidTimelineDBM.lua deleted file mode 100644 index a55d6ef..0000000 --- a/Modules/RaidTimeline/RaidTimelineDBM.lua +++ /dev/null @@ -1,8 +0,0 @@ -local ADDON_NAME = "HailMaryGuildTools" -local HMGT = LibStub("AceAddon-3.0"):GetAddon(ADDON_NAME) -if not HMGT then return end - -local RT = HMGT.RaidTimeline -if not RT then return end - --- Placeholder for later DBM-specific raid timeline integration. diff --git a/Modules/Tracker/TrackerBridge.lua b/Modules/Tracker/TrackerBridge.lua index d49c457..3edf9c9 100644 --- a/Modules/Tracker/TrackerBridge.lua +++ b/Modules/Tracker/TrackerBridge.lua @@ -80,6 +80,8 @@ function HMGT:ApplyExternalKnownSpell(sourceName, playerName, spellId, class, co knownSpells = knownSpells, externalSource = source, } + self:SetPlayerBridgeStatus(normalizedName, source) + self:DebugScoped("verbose", "TrackerBridge", "Bridge known spell source=%s player=%s spellId=%s", tostring(source), tostring(normalizedName), tostring(sid)) if tonumber(cooldown) and tonumber(cooldown) > 0 then spellEntry._hmgtExternalBaseCd = tonumber(cooldown) @@ -128,6 +130,8 @@ function HMGT:ApplyExternalSpecInfo(sourceName, playerName, class, specId, talen knownSpells = knownSpells, externalSource = source, } + self:SetPlayerBridgeStatus(normalizedName, source) + self:DebugScoped("info", "TrackerBridge", "Bridge spec sync source=%s player=%s class=%s spec=%s", tostring(source), tostring(normalizedName), tostring(classToken), tostring(spec)) self:PruneAvailabilityStates(normalizedName, knownSpells) self:TriggerTrackerUpdate("trackers") @@ -154,6 +158,7 @@ function HMGT:ApplyExternalCooldown(sourceName, playerName, spellId, cooldown) self:RegisterExternalAddonSource(source) self:ApplyExternalKnownSpell(source, normalizedName, sid, nil, cd) + self:DebugScoped("info", "TrackerBridge", "Bridge cooldown source=%s player=%s spellId=%s cooldown=%.1f", tostring(source), tostring(normalizedName), tostring(sid), cd) self:HandleRemoteSpellCast(normalizedName, sid, GetServerTime(), nil, nil, nil, cd) return true end diff --git a/Modules/Tracker/TrackerCore.lua b/Modules/Tracker/TrackerCore.lua index 39fad12..c77fe3e 100644 --- a/Modules/Tracker/TrackerCore.lua +++ b/Modules/Tracker/TrackerCore.lua @@ -4,6 +4,91 @@ if not HMGT then return end HMGT.TrackerCore = HMGT.TrackerCore or {} +HMGT.TRACKER_PRESET_DEFINITIONS = HMGT.TRACKER_PRESET_DEFINITIONS or { + interruptTracker = { + moduleName = "InterruptTracker", + dbKey = "interruptTracker", + trackerType = "normal", + trackerKey = "interruptTracker", + categories = { "interrupt" }, + defaultName = function(L) + return (L and L["IT_NAME"]) or "Interrupts" + end, + }, + raidCooldownTracker = { + moduleName = "RaidCooldownTracker", + dbKey = "raidCooldownTracker", + trackerType = "normal", + trackerKey = "raidCooldownTracker", + categories = { "lust", "defensive", "healing", "tank", "utility", "offensive", "cc", "interrupt" }, + defaultName = function(L) + return (L and L["RCD_NAME"]) or "Raid Cooldowns" + end, + }, + groupCooldownTracker = { + moduleName = "GroupCooldownTracker", + dbKey = "groupCooldownTracker", + trackerType = "group", + trackerKey = "groupCooldownTracker", + categories = { "tank", "defensive", "healing", "cc", "utility", "offensive", "lust", "interrupt" }, + includeSelfFrame = false, + showChargesOnIcon = true, + defaultName = function(L) + return (L and L["GCD_NAME"]) or "Cooldowns" + end, + }, +} + +function HMGT:GetTrackerPresetDefinitions() + return self.TRACKER_PRESET_DEFINITIONS or {} +end + +function HMGT:GetTrackerPresetDefinition(key) + local definitions = self:GetTrackerPresetDefinitions() + return definitions and definitions[tostring(key or "")] +end + +function HMGT:GetTrackerPresetDefinitionByModule(moduleName) + local target = tostring(moduleName or "") + for _, definition in pairs(self:GetTrackerPresetDefinitions()) do + if tostring(definition.moduleName or "") == target then + return definition + end + end + return nil +end + +function HMGT:GetTrackerTypeOptions() + local L = self.L + return { + normal = (L and L["OPT_TRACKER_TYPE_NORMAL"]) or "Normal tracker", + group = (L and L["OPT_TRACKER_TYPE_GROUP"]) or "Group-based tracker", + } +end + +function HMGT:BuildTrackerConfigFromPreset(presetKey, trackerId, overrides) + local definition = self:GetTrackerPresetDefinition(presetKey) + local config = overrides or {} + if not definition then + return self:CreateTrackerConfig(trackerId, config) + end + + local base = { + name = type(definition.defaultName) == "function" and definition.defaultName(self.L) or tostring(definition.defaultName or ""), + trackerType = definition.trackerType, + trackerKey = definition.trackerKey, + categories = definition.categories, + includeSelfFrame = definition.includeSelfFrame, + showChargesOnIcon = definition.showChargesOnIcon, + } + + for key, value in pairs(config) do + base[key] = value + end + + return self:CreateTrackerConfig(trackerId, base) +end + local function EntryNeedsVisualTicker(entry) if type(entry) ~= "table" then return false @@ -204,6 +289,93 @@ function HMGT:CollectTrackerEntries(tracker) return entries end +function HMGT:GetDemoEntries(trackerKey, database, settings) + local pool = {} + local poolByClass = {} + for _, entry in ipairs(database or {}) do + if settings.enabledSpells[entry.spellId] ~= false then + pool[#pool + 1] = entry + for _, classToken in ipairs(entry.classes or {}) do + poolByClass[classToken] = poolByClass[classToken] or {} + poolByClass[classToken][#poolByClass[classToken] + 1] = entry + end + end + end + if #pool == 0 then + return {} + end + + local classKeys = {} + for classToken in pairs(poolByClass) do + classKeys[#classKeys + 1] = classToken + end + if #classKeys == 0 then + classKeys[1] = "WARRIOR" + end + + local count = settings.showBar and math.min(8, #pool) or math.min(12, #pool) + local names = { "Alice", "Bob", "Clara", "Duke", "Elli", "Fynn", "Gina", "Hektor", "Ivo", "Jana", "Kira", "Lio" } + + local spellIds = {} + for _, entry in ipairs(pool) do + spellIds[#spellIds + 1] = tostring(entry.spellId) + end + table.sort(spellIds) + local signature = table.concat(spellIds, ",") .. "|" .. tostring(settings.showBar and 1 or 0) .. "|" .. tostring(count) + + local now = GetTime() + local cache = self.demoModeData[trackerKey] + if (not cache) or cache.signature ~= signature or (not cache.entries) or #cache.entries ~= count then + local cachedEntries = {} + for index = 1, count do + local classToken = classKeys[math.random(1, #classKeys)] + local classPool = poolByClass[classToken] + local spellEntry = (classPool and classPool[math.random(1, #classPool)]) or pool[math.random(1, #pool)] + local duration = math.max( + 1, + tonumber(HMGT_SpellData.GetBaseCooldown and HMGT_SpellData.GetBaseCooldown(spellEntry)) or tonumber(spellEntry.cooldown) or 60 + ) + local offset = math.random() * math.min(duration * 0.85, duration - 0.1) + cachedEntries[#cachedEntries + 1] = { + playerName = names[((index - 1) % #names) + 1], + class = classToken or ((spellEntry.classes and spellEntry.classes[1]) or "WARRIOR"), + spellEntry = spellEntry, + total = duration, + cycleStart = now - offset, + currentCharges = nil, + maxCharges = nil, + } + end + cache = { + signature = signature, + entries = cachedEntries, + } + self.demoModeData[trackerKey] = cache + end + + local entries = {} + for _, entry in ipairs(cache.entries) do + local total = math.max(1, tonumber(entry.total) or 1) + local elapsed = math.max(0, now - (entry.cycleStart or now)) + local phase = math.fmod(elapsed, total) + local remaining = total - phase + if elapsed > 0 and phase < 0.05 then + remaining = 0 + end + entries[#entries + 1] = { + playerName = entry.playerName, + class = entry.class, + spellEntry = entry.spellEntry, + remaining = remaining, + total = total, + currentCharges = entry.currentCharges, + maxCharges = entry.maxCharges, + } + end + + return entries +end + function HMGT:CollectTrackerTestEntries(tracker) local playerName = self:NormalizePlayerName(UnitName("player")) or "Player" local classToken = select(2, UnitClass("player")) @@ -385,7 +557,7 @@ function HMGT:TriggerTrackerUpdate(reason) if t0 and t1 then local mod = HMGT[name] local count = mod and mod.lastEntryCount or 0 - self:Debug("verbose", "UIUpdate %s took %.2fms entries=%s", tostring(name), t1 - t0, tostring(count)) + self:DebugScoped("verbose", "TrackerUI", "UIUpdate %s took %.2fms entries=%s", tostring(name), t1 - t0, tostring(count)) end end diff --git a/Modules/Tracker/TrackerDetection.lua b/Modules/Tracker/TrackerDetection.lua index 11ec4ff..cab2eab 100644 --- a/Modules/Tracker/TrackerDetection.lua +++ b/Modules/Tracker/TrackerDetection.lua @@ -247,7 +247,7 @@ function HMGT:RefreshOwnCooldownStateFromGame(spellId) if isLikelyGlobalCooldown or isSuspiciousShortRefresh then self:DebugScoped( "verbose", - "TrackedSpells", + "TrackerState", "Ignore suspicious refresh for %s: spellCD=%.3f gcd=%.3f existing=%.3f remaining=%.3f effective=%.3f", GetSpellDebugLabel and GetSpellDebugLabel(sid) or tostring(sid), cooldownDuration, diff --git a/Modules/Tracker/TrackerOptions.lua b/Modules/Tracker/TrackerOptions.lua index 14b8d59..bb91c17 100644 --- a/Modules/Tracker/TrackerOptions.lua +++ b/Modules/Tracker/TrackerOptions.lua @@ -142,13 +142,26 @@ local function IsPartyAttachMode(tracker) end local function IsGroupTracker(tracker) - return type(tracker) == "table" and tracker.trackerType == "group" + return HMGT.IsGroupTrackerConfig and HMGT:IsGroupTrackerConfig(tracker) or (type(tracker) == "table" and tracker.trackerType == "group") end -local TRACKER_TYPE_VALUES = { - normal = L["OPT_TRACKER_TYPE_NORMAL"] or "Normal tracker", - group = L["OPT_TRACKER_TYPE_GROUP"] or "Group-based tracker", -} +local function GetTrackerTypeValues() + return HMGT.GetTrackerTypeOptions and HMGT:GetTrackerTypeOptions() or { + normal = L["OPT_TRACKER_TYPE_NORMAL"] or "Normal tracker", + group = L["OPT_TRACKER_TYPE_GROUP"] or "Group-based tracker", + } +end + +local function GetPresetLabel(presetKey) + local definition = HMGT.GetTrackerPresetDefinition and HMGT:GetTrackerPresetDefinition(presetKey) or nil + if not definition then + return tostring(presetKey or (L["OPT_TRACKER"] or "Tracker")) + end + if type(definition.defaultName) == "function" then + return tostring(definition.defaultName(L)) + end + return tostring(definition.defaultName or definition.moduleName or presetKey) +end local function GetTrackerVisibilitySummary(tracker) local parts = {} @@ -180,7 +193,7 @@ local function GetTrackerSummaryText(tracker) local display = tracker.showBar and (L["OPT_DISPLAY_BAR"] or "Progress bars") or (L["OPT_DISPLAY_ICON"] or "Icons") return table.concat({ - string.format("|cffffd100%s|r: %s", L["OPT_TRACKER_TYPE"] or "Tracker type", TRACKER_TYPE_VALUES[tracker.trackerType or "normal"] or (L["OPT_TRACKER_TYPE_NORMAL"] or "Normal tracker")), + string.format("|cffffd100%s|r: %s", L["OPT_TRACKER_TYPE"] or "Tracker type", GetTrackerTypeValues()[tracker.trackerType or "normal"] or (L["OPT_TRACKER_TYPE_NORMAL"] or "Normal tracker")), string.format("|cffffd100%s|r: %s", L["OPT_TRACKER_CATEGORIES"] or "Categories", GetTrackerCategoriesSummary(tracker)), string.format("|cffffd100%s|r: %s", L["OPT_STATUS_MODE"] or "Mode", modeLabel), string.format("|cffffd100%s|r: %s", L["OPT_STATUS_DISPLAY"] or "Display", display), @@ -814,7 +827,7 @@ local function BuildGlobalSpellBrowserArgs() end local function BuildTrackerOverviewArgs() - return { + local args = { description = { type = "description", order = 1, @@ -833,22 +846,36 @@ local function BuildTrackerOverviewArgs() return string.format("%s\n\n%s (%d): %s", body, L["OPT_TRACKERS"] or "Tracker Bars", #trackers, table.concat(names, ", ")) end, }, - addTracker = { + } + + local definitions = HMGT.GetTrackerPresetDefinitions and HMGT:GetTrackerPresetDefinitions() or {} + local presetKeys = {} + for presetKey in pairs(definitions) do + presetKeys[#presetKeys + 1] = presetKey + end + table.sort(presetKeys, function(a, b) + return GetPresetLabel(a) < GetPresetLabel(b) + end) + + for index, presetKey in ipairs(presetKeys) do + args["addPreset_" .. presetKey] = { type = "execute", - order = 2, + order = 2 + index, width = "full", - name = L["OPT_ADD_TRACKER"] or "Add tracker", + name = function() + return string.format("%s: %s", L["OPT_ADD_TRACKER"] or "Add tracker", GetPresetLabel(presetKey)) + end, func = function() local nextId = HMGT:GetNextTrackerId() - local tracker = HMGT:CreateTrackerConfig(nextId, { - name = string.format("%s %d", L["OPT_TRACKER"] or "Tracker", nextId), - }) + local tracker = HMGT:BuildTrackerConfigFromPreset(presetKey, nextId) HMGT.db.profile.trackers = HMGT.db.profile.trackers or {} HMGT.db.profile.trackers[#HMGT.db.profile.trackers + 1] = tracker TriggerTrackerUpdate(true) end, - }, - } + } + end + + return args end local function BuildTrackerGroup(trackerId, order) @@ -1008,7 +1035,7 @@ local function BuildTrackerGroup(trackerId, order) width = "full", name = L["OPT_TRACKER_TYPE"] or "Tracker type", desc = L["OPT_TRACKER_TYPE_DESC"] or "Choose whether this tracker uses one shared frame or separate frames per group member.", - values = TRACKER_TYPE_VALUES, + values = GetTrackerTypeValues, get = function() local tracker = s() return (tracker and tracker.trackerType) or "normal" diff --git a/Modules/Tracker/TrackerSync.lua b/Modules/Tracker/TrackerSync.lua index 4823ff6..7f7c7c5 100644 --- a/Modules/Tracker/TrackerSync.lua +++ b/Modules/Tracker/TrackerSync.lua @@ -203,7 +203,7 @@ function HMGT:SendSpellStateSnapshot(snapshot, target, revision) self:DebugScoped( "verbose", - "TrackedSpells", + "TrackerSync", "SendSpellStateSnapshot target=%s spell=%s kind=%s rev=%d a=%.3f b=%.3f c=%.3f d=%.3f", tostring(target and target ~= "" and target or "GROUP"), GetSpellDebugLabel and GetSpellDebugLabel(sid) or tostring(sid), @@ -307,7 +307,7 @@ function HMGT:BroadcastRepairSpellStates() if not self:IsEnabled() then return end local sent = self:SendOwnTrackedSpellStates() if sent > 0 then - self:DebugScoped("verbose", "TrackedSpells", "RepairSpellStates sent=%d", sent) + self:DebugScoped("verbose", "TrackerSync", "RepairSpellStates sent=%d", sent) end end @@ -407,7 +407,7 @@ function HMGT:BroadcastSpellCast(spellId, snapshot) chargeRemaining = math.max(0, tonumber(remaining) or 0) chargeDuration = math.max(0, tonumber(total) or 0) end - self:DebugScoped("verbose", "TrackedSpells", "BroadcastSpellCast spell=%s serverTime=%s charges=%d/%d", + self:DebugScoped("verbose", "TrackerSync", "BroadcastSpellCast spell=%s serverTime=%s charges=%d/%d", GetSpellDebugLabel and GetSpellDebugLabel(spellId) or tostring(spellId), tostring(GetServerTime()), cur, @@ -563,7 +563,7 @@ function HMGT:StoreRemotePlayerInfo(playerName, class, specIndex, talentHash, kn end self:DebugScoped( "info", - "TrackedSpells", + "TrackerSync", "Spielerinfo von %s: class=%s spec=%s bekannteSpells=%d", tostring(playerName), tostring(class), @@ -753,7 +753,7 @@ function HMGT:ApplyRemoteSpellState(playerName, spellId, kind, revision, a, b, c if changed then self:DebugScoped( "info", - "TrackedSpells", + "TrackerSync", "Sync von %s: %s -> %s (rev=%d)", tostring(normalizedName), GetSpellDebugLabel and GetSpellDebugLabel(sid) or tostring(sid), @@ -814,7 +814,7 @@ function HMGT:OnCommReceived(prefix, message, distribution, sender) if (tonumber(protocol) or 0) >= 5 then return end - self:DebugScoped("verbose", "TrackedSpells", "Legacy cast von %s: %s ts=%s", + self:DebugScoped("verbose", "TrackerSync", "Legacy cast von %s: %s ts=%s", tostring(senderName), GetSpellDebugLabel and GetSpellDebugLabel(spellId) or tostring(spellId), tostring(timestamp)) @@ -892,7 +892,7 @@ function HMGT:OnCommReceived(prefix, message, distribution, sender) self:RememberPeerProtocolVersion(senderName, protocol) self:ClearRemoteSpellStateRevisions(senderName) self:StoreRemotePlayerInfo(senderName, class, specIndex, talentHash, knownSpellList) - self:DebugScoped("info", "TrackedSpells", "Hello von %s: class=%s spec=%s spells=%s", + self:DebugScoped("info", "TrackerSync", "Hello von %s: class=%s spec=%s spells=%s", tostring(senderName), tostring(class), tostring(specIndex), tostring(knownSpellList or "")) self:SendSyncResponse(sender) self:TriggerTrackerUpdate() @@ -1003,7 +1003,7 @@ function HMGT:OnCommReceived(prefix, message, distribution, sender) end end end - self:DebugScoped("info", "TrackedSpells", "SyncResponse von %s: cdsApplied=%d", tostring(senderName), applied) + self:DebugScoped("info", "TrackerSync", "SyncResponse von %s: cdsApplied=%d", tostring(senderName), applied) end self:TriggerTrackerUpdate() end