Ban System/Custom Ban Command

Now before we start, this is my first ever post on the Community Tutorials category, and my first time making a tutorial as well, so sorry if it doesn’t make much sense, haha

Now that you’ve (probably) read that, don’t expect this to be much of a versatile tutorial.

Today we’re going to be making your own simple ban system using Roblox’s built-in service DataStoreService. So without further ado, let’s get started.

In this tutorial we’ll be making a custom ban command/ban system, of course. With the reason being saved, so that the user sees it every time they attempt to enter the game while still banned.

I’m going to be making a server/general/regular/whatever script and putting it in ServerScriptService. I’ll then be naming it jojo for better organization.

We’ll start the script off by indexing/getting the DataStoreService and the Players service, which should be a fast and easy process. After, we’ll want to get our “BanStore”.

-------------

--- INDEX ---

-------------

--// SERVICES

local DDS = game:GetService("DataStoreService");

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

--// DECLARABLES/VARIABLES

local BanStore = DDS:GetDataStore("BanStore", 2) --scope is optional
"What is the number next to the "BanStore" string?

That is what we call a “Scope”, think of it as like a certain folder. While scopes are very optional, I’d prefer using them for better organization and usage in the feature, and for less confusion of the script.

Setting up the functions

Now that we have the DataStore and the Players service, and we’ve created a new DataStore, we’ll then have to set up the functions for the ban before the main script, for better organization.

We’ll first want to start up with a function that sets the ban for the player. We’ll make sure to wrap this in a pcall in case we run into any errors that break the entire script, like the player not actually existing and all that good stuff.

local function SetBan(Player, Reason, Duration)
    local Success, Error = pcall(function() --Protect the call in case the player specified doesn't exist, the player isn't found, or stuff like that
        BanStore:SetAsync(tostring(Player.UserId), {BanStart = os.time(), BanDuration = (Duration * secondsInADay), BanReason = Reason});
    end)
     Player:Kick(Reason);
     if not Success then
         warn("Not successful."); --for debugging
     end
end

It is a good practice to use pcalls when it comes to situations like these, just in case you run into a script-breaking error.

Now that that’s done, I think something’s missing. Yeah, something’s not right. Of course it’s obvious, the player isn’t banned - they can still join, and that is because we haven’t set up a function that searches for any key entry/data of the player in the BanStore when they join. So let’s set that up, shall we?

