Note: This tutorial is for people who understand basic to intermediate scripting.
There is a small glossary at the bottom of the tutorial if you want some quick explanations of certain functions or concepts.
So, with the recent drama, and since custom admin commands are very useful, I bring you this admin commands tutorial.
Why not use free model admin commands?
You can use custom admin commands for anything from testing your game to managing your clan’s fort.
Since they are custom, you can edit them to work however you want, understanding exactly how they work. You can also make commands specific to your game.
As a bonus, you know exactly what is being done to your game and who has access to what - something that not a good amount of free models can boast - so your game is more secure.
This tutorial covers:
- Adding & identifying admins
- Parsing arguments using string patterns
- Finding and calling command functions using a dictionary
Setup
Body
The type of admin we are making automatically parses commands. This is preferable for two reasons: it’s faster to add commands, and it is more consistent.
It is preferable to put your admin script in ServerScriptService instead of Workspace. ServerScriptService is designed for scripts, and Workspace is designed for physical objects.
It’s also recommended to give your script a comprehensive and unique name so you can find it easily and know what is for.
Once you have your script, define the Players
service and the PlayerAdded
callback:
local Players = game:GetService("Players")
Players.PlayerAdded:Connect(function(Player)
-- Empty
end)
Next, you want to setup the listener for the Chatted
event:
local Players = game:GetService("Players")
Players.PlayerAdded:Connect(function(Player)
Player.Chatted:Connect(function(Message,Recipient)
if not Recipient then
-- Empty
end
end)
end)
The if statement stops the command being parsed if the player is whispering to someone. You may not add this if you want to be able to do a command anywhere - but it’s best to keep commands out in the open, at least until you have a command log.
Adding Administrators
Body
There are many variations of how this can be done, but my favourite is to be able to define using either username or user ID in an array.
Make your array:
local Admins = {
"EmeraldSlash"; -- Username example
17614882; -- User ID example
}
Extra: Group admins
Using this method, you can also add groups and minimum ranks.
The formatting in this tutorial for a group entry in to the array will be like so:
{GroupId = 0000; RankId = 255;}
RankId will be optional.
Example:
local Admins = {
"EmeraldSlash",
{GroupId = 12345; RankId = 255}
}
From now on, any details named the same as this one will add functionality to this aspect of the admin commands.
Next, you want to be able to check if a player is an admin. We will make a function so this can be done easily in the middle of code.
local function IsAdmin(Player)
-- Empty
end
Loop through the admins, and add an if statement checking if the player has the same username as one in the admins. Return true
when the player is identified and return false
at the end of the function, which will be reached if nothing matches up.
Since we are using a number of data types, make sure to check what type of value you are checking before altering and comparing it.
local function IsAdmin(Player)
for _,Admin in pairs (Admins) do
if type(Admin) == "string" and string.lower(Admin) == string.lower(Player.Name) then
return true
end
end
return false
end
Then, you want to check if the player has the same user ID as one that has been defined in the Admins array:
local function IsAdmin(Player)
for _,Admin in pairs (Admins) do
if type(Admin) == "string" and string.lower(Admin) == string.lower(Player.Name) then
return true
elseif type(Admin) == "number" and Admin == Player.UserId then
return true
end
end
return false
end
Extra: Group Admins
Add an extra elseif to the if statement in the IsAdmin
function.
if Admin.RankId
has not been defined, replace it with 1, as a player is not in a group if their rank is 0.
local function IsAdmin(Player)
for _,Admin in pairs (Admins) do
if type(Admin) == "string" and string.lower(Admin) == string.lower(Player.Name) then
return true
elseif type(Admin) == "number" and Admin == Player.UserId then
return true
elseif type(Admin) == "table" then
local Rank = Player:GetRankInGroup(Admin.GroupId)
if Rank >= (Admin.RankId or 1) then
return true
end
end
end
return false
end
Now, all you need to do is call the function to see if a player is an admin in the PlayerAdded
callback:
Players.PlayerAdded:Connect(function(Player)
Player.Chatted:Connect(function(Message,Recipient)
if not Recipient and IsAdmin(Player) then
-- Empty
end
end)
end)
Parsing commands
Body
Now that we can check if a player actually has permissions to use the commands, we need to parse (decode) their message.
For the purposes of this tutorial, I will create a separate function to parse the message to stop spamming lots of lines of code. As the function’s body, use string.lower
to make the message lowercase.
local function ParseMessage(Player,Message)
Message = string.lower(Message)
end
Don’t forget to call the function from the PlayerAdded
callback!
Players.PlayerAdded:Connect(function(Player)
Player.Chatted:Connect(function(Message,Recipient)
if not Recipient and IsAdmin(Player) then
ParseMessage(Player,Message)
end
end)
end)
Prefix
The first part of parsing is to check if the player actually was running a command at all - in most cases, they probably won’t be.
First; define the prefix under the admins.
local Prefix = "!"
Note that some characters are used in string patterns, such as .
which is the string pattern for ‘anything’. If you put one of these characters in your prefix variable your parser will probably break. For characters such as these, you will need to ‘escape’ the pattern by putting a %
in front of the pattern character – for example, %.
When making a prefix, have a look at the table of string patterns in the Roblox documentation to see whether your prefix needs to have any string patterns escaped.
Next, we move on to the ParseMessage
function.
The first step is to check if the prefix is present in the message. For this, we use the string.match
function. This is used to find one string in another string. Example:
local MyString = "Hello, world!"
print(string.match(MyString,"world"))
-- outputs 'world'
A limitation with this is that you can only find a single value with a conventional string - but, Lua has another useful feature, called string patterns.
We will focus more on it later, but right now, all you need to know is that the ^
character anchors the match to the beginning of the string - meaning that it will only match the string if it is at the start of the other string.
An example of this:
local PrefixMatch = string.match(Message,"^"..Prefix)
-- concatenates '^' with the prefix, creating a string similar to this: ^!
-- will only return a match if the prefix is found at the start of the message
You can read more on the string.match
function here and string patterns here.
Moving on, we want to use the previous example and an if statement checking if the prefix was found:
local PrefixMatch = string.match(Message,"^"..Prefix)
if PrefixMatch then
-- Empty
end
Finally, we want to remove the prefix from the message if it was found using string.gsub
. This function is similar to string.match
except it replaces a one string with another string.
string.gsub("mainString","toReplace","replacementString",numberOfReplacements)
Since this function has the g
letter in the name, that also means it will replace every match in the main string with the replacement string, unless told otherwise. We can do this using the numberOfReplacements
argument.
The wiki article on this is here.
You can replace with an empty string (""
) to effectively remove parts of a string.
That’s what we will be doing now.
Redefine message as the version where it has the prefix removed:
Message = string.gsub(Message,PrefixMatch,"",1)
Final prefix code:
local function ParseMessage(Player,Message)
Message = string.lower(Message)
local PrefixMatch = string.match(Message,"^"..Prefix)
if PrefixMatch then
Message = string.gsub(Message,PrefixMatch,"",1)
end
end
Arguments
Now it’s time to split the message up into arguments. For future reference, we will be using the first argument as the name of the command we will be running.
Firstly, define the array that will hold the arguments:
local Arguments = {}
Once you’ve done that, it’s time to actually split the message.
We will do use this using string.gmatch
. This function acts like string.match
, but every time it is called on the same string, it will return the next match - and instead of returning a string, it returns a function, that, when called, will return what it would have.
For example:
local MyString = "I'm 7 years and 2 months old lol"
local Match = string.gmatch(MyString,"%d") -- %d is the string pattern for a number
Match()
-- returns returns 7
Match()
-- returns returns 2
The wiki article for this function is here.
For this particular use of string.gmatch
we will be using the pattern [^%s]+
.
To break it down:
-
+
returns the largest string possible whilst retaining the pattern -
[]
is a set. This allows you to have more than one pattern be searched for, have a range of characters to search for, or use complements -
^
, when used in a set, is a complement. This means the pattern will search for everything but what is defined inside the set -
%s
is the pattern to match whitespace characters
You can find the string patterns wiki article here.
To summarise: we are searching for as many non-whitespace characters in a row as possible.
A great thing about string.gmatch
(and string.gsub
, but that’s irrelevant) is that you can use them as the iterating function for a for loop. This means we can easily loop though every argument the player uses when using the previously defined pattern.
for Argument in string.gmatch(Message,"[^%s]+") do
-- Empty
end
Now all we need to do is insert the argument into the Arguments
table:
local function ParseMessage(Player,Message)
Message = string.lower(Message)
local PrefixMatch = string.match(Message,"^"..Prefix)
if PrefixMatch then
Message = string.gsub(Message,PrefixMatch,"",1)
local Arguments = {}
for Argument in string.gmatch(Message,"[^%s]+") do
table.insert(Arguments,Argument)
end
end
end
Now you have every argument the player sent in the message.
Creating Commands
Body
Before we process the commands, we first need to create some commands, and also setup a consistent format.
Command functions will be indexed by name, and passed a consistent sequence of arguments.
Firstly, create the table we will use to create commands:
local Commands = {}
Next, let’s add a basic command.
The parameters in a command function will be, in this tutorial, <player> Sender
and <array> Arguments
. In this tutorial, the command will be print
. It will print a given message to the output.
Commands.print = function(Sender,Arguments)
local Message = table.concat(Arguments," ")
print("From " ..Sender.Name..":\n"..Message)
-- /n creates a new line
end
When this is called, it should print something similar to the following example:
From EmeraldSlash:
wow this command is pretty cool
Processing Commands
Body
Now that we have a command to use, let’s set it up with the parser function.
Firstly, retrieve the first value from the array after the for loop that splits the message into the arguments array. This will be used to index the command.
local CommandName = Arguments[1]
It is also a good idea to remove it from the table, as you don’t want to pass the command name as an argument.
table.remove(Arguments,1)
Next, try and index the command function and save it to a variable, then check to see if it exists.
local CommandFunc = Commands[CommandName]
if CommandFunc ~= nil then
-- Empty
end
If it does exist, call it.
local function ParseMessage(Player,Message)
Message = string.lower(Message)
local PrefixMatch = string.match(Message,"^"..Prefix)
if PrefixMatch then
Message = string.gsub(Message,PrefixMatch,"",1)
local Arguments = {}
for Argument in string.gmatch(Message,"[^%s]+") do
table.insert(Arguments,Argument)
end
local CommandName = Arguments[1]
table.remove(Arguments,1)
local CommandFunc = Commands[CommandName]
if CommandFunc ~= nil then
CommandFunc(Player,Arguments)
end
end
end
The wiki page for table functions is here.
Finale
Body
Your code should look roughly like this:
local Admins = {
"EmeraldSlash"; -- Username example
17614882; -- User ID example
-- {GroupId = 0000;RankId = 255;} -- Group example
}
local Prefix = "!"
local Players = game:GetService("Players")
local Commands = {}
Commands.print = function(Sender,Arguments)
local Message = table.concat(Arguments," ")
print("From " ..Sender.Name..":\n"..Message)
end
local function IsAdmin(Player)
for _,Admin in pairs (Admins) do
print(Admin,Player)
if type(Admin) == "string" and string.lower(Admin) == string.lower(Player.Name) then
return true
elseif type(Admin) == "number" and Admin == Player.UserId then
return true
--[[elseif type(Admin) == "table" then
local Rank = Player:GetRankInGroup(Admin.GroupId)
if Rank >= (Admin.RankId or 1) then
return true
end]]
end
end
return false
end
local function ParseMessage(Player,Message)
Message = string.lower(Message)
local PrefixMatch = string.match(Message,"^"..Prefix)
if PrefixMatch then
Message = string.gsub(Message,PrefixMatch,"",1)
local Arguments = {}
for Argument in string.gmatch(Message,"[^%s]+") do
table.insert(Arguments,Argument)
end
local CommandName = Arguments[1]
table.remove(Arguments,1)
local CommandFunc = Commands[CommandName]
if CommandFunc ~= nil then
CommandFunc(Player,Arguments)
end
end
end
Players.PlayerAdded:Connect(function(Player)
Player.Chatted:Connect(function(Message,Recipient)
if not Recipient and IsAdmin(Player) then
ParseMessage(Player,Message)
end
end)
end)
And there you go! You now have working admin commands
Glossary
Body
Parse – resolve (a sentence) into its component parts and describe their syntactic roles. (Google)
type() – returns what data type a value is
string.lower() – returns a lowercase version of a given string
string.match() – finds and returns one string in another
string.gmatch() – string.match()
but it returns a function that will return the next match every time it is called
string.gsub() – replaces a given string in another string with a replacement string
table.conat() – concatenates values in a table into a string using the second parameter as the divider
\n – when used in a string, it creates a new line
String Patterns
A string pattern is essentially a way of using variables in strings. You can use a certain pattern to search for a variable set of characters.
For example, using the number pattern (%d
) in a string.match
will return a variable number.
string.match("Age:7","%d")
-- returns 7
string.match("Weight:3kgs","%d")
-- returns 3
Thanks for reading - hopefully this helped you out in some way
Make sure to tell me if something is wrong.