Thread Tools Display Modes
10-11-18, 06:50 PM   #1
Terenna
A Flamescale Wyrmkin
AddOn Author - Click to view addons
Join Date: Jun 2016
Posts: 105
Code optimization and issue with hyperlinks in chat

Good evening everyone,

I'm playing around with how the chat appears. I've learned how incredibly complex the chat frame is, but I'm running into an issue I can't yet figure out.

The following code modifies how the chat appears to my liking. It works in all regards except one, if I link an item or achievement or someother hyperlink into chat, and have string(s) after the hyperlink, the color of the strings after the hyperlink turns white.

To put this in perspective, I would type "Wow look at this item: (shift click item). It's got amazing stats." and this would appear: Wow look at this item: [Sick DPS Weapon]. It's got amazing stats.

I can't for the life of me figure out why a hyperlink would cause an escape from the color sequence, or how I could get around it. If anyone has a suggestion on how to fix this, or sees a way to better optimize my code, please let me know:

Lua Code:
  1. local function RGBPercToHex(r, g, b)
  2.     r = r <= 1 and r >= 0 and r or 0
  3.     g = g <= 1 and g >= 0 and g or 0
  4.     b = b <= 1 and b >= 0 and b or 0
  5.     return string.format('%02x%02x%02x', r*255, g*255, b*255)
  6. end
  7.  
  8. ChatFrame_AddMessageEventFilter('CHAT_MSG_CHANNEL', function() return true end)
  9. local f = CreateFrame('frame')
  10. f:RegisterEvent('CHAT_MSG_CHANNEL')
  11. f:SetScript('OnEvent', function(self, event, ...)
  12.     local type = strsub(event, 10)
  13.     local info = ChatTypeInfo[type]
  14.     local timeStamp
  15.     local chatMessage, sender, senderLanguage, fullChannelName, _, senderFlags, _, channelNumber, _, _, chatLineID, senderGUID = ...
  16.     local senderLinkDisplay = GetColoredName(event, ...)
  17.     local playerLink = GetPlayerLink(sender, senderLinkDisplay, chatLineID, Chat_GetChatCategory(type), tostring(channelNumber))
  18.  
  19.     --first we get the timestamp with time(), then we format it with blizz's style with BetterDate
  20.     --then we change the text color to a grey and add brackets and finally remove the annoying space that gets between the string and ]
  21.     timeStamp = gsub(string.format('|cff'..RGBPercToHex(0.5, 0.5, 0.5)..'[%s]|r', BetterDate(CHAT_TIMESTAMP_FORMAT, time())), ' ', '')
  22.  
  23.     --properly color our channel number and following .
  24.     channelNumber = string.format('|cff'..RGBPercToHex(info.r, info.g, info.b)..'%s|r', channelNumber)
  25.  
  26.     --properly color our chat message in whatever channel it is
  27.     chatMessage = string.format('|cff'..RGBPercToHex(info.r, info.g, info.b)..'%s|r', chatMessage)
  28.  
  29.     --this block of code will search the chat for mentions of character's full name and change it to their class color
  30.     local test = chatMessage:gsub('[^a-zA-Z%s]', '')
  31.     local words = {strsplit(' ', test)}
  32.     for i = 1, #words do
  33.         --since we technically just changed our sentence or word to ff000000wordr or ff000000this is my sentencer
  34.         --we need to truncate the last word's trailing r and first word's hexcode
  35.         if i == 1 then
  36.             words[i] = strsub(words[i], 8) --cut off the opening timestamp color hexcode
  37.             if i == #words then
  38.                 words[i] = words[i]:sub(1, -1) --a one word message with a player name was said, now cut off the closing r, too
  39.             end
  40.         elseif i == #words then
  41.             words[i] = words[i]:sub(1, -1) --cut off the closing r
  42.         end
  43.         local w = words[i]
  44.         if (w and not (w == 'player' or w == 'target') and UnitName(w) and UnitIsPlayer(w)) then
  45.             local _, class = UnitClass(w)
  46.             local colors = RAID_CLASS_COLORS[class]
  47.             if (colors) then --replace the words that match player names with class colors, return back to the channel color for the next word(s)
  48.                 chatMessage = gsub(chatMessage, w, '|cff'..RGBPercToHex(colors.r, colors.g, colors.b)..'%1|r|cff'..RGBPercToHex(info.r, info.g, info.b))
  49.             end
  50.         end
  51.     end
  52.     chatMessage = timeStamp..' '..channelNumber..'. '..playerLink..': '..chatMessage
  53.  
  54.     ChatFrame1:AddMessage(chatMessage)
  55. end)

Last edited by Terenna : 10-11-18 at 07:36 PM.
  Reply With Quote
10-11-18, 07:35 PM   #2
Terenna
A Flamescale Wyrmkin
AddOn Author - Click to view addons
Join Date: Jun 2016
Posts: 105
I was able to come up with a shitty hack:

I switched

Lua Code:
  1. chatMessage = string.format('|cff'..RGBPercToHex(info.r, info.g, info.b)..'%s|r', chatMessage)

to

Lua Code:
  1. chatMessage = gsub(string.format('|cff'..RGBPercToHex(info.r, info.g, info.b)..'%s', chatMessage), '|h|r', '|h|r|cff'..RGBPercToHex(info.r, info.g, info.b))

which basically just looks for the return signature used by achievements, items, etc and replaces it with a hex open of our chat channel color
  Reply With Quote
10-12-18, 11:56 AM   #3
MuffinManKen
A Flamescale Wyrmkin
AddOn Author - Click to view addons
Join Date: Dec 2009
Posts: 106
If I'm understanding you correctly, you want to be able to set the text colour that's used by default in the chat frame and are jumping through hoops to make that work with colour encodings. But if that's what you're trying to do then it might be better to do that directly:

Lua Code:
  1. ChatFrame1:SetTextColor(r,g,b)

This way when things like links reset to default, it should be this colour.
  Reply With Quote
10-12-18, 01:24 PM   #4
Terenna
A Flamescale Wyrmkin
AddOn Author - Click to view addons
Join Date: Jun 2016
Posts: 105
Thank for the reply; I appreciate you taking time to look at my code.

That would work if all the text in the ChatFrame# was going to be the same color. Like if I had dedicated tabs for each individual chat type. Currently I have all my chat in the main ChatFrame (ChatFrame1) so setting a default like that would make anything that wasn't that color appear incorrectly. I can understand the thought process since I'm only showing a function for CHAT_MSG_CHANNEL, but this was just my first trial run before I handled ALL the chat events. There's so many
  Reply With Quote
10-12-18, 02:09 PM   #5
zork
A Pyroguard Emberseer
 
zork's Avatar
AddOn Author - Click to view addons
Join Date: Jul 2008
Posts: 1,740
This is what I use for url copy
Lua Code:
  1. local DefaultSetItemRef = SetItemRef
  2.  
  3. --we replace the default setitemref and use it to parse links for alt invite and url copy
  4. function SetItemRef(link, ...)
  5.   local type, value = link:match("(%a+):(.+)")
  6.   if IsAltKeyDown() and type == "player" then
  7.     InviteUnit(value:match("([^:]+)"))
  8.   elseif (type == "url") then
  9.     local eb = LAST_ACTIVE_CHAT_EDIT_BOX or ChatFrame1EditBox
  10.     if not eb then return end
  11.     eb:SetText(value)
  12.     eb:SetFocus()
  13.     eb:HighlightText()
  14.     if not eb:IsShown() then eb:Show() end
  15.   else
  16.     return DefaultSetItemRef(link, ...)
  17.   end
  18. end
  19.  
  20. --AddMessage
  21. local function AddMessage(self, text, ...)
  22.   --channel replace (Trade and such)
  23.   text = text:gsub('|h%[(%d+)%. .-%]|h', '|h%1.|h')
  24.   --url search
  25.   text = text:gsub('([wWhH][wWtT][wWtT][%.pP]%S+[^%p%s])', '|cffffffff|Hurl:%1|h[%1]|h|r')
  26.   return self.DefaultAddMessage(self, text, ...)
  27. end
  28.  
  29. --skin chat
  30. for i = 1, NUM_CHAT_WINDOWS do
  31.   local chatframe = _G["ChatFrame"..i]
  32.   --adjust channel display
  33.   if (i ~= 2) then
  34.     chatframe.DefaultAddMessage = chatframe.AddMessage
  35.     chatframe.AddMessage = AddMessage
  36.   end
  37. end

https://github.com/zorker/rothui/blo...rChat/core.lua
__________________
| Simple is beautiful.
| WoWI AddOns | GitHub | Zork (WoW)

