Overwrite default modules in Lua Chat System?

For the record, I know this is the way most prescribe overwriting the default modules (and adding new ones) to the chat. By the end of this post you’ll understand why this doesn’t cut it.

I’ll start by just saying why I need to do this. Here’s the problem I’m solving. If your TeamColor is white, then another player can fool everyone into thinking you chatted by spamming the first line of their chat message with filler characters and then beginning the second line with your name as it would appear had you chatted. However, I have other reasons for wanting to overwrite the default chat modules too, so this isn’t the only thing I want to do with this knowledge.

The code in the Lua Chat System that decides name color is in ExtraDataInitializer. All I do is make it so if your chat name color is gonna be white, it just defaults to the regular name color. Here’s the fix.
Yes I know the W in Institutional white isn’t supposed to be capitalized, I just didn’t feel like re-recording the gif.

While using the method at the beginning of this post does work, it isn’t sufficient. You see, my group Temple of Brickbattle and by extension the group Brickbattle Maps Archive both use a “master script” that looks like this in the ServerScriptService of every group game. This imports all of the assets each of these games rely on: the tool set, the tool order editor GUI, the spectate GUI, the toggle-able classic animations, the leaderboard, the admin commands, and other various gadgets.

You can understand why this is extremely useful: it allows me to create and update possibly 30 games at once without having to open any of them, just by inserting or editing code in the module that’s acquired by the MasterScript. While I’m not sure this was the intended use of importable ModuleScripts, it does work very well. Think of it almost like InsertService.

My goal here is to incorporate this replacement ExtraDataInitializer and its installer into the master script alongside all of the other things it uses.

Now for the issue with overwriting default chat modules. Requiring an onsite module takes a little bit of time. Enough time, in fact, that it’s not guaranteed to paste the ChatModules folder in its proper location (see the first link in this thread) before the chat service has already required all of the stock default modules. Obviously if I just parented the replacement module to game:GetService("Chat"):WaitForChild("ChatModules"), that wouldn’t work either, because then it would just be an unused duplicate of the default thing. Deleting the original wouldn’t work for even more obvious reasons.

How can I replace default chat modules with my own modified versions late, while guaranteeing they work? Pinging chat expert @colbert2677.

1 Like

New problem to tackle! I might not have the best working answer for you today because your case seems rather unique mostly because of the way you’ve chosen to import pieces into your game.

This is why I like the Lua Chat so much though, because it really does have a wide range of extensibility. You can write your own widgets that are ran with the chat system, or you can make your widgets run independently and use the same API that a native chat module would and there’d be no noticeable difference.

At the heart of ExtraDataInitializer, it is just a regular chat module that makes use of the API to set up and determine some data for speakers associated with players. Specifically: Roblox staff chat colours and name colours either because the user has a team or not. In your case though, you want to overwrite name colours without forking, so we have to take a different approach - that’s to, after the fact, apply a different name colour.

My recommendation in these cases is really to try and make a compromise such that you can fork if you need to edit default behaviours, since writing over them is somewhat messy in concept. That being said, we’ll just be painting over the message, so we should be okay here.

What we’re going to aim to do is copy most of the name colour handling pieces of ExtraDataInitializer and then run them over when ExtraDataUpdated fires.

Here’s the code, with some comment steps along the way to help guide you around.

--- First, we need to get the ChatService so we can use the API.
-- ChatModules get it passed as an argument, we need to get it a different way.
local ServerScriptService = game:GetService("ServerScriptService")
local ChatService = require(ServerScriptService:WaitForChild("ChatServiceRunner").ChatService)

--- We need everything related to computing name colours.
-- This is all so we can set the name colour back later.
local NAME_COLORS =
{
	Color3.new(253/255, 41/255, 67/255), -- BrickColor.new("Bright red").Color,
	Color3.new(1/255, 162/255, 255/255), -- BrickColor.new("Bright blue").Color,
	Color3.new(2/255, 184/255, 87/255), -- BrickColor.new("Earth green").Color,
	BrickColor.new("Bright violet").Color,
	BrickColor.new("Bright orange").Color,
	BrickColor.new("Bright yellow").Color,
	BrickColor.new("Light reddish violet").Color,
	BrickColor.new("Brick yellow").Color,
}

