How to Create Game-Creator-Only Chat Commands

Do you ever wish, as the game creator, that you could easily give yourself special powers. For example, I like to increase my JumpPower and WalkSpeed. By raising these I am able to get around a large map much quicker while I am developing and testing the game. One way to accomplish this is with custom chat commands. If you have any interest in custom chat commands or superuser powers then I suggest you look at the following code.

I create a separate script for each command and place that script in ServerScriptService. The example below is named JumpPowerChatCommand:

local Players = game:GetService("Players")

-- Command to choose a jump power (note the trailing space)
local COMMAND = "/jumppower "

local function hasMatchingCommandName(text, command)
	-- Note: string.sub(message, ...) is the same as text:sub(...)
	return text:sub(1, command:len()):lower() == command:lower()
end

local function getPowerFromMessage(message)
	local powerString = message:sub(COMMAND:len() + 1) -- Cut out the "60" from "/jumppower 60".
	local power = tonumber(powerString)
	return power
end

local function onPlayerChatted(player, message, recipient)
	if hasMatchingCommandName(message, COMMAND) then
		local power = getPowerFromMessage(message)
		if power then
			local character = player.Character or player.CharacterAdded:wait()
			character.Humanoid.JumpPower = power
		end
	end
end

local function onPlayerAdded(player)
	if player.UserId == game.CreatorId then -- This command is available only to the game creator.
		player.Chatted:Connect(function(...)
			onPlayerChatted(player, ...)
		end)
	end
end

for _, player in pairs(Players:GetPlayers()) do
	onPlayerAdded(player)
end
Players.PlayerAdded:Connect(onPlayerAdded)

Iā€™ve tried to structure the code to maximize readability. And Iā€™ve added some helpful comments. If you have any questions feel free to ask.

12 Likes

Hereā€™s an alternative way:

Split the message by " " (spaces), now we have {"jumppower" ,"60"}. Convert "60" to a number. Check if the message is "jumppower". If so, check if we have the 60 argument. If both are true, update the jumppower.

Besides that, nice tutorial.

6 Likes

I like that improvement. Like any aspect of programming there is always more to learn, right? Thank you for your suggestion. Much appreciated.

2 Likes

Cool, so you have separate scripts for each command?
I wonder if having too many scripts connecting to each chat message you send and scanning your chat messages would turn into performance issues. Maybe it wouldnā€™t but its something to consider.

2 Likes

Yes, I do use a separate script for each command. Keep in mind that I only have a handful of creator-only commands. And I decided that the code would be cleaner and more easily understood by separating them. I donā€™t think performance would be an issue the way I am coding this. For another example, here is my WalkSpeedChatCommand server Script:

local Players = game:GetService("Players")

-- Command to choose a walk speed (note the trailing space)
local COMMAND = "/walkspeed "

local function hasMatchingCommandName(text, command)
	-- Note: string.sub(message, ...) is the same as text:sub(...)
	return text:sub(1, command:len()):lower() == command:lower()
end

local function getSpeedFromMessage(message)
	local speedString = message:sub(COMMAND:len() + 1) -- Cut out the "24" from "/walkspeed 24".
	local speed = tonumber(speedString)
	return speed
end

local function onPlayerChatted(player, message, recipient)
	if hasMatchingCommandName(message, COMMAND) then
		local speed = getSpeedFromMessage(message)
		if speed then
			local character = player.Character or player.CharacterAdded:wait()
			character.Humanoid.WalkSpeed = speed
		end
	end
end

local function onPlayerAdded(player)
	if player.UserId == game.CreatorId then -- This command is available only to the game creator.
		player.Chatted:Connect(function(...)
			onPlayerChatted(player, ...)
		end)
	end
end

for _, player in pairs(Players:GetPlayers()) do
	onPlayerAdded(player)
end
Players.PlayerAdded:Connect(onPlayerAdded)

Good questions, by the way. Thank you for asking them.

1 Like

Yeah, iā€™m sure you wouldnā€™t notice any problems with a handful of commands. I like this type of solution vs. the bloated admin systems that are popular. If I had to have one command like this itā€™d probably be a player reset, which would be easy to make as you have done here. Right now, Iā€™ve just been typing it into the console if i want to reset someone or so something ā€˜adminā€™. workspace.bozotrolling.Humanoid.Health = 0 take that!

2 Likes

And here is my script that allows me to switch teams in my team-based game:

local Players = game:GetService("Players")
local Teams = game:GetService("Teams")

game.Players.CharacterAutoLoads = false

-- Command to choose a team (note the trailing space)
local COMMAND = "/jointeam "

local function hasMatchingCommandName(text, command)
	-- Note: string.sub(message, ...) is the same as text:sub(...)
	return text:sub(1, command:len()):lower() == command:lower()
end

local function hasMatchingTeamName(text, name)
	-- Let's check for case-insensitive partial matches, like "red" for "Red Robins".
	return text:sub(1, name:len()):lower() == name:lower()
end

local function findTeamByName(name)
	-- Return nil if no team found.
	if Teams:FindFirstChild(name) then -- First, check for the exact name of a team.
		return Teams[name] 
	end
	for _, team in pairs(Teams:GetChildren()) do
		if hasMatchingTeamName(team.Name, name) then
			return team
		end
	end
end

local function getTeamFromMessage(message)
	local teamName = message:sub(COMMAND:len() + 1) -- Cut out the "xyz" from "/jointeam xyz".
	local team = findTeamByName(teamName)
	return team
end