"I wonder what the non-pathetic people are doing tonight?" - Rajesh Koothrappali (The Big Bang Theory)
  Reply With Quote
10-12-18, 02:56 PM   #6
Terenna
A Flamescale Wyrmkin
AddOn Author - Click to view addons
Join Date: Jun 2016
Posts: 105
Zork, thank you for your comment. I was previously using your AddMessage and skinChat loop, however, I was running into issues with tainting the DefaultAddMessage portion of AddMessage and the replacement in the skinChat loop. I posted about it here: https://www.wowinterface.com/forums/...ad.php?t=56773 in case you were interested in what I was finding.

This is my (probably shitty) attempt at not tainting the new communities section by having to go through a very verbose method as I'm describing in this thread. That is, instead of simply taking control of the DefaultAddMessage and getting the taint, I have to filter ALL messages across all event types and properly "skin" their appearance.
  Reply With Quote
10-15-18, 01:50 AM   #7
zork
A Pyroguard Emberseer
 
zork's Avatar
AddOn Author - Click to view addons
Join Date: Jul 2008
Posts: 1,740
Thanks. Yes replacing the default AddMessage and SetItemRef is bad since it can cause taint issues. Though I got no taints related to rChat yet.
If you have a working AddMessage and SetItemRef alternative that would be great.

Some code in the link is using
Lua Code:
  1. ChatFrame_AddMessageEventFilter('CHAT_MSG_CHANNEL', function() return true end)
Not sure if that makes sense when you can just remove the filter with
Lua Code:
  1. ChatFrame_RemoveMessageEventFilter (event, filter)
__________________
| Simple is beautiful.
| WoWI AddOns | GitHub | Zork (WoW)

"I wonder what the non-pathetic people are doing tonight?" - Rajesh Koothrappali (The Big Bang Theory)
  Reply With Quote
10-15-18, 03:48 AM   #8
Kanegasi
A Molten Giant
 
Kanegasi's Avatar
AddOn Author - Click to view addons
Join Date: Apr 2007
Posts: 666
Originally Posted by zork View Post
Some code in the link is using
Lua Code:
  1. ChatFrame_AddMessageEventFilter('CHAT_MSG_CHANNEL', function() return true end)
Not sure if that makes sense when you can just remove the filter with
Lua Code:
  1. ChatFrame_RemoveMessageEventFilter (event, filter)
Terenna is recreating ChatFrame_MessageEventHandler, that filter is to completely block the original event while their new code handles it, otherwise you get double messages printed.
  Reply With Quote
10-24-18, 10:27 AM   #9
Terenna
A Flamescale Wyrmkin
AddOn Author - Click to view addons
Join Date: Jun 2016
Posts: 105
This is nowhere near completed, but I wanted to provide anyone who was interested in this a sort of template that you may use to write your own code. As you can see at the bottom of the code, I've still got a vast majority of the events to handle, but believe I have most of the framework in place to make it pretty straightforward. I've tried to comment my code as much as possible and as plainly as possible.

