Creating an intermediate-advanced Admin System

a
Hi! This admin system will use a group to get the rank of the player, but I’m pretty sure you can also use datastores for this.

You will need basic scripting knowledge, nothing too advanced as I explain most things.

Let’s get right into this!

a

Create a serverscript, preferably named “Admin”, inside of ServerScriptService.
Now, create a modulescript inside of our serverscript and name it “Commands”.

image

We could do all the things in one script but we will break it up into two so it looks better. We will handle the chatted event in our serverscript and store the command functions inside of our commands modulescript. That way it’s all organized (you could create a seperate module for every command you are going to make but I don’t really like that).

a
First, we are going to define some variables.

local Players = game:GetService("Players")
local commands = require(script.Commands)
local prefix = ":"

If you don’t understand why I’m using :GetService() instead of simply a dot, it’s because if I renamed my Players service to “Clients” for example, it would error. game:GetService(“Players”) would still work. game.Players V.S. game:GetService("Players") Whats the difference? - #2 by TheNexusAvenger if you want to read more about it.

Now, let’s create our events.

Players.PlayerAdded:Connect(function(player)
	player.Chatted:Connect(function(message)
		
	end)
end)

As the name suggest, PlayerAdded executes the function connected to it whenever a player joins and passing that player as parameter. Same with Chatted, just that it executes the function passed to it, as the name suggest, whenever the player that just joined chats. The message the player sent is passed.

