you should use source sans, the font looks different on this
You can easily change the font in the ChatSettings module. You can use a Font object, or a Font enum, both are supported. The ChatSettings lua file provided to get the Classic look of the chat uses SourceSansBold
I chose Arimo
because I used to have GothamSemibold
as my font for the custom chat, and Arimo
is a font I quite like, as a replacement for Gotham
, now that it was removed
@LeoBeomkl, Interesting, it seems like stopping the resizing action and starting it again might be what is causing it. I added it to the list of known bugs, but it’s a low priority bug
– UPDATE –
Fixed multiple bugs (See the version 1.1 section)
This update should make this custom lua chat system stable, will see if more errors pop up though
Added settings for making the chat look like LegacyChat exactly
(These aren’t the settings to make it look like LegacyChat, see the ChatSettings.lua file)
If these settings are missing from your ChatSettings module, a default value is used instead. You can still use a ChatSettings module from version 1.0
I’ve noticed the size of the chat frame is smaller than how it was in the Legacy Chat when following these steps:
Here’s a comparison:
I found that it’s caused by the system using DefaultWindowSizeDesktop
from the ChatSettings module. With normal Legacy Chat, it does not use it for some reason.
Also, this works whenever TextChatService.ChatVersion
is set to LegacyChatService
. Is this intentional?
This is a bug I’ve fixed in my old thread:
I’m not quite sure what the correct size would be if you want to have the exact size of the LegacyChat. I think that bug was inconsistent, and so the size was inconsistent, but I am not sure (I went on a game that still used the LegacyChat, and got the DesktopSize). You can try to set DefaultWindowSizeDesktop
to be the same as DefaultWindowSizeTablet
I have tried setting TextChatService.ChatVersion
to LegacyChatService
, and disabled Chat.LoadDefaultChat
, and I get this error:
TextChatService is not enabled. Check TextChatService.ChatVersion and make sure it is not set as ChatVersion.LegacyChatService.
If I don’t disable Chat.LoadDefaultChat
, I don’t get any errors, but every chat message seem to go through LegacyChat. The two are using the same EventFolder, and my guess is that the LegacyChat ends up taking over the CallbackFunction for the RemoteFunctions, making it so the custom Lua chat system isn’t throwing an error?
– UPDATE –
Fixed minor bugs, and added some features including (see section v1.2 for full list):
– Types! No more guessing if a method takes in the name of a speaker, or the speaker object itself. These types are applied to ChatService, ChatChannels, and Speaker by default, but are also available from the main Chat module
Note that the types do not include Internal functions, those that start with Internal
– Channel.CanJoinFunction
and Channel.CanLeaveFunction
, for more control over whom can join a channel, instead of a boolean that acts upon every single speaker
– Chat:GetChatService()
, a more practical way to get ChatService
Here is a little code snipped for creating an admin channel, using some of the new features:
local Chat = require(game.ReplicatedStorage.Chat)
local ChatService = Chat:GetChatService() -- New v1.2 method to get ChatService, from Chat
local function CanJoinFunction(Speaker : Chat.Speaker)
local Player = Speaker:GetPlayer()
if not Player then return false end
-- This could be modified to check if a player is in a group
if not Player:GetAttribute("Admin") then return false end
return true
end
local Channel = ChatService:AddChannel("Admin", false)
Channel.Private = true
Channel.CanJoinFunction = CanJoinFunction -- New property of Channel for complex join conditions
Channel.Leavable = true
Channel.WelcomeMessage = "This is a private channel for Admins"
ChatService.SpeakerAdded:Connect(function(SpeakerName : string)
local Speaker = ChatService:GetSpeaker(SpeakerName)
local Player = Speaker:GetPlayer()
if not Player then return end -- A non-player speaker
-- Do an initial check to see if the speaker can join the channel
-- Joinable/CanJoinFunction (and the ones for leaving) is only for /j, /join and /l or /leave
if CanJoinFunction(Speaker) then
Speaker:JoinChannel("Admin")
end
-- Automatically join and leave the channel as the attribute changes
Player:GetAttributeChangedSignal("Admin"):Connect(function()
if not Speaker:IsInChannel("Admin") then
if not CanJoinFunction(Speaker) then return end
Speaker:LeaveChannel("Admin")
else
if CanJoinFunction(Speaker) then return end
Speaker:SendSystemMessage("You've been kicked from the Admin channel as you are no longer an admin", "System")
Speaker:LeaveChannel("Admin")
end
end)
end)
Does anyone know if my fork suffers from the same performance issues that plagues TextChatService?
TextChatService’s ui is disabled, but I’ve heard people say that disabling the ui doesn’t fix the lag issue. I’ve also heard that the performance issues only arise after some time (probably after a lot of messages have been sent). Has anyone used this fork in a game that has many players, or tested it in a server with many players?
My own game doesn’t have enough players for me to test this I don’t think, although there is one game, that isn’t mine but I’ve done some small things for, where I could update the chat to the forked version, and I might do that
Your input is appreciated :D
Love this! However, one issue. When I try to use :DisplaySystemMessage()
I get an error saying:
ReplicatedStorage.Chat.ChatScript.ChatMain.TextChatServiceWrapper:44: table index is nil
Below is the script I tried using:
local function sysMsg(message)
TextChatService.TextChannels_PortedChat.System:DisplaySystemMessage(message)
end
sysMsg("Hello!")
Thanks for making this port!
My port of the Lua chat system doesn’t support calling the TextChannels directly, although you can destroy them to remove a Channel or Speaker
Supporting a direct call to TextChannels is not something I currently plan on adding
On the server, you can get the speaker from
ChatService:GetSpeaker(Player.Name)
, and then use Speaker:SendSystemMessage(message : string, channel : string?)
On the client I am not too sure, I know the client can send system messages to itself (the methods are in ChatMain I believe), but I don’t know if they can be accessed from outside scripts
You can use the StarterGui:SetCore(forgot the arguments) method to send system messages from the client, that one still works
I also haven’t added types for ChatMain on the client. This is something I’ll have to look into
I’ve tested the performance of both chats, and it’s actually very easy to replicate. ExperienceChatMain will be active when a message is sent. I don’t think it gets worse as more messages are added, although I did not test it after a while and just base this assumption on the fact it’s slow from the first message you send
However, I’ve also seen ExperienceChatMain take up a lot of cpu time when LegacyChat was being used, so it is possible that both LegacyChat and TextChatService suffer from performance issue. I do not know by how much this issue is exacerbated on TextChatService (assuming it is worse on TextChatService)
The default UI seems the same
Also, Topbar is another trouble maker, and seems to also cause lag spikes when a player chats, or shortly after?
Then you have PlayerListManager also taking several miliseconds…
I dunno if this is a known issue or if it’s just happening to me, but when I type the &
key, after sending the message it turns into &
, even if I was only trying to send the symbol (&)
It’s probably due to TextChatService replacing some symbols into other characters.
Quite annoying to be honest.
Interesting, seems like the reason is @Sun_Battery23238’s answer
I could probably match and replace it when the message is received, perhaps by marking & and others with one of the disallowed whitespace characters of the legacy chat, to mark those characters (or just assume people wont write &
themselves and replace it directly)
i just janked the module to response to .chatted functionality while you chat on system (also overwriten the bubble chat with ‘…’ for privaciyer)
i added a fixer for it æææææææææææææææææææææ (useless æ filler)
thank you for this imformation i will add dis-
This is really cool, though one question. are you able to make it so you cant see the chat history?
Chat history is disabled by default
If you meant having just the input bar
How to customize Lua chat to only show chat bar, and not chat window? - #8 by TheGamer101
You can do so my changing these two settings inside of the ChatSettings module
--- Replace with true/false to force the chat type. Otherwise this will default to the setting on the website.
module.BubbleChatEnabled = true -- PlayersService.BubbleChat
module.ClassicChatEnabled = false -- PlayersService.ClassicChat
Not very intuitive setting though…
Chat history is using the old text filtering api, which wont be allowed past april 30th
By default it is disabled, it can be enabled in the FFlags module
TextChatService doesn’t have any api to store the chat history, meaning it’s impossible to do so without breaking the ToS or whatever terms (without some extremely impractical workaround like making players resend messages for people who just joined)
Unless chat history doesn’t count as a chat, but I heavily doubt that
Just to tag on about chat history here - we don’t really have a true chat history per-se, more just a log of messages the player has been there to observe since connecting.
A chat history like how IRC channels work is totally possible for the lifetime of the server, however, it’s a cause for a security concern, memory usage concern and/or an overwhelming of a player’s network connection. I considered adding Chat History to WinryChat because it’s something I was used to, but there’s a few issues with the implementation of this feature that I’ve considered.
I am absolutely sure ROBLOX’s staff have done their best to find a way to handle this properly - they likely did it before, and they’ll likely do it again because they’ve got an all-star team of computer scientists behind them.
Feel free to correct or question everything here, I’m just presenting things as if they’re worst-case scenarios, not everyday concerns. This is me overthinking the entire problem for the sake of making sure there’s a record of this somewhere.
The constants - Problems that exist in every method
These are problems which, while considerations, aren’t exactly major issues. This section is just here to go “this would be a worst case scenario - i hope we never run into it” and provide a preamble to a generally workable solution.
-
Constant 1. We have to somehow verify the receiving player’s device has enough memory to handle all messages. (We can’t, we aren’t allowed low-level access to devices).
-
Constant 2. We have to pace the creation of messages accordingly so that the receiving player’s device doesn’t freeze or stutter. (Processing Speed of a CPU).
-
Constant 3. We have to account for if the player has a bandwidth weaker than 8 Megabits (8Mb/s) This is not uncommon on 3G and 4G networks on mobile devices in countries with access to ROBLOX and while we can get away with 4-8 mb/s, we should really try make it as least impactful on the player’s network as possible. (Networking & Resources Concern)
ROBLOX is a game that should be able to run on a variety of devices. Whether it runs well on some… well, that’s another topic for others to delve into. If you’re making a static environment that doesn’t have any major scripting outside of the chat system, then this really shouldn’t be a worry. This is likely only a concern for games that are REALLY hammering a player’s device.
We are talking in the kilobytes of storage for TextLabels (and buttons for whispers for each user-name) for each message at most. 1-2kb maximum. A message alone isn’t enough for us to worry about on most experiences, it’s an insignificant transient object for the lifetime of the application. But we’ve got a resource to worry about - Device Memory (RAM).
RAM is admittedly only a concern if we’re creating and storing thousands, of message frames on the client. It’s entirely possible for a message log to meet 50MB on it’s own if a server has lasted long enough (social games can no doubt hit this if a server lives long enough). We can minimize this by say using numerical tables instead to associate words to numbers or hexadecimal keys, but that itself is an engineering effort all of it’s own.
Memory Usage on it’s own is fine. Everything we do uses memory in the ROBLOX engine. How much depends on how many things we’re doing. Just food for thought on the general problem of memory management. For developers like myself and Chilly, we absolutely should consider the resource costs (Processing, Rendering & Memory Usage) of our UI, players’ messages & any intermediary content/objects. For a Chat History feature, it’s a foundational concern… even if an excessive one when we’re dealing in mere kilobytes.
As for pacing message creation - not all CPUs are made equally. Android and iOS devices are especially known for having some really powerful, and really power-efficient, CPUs that can push through a lot nowadays. That said, ROBLOX still supports (even if only partly), old models powered by old ARM Chipsets. Most, if not all, are multi-core 1.0-1.6GHz chips with performance and efficiency cores. That being the case… then Parallel Luau would be wonderful for this type of heavy-compute task.. But it doesn’t support parallel object creation - dangit!
So we’re stuck in serial processing for the creation of messages - the bulk of our workload. We’d need to make it so that messages are created either when the player scrolls up far enough, or so that messages are created in bursts until all are created. Realistically, we’d cache the data for when it is needed, and load messages in chunks because we really do not want to be hogging memory from a user’s device for something they’ll never read.
Lastly for networking bandwidth, we can’t just send a bunch of messages at once nor can we send them in measured bursts until the player client reports that it has all messages. Well… we can, but it won’t be pretty for some users. We’d be doing the burst method of say 16 messages after the user scrolls up some point.
Our solution is to then only send what the player requests, not what we think they will want to see immediately. There are tons of ISPs and Mobile Carriers that cap data for a mobile/wired contract, cap bandwidth speeds and so much more around the world. It’s irresponsible of us to be sending needless data to players that may already struggle to enjoy life on the internet. No resource is truly free and everything has a cost at the end of the day.
Now onto the bad method…
Peer-To-Peer Message History - No.
This is the first interpretation that came to mind from:
The major issues with this come straight away in the method header.
-
You rely on one or more players to broadcast the contents of their chat history to the server, and have the server broadcast to the new player.
-
You trust the output of each player or filter each message description again as a precaution.
-
One or more players already on the server and the new player joining expend bandwidth to send and retrieve the history.
By doing peer-to-peer replication of chat history, you essentially have to verify who has longest lifetime on the server (or the most complete message history). Or you could do it at complete random and end up trying to get history parity with a person who only joined moments before the new player did.
If you want to get someone with the most complete message history, you’ll have to filter through all players through some (admittedly menial) back and forth communication. Then that player has to asynchronously send a bunch of packets to the server for all messages.
If you want to get someone at random then you may get someone with an incomplete message history who has also just joined the server. In which case, their incomplete message history is used as a template for the new player. You can try resolve this by attempting to pace the replication of messages to match or be slower than that of the other player (a complete fools errand for many reasons) or break away with new player not being able to see the full chat history.
Once you have your player to replicate from, then comes the replication itself. Do you want to trust that player’s log as a source of truth? Absolutely not! Players can still use easy-to-access tools to modify objects, and although it’s not worth the hassle to modify message logs, it can still be done. Instead, we’ll likely need to filter again… but what with? Messages from some players may not even be able to be refiltered because that player already left the game, which is, at the time of writing, a limitation of TextService.FilterStringAsync.
We can just bypass filtering altogether, trust the source and deliver previously filtered messages as a “trusted resouce”… but- oh noes… We may break ToS! We can’t verify if any (or all) given messages are safe or appropriate for all ages - which is why we’re in this mess of a new chat system migration to begin with.
In this case, we’re instigating the issues of two of the constants potentially twice. We’re going to possibly make two households with poor connections struggle. We’d using bandwidth for data we really don’t need to use to make this feature work for no good reason. We’d also be increasing the processing power (and power draw) for two or more devices at once… that’s really not good.
Of course, no one in their right mind should chose this option. Needless risk for no meaningful gain.
Players Rebroadcast Sent Messages - Technically Possible, but an awful User Experience
This is the second interpretation of the following quote:
This is more about having players store a log of every message they sent since they joined from within their Chat UI (this is really the only place they can store the raw string).
However, we’ve got three major issues:
-
We’re flooding chat with messages we don’t need to for each player.
-
If a player leaves during this process, or left before this process, while another user they were having a conversation with still remains on the server… we’re sending half of a conversation (if that) to the recieving player.
The first issue is that this solution would affect all players’ Chats. You won’t be whispering the messages to one player, you’ll be outright flooding chat with every conversation each player had since they first joined that server. Not only would you wash out players’ current conversations, you would make it nigh impossible for players to remember what they were talking about. It might even lead to players getting reported for spamming chat (not good at all).
The second issue is that by handling a chat history this way, you are having each player broadcast through a TextChannel again. For each player that is having to rebroadcast their messages, they will very likely be floodchecked because they will likely have more messages than is permitted within the floodcheck restrictions. Even if you find a way around the first issue, this isn’t just bad for the chat experience in general. It’s bad for the player as now they have to wait for the chat system to rebroadcast everything between floodchecking periods. They won’t be able to have fun or continue socialising with friends.
Lastly, there’s a good chance that players will just leave before or during this process and new players will miss context to things said in chat. We’ve all been there once to enjoy someone who has been spinning a good tale in chat. But if we only have the reactions as the user, and not the story, we might just view the server as full of fake players or bot accounts. And if a player leaves during this process, we’ll have the issue of only partial context. This one’s purely a UX issue, but it’s an integral one on why this can’t work.
Server Cached Messages - Possible, but a Pain.
This is possible, but the implementation will vary based on the programmer and their experience with networking & data management. If you want to just get things up and running regardless of the server, this should do.
-
A Server’s lifetime varies. One server may last 4 hours, while another lasts 37 minutes. The amount of text logs generated can also wildly vary, and we can’t guarantee a consistent timing of each message log - though we can get close with the help of DateTime.FromUnixTimestampMillis in exchange for a byte or few.
-
We’re either making messages visible to all clients, or using a ModuleScript to store all filtered messages.
By doing this, we can get a message history going… we can’t get it going for all players though. We’re effectively making a blanket system and hoping it’s a one-size-fits-all thing. By doing this, we’re also using more server-side resources. Just like our devices, ROBLOX’s servers also have allocated memory for the virtual machines used to run our experiences.
This is completely fine to do if you’re making an error logger that anyone can see in a lobby or a pop-up UI to help report issues. After all, a global server log for this is really helpful. The problem for chat history comes into how do we store them. We can store them in ReplicatedStorage and offload the workload to ROBLOX’s core engine (why?), or in a ModuleScript.
The ReplicatedStorage option is outright terrible for message security and for wasting resources. We’ll be making messages visible to all clients for all channels, even if that player isn’t connected to that channel. We’ll be wasting the resources of every player and the resources of the server at the same time because we’re admittedly being lazy and misusing the intended purpose of ReplicatedStorage (much like people did with Lighting before ReplicatedStorage was a thing).
The ModuleScript option is good, especially for the data sharing properties of ModuleScripts in a thread, but is a complex task. With this method, you’ll need to send the messages to the receiving player(s) for only the channels they’re connected to. When any player joins a channel, they’ll also need the messages. This also means re-sending the same messages if a player leaves, then re-joins a channel.
For there to not be a significant enough impact on server performance, you should be able to make this work with an Actor and Parallel Luau, as this would be a task that’s purely data-driven without Instances. It might even be a good use-case for a SharedTable, though someone correct me if I’m wrong there.
As for the consistent timing of each message log, this is more of a niche issue. We can use milliseconds to get a pretty good idea of where to place the message in an ordered list. The only problem is there may genuinely be times where two messages are somehow (however unlikely) sent at the same time. That’s nanoseconds apart, really. You may have two separate conversations running on two separate things and if the message order is wrong on one client, then it’ll look strange.
Needless to say, the ModuleScript option is more desirable because we’ll be able to manage how messages are sent, who they’re sent to and how they’re organized. Just at the cost of server memory.
Final Notes
If I were to approach this for a project, the main components I’d look at are:
-
Parallel Luau & Multi-threading for asynchronous processing & sharing of chat history on request
-
A SharedTable or ModuleScript containing all the messages ever sent, being logged in real-time, so that we can have one or more actors on the task.
-
RemoteEvents to faciliate server-client communication explicitly for Message History. We can leave the messages already being sent through TextChatService alone as they’re already being managed just fine.
-
A way to compress the data as finely as possible to use the least amount of memory with the least data loss.
-
A Chat UI that can detect when a player is at the top of a channel. That way, we can ask the server for more messages to read if it can. There is a hacky way to do this with an upside down scrolling frame, but I’d try find an approximated way instead.
Of course, there are other ways to do message history that people may come up with - or have come up with. It’s a very complex, technical feature that requires server and client scripting. I personally have no plans to introduce this into WinryChat - it’s client-sided only and is meant to be there so any developer can drop it into their game without any scripting issues. I also have no reason to implement this in any other project right now as there doesn’t appear to be any demand for it.
If you wish to further explore this idea, feel free to… just be warned. Here be dragons.
I have been writing this for 4 hours straight and trying to correct what’s over the top, incorrect, or just unnecessary. But someone will definitely find something later and call me out on it or something. This is an intentional exaggeration of some of the concerns with design & logic so that it’s a bit more clearer on the how and why of a Chat History than a “just add it now”.
I’ll also probably regret going on a tangent, so uh- yeah thanks for reading and please be kind… I’m very sleepy now.