Lua Code:
  1. --------------------------
  2. --Create Functions Section
  3. --------------------------
  4. local function rgbPercToHex(r, g, b) --converts decimal rgb to a hexcode
  5.     r = r <= 1 and r >= 0 and r or 0
  6.     g = g <= 1 and g >= 0 and g or 0
  7.     b = b <= 1 and b >= 0 and b or 0
  8.     return string.format('|cff%02x%02x%02x', r*255, g*255, b*255)
  9. end
  10.  
  11. local DefaultSetItemRef = SetItemRef --this lets us alt click invite and click on url links, it also probably causes taint
  12. function SetItemRef(link, ...)
  13.     local type, value = link:match('(%a+):(.+)')
  14.     if IsAltKeyDown() and type == 'player' then
  15.         InviteUnit(value:match('([^:]+)'))
  16.     elseif (type == 'url') then
  17.         local eb = LAST_ACTIVE_CHAT_EDIT_BOX or ChatFrame1EditBox
  18.         if not eb then return end
  19.         eb:Show()
  20.         eb:SetText(value)
  21.         eb:SetFocus()
  22.         eb:HighlightText()
  23.     else
  24.         return DefaultSetItemRef(link, ...)
  25.     end
  26. end
  27.  
  28. local function createPlayerLinks(event, chatType, ...) --this creates the clickable hyperlink found for the names of players in chat
  29.     local senderLinkDisplay = GetColoredName(event, ...) --blizzard function
  30.     local _, sender, _, _, _, _, _, channelNumber, _, _, chatLineID, _ = ...
  31.     local playerLink = GetPlayerLink(sender, senderLinkDisplay, chatLineID, Chat_GetChatCategory(chatType), tostring(channelNumber) or nil) --blizzard function, have to pass the channel number as a string, some events won't have a channel number (says, whispers, yells etc) so blizzard code doesn't need this argument and accepts nil, too
  32.     return playerLink
  33. end
  34.  
  35. local function hexColorNames(chatMessage, modChatColor) --this will churn through a message and find names that match players and color it by their class color
  36.     local test = chatMessage:gsub('[^a-zA-Z%s]', '')
  37.     local words = {strsplit(' ', test)}
  38.     for i = 1, #words do
  39.         --since we technically just changed our sentence or word to ff000000wordr or ff000000this is my sentencer,
  40.         --we need to truncate the last word's trailing r and first word's hexcode
  41.         if i == 1 then
  42.             words[i] = strsub(words[i], 8) --cut off the opening timestamp color hexcode
  43.             if i == #words then
  44.                 words[i] = words[i]:sub(1, -1) --a one word message with a player name was said, now cut off the closing r, too
  45.             end
  46.         elseif i == #words then
  47.             words[i] = words[i]:sub(1, -1) --cut off the closing r
  48.         end
  49.  
  50.         local w = words[i]
  51.         if (w and not (w == 'player' or w == 'target') and UnitName(w) and UnitIsPlayer(w)) then
  52.             local _, class = UnitClass(w)
  53.             local colors = RAID_CLASS_COLORS[class]
  54.             if (colors) then --color the words that match player names with class colors, return back to the channel color for the next word(s)
  55.                 chatMessage = gsub(chatMessage, w, rgbPercToHex(colors.r, colors.g, colors.b)..w..modChatColor)
  56.             end
  57.         end
  58.     end
  59.     return chatMessage
  60. end
  61.  
  62. local function handleJoinLeaveChannel(...)
  63.     local _, playerName, _, _, _, _, _, _, channelName = ...
  64.     local _, class = UnitClass(playerName)
  65.     local colors = RAID_CLASS_COLORS[class]
  66.     local playerClassColorHex = rgbPercToHex(colors.r, colors.g, colors.b)..playerName
  67.     return playerClassColorHex, channelName
  68. end
  69.  
  70. local function getNameColors(name)
  71.     if strfind(name, '-') then
  72.         name = strsub(name, 1, strfind(name, '-') - 1)
  73.     end
  74.  
  75.     local _, class = UnitClass(name)
  76.     colors = RAID_CLASS_COLORS[class]
  77.     return colors, name
  78. end
  79.  
  80. local function colorAndFormatChatString(text, modChatColor)
  81.     text = text:gsub('([wWhH][wWtT][wWtT][%.pP]%S+[^%p%s])', '|cffffffff|Hurl:%1|h[%1]|h|r') --find any URLs and make them white hyperlinks
  82.     text = modChatColor..text --color our message so it matches whatever channel we're in
  83.     text = text:gsub('|h|r', '|h|r'..modChatColor) --this is a shitty hack so if someone puts a hyperlink in chat, the text afterwards will go back to the default chat color
  84.     text = hexColorNames(text, modChatColor) --color any player names in chat by their class color
  85.     return text
  86. end
  87. -----------------------------------------------------
  88. --Create Frames, Event Handler, and Filters Section--
  89. -----------------------------------------------------
  90. local channelColorHolderTable = {} --this is used to hold the color of custom chat channels, this is important when you leave the channel
  91. --ToDo: make a for k,v in pairs(table) do /n AddMessageFilter and RegisterEvent to save lines of code
  92. ChatFrame_AddMessageEventFilter('CHAT_MSG_CHANNEL', function() return true end)
  93. ChatFrame_AddMessageEventFilter('CHAT_MSG_CHANNEL_JOIN', function() return true end)
  94. ChatFrame_AddMessageEventFilter('CHAT_MSG_CHANNEL_LEAVE', function() return true end)
  95. ChatFrame_AddMessageEventFilter('CHAT_MSG_CHANNEL_LIST', function() return true end)
  96. ChatFrame_AddMessageEventFilter('CHAT_MSG_CHANNEL_NOTICE', function() return true end)
  97. ChatFrame_AddMessageEventFilter('CHAT_MSG_CHANNEL_NOTICE_USER', function() return true end)
  98.  
  99. ChatFrame_AddMessageEventFilter('CHAT_MSG_AFK', function() return true end)
  100. ChatFrame_AddMessageEventFilter('CHAT_MSG_DND', function() return true end)
  101. --ChatFrame_AddMessageEventFilter('CHAT_MSG_BN_WHISPER', function() return true end)
  102. --ChatFrame_AddMessageEventFilter('CHAT_MSG_BN_WHISPER_INFORM', function() return true end)
  103. --ChatFrame_AddMessageEventFilter('CHAT_MSG_BN_WHISPER_PLAYER_OFFLINE', function() return true end)
  104. --ChatFrame_AddMessageEventFilter('CHAT_MSG_EMOTE', function() return true end)
  105. --ChatFrame_AddMessageEventFilter('CHAT_MSG_IGNORED', function() return true end)
  106. --ChatFrame_AddMessageEventFilter('CHAT_MSG_SAY', function() return true end)
  107. --ChatFrame_AddMessageEventFilter('CHAT_MSG_TEXT_EMOTE', function() return true end)
  108. --ChatFrame_AddMessageEventFilter('CHAT_MSG_WHISPER', function() return true end)
  109. --ChatFrame_AddMessageEventFilter('CHAT_MSG_WHISPER_INFORM', function() return true end)
  110. --ChatFrame_AddMessageEventFilter('CHAT_MSG_YELL', function() return true end)
  111.  
  112. local f = CreateFrame('frame')
  113. f:RegisterEvent('CHAT_MSG_CHANNEL')
  114. f:RegisterEvent('CHAT_MSG_CHANNEL_JOIN')
  115. f:RegisterEvent('CHAT_MSG_CHANNEL_LEAVE')
  116. f:RegisterEvent('CHAT_MSG_CHANNEL_LIST')
  117. f:RegisterEvent('CHAT_MSG_CHANNEL_NOTICE')
  118. f:RegisterEvent('CHAT_MSG_CHANNEL_NOTICE_USER')
  119.  
  120. f:RegisterEvent('CHAT_MSG_AFK')
  121. f:RegisterEvent('CHAT_MSG_DND')
  122. --f:RegisterEvent('CHAT_MSG_BN_WHISPER')
  123. --f:RegisterEvent('CHAT_MSG_BN_WHISPER_INFORM')
  124. --f:RegisterEvent('CHAT_MSG_BN_WHISPER_PLAYER_OFFLINE')
  125. --f:RegisterEvent('CHAT_MSG_EMOTE')
  126. --f:RegisterEvent('CHAT_MSG_IGNORED')
  127. --f:RegisterEvent('CHAT_MSG_SAY')
  128. --f:RegisterEvent('CHAT_MSG_TEXT_EMOTE')
  129. --f:RegisterEvent(' ')
  130. --f:RegisterEvent('CHAT_MSG_WHISPER_INFORM')
  131. --f:RegisterEvent('CHAT_MSG_YELL')
  132.  
  133. f:SetScript('OnEvent', function(self, event, ...) --this is the main handler that creates timeStamp, gets the chat color and passes it along as a hexcode
  134.     local message, player, _, fullChannelName, truncatedPlayer, _, _, channelNumber, channelName, _, _, playerGUID = ...
  135.     local chatType = strsub(event, 10) --chop off first 'CHAT_MSG_' to get the event
  136.     local chatColor = {}
  137.     if strsub(event, 10, 16) == 'CHANNEL' then --this will color chats by their proper color since they're user editable
  138.         chatColor = channelColorHolderTable[channelNumber]
  139.     else
  140.         chatColor = ChatTypeInfo[chatType]
  141.     end
  142.     local timeStamp --this will be used to make our own time stamp for each line
  143.     timeStamp = BetterDate(CHAT_TIMESTAMP_FORMAT, time())
  144.     timeStamp = rgbPercToHex(0.5, 0.5, 0.5)..'['..timeStamp..']|r' --this colors the brackets and text grey and adds them around the text
  145.     timeStamp = gsub(timeStamp, ' ', '') --this removes the random space that gets added between the end of the time string and the ]
  146.     local modChatColor = rgbPercToHex(chatColor.r, chatColor.g, chatColor.b)
  147.  
  148.     if self[event] then --handle our event
  149.         self[event](self, event, chatType, modChatColor, timeStamp, ...)
  150.     end
  151. end)
  152.  
  153. local g = CreateFrame('frame')
  154. g:RegisterEvent('UPDATE_CHAT_COLOR')
  155. --this will find any channels that are associated with numbers, that is custom channels
  156. --it does this by first removing the 'CHANNEL' before the number, and converting the string to a number
  157. --since there are many strings that don't get converted, we find only the ones that are numbers by our if check
  158. --we then make a table entry for the color rgb so when a player leaves that channel, and no color is associated anymore,
  159. --the leaving text still matches the color they left
  160. g:SetScript('OnEvent', function(self, event, ...)
  161.     local channelNumber, r, g, b = ...
  162.     channelNumber = channelNumber:gsub('CHANNEL', '')
  163.     channelNumber = tonumber(channelNumber)
  164.     if channelNumber then
  165.         channelColorHolderTable[channelNumber] = {}
  166.         channelColorHolderTable[channelNumber].r, channelColorHolderTable[channelNumber].g, channelColorHolderTable[channelNumber].b = r, g, b
  167.     end
  168. end)
  169. ----------------------------------------------
  170. --Modified GlobalStrings.lua strings Section--
  171. ----------------------------------------------
  172. CHAT_ANNOUNCEMENTS_OFF_NOTICE       = '%s disabled channel announcements in %s.|r' --playerName..modChatColor, channelName
  173. CHAT_ANNOUNCEMENTS_ON_NOTICE        = '%s enabled channel announcements in %s.|r' --playerName..modChatColor, channelName
  174. CHAT_CHANNEL_OWNER_NOTICE           = '%s is the owner of %s.|r' --playerName..modChatColor, channelName
  175. CHAT_MODERATION_OFF_NOTICE          = '%s disabled channel moderation in %s.|r' --playerName..modChatColor, channelName
  176. CHAT_MODERATION_ON_NOTICE           = '%s enabled channel moderation in %s.|r' --playerName..modChatColor, channelName
  177. CHAT_VOICE_OFF_NOTICE               = '%s disabled channel voice in %s.|r' --playerName..modChatColor, channelName
  178. CHAT_VOICE_ON_NOTICE                = '%s enabled channel voice in %s.|r' --playerName..modChatColor, channelName
  179. CHAT_SET_MODERATOR_NOTICE           = '%s received moderator privileges in %s.|r' --playerName..modChatColor, channelName
  180. CHAT_UNSET_MODERATOR_NOTICE         = '%s lost moderator privileges in %s.|r' --playerName..modChatColor, channelName
  181. CHAT_SET_SPEAK_NOTICE               = '%s received voice chat privileges in %s.|r' --playerName..modChatColor, channelName
  182. CHAT_UNSET_SPEAK_NOTICE             = '%s lost voice chat privileges in %s.|r' --playerName..modChatColor, channelName
  183. CHAT_SET_VOICE_NOTICE               = '%s received chat privileges in %s.|r' --playerName..modChatColor, channelName
  184. CHAT_UNSET_VOICE_NOTICE             = '%s lost chat privileges in %s.|r' --playerName..modChatColor, channelName
  185. CHAT_PASSWORD_CHANGED_NOTICE        = '%s changed the password of %s.|r' --playerName..modChatColor, channelName
  186. CHAT_PLAYER_ALREADY_MEMBER_NOTICE   = '%s is already a member of %s.|r' --playerName..modChatColor, channelName
  187. CHAT_OWNER_CHANGED_NOTICE           = '%s is now the owner of %s.|r' --playerName..modChatColor, channelName
  188. CHAT_INVITE_NOTICE                  = '%s%s invited you to join %s.|r' --playerName..modchatColor, channelName
  189.  
  190. CHAT_BANNED_NOTICE                  = '%sYou are banned from %s.|r' --modChatColor, channelName
  191. CHAT_INVALID_NAME_NOTICE            = '%sThe channel you tried to join, %s, does not exist.|r' --modChatColor, channelName
  192. CHAT_INVITE_WRONG_FACTION_NOTICE    = '%sThat player cannot join %s because they are the wrong faction.|r' --modChatColor, channelName
  193. CHAT_MUTED_NOTICE                   = '%sYou do not have permission to speak in %s.|r' --modChatColor, channelName
  194. CHAT_NOT_ALLOWED_IN_CHANNEL_NOTICE  = '%sYou attempted an action that is not allowed in %s.|r' --modChatColor, channelName
  195. CHAT_NOT_IN_AREA_NOTICE             = '%sYou are not in the correct area for %s.|r' --modChatColor, channelName
  196. CHAT_NOT_MEMBER_NOTICE              = '%sYou are not a member of %s.|r' --modChatColor, channelName
  197. CHAT_NOT_MODERATED_NOTICE           = '%s%s is not moderated.|r' --modChatColor, channelName
  198. CHAT_NOT_MODERATOR_NOTICE           = '%sYou are not a moderator of %s.|r' --modChatColor, channelName
  199. CHAT_NOT_OWNER_NOTICE               = '%sYou are not the owner of %s.|r' --modChatColor, channelName
  200. CHAT_YOU_JOINED_NOTICE              = '%sYou joined %s.|r' --modChatColor, channelName
  201.  
  202. CHAT_PLAYER_BANNED_NOTICE           = '%s banned %s from %s.|r' --playerName..modChatColor, actionTarget, channelName
  203. CHAT_PLAYER_KICKED_NOTICE           = '%s kicked %s from %s.|r' --playerName..modChatColor, actionTarget, channelName
  204. CHAT_PLAYER_UNBANNED_NOTICE         = '%s unbanned %s from %s.|r' --playerName..modChatColor, actionTarget, channelName
  205.  
  206. CHAT_PLAYER_INVITED_NOTICE          = 'You invited %s to join %s.|r' --actionTarget, channelName
  207. CHAT_PLAYER_INVITE_BANNED_NOTICE    = 'You cannot invite %s to %s because they are banned.|r' --actionTarget, channelName
  208.  
  209. CHAT_PLAYER_NOT_BANNED_NOTICE       = '%s is not banned from %s.|r' --actionTarget..modChatColor, channelName
  210. CHAT_PLAYER_NOT_FOUND_NOTICE        = '%s is not in %s.|r' --actionTarget..modChatColor, channelName
  211. --------------------------------------
  212. --Chat Channel Modifications Section--
  213. --------------------------------------
  214. local noticeUserStrings = {
  215.     ['ANNOUNCEMENTS_OFF']   = true,
  216.     ['ANNOUNCEMENTS_ON']    = true,
  217.     ['CHANNEL_OWNER']       = true,
  218.     ['MODERATION_OFF']      = true,
  219.     ['MODERATION_ON']       = true,
  220.     ['VOICE_OFF']           = true,
  221.     ['VOICE_ON']            = true,
  222.     ['SET_MODERATOR']       = true,
  223.     ['UNSET_MODERATOR']     = true,
  224.     ['SET_SPEAK']           = true,
  225.     ['UNSET_SPEAK']         = true,
  226.     ['SET_VOICE']           = true,
  227.     ['UNSET_VOICE']         = true,
  228.     ['PASSWORD_CHANGED']    = true,
  229.     ['PLAYER_ALREADY_MEMBER']   = true,
  230.     ['OWNER_CHANGED']       = true,
  231.     ['INVITE_NOTICE']       = true
  232. }
  233.  
  234. local channelNoticeStrings = {
  235.     ['BANNED']              = true,
  236.     ['INVALID_NAME']        = true,
  237.     ['INVITE_WRONG_FACTION']= true,
  238.     ['MUTED']               = true,
  239.     ['NOT_ALLOWED_IN_CHANNEL']  = true,
  240.     ['NOT_IN_AREA']         = true,
  241.     ['NOT_MEMBER']          = true,
  242.     ['NOT_MODERATED']       = true,
  243.     ['NOT_MODERATOR']       = true,
  244.     ['NOT_OWNER']           = true,
  245.     ['YOU_JOINED']          = true
  246. }
  247.  
  248. local targetActionNoticeStrings = {
  249.     ['PLAYER_BANNED']       = true,
  250.     ['PLAYER_KICKED']       = true,
  251.     ['PLAYER_UNBANNED']     = true
  252. }
  253.  
  254. local yourInviteNoticeStrings = {
  255.     ['PLAYER_INVITED']      = true,
  256.     ['PLAYER_INVITE_BANNED']= true
  257. }
  258.  
  259. local targetErrorNoticeStrings = {
  260.     ['PLAYER_NOT_BANNED']   = true,
  261.     ['PLAYER_NOT_FOUND']    = true
  262. }
  263.  
  264. function f:CHAT_MSG_CHANNEL(event, chatType, modChatColor, timeStamp, ...) --anytime a message is added by you or another player
  265.     local chatMessage, sender, senderLanguage, fullChannelName, arg5, senderFlags, arg7, channelNumber, arg9, arg10, chatLineID, senderGUID = ...
  266.  
  267.     channelNumber = modChatColor..channelNumber..'.|r' --properly color and skin the channel number to appear as '#.'
  268.  
  269.     chatMessage = colorAndFormatChatString(chatMessage, modChatColor) --handle url hyperlinking, channel coloring, and coloring names that may appear in chat
  270.  
  271.     if senderGUID then --hack for local defense 'x zone is under attack' messages trying to make player links when no one was making the phrase
  272.         local playerLink = createPlayerLinks(event, chatType, ...) --generate our hyperlinks for players in chat
  273.         chatMessage = timeStamp.. ' '..channelNumber..' '..playerLink..modChatColor..': |r'..chatMessage --put it all together
  274.     else
  275.         chatMessage = timeStamp..' '..channelNumber..modChatColor..': |r'..chatMessage --put it all together
  276.     end
  277.  
  278.     ChatFrame1:AddMessage(chatMessage)
  279. end
  280.  
  281. function f:CHAT_MSG_CHANNEL_NOTICE(event, chatType, modChatColor, timeStamp, ...) --fires when you change channels
  282.     local notice, _, _, _, _, _, _, _, channelName = ...
  283.     if notice then
  284.         local _, class = UnitClass('player')
  285.         local colors = RAID_CLASS_COLORS[class]
  286.         local You = rgbPercToHex(colors.r, colors.g, colors.b)..'You' --color the word 'You' by your class color
  287.  
  288.         if notice == 'YOU_CHANGED' then
  289.             ChatFrame1:AddMessage(timeStamp..' '..You..modChatColor..' joined '..channelName..'.|r')
  290.         elseif (notice == 'YOU_LEFT' or notice == 'SUSPENDED') then
  291.             ChatFrame1:AddMessage(timeStamp..' '..You..modChatColor..' left '..channelName..'.|r')
  292.         elseif notice == 'THROTTLED' then
  293.             ChatFrame1:AddMessage(timeStamp..' '..You..modChatColor..' were throttled in '..channelName..'.|r')
  294.         end
  295.     end
  296. end
  297.  
  298. function f:CHAT_MSG_CHANNEL_JOIN(event, chatType, modChatColor, timeStamp, ...) --fires when another player joins a channel you're in
  299.     local playerClassColorHex, channelName = handleJoinLeaveChannel(...) --returns a colorized player name and the colored channel name
  300.     ChatFrame1:AddMessage(timestamp..' '..playerName..modChatColor..' + '..channelName..'.|r')
  301. end
  302.  
  303. function f:CHAT_MSG_CHANNEL_LEAVE(event, chatType, modChatColor, timeStamp, ...) --fires when another player leaves a channel you're in
  304.     local playerClassColorHex, channelName = handleJoinLeaveChannel(...) --returns a colorized player name and the colored channel name
  305.     ChatFrame1:AddMessage(timestamp..' '..playerName..modChatColor..' - '..channelName..'.|r')
  306. end
  307.  
  308. function f:CHAT_MSG_CHANNEL_LIST(event, chatType, modChatColor, timeStamp, ...) --fires when you type /script ListChannels()
  309.     local arg1 = ... --arg1 is the string of channels
  310.     ChatFrame1:AddMessage(timeStamp..' '..'|cffc0c0c0'..arg1..'|r') --cffc0c0c0 is the hexcode for a grey text
  311. end
  312.  
  313. function f:CHAT_MSG_CHANNEL_NOTICE_USER(event, chatType, modChatColor, timeStamp, ...) --fires after a LOT of different chat events related to channel management
  314.     local notice, playerName, _, _, actionTarget, _, _, _, channelName = ...
  315.     local colors, targetColors --call these up top so they're accessible to the if statements below
  316.     if playerName then
  317.         colors, playerName = getNameColors(playerName)
  318.         playerName = rgbPercToHex(colors. r, colors.g, colors.b)..playerName
  319.     end
  320.  
  321.     if actionTarget then
  322.         targetColors = getNameColors(actionTarget)
  323.         actionTarget = rgbPercToHex(targetColors.r, targetColors.g, targetColors.b)..actionTarget
  324.     end
  325.     --the idea of this is to run through various hash tables and handle these strings accordingly. this has more pipeline stalls than I'd like,
  326.     --but it's better than a much longer if elseif chain for each individual case. the final lines have a catch in case I missed a notice and someone finds it other than me
  327.     if noticeUserStrings[notice] then
  328.         colors = getNameColors('player')
  329.         local you = rgbPercToHex(colors.r, colors.g, colors.b)..'you'
  330.         _G['CHAT_'..notice..'_NOTICE'] = _G['CHAT_'..notice..'_NOTICE']:gsub('you', you..modChatColor) --replace 'you' with a class colored 'you'
  331.         ChatFrame1:AddMessage(string.format(timeStamp..' '.._G['CHAT_'..notice..'_NOTICE'], playerName..modChatColor, channelName))
  332.     elseif channelNoticeStrings[notice] then
  333.         colors = getNameColors('player')
  334.         local You = rgbPercToHex(colors.r, colors.g, colors.b)..'You'
  335.         local you = rgbPercToHex(colors.r, colors.g, colors.b)..'you'
  336.         _G['CHAT_'..notice..'_NOTICE'] = _G['CHAT_'..notice..'_NOTICE']:gsub('You', You..modChatColor) --replace 'You' with a class colored 'You'
  337.         _G['CHAT_'..notice..'_NOTICE'] = _G['CHAT_'..notice..'_NOTICE']:gsub('you', you..modChatColor) --replace 'you' with a class colored 'you'
  338.         ChatFrame1:AddMessage(string.format(timeStamp..' '.._G['CHAT_'..notice..'_NOTICE'], modChatColor, channelName))
  339.     elseif targetActionNoticeStrings[notice] then
  340.         ChatFrame1:AddMessage(string.format(timeStamp..' '.._G['CHAT_'..notice..'_NOTICE'], playerName..modChatColor, actionTarget, channelName))
  341.     elseif yourInviteNoticeStrings[notice] then
  342.         colors = getNameColors('player')
  343.         local You = rgbPercToHex(colors.r, colors.g, colors.b)..'You'
  344.         _G['CHAT_'..notice..'NOTICE'] = _G['CHAT_'..notice..'_NOTICE']:gsub('You', You..modChatColor) --replace 'You' with a class colored 'You'
  345.         ChatFrame1:AddMessage(string.format(timeStamp..' '.._G['CHAT_'..notice..'_NOTICE'], actionTarget, channelName))
  346.     elseif targetActionNoticeStrings[notice] then
  347.         ChatFrame1:AddMessage(string.format(timeStamp..' '.._G['CHAT_'..notice..'_NOTICE'], actionTarget..modChatColor, channelName))
  348.     else
  349.         print('tell terenna that the '..notice..' is not properly handled.')
  350.     end
  351. end
  352.  
  353. ------------------------------------------------
  354. --Say, Yell, and Whisper Modifications Section--
  355. ------------------------------------------------
  356. --[[
  357. removing the brackets around this would allow you to once again see the player you whispered's afk message. we filtered it out above.
  358. function f:CHAT_MSG_AFK(event, chatType, modChatColor, timeStamp, ...)
  359.     local afkMessage, fullNameAndRealm, _, _, nameNoRealm = ...
  360.     local playerLink = createPlayerLinks(event, chatType, ...) --generate our hyperlinks for players in chat
  361.     afkMessage = colorAndFormatChatString(afkMessage, modChatColor)
  362.     afkMessage = timeStamp.. ' '..playerLink..modChatColor..' is away: |r'..afkMessage --put it all together
  363.     ChatFrame1:AddMessage(afkMessage)
  364. end
  365. ]]--
  366. --[[
  367. removing the brackets around this would allow you to once again see the player you whispered's dnd message. we filtered it out above.
  368. function f:CHAT_MSG_DND(event, chatType, modChatColor, timeStamp, ...)
  369.     local dndMessage, fullNameAndRealm, _, _, nameNoRealm = ...
  370.     local playerLink = createPlayerLinks(event, chatType, ...) --generate our hyperlinks for players in chat
  371.     dndMessage = colorAndFormatChatString(dndMessage, modChatColor)
  372.     dndMessage = timeStamp.. ' '..playerLink..modChatColor..' does not wished to be disturbed: |r'..dndMessage --put it all together
  373.     ChatFrame1:AddMessage(dndMessage)
  374. end
  375. ]]--
  376. --[[
  377. CHAT_MSG_ACHIEVEMENT
  378. CHAT_MSG_ADDON
  379. CHAT_MSG_BG_SYSTEM_ALLIANCE
  380. CHAT_MSG_BG_SYSTEM_HORDE
  381. CHAT_MSG_BG_SYSTEM_NEUTRAL
  382. CHAT_MSG_BN_INLINE_TOAST_ALERT
  383. CHAT_MSG_BN_INLINE_TOAST_BROADCAST
  384. CHAT_MSG_BN_INLINE_TOAST_BROADCAST_INFORM
  385. CHAT_MSG_BN_INLINE_TOAST_CONVERSATION
  386. CHAT_MSG_BN_WHISPER
  387. CHAT_MSG_BN_WHISPER_INFORM
  388. CHAT_MSG_BN_WHISPER_PLAYER_OFFLINE
  389. CHAT_MSG_COMBAT_FACTION_CHANGE
  390. CHAT_MSG_COMBAT_HONOR_GAIN
  391. CHAT_MSG_COMBAT_MISC_INFO
  392. CHAT_MSG_COMBAT_XP_GAIN
  393. CHAT_MSG_COMMUNITIES_CHANNEL
  394. CHAT_MSG_CURRENCY
  395. CHAT_MSG_EMOTE
  396. CHAT_MSG_FILTERED
  397. CHAT_MSG_GUILD
  398. CHAT_MSG_GUILD_ACHIEVEMENT
  399. CHAT_MSG_GUILD_ITEM_LOOTED
  400. CHAT_MSG_IGNORED
  401. CHAT_MSG_INSTANCE_CHAT
  402. CHAT_MSG_INSTANCE_CHAT_LEADER
  403. CHAT_MSG_LOOT
  404. CHAT_MSG_MONEY
  405. CHAT_MSG_MONSTER_EMOTE
  406. CHAT_MSG_MONSTER_PARTY
  407. CHAT_MSG_MONSTER_SAY
  408. CHAT_MSG_MONSTER_WHISPER
  409. CHAT_MSG_MONSTER_YELL
  410. CHAT_MSG_OFFICER
  411. CHAT_MSG_OPENING
  412. CHAT_MSG_PARTY
  413. CHAT_MSG_PARTY_LEADER
  414. CHAT_MSG_PET_BATTLE_COMBAT_LOG
  415. CHAT_MSG_PET_BATTLE_INFO
  416. CHAT_MSG_PET_INFO
  417. CHAT_MSG_RAID
  418. CHAT_MSG_RAID_BOSS_EMOTE
  419. CHAT_MSG_RAID_BOSS_WHISPER
  420. CHAT_MSG_RAID_LEADER
  421. CHAT_MSG_RAID_WARNING
  422. CHAT_MSG_RESTRICTED
  423. CHAT_MSG_SAY
  424. CHAT_MSG_SKILL
  425. CHAT_MSG_SYSTEM
  426. CHAT_MSG_TARGETICONS
  427. CHAT_MSG_TEXT_EMOTE
  428. CHAT_MSG_TRADESKILLS
  429. CHAT_MSG_WHISPER
  430. CHAT_MSG_WHISPER_INFORM
  431. CHAT_MSG_YELL
  432. CHAT_SERVER_DISCONNECTED
  433. CHAT_SERVER_RECONNECTED
  434. CLUB_MESSAGE_ADDED
  435. ]]--
  Reply With Quote
