1 Commits
v2.0.0 ... dev

Author SHA1 Message Date
Torsten Brendgen
6151b434b1 Enhance version management features and localization for HMGT 2026-04-16 16:34:01 +02:00
4 changed files with 374 additions and 44 deletions

View File

@@ -3,6 +3,212 @@ 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 CLASS_ICON_TCOORDS = CLASS_ICON_TCOORDS or {}
local function NormalizeName(name)
if HMGT.NormalizePlayerName then
return HMGT:NormalizePlayerName(name)
end
return tostring(name or "")
end
local function GetRosterRows()
local rows = {}
local seen = {}
local function addUnit(unitId)
if not unitId or not UnitExists(unitId) then
return
end
local name = NormalizeName(UnitName(unitId))
if not name or name == "" or seen[name] then
return
end
seen[name] = true
rows[#rows + 1] = {
name = name,
class = select(2, UnitClass(unitId)),
isLeader = UnitIsGroupLeader and UnitIsGroupLeader(unitId) or false,
isAssistant = UnitIsGroupAssistant and UnitIsGroupAssistant(unitId) or false,
connected = UnitIsConnected and UnitIsConnected(unitId) ~= false or true,
isPlayer = UnitIsUnit and UnitIsUnit(unitId, "player") or unitId == "player",
}
end
if IsInRaid() then
for i = 1, GetNumGroupMembers() do
addUnit("raid" .. i)
end
elseif IsInGroup() then
addUnit("player")
for i = 1, GetNumSubgroupMembers() do
addUnit("party" .. i)
end
else
addUnit("player")
end
table.sort(rows, function(a, b)
if a.isLeader ~= b.isLeader then
return a.isLeader
end
if a.isPlayer ~= b.isPlayer then
return a.isPlayer
end
return tostring(a.name or "") < tostring(b.name or "")
end)
return rows
end
local function GetPlayerVersionText(name)
local normalized = NormalizeName(name)
if normalized == NormalizeName(UnitName("player")) then
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
end
return nil, tonumber(protocol) or 0, false
end
local function ApplyClassIcon(texture, classTag)
if not texture then
return
end
local coords = classTag and CLASS_ICON_TCOORDS[classTag]
if coords then
texture:SetTexture("Interface\\GLUES\\CHARACTERCREATE\\UI-CHARACTERCREATE-CLASSES")
texture:SetTexCoord(coords[1], coords[2], coords[3], coords[4])
texture:Show()
else
texture:SetTexture(nil)
texture:Hide()
end
end
local function AcquireVersionRow(window, index)
window.versionRows = window.versionRows or {}
local row = window.versionRows[index]
if row then
return row
end
local parent = window.scrollChild
row = CreateFrame("Frame", nil, parent)
row:SetHeight(22)
row.background = row:CreateTexture(nil, "BACKGROUND")
row.background:SetAllPoints(row)
row.background:SetColorTexture(1, 1, 1, 0.03)
row.classIcon = row:CreateTexture(nil, "ARTWORK")
row.classIcon:SetSize(16, 16)
row.classIcon:SetPoint("LEFT", row, "LEFT", 4, 0)
row.nameText = row:CreateFontString(nil, "OVERLAY", "GameFontHighlight")
row.nameText:SetPoint("LEFT", row.classIcon, "RIGHT", 6, 0)
row.nameText:SetJustifyH("LEFT")
row.versionText = row:CreateFontString(nil, "OVERLAY", "GameFontHighlight")
row.versionText:SetPoint("LEFT", row, "LEFT", 250, 0)
row.versionText:SetWidth(150)
row.versionText:SetJustifyH("LEFT")
row.protocolText = row:CreateFontString(nil, "OVERLAY", "GameFontHighlight")
row.protocolText:SetPoint("LEFT", row, "LEFT", 410, 0)
row.protocolText:SetWidth(100)
row.protocolText:SetJustifyH("LEFT")
window.versionRows[index] = row
return row
end
function HMGT:RefreshVersionNoticeWindow()
local window = self.versionNoticeWindow
if not window then
return
end
local roster = GetRosterRows()
local localName = NormalizeName(UnitName("player"))
for index, info in ipairs(roster) do
local row = AcquireVersionRow(window, index)
row:ClearAllPoints()
if index == 1 then
row:SetPoint("TOPLEFT", window.scrollChild, "TOPLEFT", 0, 0)
row:SetPoint("TOPRIGHT", window.scrollChild, "TOPRIGHT", 0, 0)
else
row:SetPoint("TOPLEFT", window.versionRows[index - 1], "BOTTOMLEFT", 0, -2)
row:SetPoint("TOPRIGHT", window.versionRows[index - 1], "BOTTOMRIGHT", 0, -2)
end
ApplyClassIcon(row.classIcon, info.class)
local nameLabel = tostring(info.name or UNKNOWN)
if info.isLeader then
nameLabel = string.format("%s %s", nameLabel, L["VERSION_WINDOW_LEADER_TAG"] or "(Leader)")
elseif info.isAssistant then
nameLabel = string.format("%s %s", nameLabel, L["VERSION_WINDOW_ASSISTANT_TAG"] or "(Assist)")
end
if info.isPlayer or info.name == localName then
nameLabel = string.format("%s %s", nameLabel, L["VERSION_WINDOW_SELF_TAG"] or "(You)")
end
row.nameText:SetText(nameLabel)
row.nameText:SetTextColor(1, 0.82, 0.1, 1)
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)
row.protocolText:SetText(protocol > 0 and tostring(protocol) or "-")
row.protocolText:SetTextColor(0.75, 0.75, 0.75, 1)
else
row.versionText:SetText(L["VERSION_WINDOW_MISSING_ADDON"] or "Addon not installed")
row.versionText:SetTextColor(1, 0.25, 0.25, 1)
row.protocolText:SetText("-")
row.protocolText:SetTextColor(1, 0.25, 0.25, 1)
end
row:Show()
end
if window.versionRows then
for index = #roster + 1, #window.versionRows do
window.versionRows[index]:Hide()
end
end
local contentHeight = math.max(1, (#roster * 24))
window.scrollChild:SetHeight(contentHeight)
local known = 0
for _, info in ipairs(roster) do
local _, _, hasAddon = GetPlayerVersionText(info.name)
if hasAddon then
known = known + 1
end
end
window.messageText:SetText(L["VERSION_WINDOW_MESSAGE"] or "Hail Mary Guild Tools versions in your current group")
window.detailText:SetText(string.format(
L["VERSION_WINDOW_CURRENT"] or "Current version: %s | Protocol: %s",
tostring(HMGT.ADDON_VERSION or "dev"),
tostring(HMGT.PROTOCOL_VERSION or "?")
))
window:SetStatusText(string.format(
L["VERSION_WINDOW_STATUS"] or "Detected HMGT on %d/%d players",
tonumber(known) or 0,
tonumber(#roster) or 0
))
end
function HMGT:EnsureVersionNoticeWindow()
if self.versionNoticeWindow then
@@ -10,21 +216,21 @@ function HMGT:EnsureVersionNoticeWindow()
end
self.versionNoticeWindowStatus = self.versionNoticeWindowStatus or {
width = 560,
height = 240,
width = 640,
height = 420,
}
local window = self:CreateAceWindow("versionNotice", {
title = L["VERSION_WINDOW_TITLE"] or "HMGT Version Check",
statusText = "",
statusTable = self.versionNoticeWindowStatus,
width = self.versionNoticeWindowStatus.width or 560,
height = self.versionNoticeWindowStatus.height or 240,
width = self.versionNoticeWindowStatus.width or 640,
height = self.versionNoticeWindowStatus.height or 420,
backgroundTexture = "Interface\\AddOns\\HailMaryGuildTools\\Media\\HailMaryLogo.png",
backgroundWidth = 220,
backgroundHeight = 120,
backgroundOffsetY = -8,
backgroundAlpha = 0.12,
backgroundAlpha = 0.08,
strata = "FULLSCREEN_DIALOG",
})
if not window then
@@ -32,26 +238,64 @@ function HMGT:EnsureVersionNoticeWindow()
end
local content = window:GetContent()
local messageText = content:CreateFontString(nil, "OVERLAY", "GameFontHighlightLarge")
messageText:SetPoint("TOPLEFT", content, "TOPLEFT", 28, -28)
messageText:SetPoint("TOPRIGHT", content, "TOPRIGHT", -28, -28)
messageText:SetJustifyH("CENTER")
messageText:SetJustifyV("MIDDLE")
messageText:SetPoint("TOPLEFT", content, "TOPLEFT", 24, -22)
messageText:SetPoint("TOPRIGHT", content, "TOPRIGHT", -24, -22)
messageText:SetJustifyH("LEFT")
messageText:SetTextColor(1, 0.82, 0.1, 1)
messageText:SetText(L["VERSION_WINDOW_MESSAGE"] or "A new version of Hail Mary Guild Tools is available.")
window.messageText = messageText
local detailText = content:CreateFontString(nil, "OVERLAY", "GameFontHighlight")
detailText:SetPoint("TOPLEFT", messageText, "BOTTOMLEFT", 0, -18)
detailText:SetPoint("TOPRIGHT", messageText, "BOTTOMRIGHT", 0, -18)
detailText:SetJustifyH("CENTER")
detailText:SetJustifyV("TOP")
if detailText.SetSpacing then
detailText:SetSpacing(2)
end
detailText:SetPoint("TOPLEFT", messageText, "BOTTOMLEFT", 0, -8)
detailText:SetPoint("TOPRIGHT", messageText, "BOTTOMRIGHT", 0, -8)
detailText:SetJustifyH("LEFT")
detailText:SetTextColor(0.9, 0.9, 0.9, 1)
window.detailText = detailText
local refreshButton = AceGUI and AceGUI:Create("Button") or nil
if refreshButton then
refreshButton:SetText(L["VERSION_WINDOW_REFRESH"] or "Refresh")
refreshButton:SetWidth(120)
refreshButton:SetCallback("OnClick", function()
HMGT:RequestSync("VersionWindow")
HMGT:RefreshVersionNoticeWindow()
end)
refreshButton.frame:SetParent(window.frame)
refreshButton.frame:ClearAllPoints()
refreshButton.frame:SetPoint("TOPRIGHT", content, "TOPRIGHT", -24, -68)
refreshButton.frame:Show()
window.refreshButton = refreshButton
end
local header = CreateFrame("Frame", nil, content)
header:SetPoint("TOPLEFT", detailText, "BOTTOMLEFT", 0, -14)
header:SetPoint("TOPRIGHT", content, "TOPRIGHT", -24, -96)
header:SetHeight(18)
window.header = header
local nameHeader = header:CreateFontString(nil, "OVERLAY", "GameFontNormal")
nameHeader:SetPoint("LEFT", header, "LEFT", 24, 0)
nameHeader:SetText(L["VERSION_WINDOW_COLUMN_PLAYER"] or "Player")
local versionHeader = header:CreateFontString(nil, "OVERLAY", "GameFontNormal")
versionHeader:SetPoint("LEFT", header, "LEFT", 250, 0)
versionHeader:SetText(L["VERSION_WINDOW_COLUMN_VERSION"] or "Version")
local protocolHeader = header:CreateFontString(nil, "OVERLAY", "GameFontNormal")
protocolHeader:SetPoint("LEFT", header, "LEFT", 410, 0)
protocolHeader:SetText(L["VERSION_WINDOW_COLUMN_PROTOCOL"] or "Protocol")
local scrollFrame = CreateFrame("ScrollFrame", nil, content, "UIPanelScrollFrameTemplate")
scrollFrame:SetPoint("TOPLEFT", header, "BOTTOMLEFT", 0, -6)
scrollFrame:SetPoint("BOTTOMRIGHT", content, "BOTTOMRIGHT", -28, 24)
window.scrollFrame = scrollFrame
local scrollChild = CreateFrame("Frame", nil, scrollFrame)
scrollChild:SetSize(1, 1)
scrollFrame:SetScrollChild(scrollChild)
window.scrollChild = scrollChild
self.versionNoticeWindow = window
return window
end
@@ -67,36 +311,16 @@ function HMGT:ShowVersionMismatchPopup(playerName, detail, sourceTag, opts)
}
end
local info = self.latestVersionMismatch or {}
local window = self:EnsureVersionNoticeWindow()
if not window then
return
end
local hasMismatch = info.playerName or info.detail
window:SetTitle(L["VERSION_WINDOW_TITLE"] or "HMGT Version Check")
if hasMismatch then
window.messageText:SetText(L["VERSION_WINDOW_MESSAGE"] or "A new version of Hail Mary Guild Tools is available.")
window.detailText:SetText(string.format(
L["VERSION_WINDOW_DETAIL"] or "Detected via %s from %s.\n%s",
tostring(info.sourceTag or "?"),
tostring(info.playerName or UNKNOWN),
tostring(info.detail or "")
))
else
window.messageText:SetText(L["VERSION_WINDOW_NO_MISMATCH"] or "No newer HMGT version has been detected in your current group.")
window.detailText:SetText(string.format(
L["VERSION_WINDOW_CURRENT"] or "Current version: %s | Protocol: %s",
tostring(HMGT.ADDON_VERSION or "dev"),
tostring(HMGT.PROTOCOL_VERSION or "?")
))
end
self:DevTrace("Version", hasMismatch and "window_show_mismatch" or "window_show_current", {
player = info.playerName,
source = info.sourceTag,
detail = info.detail,
self:RefreshVersionNoticeWindow()
self:DevTrace("Version", "window_show", {
player = playerName,
source = sourceTag,
detail = detail,
})
window:Show()
window:Raise()

View File

@@ -280,6 +280,7 @@ HMGT.pendingSpellPowerCosts = {}
HMGT.demoModeData = {}
HMGT.peerVersions = {}
HMGT.versionWarnings = {}
HMGT.versionWhisperWarnings = {}
HMGT.debugBuffer = {}
HMGT.debugBufferMax = 500
HMGT.enabledDebugScopes = {
@@ -484,6 +485,79 @@ function HMGT:RememberPeerProtocolVersion(playerName, protocol)
self.peerProtocols[normalizedName] = numeric
end
local function ParseVersionTokens(version)
local tokens = {}
local text = tostring(version or "")
for number in string.gmatch(text, "(%d+)") do
tokens[#tokens + 1] = tonumber(number) or 0
end
return tokens
end
function HMGT:CompareAddonVersions(leftVersion, rightVersion)
local left = ParseVersionTokens(leftVersion)
local right = ParseVersionTokens(rightVersion)
local count = math.max(#left, #right)
for i = 1, count do
local a = tonumber(left[i]) or 0
local b = tonumber(right[i]) or 0
if a ~= b then
return (a < b) and -1 or 1
end
end
local leftText = tostring(leftVersion or "")
local rightText = tostring(rightVersion or "")
if leftText == rightText then
return 0
end
if leftText < rightText then
return -1
end
return 1
end
function HMGT:IsPlayerGroupLeader()
if not IsInGroup() and not IsInRaid() then
return true
end
return UnitIsGroupLeader and UnitIsGroupLeader("player") or false
end
function HMGT:SendOutdatedVersionWhisper(playerName, remoteVersion)
local target = self:NormalizePlayerName(playerName)
local localVersion = tostring(self.ADDON_VERSION or "dev")
local remoteText = tostring(remoteVersion or "?")
if not target or target == "" or not self:IsPlayerGroupLeader() then
return false
end
if self:CompareAddonVersions(localVersion, remoteText) <= 0 then
return false
end
local warningKey = string.format("%s|%s|%s", tostring(target), remoteText, localVersion)
if self.versionWhisperWarnings[warningKey] then
return false
end
self.versionWhisperWarnings[warningKey] = true
local message = string.format(
L["VERSION_OUTDATED_WHISPER"] or "Your Hail Mary Guild Tools version is outdated. You have %s, the group leader has %s.",
remoteText,
localVersion
)
if C_ChatInfo and type(C_ChatInfo.SendChatMessage) == "function" then
C_ChatInfo.SendChatMessage(message, "WHISPER", nil, target)
elseif type(SendChatMessage) == "function" then
SendChatMessage(message, "WHISPER", nil, target)
else
return false
end
return true
end
function HMGT:SendReliableAck(target, messageId)
if not target or target == "" or not messageId or messageId == "" then
return
@@ -682,6 +756,12 @@ function HMGT:RegisterPeerVersion(playerName, version, protocol, sourceTag)
if not playerName then return end
self.peerVersions[playerName] = version
self:RememberPeerProtocolVersion(playerName, protocol)
if self.versionNoticeWindow and self.versionNoticeWindow.IsShown and self.versionNoticeWindow:IsShown() and self.RefreshVersionNoticeWindow then
self:RefreshVersionNoticeWindow()
end
if version and version ~= "" then
self:SendOutdatedVersionWhisper(playerName, version)
end
local mismatch = false
local details = {}
if version and version ~= "" and version ~= ADDON_VERSION then
@@ -4436,11 +4516,17 @@ function HMGT:OnGroupRosterUpdate()
self.remoteSpellStateRevisions[name] = nil
self.peerVersions[name] = nil
self.versionWarnings[name] = nil
if self.peerProtocols then
self.peerProtocols[name] = nil
end
end
end
local count = 0
for _ in pairs(validPlayers) do count = count + 1 end
self:Debug("verbose", "OnGroupRosterUpdate validPlayers=%d", count)
if self.versionNoticeWindow and self.versionNoticeWindow.IsShown and self.versionNoticeWindow:IsShown() and self.RefreshVersionNoticeWindow then
self:RefreshVersionNoticeWindow()
end
if HMGT.TrackerManager and HMGT.TrackerManager.InvalidateAnchorLayout then
HMGT.TrackerManager:InvalidateAnchorLayout()
end

View File

@@ -18,10 +18,20 @@ L["SLASH_HINT"] = "/hmgt Optionen | /hmgt lock/unlock | /hmgt dem
L["VERSION_MISMATCH_CHAT"] = "Versionskonflikt mit %s: %s"
L["VERSION_MISMATCH_POPUP"] = "HailMaryGuildTools Konflikt mit %s.\n%s\nQuelle: %s"
L["VERSION_WINDOW_TITLE"] = "HMGT Versionscheck"
L["VERSION_WINDOW_MESSAGE"] = "Es gibt eine neue Version von Hail Mary Guild Tools."
L["VERSION_WINDOW_MESSAGE"] = "Hail Mary Guild Tools Versionen in deiner aktuellen Gruppe"
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_REFRESH"] = "Aktualisieren"
L["VERSION_WINDOW_COLUMN_PLAYER"] = "Spieler"
L["VERSION_WINDOW_COLUMN_VERSION"] = "Version"
L["VERSION_WINDOW_COLUMN_PROTOCOL"] = "Protokoll"
L["VERSION_WINDOW_MISSING_ADDON"] = "Addon nicht vorhanden"
L["VERSION_WINDOW_LEADER_TAG"] = "(Leiter)"
L["VERSION_WINDOW_ASSISTANT_TAG"] = "(Assist)"
L["VERSION_WINDOW_SELF_TAG"] = "(Du)"
L["VERSION_OUTDATED_WHISPER"] = "Deine Hail Mary Guild Tools Version ist veraltet. Du hast %s, der Gruppenleiter hat %s."
L["VERSION_WINDOW_DEBUG_ONLY"] = "HMGT: /hmgt version ist nur bei aktiviertem Debugmodus verfuegbar."
L["VERSION_WINDOW_DEVTOOLS_ONLY"] = "HMGT: /hmgt version ist nur bei aktivierten Entwicklerwerkzeugen verfuegbar."

View File

@@ -18,10 +18,20 @@ L["SLASH_HINT"] = "/hmgt options | /hmgt lock/unlock | /hmgt demo
L["VERSION_MISMATCH_CHAT"] = "Version mismatch with %s: %s"
L["VERSION_MISMATCH_POPUP"] = "HailMaryGuildTools mismatch with %s.\n%s\nSource: %s"
L["VERSION_WINDOW_TITLE"] = "HMGT Version Check"
L["VERSION_WINDOW_MESSAGE"] = "A new version of Hail Mary Guild Tools is available."
L["VERSION_WINDOW_MESSAGE"] = "Hail Mary Guild Tools versions in your current group"
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_REFRESH"] = "Refresh"
L["VERSION_WINDOW_COLUMN_PLAYER"] = "Player"
L["VERSION_WINDOW_COLUMN_VERSION"] = "Version"
L["VERSION_WINDOW_COLUMN_PROTOCOL"] = "Protocol"
L["VERSION_WINDOW_MISSING_ADDON"] = "Addon not installed"
L["VERSION_WINDOW_LEADER_TAG"] = "(Leader)"
L["VERSION_WINDOW_ASSISTANT_TAG"] = "(Assist)"
L["VERSION_WINDOW_SELF_TAG"] = "(You)"
L["VERSION_OUTDATED_WHISPER"] = "Your Hail Mary Guild Tools version is outdated. You have %s, the group leader has %s."
L["VERSION_WINDOW_DEBUG_ONLY"] = "HMGT: /hmgt version is only available while debug mode is enabled."
L["VERSION_WINDOW_DEVTOOLS_ONLY"] = "HMGT: /hmgt version is only available while developer tools are enabled."