local function onPlayerChatted(player, message, recipient)
	if hasMatchingCommandName(message, COMMAND) then
		-- Matched "/JOINTEAM xyz" to our join command prefix "/jointeam "
		local team = getTeamFromMessage(message)
		if team then
			player.Team = team
			player.Neutral = false
		else
			player.Team = nil
			player.Neutral = true
		end
	end
end

local function onPlayerAdded(player)
	if player.UserId == game.CreatorId then -- This command is available only to the game creator.
		player.Chatted:Connect(function(...)
			onPlayerChatted(player, ...)
		end)
	end
end

for _, player in pairs(Players:GetPlayers()) do
	onPlayerAdded(player)
end
Players.PlayerAdded:Connect(onPlayerAdded)

If anyone has a better way of structuring these three scripts Iā€™d love to see it.

1 Like

Iā€™d probably just suggest combining them, have one listener (one :connect) that parses the chat then decides which function to run. I guess you could get fancy with module scripts and such, but then if youā€™re going to do all that, you might as well get one of those more complex admin systems that others have created.

2 Likes

Exactly. I came up with this approach as a way to avoid using a big admin tool. On the other had, there is no way my way would be manageable for a complete admin system. Apples and oranges and donā€™t over-engineer. And donā€™t let ā€œperfectionā€ be the enemy of ā€œgood enoughā€.

Iā€™ve also coded these backdoor things using keybindings. But then I always forget what key is assigned to what command. So this chat approach just felt like a pretty good solution.

2 Likes

We could create a declarative state machine as well, but, you know, KISS and all that and life is short, right? :wink:

2 Likes

Yep, Iā€™m all for KISS, thatā€™s why my solution so far has been to do without admin commands. :slight_smile:

1 Like

What is the syntax for this method?

edit:
nevermind, i found it with a quick search:

local str = string.split("Hello World!", " ")
2 Likes

I tried it out. I like it. One thing I didnā€™t like was that everyone can see what youā€™re typing in. I was trying to figure a way around that and I kinda stumbled on a hacky way to do it. I know there are ways to tap into the chat system and have a function that prevents the ā€˜commandsā€™ from being sent as messagesā€¦ but in the spirit of simplicity and with only your script, the workaround that delivers a similar result is to use the switch channel command instead of the plain slash. I change your code to:

local COMMAND = "/c walkspeed "

then I am able to type into the chat /c walkspeed 60
I get the error that there is no such channel, butā€¦ the walkspeed function is executed and the walkspeed is changed, and, nobody seeā€™s my chat.

image

I modified your script for a reset script that will reset any player by user name or displayname when I type /c reset Sir_Highness this happens:

1 Like

I love it! Thanks for posting your version. I was willing to let others see my commands, but I could definitely see when you wouldnā€™t want the ā€œmagicā€ to be seen. I suppose the next improvement would be to allow both options (/ and /c) to be possible for the same chat command. That way you would have a choice. After that we can make the whole thing object-oriented and then make a complete system admin framework. :wink:

Well, here is your first two scripts combined into one script with a configurable command dictionary, under 50 lines:

local Players = game:GetService("Players")

-- Command definition table, to add new cmd add in new Constand and also new dictionary entry
local CMDJUMP = "/jumppower"
local CMDSPEED = "/walkspeed"

local COMMANDS = {
	[CMDJUMP] = CMDJUMP; 
	[CMDSPEED] = CMDSPEED;
	}

local function onPlayerChatted(player, message, recipient)
	local stringarray = string.split(message, " ")
	local command = stringarray[1]
	local argument = stringarray[2]
	if argument and COMMANDS[command] then -- if command string exists in the command dictionary then...
		if COMMANDS[command] == CMDJUMP then
			local power = tonumber(argument)
			if power then
				local character = player.Character or player.CharacterAdded:wait()
				character.Humanoid.JumpPower = power
			end
		end
		if COMMANDS[stringarray[1]] == CMDSPEED then
			local speed = tonumber(argument)
			local character = player.Character or player.CharacterAdded:wait()
			if speed then  --make sure speed was entered as a valid number
				character.Humanoid.WalkSpeed = speed
			end
		end
	end
end

local function onPlayerAdded(player)
	if player.UserId == game.CreatorId then -- This command is available only to the game creator.
		player.Chatted:Connect(function(...)
			onPlayerChatted(player, ...)
		end)
	end
end

for _, player in pairs(Players:GetPlayers()) do
	onPlayerAdded(player)
end
Players.PlayerAdded:Connect(onPlayerAdded)

It might make sense to break the commands out as functions to themselves after they are parsed if youā€™re going to get complicated with them.

3 Likes

Why do it in separate script if one script can do the big lifting?

plr.Chatted:Connect(function(msg)
--i didn't script admin commands for 1 year so I might do it wrong
if msgiscommand then
--do something
end
if msgisanothercommand then
--do something
end
end)

I believe 1 script is more efficient than multiple scripts.

Atleast use modulescripts

(Oh yeah the msg is command thing is just an example)

1 Like

Yep, that is what I did in the script above your post. I put two of his commands into one script. I think I did it in a way that would be easy to add more.

1 Like

Yeah but scripting it directly in a single script is faster.

Youā€™re confusing me, that is what I did. I did what you are saying, yet you are suggesting that what you say is preferable. I demonstrated a way to combine them, if he wants to add all his commands in that way, he can use my example and have them all ā€¦ in a single script. As you have subsequently suggested.

3 Likes

Oh okay, Iā€™m actually an idiot.