Now, we can check if the message was a command by checking if it starts with the prefix.

	player.Chatted:Connect(function(message)
		if string.sub(message, 1, #prefix) == prefix then
			--his message starts with our prefix!
		end
	end)

string.sub is a function that returns the substring of the given string starting at, in our example, 1 and continuing until, in our case, the amount of characters of prefix.

string.sub examples
local var = "MyString"

print(string.sub(var, 1, 2)) --> "My"
local var = "Great!"

print(string.sub(var, 2, 3)) --> "re"
local var = "Bruh."

print(string.sub(var, 3, #var)) --> "uh."

Now, what we want to do is find the command that he wants to call.
What he just said could look like this: “:teleport player1 player2”
The way we can extract that teleport from that string is by again using string.sub to remove the prefix and another string function, string.split.

local messageWithoutPrefix = string.sub(message, #prefix + 1, #message)

Now, this might look a little bit more complicated than what we did before.
All this is saying is that it should start at the end of the prefix plus one, because of the space after the command, and end at the end of the message.
Now if we had “:teleport player1 player2” as command, it would become “teleport player1 player”.

All we have to do now is get the first word. Here is where we are going to use string.split.
The first argument it takes is the string we want to split, the second is by what we want to split it (seperator). It will return a table including the splits.

string.split examples
local var = "Hello World"
local splits = string.split(var, " ")

print("First split: " .. splits[1]) --> "First split: Hello"
print("Second split: " .. splits[2]) --> "Second split: World"
local var = "Hello,World"
local splits = string.split(var, ",")

print("First split: " .. splits[1]) --> "First split: Hello"
print("Second split: " .. splits[2]) --> "Second split: World"
local var = "Hello World"
local splits = string.split(var, "ll")

print("First split: " .. splits[1]) --> "First split: He"
print("Second split: " .. splits[2]) --> "Second split: o World"
local splits = string.split(messageWithoutPrefix, " ")

Now, knowing that, we can already get the first word (probably the command) by doing splits[1].

if string.sub(message, 1, #prefix) == prefix then
	--his message starts with our prefix!
	local messageWithoutPrefix = string.sub(message, #prefix + 1, #message)
	local splits = string.split(messageWithoutPrefix, " ")
	local firstWord = splits[1]
end

Great. What we want to do now is to check, if the command he is trying to execute really exists inside of our commands modulescript as function (maybe he made a typo).

if commands[firstWord] then

end

This is checking if there is something called the thing he said as first word inside of our commands modulescript (modulescripts return a table). → Don’t worry, this would not error if it doesn’t exist, it will simply return nil.

Alright, now, what we want to do is to just execute that function with the parameters the player that is trying to execute this command gave us. We already have all the words in his message because of our splits variable. What we don’t want to pass though is the first word, which is the command. So let’s remove that from our splits table, which is the table whose elements we want to pass as parameters to the function.

if commands[firstWord] then
	table.remove(splits, 1)
end

We are almost done now. The only thing we have to do now is to execute the function inside of our commands modulescript.

commands[firstWord](player, table.unpack(splits))

We pass the player who executed this command as first parameter, so later in the module we can check if that player has a high enough rank. Next, we pass all the other words he said after the command, which are probably the arguments. We use table.unpack for that, we could pass the table too, but I like it better like this, so we don’t have to deal with unpacking the words in the modulescript.

table.unpack examples
local t = {
	"firstElement",
	"secondElement",
	"thirdElement"
}

print(table.unpack(t)) --> firstElement secondElement thirdElement
local t = {

"firstElement",

"secondElement",

"thirdElement"

}

local firstVariable, secondVariable, thirdVariable = table.unpack(t)

print("Our first variable is: " .. firstVariable) --> Our first variable is: firstElement

print("Our second variable is: " .. secondVariable) --> Our second variable is: secondElement

print("Our third variable is: " .. thirdVariable) --> Our third variable is: thirdElement

Alright. The serverscript part is done.
Here is the entire script:

local Players = game:GetService("Players")
local commands = require(script.Commands)
local prefix = ":"

Players.PlayerAdded:Connect(function(player)
	player.Chatted:Connect(function(message)
		if string.sub(message, 1, #prefix) == prefix then
			--his message starts with our prefix!
			local messageWithoutPrefix = string.sub(message, #prefix + 1, #message)
			local splits = string.split(messageWithoutPrefix, " ")
			local firstWord = splits[1]
			
			if commands[firstWord] then
				table.remove(splits, 1)
				commands[firstWord](player, table.unpack(splits))
			end
		end
	end)
end)

a
You can now make functions inside of our modulescript and the names will be what the player has to say to execute them. A function that you almost always need is the getPlayerByName function. So even if the player passes something like “pla”, it will still return “Player1”.

local function getPlayerByPlayerName(name)
	if name then
		for i,v in ipairs(Players:GetPlayers()) do
			if string.lower(string.sub(v.Name, 1, #name)) == string.lower(name) then
				return v
			end
		end
	end
end

This is nothing new, I’ve already talked about string.sub (examples above). string.lower is self explanatory. “pLaYeR1” → “player1”.

We will script some commands, you can add onto that. First, I will make a roleForRank table though. You can also use :GetRankInGroup() for this but I like this method better.

local ranksForRole = {
	Guest = 1,
	Mod = 2,
	Admin = 3,
	Owner = 4
}

We will also make a hasRank function, so we don’t have to do it manually everytimes.

local function hasRank(player, needed)
	local rank = ranksForRole[player:GetRoleInGroup(groupId)]
	if rank >= needed then
		return true
	end
end

First, let’s script a shutdown command. We basically want to iterate over every player and kick them with the reason the player has given. First, we want to check if the player’s rank is high enough though.
Because we made that function all we have to do is

function Commands.shutdown(plr, reason)
	if hasRank(plr, 2) then

    end
end

Now we need to get the reason. Remember how we seperated the arguments when firing a function? That means that our spaces are gone. We have to get them back.

local function getReasonFromDots(...)
    local args = {...}
    local str = ""

    for i,v in ipairs(args) do
        if i == 1 then
            str = v
        else
            str = str .. " " .. v
        end
    end

    if str == "" then
        return "No reason given."
    else
        return str
    end
end

We can use this function for it. … basically means every argument passed to it. We make a table out of them so we can loop through them.

reason = getReasonFromDots(...)

If no reason is passed, it will become “No reason given.”
Now all we have to do is kick every player.

function Commands.shutdown(plr, ...)
	if hasRank(plr, 2) then
		local reason = getStringFromDots(...)
		for i,v in ipairs(Players:GetPlayers()) do
			v:Kick("Server shutdown. Reason: " .. reason)
		end
	end
end

We are done!

Now let’s script a to command. Same thing as before, we check the rank.

function Commands.to(player, toPlayerName)
	if hasRank(player, 2) then

    end
end

But now, we have to get the player from the toPlayerName parameter (e.g: :to pla).
We’ve already defined a function that does that for us above. All we have to do is set the CFrame of our mod to the CFrame of the player he wants to go to with an offset of 5 on the z axis (optional).

function Commands.to(player, toPlayerName)
	if hasRank(player, 2) then
		local plrToTeleportTo = getPlayerByPlayerName(toPlayerName)
		if plrToTeleportTo then
			player.Character.HumanoidRootPart.CFrame = plrToTeleportTo.Character.HumanoidRootPart.CFrame * CFrame.new(0, 0, 5)  --you can use CFrame.Angles to rotate the player too so he is looking at him
		end
	end
end

If we have a to command, let’s also script a bring command, shouldn’t be too different, just that now instead of setting our mod’s CFrame to the player’s CFrame, we set the player’s CFrame to our mod’s CFrame.

function Commands.bring(player, plrToBring)
	if hasRank(player, 2) then
		local plrToBring = getPlayerByPlayerName(plrToBring)
		if plrToBring then
			plrToBring.Character.HumanoidRootPart.CFrame = player.Character.HumanoidRootPart.CFrame * CFrame.new(0, 0, 5)
		end
	end	
end

Now, let’s also script a kick command. This should be easier than to or bring.

function Commands.kick(player, plrToKick, reason)
	if hasRank(player, 2) then
        reason = getReasonFromDots(...)
		local plrToKick = getPlayerByPlayerName(plrToKick)
		if plrToKick then
			plrToKick:Kick("You were kicked. Reason: " .. reason)
		end
	end
end

See how much the hasRank and getPlayerByPlayerName functions help us? We’d have to write the same thing again and again if we didn’t make them.

a
Alright, this will be a little bit more complicated.
We have to use string.find. It returns 2 things. StartsAt and endsAt (exactly what we use for string.sub btw).

string.find examples
local var = "lol :bring player"

local beginsAt, endsAt = string.find(var, ":")

print("Begins at: " .. beginsAt) --> --> "Begins at: 5"
print("Ends at: " .. endsAt) --> "Ends at: 5"
local var = "hello guys it's me!"

local beginsAt, endsAt = string.find(var, "guys")

print("Begins at: " .. beginsAt) --> --> "Begins at: 7"
print("Ends at: " .. endsAt) --> "Ends at: 10"
local var = "lolimgonnado:bring plr"

local beginsAt, endsAt = string.find(var, ":")

print("Begins at: " .. beginsAt) --> --> "Begins at: 13"
print("Ends at: " .. endsAt) --> "Ends at: 13"

We will replace our string.sub(message, 1, #prefix) with string.find(message, prefix.
If it returns anything, it means that the prefix was found in the message.

local Players = game:GetService("Players")
local commands = require(script.Commands)
local prefix = ":"

Players.PlayerAdded:Connect(function(player)
	player.Chatted:Connect(function(message)
		if string.find(message, prefix) then
			local startsAt, endsAt = string.find(message, prefix)
			local messageWithoutPrefix = string.sub(message, endsAt + 1, #message)
			--"lol :bring pla" --> "bring pla"
			local splits = string.split(messageWithoutPrefix, " ")
			local command = splits[1]
			
			if commands[command] then
				table.remove(splits, 1)
				commands[command](player, table.unpack(splits))
			end
		end
	end)
end)
45 Likes

This seems awesome, I’ll be sure to try it out.

3 Likes

Here’s a little trick if you want to be able to run multiple commands in one line :wink:

function FindCommand(player, message)
	local adminLevel = CheckAdmin(player) -- Admin Checking Function
	if player ~= nil and message ~= nil and adminLevel > 1 then
		if string.match(message, prefix) then -- Seeing if the prefix is there
			local variousCommands = string.split(message, prefix) -- Splits the command up by the prefix
			table.remove(variousCommands, 1) -- Removes the first argument
			for i,v in ipairs(variousCommands) do
                print(v)
            end
        end
    end
end)

If I said: "I will resetstats for everyone !resetstats all !respawn all "

It would print
resetstats all
respawn all

Just something to keep in mind since most admins provide this :smiley:

8 Likes

I just rewrote the entire tutorial including the scripts. If you have any questions just ask here.

4 Likes

This is fixable with some string pattern:

string.match(message, "^"..prefix)

^ (or anchor) tells to match the pattern only if the prefix starts from the beginning of the string.

1 Like

Ah, love the special character, although the purpose was more to see if the prefix was anywhere in the command. Since we wanted the ability for multi line commands (even after they talk)
For instance

Hey guys, we will start the round now !startround !resetstats all

(Although I probably should have used string.find instead :stuck_out_tongue: )

1 Like

The multiple command idea is great, but it’s way too difficult to make for beginners. Sorry if I didn’t read your reply correctly, I was sleepy at the moment.

1 Like

What did I do wrong? It just won’t work this is my code for the module script.

local module = {}
local function getPlayerByPlayerName(name)
for i,v in ipairs(Players:GetPlayers()) do
if string.lower(string.sub(v.Name, 1, #name)) == string.lower(name) then
return v
end
end
end
local ranksForRole = {
Guest = 1,
Mod = 2,
Admin = 3,
Owner = 254
}
local function hasRank(player, needed)
local rank = ranksForRole[player:GetRoleInGroup(5199605)]
if rank >= needed then
return true
end
end
function Commands.shutdown(plr, reason)
if hasRank(plr, 2) then
reason = reason or “No reason given.”
for i,v in ipairs(Players:GetPlayers()) do
v:Kick("Server shutdown. Reason: " … reason)
end
end
end
function Commands.to(player, toPlayerName)
if hasRank(player, 2) then
local plrToTeleportTo = getPlayerByPlayerName(toPlayerName)
if plrToTeleportTo then
player.Character.HumanoidRootPart.CFrame = plrToTeleportTo.Character.HumanoidRootPart.CFrame * CFrame.new(0, 0, 5) --you can use CFrame.Angles to rotate the player too so he is looking at him
end
end
end
function Commands.bring(player, plrToBring)
if hasRank(player, 2) then
local plrToBring = getPlayerByPlayerName(plrToBring)
if plrToBring then
plrToBring.Character.HumanoidRootPart.CFrame = player.Character.HumanoidRootPart.CFrame * CFrame.new(0, 0, 5)
end
end
end
function Commands.kick(player, plrToKick, reason)
if hasRank(player, 2) then
local plrToKick = getPlayerByPlayerName(plrToKick)
if plrToKick then
plrToKick:Kick("You were kicked. Reason: " … reason)
end
end
end
return module

2 Likes

Instead of naming the module-table “Commands”, you named it “module”. You didn’t define Players. Instead of concatenating with two dots, you did it with three. You used ” for strings instead of ".
This should be the fixed version:

local Commands = {}
local Players = game:GetService("Players")

local function getPlayerByPlayerName(name)
	for i,v in ipairs(Players:GetPlayers()) do
		if string.lower(string.sub(v.Name, 1, #name)) == string.lower(name) then
			return v
		end
	end
end

local ranksForRole = {
	Guest = 1,
	Mod = 2,
	Admin = 3,
	Owner = 254
}
local function hasRank(player, needed)
	local rank = ranksForRole[player:GetRoleInGroup(5199605)]
	if rank >= needed then
		return true
	end
end

function Commands.shutdown(plr, reason)
	if hasRank(plr, 2) then
		reason = reason or "No reason given."
		for i,v in ipairs(Players:GetPlayers()) do
			v:Kick("Server shutdown. Reason: " .. reason)
		end
	end
end

function Commands.to(player, toPlayerName)
	if hasRank(player, 2) then
		local plrToTeleportTo = getPlayerByPlayerName(toPlayerName)
		if plrToTeleportTo then
			player.Character.HumanoidRootPart.CFrame = plrToTeleportTo.Character.HumanoidRootPart.CFrame * CFrame.new(0, 0, 5) --you can use CFrame.Angles to rotate the player too so he is looking at him
		end
	end
end

function Commands.bring(player, plrToBring)
	if hasRank(player, 2) then
		local plrToBring = getPlayerByPlayerName(plrToBring)
		if plrToBring then
			plrToBring.Character.HumanoidRootPart.CFrame = player.Character.HumanoidRootPart.CFrame * CFrame.new(0, 0, 5)
		end
	end
end

function Commands.kick(player, plrToKick, reason)
	if hasRank(player, 2) then
		local plrToKick = getPlayerByPlayerName(plrToKick)
		if plrToKick then
			plrToKick:Kick("You were kicked. Reason: " .. reason)
		end
	end
end

return Commands

Just message me if there is something still not working.

2 Likes

Okay thanks so much I will try it later!

1 Like

I am getting the following error

18:29:33.425 ServerScriptService.Admin.Commands:20: attempt to compare number and nil - Server - Commands:20

1 Like

That means that you have a role in the group that you haven’t included in the table.
Replace the hasRank function with this:

local function hasRank(player, needed)
	print(player:GetRoleInGroup(5199605))
	local rank = ranksForRole[player:GetRoleInGroup(5199605)]
	if rank >= needed then
		return true
	end
end

It should print your role in the output so you know what to add to the table.

2 Likes

Okay I’ll try it maybe in like a hour

image

This is all its saying yet it does nothing

EDIT: OH wait so I add the role to it?

EDIT Again: Wait it has a - in it tho sooo how to I do that? I tested with another group it worked perfect!

1 Like

Also, this may sound stupid but how do I turn this into a command

 	Function = function(speaker, args)
		local plr = args[1]
		if plr.Character then
			for i,v in pairs(game.ServerStorage.BuildingTools:GetChildren()) do
				v:Clone().Parent = plr.Backpack
			end
		end
	end;
1 Like

See how it printed “CO-Owner”? That’s the role you have to add to your ranksForRole table.
It has a - in it, so you have to use [“”]

local ranksForRole = {
	Guest = 1,
	Mod = 2,
	Admin = 3,
    ["CO-Owner"] = 4,
	Owner = 254
}

I’d do it like this:

function Commands.giveBuildingTools(plr, to)
	if hasRank(plr, 2) then --Mods and above
		local plrToGiveToolsTo
		
		if string.lower(to) == "me" then
			plrToGiveToolsTo = plr
		else
			plrToGiveToolsTo = getPlayerByPlayerName(to)
		end
		
		if plrToGiveToolsTo then
			for i,v in ipairs(game:GetService("ServerStorage").BuildingTools:GetChildren()) do
				v:Clone().Parent = plrToGiveToolsTo.Backpack
			end
		end
	end
end

It would look a little bit better if you defined BuildingsTool at the top of your module so you could just do for i,v in ipairs(BuildingTools:GetChildren()), but it works like that too.

Thanks so much sorry I am a newer ish coder so this helped a lot!

I did get 1 error though with the givebuildingtools line

1 Like

That’s because you said “:giveBuildingTools” without any parameters like “:giveBuildingTools player1” or “:giveBuildingTools me”, but that shouldn’t error, I forgot to add the case of that argument being nil.
Replace the function with this:

function Commands.giveBuildingTools(plr, to)
	if hasRank(plr, 2) then --Mods and above
		local plrToGiveToolsTo

		if to and string.lower(to) == "me" then
			plrToGiveToolsTo = plr
		else
			plrToGiveToolsTo = getPlayerByPlayerName(to)
		end

		if plrToGiveToolsTo then
			for i,v in ipairs(game:GetService("ServerStorage").BuildingTools:GetChildren()) do
				v:Clone().Parent = plrToGiveToolsTo.Backpack
			end
		end
	end
end

And to everyone reading this (including you), replace the getPlayerByPlayerName function with this: (I edited the tutorial)

local function getPlayerByPlayerName(name)
	if name then --name could be nil (player forgot to include the parameter)
		for i,v in ipairs(Players:GetPlayers()) do
			if string.lower(string.sub(v.Name, 1, #name)) == string.lower(name) then
				return v
			end
		end
	end
end

Thanks I think that’s all I wanted to ask :smile:

1 Like