Why won't Roblox's chat run this command?

A lot of the time when I make commands specific to my games, I resort to chat commands as they are the quickest to script. Because of this, I decided to check out Roblox’s chat system’s ability to register commands, though I’m having no luck getting it to actually work.

I’m trying to register a command using one of my scripts, and the command does something internally that requires the function to be inside the script it effects. So, I made the script register the function with Roblox’s chat.

The issue is that the function doesn’t seem to run at all. There’s no error when I register it, and when I do a check through the ChatService module, it says it’s registered. Trying to register it a second time with the same id errors, as it should. Therefore, the function has been registered yet still won’t run no matter what I chat.

I’ve looked at the wiki on how they do it, and the only thing they do different is that they register the function using a module script inside the chat modules folder. Though as far as I can tell, having a module there is just for convenience, and shouldn’t actually make a difference. Also, as I said earlier, the function the command uses needs to be inside a different script, so putting it inside a module in the chat modules folder isn’t an option.

To show an example of it not working, I made this example script:

local function TestFunction()
	print("Why doesn't this work?")
	return true
end

local ChatService = require(game:GetService("Chat"):WaitForChild("ChatServiceRunner"):WaitForChild("ChatService"))
ChatService:RegisterProcessCommandsFunction("TestFunction", TestFunction)

Why doesn’t this work?

So I was reading this wiki article here: Documentation - Roblox Creator Hub

and when its in a ModuleScript, it is passed a ChatService argument. I’m not entirely sure, but the value it gives the module may have been altered with more specific functionality for that player/game’s chat.

You could have one ModuleScript inside ChatService and then use BindableEvents/BindableFunctions from your main script to run code from the module accordingly if that might help.

1 Like

The last line of the ChatService module is:

return module.new()

The problem with this is that each time the module is required, it’s returning a new instance. So the ChatService table you’re registering your function with is not the one the chat system created and is using. You need to provide your script with a reference to the ChatService instance made by the chat system. One simple way to do this is as @Flowtonio suggested, a dummy module that lets you get this value via a BindableFunction.

2 Likes

Is there a reason it returns a new one each time rather than returning the same one? Seems odd to me but I’ll post a solution with an example to my problem in a moment for anyone who comes across this.

Thanks for both of your help @AllYourBlox @Flowtonio

1 Like

EDIT: My original explanation just below is incorrect. I glanced over ChatService and my brain did not register that it was returning with return module.new() (the table ‘object’ return value), not return module.new (the constructor function). I was seeing what I expected to see, not what is there, so my explanation doesn’t fit. The real reason there are two different ChatService instances is because of the chat system cloning ChatServiceRunner into ServerScriptService, as explained in my later reply below.

This is just a common Lua OOP programming pattern. A ModuleScript will only execute its top-level code the first time it is required in the same context (e.g. server or client), which is good for libraries or when you want static members, but this module was meant to act like a class, and it returns what is effectively its constructor function. I don’t think it’s necessary in this particular case, as only one is ever constructed, I think it’s probably just done this way for consistency with the rest of the chat system code which does make multiple instances of things like Speaker, Channel, etc.

image

Sadly it appears I can’t actually obtain the ChatService through a bindable function, so the only solution is to make a module script for every case I need, as @Flowtonio suggested. I was hoping I could just make one bindable function to obtain it, and use the ChatService directly in my scripts.

Instead of trying to obtain it through the module, it’d be better to just run all the main code through the module, and have your serverscript send signals telling it when to run what function.

Ex:

game:GetService("ReplicatedStorage").bFunction.OnInvoke = function(protocol)
    if protocol == 1 then
        print("Hello World")
    end
end

You could also have the argument you pass into the BindableFunction be a callback to make it easier if you prefer that way.

1 Like

There’s some good, perfectly fine solutions above, but due to how I like my code being I really wanted to obtain the ChatService in my script. Though I wasn’t able to obtain it, I was able to obtain what I think is the next best thing, so here’s my solution:

Since we can’t obtain the ChatService through a bindable function, we can make a proxy. In this case, I only wanted the one function from ChatService, so what I did is put a function of the same name in my proxy table. This proxy is made in a module script, such as seen below:
image

Inside ChatServiceProvider:

local function Run(ChatService)
	--Make a bindable function to obtain the proxy table, and put it in ServerStorage
	local ProviderFunction = Instance.new("BindableFunction")
	ProviderFunction.Name = "ChatServiceProvider"
	ProviderFunction.Parent = game:GetService("ServerStorage")
	
	--Make our proxy table, and put all the functions you want from ChatService in it
	local ProxyChatService = {}
	function ProxyChatService:RegisterProcessCommandsFunction(functionId, func)
		ChatService:RegisterProcessCommandsFunction(functionId, func)
	end
	
	--Return the proxy whenever a script invokes the bindable function
	function ProviderFunction.OnInvoke()
		return ProxyChatService
	end
end

return Run

Then, from any other server script you can gain access to the proxy table and use the function like this:

local function TestFunction()
	print("It works with a proxy!")
	return true
end

local ChatService = game:GetService("ServerStorage"):WaitForChild("ChatServiceProvider"):Invoke()
ChatService:RegisterProcessCommandsFunction("TestFunction", TestFunction)

Edit:
It’s very important you have the highlighted object below:
image

It’s a BoolValue, and it should be marked true. Without this, the other default modules you see in the picture will not load. This breaks any of the features they have, which includes whispering and team chat.


Edit 2:
You could also probably just program the module in a way that lets you obtain the ChatService from it, though somehow using a bindable function to get a proxy feels cleaner to me.

5 Likes

I’m confused as to why the return value of module.new() is not cached. Why wouldn’t subsequent require(ChatService) calls on the server return this same table?

Oh, my bad, I’ve missed an important detail in the original post and misidentified the problem. The script ‘ChatServiceRunner’ that is a child of Chat doesn’t actually run from there, it’s cloned into ServerScriptService. That module return value is cached, as you say, the real issue is that there are two copies of the ChatService module. If you change the code in the first post of the thread to be this:

local function TestFunction()
	print("Why doesn't this work?")
	return true
end

local ChatService = require(game:GetService("ServerScriptService"):WaitForChild("ChatServiceRunner"):WaitForChild("ChatService"))
ChatService:RegisterProcessCommandsFunction("TestFunction", TestFunction)

it will likely work. The only difference from the original is that the require is requiring the module that’s in ServerScriptService, not the one under Chat.

I will edit my original explanation above, because I was seeing what I expected to see regarding the Lua OOP pattern, not what was actually there. Specifically, I wrote my explanation thinking the ChatService module had return module.new at the end, not return module.new(). The latter is unusual (to even bother with having a new() function for a singleton).

1 Like