local function GetNameValue(pName)
	local value = 0
	for index = 1, #pName do
		local cValue = string.byte(string.sub(pName, index, index))
		local reverseIndex = #pName - index + 1
		if #pName%2 == 1 then
			reverseIndex = reverseIndex - 1
		end
		if reverseIndex%4 >= 2 then
			cValue = -cValue
		end
		value = value + cValue
	end
	return value
end

local color_offset = 0
local function ComputeNameColor(pName)
	return NAME_COLORS[((GetNameValue(pName) + color_offset) % #NAME_COLORS) + 1]
end

--- This is the main function we wanted access to, which determines name colour.
local function GetNameColor(speaker)
	local player = speaker:GetPlayer()
	if player then
		-- We make our modification here to omit Institutional white teams from using the TeamColor for name.
		-- To avoid creating a new BrickColor object just to compare, we compare using strings (BrickColor.Name).
		if player.Team ~= nil and player.TeamColor.Name ~= "Institutional white" then
			return player.TeamColor.Color
		end
	end
	return ComputeNameColor(speaker.Name)
end

--- As our widget is external, we want to make sure existing speakers are caught.
-- You will see why we need to make the function this way later below.
local function speakerAdded(speakerName)
	-- SpeakerAdded fire with speaker names, so we need to get the speaker object first. Existence is guaranteed.
	local speaker = ChatService:GetSpeaker(speakerName)
	-- We also need a player object to work with. Existence is not guaranteed.
	local player = speaker:GetPlayer()
	-- If GetPlayer returns nil, it's a non-player speaker. We don't care about non-player speakers now.
	if player then
		-- We will use ExtraDataUpdated to check when ExtraDataInitializer changes the name colour
		-- either during initial setup or when the player's team changes.
		speaker.ExtraDataUpdated:Connect(function (key, data)
			if key == "NameColor" then
				-- NameColor was the key changed. Run our updated colour getter.
				local nameColor = GetNameColor(speaker)
				-- We do not use SetExtraData because it fires ExtraDataUpdated. Updating a
				-- value within a changed event listening on it would result in re-entrancy errors.
				speaker.ExtraData.NameColor = nameColor
			end
		end)
	end
end

--- Connect our function and run them in case any speakers exist already.
ChatService.SpeakerAdded:Connect(speakerAdded)
for _, speaker in ipairs(ChatService:GetSpeakerList()) do
	speakerAdded(speaker)
end

This is a fairly new problem for me to have solved. It would probably be significantly easier to go by with a fork as opposed to overwriting but because of the way you handle your game’s code, anything to do with working with the system directly falls through. I think what I’ve supplied should work. I’m not quite the expert when it comes to unconventional methods. :sweat_smile:

I encountered a weird problem myself while testing without forking or having this code enabled though and it’s that once I got onto the Institutional white team, the chat colour just never changed - it would if I started out on a different team though.

4 Likes

As the guy that originally moved the Lua Chat System documentation to the Developer Hub… this is a solid answer. I’m just here to spit out a link to it for anyone who happens to be reading this thread, since devhub search isn’t showing it for some reason.

1 Like

You can use the Messaging service. it can send messages across service to scripts that are subscribed to that channel.

You can use PublishAsync to publish messages and SubscribeAsync to wait for msgs.

From there you can delete the old chat and create a gui thats based in 1 server and uses PublishAsync and SubscribeAsync to allow players to send and recieve messages in your very own chat gui.

This means you can even broadcast messages like server shutdown warnings, or who shot who. its got a lot of uses.

Another good read from a chat system pro (among other things I’m sure). Once again I appreciate the in-depth explanation. In short, it seems you can’t replace the default chat modules after they’ve already loaded in. Appears you gotta take the ol’ modders’ path and listen for things to change right before immediately changing them yourself (but not using SetExtraData so as not to start an infinite update loop). Thanks for putting as many comments in there as you could fit, definitely helped hasten my understanding of what’s going on in each line.

Similarly, this is how I made a certain black-colored system message have a white TextStrokeColor3 to make it more legible against a dark background, as there were no parameters related to modifying TextStrokeColor3 with the function I used (ChatMakeSystemMessage) nor even as deep as the Util client chat module. For similar reasons outlined in the OP, I couldn’t overwrite Util either. Ended up just listening for the raw change from inside the chat under PlayerGui which I know is more of an F-U solution but it works.

I had this issue in testing earlier today too, even without any modifications to the chat. Seemed like no matter what team I joined the game on, changing the team did nothing to change my name color when I chatted again. Could be an issue isolated to Studio though.

1 Like