ElvUI/Modules/Chat/Chat.lua

3316 lines
116 KiB
Lua
Raw Permalink Normal View History

2020-11-13 14:27:50 -05:00
local E, L, V, P, G = unpack(select(2, ...)); --Import: Engine, Locales, PrivateDB, ProfileDB, GlobalDB
local CH = E:GetModule('Chat')
local LO = E:GetModule('Layout')
local Skins = E:GetModule('Skins')
local LSM = E.Libs.LSM
local _G = _G
local gsub, strfind, gmatch, format, max = gsub, strfind, gmatch, format, max
local ipairs, sort, wipe, date, time, difftime = ipairs, sort, wipe, date, time, difftime
local pairs, unpack, select, tostring, pcall, next, tonumber, type = pairs, unpack, select, tostring, pcall, next, tonumber, type
local strlower, strsub, strlen, strupper, strtrim, strmatch = strlower, strsub, strlen, strupper, strtrim, strmatch
local tinsert, tremove, tconcat = tinsert, tremove, table.concat
local Ambiguate = Ambiguate
local BetterDate = BetterDate
local BNGetNumFriendInvites = BNGetNumFriendInvites
local BNGetNumFriends = BNGetNumFriends
local CreateFrame = CreateFrame
local FlashClientIcon = FlashClientIcon
local GetAchievementInfo = GetAchievementInfo
local GetAchievementInfoFromHyperlink = GetAchievementInfoFromHyperlink
local GetBNPlayerCommunityLink = GetBNPlayerCommunityLink
local GetBNPlayerLink = GetBNPlayerLink
local GetChannelName = GetChannelName
local GetChatWindowInfo = GetChatWindowInfo
local GetCursorPosition = GetCursorPosition
local GetCVar, GetCVarBool = GetCVar, GetCVarBool
local GetGuildRosterMOTD = GetGuildRosterMOTD
local GetInstanceInfo = GetInstanceInfo
local GetItemInfoFromHyperlink = GetItemInfoFromHyperlink
local GetMouseFocus = GetMouseFocus
local GetNumGroupMembers = GetNumGroupMembers
local GetPlayerCommunityLink = GetPlayerCommunityLink
local GetPlayerInfoByGUID = GetPlayerInfoByGUID
local GetPlayerLink = GetPlayerLink
local GetRaidRosterInfo = GetRaidRosterInfo
local GMChatFrame_IsGM = GMChatFrame_IsGM
local GMError = GMError
local hooksecurefunc = hooksecurefunc
local InCombatLockdown = InCombatLockdown
local IsAltKeyDown = IsAltKeyDown
local IsInRaid, IsInGroup = IsInRaid, IsInGroup
local IsShiftKeyDown = IsShiftKeyDown
local PlaySound = PlaySound
local PlaySoundFile = PlaySoundFile
local RemoveExtraSpaces = RemoveExtraSpaces
local RemoveNewlines = RemoveNewlines
local ToggleFrame = ToggleFrame
local ToggleQuickJoinPanel = ToggleQuickJoinPanel
local UnitExists, UnitIsUnit = UnitExists, UnitIsUnit
local UnitGroupRolesAssigned = UnitGroupRolesAssigned
local IsActivePlayerMentor = IsActivePlayerMentor
local UnitName = UnitName
local C_DateAndTime_GetCurrentCalendarTime = C_DateAndTime.GetCurrentCalendarTime
local C_PlayerMentorship_IsActivePlayerConsideredNewcomer = C_PlayerMentorship.IsActivePlayerConsideredNewcomer
local C_BattleNet_GetAccountInfoByID = C_BattleNet.GetAccountInfoByID
local C_BattleNet_GetFriendAccountInfo = C_BattleNet.GetFriendAccountInfo
local C_BattleNet_GetFriendGameAccountInfo = C_BattleNet.GetFriendGameAccountInfo
local C_BattleNet_GetFriendNumGameAccounts = C_BattleNet.GetFriendNumGameAccounts
local C_ChatInfo_GetChannelRuleset = C_ChatInfo.GetChannelRuleset
local C_Club_GetInfoFromLastCommunityChatLine = C_Club.GetInfoFromLastCommunityChatLine
local C_LFGList_GetActivityInfo = C_LFGList.GetActivityInfo
local C_LFGList_GetSearchResultInfo = C_LFGList.GetSearchResultInfo
local C_SocialGetLastItem = C_Social.GetLastItem
local C_SocialIsSocialEnabled = C_Social.IsSocialEnabled
local C_SocialQueue_GetGroupMembers = C_SocialQueue.GetGroupMembers
local C_SocialQueue_GetGroupQueues = C_SocialQueue.GetGroupQueues
local C_VoiceChat_GetMemberName = C_VoiceChat.GetMemberName
local C_VoiceChat_SetPortraitTexture = C_VoiceChat.SetPortraitTexture
local ChatChannelRuleset_Mentor = Enum.ChatChannelRuleset.Mentor
local SOCIAL_QUEUE_QUEUED_FOR = gsub(SOCIAL_QUEUE_QUEUED_FOR, ':%s?$', '') --some language have `:` on end
local BNET_CLIENT_WOW = BNET_CLIENT_WOW
local LFG_LIST_AND_MORE = LFG_LIST_AND_MORE
local UNKNOWN = UNKNOWN
-- GLOBALS: ElvCharacterDB
CH.GuidCache = {}
CH.ClassNames = {}
CH.Keywords = {}
CH.PluginMessageFilters = {}
CH.Smileys = {}
CH.TalkingList = {}
local lfgRoles = {}
local throttle = {}
local PLAYER_REALM = E:ShortenRealm(E.myrealm)
local PLAYER_NAME = format('%s-%s', E.myname, PLAYER_REALM)
local DEFAULT_STRINGS = {
GUILD = L["G"],
PARTY = L["P"],
RAID = L["R"],
OFFICER = L["O"],
PARTY_LEADER = L["PL"],
RAID_LEADER = L["RL"],
INSTANCE_CHAT = L["I"],
INSTANCE_CHAT_LEADER = L["IL"],
PET_BATTLE_COMBAT_LOG = _G.PET_BATTLE_COMBAT_LOG,
}
local hyperlinkTypes = {
achievement = true,
apower = true,
currency = true,
enchant = true,
glyph = true,
instancelock = true,
item = true,
keystone = true,
quest = true,
spell = true,
talent = true,
unit = true
}
local tabTexs = {
'',
'Selected',
'Highlight'
}
local historyTypes = { -- the events set on the chats are still in FindURL_Events, this is used to ignore some types only
CHAT_MSG_WHISPER = 'WHISPER',
CHAT_MSG_WHISPER_INFORM = 'WHISPER',
CHAT_MSG_BN_WHISPER = 'WHISPER',
CHAT_MSG_BN_WHISPER_INFORM = 'WHISPER',
CHAT_MSG_GUILD = 'GUILD',
CHAT_MSG_GUILD_ACHIEVEMENT = 'GUILD',
CHAT_MSG_OFFICER = 'OFFICER',
CHAT_MSG_PARTY = 'PARTY',
CHAT_MSG_PARTY_LEADER = 'PARTY',
CHAT_MSG_RAID = 'RAID',
CHAT_MSG_RAID_LEADER = 'RAID',
CHAT_MSG_RAID_WARNING = 'RAID',
CHAT_MSG_INSTANCE_CHAT = 'INSTANCE',
CHAT_MSG_INSTANCE_CHAT_LEADER = 'INSTANCE',
CHAT_MSG_CHANNEL = 'CHANNEL',
CHAT_MSG_SAY = 'SAY',
CHAT_MSG_YELL = 'YELL',
CHAT_MSG_EMOTE = 'EMOTE' -- this never worked, check it sometime.
}
local canChangeMessage = function(arg1, id)
if id and arg1 == '' then return id end
end
function CH:MessageIsProtected(message)
return message and (message ~= gsub(message, '(:?|?)|K(.-)|k', canChangeMessage))
end
function CH:RemoveSmiley(key)
if key and (type(key) == 'string') then
CH.Smileys[key] = nil
end
end
function CH:AddSmiley(key, texture)
if key and (type(key) == 'string' and not strfind(key, ':%%', 1, true)) and texture then
CH.Smileys[key] = texture
end
end
local rolePaths = {
TANK = E:TextureString(E.Media.Textures.Tank, ':15:15:0:0:64:64:2:56:2:56'),
HEALER = E:TextureString(E.Media.Textures.Healer, ':15:15:0:0:64:64:2:56:2:56'),
DAMAGER = E:TextureString(E.Media.Textures.DPS, ':15:15')
}
local specialChatIcons
do --this can save some main file locals
local x, y = ':16:16',':13:25'
local ElvBlue = E:TextureString(E.Media.ChatLogos.ElvBlue,y)
local ElvGreen = E:TextureString(E.Media.ChatLogos.ElvGreen,y)
local ElvOrange = E:TextureString(E.Media.ChatLogos.ElvOrange,y)
--local ElvPink = E:TextureString(E.Media.ChatLogos.ElvPink,y)
local ElvPurple = E:TextureString(E.Media.ChatLogos.ElvPurple,y)
local ElvRed = E:TextureString(E.Media.ChatLogos.ElvRed,y)
local ElvYellow = E:TextureString(E.Media.ChatLogos.ElvYellow,y)
local ElvSimpy = E:TextureString(E.Media.ChatLogos.ElvSimpy,y)
local Bathrobe = E:TextureString(E.Media.ChatLogos.Bathrobe,x)
local MrHankey = E:TextureString(E.Media.ChatLogos.MrHankey,x)
local Rainbow = E:TextureString(E.Media.ChatLogos.Rainbow,x)
local Hibiscus = E:TextureString(E.Media.ChatLogos.Hibiscus,x)
local Clover = E:TextureString(E.Media.ChatLogos.Clover,x)
local GoldShield = E:TextureString(E.Media.ChatLogos.GoldShield,x)
local DeathlyH = E:TextureString(E.Media.ChatLogos.DeathlyHallows,x)
local Gem = E:TextureString(E.Media.ChatLogos.Gem,x)
--[[ Simpys Thing: new icon color every message, in order then reversed back, repeating of course
local a, b, c = 0, false, {ElvRed, ElvOrange, ElvYellow, ElvGreen, ElvBlue, ElvPurple, ElvPink}
(a = a - (b and 1 or -1) if (b and a == 1 or a == 0) or a == #c then b = not b end return c[a])
]]
local itsElv, itsMis, itsMel, itsSimpy, itsTheFlyestNihilist
do --Simpy Chaos: super cute text coloring function that ignores hyperlinks and keywords
local e, f, g = {'||','|[Cc].-|[Rr]','|[TA].-|[ta]','|H.-|h.-|h'}, {}, {}
local prettify = function(t,...) return gsub(gsub(E:TextGradient(gsub(gsub(t,'%%%%','\27'),'\124\124','\26'),...),'\27','%%%%'),'\26','||') end
local protectText = function(t, u, v) local w = E:EscapeString(v) local r, s = strfind(u, w) while f[r] do r, s = strfind(u, w, s) end if r then tinsert(g, r) f[r] = w end return gsub(t, w, '\24') end
local specialText = function(t,...) local u = t for _, w in ipairs(e) do for k in gmatch(t, w) do t = protectText(t, u, k) end end t = prettify(t,...)
if next(g) then if #g > 1 then sort(g) end for n in gmatch(t, '\24') do local _, v = next(g) t = gsub(t, n, f[v], 1) tremove(g, 1) f[v] = nil end end return t
end
--Simpys Ghoulish: A366CC, 88E032, 33BDBE, 88E032, A366CC
local SimpyColors = function(t) return specialText(t, 0.63,0.40,0.80, 0.63,0.40,0.80, 0.53,0.87,0.19, 0.53,0.87,0.19, 0.20,0.74,0.74, 0.20,0.74,0.74, 0.53,0.87,0.19, 0.53,0.87,0.19, 0.63,0.40,0.80, 0.63,0.40,0.80) end
--Detroit Lions: Honolulu Blue to Silver [Elv: I stoles it @Simpy]
local ElvColors = function(t) return specialText(t, 0,0.42,0.69, 0.61,0.61,0.61) end
--Rainbow: FD3E44, FE9849, FFDE4B, 6DFD65, 54C4FC, A35DFA, C679FB, FE81C1
local MisColors = function(t) return specialText(t, 0.99,0.24,0.26, 0.99,0.59,0.28, 1.00,0.87,0.29, 0.42,0.99,0.39, 0.32,0.76,0.98, 0.63,0.36,0.98, 0.77,0.47,0.98, 0.99,0.50,0.75) end
--Light Spring: 50DAD3, 56E580, D8DA33, DFA455, EE8879, F972D1, B855DF, 50DAD3
local MelColors = function(t) return specialText(t, 0.31,0.85,0.82, 0.33,0.89,0.50, 0.84,0.85,0.20, 0.87,0.64,0.33, 0.93,0.53,0.47, 0.97,0.44,0.81, 0.72,0.33,0.87, 0.31,0.85,0.82) end
--Class: Normal to Negative (Orange->Blue, Red->Cyan, etc)
local nm = function(c) return max(1-c,0.15) end
local NihiColors = function(class) local c = _G.RAID_CLASS_COLORS[class] local c1,c2,c3, n1,n2,n3 = c.r,c.g,c.b, nm(c.r), nm(c.g), nm(c.b) return function(t) return specialText(t, c1,c2,c3, n1,n2,n3, c1,c2,c3, n1,n2,n3) end end
itsSimpy = function() return ElvSimpy, SimpyColors end
itsElv = function() return ElvBlue, ElvColors end
itsMel = function() return Hibiscus, MelColors end
itsMis = function() return Rainbow, MisColors end
itsTheFlyestNihilist = function(class) local icon, prettyText = E:TextureString(E.Media.ChatLogos['Fox'..class],x), NihiColors(strupper(class)) return function() return icon, prettyText end end
end
specialChatIcons = {
-- Elv
['Elv-Spirestone'] = itsElv,
['Elvz-Spirestone'] = itsElv,
['Fleshlite-Spirestone'] = itsElv,
['Elvidan-Spirestone'] = itsElv,
['Elvilas-Spirestone'] = itsElv,
['Fraku-Spirestone'] = itsElv,
['Jarvix-Spirestone'] = itsElv,
['Watermelon-Spirestone'] = itsElv,
['Zinxbe-Spirestone'] = itsElv,
['Whorlock-Spirestone'] = itsElv,
-- Blazeflack
['Blazii-Silvermoon'] = ElvBlue, -- Priest
['Chazii-Silvermoon'] = ElvBlue, -- Shaman
-- Affinity
['Affinichi-Illidan'] = Bathrobe,
['Affinitii-Illidan'] = Bathrobe,
['Affinity-Illidan'] = Bathrobe,
['Uplift-Illidan'] = Bathrobe,
-- Tirain (NOTE: lol)
['Tierone-Spirestone'] = 'Dr. ',
['Tirain-Spirestone'] = MrHankey,
['Sinth-Spirestone'] = MrHankey,
['Tee-Spirestone'] = MrHankey,
-- Mis (NOTE: I will forever have the picture you accidently shared of the manikin wearing a strapon burned in my brain)
['Misdîrect-Spirestone'] = itsMis,
['Misoracle-Spirestone'] = itsMis,
['MisLight-Spirestone'] = itsMis,
['MisDivine-Spirestone'] = itsMis,
['MisMayhem-Spirestone'] = itsMis,
['Mismonk-Spirestone'] = itsMis,
['Misillidan-Spirestone'] = itsMis,
['Mispel-Spirestone'] = itsMis,
['Misdecay-Spirestone'] = itsMis,
['Mislust-Spirestone'] = itsMis,
['Misdivine-Spirestone'] = itsMis,
['Mislight-Spirestone'] = itsMis,
-- Luckyone
['Luckyone-LaughingSkull'] = Clover,
['Luckypriest-LaughingSkull'] = Clover,
['Luckymonkas-LaughingSkull'] = Clover,
['Luckydk-LaughingSkull'] = Clover,
['Luckyhunter-LaughingSkull'] = Clover,
['Unluckyone-LaughingSkull'] = Clover,
['Notlucky-LaughingSkull'] = Clover,
['Luckymage-LaughingSkull'] = Clover,
['Luckydh-LaughingSkull'] = Clover,
['Luckywl-LaughingSkull'] = Clover,
['Luckyrogue-LaughingSkull'] = Clover,
['Luckypala-LaughingSkull'] = Clover,
-- NihilisticPandemonium
['Dirishia-WyrmrestAccord'] = itsTheFlyestNihilist('Warlock'),
['Xanikani-WyrmrestAccord'] = itsTheFlyestNihilist('Mage'),
['Rikanza-WyrmrestAccord'] = itsTheFlyestNihilist('Monk'),
['Onaguda-WyrmrestAccord'] = itsTheFlyestNihilist('Druid'),
['Cerishia-WyrmrestAccord'] = itsTheFlyestNihilist('Priest'),
['Vellilara-WyrmrestAccord'] = itsTheFlyestNihilist('DemonHunter'),
['Sayalia-WyrmrestAccord'] = itsTheFlyestNihilist('DeathKnight'),
['Pakasta-WyrmrestAccord'] = itsTheFlyestNihilist('Paladin'),
['Orlyrala-WyrmrestAccord'] = itsTheFlyestNihilist('Shaman'),
['Scerila-WyrmrestAccord'] = itsTheFlyestNihilist('Rogue'),
['Ralaniki-WyrmrestAccord'] = itsTheFlyestNihilist('Hunter'),
['Moyanza-WyrmrestAccord'] = itsTheFlyestNihilist('Warrior'),
['Erasaya-WyrmrestAccord'] = itsTheFlyestNihilist('DeathKnight'),
['Linabla-WyrmrestAccord'] = itsTheFlyestNihilist('Druid'),
['Dirikoa-WyrmrestAccord'] = itsTheFlyestNihilist('Hunter'),
['Elaedarel-WyrmrestAccord'] = itsTheFlyestNihilist('Warlock'),
['Alydrer-WyrmrestAccord'] = itsTheFlyestNihilist('Warlock'),
['Issia-WyrmrestAccord'] = itsTheFlyestNihilist('Priest'),
['Leitara-WyrmrestAccord'] = itsTheFlyestNihilist('Warrior'),
['Cherlyth-WyrmrestAccord'] = itsTheFlyestNihilist('Druid'),
['Tokashami-WyrmrestAccord'] = itsTheFlyestNihilist('Shaman'),
['Millop-WyrmrestAccord'] = itsTheFlyestNihilist('Hunter'),
['Aeondalew-WyrmrestAccord'] = itsTheFlyestNihilist('DeathKnight'),
-- Merathilis
['Asragoth-Shattrath'] = ElvPurple, -- [Alliance] Warlock
['Brítt-Shattrath'] = ElvBlue, -- [Alliance] Warrior
['Damará-Shattrath'] = ElvRed, -- [Alliance] Paladin
['Jazira-Shattrath'] = ElvBlue, -- [Alliance] Priest
['Jústice-Shattrath'] = ElvYellow, -- [Alliance] Rogue
['Maithilis-Shattrath'] = ElvGreen, -- [Alliance] Monk
['Mattdemôn-Shattrath'] = ElvPurple, -- [Alliance] DH
['Melisendra-Shattrath'] = ElvBlue, -- [Alliance] Mage
['Merathilis-Shattrath'] = ElvOrange, -- [Alliance] Druid
['Merathilîs-Shattrath'] = ElvBlue, -- [Alliance] Shaman
['Róhal-Shattrath'] = ElvGreen, -- [Alliance] Hunter
-- Simpy
['Arieva-Cenarius'] = itsSimpy, -- Hunter
['Buddercup-Cenarius'] = itsSimpy, -- Rogue
['Cutepally-Cenarius'] = itsSimpy, -- Paladin
['Ezek-Cenarius'] = itsSimpy, -- DK
['Glice-Cenarius'] = itsSimpy, -- Warrior
['Kalline-Cenarius'] = itsSimpy, -- Shaman
['Puttietat-Cenarius'] = itsSimpy, -- Druid
['Simpy-Cenarius'] = itsSimpy, -- Warlock
['Twigly-Cenarius'] = itsSimpy, -- Monk
['Imsobeefy-Cenarius'] = itsSimpy, -- [Horde] Shaman
['Imsocheesy-Cenarius'] = itsSimpy, -- [Horde] Priest
['Imsojelly-Cenarius'] = itsSimpy, -- [Horde] DK
['Imsojuicy-Cenarius'] = itsSimpy, -- [Horde] Druid
['Imsopeachy-Cenarius'] = itsSimpy, -- [Horde] DH
['Imsosalty-Cenarius'] = itsSimpy, -- [Horde] Paladin
['Imsospicy-Cenarius'] = itsSimpy, -- [Horde] Mage
['Imsonutty-Cenarius'] = itsSimpy, -- [Horde] Hunter
['Imsotasty-Cenarius'] = itsSimpy, -- [Horde] Monk
['Imsosaucy-Cenarius'] = itsSimpy, -- [Horde] Warlock
['Imsodrippy-Cenarius'] = itsSimpy, -- [Horde] Rogue
['Bunne-CenarionCircle'] = itsSimpy, -- [RP] Warrior
['Loppie-CenarionCircle'] = itsSimpy, -- [RP] Monk
['Loppybunny-CenarionCircle'] = itsSimpy, -- [RP] Mage
['Rubee-CenarionCircle'] = itsSimpy, -- [RP] DH
['Wennie-CenarionCircle'] = itsSimpy, -- [RP] Priest
-- Melbelle (Simpys Bestie)
['Melbelle-Bladefist'] = itsMel, -- Hunter
['Deathchaser-Bladefist'] = itsMel, -- DH
['Alyosha-Cenarius'] = itsMel, -- Warrior
['Dãwn-Cenarius'] = itsMel, -- Paladin
['Faelen-Cenarius'] = itsMel, -- Rogue
['Freckles-Cenarius'] = itsMel, -- DK
['Lõvi-Cenarius'] = itsMel, -- Priest
['Melbelle-Cenarius'] = itsMel, -- Druid
['Perìwìnkle-Cenarius'] = itsMel, -- Shaman
['Pìper-Cenarius'] = itsMel, -- Warlock
['Spãrkles-Cenarius'] = itsMel, -- Mage
['Alybones-Cenarius'] = itsMel, -- [Horde] DK
['Alyfreeze-Cenarius'] = itsMel, -- [Horde] Mage
['Alykins-Cenarius'] = itsMel, -- [Horde] DH
['Alyrage-Cenarius'] = itsMel, -- [Horde] Warrior
['Alysneaks-Cenarius'] = itsMel, -- [Horde] Rogue
['Alytotes-Cenarius'] = itsMel, -- [Horde] Shaman
-- Lulupeep (Nihilist's wife)
['Arïä-WyrmrestAccord'] = DeathlyH,
['Belladonnä-WyrmrestAccord'] = DeathlyH,
['Cadense-WyrmrestAccord'] = DeathlyH,
['Cäydence-WyrmrestAccord'] = DeathlyH,
['Esmæ-WyrmrestAccord'] = DeathlyH,
['Falorya-WyrmrestAccord'] = DeathlyH,
['Fufus-WyrmrestAccord'] = DeathlyH,
['Gemmä-WyrmrestAccord'] = DeathlyH,
['Lilliës-WyrmrestAccord'] = DeathlyH,
['Louisianagrl-WyrmrestAccord'] = DeathlyH,
['Lulupeep-WyrmrestAccord'] = DeathlyH,
['Nolalove-WyrmrestAccord'] = DeathlyH,
['Onyxnovä-WyrmrestAccord'] = DeathlyH,
['Rukíá-WyrmrestAccord'] = DeathlyH,
['Songbïrd-WyrmrestAccord'] = DeathlyH,
['Vidiä-WyrmrestAccord'] = DeathlyH,
-- Quickhanz (Nihilist's absolute bestie)
['Zandahanz-Area52'] = GoldShield,
-- AcidWeb
['Livarax-BurningLegion'] = Gem,
['Filevandrel-BurningLegion'] = Gem,
['Akavaya-BurningLegion'] = Gem,
}
end
function CH:ChatFrame_OnMouseScroll(delta)
local numScrollMessages = CH.db.numScrollMessages or 3
if delta < 0 then
if IsShiftKeyDown() then
self:ScrollToBottom()
elseif IsAltKeyDown() then
self:ScrollDown()
else
for _ = 1, numScrollMessages do
self:ScrollDown()
end
end
elseif delta > 0 then
if IsShiftKeyDown() then
self:ScrollToTop()
elseif IsAltKeyDown() then
self:ScrollUp()
else
for _ = 1, numScrollMessages do
self:ScrollUp()
end
end
if CH.db.scrollDownInterval ~= 0 then
if self.ScrollTimer then
CH:CancelTimer(self.ScrollTimer, true)
end
self.ScrollTimer = CH:ScheduleTimer('ScrollToBottom', CH.db.scrollDownInterval, self)
end
end
end
function CH:GetGroupDistribution()
local _, instanceType = GetInstanceInfo()
if instanceType == 'pvp' then return '/bg ' end
if IsInRaid() then return '/ra ' end
if IsInGroup() then return '/p ' end
return '/s '
end
function CH:InsertEmotions(msg)
for word in gmatch(msg, '%s-%S+%s*') do
word = strtrim(word)
local pattern = E:EscapeString(word)
local emoji = CH.Smileys[pattern]
if emoji and strmatch(msg, '[%s%p]-'..pattern..'[%s%p]*') then
local base64 = E.Libs.Base64:Encode(word) -- btw keep `|h|cFFffffff|r|h` as it is
msg = gsub(msg, '([%s%p]-)'..pattern..'([%s%p]*)', (base64 and ('%1|Helvmoji:%%'..base64..'|h|cFFffffff|r|h') or '%1')..emoji..'%2')
end
end
return msg
end
function CH:GetSmileyReplacementText(msg)
if not msg or not CH.db.emotionIcons or strfind(msg, '/run') or strfind(msg, '/dump') or strfind(msg, '/script') then return msg end
local outstr = ''
local origlen = strlen(msg)
local startpos = 1
local endpos, _
while(startpos <= origlen) do
local pos = strfind(msg,'|H',startpos,true)
endpos = pos or origlen
outstr = outstr .. CH:InsertEmotions(strsub(msg,startpos,endpos)) --run replacement on this bit
startpos = endpos + 1
if pos ~= nil then
_, endpos = strfind(msg,'|h.-|h',startpos)
endpos = endpos or origlen
if startpos < endpos then
outstr = outstr .. strsub(msg,startpos,endpos) --don't run replacement on this bit
startpos = endpos + 1
end
end
end
return outstr
end
function CH:CopyButtonOnMouseUp(btn)
local chat = self:GetParent()
if btn == 'RightButton' and chat:GetID() == 1 then
ToggleFrame(_G.ChatMenu)
else
CH:CopyChat(chat)
end
end
function CH:CopyButtonOnEnter()
self:SetAlpha(1)
end
function CH:CopyButtonOnLeave()
local chat = self:GetParent()
if _G[chat:GetName()..'TabText']:IsShown() then
self:SetAlpha(0.35)
else
self:SetAlpha(0)
end
end
function CH:ChatFrameTab_SetAlpha(_, skip)
if skip then return end
local chat = CH:GetOwner(self)
self:SetAlpha((not chat.isDocked or self.selected) and 1 or 0.6, true)
end
do
local charCount
function CH:CountLinkCharacters()
charCount = charCount + (strlen(self) + 4) -- 4 is ending '|h|r'
end
local repeatedText
function CH:EditBoxOnTextChanged()
local text = self:GetText()
local len = strlen(text)
if (not repeatedText or not strfind(text, repeatedText, 1, true)) and InCombatLockdown() then
local MIN_REPEAT_CHARACTERS = CH.db.numAllowedCombatRepeat
if len > MIN_REPEAT_CHARACTERS then
local repeatChar = true
for i = 1, MIN_REPEAT_CHARACTERS, 1 do
local first = -1 - i
if strsub(text,-i,-i) ~= strsub(text,first,first) then
repeatChar = false
break
end
end
if repeatChar then
repeatedText = text
self:Hide()
return
end
end
end
if len == 4 then
if text == '/tt ' then
local Name, Realm = UnitName('target')
if Name then
Name = gsub(Name,'%s','')
if Realm and Realm ~= '' then
Name = format('%s-%s', Name, E:ShortenRealm(Realm))
end
end
if Name then
_G.ChatFrame_SendTell(Name, self.chatFrame)
else
_G.UIErrorsFrame:AddMessage(E.InfoColor .. L["Invalid Target"])
end
elseif text == '/gr ' then
self:SetText(CH:GetGroupDistribution() .. strsub(text, 5))
_G.ChatEdit_ParseText(self, 0)
end
end
-- recalculate the character count correctly with hyperlinks in it, using gsub so it matches multiple without gmatch
charCount = 0
gsub(text, '(|cff%x%x%x%x%x%x|H.-|h).-|h|r', CH.CountLinkCharacters)
if charCount ~= 0 then len = len - charCount end
self.characterCount:SetText(len > 0 and (255 - len) or '')
if repeatedText then
repeatedText = nil
end
end
end
function CH:EditBoxOnKeyDown(key)
--Work around broken SetAltArrowKeyMode API. Code from Prat and modified by Simpy
if (not self.historyLines) or #self.historyLines == 0 then
return
end
if key == 'DOWN' then
self.historyIndex = self.historyIndex - 1
if self.historyIndex < 1 then
self.historyIndex = 0
self:SetText('')
return
end
elseif key == 'UP' then
self.historyIndex = self.historyIndex + 1
if self.historyIndex > #self.historyLines then
self.historyIndex = #self.historyLines
end
else
return
end
self:SetText(strtrim(self.historyLines[#self.historyLines - (self.historyIndex - 1)]))
end
function CH:EditBoxFocusGained()
if not _G.LeftChatPanel:IsShown() then
_G.LeftChatPanel.editboxforced = true
_G.LeftChatToggleButton:OnEnter()
self:Show()
end
end
function CH:EditBoxFocusLost()
if _G.LeftChatPanel.editboxforced then
_G.LeftChatPanel.editboxforced = nil
if _G.LeftChatPanel:IsShown() then
_G.LeftChatToggleButton:OnLeave()
self:Hide()
end
end
self.historyIndex = 0
end
function CH:UpdateEditboxFont(chatFrame)
local style = GetCVar('chatStyle')
if style == 'classic' and CH.LeftChatWindow then
chatFrame = CH.LeftChatWindow
end
if chatFrame == _G.GeneralDockManager.primary then
chatFrame = _G.GeneralDockManager.selected
end
local id = chatFrame:GetID()
local font = LSM:Fetch('font', CH.db.font)
local _, fontSize = _G.FCF_GetChatWindowInfo(id)
local editbox = _G.ChatEdit_ChooseBoxForSend(chatFrame)
editbox:FontTemplate(font, fontSize, 'NONE')
editbox.header:FontTemplate(font, fontSize, 'NONE')
if editbox.characterCount then
editbox.characterCount:FontTemplate(font, fontSize, 'NONE')
end
-- the header and text will not update the placement without focus
if editbox and editbox:IsShown() then
editbox:SetFocus()
end
end
function CH:StyleChat(frame)
local name = frame:GetName()
local tab = CH:GetTab(frame)
local id = frame:GetID()
local _, fontSize = _G.FCF_GetChatWindowInfo(id)
local font, size, outline = LSM:Fetch('font', CH.db.font), fontSize, CH.db.fontOutline
frame:FontTemplate(font, size, outline)
frame:SetTimeVisible(CH.db.inactivityTimer)
frame:SetMaxLines(CH.db.maxLines)
frame:SetFading(CH.db.fade)
tab.Text:FontTemplate(LSM:Fetch('font', CH.db.tabFont), CH.db.tabFontSize, CH.db.tabFontOutline)
if frame.styled then return end
frame:SetFrameLevel(4)
frame:SetClampRectInsets(0,0,0,0)
frame:SetClampedToScreen(false)
frame:StripTextures(true)
_G[name..'ButtonFrame']:Kill()
local scrollTex = _G[name..'ThumbTexture']
local scrollToBottom = frame.ScrollToBottomButton
local scroll = frame.ScrollBar
local editbox = frame.editBox
if scroll then
scroll:Kill()
scrollToBottom:Kill()
scrollTex:Kill()
end
--Character count
local charCount = editbox:CreateFontString(nil, 'ARTWORK')
charCount:FontTemplate()
charCount:SetTextColor(190, 190, 190, 0.4)
charCount:Point('TOPRIGHT', editbox, 'TOPRIGHT', -5, 0)
charCount:Point('BOTTOMRIGHT', editbox, 'BOTTOMRIGHT', -5, 0)
charCount:SetJustifyH('CENTER')
charCount:Width(40)
editbox.characterCount = charCount
for _, texName in pairs(tabTexs) do
_G[name..'Tab'..texName..'Left']:SetTexture()
_G[name..'Tab'..texName..'Middle']:SetTexture()
_G[name..'Tab'..texName..'Right']:SetTexture()
end
hooksecurefunc(tab, 'SetAlpha', CH.ChatFrameTab_SetAlpha)
if not tab.left then tab.left = _G[name..'TabLeft'] end
tab.Text:ClearAllPoints()
tab.Text:Point('LEFT', tab, 'LEFT', tab.left:GetWidth(), 0)
tab:Height(22)
if tab.conversationIcon then
tab.conversationIcon:ClearAllPoints()
tab.conversationIcon:Point('RIGHT', tab.Text, 'LEFT', -1, 0)
end
local a, b, c = select(6, editbox:GetRegions()); a:Kill(); b:Kill(); c:Kill()
_G[name..'EditBoxLeft']:Kill()
_G[name..'EditBoxMid']:Kill()
_G[name..'EditBoxRight']:Kill()
editbox:SetAltArrowKeyMode(CH.db.useAltKey)
editbox:SetAllPoints(_G.LeftChatDataPanel)
editbox:HookScript('OnTextChanged', CH.EditBoxOnTextChanged)
CH:SecureHook(editbox, 'AddHistoryLine', 'ChatEdit_AddHistory')
--Work around broken SetAltArrowKeyMode API
editbox.historyLines = ElvCharacterDB.ChatEditHistory
editbox.historyIndex = 0
editbox:HookScript('OnKeyDown', CH.EditBoxOnKeyDown)
editbox:Hide()
editbox:HookScript('OnEditFocusGained', CH.EditBoxFocusGained)
editbox:HookScript('OnEditFocusLost', CH.EditBoxFocusLost)
for _, text in pairs(editbox.historyLines) do
editbox:AddHistoryLine(text)
end
--copy chat button
local copyButton = CreateFrame('Frame', format('ElvUI_CopyChatButton%d', id), frame)
copyButton:EnableMouse(true)
copyButton:SetAlpha(0.35)
copyButton:Size(20, 22)
copyButton:Point('TOPRIGHT', 0, -4)
copyButton:SetFrameLevel(frame:GetFrameLevel() + 5)
frame.copyButton = copyButton
local copyTexture = frame.copyButton:CreateTexture(nil, 'OVERLAY')
copyTexture:SetInside()
copyTexture:SetTexture(E.Media.Textures.Copy)
copyButton.texture = copyTexture
copyButton:SetScript('OnMouseUp', CH.CopyButtonOnMouseUp)
copyButton:SetScript('OnEnter', CH.CopyButtonOnEnter)
copyButton:SetScript('OnLeave', CH.CopyButtonOnLeave)
CH:ToggleChatButton(copyButton)
_G.QuickJoinToastButton:Hide()
_G.GeneralDockManagerOverflowButtonList:CreateBackdrop('Transparent')
Skins:HandleNextPrevButton(_G.GeneralDockManagerOverflowButton, 'down', nil, true)
frame.styled = true
end
function CH:GetChatTime()
local unix = time()
local realm = not CH.db.timeStampLocalTime and C_DateAndTime_GetCurrentCalendarTime()
if realm then -- blizzard is weird
realm.day = realm.monthDay
realm.min = date('%M', unix)
realm.sec = date('%S', unix)
realm = time(realm)
end
return realm or unix
end
function CH:AddMessage(msg, infoR, infoG, infoB, infoID, accessID, typeID, isHistory, historyTime)
local historyTimestamp --we need to extend the arguments on AddMessage so we can properly handle times without overriding
if isHistory == 'ElvUI_ChatHistory' then historyTimestamp = historyTime end
if CH.db.timeStampFormat and CH.db.timeStampFormat ~= 'NONE' then
local timeStamp = BetterDate(CH.db.timeStampFormat, historyTimestamp or CH:GetChatTime())
timeStamp = gsub(timeStamp, ' ', '')
timeStamp = gsub(timeStamp, 'AM', ' AM')
timeStamp = gsub(timeStamp, 'PM', ' PM')
if CH.db.useCustomTimeColor then
local color = CH.db.customTimeColor
local hexColor = E:RGBToHex(color.r, color.g, color.b)
msg = format('%s[%s]|r %s', hexColor, timeStamp, msg)
else
msg = format('[%s] %s', timeStamp, msg)
end
end
if CH.db.copyChatLines then
msg = format('|Hcpl:%s|h%s|h %s', self:GetID(), E:TextureString(E.Media.Textures.ArrowRight, ':14'), msg)
end
self.OldAddMessage(self, msg, infoR, infoG, infoB, infoID, accessID, typeID)
end
function CH:UpdateSettings()
for _, name in ipairs(_G.CHAT_FRAMES) do
_G[name..'EditBox']:SetAltArrowKeyMode(CH.db.useAltKey)
end
end
local removeIconFromLine
do
local raidIconFunc = function(x) x = x~='' and _G['RAID_TARGET_'..x];return x and ('{'..strlower(x)..'}') or '' end
local stripTextureFunc = function(w, x, y) if x=='' then return (w~='' and w) or (y~='' and y) or '' end end
local hyperLinkFunc = function(w, x, y) if w~='' then return end
local emoji = (x~='' and x) and strmatch(x, 'elvmoji:%%(.+)')
return (emoji and E.Libs.Base64:Decode(emoji)) or y
end
local fourString = function(v, w, x, y)
return format('%s%s%s', v, w, (v and v == '1' and x) or y)
end
removeIconFromLine = function(text)
text = gsub(text, [[|TInterface\TargetingFrame\UI%-RaidTargetingIcon_(%d+):0|t]], raidIconFunc) --converts raid icons into {star} etc, if possible.
text = gsub(text, '(%s?)(|?)|[TA].-|[ta](%s?)', stripTextureFunc) --strip any other texture out but keep a single space from the side(s).
text = gsub(text, '(|?)|H(.-)|h(.-)|h', hyperLinkFunc) --strip hyperlink data only keeping the actual text.
text = gsub(text, '(%d+)(.-)|4(.-):(.-);', fourString) --stuff where it goes 'day' or 'days' like played; tech this is wrong but okayish
return text
end
end
local function colorizeLine(text, r, g, b)
local hexCode = E:RGBToHex(r, g, b)
return format('%s%s|r', hexCode, text)
end
local copyLines = {}
function CH:GetLines(frame)
local index = 1
for i = 1, frame:GetNumMessages() do
local message, r, g, b = frame:GetMessageInfo(i)
if message and not CH:MessageIsProtected(message) then
--Set fallback color values
r, g, b = r or 1, g or 1, b or 1
--Remove icons
message = removeIconFromLine(message)
--Add text color
message = colorizeLine(message, r, g, b)
copyLines[index] = message
index = index + 1
end
end
return index - 1
end
function CH:CopyChat(frame)
if not _G.CopyChatFrame:IsShown() then
local _, fontSize = _G.FCF_GetChatWindowInfo(frame:GetID())
if fontSize < 10 then fontSize = 12 end
_G.FCF_SetChatWindowFontSize(frame, frame, 0.01)
_G.CopyChatFrame:Show()
local lineCt = CH:GetLines(frame)
local text = tconcat(copyLines, ' \n', 1, lineCt)
_G.FCF_SetChatWindowFontSize(frame, frame, fontSize)
_G.CopyChatFrameEditBox:SetText(text)
else
_G.CopyChatFrame:Hide()
end
end
function CH:GetOwner(tab)
if not tab.owner then
tab.owner = _G[format('ChatFrame%s', tab:GetID())]
end
return tab.owner
end
function CH:GetTab(chat)
if not chat.tab then
chat.tab = _G[format('ChatFrame%sTab', chat:GetID())]
end
return chat.tab
end
function CH:TabOnEnter(tab)
tab.Text:Show()
if tab.conversationIcon then
tab.conversationIcon:Show()
end
if not CH.db.hideCopyButton then
local chat = CH:GetOwner(tab)
if chat and chat.copyButton and GetMouseFocus() ~= chat.copyButton then
chat.copyButton:SetAlpha(0.35)
end
end
end
function CH:TabOnLeave(tab)
tab.Text:Hide()
if tab.conversationIcon then
tab.conversationIcon:Hide()
end
if not CH.db.hideCopyButton then
local chat = CH:GetOwner(tab)
if chat and chat.copyButton and GetMouseFocus() ~= chat.copyButton then
chat.copyButton:SetAlpha(0)
end
end
end
function CH:ChatOnEnter(chat)
CH:TabOnEnter(CH:GetTab(chat))
end
function CH:ChatOnLeave(chat)
CH:TabOnLeave(CH:GetTab(chat))
end
function CH:HandleFadeTabs(chat, hook)
local tab = CH:GetTab(chat)
if hook then
if not CH.hooks or not CH.hooks[chat] or not CH.hooks[chat].OnEnter then
CH:HookScript(chat, 'OnEnter', 'ChatOnEnter')
CH:HookScript(chat, 'OnLeave', 'ChatOnLeave')
end
if not CH.hooks or not CH.hooks[tab] or not CH.hooks[tab].OnEnter then
CH:HookScript(tab, 'OnEnter', 'TabOnEnter')
CH:HookScript(tab, 'OnLeave', 'TabOnLeave')
end
else
if CH.hooks and CH.hooks[chat] and CH.hooks[chat].OnEnter then
CH:Unhook(chat, 'OnEnter')
CH:Unhook(chat, 'OnLeave')
end
if CH.hooks and CH.hooks[tab] and CH.hooks[tab].OnEnter then
CH:Unhook(tab, 'OnEnter')
CH:Unhook(tab, 'OnLeave')
end
end
local focus = GetMouseFocus()
if not hook then
CH:TabOnEnter(tab)
elseif focus ~= tab and focus ~= chat then
CH:TabOnLeave(tab)
end
end
function CH:ChatEdit_SetLastActiveWindow(editbox)
local style = editbox.chatStyle or GetCVar('chatStyle')
if style == 'im' then editbox:SetAlpha(0.5) end
end
function CH:FCFDock_SelectWindow(_, chatFrame)
if chatFrame then
CH:UpdateEditboxFont(chatFrame)
end
end
function CH:ChatEdit_ActivateChat(editbox)
if editbox and editbox.chatFrame then
CH:UpdateEditboxFont(editbox.chatFrame)
end
end
function CH:ChatEdit_DeactivateChat(editbox)
local style = editbox.chatStyle or GetCVar('chatStyle')
if style == 'im' then editbox:Hide() end
end
function CH:UpdateEditboxAnchors()
local cvar = (type(self) == 'string' and self) or GetCVar('chatStyle')
local classic = cvar == 'classic'
local leftChat = classic and _G.LeftChatPanel
local bottomheight, topheight = 1, 0
local width = classic and 0 or 5
local panel_height = 22
for _, name in ipairs(_G.CHAT_FRAMES) do
local frame = _G[name]
local editbox = frame and frame.editBox
if not editbox then return end
editbox.chatStyle = cvar
local anchorTo = leftChat or frame
editbox:ClearAllPoints()
if CH.db.editBoxPosition == 'BELOW_CHAT' then
if not classic then bottomheight, topheight = 6, -4 end
editbox:Point('TOPLEFT', anchorTo, 'BOTTOMLEFT', -width, topheight)
editbox:Point('BOTTOMRIGHT', anchorTo, 'BOTTOMRIGHT', width, -(panel_height+bottomheight))
else
if not classic then bottomheight, topheight = 5, 3 end
editbox:Point('BOTTOMLEFT', anchorTo, 'TOPLEFT', -width, topheight)
editbox:Point('TOPRIGHT', anchorTo, 'TOPRIGHT', width, panel_height+bottomheight)
end
end
end
function CH:FindChatWindows()
if not CH.db.panelSnapping then return end
local left, right = CH.LeftChatWindow, CH.RightChatWindow
-- they already exist just return them :)
if left and right then
return left, right
end
local docker = _G.GeneralDockManager.primary
for _, name in ipairs(_G.CHAT_FRAMES) do
local chat = _G[name]
if (chat.isDocked and docker) or chat:IsShown() then
if not left and E:FramesOverlap(chat, _G.LeftChatPanel) then
left = chat
elseif not right and E:FramesOverlap(chat, _G.RightChatPanel) then
right = chat
end
-- if both are found just return now, don't wait
if left and right then
return left, right
end
end
end
-- none or one was found
return left, right
end
function CH:GetDockerParent(docker, chat)
if not docker then return end
local _, relativeTo = chat:GetPoint()
if relativeTo == docker then
return docker:GetParent()
end
end
function CH:UpdateChatTab(chat)
local fadeLeft, fadeRight
if CH.db.fadeTabsNoBackdrop then
local both = CH.db.panelBackdrop == 'HIDEBOTH'
fadeLeft = (both or CH.db.panelBackdrop == 'RIGHT')
fadeRight = (both or CH.db.panelBackdrop == 'LEFT')
end
if chat == CH.LeftChatWindow then
CH:GetTab(chat):SetParent(_G.LeftChatPanel or _G.UIParent)
chat:SetParent(_G.LeftChatPanel or _G.UIParent)
CH:HandleFadeTabs(chat, fadeLeft)
elseif chat == CH.RightChatWindow then
CH:GetTab(chat):SetParent(_G.RightChatPanel or _G.UIParent)
chat:SetParent(_G.RightChatPanel or _G.UIParent)
CH:HandleFadeTabs(chat, fadeRight)
else
local docker = _G.GeneralDockManager.primary
local parent = CH:GetDockerParent(docker, chat)
-- we need to update the tab parent to mimic the docker
CH:GetTab(chat):SetParent(parent or _G.UIParent)
chat:SetParent(parent or _G.UIParent)
if parent and docker == CH.LeftChatWindow then
CH:HandleFadeTabs(chat, fadeLeft)
elseif parent and docker == CH.RightChatWindow then
CH:HandleFadeTabs(chat, fadeRight)
else
CH:HandleFadeTabs(chat, CH.db.fadeUndockedTabs and CH:IsUndocked(chat, docker))
end
end
end
function CH:UpdateChatTabs()
for _, name in ipairs(_G.CHAT_FRAMES) do
CH:UpdateChatTab(_G[name])
end
end
function CH:ToggleChatButton(button)
if button then
button:SetShown(not CH.db.hideCopyButton)
end
end
function CH:ToggleCopyChatButtons()
for _, name in ipairs(_G.CHAT_FRAMES) do
CH:ToggleChatButton(_G[name].copyButton)
end
end
function CH:RefreshToggleButtons()
_G.LeftChatToggleButton:SetAlpha(E.db.LeftChatPanelFaded and CH.db.fadeChatToggles and 0 or 1)
_G.RightChatToggleButton:SetAlpha(E.db.RightChatPanelFaded and CH.db.fadeChatToggles and 0 or 1)
_G.LeftChatToggleButton:SetShown(not CH.db.hideChatToggles and E.db.datatexts.panels.LeftChatDataPanel.enable)
_G.RightChatToggleButton:SetShown(not CH.db.hideChatToggles and E.db.datatexts.panels.RightChatDataPanel.enable)
end
function CH:IsUndocked(chat, docker)
if not docker then docker = _G.GeneralDockManager.primary end
local primaryUndocked = docker ~= CH.LeftChatWindow and docker ~= CH.RightChatWindow
return not chat.isDocked or (primaryUndocked and ((chat == docker) or CH:GetDockerParent(docker, chat)))
end
function CH:Unsnapped(chat)
if chat == CH.LeftChatWindow then
CH.LeftChatWindow = nil
elseif chat == CH.RightChatWindow then
CH.RightChatWindow = nil
end
end
function CH:ClearSnapping()
CH.LeftChatWindow = nil
CH.RightChatWindow = nil
end
function CH:SnappingChanged(chat)
CH:Unsnapped(chat)
if chat == _G.GeneralDockManager.primary then
for _, frame in ipairs(_G.GeneralDockManager.DOCKED_CHAT_FRAMES) do
CH:PositionChat(frame)
end
else
CH:PositionChat(chat)
end
end
function CH:ShowBackground(background, show)
if not background then return end
if show then
background.Show = nil
background:Show()
else
background:Kill()
end
end
function CH:PositionChat(chat)
CH.LeftChatWindow, CH.RightChatWindow = CH:FindChatWindows()
local docker = _G.GeneralDockManager.primary
if chat == docker then
local iconParent, chatParent = CH:GetAnchorParents(chat)
_G.GeneralDockManager:SetParent(chatParent)
if CH.db.pinVoiceButtons and not CH.db.hideVoiceButtons then
CH:ReparentVoiceChatIcon(iconParent or chatParent)
end
end
CH:UpdateChatTab(chat)
if chat:IsMovable() then
chat:SetUserPlaced(true)
end
if chat.FontStringContainer then
chat.FontStringContainer:ClearAllPoints()
chat.FontStringContainer:SetPoint('TOPLEFT', chat, 'TOPLEFT', -1, 1)
chat.FontStringContainer:SetPoint('BOTTOMRIGHT', chat, 'BOTTOMRIGHT', 1, -1)
end
if chat:IsShown() then
-- that chat font container leaks outside of its frame
-- we cant clip it, so lets force that leak sooner so
-- i can position it properly, patch: 8.3.0 ~Simpy
chat:Hide()
chat:Show()
end
local BASE_OFFSET = 32
if chat == CH.LeftChatWindow then
local LOG_OFFSET = chat:GetID() == 2 and (_G.LeftChatTab:GetHeight() + 4) or 0
chat:ClearAllPoints()
chat:SetPoint('BOTTOMLEFT', _G.LeftChatPanel, 'BOTTOMLEFT', 5, 5)
chat:SetSize(CH.db.panelWidth - 10, CH.db.panelHeight - BASE_OFFSET - LOG_OFFSET)
CH:ShowBackground(chat.Background, false)
elseif chat == CH.RightChatWindow then
local LOG_OFFSET = chat:GetID() == 2 and (_G.LeftChatTab:GetHeight() + 4) or 0
chat:ClearAllPoints()
chat:SetPoint('BOTTOMLEFT', _G.RightChatPanel, 'BOTTOMLEFT', 5, 5)
chat:SetSize((CH.db.separateSizes and CH.db.panelWidthRight or CH.db.panelWidth) - 10, (CH.db.separateSizes and CH.db.panelHeightRight or CH.db.panelHeight) - BASE_OFFSET - LOG_OFFSET)
CH:ShowBackground(chat.Background, false)
else -- show if: not docked, or ChatFrame1, or attached to ChatFrame1
CH:ShowBackground(chat.Background, CH:IsUndocked(chat, docker))
end
end
function CH:PositionChats()
_G.LeftChatPanel:Size(CH.db.panelWidth, CH.db.panelHeight)
if CH.db.separateSizes then
_G.RightChatPanel:Size(CH.db.panelWidthRight, CH.db.panelHeightRight)
else
_G.RightChatPanel:Size(CH.db.panelWidth, CH.db.panelHeight)
end
LO:RepositionChatDataPanels()
-- dont proceed when chat is disabled
if not E.private.chat.enable then return end
for _, name in ipairs(_G.CHAT_FRAMES) do
CH:PositionChat(_G[name])
end
end
function CH:Panel_ColorUpdate()
local panelColor = CH.db.panelColor
self:SetBackdropColor(panelColor.r, panelColor.g, panelColor.b, panelColor.a)
end
function CH:Panels_ColorUpdate()
local panelColor = CH.db.panelColor
_G.LeftChatPanel.backdrop:SetBackdropColor(panelColor.r, panelColor.g, panelColor.b, panelColor.a)
_G.RightChatPanel.backdrop:SetBackdropColor(panelColor.r, panelColor.g, panelColor.b, panelColor.a)
if _G.ChatButtonHolder then
_G.ChatButtonHolder:SetBackdropColor(panelColor.r, panelColor.g, panelColor.b, panelColor.a)
end
end
function CH:UpdateChatTabColors()
for _, name in ipairs(_G.CHAT_FRAMES) do
local tab = CH:GetTab(_G[name])
CH:FCFTab_UpdateColors(tab, tab.selected)
end
end
E.valueColorUpdateFuncs[CH.UpdateChatTabColors] = true
function CH:ScrollToBottom(frame)
frame:ScrollToBottom()
CH:CancelTimer(frame.ScrollTimer, true)
end
function CH:PrintURL(url)
return '|cFFFFFFFF[|Hurl:'..url..'|h'..url..'|h]|r '
end
function CH:FindURL(event, msg, author, ...)
if not CH.db.url then
msg = CH:CheckKeyword(msg, author)
msg = CH:GetSmileyReplacementText(msg)
return false, msg, author, ...
end
local text, tag = msg, strmatch(msg, '{(.-)}')
if tag and _G.ICON_TAG_LIST[strlower(tag)] then
text = gsub(gsub(text, '(%S)({.-})', '%1 %2'), '({.-})(%S)', '%1 %2')
end
text = gsub(gsub(text, '(%S)(|c.-|H.-|h.-|h|r)', '%1 %2'), '(|c.-|H.-|h.-|h|r)(%S)', '%1 %2')
-- http://example.com
local newMsg, found = gsub(text, '(%a+)://(%S+)%s?', CH:PrintURL('%1://%2'))
if found > 0 then return false, CH:GetSmileyReplacementText(CH:CheckKeyword(newMsg, author)), author, ... end
-- www.example.com
newMsg, found = gsub(text, 'www%.([_A-Za-z0-9-]+)%.(%S+)%s?', CH:PrintURL('www.%1.%2'))
if found > 0 then return false, CH:GetSmileyReplacementText(CH:CheckKeyword(newMsg, author)), author, ... end
-- example@example.com
newMsg, found = gsub(text, '([_A-Za-z0-9-%.]+)@([_A-Za-z0-9-]+)(%.+)([_A-Za-z0-9-%.]+)%s?', CH:PrintURL('%1@%2%3%4'))
if found > 0 then return false, CH:GetSmileyReplacementText(CH:CheckKeyword(newMsg, author)), author, ... end
-- IP address with port 1.1.1.1:1
newMsg, found = gsub(text, '(%d%d?%d?)%.(%d%d?%d?)%.(%d%d?%d?)%.(%d%d?%d?)(:%d+)%s?', CH:PrintURL('%1.%2.%3.%4%5'))
if found > 0 then return false, CH:GetSmileyReplacementText(CH:CheckKeyword(newMsg, author)), author, ... end
-- IP address 1.1.1.1
newMsg, found = gsub(text, '(%d%d?%d?)%.(%d%d?%d?)%.(%d%d?%d?)%.(%d%d?%d?)%s?', CH:PrintURL('%1.%2.%3.%4'))
if found > 0 then return false, CH:GetSmileyReplacementText(CH:CheckKeyword(newMsg, author)), author, ... end
msg = CH:CheckKeyword(msg, author)
msg = CH:GetSmileyReplacementText(msg)
return false, msg, author, ...
end
function CH:SetChatEditBoxMessage(message)
local ChatFrameEditBox = _G.ChatEdit_ChooseBoxForSend()
local editBoxShown = ChatFrameEditBox:IsShown()
local editBoxText = ChatFrameEditBox:GetText()
if not editBoxShown then
_G.ChatEdit_ActivateChat(ChatFrameEditBox)
end
if editBoxText and editBoxText ~= '' then
ChatFrameEditBox:SetText('')
end
ChatFrameEditBox:Insert(message)
ChatFrameEditBox:HighlightText()
end
local function HyperLinkedCPL(data)
if strsub(data, 1, 3) == 'cpl' then
local chatID = strsub(data, 5)
local chat = _G[format('ChatFrame%d', chatID)]
if not chat then return end
local scale = chat:GetEffectiveScale() --blizzard does this with `scale = UIParent:GetScale()`
local cursorX, cursorY = GetCursorPosition()
cursorX, cursorY = (cursorX / scale), (cursorY / scale)
local _, lineIndex = chat:FindCharacterAndLineIndexAtCoordinate(cursorX, cursorY)
if lineIndex then
local visibleLine = chat.visibleLines and chat.visibleLines[lineIndex]
local message = visibleLine and visibleLine.messageInfo and visibleLine.messageInfo.message
if message and message ~= '' then
message = gsub(message, '|c%x%x%x%x%x%x%x%x(.-)|r', '%1')
message = strtrim(removeIconFromLine(message))
if not CH:MessageIsProtected(message) then
CH:SetChatEditBoxMessage(message)
end
end
end
end
end
local function HyperLinkedSQU(data)
if strsub(data, 1, 3) == 'squ' then
if not _G.QuickJoinFrame:IsShown() then
ToggleQuickJoinPanel()
end
local guid = strsub(data, 5)
if guid and guid ~= '' then
_G.QuickJoinFrame:SelectGroup(guid)
_G.QuickJoinFrame:ScrollToGroup(guid)
end
end
end
local function HyperLinkedURL(data)
if strsub(data, 1, 3) == 'url' then
local currentLink = strsub(data, 5)
if currentLink and currentLink ~= '' then
CH:SetChatEditBoxMessage(currentLink)
end
end
end
local SetHyperlink = _G.ItemRefTooltip.SetHyperlink
function _G.ItemRefTooltip:SetHyperlink(data, ...)
if strsub(data, 1, 3) == 'cpl' then
HyperLinkedCPL(data)
elseif strsub(data, 1, 3) == 'squ' then
HyperLinkedSQU(data)
elseif strsub(data, 1, 3) == 'url' then
HyperLinkedURL(data)
else
SetHyperlink(self, data, ...)
end
end
local hyperLinkEntered
function CH:OnHyperlinkEnter(frame, refString)
if InCombatLockdown() then return end
local linkToken = strmatch(refString, '^([^:]+)')
if hyperlinkTypes[linkToken] then
_G.GameTooltip:SetOwner(frame, 'ANCHOR_CURSOR')
_G.GameTooltip:SetHyperlink(refString)
_G.GameTooltip:Show()
hyperLinkEntered = frame
end
end
function CH:OnHyperlinkLeave()
if hyperLinkEntered then
hyperLinkEntered = nil
_G.GameTooltip:Hide()
end
end
function CH:OnMouseWheel(frame)
if hyperLinkEntered == frame then
hyperLinkEntered = false
_G.GameTooltip:Hide()
end
end
function CH:ToggleHyperlink(enable)
for _, frameName in ipairs(_G.CHAT_FRAMES) do
local frame = _G[frameName]
local hooked = CH.hooks and CH.hooks[frame] and CH.hooks[frame].OnHyperlinkEnter
if enable and not hooked then
CH:HookScript(frame, 'OnHyperlinkEnter')
CH:HookScript(frame, 'OnHyperlinkLeave')
CH:HookScript(frame, 'OnMouseWheel')
elseif not enable and hooked then
CH:Unhook(frame, 'OnHyperlinkEnter')
CH:Unhook(frame, 'OnHyperlinkLeave')
CH:Unhook(frame, 'OnMouseWheel')
end
end
end
function CH:DisableChatThrottle()
wipe(throttle)
end
function CH:ShortChannel()
return format('|Hchannel:%s|h[%s]|h', self, DEFAULT_STRINGS[strupper(self)] or gsub(self, 'channel:', ''))
end
function CH:HandleShortChannels(msg)
msg = gsub(msg, '|Hchannel:(.-)|h%[(.-)%]|h', CH.ShortChannel)
msg = gsub(msg, 'CHANNEL:', '')
msg = gsub(msg, '^(.-|h) '..L["whispers"], '%1')
msg = gsub(msg, '^(.-|h) '..L["says"], '%1')
msg = gsub(msg, '^(.-|h) '..L["yells"], '%1')
msg = gsub(msg, '<'.._G.AFK..'>', '[|cffFF0000'..L["AFK"]..'|r] ')
msg = gsub(msg, '<'.._G.DND..'>', '[|cffE7E716'..L["DND"]..'|r] ')
msg = gsub(msg, '^%['.._G.RAID_WARNING..'%]', '['..L["RW"]..']')
return msg
end
function CH:GetBNFirstToonClassColor(id)
if not id then return end
for i = 1, BNGetNumFriends() do
local accountInfo = C_BattleNet_GetFriendAccountInfo(i)
if accountInfo and (accountInfo.gameAccountInfo and accountInfo.gameAccountInfo.isOnline) and accountInfo.bnetAccountID == id then
local numGameAccounts = C_BattleNet_GetFriendNumGameAccounts(i)
if numGameAccounts and numGameAccounts > 0 then
for y = 1, numGameAccounts do
local gameAccountInfo = C_BattleNet_GetFriendGameAccountInfo(i, y)
local className = gameAccountInfo and gameAccountInfo.className
if className and className ~= '' and (gameAccountInfo.clientProgram == BNET_CLIENT_WOW) then
return className --return the first toon's class
end
end
end
break
end
end
end
function CH:GetBNFriendColor(name, id, useBTag)
local accountInfo = C_BattleNet_GetAccountInfoByID(id)
if not accountInfo then return name end
local battleTag, isBattleTagFriend, gameAccountInfo = accountInfo.battleTag, accountInfo.isBattleTagFriend, accountInfo.gameAccountInfo
local BATTLE_TAG = battleTag and strmatch(battleTag,'([^#]+)')
local TAG = (useBTag or CH.db.useBTagName) and BATTLE_TAG
local Class
if not (gameAccountInfo and gameAccountInfo.gameAccountID) then
local firstToonClass = CH:GetBNFirstToonClassColor(id)
if firstToonClass then
Class = E:UnlocalizedClassName(firstToonClass)
else
return TAG or name, isBattleTagFriend and BATTLE_TAG
end
end
if not Class then
Class = gameAccountInfo and gameAccountInfo.className and E:UnlocalizedClassName(gameAccountInfo.className)
end
local Color = E:ClassColor(Class)
return (Color and format('|c%s%s|r', Color.colorStr, TAG or name)) or TAG or name, isBattleTagFriend and BATTLE_TAG
end
local PluginIconsCalls = {}
function CH:AddPluginIcons(func)
tinsert(PluginIconsCalls, func)
end
function CH:GetPluginIcon(sender)
for _, func in ipairs(PluginIconsCalls) do
local icon = func(sender)
if icon and icon ~= '' then
return icon
end
end
end
function CH:AddPluginMessageFilter(func, position)
if position then
tinsert(CH.PluginMessageFilters, position, func)
else
tinsert(CH.PluginMessageFilters, func)
end
end
--Modified copy from FrameXML ChatFrame.lua to add CUSTOM_CLASS_COLORS (args were changed)
function CH:GetColoredName(event, _, arg2, _, _, _, _, _, arg8, _, _, _, arg12)
local chatType = strsub(event, 10)
local subType = strsub(chatType, 1, 7)
if subType == 'WHISPER' then
chatType = 'WHISPER'
elseif subType == 'CHANNEL' then
chatType = 'CHANNEL'..arg8
end
--ambiguate guild chat names
arg2 = Ambiguate(arg2, (chatType == 'GUILD' and 'guild') or 'none')
local info = arg12 and _G.ChatTypeInfo[chatType]
if info and _G.Chat_ShouldColorChatByClass(info) then
local data = CH:GetPlayerInfoByGUID(arg12)
local classColor = data and data.classColor
if classColor then
return format('\124cff%.2x%.2x%.2x%s\124r', classColor.r*255, classColor.g*255, classColor.b*255, arg2)
end
end
return arg2
end
--Copied from FrameXML ChatFrame.lua and modified to add CUSTOM_CLASS_COLORS
local seenGroups = {}
function CH:ChatFrame_ReplaceIconAndGroupExpressions(message, noIconReplacement, noGroupReplacement)
wipe(seenGroups)
local ICON_LIST, ICON_TAG_LIST, GROUP_TAG_LIST = _G.ICON_LIST, _G.ICON_TAG_LIST, _G.GROUP_TAG_LIST
for tag in gmatch(message, '%b{}') do
local term = strlower(gsub(tag, '[{}]', ''))
if not noIconReplacement and ICON_TAG_LIST[term] and ICON_LIST[ICON_TAG_LIST[term]] then
message = gsub(message, tag, ICON_LIST[ICON_TAG_LIST[term]] .. '0|t')
elseif not noGroupReplacement and GROUP_TAG_LIST[term] then
local groupIndex = GROUP_TAG_LIST[term]
if not seenGroups[groupIndex] then
seenGroups[groupIndex] = true
local groupList = '['
for i = 1, GetNumGroupMembers() do
local name, _, subgroup, _, _, classFileName = GetRaidRosterInfo(i)
if name and subgroup == groupIndex then
local classColorTable = E:ClassColor(classFileName)
if classColorTable then
name = format('\124cff%.2x%.2x%.2x%s\124r', classColorTable.r*255, classColorTable.g*255, classColorTable.b*255, name)
end
groupList = groupList..(groupList == '[' and '' or _G.PLAYER_LIST_DELIMITER)..name
end
end
if groupList ~= '[' then
groupList = groupList..']'
message = gsub(message, tag, groupList, 1)
end
end
end
end
return message
end
-- copied from ChatFrame.lua
local function GetPFlag(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15, arg16, arg17)
-- Renaming for clarity:
local specialFlag = arg6;
--local zoneChannelID = arg7;
--local localChannelID = arg8;
if specialFlag ~= '' then
if specialFlag == 'GM' or specialFlag == 'DEV' then
-- Add Blizzard Icon if this was sent by a GM/DEV
return '|TInterface\\ChatFrame\\UI-ChatIcon-Blizz:12:20:0:0:32:16:4:28:0:16|t '
elseif specialFlag == 'GUIDE' then
if C_PlayerMentorship_IsActivePlayerConsideredNewcomer() then
return _G.NPEV2_CHAT_USER_TAG_GUIDE .. ' ' -- possibly unable to save global string with trailing whitespace...
end
elseif specialFlag == 'NEWCOMER' then
if IsActivePlayerMentor() then
return _G.NPEV2_CHAT_USER_TAG_NEWCOMER
end
else
return _G['CHAT_FLAG_'..specialFlag]
end
end
return "";
end
function CH:ChatFrame_MessageEventHandler(frame, event, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15, arg16, arg17, isHistory, historyTime, historyName, historyBTag)
-- ElvUI Chat History Note: isHistory, historyTime, historyName, and historyBTag are passed from CH:DisplayChatHistory() and need to be on the end to prevent issues in other addons that listen on ChatFrame_MessageEventHandler.
-- we also send isHistory and historyTime into CH:AddMessage so that we don't have to override the timestamp.
if strsub(event, 1, 8) == 'CHAT_MSG' then
if arg16 then return true end -- hiding sender in letterbox: do NOT even show in chat window (only shows in cinematic frame)
local notChatHistory, historySavedName --we need to extend the arguments on CH.ChatFrame_MessageEventHandler so we can properly handle saved names without overriding
if isHistory == 'ElvUI_ChatHistory' then
if historyBTag then arg2 = historyBTag end -- swap arg2 (which is a |k string) to btag name
historySavedName = historyName
else
notChatHistory = true
end
local chatType = strsub(event, 10)
local info = _G.ChatTypeInfo[chatType]
--If it was a GM whisper, dispatch it to the GMChat addon.
if arg6 == 'GM' and chatType == 'WHISPER' then
return
end
local chatFilters = _G.ChatFrame_GetMessageEventFilters(event)
if chatFilters then
for _, filterFunc in next, chatFilters do
local filter, newarg1, newarg2, newarg3, newarg4, newarg5, newarg6, newarg7, newarg8, newarg9, newarg10, newarg11, newarg12, newarg13, newarg14 = filterFunc(frame, event, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14)
if filter then
return true
elseif newarg1 then
arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14 = newarg1, newarg2, newarg3, newarg4, newarg5, newarg6, newarg7, newarg8, newarg9, newarg10, newarg11, newarg12, newarg13, newarg14
end
end
end
-- data from populated guid info
local nameWithRealm, realm
local data = CH:GetPlayerInfoByGUID(arg12)
if data then
realm = data.realm
nameWithRealm = data.nameWithRealm
end
-- fetch the name color to use
local coloredName = historySavedName or CH:GetColoredName(event, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14)
local channelLength = strlen(arg4)
local infoType = chatType
if chatType == 'COMMUNITIES_CHANNEL' or ((strsub(chatType, 1, 7) == 'CHANNEL') and (chatType ~= 'CHANNEL_LIST') and ((arg1 ~= 'INVITE') or (chatType ~= 'CHANNEL_NOTICE_USER'))) then
if arg1 == 'WRONG_PASSWORD' then
local _, popup = _G.StaticPopup_Visible('CHAT_CHANNEL_PASSWORD')
if popup and strupper(popup.data) == strupper(arg9) then
return -- Don't display invalid password messages if we're going to prompt for a password (bug 102312)
end
end
local found = false
for index, value in pairs(frame.channelList) do
if channelLength > strlen(value) then
-- arg9 is the channel name without the number in front...
if ((arg7 > 0) and (frame.zoneChannelList[index] == arg7)) or (strupper(value) == strupper(arg9)) then
found = true
infoType = 'CHANNEL'..arg8
info = _G.ChatTypeInfo[infoType]
if chatType == 'CHANNEL_NOTICE' and (arg1 == 'YOU_LEFT') then
frame.channelList[index] = nil
frame.zoneChannelList[index] = nil
end
break
end
end
end
if not found or not info then
return true
end
end
local chatGroup = _G.Chat_GetChatCategory(chatType)
local chatTarget
if chatGroup == 'CHANNEL' then
chatTarget = tostring(arg8)
elseif chatGroup == 'WHISPER' or chatGroup == 'BN_WHISPER' then
if not(strsub(arg2, 1, 2) == '|K') then
chatTarget = strupper(arg2)
else
chatTarget = arg2
end
end
if _G.FCFManager_ShouldSuppressMessage(frame, chatGroup, chatTarget) then
return true
end
if chatGroup == 'WHISPER' or chatGroup == 'BN_WHISPER' then
if frame.privateMessageList and not frame.privateMessageList[strlower(arg2)] then
return true
elseif frame.excludePrivateMessageList and frame.excludePrivateMessageList[strlower(arg2)] and ((chatGroup == 'WHISPER' and GetCVar('whisperMode') ~= 'popout_and_inline') or (chatGroup == 'BN_WHISPER' and GetCVar('whisperMode') ~= 'popout_and_inline')) then
return true
end
end
if frame.privateMessageList then
-- Dedicated BN whisper windows need online/offline messages for only that player
if (chatGroup == 'BN_INLINE_TOAST_ALERT' or chatGroup == 'BN_WHISPER_PLAYER_OFFLINE') and not frame.privateMessageList[strlower(arg2)] then
return true
end
-- HACK to put certain system messages into dedicated whisper windows
if chatGroup == 'SYSTEM' then
local matchFound = false
local message = strlower(arg1)
for playerName in pairs(frame.privateMessageList) do
local playerNotFoundMsg = strlower(format(_G.ERR_CHAT_PLAYER_NOT_FOUND_S, playerName))
local charOnlineMsg = strlower(format(_G.ERR_FRIEND_ONLINE_SS, playerName, playerName))
local charOfflineMsg = strlower(format(_G.ERR_FRIEND_OFFLINE_S, playerName))
if message == playerNotFoundMsg or message == charOnlineMsg or message == charOfflineMsg then
matchFound = true
break
end
end
if not matchFound then
return true
end
end
end
if (chatType == 'SYSTEM' or chatType == 'SKILL' or chatType == 'CURRENCY' or chatType == 'MONEY' or
chatType == 'OPENING' or chatType == 'TRADESKILLS' or chatType == 'PET_INFO' or chatType == 'TARGETICONS' or chatType == 'BN_WHISPER_PLAYER_OFFLINE') then
frame:AddMessage(arg1, info.r, info.g, info.b, info.id, nil, nil, isHistory, historyTime)
elseif chatType == 'LOOT' then
-- Append [Share] hyperlink if this is a valid social item and you are the looter.
if arg12 == E.myguid and C_SocialIsSocialEnabled() then
local itemID, creationContext = GetItemInfoFromHyperlink(arg1)
if itemID and C_SocialGetLastItem() == itemID then
arg1 = arg1 .. ' ' .. _G.Social_GetShareItemLink(creationContext, true)
end
end
frame:AddMessage(arg1, info.r, info.g, info.b, info.id, nil, nil, isHistory, historyTime)
elseif strsub(chatType,1,7) == 'COMBAT_' then
frame:AddMessage(arg1, info.r, info.g, info.b, info.id, nil, nil, isHistory, historyTime)
elseif strsub(chatType,1,6) == 'SPELL_' then
frame:AddMessage(arg1, info.r, info.g, info.b, info.id, nil, nil, isHistory, historyTime)
elseif strsub(chatType,1,10) == 'BG_SYSTEM_' then
frame:AddMessage(arg1, info.r, info.g, info.b, info.id, nil, nil, isHistory, historyTime)
elseif strsub(chatType,1,11) == 'ACHIEVEMENT' then
-- Append [Share] hyperlink
if arg12 == E.myguid and C_SocialIsSocialEnabled() then
local achieveID = GetAchievementInfoFromHyperlink(arg1)
if achieveID then
arg1 = arg1 .. ' ' .. _G.Social_GetShareAchievementLink(achieveID, true)
end
end
frame:AddMessage(format(arg1, GetPlayerLink(arg2, ('[%s]'):format(coloredName))), info.r, info.g, info.b, info.id, nil, nil, isHistory, historyTime)
elseif strsub(chatType,1,18) == 'GUILD_ACHIEVEMENT' then
local message = format(arg1, GetPlayerLink(arg2, ('[%s]'):format(coloredName)))
if C_SocialIsSocialEnabled() then
local achieveID = GetAchievementInfoFromHyperlink(arg1)
if achieveID then
local isGuildAchievement = select(12, GetAchievementInfo(achieveID))
if isGuildAchievement then
message = message .. ' ' .. _G.Social_GetShareAchievementLink(achieveID, true)
end
end
end
frame:AddMessage(message, info.r, info.g, info.b, info.id, nil, nil, isHistory, historyTime)
elseif chatType == 'IGNORED' then
frame:AddMessage(format(_G.CHAT_IGNORED, arg2), info.r, info.g, info.b, info.id, nil, nil, isHistory, historyTime)
elseif chatType == 'FILTERED' then
frame:AddMessage(format(_G.CHAT_FILTERED, arg2), info.r, info.g, info.b, info.id, nil, nil, isHistory, historyTime)
elseif chatType == 'RESTRICTED' then
frame:AddMessage(_G.CHAT_RESTRICTED_TRIAL, info.r, info.g, info.b, info.id, nil, nil, isHistory, historyTime)
elseif chatType == 'CHANNEL_LIST' then
if channelLength > 0 then
frame:AddMessage(format(_G['CHAT_'..chatType..'_GET']..arg1, tonumber(arg8), arg4), info.r, info.g, info.b, info.id, nil, nil, isHistory, historyTime)
else
frame:AddMessage(arg1, info.r, info.g, info.b, info.id, nil, nil, isHistory, historyTime)
end
elseif chatType == 'CHANNEL_NOTICE_USER' then
local globalstring = _G['CHAT_'..arg1..'_NOTICE_BN']
if not globalstring then
globalstring = _G['CHAT_'..arg1..'_NOTICE']
end
if not globalstring then
GMError(('Missing global string for %q'):format('CHAT_'..arg1..'_NOTICE_BN'))
return
end
if arg5 ~= '' then
-- TWO users in this notice (E.G. x kicked y)
frame:AddMessage(format(globalstring, arg8, arg4, arg2, arg5), info.r, info.g, info.b, info.id, nil, nil, isHistory, historyTime)
elseif arg1 == 'INVITE' then
frame:AddMessage(format(globalstring, arg4, arg2), info.r, info.g, info.b, info.id, nil, nil, isHistory, historyTime)
else
frame:AddMessage(format(globalstring, arg8, arg4, arg2), info.r, info.g, info.b, info.id, nil, nil, isHistory, historyTime)
end
if arg1 == 'INVITE' and GetCVarBool('blockChannelInvites') then
frame:AddMessage(_G.CHAT_MSG_BLOCK_CHAT_CHANNEL_INVITE, info.r, info.g, info.b, info.id, nil, nil, isHistory, historyTime)
end
elseif chatType == 'CHANNEL_NOTICE' then
local accessID = _G.ChatHistory_GetAccessID(chatGroup, arg8);
local typeID = _G.ChatHistory_GetAccessID(infoType, arg8, arg12);
if arg1 == 'YOU_CHANGED' and C_ChatInfo_GetChannelRuleset(arg8) == ChatChannelRuleset_Mentor then
_G.ChatFrame_UpdateDefaultChatTarget(frame)
_G.ChatEdit_UpdateNewcomerEditBoxHint(frame.editBox)
else
if arg1 == 'YOU_LEFT' then
_G.ChatEdit_UpdateNewcomerEditBoxHint(frame.editBox, arg8)
end
local globalstring
if arg1 == 'TRIAL_RESTRICTED' then
globalstring = _G.CHAT_TRIAL_RESTRICTED_NOTICE_TRIAL
else
globalstring = _G['CHAT_'..arg1..'_NOTICE_BN']
if not globalstring then
globalstring = _G['CHAT_'..arg1..'_NOTICE']
if not globalstring then
GMError(('Missing global string for %q'):format('CHAT_'..arg1..'_NOTICE'))
return
end
end
end
frame:AddMessage(format(globalstring, arg8, _G.ChatFrame_ResolvePrefixedChannelName(arg4)), info.r, info.g, info.b, info.id, accessID, typeID, isHistory, historyTime)
end
elseif chatType == 'BN_INLINE_TOAST_ALERT' then
local globalstring = _G['BN_INLINE_TOAST_'..arg1]
if not globalstring then
GMError(('Missing global string for %q'):format('BN_INLINE_TOAST_'..arg1))
return
end
local message
if arg1 == 'FRIEND_REQUEST' then
message = globalstring
elseif arg1 == 'FRIEND_PENDING' then
message = format(_G.BN_INLINE_TOAST_FRIEND_PENDING, BNGetNumFriendInvites())
elseif arg1 == 'FRIEND_REMOVED' or arg1 == 'BATTLETAG_FRIEND_REMOVED' then
message = format(globalstring, arg2)
elseif arg1 == 'FRIEND_ONLINE' or arg1 == 'FRIEND_OFFLINE' then
local accountInfo = C_BattleNet_GetAccountInfoByID(arg13)
if not accountInfo then return end
local client = accountInfo.gameAccountInfo and accountInfo.gameAccountInfo.clientProgram
if client and client ~= '' then
local characterName = _G.BNet_GetValidatedCharacterName(accountInfo.gameAccountInfo.characterName, accountInfo.battleTag, client) or ''
local characterNameText = _G.BNet_GetClientEmbeddedTexture(client, 14)..characterName
local linkDisplayText = ('[%s] (%s)'):format(arg2, characterNameText)
local playerLink = GetBNPlayerLink(arg2, linkDisplayText, arg13, arg11, chatGroup, 0)
message = format(globalstring, playerLink)
else
local linkDisplayText = ('[%s]'):format(arg2)
local playerLink = GetBNPlayerLink(arg2, linkDisplayText, arg13, arg11, chatGroup, 0)
message = format(globalstring, playerLink)
end
else
local linkDisplayText = ('[%s]'):format(arg2)
local playerLink = GetBNPlayerLink(arg2, linkDisplayText, arg13, arg11, chatGroup, 0)
message = format(globalstring, playerLink)
end
frame:AddMessage(message, info.r, info.g, info.b, info.id, nil, nil, isHistory, historyTime)
elseif chatType == 'BN_INLINE_TOAST_BROADCAST' then
if arg1 ~= '' then
arg1 = RemoveNewlines(RemoveExtraSpaces(arg1))
local linkDisplayText = ('[%s]'):format(arg2)
local playerLink = GetBNPlayerLink(arg2, linkDisplayText, arg13, arg11, chatGroup, 0)
frame:AddMessage(format(_G.BN_INLINE_TOAST_BROADCAST, playerLink, arg1), info.r, info.g, info.b, info.id, nil, nil, isHistory, historyTime)
end
elseif chatType == 'BN_INLINE_TOAST_BROADCAST_INFORM' then
if arg1 ~= '' then
frame:AddMessage(_G.BN_INLINE_TOAST_BROADCAST_INFORM, info.r, info.g, info.b, info.id, nil, nil, isHistory, historyTime)
end
else
local body
if chatType == 'WHISPER_INFORM' and GMChatFrame_IsGM and GMChatFrame_IsGM(arg2) then
return
end
local showLink = 1
if strsub(chatType, 1, 7) == 'MONSTER' or strsub(chatType, 1, 9) == 'RAID_BOSS' then
showLink = nil
else
arg1 = gsub(arg1, '%%', '%%%%')
end
-- Search for icon links and replace them with texture links.
arg1 = CH:ChatFrame_ReplaceIconAndGroupExpressions(arg1, arg17, not _G.ChatFrame_CanChatGroupPerformExpressionExpansion(chatGroup)) -- If arg17 is true, don't convert to raid icons
--Remove groups of many spaces
arg1 = RemoveExtraSpaces(arg1)
--ElvUI: Get class colored name for BattleNet friend
if chatType == 'BN_WHISPER' or chatType == 'BN_WHISPER_INFORM' then
coloredName = historySavedName or CH:GetBNFriendColor(arg2, arg13)
end
local playerLink
local playerLinkDisplayText = coloredName
local relevantDefaultLanguage = frame.defaultLanguage
if chatType == 'SAY' or chatType == 'YELL' then
relevantDefaultLanguage = frame.alternativeDefaultLanguage
end
local usingDifferentLanguage = (arg3 ~= '') and (arg3 ~= relevantDefaultLanguage)
local usingEmote = (chatType == 'EMOTE') or (chatType == 'TEXT_EMOTE')
if usingDifferentLanguage or not usingEmote then
playerLinkDisplayText = ('[%s]'):format(coloredName)
end
local isCommunityType = chatType == 'COMMUNITIES_CHANNEL'
local playerName, lineID, bnetIDAccount = arg2, arg11, arg13
if isCommunityType then
local isBattleNetCommunity = bnetIDAccount ~= nil and bnetIDAccount ~= 0
local messageInfo, clubId, streamId = C_Club_GetInfoFromLastCommunityChatLine()
if messageInfo ~= nil then
if isBattleNetCommunity then
playerLink = GetBNPlayerCommunityLink(playerName, playerLinkDisplayText, bnetIDAccount, clubId, streamId, messageInfo.messageId.epoch, messageInfo.messageId.position)
else
playerLink = GetPlayerCommunityLink(playerName, playerLinkDisplayText, clubId, streamId, messageInfo.messageId.epoch, messageInfo.messageId.position)
end
else
playerLink = playerLinkDisplayText
end
else
if chatType == 'BN_WHISPER' or chatType == 'BN_WHISPER_INFORM' then
playerLink = GetBNPlayerLink(playerName, playerLinkDisplayText, bnetIDAccount, lineID, chatGroup, chatTarget)
elseif ((chatType == 'GUILD' or chatType == 'TEXT_EMOTE') or arg14) and (nameWithRealm and nameWithRealm ~= playerName) then
playerName = nameWithRealm
playerLink = GetPlayerLink(playerName, playerLinkDisplayText, lineID, chatGroup, chatTarget)
else
playerLink = GetPlayerLink(playerName, playerLinkDisplayText, lineID, chatGroup, chatTarget)
end
end
local message = arg1
if arg14 then --isMobile
message = _G.ChatFrame_GetMobileEmbeddedTexture(info.r, info.g, info.b)..message
end
-- Player Flags
local pflag = GetPFlag(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15, arg16, arg17)
local chatIcon, pluginChatIcon = specialChatIcons[playerName], CH:GetPluginIcon(playerName)
if type(chatIcon) == 'function' then
local icon, prettify = chatIcon()
if prettify and not CH:MessageIsProtected(message) then
message = prettify(message)
end
chatIcon = icon or ''
end
-- LFG Role Flags
local lfgRole = lfgRoles[playerName]
if lfgRole and (chatType == 'PARTY_LEADER' or chatType == 'PARTY' or chatType == 'RAID' or chatType == 'RAID_LEADER' or chatType == 'INSTANCE_CHAT' or chatType == 'INSTANCE_CHAT_LEADER') then
pflag = pflag..lfgRole
end
-- Special Chat Icon
if chatIcon then
pflag = pflag..chatIcon
end
-- Plugin Chat Icon
if pluginChatIcon then
pflag = pflag..pluginChatIcon
end
if usingDifferentLanguage then
local languageHeader = '['..arg3..'] '
if showLink and (arg2 ~= '') then
body = format(_G['CHAT_'..chatType..'_GET']..languageHeader..message, pflag..playerLink)
else
body = format(_G['CHAT_'..chatType..'_GET']..languageHeader..message, pflag..arg2)
end
else
if not showLink or arg2 == '' then
if chatType == 'TEXT_EMOTE' then
body = message
else
body = format(_G['CHAT_'..chatType..'_GET']..message, pflag..arg2, arg2)
end
else
if chatType == 'EMOTE' then
body = format(_G['CHAT_'..chatType..'_GET']..message, pflag..playerLink)
elseif chatType == 'TEXT_EMOTE' and realm then
if info.colorNameByClass then
body = gsub(message, arg2..'%-'..realm, pflag..gsub(playerLink, '(|h|c.-)|r|h$','%1-'..realm..'|r|h'), 1)
else
body = gsub(message, arg2..'%-'..realm, pflag..gsub(playerLink, '(|h.-)|h$','%1-'..realm..'|h'), 1)
end
elseif chatType == 'TEXT_EMOTE' then
body = gsub(message, arg2, pflag..playerLink, 1)
elseif chatType == 'GUILD_ITEM_LOOTED' then
body = gsub(message, '$s', GetPlayerLink(arg2, playerLinkDisplayText))
else
body = format(_G['CHAT_'..chatType..'_GET']..message, pflag..playerLink)
end
end
end
-- Add Channel
if channelLength > 0 then
body = '|Hchannel:channel:'..arg8..'|h['.._G.ChatFrame_ResolvePrefixedChannelName(arg4)..']|h '..body
end
if CH.db.shortChannels and (chatType ~= 'EMOTE' and chatType ~= 'TEXT_EMOTE') then
body = CH:HandleShortChannels(body)
end
for _, filter in ipairs(CH.PluginMessageFilters) do
body = filter(body)
end
local accessID = _G.ChatHistory_GetAccessID(chatGroup, chatTarget)
local typeID = _G.ChatHistory_GetAccessID(infoType, chatTarget, arg12 or arg13)
local alertType = notChatHistory and not CH.SoundTimer and not strfind(event, '_INFORM') and CH.db.channelAlerts[historyTypes[event]]
if alertType and alertType ~= 'None' and arg2 ~= PLAYER_NAME and (not CH.db.noAlertInCombat or not InCombatLockdown()) then
CH.SoundTimer = E:Delay(5, CH.ThrottleSound)
PlaySoundFile(LSM:Fetch('sound', alertType), 'Master')
end
frame:AddMessage(body, info.r, info.g, info.b, info.id, accessID, typeID, isHistory, historyTime)
end
if notChatHistory and (chatType == 'WHISPER' or chatType == 'BN_WHISPER') then
_G.ChatEdit_SetLastTellTarget(arg2, chatType)
FlashClientIcon()
end
if notChatHistory and not frame:IsShown() then
if (frame == _G.DEFAULT_CHAT_FRAME and info.flashTabOnGeneral) or (frame ~= _G.DEFAULT_CHAT_FRAME and info.flashTab) then
if not _G.CHAT_OPTIONS.HIDE_FRAME_ALERTS or chatType == 'WHISPER' or chatType == 'BN_WHISPER' then
if not _G.FCFManager_ShouldSuppressMessageFlash(frame, chatGroup, chatTarget) then
_G.FCF_StartAlertFlash(frame) --This would taint if we were not using LibChatAnims
end
end
end
end
return true
end
end
function CH:ChatFrame_ConfigEventHandler(...)
return _G.ChatFrame_ConfigEventHandler(...)
end
function CH:ChatFrame_SystemEventHandler(frame, event, message, ...)
return _G.ChatFrame_SystemEventHandler(frame, event, message, ...)
end
function CH:ChatFrame_OnEvent(...)
if CH:ChatFrame_ConfigEventHandler(...) then return end
if CH:ChatFrame_SystemEventHandler(...) then return end
if CH:ChatFrame_MessageEventHandler(...) then return end
end
function CH:FloatingChatFrame_OnEvent(...)
CH:ChatFrame_OnEvent(...)
_G.FloatingChatFrame_OnEvent(...)
end
local function FloatingChatFrameOnEvent(...)
CH:FloatingChatFrame_OnEvent(...)
end
function CH:ChatFrame_SetScript(script, func)
if script == 'OnMouseWheel' and func ~= CH.ChatFrame_OnMouseScroll then
self:SetScript(script, CH.ChatFrame_OnMouseScroll)
end
end
function CH:SetupChat()
if not E.private.chat.enable then return end
for _, frameName in ipairs(_G.CHAT_FRAMES) do
local frame = _G[frameName]
local id = frame:GetID()
CH:StyleChat(frame)
_G.FCFTab_UpdateAlpha(frame)
if id ~= 2 and not frame.OldAddMessage then
--Don't add timestamps to combat log, they don't work.
--This usually taints, but LibChatAnims should make sure it doesn't.
frame.OldAddMessage = frame.AddMessage
frame.AddMessage = CH.AddMessage
end
if not frame.scriptsSet then
if id ~= 2 then
frame:SetScript('OnEvent', FloatingChatFrameOnEvent)
end
frame:SetScript('OnMouseWheel', CH.ChatFrame_OnMouseScroll)
hooksecurefunc(frame, 'SetScript', CH.ChatFrame_SetScript)
frame.scriptsSet = true
end
end
CH:ToggleHyperlink(CH.db.hyperlinkHover)
local chat = _G.GeneralDockManager.primary
_G.GeneralDockManager:ClearAllPoints()
_G.GeneralDockManager:Point('BOTTOMLEFT', chat, 'TOPLEFT', 0, 3)
_G.GeneralDockManager:Point('BOTTOMRIGHT', chat, 'TOPRIGHT', 0, 3)
_G.GeneralDockManager:Height(22)
_G.GeneralDockManagerScrollFrame:Height(22)
_G.GeneralDockManagerScrollFrameChild:Height(22)
CH:PositionChats()
if not CH.HookSecured then
CH:SecureHook('FCF_OpenTemporaryWindow', 'SetupChat')
CH.HookSecured = true
end
end
local function PrepareMessage(author, message)
if author and author ~= '' and message and message ~= '' then
return strupper(author) .. message
end
end
function CH:ChatThrottleHandler(arg1, arg2, when)
local msg = PrepareMessage(arg1, arg2)
if msg then
for message, object in pairs(throttle) do
if difftime(when, object.time) >= CH.db.throttleInterval then
throttle[message] = nil
end
end
if not throttle[msg] then
throttle[msg] = {time = time(), count = 1}
else
throttle[msg].count = throttle[msg].count + 1
end
end
end
function CH:ChatThrottleBlockFlag(author, message, when)
local msg = (author ~= PLAYER_NAME) and (CH.db.throttleInterval ~= 0) and PrepareMessage(author, message)
local object = msg and throttle[msg]
return object and object.time and object.count and object.count > 1 and (difftime(when, object.time) <= CH.db.throttleInterval), object
end
function CH:ChatThrottleIntervalHandler(event, message, author, ...)
local blockFlag, blockObject = CH:ChatThrottleBlockFlag(author, message, time())
if blockFlag then
return true
else
if blockObject then blockObject.time = time() end
return CH:FindURL(event, message, author, ...)
end
end
function CH:CHAT_MSG_CHANNEL(event, message, author, ...)
return CH:ChatThrottleIntervalHandler(event, message, author, ...)
end
function CH:CHAT_MSG_YELL(event, message, author, ...)
return CH:ChatThrottleIntervalHandler(event, message, author, ...)
end
function CH:CHAT_MSG_SAY(event, message, author, ...)
return CH:ChatThrottleIntervalHandler(event, message, author, ...)
end
function CH:ThrottleSound()
CH.SoundTimer = nil
end
local protectLinks = {}
function CH:CheckKeyword(message, author)
local letInCombat = not CH.db.noAlertInCombat or not InCombatLockdown()
local letSound = not CH.SoundTimer and (CH.db.keywordSound ~= 'None' and author ~= PLAYER_NAME) and letInCombat
for hyperLink in gmatch(message, '|%x+|H.-|h.-|h|r') do
protectLinks[hyperLink] = gsub(hyperLink,'%s','|s')
if letSound then
for keyword in pairs(CH.Keywords) do
if hyperLink == keyword then
CH.SoundTimer = E:Delay(5, CH.ThrottleSound)
PlaySoundFile(LSM:Fetch('sound', CH.db.keywordSound), 'Master')
letSound = false -- dont let a second sound fire below
break
end
end
end
end
for hyperLink, tempLink in pairs(protectLinks) do
message = gsub(message, E:EscapeString(hyperLink), tempLink)
end
local rebuiltString
local isFirstWord = true
for word in gmatch(message, '%s-%S+%s*') do
if not next(protectLinks) or not protectLinks[gsub(gsub(word,'%s',''),'|s',' ')] then
local tempWord = gsub(word, '[%s%p]', '')
local lowerCaseWord = strlower(tempWord)
for keyword in pairs(CH.Keywords) do
if lowerCaseWord == strlower(keyword) then
word = gsub(word, tempWord, format('%s%s|r', E.media.hexvaluecolor, tempWord))
if letSound then -- dont break because it's recoloring all found
CH.SoundTimer = E:Delay(5, CH.ThrottleSound)
PlaySoundFile(LSM:Fetch('sound', CH.db.keywordSound), 'Master')
letSound = false -- but dont let additional hits call the sound
end
end
end
if CH.db.classColorMentionsChat then
tempWord = gsub(word,'^[%s%p]-([^%s%p]+)([%-]?[^%s%p]-)[%s%p]*$','%1%2')
lowerCaseWord = strlower(tempWord)
local classMatch = CH.ClassNames[lowerCaseWord]
local wordMatch = classMatch and lowerCaseWord
if wordMatch and not E.global.chat.classColorMentionExcludedNames[wordMatch] then
local classColorTable = E:ClassColor(classMatch)
if classColorTable then
word = gsub(word, gsub(tempWord, '%-','%%-'), format('\124cff%.2x%.2x%.2x%s\124r', classColorTable.r*255, classColorTable.g*255, classColorTable.b*255, tempWord))
end
end
end
end
if isFirstWord then
rebuiltString = word
isFirstWord = false
else
rebuiltString = rebuiltString..word
end
end
for hyperLink, tempLink in pairs(protectLinks) do
rebuiltString = gsub(rebuiltString, E:EscapeString(tempLink), hyperLink)
protectLinks[hyperLink] = nil
end
return rebuiltString
end
function CH:AddLines(lines, ...)
for i = select('#', ...), 1, -1 do
local x = select(i, ...)
if x:IsObjectType('FontString') and not x:GetName() then
tinsert(lines, x:GetText())
end
end
end
function CH:ChatEdit_OnEnterPressed(editBox)
editBox:ClearHistory() -- we will use our own editbox history so keeping them populated on blizzards end is pointless
local chatType = editBox:GetAttribute('chatType')
local chatFrame = chatType and editBox:GetParent()
if chatFrame and (not chatFrame.isTemporary) and (_G.ChatTypeInfo[chatType].sticky == 1) then
if not CH.db.sticky then chatType = 'SAY' end
editBox:SetAttribute('chatType', chatType)
end
end
function CH:SetChatFont(dropDown, chatFrame, fontSize)
if not chatFrame then chatFrame = _G.FCF_GetCurrentChatFrame() end
if not fontSize then fontSize = dropDown.value end
chatFrame:FontTemplate(LSM:Fetch('font', CH.db.font), fontSize, CH.db.fontOutline)
CH:UpdateEditboxFont(chatFrame)
end
CH.SecureSlashCMD = {
'^/rl',
'^/tar',
'^/target',
'^/startattack',
'^/stopattack',
'^/assist',
'^/cast',
'^/use',
'^/castsequence',
'^/cancelaura',
'^/cancelform',
'^/equip',
'^/exit',
'^/camp',
'^/logout'
}
function CH:ChatEdit_AddHistory(_, line) -- editBox, line
line = line and strtrim(line)
if line and strlen(line) > 0 then
for _, command in next, CH.SecureSlashCMD do
if strmatch(line, command) then
return
end
end
for index, text in pairs(ElvCharacterDB.ChatEditHistory) do
if text == line then
tremove(ElvCharacterDB.ChatEditHistory, index)
break
end
end
tinsert(ElvCharacterDB.ChatEditHistory, line)
if #ElvCharacterDB.ChatEditHistory > CH.db.editboxHistorySize then
tremove(ElvCharacterDB.ChatEditHistory, 1)
end
end
end
function CH:UpdateChatKeywords()
wipe(CH.Keywords)
local keywords = CH.db.keywords
keywords = gsub(keywords,',%s',',')
for stringValue in gmatch(keywords, '[^,]+') do
if stringValue ~= '' then
CH.Keywords[stringValue] = true
end
end
end
function CH:PET_BATTLE_CLOSE()
if not CH.db.autoClosePetBattleLog then return end
-- closing a chat tab (or window) in combat = chat tab (or window) goofs..
-- might have something to do with HideUIPanel inside of FCF_Close
if InCombatLockdown() then
CH:RegisterEvent('PLAYER_REGEN_ENABLED', 'PET_BATTLE_CLOSE')
return
else -- we can take this off once it goes through once
CH:UnregisterEvent('PLAYER_REGEN_ENABLED')
end
for _, frameName in ipairs(_G.CHAT_FRAMES) do
local chat = _G[frameName]
local tab = CH:GetTab(chat)
local text = tab and tab.Text:GetText()
if text and strmatch(text, DEFAULT_STRINGS.PET_BATTLE_COMBAT_LOG) then
_G.FCF_Close(chat)
break -- we found it, dont gotta keep lookin'
end
end
end
function CH:FCF_Close(chat)
-- clear these off when it's closed, used by FCFTab_UpdateColors
local tab = CH:GetTab(chat)
tab.whisperName = nil
tab.classColor = nil
end
function CH:UpdateFading()
for _, frameName in ipairs(_G.CHAT_FRAMES) do
local frame = _G[frameName]
if frame then
frame:SetTimeVisible(CH.db.inactivityTimer)
frame:SetFading(CH.db.fade)
end
end
end
function CH:DisplayChatHistory()
local data = ElvCharacterDB.ChatHistoryLog
if not (data and next(data)) then return end
if not CH:GetPlayerInfoByGUID(E.myguid) then
E:Delay(0.1, CH.DisplayChatHistory)
return
end
for _, chat in ipairs(_G.CHAT_FRAMES) do
for _, d in ipairs(data) do
if type(d) == 'table' then
for _, messageType in pairs(_G[chat].messageTypeList) do
local historyType, skip = historyTypes[d[50]]
if historyType then -- let others go by..
if not CH.db.showHistory[historyType] then skip = true end -- but kill ignored ones
end
if not skip and gsub(strsub(d[50],10),'_INFORM','') == messageType then
if d[1] and not CH:MessageIsProtected(d[1]) then
CH:ChatFrame_MessageEventHandler(_G[chat],d[50],d[1],d[2],d[3],d[4],d[5],d[6],d[7],d[8],d[9],d[10],d[11],d[12],d[13],d[14],d[15],d[16],d[17],'ElvUI_ChatHistory',d[51],d[52],d[53])
end
end
end
end
end
end
end
tremove(_G.ChatTypeGroup.GUILD, 2)
function CH:DelayGuildMOTD()
local delay, checks, delayFrame, chat = 0, 0, CreateFrame('Frame')
tinsert(_G.ChatTypeGroup.GUILD, 2, 'GUILD_MOTD')
delayFrame:SetScript('OnUpdate', function(df, elapsed)
delay = delay + elapsed
if delay < 5 then return end
local msg = GetGuildRosterMOTD()
if msg and strlen(msg) > 0 then
for _, frame in ipairs(_G.CHAT_FRAMES) do
chat = _G[frame]
if chat and chat:IsEventRegistered('CHAT_MSG_GUILD') then
CH:ChatFrame_SystemEventHandler(chat, 'GUILD_MOTD', msg)
chat:RegisterEvent('GUILD_MOTD')
end
end
df:SetScript('OnUpdate', nil)
else -- 5 seconds can be too fast for the API response. let's try once every 5 seconds (max 5 checks).
delay, checks = 0, checks + 1
if checks >= 5 then
df:SetScript('OnUpdate', nil)
end
end
end)
end
function CH:SaveChatHistory(event, ...)
local historyType = historyTypes[event]
if historyType then -- let others go by..
if not CH.db.showHistory[historyType] then return end -- but kill ignored ones
end
if CH.db.throttleInterval ~= 0 and (event == 'CHAT_MSG_SAY' or event == 'CHAT_MSG_YELL' or event == 'CHAT_MSG_CHANNEL') then
local message, author = ...
local when = time()
CH:ChatThrottleHandler(author, message, when)
if CH:ChatThrottleBlockFlag(author, message, when) then
return
end
end
if not CH.db.chatHistory then return end
local data = ElvCharacterDB.ChatHistoryLog
if not data then return end
local tempHistory = {}
for i = 1, select('#', ...) do
tempHistory[i] = select(i, ...) or false
end
if #tempHistory > 0 and not CH:MessageIsProtected(tempHistory[1]) then
tempHistory[50] = event
tempHistory[51] = CH:GetChatTime()
local coloredName, battleTag
if tempHistory[13] > 0 then coloredName, battleTag = CH:GetBNFriendColor(tempHistory[2], tempHistory[13], true) end
if battleTag then tempHistory[53] = battleTag end -- store the battletag, only when the person is known by battletag, so we can replace arg2 later in the function
tempHistory[52] = coloredName or CH:GetColoredName(event, ...)
tinsert(data, tempHistory)
while #data >= CH.db.historySize do
tremove(data, 1)
end
end
end
function CH:GetCombatLog()
local LOG = _G.COMBATLOG -- ChatFrame2
if LOG then return LOG, CH:GetTab(LOG) end
end
function CH:FCFDock_UpdateTabs(dock)
if dock == _G.GeneralDockManager then
local logchat, logchattab = CH:GetCombatLog()
dock.scrollFrame:ClearAllPoints()
dock.scrollFrame:Point('RIGHT', dock.overflowButton, 'LEFT')
dock.scrollFrame:Point('TOPLEFT', (logchat.isDocked and logchattab) or CH:GetTab(dock.primary), 'TOPRIGHT')
end
end
function CH:FCF_SetWindowAlpha(frame, alpha)
frame.oldAlpha = alpha or 1
end
function CH:CheckLFGRoles()
if not CH.db.lfgIcons or not IsInGroup() then return end
wipe(lfgRoles)
local playerRole = UnitGroupRolesAssigned('player')
if playerRole then
lfgRoles[PLAYER_NAME] = rolePaths[playerRole]
end
local unit = (IsInRaid() and 'raid' or 'party')
for i = 1, GetNumGroupMembers() do
if UnitExists(unit..i) and not UnitIsUnit(unit..i, 'player') then
local role = UnitGroupRolesAssigned(unit..i)
local name, realm = UnitName(unit..i)
if role and name then
name = (realm and realm ~= '' and name..'-'..realm) or name..'-'..PLAYER_REALM
lfgRoles[name] = rolePaths[role]
end
end
end
end
function CH:SocialQueueIsLeader(playerName, leaderName)
if leaderName == playerName then
return true
end
for i = 1, BNGetNumFriends() do
local accountInfo = C_BattleNet_GetFriendAccountInfo(i)
if accountInfo and accountInfo.gameAccountInfo and accountInfo.gameAccountInfo.isOnline then
local numGameAccounts = C_BattleNet_GetFriendNumGameAccounts(i)
if numGameAccounts then
for y = 1, numGameAccounts do
local gameAccountInfo = C_BattleNet_GetFriendGameAccountInfo(i, y)
if gameAccountInfo and (gameAccountInfo.clientProgram == BNET_CLIENT_WOW) and (accountInfo.accountName == playerName) then
playerName = gameAccountInfo.characterName
if gameAccountInfo.realmName and gameAccountInfo.realmName ~= E.myrealm then
playerName = format('%s-%s', playerName, E:ShortenRealm(gameAccountInfo.realmName))
end
if leaderName == playerName then
return true
end
end
end
end
end
end
end
local socialQueueCache = {}
local function RecentSocialQueue(TIME, MSG)
local previousMessage = false
if next(socialQueueCache) then
for guid, tbl in pairs(socialQueueCache) do
-- !dont break this loop! its used to keep the cache updated
if TIME and (difftime(TIME, tbl[1]) >= 300) then
socialQueueCache[guid] = nil --remove any older than 5m
elseif MSG and (MSG == tbl[2]) then
previousMessage = true --dont show any of the same message within 5m
-- see note for `message` in `SocialQueueMessage` about `MSG` content
end
end
end
return previousMessage
end
function CH:SocialQueueMessage(guid, message)
if not (guid and message) then return end
-- `guid` is something like `Party-1147-000011202574` and appears to update each time for solo requeue, otherwise on new group creation.
-- `message` is something like `|cff82c5ff|Kf58|k000000000000|k|r queued for: |cff00CCFFRandom Legion Heroic|r `
-- prevent duplicate messages within 5 minutes
local TIME = time()
if RecentSocialQueue(TIME, message) then return end
socialQueueCache[guid] = {TIME, message}
--UI_71_SOCIAL_QUEUEING_TOAST = 79739; appears to have no sound?
PlaySound(7355) --TUTORIAL_POPUP
E:Print(format('|Hsqu:%s|h%s|h', guid, strtrim(message)))
end
function CH:SocialQueueEvent(_, guid, numAddedItems) -- event, guid, numAddedItems
if not CH.db.socialQueueMessages then return end
if numAddedItems == 0 or not guid then return end
local players = C_SocialQueue_GetGroupMembers(guid)
if not players then return end
local firstMember, numMembers, extraCount, coloredName = players[1], #players, ''
local playerName, nameColor = _G.SocialQueueUtil_GetRelationshipInfo(firstMember.guid, nil, firstMember.clubId)
if numMembers > 1 then
extraCount = format(' +%s', numMembers - 1)
end
if playerName and playerName ~= '' then
coloredName = format('%s%s|r%s', nameColor, playerName, extraCount)
else
coloredName = format('{%s%s}', UNKNOWN, extraCount)
end
local queues = C_SocialQueue_GetGroupQueues(guid)
local firstQueue = queues and queues[1]
local isLFGList = firstQueue and firstQueue.queueData and firstQueue.queueData.queueType == 'lfglist'
if isLFGList and firstQueue and firstQueue.eligible then
local activityID, name, leaderName, fullName, isLeader
if firstQueue.queueData.lfgListID then
local searchResultInfo = C_LFGList_GetSearchResultInfo(firstQueue.queueData.lfgListID)
if searchResultInfo then
activityID, name, leaderName = searchResultInfo.activityID, searchResultInfo.name, searchResultInfo.leaderName
isLeader = CH:SocialQueueIsLeader(playerName, leaderName)
end
end
if activityID or firstQueue.queueData.activityID then
fullName = C_LFGList_GetActivityInfo(activityID or firstQueue.queueData.activityID)
end
if name then
CH:SocialQueueMessage(guid, format('%s %s: [%s] |cff00CCFF%s|r', coloredName, (isLeader and L["is looking for members"]) or L["joined a group"], fullName or UNKNOWN, name))
else
CH:SocialQueueMessage(guid, format('%s %s: |cff00CCFF%s|r', coloredName, (isLeader and L["is looking for members"]) or L["joined a group"], fullName or UNKNOWN))
end
elseif firstQueue then
local output, outputCount, queueCount = '', '', 0
for _, queue in pairs(queues) do
if type(queue) == 'table' and queue.eligible then
local queueName = (queue.queueData and _G.SocialQueueUtil_GetQueueName(queue.queueData)) or ''
if queueName ~= '' then
if output == '' then
output = gsub(queueName, '\n.+','') -- grab only the first queue name
queueCount = queueCount + select(2, gsub(queueName, '\n','')) -- collect additional on single queue
else
queueCount = queueCount + 1 + select(2, gsub(queueName, '\n','')) -- collect additional on additional queues
end
end
end
end
if output ~= '' then
if queueCount > 0 then outputCount = format(LFG_LIST_AND_MORE, queueCount) end
CH:SocialQueueMessage(guid, format('%s %s: |cff00CCFF%s|r %s', coloredName, SOCIAL_QUEUE_QUEUED_FOR, output, outputCount))
end
end
end
local FindURL_Events = {
'CHAT_MSG_WHISPER',
'CHAT_MSG_WHISPER_INFORM',
'CHAT_MSG_BN_WHISPER',
'CHAT_MSG_BN_WHISPER_INFORM',
'CHAT_MSG_BN_INLINE_TOAST_BROADCAST',
'CHAT_MSG_GUILD_ACHIEVEMENT',
'CHAT_MSG_GUILD',
'CHAT_MSG_OFFICER',
'CHAT_MSG_PARTY',
'CHAT_MSG_PARTY_LEADER',
'CHAT_MSG_RAID',
'CHAT_MSG_RAID_LEADER',
'CHAT_MSG_RAID_WARNING',
'CHAT_MSG_INSTANCE_CHAT',
'CHAT_MSG_INSTANCE_CHAT_LEADER',
'CHAT_MSG_CHANNEL',
'CHAT_MSG_SAY',
'CHAT_MSG_YELL',
'CHAT_MSG_EMOTE',
'CHAT_MSG_AFK',
'CHAT_MSG_DND',
'CHAT_MSG_COMMUNITIES_CHANNEL',
}
function CH:DefaultSmileys()
local x = ':16:16'
if next(CH.Smileys) then
wipe(CH.Smileys)
end
-- new keys
CH:AddSmiley(':angry:', E:TextureString(E.Media.ChatEmojis.Angry,x))
CH:AddSmiley(':blush:', E:TextureString(E.Media.ChatEmojis.Blush,x))
CH:AddSmiley(':broken_heart:', E:TextureString(E.Media.ChatEmojis.BrokenHeart,x))
CH:AddSmiley(':call_me:', E:TextureString(E.Media.ChatEmojis.CallMe,x))
CH:AddSmiley(':cry:', E:TextureString(E.Media.ChatEmojis.Cry,x))
CH:AddSmiley(':facepalm:', E:TextureString(E.Media.ChatEmojis.Facepalm,x))
CH:AddSmiley(':grin:', E:TextureString(E.Media.ChatEmojis.Grin,x))
CH:AddSmiley(':heart:', E:TextureString(E.Media.ChatEmojis.Heart,x))
CH:AddSmiley(':heart_eyes:', E:TextureString(E.Media.ChatEmojis.HeartEyes,x))
CH:AddSmiley(':joy:', E:TextureString(E.Media.ChatEmojis.Joy,x))
CH:AddSmiley(':kappa:', E:TextureString(E.Media.ChatEmojis.Kappa,x))
CH:AddSmiley(':middle_finger:', E:TextureString(E.Media.ChatEmojis.MiddleFinger,x))
CH:AddSmiley(':murloc:', E:TextureString(E.Media.ChatEmojis.Murloc,x))
CH:AddSmiley(':ok_hand:', E:TextureString(E.Media.ChatEmojis.OkHand,x))
CH:AddSmiley(':open_mouth:', E:TextureString(E.Media.ChatEmojis.OpenMouth,x))
CH:AddSmiley(':poop:', E:TextureString(E.Media.ChatEmojis.Poop,x))
CH:AddSmiley(':rage:', E:TextureString(E.Media.ChatEmojis.Rage,x))
CH:AddSmiley(':sadkitty:', E:TextureString(E.Media.ChatEmojis.SadKitty,x))
CH:AddSmiley(':scream:', E:TextureString(E.Media.ChatEmojis.Scream,x))
CH:AddSmiley(':scream_cat:', E:TextureString(E.Media.ChatEmojis.ScreamCat,x))
CH:AddSmiley(':slight_frown:', E:TextureString(E.Media.ChatEmojis.SlightFrown,x))
CH:AddSmiley(':smile:', E:TextureString(E.Media.ChatEmojis.Smile,x))
CH:AddSmiley(':smirk:', E:TextureString(E.Media.ChatEmojis.Smirk,x))
CH:AddSmiley(':sob:', E:TextureString(E.Media.ChatEmojis.Sob,x))
CH:AddSmiley(':sunglasses:', E:TextureString(E.Media.ChatEmojis.Sunglasses,x))
CH:AddSmiley(':thinking:', E:TextureString(E.Media.ChatEmojis.Thinking,x))
CH:AddSmiley(':thumbs_up:', E:TextureString(E.Media.ChatEmojis.ThumbsUp,x))
CH:AddSmiley(':semi_colon:', E:TextureString(E.Media.ChatEmojis.SemiColon,x))
CH:AddSmiley(':wink:', E:TextureString(E.Media.ChatEmojis.Wink,x))
CH:AddSmiley(':zzz:', E:TextureString(E.Media.ChatEmojis.ZZZ,x))
CH:AddSmiley(':stuck_out_tongue:', E:TextureString(E.Media.ChatEmojis.StuckOutTongue,x))
CH:AddSmiley(':stuck_out_tongue_closed_eyes:', E:TextureString(E.Media.ChatEmojis.StuckOutTongueClosedEyes,x))
-- Darth's keys
CH:AddSmiley(':meaw:', E:TextureString(E.Media.ChatEmojis.Meaw,x))
-- Simpy's keys
CH:AddSmiley('>:%(', E:TextureString(E.Media.ChatEmojis.Rage,x))
CH:AddSmiley(':%$', E:TextureString(E.Media.ChatEmojis.Blush,x))
CH:AddSmiley('<\\3', E:TextureString(E.Media.ChatEmojis.BrokenHeart,x))
CH:AddSmiley(':\'%)', E:TextureString(E.Media.ChatEmojis.Joy,x))
CH:AddSmiley(';\'%)', E:TextureString(E.Media.ChatEmojis.Joy,x))
CH:AddSmiley(',,!,,', E:TextureString(E.Media.ChatEmojis.MiddleFinger,x))
CH:AddSmiley('D:<', E:TextureString(E.Media.ChatEmojis.Rage,x))
CH:AddSmiley(':o3', E:TextureString(E.Media.ChatEmojis.ScreamCat,x))
CH:AddSmiley('XP', E:TextureString(E.Media.ChatEmojis.StuckOutTongueClosedEyes,x))
CH:AddSmiley('8%-%)', E:TextureString(E.Media.ChatEmojis.Sunglasses,x))
CH:AddSmiley('8%)', E:TextureString(E.Media.ChatEmojis.Sunglasses,x))
CH:AddSmiley(':%+1:', E:TextureString(E.Media.ChatEmojis.ThumbsUp,x))
CH:AddSmiley(':;:', E:TextureString(E.Media.ChatEmojis.SemiColon,x))
CH:AddSmiley(';o;', E:TextureString(E.Media.ChatEmojis.Sob,x))
-- old keys
CH:AddSmiley(':%-@', E:TextureString(E.Media.ChatEmojis.Angry,x))
CH:AddSmiley(':@', E:TextureString(E.Media.ChatEmojis.Angry,x))
CH:AddSmiley(':%-%)', E:TextureString(E.Media.ChatEmojis.Smile,x))
CH:AddSmiley(':%)', E:TextureString(E.Media.ChatEmojis.Smile,x))
CH:AddSmiley(':D', E:TextureString(E.Media.ChatEmojis.Grin,x))
CH:AddSmiley(':%-D', E:TextureString(E.Media.ChatEmojis.Grin,x))
CH:AddSmiley(';%-D', E:TextureString(E.Media.ChatEmojis.Grin,x))
CH:AddSmiley(';D', E:TextureString(E.Media.ChatEmojis.Grin,x))
CH:AddSmiley('=D', E:TextureString(E.Media.ChatEmojis.Grin,x))
CH:AddSmiley('xD', E:TextureString(E.Media.ChatEmojis.Grin,x))
CH:AddSmiley('XD', E:TextureString(E.Media.ChatEmojis.Grin,x))
CH:AddSmiley(':%-%(', E:TextureString(E.Media.ChatEmojis.SlightFrown,x))
CH:AddSmiley(':%(', E:TextureString(E.Media.ChatEmojis.SlightFrown,x))
CH:AddSmiley(':o', E:TextureString(E.Media.ChatEmojis.OpenMouth,x))
CH:AddSmiley(':%-o', E:TextureString(E.Media.ChatEmojis.OpenMouth,x))
CH:AddSmiley(':%-O', E:TextureString(E.Media.ChatEmojis.OpenMouth,x))
CH:AddSmiley(':O', E:TextureString(E.Media.ChatEmojis.OpenMouth,x))
CH:AddSmiley(':%-0', E:TextureString(E.Media.ChatEmojis.OpenMouth,x))
CH:AddSmiley(':P', E:TextureString(E.Media.ChatEmojis.StuckOutTongue,x))
CH:AddSmiley(':%-P', E:TextureString(E.Media.ChatEmojis.StuckOutTongue,x))
CH:AddSmiley(':p', E:TextureString(E.Media.ChatEmojis.StuckOutTongue,x))
CH:AddSmiley(':%-p', E:TextureString(E.Media.ChatEmojis.StuckOutTongue,x))
CH:AddSmiley('=P', E:TextureString(E.Media.ChatEmojis.StuckOutTongue,x))
CH:AddSmiley('=p', E:TextureString(E.Media.ChatEmojis.StuckOutTongue,x))
CH:AddSmiley(';%-p', E:TextureString(E.Media.ChatEmojis.StuckOutTongueClosedEyes,x))
CH:AddSmiley(';p', E:TextureString(E.Media.ChatEmojis.StuckOutTongueClosedEyes,x))
CH:AddSmiley(';P', E:TextureString(E.Media.ChatEmojis.StuckOutTongueClosedEyes,x))
CH:AddSmiley(';%-P', E:TextureString(E.Media.ChatEmojis.StuckOutTongueClosedEyes,x))
CH:AddSmiley(';%-%)', E:TextureString(E.Media.ChatEmojis.Wink,x))
CH:AddSmiley(';%)', E:TextureString(E.Media.ChatEmojis.Wink,x))
CH:AddSmiley(':S', E:TextureString(E.Media.ChatEmojis.Smirk,x))
CH:AddSmiley(':%-S', E:TextureString(E.Media.ChatEmojis.Smirk,x))
CH:AddSmiley(':,%(', E:TextureString(E.Media.ChatEmojis.Cry,x))
CH:AddSmiley(':,%-%(', E:TextureString(E.Media.ChatEmojis.Cry,x))
CH:AddSmiley(':\'%(', E:TextureString(E.Media.ChatEmojis.Cry,x))
CH:AddSmiley(':\'%-%(', E:TextureString(E.Media.ChatEmojis.Cry,x))
CH:AddSmiley(':F', E:TextureString(E.Media.ChatEmojis.MiddleFinger,x))
CH:AddSmiley('<3', E:TextureString(E.Media.ChatEmojis.Heart,x))
CH:AddSmiley('</3', E:TextureString(E.Media.ChatEmojis.BrokenHeart,x))
end
local channelButtons = {
_G.ChatFrameChannelButton,
_G.ChatFrameToggleVoiceDeafenButton,
_G.ChatFrameToggleVoiceMuteButton
}
function CH:GetAnchorParents(chat)
local Left = (chat == CH.LeftChatWindow and _G.LeftChatPanel)
local Right = (chat == CH.RightChatWindow and _G.RightChatPanel)
local Chat, TabPanel = Left or Right or _G.UIParent
if CH.db.panelTabBackdrop and not ((CH.db.panelBackdrop == 'HIDEBOTH') or (Left and CH.db.panelBackdrop == 'RIGHT') or (Right and CH.db.panelBackdrop == 'LEFT')) then
TabPanel = (Left and _G.LeftChatTab) or (Right and _G.RightChatTab)
end
return TabPanel or Chat, Chat
end
function CH:ReparentVoiceChatIcon(parent)
if not parent then
parent = CH:GetAnchorParents(_G.GeneralDockManager.primary)
end
for _, button in pairs(channelButtons) do
button:SetParent(parent)
end
end
function CH:RepositionOverflowButton()
_G.GeneralDockManagerOverflowButton:ClearAllPoints()
_G.GeneralDockManagerOverflowButton:Point('RIGHT', channelButtons[(channelButtons[3]:IsShown() and 3) or 1], 'LEFT', -4, 0)
end
function CH:UpdateVoiceChatIcons()
for _, button in ipairs(channelButtons) do
button.Icon:SetDesaturated(CH.db.desaturateVoiceIcons)
end
end
function CH:HandleChatVoiceIcons()
if CH.db.hideVoiceButtons then
for _, button in ipairs(channelButtons) do
button:Hide()
end
elseif CH.db.pinVoiceButtons then
for index, button in ipairs(channelButtons) do
Skins:HandleButton(button, nil, nil, true)
button.Icon:SetDesaturated(CH.db.desaturateVoiceIcons)
button:ClearAllPoints()
if index == 1 then
button:Point('RIGHT', _G.GeneralDockManager, 'RIGHT', 2, 0)
else
button:Point('RIGHT', channelButtons[index-1], 'LEFT')
end
end
CH:RepositionOverflowButton()
channelButtons[3]:HookScript('OnShow', CH.RepositionOverflowButton)
channelButtons[3]:HookScript('OnHide', CH.RepositionOverflowButton)
else
CH:CreateChatVoicePanel()
end
if not CH.db.hideVoiceButtons then
_G.GeneralDockManagerOverflowButtonList:SetFrameStrata('LOW')
_G.GeneralDockManagerOverflowButtonList:SetFrameLevel(5)
end
if not CH.db.pinVoiceButtons then
_G.GeneralDockManagerOverflowButton:ClearAllPoints()
_G.GeneralDockManagerOverflowButton:Point('RIGHT', _G.GeneralDockManager, 'RIGHT', -4, 0)
end
end
function CH:CreateChatVoicePanel()
local Holder = CreateFrame('Frame', 'ChatButtonHolder', E.UIParent, 'BackdropTemplate')
Holder:ClearAllPoints()
Holder:Point('BOTTOMLEFT', _G.LeftChatPanel, 'TOPLEFT', 0, 1)
Holder:Size(30, 86)
Holder:SetTemplate('Transparent', nil, true)
Holder:SetBackdropColor(CH.db.panelColor.r, CH.db.panelColor.g, CH.db.panelColor.b, CH.db.panelColor.a)
E:CreateMover(Holder, 'SocialMenuMover', _G.BINDING_HEADER_VOICE_CHAT, nil, nil, nil, nil, nil, 'chat')
channelButtons[1]:ClearAllPoints()
channelButtons[1]:Point('TOP', Holder, 'TOP', 0, -2)
for _, button in ipairs(channelButtons) do
Skins:HandleButton(button, nil, nil, true)
button.Icon:SetParent(button)
button.Icon:SetDesaturated(CH.db.desaturateVoiceIcons)
button:SetParent(Holder)
end
_G.ChatAlertFrame:ClearAllPoints()
_G.ChatAlertFrame:Point('BOTTOM', channelButtons[1], 'TOP', 1, 3)
-- Skin the QuickJoinToastButton
local Button = _G.QuickJoinToastButton
Button:CreateBackdrop()
Button:SetParent(Holder)
Button:ClearAllPoints()
Button:Point('BOTTOM', Holder, 'TOP', -E.Border, 2*E.Border)
Button:Size(30, 32)
-- Button:Hide() -- DONT KILL IT! If we use hide we also hide the Toasts, which are used in other Plugins.
-- Change the QuickJoin Textures. Looks better =)
local friendTex = [[Interface\HELPFRAME\ReportLagIcon-Chat]]
local queueTex = [[Interface\HELPFRAME\HelpIcon-ItemRestoration]]
Button.FriendsButton:SetTexture(friendTex)
Button.QueueButton:SetTexture(queueTex)
hooksecurefunc(Button, 'ToastToFriendFinished', function(t)
t.FriendsButton:SetShown(not t.displayedToast)
t.FriendCount:SetShown(not t.displayedToast)
end)
hooksecurefunc(Button, 'UpdateQueueIcon', function(t)
if not t.displayedToast then return end
t.FriendsButton:SetTexture(friendTex)
t.QueueButton:SetTexture(queueTex)
t.FlashingLayer:SetTexture(queueTex)
t.FriendsButton:SetShown(false)
t.FriendCount:SetShown(false)
end)
Button:HookScript('OnMouseDown', function(t) t.FriendsButton:SetTexture(friendTex) end)
Button:HookScript('OnMouseUp', function(t) t.FriendsButton:SetTexture(friendTex) end)
-- Skin the `QuickJoinToastButton.Toast`
Button.Toast:ClearAllPoints()
Button.Toast:Point('LEFT', Button, 'RIGHT', -6, 0)
Button.Toast.Background:SetTexture('')
Button.Toast:CreateBackdrop('Transparent')
Button.Toast.backdrop:Hide()
hooksecurefunc(Button, 'ShowToast', function() Button.Toast.backdrop:Show() end)
hooksecurefunc(Button, 'HideToast', function() Button.Toast.backdrop:Hide() end)
end
function CH:BuildCopyChatFrame()
local frame = CreateFrame('Frame', 'CopyChatFrame', E.UIParent, 'BackdropTemplate')
tinsert(_G.UISpecialFrames, 'CopyChatFrame')
frame:SetTemplate('Transparent')
frame:Size(700, 200)
frame:Point('BOTTOM', E.UIParent, 'BOTTOM', 0, 3)
frame:Hide()
frame:SetMovable(true)
frame:EnableMouse(true)
frame:SetResizable(true)
frame:SetMinResize(350, 100)
frame:SetScript('OnMouseDown', function(copyChat, button)
if button == 'LeftButton' and not copyChat.isMoving then
copyChat:StartMoving()
copyChat.isMoving = true
elseif button == 'RightButton' and not copyChat.isSizing then
copyChat:StartSizing()
copyChat.isSizing = true
end
end)
frame:SetScript('OnMouseUp', function(copyChat, button)
if button == 'LeftButton' and copyChat.isMoving then
copyChat:StopMovingOrSizing()
copyChat.isMoving = false
elseif button == 'RightButton' and copyChat.isSizing then
copyChat:StopMovingOrSizing()
copyChat.isSizing = false
end
end)
frame:SetScript('OnHide', function(copyChat)
if copyChat.isMoving or copyChat.isSizing then
copyChat:StopMovingOrSizing()
copyChat.isMoving = false
copyChat.isSizing = false
end
end)
frame:SetFrameStrata('DIALOG')
local scrollArea = CreateFrame('ScrollFrame', 'CopyChatScrollFrame', frame, 'UIPanelScrollFrameTemplate')
scrollArea:Point('TOPLEFT', frame, 'TOPLEFT', 8, -30)
scrollArea:Point('BOTTOMRIGHT', frame, 'BOTTOMRIGHT', -30, 8)
Skins:HandleScrollBar(_G.CopyChatScrollFrameScrollBar)
scrollArea:SetScript('OnSizeChanged', function(scroll)
_G.CopyChatFrameEditBox:Width(scroll:GetWidth())
_G.CopyChatFrameEditBox:Height(scroll:GetHeight())
end)
scrollArea:HookScript('OnVerticalScroll', function(scroll, offset)
_G.CopyChatFrameEditBox:SetHitRectInsets(0, 0, offset, (_G.CopyChatFrameEditBox:GetHeight() - offset - scroll:GetHeight()))
end)
local editBox = CreateFrame('EditBox', 'CopyChatFrameEditBox', frame)
editBox:SetMultiLine(true)
editBox:SetMaxLetters(99999)
editBox:EnableMouse(true)
editBox:SetAutoFocus(false)
editBox:SetFontObject(_G.ChatFontNormal)
editBox:Width(scrollArea:GetWidth())
editBox:Height(200)
editBox:SetScript('OnEscapePressed', function() _G.CopyChatFrame:Hide() end)
scrollArea:SetScrollChild(editBox)
_G.CopyChatFrameEditBox:SetScript('OnTextChanged', function(_, userInput)
if userInput then return end
local _, Max = _G.CopyChatScrollFrameScrollBar:GetMinMaxValues()
for _ = 1, Max do
_G.ScrollFrameTemplate_OnMouseWheel(_G.CopyChatScrollFrame, -1)
end
end)
local close = CreateFrame('Button', 'CopyChatFrameCloseButton', frame, 'UIPanelCloseButton, BackdropTemplate')
close:Point('TOPRIGHT')
close:SetFrameLevel(close:GetFrameLevel() + 1)
close:EnableMouse(true)
Skins:HandleCloseButton(close)
end
CH.TabStyles = {
NONE = '%s',
ARROW = '%s>|r%s%s<|r',
ARROW1 = '%s>|r %s %s<|r',
ARROW2 = '%s<|r%s%s>|r',
ARROW3 = '%s<|r %s %s>|r',
BOX = '%s[|r%s%s]|r',
BOX1 = '%s[|r %s %s]|r',
CURLY = '%s{|r%s%s}|r',
CURLY1 = '%s{|r %s %s}|r',
CURVE = '%s(|r%s%s)|r',
CURVE1 = '%s(|r %s %s)|r',
}
function CH:FCFTab_UpdateColors(tab, selected)
if not tab then return end
if tab:GetParent() == _G.ChatConfigFrameChatTabManager then
if selected then
tab.Text:SetTextColor(1, 1, 1)
end
local name = GetChatWindowInfo(tab:GetID())
if name then
tab.Text:SetText(name)
end
tab:SetAlpha(1) -- for some reason blizzard likes to change the alpha here? idk
else -- actual chat tab and other
local chat = CH:GetOwner(tab)
if not chat then return end
tab.selected = selected
local whisper = tab.conversationIcon and chat.chatTarget
local name = chat.name
if whisper and not tab.whisperName then
tab.whisperName = gsub(E:StripMyRealm(name), '([%S]-)%-[%S]+', '%1|cFF999999*|r')
end
if selected then
if CH.db.tabSelector == 'NONE' then
tab:SetFormattedText(CH.TabStyles.NONE, tab.whisperName or name)
else
local color = CH.db.tabSelectorColor
local hexColor = E:RGBToHex(color.r, color.g, color.b)
tab:SetFormattedText(CH.TabStyles[CH.db.tabSelector] or CH.TabStyles.ARROW1, hexColor, tab.whisperName or name, hexColor)
end
if CH.db.tabSelectedTextEnabled then
local color = CH.db.tabSelectedTextColor
tab.Text:SetTextColor(color.r, color.g, color.b)
return -- using selected text color
end
end
if whisper then
if not selected then
tab:SetText(tab.whisperName or name)
end
if not tab.classColor then
local classMatch = CH.ClassNames[strlower(name)]
if classMatch then tab.classColor = E:ClassColor(classMatch) end
end
if tab.classColor then
tab.Text:SetTextColor(tab.classColor.r, tab.classColor.g, tab.classColor.b)
end
else
if not selected then
tab:SetText(name)
end
tab.Text:SetTextColor(unpack(E.media.rgbvaluecolor))
end
end
end
function CH:GetAvailableHead()
for _, ChatHead in ipairs(CH.ChatHeadFrame) do
if not ChatHead:IsShown() then
return ChatHead
end
end
end
function CH:GetHeadByID(memberID)
for _, ChatHead in ipairs(CH.ChatHeadFrame) do
if ChatHead.memberID == memberID then
return ChatHead
end
end
end
function CH:ConfigureHead(memberID, channelID)
local frame = CH:GetAvailableHead()
if not frame then return end
frame.memberID = memberID
frame.channelID = channelID
C_VoiceChat_SetPortraitTexture(frame.Portrait.texture, memberID, channelID)
local memberName = C_VoiceChat_GetMemberName(memberID, channelID)
local r, g, b = _G.Voice_GetVoiceChannelNotificationColor(channelID)
frame.Name:SetText(memberName or '')
frame.Name:SetVertexColor(r, g, b, 1)
frame:Show()
end
function CH:DeconfigureHead(memberID) -- memberID, channelID
local frame = CH:GetHeadByID(memberID)
if not frame then return end
frame.memberID = nil
frame.channelID = nil
frame:Hide()
end
function CH:VoiceOverlay(event, ...)
if event == 'VOICE_CHAT_CHANNEL_MEMBER_SPEAKING_STATE_CHANGED' then
local memberID, channelID, isTalking = ...
if isTalking then
CH.TalkingList[memberID] = channelID
CH:ConfigureHead(memberID, channelID)
else
CH.TalkingList[memberID] = nil
CH:DeconfigureHead(memberID, channelID)
end
elseif event == 'VOICE_CHAT_CHANNEL_MEMBER_ENERGY_CHANGED' then
local memberID, channelID, volume = ...
local frame = CH:GetHeadByID(memberID)
if frame and channelID == frame.channelID then
frame.StatusBar.anim.progress:SetChange(volume)
frame.StatusBar.anim.progress:Play()
frame.StatusBar:SetStatusBarColor(E:ColorGradient(volume, 1, 0, 0, 1, 1, 0, 0, 1, 0))
end
--[[elseif event == 'VOICE_CHAT_CHANNEL_TRANSMIT_CHANGED' then
local channelID, isTransmitting = ...
local localPlayerMemberID = C_VoiceChat.GetLocalPlayerMemberID(channelID)
if isTransmitting and not CH.TalkingList[localPlayerMemberID] then
CH.TalkingList[localPlayerMemberID] = channelID
CH:ConfigureHead(localPlayerMemberID, channelID)
end]]
end
end
function CH:SetChatHeadOrientation(position)
local point, relativePoint, offset = 'TOP', 'BOTTOM', -E.Border*3
if position == 'BOTTOM' then -- This is never used. Maybe was supposed to be an option at one point?
point, relativePoint, offset = 'BOTTOM', 'TOP', E.Border*3
end
for i, ChatHead in ipairs(CH.ChatHeadFrame) do
ChatHead:ClearAllPoints()
ChatHead:Point(point, i == 1 and CH.ChatHeadFrame or CH.ChatHeadFrame[i - 1], relativePoint, 0, offset)
end
end
function CH:GetPlayerInfoByGUID(guid)
local data = CH.GuidCache[guid]
if not data then
local ok, localizedClass, englishClass, localizedRace, englishRace, sex, name, realm = pcall(GetPlayerInfoByGUID, guid)
if not (ok and englishClass) then return end
if realm == '' then realm = nil end -- dont add realm for people on your realm
local shortRealm, nameWithRealm = realm and E:ShortenRealm(realm)
if name and name ~= '' then
nameWithRealm = (shortRealm and name..'-'..shortRealm) or name..'-'..PLAYER_REALM
end
-- move em into a table
data = {
localizedClass = localizedClass,
englishClass = englishClass,
localizedRace = localizedRace,
englishRace = englishRace,
sex = sex,
name = name,
realm = realm,
nameWithRealm = nameWithRealm -- we use this to correct mobile to link with the realm as well
}
-- add it to ClassNames
if name then
CH.ClassNames[strlower(name)] = englishClass
end
if nameWithRealm then
CH.ClassNames[strlower(nameWithRealm)] = englishClass
end
-- push into the cache
CH.GuidCache[guid] = data
end
-- we still need to recheck this each time because CUSTOM_CLASS_COLORS can change
if data then data.classColor = E:ClassColor(data.englishClass) end
return data
end
function CH:ResetEditboxHistory()
ElvCharacterDB.ChatEditHistory = {}
end
function CH:ResetHistory()
ElvCharacterDB.ChatHistoryLog = {}
end
function CH:Initialize()
if ElvCharacterDB.ChatHistory then ElvCharacterDB.ChatHistory = nil end --Depreciated
if ElvCharacterDB.ChatLog then ElvCharacterDB.ChatLog = nil end --Depreciated
CH:DelayGuildMOTD() -- Keep this before `is Chat Enabled` check
CH.db = E.db.chat
if not E.private.chat.enable then
-- if the chat module is off we still need to spawn the dts for the panels
-- if we are going to have the panels show even when it's disabled
CH:PositionChats()
CH:Panels_ColorUpdate()
return
end
CH.Initialized = true
if not ElvCharacterDB.ChatEditHistory then ElvCharacterDB.ChatEditHistory = {} end
if not ElvCharacterDB.ChatHistoryLog or not CH.db.chatHistory then ElvCharacterDB.ChatHistoryLog = {} end
_G.ChatFrameMenuButton:Kill()
CH:SetupChat()
CH:DefaultSmileys()
CH:UpdateChatKeywords()
CH:UpdateFading()
CH:CheckLFGRoles()
CH:Panels_ColorUpdate()
CH:HandleChatVoiceIcons()
CH:UpdateEditboxAnchors()
E:UpdatedCVar('chatStyle', CH.UpdateEditboxAnchors)
CH:SecureHook('GetPlayerInfoByGUID')
CH:SecureHook('ChatEdit_SetLastActiveWindow')
CH:SecureHook('ChatEdit_DeactivateChat')
CH:SecureHook('ChatEdit_ActivateChat')
CH:SecureHook('ChatEdit_OnEnterPressed')
CH:SecureHook('FCFDock_UpdateTabs')
CH:SecureHook('FCF_Close')
CH:SecureHook('FCF_SetWindowAlpha')
CH:SecureHook('FCFTab_UpdateColors')
CH:SecureHook('FCFDock_SelectWindow')
CH:SecureHook('FCF_SetChatWindowFontSize', 'SetChatFont')
CH:SecureHook('FCF_SavePositionAndDimensions', 'SnappingChanged')
CH:SecureHook('FCF_UnDockFrame', 'SnappingChanged')
CH:SecureHook('FCF_DockFrame', 'SnappingChanged')
CH:SecureHook('FCF_ResetChatWindows', 'ClearSnapping')
CH:SecureHook('RedockChatWindows', 'ClearSnapping')
CH:RegisterEvent('UPDATE_CHAT_WINDOWS', 'SetupChat')
CH:RegisterEvent('UPDATE_FLOATING_CHAT_WINDOWS', 'SetupChat')
CH:RegisterEvent('GROUP_ROSTER_UPDATE', 'CheckLFGRoles')
CH:RegisterEvent('SOCIAL_QUEUE_UPDATE', 'SocialQueueEvent')
CH:RegisterEvent('PET_BATTLE_CLOSE')
if E.private.general.voiceOverlay then
CH:RegisterEvent('VOICE_CHAT_CHANNEL_MEMBER_SPEAKING_STATE_CHANGED', 'VoiceOverlay')
CH:RegisterEvent('VOICE_CHAT_CHANNEL_MEMBER_ENERGY_CHANGED', 'VoiceOverlay')
CH:RegisterEvent('VOICE_CHAT_CHANNEL_TRANSMIT_CHANGED', 'VoiceOverlay')
CH:RegisterEvent('VOICE_CHAT_COMMUNICATION_MODE_CHANGED', 'VoiceOverlay')
CH:RegisterEvent('VOICE_CHAT_CHANNEL_MEMBER_REMOVED', 'VoiceOverlay')
CH:RegisterEvent('VOICE_CHAT_CHANNEL_REMOVED', 'VoiceOverlay')
CH:RegisterEvent('VOICE_CHAT_CHANNEL_DEACTIVATED', 'VoiceOverlay')
_G.VoiceActivityManager:UnregisterAllEvents()
end
if _G.WIM then
_G.WIM.RegisterWidgetTrigger('chat_display', 'whisper,chat,w2w,demo', 'OnHyperlinkClick', function(frame) CH.clickedframe = frame end)
_G.WIM.RegisterItemRefHandler('url', HyperLinkedURL)
_G.WIM.RegisterItemRefHandler('squ', HyperLinkedSQU)
_G.WIM.RegisterItemRefHandler('cpl', HyperLinkedCPL)
end
for _, event in pairs(FindURL_Events) do
_G.ChatFrame_AddMessageEventFilter(event, CH[event] or CH.FindURL)
local nType = strsub(event, 10)
if nType ~= 'AFK' and nType ~= 'DND' and nType ~= 'COMMUNITIES_CHANNEL' then
CH:RegisterEvent(event, 'SaveChatHistory')
end
end
if CH.db.chatHistory then CH:DisplayChatHistory() end
CH:BuildCopyChatFrame()
-- Editbox Backdrop Color
hooksecurefunc('ChatEdit_UpdateHeader', function(editbox)
local chatType = editbox:GetAttribute('chatType')
if not chatType then return end
local ChatTypeInfo = _G.ChatTypeInfo
local info = ChatTypeInfo[chatType]
local chanTarget = editbox:GetAttribute('channelTarget')
local chanName = chanTarget and GetChannelName(chanTarget)
--Increase inset on right side to make room for character count text
local insetLeft, insetRight, insetTop, insetBottom = editbox:GetTextInsets()
editbox:SetTextInsets(insetLeft, insetRight + 30, insetTop, insetBottom)
if not editbox.backdrop then
editbox:CreateBackdrop(nil, true)
editbox.backdrop:SetAllPoints()
end
if chanName and (chatType == 'CHANNEL') then
if chanName == 0 then
editbox.backdrop:SetBackdropBorderColor(unpack(E.media.bordercolor))
else
info = ChatTypeInfo[chatType..chanName]
editbox.backdrop:SetBackdropBorderColor(info.r, info.g, info.b)
end
else
editbox.backdrop:SetBackdropBorderColor(info.r, info.g, info.b)
end
end)
--Chat Heads Frame
CH.ChatHeadFrame = CreateFrame('Frame', 'ElvUIChatHeadFrame', E.UIParent)
CH.ChatHeadFrame:Point('TOPLEFT', E.UIParent, 'TOPLEFT', 368, -210)
CH.ChatHeadFrame:Size(200, 20)
E:CreateMover(CH.ChatHeadFrame, 'VOICECHAT', L["Voice Overlay"])
CH.maxHeads = 5
CH.volumeBarHeight = 3
local CHAT_HEAD_HEIGHT = 40
for i = 1, CH.maxHeads do
local chatHead = CreateFrame('Frame', 'ElvUIChatHeadFrame'..i, CH.ChatHeadFrame)
chatHead:Width(CH.ChatHeadFrame:GetWidth())
chatHead:Height(CHAT_HEAD_HEIGHT)
chatHead.Portrait = CreateFrame('Frame', nil, chatHead, 'BackdropTemplate')
chatHead.Portrait:Width(CHAT_HEAD_HEIGHT - CH.volumeBarHeight)
chatHead.Portrait:Height(CHAT_HEAD_HEIGHT - CH.volumeBarHeight - E.Border*2)
chatHead.Portrait:Point('TOPLEFT', chatHead, 'TOPLEFT')
chatHead.Portrait:SetTemplate()
chatHead.Portrait.texture = chatHead.Portrait:CreateTexture(nil, 'OVERLAY')
chatHead.Portrait.texture:SetTexCoord(0.15, 0.85, 0.15, 0.85)
chatHead.Portrait.texture:SetInside(chatHead.Portrait)
chatHead.Name = chatHead:CreateFontString(nil, 'OVERLAY')
chatHead.Name:FontTemplate(nil, 20)
chatHead.Name:Point('LEFT', chatHead.Portrait, 'RIGHT', 2, 0)
chatHead.StatusBar = CreateFrame('StatusBar', nil, chatHead)
chatHead.StatusBar:Point('TOPLEFT', chatHead.Portrait, 'BOTTOMLEFT', E.Border, -E.Spacing*3)
chatHead.StatusBar:Width(CHAT_HEAD_HEIGHT - E.Border*2 - CH.volumeBarHeight)
chatHead.StatusBar:Height(CH.volumeBarHeight)
chatHead.StatusBar:CreateBackdrop()
chatHead.StatusBar:SetStatusBarTexture(E.media.normTex)
chatHead.StatusBar:SetMinMaxValues(0, 1)
chatHead.StatusBar.anim = _G.CreateAnimationGroup(chatHead.StatusBar)
chatHead.StatusBar.anim.progress = chatHead.StatusBar.anim:CreateAnimation('Progress')
chatHead.StatusBar.anim.progress:SetEasing('Out')
chatHead.StatusBar.anim.progress:SetDuration(.3)
chatHead:Hide()
CH.ChatHeadFrame[i] = chatHead
end
CH:SetChatHeadOrientation('TOP')
end
E:RegisterModule(CH:GetName())