String.match incompatible with RichText?

In my game, NPCs are able to use the chat in different scenarios. Since this system was made in LegacyChatService, the text that would appear in the chat is [NPCName]: Message. When trying to convert this to TextChatService, im trying to check for the NPC’s name in between the brackets and then set it as the TextChatProperties.PrefixText. Then I remove the NPC’s name from the Text property using string.match.

Heres the result
image

And here is the code thats in the OnIncomingMessage callback

local NPCName = string.match(message.Metadata, "%b[]") -- its probably from an NPC
local Pos = NPCName and string.find(message.Metadata, NPCName)
if NPCName and Pos == 2 then -- check if NPC name is at the start
	local Properties = Instance.new("TextChatMessageProperties")
	Properties.PrefixText = string.sub(NPCName, 2, string.len(NPCName) - 1)..":"
	print(NPCName, message.Text, string.match(message.Text, "["..NPCName.."]:")) -- prints: [Soldier], <font color='#ff0000'>[Soldier]: teamassists is a threat, take them out!</font>, r]:
	Properties.Text = string.gsub(message.Text, "["..NPCName.."]:", "")

	return Properties
end

I have no idea why its not working

I have tried removing the extra brackets in the string.gsub but that results in none of it matching at all for some reason

what am i doing wrong?

2 Likes

Cause it’ll always return the raw text, now since no one has offered a solution I’m gonna give a janky ah solution BUT if anyone has any better it’d be appreciated.

Now, TextLabels have this property called “ContentText” basically what that property does is uses what the text would be in a rendered format.

I’d say one of the perks of using this over attempting to create your own ContentText functionality to simulate it is pretty much being future proof. If roblox decides to add new functionality to it then it’ll update plus you know for sure what is shown in the message is what will always be given back.

function getFormattedText(text: string): string
	local TextLabel = Instance.new("TextLabel")
	TextLabel.Text = text
	TextLabel.RichText = true
	
	local Formatted = TextLabel.ContentText
	TextLabel:Destroy()
	TextLabel = nil
	return Formatted
end

print(getFormattedText("<b>Hello World...</b>")) -- Hello World...
3 Likes

Ideally you should be modifying the message as it is being generated, not doing string manipulation after the fact. What script is controlling the name being placed in the brackets in the first place?

1 Like

its a server sided script that uses string.format to make the text and then sends it and the color for it to be through a remote event and the color gets applied to the text with this pretty useful function i made

local function ConvertToRichText(Text, Color) -- for text chat service
	Color = Color or Color3.new(1,1,1)
	local ColorHex = Color:ToHex()
	
	local Metadata = "<font color='%s'>%s</font>"
	local Message = string.format(Metadata, ("#"..ColorHex), Text)
	
	return Message
end

wait, what do you mean by raw text

is it the string with all of the richtext stuff in?

The “raw” text is the string that contains all the formatting/control character that get interpreted/formatted by the Rich Text renderer to do whatever its set to do with those tags.

The ContentText, then, is the string of character that actually get displayed, with all the formatting stuff removed.


Unfortunately, that information is irrelevant to your problem, which is actually more basic than that: NPCName already contains the ‘[’ and ‘]’ characters you’re trying to add back in to it!

Lua patterns treat ‘[’ and ‘]’ as special “character classes”, too, but they don’t nest, which is why you’re getting that weird cut-off behavior with "r]:".

print(string.match("[test]: hoorah", "test"))
--> test
print(string.match("[test]: hoorah", "[test]"))
--> t
print(string.match("[test]: hoorah", "[test]:"))
--> nil
print(string.match("[test]: hoorah", "[[test]]:"))
--> t]:

And, the correct version:

print(string.match("[test]: hoorah", "%[test%]:"))
--> [test]:

Doing this “correctly” requires you changing the order of operations a tad. Ideally, your Server script generating the NPC messages should not be doing Rich Formatting on them at all, leaving it for the Clients to handle. The issue here is fundamentally a mildly bad API (why do TextChatMessage TextSources need to be Players? Letting the Server register various custom agents could solve this readily.) that you’re circumventing in an awkward way.

Strip your ConvertToRichText function from the message being sent by the Server and put that code on the Client: the OnIncomingMessage callback should receive unformatted text that is just the message itself, e.g. the string "[Soldier]: hoorah blah blah etc etc!!!". I don’t know what you’re passing to the Metadata field, but assuming the message.Text is the above string without any Rich Text decorators, something like this should work as a drop-in replacement for the code in your first post:

local NPCName, NPCMessage = string.match(message.Text, "^(%b[]): (.+)") -- capture NPC name and message separately
if NPCName then
    local Properties = Instance.new("TextChatMessageProperties")

    Properties.PrefixText = string.sub(NPCName, 2, -2)..": " -- neat little feature of string.sub slicing!
                                                             -- Also, puts the space between the : and message here

    Properties.Text = getFormattedText( NPCMessage, Color3.new(1,0,0) ) -- do the Rich Text decorating *here*

    return Properties
end

Now, obviously, this only does a single fixed color (red) for all NPCs, and ignores all other Rich Text decorators you may or may not have been using. The solution to this is to use the message.Metadata field! That’s what it’s for, even!

I’m not sure what the Metadata field gets populated with for player messages, but for your NPC or other System messages you can create a custom format with whatever specific details you need to decorate the messages appropriately.

For example, assume that my Server is sending this message:

message.Metadata = "{NPCMessage}<sc>Soldier 76</sc>"
message.Text = "<font color='#FF0000'>hoorah, <i>who wants to live forever!?</i></font>"

My OnIncomingMessage callback can then (at least partially) be this:

local SystemMessageType, Params = string.match(message.Metadata, "^(%b{})(.+)")
if SystemMessageType then
    if SystemMessageType == "{NPCMessage}" then
        local Properties = Instance.new("TextChatMessageProperties")
    
        Properties.PrefixText = Params
        Properties.Text = message.Text
    
        return Properties
    elseif SystemMessageType == ...
...
else -- Not a System Message
...
end

Since the only parameter for the NPC message is their name, I can avoid doing any string manipulation apart from testing if and what kind of System message was received. This is what I meant by my original post – you should control the message as it is being generated, modifying that generation itself, rather than trying to take it apart and re-format it somewhere else.

This still isn’t ideal; it wastes bytes sending the Rich Text decoration characters along with the message itself, but it’s simple and effective and probably not even a problem anyway.

If you need something particularly complicated for the Metadata, you might bother using JSON as the format, as there are loads of libraries out there to convert to and from JSON strings and Lua Tables. The solution I came up with here only parameterized the name, but you could also use it to, for example, force the NPC to play a particular animation for that message or choose whether he sends it like a whisper/DM to the current player or anything else you can think of.

1 Like

your updated code worked, thanks bro
i remember the first time i wrote a string that was just “[” it gave me an error but i didnt think much of it lol

yeah the ConvertToRichText function was in the client and the server would just send the client the npc’s name and the message to display in the chat

yeah it was