10-24-18, 01:37 PM   #10
MuffinManKen
A Flamescale Wyrmkin
AddOn Author - Click to view addons
Join Date: Dec 2009
Posts: 106
You might find some of the code in Blizzard's Util.lua handy. Notably there is a ColorMixin that converts to Hex, a Clamp function, and a WrapTextInColorCode function.

RAID_CLASS_COLORS also already has the color in hex format as the .colorStr so you can save some computation by using that rather than clamping multiple values and reformatting the result.
  Reply With Quote
11-06-18, 12:02 AM   #11
Terenna
A Flamescale Wyrmkin
AddOn Author - Click to view addons
Join Date: Jun 2016
Posts: 105
I'm nearing completion on this addon, but there's a function that I feel is just not very optimized.

Lua Code:
  1. local function hexColorNames(chatMessage, modChatColor) --this will churn through a message and find names that match players and color it by their class color
  2.     local words = {strsplit(' ', chatMessage)}
  3.     local name = UnitName('player')
  4.     local combinedName = (name..'-'..GetRealmName())
  5.     for i = 1, #words do
  6.         local w = words[i]
  7.         if (w and not (w == 'player' or w == 'target') and UnitName(w) and UnitIsPlayer(w)) then
  8.             local _, class = UnitClass(w)
  9.             local colors = RAID_CLASS_COLORS[class] --color the words that match player names with class colors, return back to the channel color for the next word(s)
  10.             if strfind(w, '-') then --this weird thing is because - is found in hyphenated names, - is also a magic character, if we simply use gsub, it never finds the full name because it gets stuck on '-'
  11.                 local hyphenatedName = w --hold the old hyphenated name so we can replace it
  12.                 local a, b = strsplit('-', w) --split the hyphenated name into two strings
  13.                 w = a..'%-'..b --rejoin the strings with a % before the - so our gsub can actually look for it
  14.                 chatMessage = chatMessage:gsub(w, '|c'..colors.colorStr..hyphenatedName..modChatColor)
  15.             else
  16.                 chatMessage = chatMessage:gsub(w, '|c'..colors.colorStr..w..modChatColor)
  17.             end
  18.         elseif combinedName:lower() == w:lower() then --if for whatever reason someone types your name and realm unitname(w) AND unitisplayer(w) would be false for the whole name and realm
  19.             local _, class = UnitClass('player')
  20.             local colors = RAID_CLASS_COLORS[class]
  21.             local hyphenatedName = w --hold the old hyphenated name so we can replace it
  22.             local a, b = strsplit('-', w) --split the hyphenated name into two strings
  23.             w = a..'%-'..b --rejoin the strings with a % before the - so our gsub can actually look for it
  24.             chatMessage = chatMessage:gsub(w, '|c'..colors.colorStr..hyphenatedName..modChatColor)
  25.         end
  26.     end
  27.     return chatMessage
  28. end

