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 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 return self.versionNoticeWindow end self.versionNoticeWindowStatus = self.versionNoticeWindowStatus or { 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 640, height = self.versionNoticeWindowStatus.height or 420, backgroundTexture = "Interface\\AddOns\\HailMaryGuildTools\\Media\\HailMaryLogo.png", backgroundWidth = 220, backgroundHeight = 120, backgroundOffsetY = -8, backgroundAlpha = 0.08, strata = "FULLSCREEN_DIALOG", }) if not window then return nil end local content = window:GetContent() local messageText = content:CreateFontString(nil, "OVERLAY", "GameFontHighlightLarge") 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) window.messageText = messageText local detailText = content:CreateFontString(nil, "OVERLAY", "GameFontHighlight") 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 function HMGT:ShowVersionMismatchPopup(playerName, detail, sourceTag, opts) opts = opts or {} if playerName or detail or sourceTag then self.latestVersionMismatch = { playerName = playerName, detail = detail, sourceTag = sourceTag, } end local window = self:EnsureVersionNoticeWindow() if not window then return end self:RefreshVersionNoticeWindow() self:DevTrace("Version", "window_show", { player = playerName, source = sourceTag, detail = detail, }) window:Show() window:Raise() end