From 2c7233ba36da13cc771b8afb3b0474b9c5fc9d5e Mon Sep 17 00:00:00 2001 From: Maelareth Date: Wed, 10 Jun 2026 21:11:30 +0200 Subject: [PATCH 1/2] feat(nicknames): NSRT precedence conflict popup (prototype) Detect when Northern Sky Raid Tools is also set to manage names on DandersFrames frames (it overwrites DF:GetUnitName), and prompt the user once which should win. The choice (framePrecedence) is honoured by a new DF:GetFrameName that resolves our own nicknames first, so DandersFrames stays authoritative even though NSRT re-overwrites the hook. Entirely DF-side: only reads NSRT's toggle, never modifies it. Popup strings hard-coded for the prototype; localise before merge. --- Features/Nicknames.lua | 155 ++++++++++++++++++++++++++++++++++++ Frames/Bars.lua | 2 +- Frames/Core.lua | 14 ++++ Frames/Update.lua | 4 +- TextDesigner/DataSource.lua | 2 +- 5 files changed, 173 insertions(+), 4 deletions(-) diff --git a/Features/Nicknames.lua b/Features/Nicknames.lua index 83f1fb96..6f7cef0f 100644 --- a/Features/Nicknames.lua +++ b/Features/Nicknames.lua @@ -1035,6 +1035,157 @@ end -- INIT (called from Core.lua after PLAYER_LOGIN) -- ============================================================ +-- ============================================================ +-- NSRT PRECEDENCE / CONFLICT PROMPT +-- Northern Sky Raid Tools can also be set to put nicknames on DandersFrames +-- frames; when its "DandersFrames" toggle is on it OVERWRITES DF:GetUnitName, +-- so the two would fight. We detect that, prompt the user once which should +-- win, and store the choice (framePrecedence) which DF:GetFrameName honours. +-- Entirely DF-side: we never modify NSRT, only READ its toggle. +-- NOTE: strings here are hard-coded for the prototype — localise (L[...] + +-- enUS) before merge. +-- ============================================================ + +-- True if NSRT is loaded AND configured to manage names on DandersFrames frames. +function NK:NSRTManagingNames() + return (C_AddOns and C_AddOns.IsAddOnLoaded and C_AddOns.IsAddOnLoaded("NorthernSkyRaidTools")) + and NSRT and NSRT.Settings + and NSRT.Settings["GlobalNickNames"] + and NSRT.Settings["DandersFrames"] + and true or false +end + +-- Should DandersFrames' own nicknames win on our frames? Yes — unless the user +-- explicitly chose NSRT AND NSRT is actually managing names right now. +function NK:HasPrecedence() + local data = NK:GetDB() + if data and data.framePrecedence == "nsrt" and NK:NSRTManagingNames() then + return false + end + return true +end + +-- The name to display on our own frames, honouring precedence. When DF wins we +-- resolve our own nickname first; when the user has chosen NSRT we ask NSRT's +-- own resolver DIRECTLY (NSAPI:GetName) so its name shows, bypassing our hook +-- (which would otherwise keep returning the DF nickname). +function NK:GetDisplayName(unit) + if NK:HasPrecedence() then + local nick = NK:Resolve(unit) + if nick then return nick end + return DF:GetUnitName(unit) + end + local raw = UnitName(unit) + if NSAPI and NSAPI.GetName and raw then + return NSAPI:GetName(raw, "DandersFrames") or raw + end + return raw or unit +end + +-- Build + show the one-time conflict popup (DandersFrames' own alert style). +function NK:ShowConflictPopup() + if NK._conflictPopup then NK._conflictPopup:Show(); return end + + local theme = (DF.GUI and DF.GUI.GetThemeColor and DF.GUI.GetThemeColor()) + or { r = 0.9, g = 0.55, b = 0.15 } + + local popup = CreateFrame("Frame", "DFNicknameConflictPopup", UIParent, "BackdropTemplate") + popup:SetSize(490, 250) + popup:SetPoint("CENTER") + popup:SetFrameStrata("FULLSCREEN_DIALOG") + popup:SetFrameLevel(200) + popup:SetBackdrop({ bgFile = "Interface\\Buttons\\WHITE8x8", edgeFile = "Interface\\Buttons\\WHITE8x8", edgeSize = 2 }) + popup:SetBackdropColor(0.1, 0.1, 0.1, 0.98) + popup:SetBackdropBorderColor(theme.r, theme.g, theme.b, 1) + popup:EnableMouse(true) + popup:SetMovable(true) + popup:RegisterForDrag("LeftButton") + popup:SetScript("OnDragStart", popup.StartMoving) + popup:SetScript("OnDragStop", popup.StopMovingOrSizing) + tinsert(UISpecialFrames, "DFNicknameConflictPopup") -- Esc closes (re-prompts next login) + + local title = popup:CreateFontString(nil, "OVERLAY", "DFFontNormalLarge") + title:SetPoint("TOP", 0, -16) + title:SetText("Addon nicknames conflict") + title:SetTextColor(1, 0.3, 0.3) + + local warnTex = "Interface\\AddOns\\DandersFrames\\Media\\Icons\\warning" + local lw = popup:CreateTexture(nil, "OVERLAY"); lw:SetSize(18, 18) + lw:SetPoint("RIGHT", title, "LEFT", -8, 0); lw:SetTexture(warnTex); lw:SetVertexColor(1, 0.3, 0.3) + local rw = popup:CreateTexture(nil, "OVERLAY"); rw:SetSize(18, 18) + rw:SetPoint("LEFT", title, "RIGHT", 8, 0); rw:SetTexture(warnTex); rw:SetVertexColor(1, 0.3, 0.3) + + local msg = popup:CreateFontString(nil, "OVERLAY", "DFFontHighlight") + msg:SetPoint("TOP", title, "BOTTOM", 0, -16) + msg:SetPoint("LEFT", 28, 0); msg:SetPoint("RIGHT", -28, 0); msg:SetJustifyH("CENTER") + msg:SetText("Both |cffe68c26DandersFrames|r and |cff6fb1e0Northern Sky Raid Tools|r are set to show nicknames on your frames.\n\nWhich one should decide the names shown here?") + + local sub = popup:CreateFontString(nil, "OVERLAY", "DFFontHighlightSmall") + sub:SetPoint("TOP", msg, "BOTTOM", 0, -12) + sub:SetPoint("LEFT", 28, 0); sub:SetPoint("RIGHT", -28, 0); sub:SetJustifyH("CENTER") + sub:SetText("This only changes who controls names on DandersFrames frames - you can change it later in Nicknames settings.") + sub:SetTextColor(0.7, 0.7, 0.7) + + local function choose(pref) + local d = NK:GetDB(); if d then d.framePrecedence = pref end + popup:Hide() + NK:RefreshAllFrames() + end + + local function makeButton(text, primary, onClick) + local b = CreateFrame("Button", nil, popup, "BackdropTemplate") + b:SetSize(225, 42) + b:SetBackdrop({ bgFile = "Interface\\Buttons\\WHITE8x8", edgeFile = "Interface\\Buttons\\WHITE8x8", edgeSize = 1 }) + if primary then + b:SetBackdropColor(theme.r * 0.3, theme.g * 0.3, theme.b * 0.3, 1) + b:SetBackdropBorderColor(theme.r, theme.g, theme.b, 1) + else + b:SetBackdropColor(0.15, 0.15, 0.15, 1) + b:SetBackdropBorderColor(0.4, 0.4, 0.4, 1) + end + local t = b:CreateFontString(nil, "OVERLAY", "DFFontHighlightSmall") + t:SetPoint("LEFT", 6, 0); t:SetPoint("RIGHT", -6, 0); t:SetJustifyH("CENTER") + t:SetWordWrap(true); t:SetText(text); t:SetTextColor(1, 1, 1) + b:SetScript("OnEnter", function(self) self:SetBackdropBorderColor(theme.r, theme.g, theme.b, 1) end) + b:SetScript("OnLeave", function(self) + if primary then self:SetBackdropBorderColor(theme.r, theme.g, theme.b, 1) + else self:SetBackdropBorderColor(0.4, 0.4, 0.4, 1) end + end) + b:SetScript("OnClick", onClick) + return b + end + + local dfBtn = makeButton("Use DandersFrames nicknames", true, function() choose("self") end) + dfBtn:SetPoint("BOTTOM", -118, 16) + local nsBtn = makeButton("Use Northern Sky Raid Tools nicknames", false, function() choose("nsrt") end) + nsBtn:SetPoint("BOTTOM", 118, 16) + + NK._conflictPopup = popup + popup:Show() +end + +-- Prompt once if there's an unresolved DF/NSRT name conflict. Defers out of combat. +function NK:CheckConflictPrompt() + local data = NK:GetDB() + if not data or not data.enabled then return end + if data.framePrecedence ~= nil then return end -- already decided + if not NK:NSRTManagingNames() then return end -- no conflict + if InCombatLockdown and InCombatLockdown() then + if not NK._conflictCombatWatch then + local f = CreateFrame("Frame") + f:RegisterEvent("PLAYER_REGEN_ENABLED") + f:SetScript("OnEvent", function(self) + self:UnregisterEvent("PLAYER_REGEN_ENABLED") + NK._conflictCombatWatch = nil + NK:CheckConflictPrompt() + end) + NK._conflictCombatWatch = f + end + return + end + NK:ShowConflictPopup() +end + function NK:Init() if self.initialized then return end self.initialized = true @@ -1059,4 +1210,8 @@ function NK:Init() -- Make sure any names already on screen pick up existing rules. NK:RefreshAllFrames() + + -- If NSRT is also set to manage our frame names, prompt once which wins. + -- Delayed so NSRT's saved vars / settings are loaded first. + if C_Timer then C_Timer.After(3, function() NK:CheckConflictPrompt() end) end end diff --git a/Frames/Bars.lua b/Frames/Bars.lua index 3bb3790d..04933b30 100644 --- a/Frames/Bars.lua +++ b/Frames/Bars.lua @@ -2119,7 +2119,7 @@ function DF:UpdateName(frame) -- Use raid DB for raid frames, party DB for party frames local db = DF:GetFrameDB(frame) - local name = DF:GetUnitName(frame.unit) + local name = DF:GetFrameName(frame.unit) -- Truncate name if needed (UTF-8 aware) if name then diff --git a/Frames/Core.lua b/Frames/Core.lua index cfd14ea7..7597f246 100644 --- a/Frames/Core.lua +++ b/Frames/Core.lua @@ -511,6 +511,20 @@ function DF:GetUnitName(unit) return UnitName(unit) or unit end +-- Display name used by our own frame rendering. DandersFrames' native nicknames +-- resolve FIRST here, so they stay authoritative on our frames even when another +-- nickname addon (e.g. NSRT) overwrites DF:GetUnitName. NK:HasPrecedence() gates +-- this: it returns false only when the user has explicitly chosen to let the +-- other addon win, in which case we fall through to DF:GetUnitName (which that +-- addon may own) and finally the raw name. +function DF:GetFrameName(unit) + local NK = DF.Nicknames + if NK and NK.GetDisplayName then + return NK:GetDisplayName(unit) + end + return DF:GetUnitName(unit) +end + -- Iterator for all compact unit frames (player, party, raid) -- Accepts a callback function OR returns an iterator if no callback provided -- Usage with callback: DF:IterateCompactFrames(function(frame) ... end) diff --git a/Frames/Update.lua b/Frames/Update.lua index c5e6c1c4..c778fc6a 100644 --- a/Frames/Update.lua +++ b/Frames/Update.lua @@ -535,7 +535,7 @@ function DF:UpdateUnitFrame(frame, source) if hideLegacyText then frame.nameText:Hide() else - local name = DF:GetUnitName(unit) or unit + local name = DF:GetFrameName(unit) or unit -- Truncate name if needed (UTF-8 aware) local nameLength = db.nameTextLength or 0 if nameLength > 0 and DF:UTF8Len(name) > nameLength then @@ -598,7 +598,7 @@ function DF:UpdateUnitFrame(frame, source) if hideLegacyText then frame.nameText:Hide() else - local name = DF:GetUnitName(unit) or unit + local name = DF:GetFrameName(unit) or unit -- Truncate name if needed (UTF-8 aware) local nameLength = db.nameTextLength or 0 if nameLength > 0 and DF:UTF8Len(name) > nameLength then diff --git a/TextDesigner/DataSource.lua b/TextDesigner/DataSource.lua index d014f304..0a480b75 100644 --- a/TextDesigner/DataSource.lua +++ b/TextDesigner/DataSource.lua @@ -146,7 +146,7 @@ LiveSource.__index = LiveSource function LiveSource:_isMock() return false end function LiveSource:GetName() - if DF.GetUnitName then return DF:GetUnitName(self.unit) end + if DF.GetFrameName then return DF:GetFrameName(self.unit) end return UnitName(self.unit) or "" end From 561db04c76a27953e21fca9b782d224d4150b89e Mon Sep 17 00:00:00 2001 From: Maelareth Date: Thu, 11 Jun 2026 07:19:13 +0200 Subject: [PATCH 2/2] feat(nicknames): finalise NSRT precedence popup - Localise all popup strings (L[...] + enUS registration); addon names are passed as format args so translators never touch them - Add a Name precedence dropdown to the Nicknames options page (only shown when NSRT is installed), making good on the popup's 'change it later' promise - Only prompt when the user actually has DF nicknames to show (entries, received, or a broadcast nick) - without this every NSRT user would get the popup with nothing to choose between - CHANGELOG entry --- CHANGELOG.md | 1 + Features/Nicknames.lua | 23 ++++++++++++++++------- Locales/enUS.lua | 7 +++++++ Options/NicknamesPage.lua | 30 ++++++++++++++++++++++++++++++ 4 files changed, 54 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 42e30aa9..0068acdc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -40,6 +40,7 @@ * Moved **Pixel-Perfect Scaling** to **General → Settings**, since it applies globally to both party and raid. (by Krathe) * (Aura Designer) New icon and square indicators now default their **border inset to 0**, flush with the icon edge, matching the other indicator types. (by Krathe) * (Auto Layouts) Saved layouts are tidied of leftover built-in text overrides after the Text Designer migration, so the override list no longer lists dead entries. (by Krathe) +* (Nicknames) **Northern Sky Raid Tools compatibility** — when NSRT is also set to put nicknames on DandersFrames frames, a one-time prompt lets you choose which addon decides the names shown on your frames (changeable later under **General → Nicknames → Name precedence**), so the two no longer silently fight. (by Maelareth) ### Bug Fixes diff --git a/Features/Nicknames.lua b/Features/Nicknames.lua index 6f7cef0f..c8beec8e 100644 --- a/Features/Nicknames.lua +++ b/Features/Nicknames.lua @@ -1042,8 +1042,7 @@ end -- so the two would fight. We detect that, prompt the user once which should -- win, and store the choice (framePrecedence) which DF:GetFrameName honours. -- Entirely DF-side: we never modify NSRT, only READ its toggle. --- NOTE: strings here are hard-coded for the prototype — localise (L[...] + --- enUS) before merge. +-- The same choice can be changed later in the Nicknames options page. -- ============================================================ -- True if NSRT is loaded AND configured to manage names on DandersFrames frames. @@ -1086,6 +1085,7 @@ end function NK:ShowConflictPopup() if NK._conflictPopup then NK._conflictPopup:Show(); return end + local L = DF.L local theme = (DF.GUI and DF.GUI.GetThemeColor and DF.GUI.GetThemeColor()) or { r = 0.9, g = 0.55, b = 0.15 } @@ -1106,7 +1106,7 @@ function NK:ShowConflictPopup() local title = popup:CreateFontString(nil, "OVERLAY", "DFFontNormalLarge") title:SetPoint("TOP", 0, -16) - title:SetText("Addon nicknames conflict") + title:SetText(L["Addon nicknames conflict"]) title:SetTextColor(1, 0.3, 0.3) local warnTex = "Interface\\AddOns\\DandersFrames\\Media\\Icons\\warning" @@ -1118,12 +1118,13 @@ function NK:ShowConflictPopup() local msg = popup:CreateFontString(nil, "OVERLAY", "DFFontHighlight") msg:SetPoint("TOP", title, "BOTTOM", 0, -16) msg:SetPoint("LEFT", 28, 0); msg:SetPoint("RIGHT", -28, 0); msg:SetJustifyH("CENTER") - msg:SetText("Both |cffe68c26DandersFrames|r and |cff6fb1e0Northern Sky Raid Tools|r are set to show nicknames on your frames.\n\nWhich one should decide the names shown here?") + msg:SetText(L["Both %s and %s are set to show nicknames on your frames.\n\nWhich one should decide the names shown here?"] + :format("|cffe68c26DandersFrames|r", "|cff6fb1e0Northern Sky Raid Tools|r")) local sub = popup:CreateFontString(nil, "OVERLAY", "DFFontHighlightSmall") sub:SetPoint("TOP", msg, "BOTTOM", 0, -12) sub:SetPoint("LEFT", 28, 0); sub:SetPoint("RIGHT", -28, 0); sub:SetJustifyH("CENTER") - sub:SetText("This only changes who controls names on DandersFrames frames - you can change it later in Nicknames settings.") + sub:SetText(L["This only changes who controls names on DandersFrames frames - you can change it later in Nicknames settings."]) sub:SetTextColor(0.7, 0.7, 0.7) local function choose(pref) @@ -1155,9 +1156,9 @@ function NK:ShowConflictPopup() return b end - local dfBtn = makeButton("Use DandersFrames nicknames", true, function() choose("self") end) + local dfBtn = makeButton(L["Use %s nicknames"]:format("DandersFrames"), true, function() choose("self") end) dfBtn:SetPoint("BOTTOM", -118, 16) - local nsBtn = makeButton("Use Northern Sky Raid Tools nicknames", false, function() choose("nsrt") end) + local nsBtn = makeButton(L["Use %s nicknames"]:format("Northern Sky Raid Tools"), false, function() choose("nsrt") end) nsBtn:SetPoint("BOTTOM", 118, 16) NK._conflictPopup = popup @@ -1169,6 +1170,14 @@ function NK:CheckConflictPrompt() local data = NK:GetDB() if not data or not data.enabled then return end if data.framePrecedence ~= nil then return end -- already decided + -- Only prompt users who actually USE DF nicknames (enabled defaults true, + -- so without this every NSRT user would get the popup with nothing to + -- choose between). Once they add a first nickname, the conflict is real + -- and the popup appears at next login (framePrecedence is still nil). + local hasAny = (data.entries and #data.entries > 0) + or (NK.received and next(NK.received) ~= nil) + or (data.selfNick and data.selfNick ~= "") + if not hasAny then return end if not NK:NSRTManagingNames() then return end -- no conflict if InCombatLockdown and InCombatLockdown() then if not NK._conflictCombatWatch then diff --git a/Locales/enUS.lua b/Locales/enUS.lua index 7547f446..6d80c171 100644 --- a/Locales/enUS.lua +++ b/Locales/enUS.lua @@ -1883,6 +1883,7 @@ L["Add a nickname"] = true L["Add from"] = true L["Add from:"] = true L["Added"] = true +L["Addon nicknames conflict"] = true L["All nicknames"] = true L["Angle "] = true L["Apply to"] = true @@ -1895,6 +1896,7 @@ L["Blocked: contained formatting codes"] = true L["Blocked: contains a filtered word"] = true L["Blocked: empty"] = true L["Blocked: too long"] = true +L["Both %s and %s are set to show nicknames on your frames.\n\nWhich one should decide the names shown here?"] = true L["Brackets [name]"] = true L["Character / text"] = true L["Contains"] = true @@ -1911,6 +1913,8 @@ L["Mark nicknames"] = true L["Marker style"] = true L["Match"] = true L["My added"] = true +L["Name precedence"] = true +L["Names on frames decided by"] = true L["Needs re-link"] = true L["Nickname"] = true L["Nicknames"] = true @@ -1919,6 +1923,7 @@ L["No characters found."] = true L["No group members found."] = true L["No nickname rules yet. Add one above."] = true L["No nicknames received yet."] = true +L["Northern Sky Raid Tools can also show nicknames on DandersFrames frames. Choose which one decides the names shown here."] = true L["Overlapping rule"] = true L["Overlaps with rule(s) %s. For names they share, the rule higher in the list wins."] = true L["Overridden"] = true @@ -1937,6 +1942,8 @@ L["Source"] = true L["Starts with"] = true L["Starts with: matches any character whose name begins with this text."] = true L["This Battle.net friend could not be matched after an update. Remove this rule and add them again."] = true +L["This only changes who controls names on DandersFrames frames - you can change it later in Nicknames settings."] = true +L["Use %s nicknames"] = true L["You are not in a guild."] = true L["Your nickname (broadcast)"] = true diff --git a/Options/NicknamesPage.lua b/Options/NicknamesPage.lua index e292e24f..8f99647c 100644 --- a/Options/NicknamesPage.lua +++ b/Options/NicknamesPage.lua @@ -777,6 +777,36 @@ function DF.BuildNicknamesPage(guiRef, pageRef, dbRef) recvContent:SetHeight(mmax(1, n * ROW_HEIGHT)) end + -- ===== Name precedence (only shown when NSRT can also manage our names) ===== + -- Northern Sky Raid Tools can also be set to put nicknames on DandersFrames + -- frames; when both are active they fight. This mirrors the one-time conflict + -- popup (Features/Nicknames.lua) so the choice can be changed here later. + if C_AddOns and C_AddOns.IsAddOnLoaded and C_AddOns.IsAddOnLoaded("NorthernSkyRaidTools") then + local precHeader = parent:CreateFontString(nil, "OVERLAY", "DFFontNormal") + precHeader:SetPoint("TOPLEFT", recvBg, "BOTTOMLEFT", 0, -18) + precHeader:SetText(L["Name precedence"]) + do local pc = themeColor(); precHeader:SetTextColor(pc.r, pc.g, pc.b) end + + local precDesc = parent:CreateFontString(nil, "OVERLAY", "DFFontDisableSmall") + precDesc:SetPoint("TOPLEFT", precHeader, "BOTTOMLEFT", 0, -6) + precDesc:SetWidth(LIST_WIDTH) + precDesc:SetJustifyH("LEFT") + precDesc:SetText(L["Northern Sky Raid Tools can also show nicknames on DandersFrames frames. Choose which one decides the names shown here."]) + + local precOpts = { + self = "DandersFrames", nsrt = "Northern Sky Raid Tools", + _order = { "self", "nsrt" }, + } + local precDD = GUI:CreateDropdown(parent, L["Names on frames decided by"], precOpts, nil, nil, nil, + function() local d = NK:GetDB(); return (d and d.framePrecedence == "nsrt") and "nsrt" or "self" end, + function(v) + local d = NK:GetDB(); if d then d.framePrecedence = v end + NK:RefreshAllFrames() + end) + precDD:SetSize(220, 50) + precDD:SetPoint("TOPLEFT", precDesc, "BOTTOMLEFT", -4, -8) + end + -- Keep this panel in sync with changes made anywhere (e.g. incoming shares). NK.onChange = Refresh Refresh()