The goal of this function is to split any message you send to it by words. Since these chatMessage strings can have non-ascii characters via player names and the like, I just use the blizzard strsplit function to break the statement up into individual words. Each word is checked to see if it is a player's name, and if it is, it's colored by their class. I ran into particular issues with hyphenated names for off-realm players, since '-' is a magic character, typical gsub functions would get caught up with that. So I have to do a lot of string manipulation.

Furthering this issue, if you type in your full charactername-realmname, this returns false on your server, but true on a client off-server, so I had to write a second part to the if statement to get around this weird issue (if you don't understand what I'm saying, type /script print(UnitName('yourCharacterName-yourRealmName')) on your realm and on another realm. It will return false and true, respectively.

If you can see someway to better optimize this function, I'd greatly appreciate it. The function works through every scenario currently, I just don't know (but likely assume) if it can be written better. Thank you!

Last edited by Terenna : 11-06-18 at 12:25 AM.
  Reply With Quote
11-06-18, 06:11 PM   #12
Kanegasi
A Molten Giant
 
Kanegasi's Avatar
AddOn Author - Click to view addons
Join Date: Apr 2007
Posts: 666
Lua Code:
  1. local function hexColorNames(chatMessage, modChatColor)
  2.     local words,coloredMessage,s,p,class = {strsplit(' ', chatMessage)},''
  3.     local combinedName = UnitName('player')..'-'..GetRealmName()
  4.     local _, playerClass = UnitClass('player')
  5.     for k, w in ipairs(words) do
  6.         s,p = false,false
  7.         if w ~= 'player' and w ~= 'target' and UnitIsPlayer(w) then
  8.             _, class = UnitClass(w)
  9.             s = true
  10.         elseif combinedName:lower() == w:lower() then
  11.             p = true
  12.         end
  13.         w = (s or p) and '|c'..RAID_CLASS_COLORS[p and playerClass or class].colorStr..w..modChatColor or w
  14.         coloredMessage = coloredMessage == '' and w or coloredMessage..' '..w
  15.     end
  16.     return coloredMessage
  17. end


Since you're iterating through the message word-for-word, just rebuild it word-for-word instead of using gsub. I also went with boolean switches and dropped some other stuff like UnitName being redundant since UnitIsPlayer is fine by itself.

I did not test this.
  Reply With Quote
11-06-18, 07:38 PM   #13
Terenna
A Flamescale Wyrmkin
AddOn Author - Click to view addons
Join Date: Jun 2016
Posts: 105
Damn dude, I suck at coding compared to that work of art
  Reply With Quote
01-06-19, 09:33 PM   #14
Terenna
A Flamescale Wyrmkin
AddOn Author - Click to view addons
Join Date: Jun 2016
Posts: 105
Back again with further optimization on the function previously discussed.

The old version, shown here:
Lua Code:
  1. local function modHexColorNames(chatMessage, modChatColor)
  2.     local coloredMessage = ''
  3.     local _, class
  4.     local words = {strsplit(' ', chatMessage)}
  5.     for i = 1, #words do
  6.         local w = words[i]
  7.         local s, p = false, false
  8.         local lowW, modW = w:lower(), w:gsub('%.', '')
  9.         if (lowW ~= 'player' and lowW ~= 'target' and lowW ~= 'focus' and UnitIsPlayer(modW)) then
  10.             _, class = UnitClass(modW)
  11.             s = true
  12.             if strfind(w, '-') then
  13.                 w = strsub(w, 1, strfind(w, '-') - 1)
  14.             end
  15.         elseif (w == 'You' or w == 'you') then
  16.             p = true
  17.         end
  18.         w = ((s or p) and '|c'..RAID_CLASS_COLORS[(p and playerClass) or class].colorStr..w..modChatColor) or w --(w = w, unless s or p is true, then color w by RAID_CLASS_COLORS
  19.         coloredMessage = coloredMessage == '' and w or coloredMessage..' '..w
  20.     end
  21.     return coloredMessage
  22. end

would not properly color instances where people typed "," "." "!" attached to a player's name. I rewrote it (probably poorly) to account for possessive form of names (Terenna's), and also instances where there was punctuation in the word (Terenna!).

The current version works flawlessly, under any weird scenario I can throw at it; I'm once again looking for optimization as I feel it has a lot of if then elseif pipeline stalls. The code is heavily commented so you can see (hopefully) my logic throughout writing it.

Lua Code:
  1. local function modHexColorNames(chatMessage, modChatColor) --differs from hexColorNames because it class colors 'You' and 'you' and 'Your' and 'your' and removes realm names
  2.     local words, coloredMessage = {strsplit(' ', chatMessage)}, '' --we use the blizzard strsplit function to split our string into words (we separate the words by spaces) and put them in a table called 'words'
  3.     for i = 1, #words do --we parse through the table of words one at a time
  4.         local w, s, p, q, modW, lowModW, _, class = words[i], false, false, false --we set w to our 'words' table value we are parsing through, s, p, and q will be used to determine our conditionals, modW is our word with "'s" for possessives or other punctuation removed, lowModW, is just modW:lower()
  5.         local lowW = w:lower()
  6.         if strfind(w, '%p') then --determine if our word even has punctuation
  7.             modW = w:gsub("%'s", '') --remove "'s" from potential player names in possessive form
  8.             modW = modW:gsub('%p', '') --remove any other punctuation from our modW
  9.             lowModW = modW:lower() --lower our modW to make it easier to see if it's one of the 'unit' names below
  10.             q = true
  11.         end
  12.         if (q and (lowModW ~= 'player' and lowModW ~= 'target' and lowModW ~= 'focus' and UnitIsPlayer(modW))) or (lowW ~= 'player' and lowW ~= 'target' and lowW ~= 'focus' and UnitIsPlayer(lowW)) then --our word had a punctuation in it, see if the word without punctuation is a player
  13.             if q then
  14.                 _, class = UnitClass(modW) --use our punctuation-less word to determine class
  15.             else
  16.                 _, class = UnitClass(lowW) --just use our lowered word to determine class
  17.             end
  18.             s = true --let's us know we're dealing with a word that should be colored
  19.             if strfind(w, '-') then --removes the hyphen and realmname from words
  20.                 w = strsub(w, 1, strfind(w, '-') - 1)
  21.             end
  22.         elseif (q and (lowModW == 'you' or lowModW == 'your')) or lowW == 'you' or lowW == 'your' then --our lowered word may be you or your, but also may contain a punctuation, see if it does
  23.             p = true --let's us know we're dealing with you or your that should be player class colored
  24.         end
  25.         if (q and s) then --we have a word with punctuation that should be class colored
  26.             w = w:gsub(modW, '|c'..RAID_CLASS_COLORS[class].colorStr..modW..modChatColor)
  27.         elseif s then --we have a word without punctuation that should be class colored
  28.             w = '|c'..RAID_CLASS_COLORS[class].colorStr..w..modChatColor
  29.         elseif (q and p) then --we have you or your with punctuation that should be player class colored
  30.             w = w:gsub(modW, '|c'..playerClassColorTable.colorStr..modW..modChatColor)
  31.         elseif p then --we have you or your without punctuation that should be player class colored
  32.             w = '|c'..playerClassColorTable.colorStr..w..modChatColor
  33.         end
  34.         coloredMessage = coloredMessage == '' and w or coloredMessage..' '..w
  35.     end
  36.     return coloredMessage
  37. end

Any insight, as always, is greatly appreciated.
  Reply With Quote
01-07-19, 11:50 AM   #15
jlam
A Fallenroot Satyr
AddOn Author - Click to view addons
Join Date: Oct 2010
Posts: 29
Your function does this when it is invoked:
Code:
local words, coloredMessage = {strsplit(' ', chatMessage)}, ''
for i = 1, #words do
    ...
end
You are not using words in any way further on in the code other than to iterate through that array, so you can instead do this:
Code:
for word in gmatch(chatMessage, '%S') do
    ...
end
This more concisely iterates through the chatMessage string and selecting "words" composed of non-whitespace characters.

You are also building up a string coloredMessage by concatenating word by word, which is considered very inefficient in Lua. The usual practice is to place all of the words into an array and then call table.concat to form the complete string. You can do this as:
Code:
local modHexColorNames
do
    local coloredMessage = {} -- static local table
    modHexColorNames = function(chatMessage, modChatColor)
        wipe(coloredMessage)  -- empty the table before re-using.
        for word in gmatch(chatMessage, '%S') do
            local w
            -- do stuff to set w to the word for the colored message
            ...
            table.insert(coloredMessage, w)
        end
        return table.concat(coloredMessage, ' ')
    end
end
table.concat returns an empty string if the table is empty, so you will always get at least an empty string as the return value.
  Reply With Quote
01-08-19, 02:59 PM   #16
Terenna
A Flamescale Wyrmkin
AddOn Author - Click to view addons
Join Date: Jun 2016
Posts: 105
hey jlam, thanks for the advice

I was able to rewrite the code to look like
Lua Code:
  1. local letterTable, coloredMessage, holder, placeHolderWord = {}, {} --letterTable will be used to hold our individual words free from any punctuation or numbers, coloredMessage is ultimately what we will return, holder is used to find strings of letters and insert them into our letterTable, placeHolderWord is so we can perform a final gsub at the end
  2. local function hexColorNames(chatMessage, modChatColor)
  3.     wipe(coloredMessage) --blizzard function that will clear our table
  4.     for word in gmatch(chatMessage, '%S+') do --look at each word individually, separating them by spaces (we look for non-whitespace)
  5.         placeHolderWord = word
  6.         local _
  7.         local class, name, coloredName = nil, nil, nil
  8.         wipe(letterTable) --wipe our letterTable from any previous entries
  9.         while strfind(placeHolderWord, '%a') do --we're going to be removing all blocks of letters from our word
  10.             holder = placeHolderWord:match('[^%p%s%d]+') --extract our blocks of letters, we don't want numbers, spaces, or punctuation, (we use this instead of [%a]+ because it allows for non-ascii characters
  11.             if holder then --safety so we don't throw an error inserting nil into a table
  12.                 table.insert(letterTable, holder) --put our letter block into our letterTable holder
  13.                 placeHolderWord = placeHolderWord:gsub(holder, '') --names can ultimately be a simple name from your realm, a name with
  14.             end
  15.         end
  16.        
  17.         if (#letterTable == 1 and letterTable[1]:lower() ~= 'player' and letterTable[1]:lower() ~= 'target' and letterTable[1]:lower() ~= 'focus') then --our table is only 1 entry long, and therefore it can't be someone on another realm
  18.             name = letterTable[1]
  19.             _, class = UnitClass(letterTable[1])
  20.         elseif #letterTable == 2 then
  21.             if strfind(word, 'Area52') then
  22.                 name = letterTable[1]..'%-'..letterTable[2]..'52' --we use the % so we can gsub; the '52' won't be transferred to letterTable as it specifically excludes numbers, this is a shitty hack for people on Area 52
  23.                 _, class = UnitClass(letterTable[1]..'-'..letterTable[2]..'52')
  24.             else
  25.                 name = letterTable[1]..'%-'..letterTable[2] --we use the % so we can gsub
  26.                 _, class = UnitClass(letterTable[1]..'-'..letterTable[2])
  27.             end
  28.         elseif #letterTable >= 3 then --we use >= rather than == incase someone adds letters afterwards to try and catch more names this way
  29.             name = letterTable[1]..'%-'..letterTable[2].."%'"..letterTable[3] --we use the % in front of the magic characters so we can gsub
  30.             _, class = UnitClass(letterTable[1]..'-'..letterTable[2].."'"..letterTable[3])
  31.         end
  32.        
  33.         if (class and class ~= '') then --if we actually got a class and it's not '' then we can modify the text to have class color and return back to the text color before it
  34.             coloredName = '|c'..RAID_CLASS_COLORS[class or class:gsub(' ', '')].colorStr..name..modChatColor
  35.             word = word:gsub(name, coloredName)
  36.         end
  37.        
  38.         table.insert(coloredMessage, word)
  39.     end
  40.    
  41.     return(table.concat(coloredMessage, ' '))
  42. end

and
Lua Code:
  1. local function modHexColorNames(chatMessage, modChatColor) --differs from hexColorNames because it class colors 'You' and 'you' and 'Your' and 'your' and removes realmNames from players
  2.     wipe(coloredMessage) --blizzard function that will clear our table
  3.     for word in gmatch(chatMessage, '%S+') do --look at each word individually, separating them by spaces (we look for non-whitespace)
  4.         placeHolderWord = word
  5.         local _
  6.         local class, name, coloredName = nil, nil, nil
  7.         wipe(letterTable) --wipe our letterTable from any previous entries
  8.         while strfind(placeHolderWord, '%a') do --we're going to be removing all blocks of letters from our word
  9.             holder = placeHolderWord:match('[^%p%s%d]+') --extract our blocks of letters, we don't want numbers, spaces, or punctuation, (we use this instead of [%a]+ because it allows for non-ascii characters
  10.             if holder then --safety so we don't throw an error inserting nil into a table
  11.                 table.insert(letterTable, holder) --put our letter block into our letterTable holder
  12.                 placeHolderWord = placeHolderWord:gsub(holder, '') --names can ultimately be a simple name from your realm, a name with
  13.             end
  14.         end
  15.        
  16.         for i = 1, 3 do
  17.             if (i == 1 and letterTable[i]) then --the potential name doesn't have a realm
  18.                 _, class = UnitClass(letterTable[1])
  19.                 if class and class ~= '' then break end
  20.             elseif (i == 2 and letterTable[i]) then --the potential name has at least a realm name
  21.                 if strfind(word, 'Area52') then --hack because our match specifically avoids numbers, Area 52 is the only realm with numbers
  22.                     _, class = UnitClass(letterTable[1]..'-'..letterTable[2]..'52')
  23.                     if class and class ~= '' then break end
  24.                 else
  25.                     _, class = UnitClass(letterTable[1]..'-'..letterTable[2])
  26.                     if class and class ~= '' then break end
  27.                 end
  28.             elseif (i == 3 and letterTable[i]) then --the potential name might have a realm and an ' in the realmName
  29.                 _, class = UnitClass(letterTable[1]..'-'..letterTable[2].."'"..letterTable[3])
  30.             end
  31.         end
  32.                
  33.         if (class and class ~= '') then --if we actually got a class and it's not '' then we can modify the text to have class color and return back to the text color before it
  34.             if #letterTable == 2 then
  35.                 word = word:gsub('%-%P+', '') --remove the realm name but preserve any punctuation attached at the end
  36.             elseif #letterTable == 3 then
  37.                 word = word:gsub('%-%P+', '') --remove the realm name, but we're going to get stuck on the '
  38.                 word = word:gsub("%'%P+", '') --remove the rest of the realm name
  39.             end
  40.             coloredName = '|c'..RAID_CLASS_COLORS[class or class:gsub(' ', '')].colorStr..letterTable[1]..modChatColor --color the name
  41.             word = word:gsub(letterTable[1], coloredName) --replace the name with the colored name
  42.         end
  43.        
  44.         if (letterTable[1] and (letterTable[1]:lower() == 'you' or letterTable[1]:lower() == 'your')) then --color this
  45.             coloredName = '|c'..playerClassColorTable.colorStr..letterTable[1]..modChatColor --this is an addon-wide static formed at the beginning of the addon as it's frequently used
  46.             word = word:gsub(letterTable[1], coloredName)
  47.         end
  48.        
  49.         table.insert(coloredMessage, word)
  50.     end
  51.    
  52.     return(table.concat(coloredMessage, ' '))
  53. end

It works with non-ascii characters, works with off-realm players, works with people on realms with apostrophes, works under every scenario I can throw at it. It is fairly "hard-coded." I say that because player names can only be a name, a name-realm, or a name-re'alm with no numbers or punctuations in the name. This allowed me to perform the #letterTable == integer test.

Let me know what you think, or if you see a better way of doing this.

Last edited by Terenna : 01-08-19 at 06:57 PM. Reason: added both functions and commented the code more
  Reply With Quote
01-09-19, 09:09 PM   #17
jlam
A Fallenroot Satyr
AddOn Author - Click to view addons
Join Date: Oct 2010
Posts: 29
I dry-coded this (didn't run, didn't check for syntax errors), but hopefully it gets the idea across. It looks like you're mainly concerned with replacing names with class-colored versions of those same names, and you have two functions for doing the name-replacement in slightly different ways. Since the two functions are so similar, I made them into a single function implementation.

Now that I see what you're trying to accomplish, I think there's something smarter that can be done to iterate through chatMessage and alternate grabbing whitespace and non-whitespace. I'll think about how to do that tomorrow when I have time.

Lua Code:
  1. -- Gets a name from the given string after stripping out punctuation.
  2. local function getName(s)
  3.     local stripped = s:gsub("'s$", ''):gsub('%p', '') -- strip possessive then punctuation
  4.     local name, realm = UnitName(stripped)
  5.     local fullName = (realm and realm ~= "") and name .. '-' .. realm or name
  6.     if fullName == stripped then
  7.         -- "stripped" isn't a unit ID or GUID.
  8.         return fullName, name, realm
  9.     end
  10.     return nil
  11. end
  12.  
  13. --[[
  14.     Extracts a full name (relative to the player's realm) from the given string
  15.     Returns:
  16.         unit ID: a unit ID that can be passed to unit functions, or nil
  17.         fullName: "name-realm", or "name" if the realm is the same as the player's realm, or nil.
  18. --]]
  19. local function extractFullName(s)
  20.     local fullName = getName(s)
  21.     return fullName, fullname
  22. end
  23.  
  24. --[[
  25.     Extracts a simple name (no realm), or "you" or "your", from the given string.
  26.     Returns:
  27.         unit ID: a unit ID that can be passed to unit functions, or nil
  28.         name: "name" with no realm information, or nil.
  29. --]]
  30. local playerName = UnitName("player")
  31.  
  32. local function extractShortName(s)
  33.     local fullName, name = getName(s)
  34.     if fullName then
  35.         return fullName, name
  36.     end
  37.     local stripped = s:gsub("'s$", ''):gsub('%p', '') -- strip possessive then punctuation
  38.     local lowered = stripped:lower()
  39.     if lowered == "you" or lowered == "your" then
  40.         return playerName, stripped
  41.     end
  42.     return nil
  43. end
  44.  
  45. -- Replaces name with class-colored names in chatMessage.
  46. local hexColorNames
  47. do
  48.     local coloredMessage = {}
  49.  
  50.     function hexColorNames(chatMessage, modChatColor)
  51.         extractNameFunc = extractNameFunc or extractFullName
  52.         wipe(coloredMessage)
  53.         for word in gmatch(chatMessage, "%S+") do
  54.             local unit, name = extractNameFunc(word)
  55.             if unit and name then
  56.                 local _, class = UnitClass(unit)
  57.                 if class then
  58.                     local coloredName = "|c" .. RAID_CLASS_COLORS[class].colorStr .. name .. modChatColor
  59.                     word = word:gsub(name, coloredName)
  60.                 end
  61.             end
  62.             table.insert(coloredMessage, word)
  63.         end
  64.         return table.concat(coloredMessage, ' ')
  65.     end
  66. end
  67.  
  68. local function modHexColorNames(chatMessage, modChatColor)
  69.     return hexColorNames(chatMessage, modChatColor, extractShortName)
  70. end
  Reply With Quote
01-10-19, 10:33 PM   #18
Terenna
A Flamescale Wyrmkin
AddOn Author - Click to view addons
Join Date: Jun 2016
Posts: 105
The code currently does not work with off-realm people due to the nature of getName() stripping punctuation (- and ') from realm names, thereby leaving stripped with strings like nameKelthuzad which is then needed to be compared and obviously isn't a name. I'm working on fixing it now.

The code also needed to be edited slightly to function (not properly passing and handing off key arguments mainly). I edited the code as found here:
Lua Code:
  1. local function getName(s) --Gets a name from the given string after stripping out possessives and punctuation.
  2.     local stripped = s:gsub("%'s$", ''):gsub('%b{}', ''):gsub('%p', '') --strip possessive, then phrases inside of {} that we commonly see with addons putting target markers in chat then punctuation
  3.     local name, realmName = UnitName(stripped) --get the name's name and realm if applicable
  4.     local fullName = (realmName and realmName ~= '') and name..'-'..realmName or name --if the player has a realmName, their full name is name..'-'..realmName, if not, it's just the name
  5.     if fullName == stripped then --check to see if their full name is the same as the stripped word (sans possessive and punctuation), this also removes unitNames like 'player' or 'focus' etc
  6.         return fullName, name, realmName
  7.     end
  8.  
  9.     return nil --the word passed to the function was not a player's name
  10. end
  11.  
  12. local function extractFullName(s) --will either return a name-realm or just the name, depending on if the player is on the same server
  13.     local fullName, name = getName(s)
  14.     return fullName, name
  15. end
  16.  
  17. local function extractShortName(s) --will only return a name, removes -realmName; in addition, it colors the words you and your by the player's class color
  18.     local fullName, name = getName(s)
  19.     if fullName then --this is only true if the word is a name, this might be a name-realm, however, we use the name that is passed, not the fullName later
  20.         return fullName, name
  21.     end
  22.  
  23.     local stripped = s:gsub("%'s$", ''):gsub('%b{}', ''):gsub('%p', '') --strip possessive, then phrases inside of {} that we commonly see with addons putting target markers in chat then punctuation
  24.     local lowered = stripped:lower()
  25.     if lowered == 'you' or lowered == 'your' then
  26.         return playersName, stripped --we use playersName just so when we check for (unit and name) below we pass the logic test, we don't actually substitute the word you or your with the player's name
  27.     end
  28.  
  29.     return nil --the word passed to the function was neither a player's name or you or your
  30. end
  31.  
  32. local coloredMessage = {} --table to hold our words for concatenation
  33. local function hexColorNames(chatMessage, modChatColor, extractShortName)
  34.     wipe(coloredMessage) --clear our table to make sure we're not adding to a previous entry
  35.     local extractNameFunc = extractShortName or extractFullName --decide if we want do a short or a long based on whether we run modHexColorNames which passes the extractShortName argument
  36.     for word in gmatch(chatMessage, '%S+') do --splits up a string into words by splitting spaces
  37.         local unit, name = extractNameFunc(word) --will either pass it to extractShortName if we're calling this function from modHexColorNames, or extractFullName if we're calling this function from hexColorNames
  38.         if (unit and name) then
  39.             local _, class = UnitClass(unit)
  40.             if class then
  41.                 local coloredName = '|c'..RAID_CLASS_COLORS[class].colorStr..name..modChatColor
  42.                 word = word:gsub(name, coloredName)
  43.             end
  44.         end
  45.         table.insert(coloredMessage, word) --insert the word into our message table irrespective of whether it was a "special" word that requires coloring
  46.     end
  47.     return table.concat(coloredMessage, ' ') --return the phrase re-separated by spaces
  48. end
  49.  
  50. local function modHexColorNames(chatMessage, modChatColor)
  51.     return hexColorNames(chatMessage, modChatColor, extractShortName) --passes the 3rd argument to hexColorNames so it knows to extractShortName
  52. end

Last edited by Terenna : 01-10-19 at 11:10 PM.
  Reply With Quote

WoWInterface » Developer Discussions » Lua/XML Help » Code optimization and issue with hyperlinks in chat

Thread Tools
Display Modes

Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

vB code is On
Smilies are On
[IMG] code is On
HTML code is Off