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.