local function GetBan(Player)
    local Success, Result = pcall(function()
        return BanStore:GetAsync(tostring(Player.UserId, "TempBan");
    end)

    if Success then --see if datastore request was successful
        if Result then --check if any data for the player was found
           if Result.BanStart + Result.BanDuration < os.time() then --check if temp ban duration is over
              return true;
          else
             return false;
          end
       end
    end
end

All right, that’s out of the way, we’re making some good time. And it only took two functions, awesome.

Now let’s initialize the command.

INTIALIZATION

Of course anyone experienced could easily guess it. We’ll be using the .Chatted event with a selected prefix which you can change to anything you want.

----------------------

--- INITIALIZATION ---

----------------------

Players.PlayerAdded:Connect(function(plr)

      plr.Chatted:Connect(function(message)

          --empty

     end)

end)

But wait! I think you forgot something. Yeah, that’s right.

We wouldn’t really want to have every player of the game have access to the command, would we? So we should restrict this to the selected. But since this is just a tutorial, I won’t really be going over that, haha.

Players.PlayerAdded:Connect(function(plr)
	if plr.UserId == game.CreatorId then --restrict this command only to the owner
		
	end
end)

Easy, we’ve restricted the command to only a few lines and words, now let’s get to the main part which you probably don’t know how to do if you aren’t experienced with string patterns and all that.

Players.PlayerAdded:Connect(function(plr)
	if plr.UserId == game.CreatorId then --restrict this command only to the owner
		plr.Chatted:Connect(function(message)
			if string.sub(message, 1, #"/ban"):lower() == "/ban" then
				local args = string.split(message, " "); --split string message via space, and iterate them into a table
				local numbers = "%d" or "%d%d"; -- string pattern to look for duration/numbers
				local duration = string.sub(message, string.find(message, numbers));
				local playerToBan = Players:FindFirstChild(args[2]); --search for player via specified playername in command
				local reason = string.sub(message, string.len("/ban " .. args[2] .. " " .. duration) + 1); --search for reason
			end
		end)
	end
end)

More info about string patterns can be found here.

Great! Now you have access to each important element of the message. Now all we have to do is check if all these even exist.

Players.PlayerAdded:Connect(function(plr)
	if plr.UserId == game.CreatorId then --restrict this command only to the owner
		plr.Chatted:Connect(function(message)
			if string.sub(message, 1, #"/ban"):lower() == "/ban" then
				local args = string.split(message, " "); --split string message via space, and iterate them into a table
				local numbers = "%d" or "%d%d"; -- string pattern to look for duration/numbers
				local duration = string.sub(message, string.find(message, numbers));
				local playerToBan = Players:FindFirstChild(args[2]); --search for player via specified playername in command
				local reason = string.sub(message, string.len("/ban " .. args[2] .. " " .. duration) + 1); --search for reason
				if playerToBan then
					if duration then
						if reason then
							SetBan(playerToBan, reason, duration);
							print("Player has been kicked!");
						end
					end
				end
			end
		end)
	end
end)

Now we are done with the chat command, but you probably forgot one thing - that’s right. We forgot to add in GetBan. We’ll be using that before the owner if statement, since, it’s not like the owner can get banned or have any entry data, right? haha.

local IsBanned = GetBan(plr);

if IsBanned ~= nil then
	plr:Kick(IsBanned);
else
	print("Player's ban has been lifted.");
end

And now we are done. Just in a few lines. (Notify me if I missed something or got something wrong).

v Check out the full code below v

Full Code
-------------
--- INDEX ---
-------------

--// SERVICES

local DDS = game:GetService("DataStoreService");
local Players = game:GetService("Players");

--// DECLARABLES/VARIABLES

local BanStore = DDS:GetDataStore("BanStore", 2) --scope is optional
local secondsInADay = 86400

-----------------
--- FUNCTIONS ---
-----------------

local function SetBan(Player, Reason, Duration)
	local Success, Error = pcall(function() --Protect the call in case the player doesn't exist or the player wasn't found or anything that may cause an error that breaks the whole script;
		BanStore:SetAsync(tostring(Player.UserId), {BanStart = os.time(), BanDuration = (Duration * secondsInADay), BanReason = Reason});
	end)
	Player:Kick(Reason);
	if not Success then --for debugging
		warn("Not successful.")
	end
end

local function GetBan(Player)
	local Success, Result = pcall(function()
		return BanStore:GetAsync(tostring(Player.UserId), "TempBan");
	end)

	if Success then --See if DataStore request was successful or not in order to prevent running into any errors
		if Result then --see if ban data exists for the specified player or not
			if Result.BanStart + Result.BanDuration > os.time() then --see if ban duration has passed
				return nil;
			else
				return Result.BanReason;
			end
		end
	end
end

----------------------
--- INITIALIZATION ---
----------------------

Players.PlayerAdded:Connect(function(plr)
	local IsBanned = GetBan(plr);

	if IsBanned ~= nil then
		plr:Kick(IsBanned);
	else
		print("Player's ban has been lifted.");
	end
	
	if plr.UserId == game.CreatorId then --restrict this command only to the owner
		plr.Chatted:Connect(function(message)
			if string.sub(message, 1, #"/ban"):lower() == "/ban" then
				local args = string.split(message, " "); --split string message via space, and iterate them into a table
				local numbers = "%d" or "%d%d"; -- string pattern to look for duration/numbers
				local duration = string.sub(message, string.find(message, numbers));
				local playerToBan = Players:FindFirstChild(args[2]); --search for player via specified playername in command
				local reason = string.sub(message, string.len("/ban " .. args[2] .. " " .. duration) + 1); --search for reason
				if playerToBan then
					if duration then
						if reason then
							SetBan(playerToBan, reason, duration);
							print("Player has been banned!");
						end
					end
				end
			end
		end)
	end
end)

Now, don’t abuse your power!

I’d also like to remind you that duration is in days, not seconds, in case anyone misunderstands.

EDIT #1: Before anyone tells me it’s not working, when testing it on Roblox Studio it wouldn’t really work. Comment out the part where it checks if the player’s ID is the same as the game creator’s ID, and it should work upon testing.

51 Likes

Wow, useful tutorial, only thing I would advise you not to do is create huge if statements, try to use the and / or statements to keep the size of your scripts down a bit.

6 Likes

you can shorten it to

if Success and Result then
 --Something 
end

it check if it success and if it does available at the same time

7 Likes

and this to

if playerToBan and duration and reason then
  --code
end

it will check three thing without doing that huge if statement

6 Likes

How would I convert the duration variable to days in real time instead of UNIX time? I’m making a gui and having the time a player is banned for would be nice.

2 Likes

os.time() is in real time though, it uses UTC unlike tick() which is in unix

3 Likes

I meant something easily readable like

“You have been banned for 3 days” or something

2 Likes

Use math.abs to determine the remaining days between the start and the end.

2 Likes

I separated it so that it could print within SUccess and not success and result

2 Likes

There are plenty of time formatting modules that you can find in #resources:community-resources - just do some research by examining the source code etc. to figure out how to craft your own.

2 Likes

Your getting the players userid so you will use tonumber

2 Likes

Not actually, the player’s userid is already an int/number, and we’re converting it into a string because that’s what we’ll be using for the name, and what we’re searching for any key entry/data for the player upon joining.

3 Likes

I’m making a UI based off of this, by the way! I need to do a bit more polishing first.

There’s two things wrong with this that my UI fixes:

  1. You can’t unban a user without using “/ban name 0 a” and it’s pretty weird.
  2. You put the < sign the wrong way here

My UI also adds a warning message after your ban is over :smile:

3 Likes

Yeah it’s correct. It’s supposed to be < instead of >. Kind of put it in the wrong way with my half-awake self. I’ll be working on an unban soon.

3 Likes

Good tutorial. I had a admin system and I was trying to make a ban sytem and this tutorial made it easier and better. Thanks!

3 Likes

Glad to see I was actually able to help, awesome. (You’re welcome)

Feel free to PM me when you have any inquiries, questions about anything, and all that good stuff. I try to be as active as I can on the DevForums, and I’m looking forward to making some friends on it as well.

4 Likes

I’ve uploaded the gui on a seperate post!

7 Likes

I keep getting this weird error?

ServerScriptService.Script:63: invalid argument #2 to ‘sub’ (number expected, got nil)

2 Likes

What is the format for the command? I cant seem to get the command right

1 Like

You should change the # thing with 4.

/ban

1 Like