Files
coa-chatter/Chatter/Modules/AltNames.lua
T
florian.berthold 5eaec81f02 chore: move addon into Chatter/ + add standard .gitignore
Matches the Exiles fork-layout convention (each addon in its own folder).
2026-05-25 10:59:27 +02:00

353 lines
10 KiB
Lua

local mod = Chatter:NewModule("Alt Linking", "AceHook-3.0", "AceEvent-3.0")
local L = LibStub("AceLocale-3.0"):GetLocale("Chatter")
mod.modName = L["Alt Linking"]
local NAMES
local GUILDNOTES
local pairs = _G.pairs
local select = _G.select
local setmetatable = _G.setmetatable
local tinsert = _G.tinsert
local tremove = _G.tremove
local type = _G.type
local unpack = _G.unpack
local strlower= _G.string.lower
local gmatch = _G.string.gmatch
local leftBracket, rightBracket
local defaults = {
realm = {},
profile = {
guildNotes=true,
altNotesFallback=true,
colorMode = "COLOR_MOD",
color = {0.6, 0.6, 0.6},
leftBracket = "[",
rightBracket = "]",
}
}
local colorModes = {
COLOR_MOD = L["Use PlayerNames coloring"],
CUSTOM = L["Use custom color"],
CHANNEL = L["Use channel color"]
}
local customColorNames = setmetatable({}, {
__index = function(t, v)
local r, g, b = unpack(mod.db.profile.color)
t[v] = ("|cff%02x%02x%02x%s|r"):format(r * 255, g * 255, b * 255, v)
return t[v]
end
})
local options
function mod:GetOptions()
options = options or {
guildNotes = {
order=100,
type = "toggle",
name = L["Use guildnotes"],
desc = L["Look in guildnotes for character names, unless a note is set manually"],
get = function()
return mod.db.profile.guildNotes
end,
set = function(info, v)
mod.db.profile.guildNotes = v
mod:EnableGuildNotes(v)
end,
},
altNotesFallback = {
order=101,
type = "toggle",
name = L["Alt note fallback"],
desc = L["If no name can be found for an 'alt' rank character, use entire note"],
disabled = function()
return not mod.db.profile.guildNotes
end,
get = function()
return mod.db.profile.altNotesFallback
end,
set = function(info, v)
mod.db.profile.altNotesFallback = v
mod:ScanGuildNotes()
end,
},
colorMode = {
order=110,
type = "select",
name = L["Name color"],
desc = L["Set the coloring mode for alt names"],
values = colorModes,
get = function()
return mod.db.profile.colorMode
end,
set = function(info, v)
mod.db.profile.colorMode = v
end
},
color = {
order=111,
type = "color",
name = L["Custom color"],
desc = L["Select the custom color to use for alt names"],
get = function()
return unpack(mod.db.profile.color)
end,
set = function(info, r, g, b)
mod.db.profile.color[1] = r
mod.db.profile.color[2] = g
mod.db.profile.color[3] = b
for k, v in pairs(customColorNames) do
customColorNames[k] = nil
end
end,
disabled = function() return mod.db.profile.colorMode ~= "CUSTOM" end
},
leftbracket = {
type = "input",
name = L["Left Bracket"],
desc = L["Character to use for the left bracket"],
get = function() return mod.db.profile.leftBracket end,
set = function(i, v)
mod.db.profile.leftBracket = v
leftBracket = v
end
},
rightbracket = {
type = "input",
name = L["Right Bracket"],
desc = L["Character to use for the right bracket"],
get = function() return mod.db.profile.rightBracket end,
set = function(i, v)
mod.db.profile.rightBracket = v
rightBracket = v
end
},
}
return options
end
local accept = function(self, char)
local editBox = _G[this:GetParent():GetName().."EditBox"]
local main = editBox:GetText()
mod:AddAlt(char, main)
this:GetParent():Hide()
end
StaticPopupDialogs['MENUITEM_SET_MAIN'] = {
text = L["Who is %s's main?"],
button1 = TEXT(ACCEPT),
button2 = TEXT(CANCEL),
hasEditBox = 1,
maxLetters = 128,
exclusive = 0,
OnShow = function()
_G[this:GetName().."EditBox"]:SetFocus()
end,
OnHide = function()
if ( _G[this:GetName().."EditBox"]:IsShown() ) then
_G[this:GetName().."EditBox"]:SetFocus();
end
_G[this:GetName().."EditBox"]:SetText("");
end,
OnAccept = accept,
EditBoxOnEnterPressed = accept,
EditBoxOnEscapePressed = function() this:GetParent():Hide() end,
timeout = 0,
whileDead = 1,
hideOnEscape = 1
}
UnitPopupButtons["SET_MAIN"] = {
text = L["Set Main"],
dist = 0,
func = mod.GetMainName
}
function mod:OnInitialize()
self.db = Chatter.db:RegisterNamespace("AltLinks", defaults)
end
function mod:Decorate(frame)
if not self:IsHooked(frame,"AddMessage") then
self:RawHook(frame, "AddMessage", true)
end
end
function mod:OnEnable()
NAMES = self.db.realm
UnitPopupButtons["SET_MAIN"].func = self.GetMainName
tinsert(UnitPopupMenus["SELF"], #UnitPopupMenus["SELF"] - 1, "SET_MAIN")
tinsert(UnitPopupMenus["PLAYER"], #UnitPopupMenus["PLAYER"] - 1, "SET_MAIN")
tinsert(UnitPopupMenus["FRIEND"], #UnitPopupMenus["FRIEND"] - 1, "SET_MAIN")
tinsert(UnitPopupMenus["PARTY"], #UnitPopupMenus["PARTY"] - 1, "SET_MAIN")
self:SecureHook("UnitPopup_ShowMenu")
leftBracket, rightBracket = self.db.profile.leftBracket, self.db.profile.rightBracket
mod:EnableGuildNotes(mod.db.profile.guildNotes)
for i = 1, NUM_CHAT_WINDOWS do
local cf = _G["ChatFrame" .. i]
if cf ~= COMBATLOG then
self:RawHook(cf, "AddMessage", true)
end
end
for index,name in ipairs(self.TempChatFrames) do
local cf = _G[name]
if cf then
self:RawHook(cf, "AddMessage", true)
end
end
self.colorMod = Chatter:GetModule("Player Class Colors")
end
local types = {"SELF", "PLAYER", "FRIEND", "PARTY"}
function mod:OnDisable()
for j = 1, #types do
local t = types[j]
for i = 1, #UnitPopupMenus[t] do
if UnitPopupMenus[t][i] == "SET_MAIN" then
tremove(UnitPopupMenus[t], i)
break
end
end
end
mod:EnableGuildNotes(false)
end
function mod.GetMainName()
local alt = UIDROPDOWNMENU_INIT_MENU.name
local popup = StaticPopup_Show("MENUITEM_SET_MAIN", alt)
if popup then
popup.data = alt
local editbox = getglobal(popup:GetName().."EditBox")
editbox:SetText(NAMES[alt] or GUILDNOTES[alt] or "")
editbox:HighlightText()
end
end
function mod:UnitPopup_ShowMenu(dropdownMenu, which, unit, name, userData, ...)
for i=1, UIDROPDOWNMENU_MAXBUTTONS do
local button = _G["DropDownList"..UIDROPDOWNMENU_MENU_LEVEL.."Button"..i];
if button.value == "SET_MAIN" then
button.func = UnitPopupButtons["SET_MAIN"].func
end
end
end
function mod:AddAlt(alt, main)
if #main == 0 then
if GUILDNOTES[alt] then
-- let the user store an empty note, meaning "dont show me this main"
else
main = nil
end
end
NAMES[alt] = main
end
local function pName(msg, name)
if name and #name > 0 then
local alt = NAMES[name] or GUILDNOTES[name]
if alt and alt ~= "" then -- empty notes can be stored to override guildnote data
local mode = mod.db.profile.colorMode
if mode == "CUSTOM" then
alt = customColorNames[alt]
elseif mode == "COLOR_MOD" and mod.colorMod and mod.colorMod:IsEnabled() then
alt = mod.colorMod:ColorName(alt)
end
return ("%s%s%s%s"):format( msg, leftBracket, alt, rightBracket )
end
end
return msg
end
function mod:AddMessage(frame, text, ...)
if text and type(text) == "string" then
--text = text:gsub("(|Hplayer:([^:]+)[:%d+]*|h.-|h)", pName)
text = text:gsub("(|Hplayer:([^:]+).-|h.-|h)", pName)
end
return self.hooks[frame].AddMessage(frame, text, ...)
end
function mod:Info()
return L["Enables you to right-click a person's name in chat and set a note on them to be displayed in chat, such as their main character's name. Can also scan guild notes for character names to display, if no note has been manually set."]
end
function mod:EnableGuildNotes(enable)
GUILDNOTES={}
if enable then
mod:RegisterEvent("GUILD_ROSTER_UPDATE")
if IsInGuild() then
GuildRoster()
end
mod:ScanGuildNotes() -- Unfortunately we can't count on GuildRoster() triggering the event if someone else triggered it recently. So we try once at first straight off the bat.
else
mod:UnregisterEvent("GUILD_ROSTER_UPDATE")
end
end
local doscan=true -- always the first time we start up
function mod:GUILD_ROSTER_UPDATE(event,arg1)
-- arg1 gets set for SOME changes to the guild, but notably not for player notes.. doh (unless you're the one editing them yourself)
-- we force a scan when the guild frame is actually visible (i.e. when we know the player is actually interested in seeing changes)
-- i'd like to be able to not have the guildframe check there, but there's plenty of stupid-ass addons that spam GuildRoster() every 10/15/20 seconds, so ... no.
if arg1 or GuildFrame:IsVisible() or doscan then
doscan=false
mod:ScanGuildNotes()
end
if arg1 then
-- but it appears that when arg1 is set, the player note change isn't available yet; that happens on the next arg1=nil update (about 0.1s later), so catch that one too. ghod this is messy.
doscan=true
end
end
function mod:ScanGuildNotes()
if not IsInGuild() then
return
end
--DBG print("Scanning guildnotes!")
--DBG local n,nFallback=0,0
local names = {} -- ["playername"]="Playername" (note lowercase = uppercase) (yes, this works for 'foreign' letters too in WoW, even though it does not in standard Lua)
GUILDNOTES = {} -- Yes, we do want to zap it, otherwise we end up storing notes for people being promoted/demoted through alt ranks and stuff
-- #1: find all names
for i=1,GetNumGuildMembers(true) do
local name = GetGuildRosterInfo(i)
names[strlower(name or "?")] = name
end
-- #2: scan all words in all guild notes, see if a name is mentioned
for i=1,GetNumGuildMembers(true) do
local name, rank, rankIndex, level, class, zone, note, officernote, online, status = GetGuildRosterInfo(i);
local success
for word in gmatch(strlower(note or ""), "[%a\128-\255]+") do
if names[word] then
GUILDNOTES[name] = names[word]
success = true
--DBG n=n+1
break
end
end
if not success and mod.db.profile.altNotesFallback and note and note~="" then
-- #3: no joy? then if this is an 'alt' rank, use the entire note
rank=strlower(rank or "")
if strfind(rank, "alt") or
strfind(rank, L["alt2"]) or
strfind(rank, L["alt3"]) then
GUILDNOTES[name] = note
--DBG print("Fallback: ",note)
--DBG nFallback=nFallback+1
end
end
end
--DBG print("Mapped",n,"names and",nFallback,"fallbacks!")
end