Help making a temp ban system

I recommend adding this to the script then:

function UnbanUsers()
    while true do
        wait(1800)
        for _,v in pairs(banned) do
             table.remove(banned, v)
        end
    end
end

local unbancoro = coroutine.create(UnbanUsers)
coroutine.resume(unbancoro)

I’ll add a patch for all servers later. Sorry for the miss reading of your post.

Literally just use a datastore. Doing this will NOT persist across servers and will not allow you to add players without updating the actual script.

First of all, server script or local script, the local keyword has the same behavior.
local variable read/write usually tends to be a lot faster than for globals, regardless of the use.

Highlighting this part:

Thus, if a local variable is registered top-level, it’s “local” to the whole program.

So, in reality, using global variables when they are not needed is a bad practice, period. Opinions do not factor in this case.

Quoting the lua basics (written by the creator of lua!)
SIyII

The “sin” variable is being localized and used to access it faster than a global value (math.sin). It is a micro-optimization, but still.

I actually made a system for this yesterday, it bans players from a single server when they die such that they need to rejoin a different server to continue playing.

local Players = game:GetService("Players")

local KickedPlayers = {}

Players.PlayerAdded:Connect(function(Player)
	Player.CharacterAdded:Connect(function(Character)
		local Humanoid = Character:WaitForChild("Humanoid")
		Humanoid.Died:Connect(function()
			table.insert(KickedPlayers, Player.UserId)
			Player:Kick("You have died!")
		end)
	end)
	
	if table.find(KickedPlayers, Player.UserId) then
		Player:Kick("You have already died in this server!")
	end
end)

Logic should be relatively simple to follow, I used the “UserId” property of the player instance instead of the player instance itself as the latter would change if the player rejoined the same server instance, the former will never change.

Here’s a similar script to the previous one but it incorporates DataStores, such that if a player who has previously died attempts to rejoin any server they will be kicked.

local dataModel = game
local dataStoreService = dataModel:GetService("DataStoreService")
local dataStore = dataStoreService:GetDataStore("DataStore")
local players = dataModel:GetService("Players")

local kickedPlayers = {}
local protectedCall = pcall

local function onPlayerAdded(player)
	local function onCharacterAdded(character)
		local function onHumanoidDied()
			table.insert(kickedPlayers, player.UserId)
			
			local success, result = protectedCall(function()
				dataStore:SetAsync(player.UserId, true) --"true" indicates dead.
			end)
			
			if success then
				print(result)
			else
				warn(result)
			end
			
			player:Kick("You have died!")
		end
		
		local humanoid = character:WaitForChild("Humanoid")
		humanoid.Died:Connect(onHumanoidDied)
	end
	
	if table.find(kickedPlayers, player.UserId) then
		player:Kick("You have already died in this server!")
	end

	local success, result = protectedCall(function()
		return dataStore:GetAsync(player.UserId)
	end)
	
	if success then
		if result then
			player:Kick("You have already died in another server!")
		end
	else
		warn(result)
	end
end

players.PlayerAdded:Connect(onPlayerAdded)

Thank you so much for the response! From what I can see from the script it seems to be a permanent ban.
Is there a way to change this to a temp ban? :slight_smile:

local banDataStore = game:GetService("DataStoreService"):GetDataStore("BannedPlayers")
local players = game:GetService("Players")

function GetPlayerIDFromName(playerName : string)
    local success, playerID = pcall(function()
        return players:GetUserIdFromNameAsync(playerName)
    end)

    if not success then
        print("Failed to get user.")
        return
    end
    return playerID
end

function BanPlayer(playerName : string, time : number)
    local playerID = GetPlayerIDFromName(playerName)
    if not playerID then return end

    local success = pcall(function()
        if time > 0 then
            banDataStore:SetAsync(playerID, time*60+os.time()) -- timed ban
        else
            banDataStore:SetAsync(playerID,nil) -- nil means perm ban.
        end
    end)

    if success then
        if time > 0 then
            print(string.format("%s has been banned for %s minutes.",playerName,time))
        else
            print(string.format("%s has been banned permanently.",playerName))
        end
    end

    local player = players:FindFirstChild(playerName)
    if player and success then
        if time > 0 then
            player:Kick(string.format("You have been banned for %s minutes.",time))
        else
            player:Kick(string.format("You have been banned permanently."))
        end
    end
end

function UnBanPlayer(playerName : string)
    local playerID = GetPlayerIDFromName(playerName)
    if not playerID then return end

    local success = pcall(function()
        banDataStore:SetAsync(playerID,0) -- 0 means not banned.
    end)
end

function CheckBan(player : Player)
    local success, time = pcall(function()
        return banDataStore:GetAsync(player.UserId)
    end)
    if not success then return end

    if time > 0 then
        player:Kick(string.format("You have been banned. Time Remaining: %s minutes.",math.round((time-os.time())/60)))
    elseif time == nil then
        player:Kick(string.format("You have been banned permanently."))
    end
end

Here you go I have made a simple script it is not the most perfect script but it should satisfy your needs.
I am not going to make the command part for that you should do some research and try to implement it yourself but I will give you some resources which you can get started on.

Player.PlayerChatted API-Reference
Lua String Library

These 2 should do it. I highly recommend you fiddle around the first try to figure it out. If you manage to make it by yourself great you are on your way to becoming a developer if not that’s also fine getting used to the language may take some time. I will also leave a link to a guide.

How to make basic admin commands

Enjoy the process.

Yes, no problem, that’s a relatively quick change.

local dataModel = game
local dataStoreService = dataModel:GetService("DataStoreService")
local dataStore = dataStoreService:GetDataStore("DataStore")
local players = dataModel:GetService("Players")

local kickedPlayers = {}
local protectedCall = pcall

local function onPlayerAdded(player)
	local function onCharacterAdded(character)
		local function onHumanoidDied()
			table.insert(kickedPlayers, player.UserId)

			local success, result = protectedCall(function()
				dataStore:SetAsync(player.UserId, tick()) --"true" indicates dead.
			end)

			if success then
				print(result)
			else
				warn(result)
			end

			player:Kick("You have died!")
		end

		local humanoid = character:WaitForChild("Humanoid")
		humanoid.Died:Connect(onHumanoidDied)
	end

	if table.find(kickedPlayers, player.UserId) then
		player:Kick("You have already died in this server!")
	end

	local success, result = protectedCall(function()
		return dataStore:GetAsync(player.UserId)
	end)

	if success then
		if result then
			if tick() - result <= 86400 then --1 day.
				player:Kick("You have already died in another server! Please rejoin in "..(86400)-(tick() - result).." seconds.")
			else
				local success, result = protectedCall(function()
					return dataStore:RemoveAsync(player.UserId)
				end)
			end
		end
	else
		warn(result)
	end
end

players.PlayerAdded:Connect(onPlayerAdded)

This example uses 1 day bans (timeouts).

Hmm… For some reason this isn’t working. I’ve been looking through the code for a potential cause for it to not be working. However, I can’t find why. The player doesn’t get kicked on death. I also tried it with a remote event and it didn’t work. There’s nothing in the output and Studio Access to API Services is enabled.

My bad, I just forgot to hookup the “CharacterAdded” event to the “onCharacterAdded” function.

player.CharacterAdded:Connect(onCharacterAdded)

Here’s the full script with that change implemented (it works now).

local dataModel = game
local dataStoreService = dataModel:GetService("DataStoreService")
local dataStore = dataStoreService:GetDataStore("DataStore")
local players = dataModel:GetService("Players")

local kickedPlayers = {}
local protectedCall = pcall

local function onPlayerAdded(player)
	local function onCharacterAdded(character)
		local function onHumanoidDied()
			table.insert(kickedPlayers, player.UserId)

			local success, result = protectedCall(function()
				dataStore:SetAsync(player.UserId, tick()) --"true" indicates dead.
			end)

			if success then
				print(result)
			else
				warn(result)
			end

			player:Kick("You have died!")
		end

		local humanoid = character:WaitForChild("Humanoid")
		humanoid.Died:Connect(onHumanoidDied)
	end
	
	player.CharacterAdded:Connect(onCharacterAdded)
	
	if table.find(kickedPlayers, player.UserId) then
		player:Kick("You have already died in this server!")
	end

	local success, result = protectedCall(function()
		return dataStore:GetAsync(player.UserId)
	end)

	if success then
		if result then
			if tick() - result <= 86400 then --1 day.
				player:Kick("You have already died in another server! Please rejoin in "..math.round((86400)-(tick() - result)).." seconds.")
			else
				local success, result = protectedCall(function()
					return dataStore:RemoveAsync(player.UserId)
				end)
			end
		end
	else
		warn(result)
	end
end

players.PlayerAdded:Connect(onPlayerAdded)

Wonderful! Thank you so much, is there a way to unban myself? (for the purpose of testing :joy:)

local dataModel = game
local dataStoreService = dataModel:GetService("DataStoreService")
local dataStore = dataStoreService:GetDataStore("DataStore")
local players = dataModel:GetService("Players")

local whiteListed = {1, 2, 3} --Add IDs of whitelisted players here.
local kickedPlayers = {}
local protectedCall = pcall

local function onPlayerAdded(player)
	local function onCharacterAdded(character)
		local function onHumanoidDied()			
			table.insert(kickedPlayers, player.UserId)

			local success, result = protectedCall(function()
				dataStore:SetAsync(player.UserId, tick()) --"tick" records time.
			end)

			if success then
				print(result)
			else
				warn(result)
			end

			player:Kick("You have died!")
		end

		local humanoid = character:WaitForChild("Humanoid")
		humanoid.Died:Connect(onHumanoidDied)
	end
	player.CharacterAdded:Connect(onCharacterAdded)
	
	if table.find(whiteListed, player.UserId) then
		if table.find(kickedPlayers, player.UserId) then
			table.remove(kickedPlayers, table.find(kickedPlayers, player.UserId))
		end
		
		local success, result = protectedCall(function()
			return dataStore:RemoveAsync(player.UserId)
		end)

		if success then
			print(result)
		else
			warn(result)
		end
	end
	
	if table.find(kickedPlayers, player.UserId) then
		player:Kick("You have already died in this server!")
	end

	local success, result = protectedCall(function()
		return dataStore:GetAsync(player.UserId)
	end)

	if success then
		if result then
			if tick() - result <= 86400 then --1 day.
				player:Kick("You have already died in another server! Please rejoin in "..math.round((86400)-(tick() - result)).." seconds.")
			else
				local success, result = protectedCall(function()
					return dataStore:RemoveAsync(player.UserId)
				end)
			end
		end
	else
		warn(result)
	end
end

players.PlayerAdded:Connect(onPlayerAdded)

Here you go, I’ve added a whitelist to the top of the script.

local dataModel = game
local dataStoreService = dataModel:GetService("DataStoreService")
local dataStore = dataStoreService:GetDataStore("DataStore")
local players = dataModel:GetService("Players")

local whiteListed = {1, 2, 3} --Add IDs of whitelisted players here.
local kickedPlayers = {}
local protectedCall = pcall

local function onPlayerAdded(player)
	local function onCharacterAdded(character)
		local function onHumanoidDied()			
			table.insert(kickedPlayers, player.UserId)

			local success, result = protectedCall(function()
				dataStore:SetAsync(player.UserId, tick()) --"tick" records time.
			end)

			if success then
				print(result)
			else
				warn(result)
			end

			player:Kick("You have died!")
		end

		local humanoid = character:WaitForChild("Humanoid")
		humanoid.Died:Connect(onHumanoidDied)
	end
	player.CharacterAdded:Connect(onCharacterAdded)
	
	if table.find(kickedPlayers, player.UserId) then
		if not table.find(whiteListed, player.UserId) then
			player:Kick("You have already died in this server!")
		end
	end

	local success, result = protectedCall(function()
		return dataStore:GetAsync(player.UserId)
	end)

	if success then
		if result then
			if tick() - result <= 86400 then --1 day.
				if not table.find(whiteListed, player.UserId) then
					player:Kick("You have already died in another server! Please rejoin in "..math.round((86400)-(tick() - result)).." seconds.")
				end
			else
				local success, result = protectedCall(function()
					return dataStore:RemoveAsync(player.UserId)
				end)
			end
		end
	else
		warn(result)
	end
end

players.PlayerAdded:Connect(onPlayerAdded)

Similar script with a slightly different implementation (I prefer this approach).

table.method is O(n) because each time we run a function or insert anything it shifts the memory over.

Using a Hash would be the smartest thing to do. With that you can use the same logic you just displayed.

Fortunately we’re not working with arrays which contain thousands of entries so the difference is negligible.

I would disagree, front page games get heavy player counts. This system should be built to scale. The logic that we shouldn’t write code effectively the first time is what separates good developers from bad ones.

The tables are unique and restricted to a single server script (it isn’t a table shared by all active server instances).

I’ve performed bench-marking tests on indexing dictionaries versus the table library functions and even with thousands and thousands of entries the difference is minuscule (a fraction of a second at most).

Alright, last thing. (thanks for sticking around with me)

How can I make this activate when a RemoteEvent is fired? Rather than when the player dies.

local rem = game:GetService("ReplicatedStorage"):WaitForChild("RemoteEvent")
rem.OnServerEvent:Connect(onPlayerAdded)

The code you presented does not need to have any of the functionality aside from the whitelist information. I cant fathom why there is a kicked table as that data is already stored in a data store. It would be logical to use an array if there was a problem setting data store keys, but because each key is unique to the player, that logic doesn’t make sense.

Using the data store you already set the ban time, you can use a queue to check the data store for the information if requests are the issue. But because not many people will be banned, roblox default request limit wont cause any problems for checking the time. Therefore, we can completely remove the kicked table. Not to mention by using what your using you turned the already O(n) into O(n^2) which is exponentially slower.

As for being off topic, this conversation would help the OP understand what not to do. You can argue otherwise, however, its important to understand the difference between good and bad code. Some folks go a while without understanding the difference thus interfering with their development careers.

local dataModel = game
local dataStoreService = dataModel:GetService("DataStoreService")
local dataStore = dataStoreService:GetDataStore("DataStore")
local players = dataModel:GetService("Players")

local whiteListed = {1, 2, 3} --Add IDs of whitelisted players here.
local protectedCall = pcall

local function onPlayerAdded(player)
	local function onCharacterAdded(character)
		local function onHumanoidDied()			
			local success, result = protectedCall(function()
				dataStore:SetAsync(player.UserId, tick()) --"tick" records time.
			end)

			if success then
				print(result)
			else
				warn(result)
			end

			task.wait(players.RespawnTime)
			player:Kick("You have died!")
		end

		local humanoid = character:WaitForChild("Humanoid")
		humanoid.Died:Connect(onHumanoidDied)
	end
	player.CharacterAdded:Connect(onCharacterAdded)
	local success, result = protectedCall(function()
		return dataStore:GetAsync(player.UserId)
	end)

	if success then
		if result then
			if tick() - result <= 86400 then --1 day.
				if not table.find(whiteListed, player.UserId) then
					player:Kick("You have already died in another server! Please rejoin in "..math.round((86400)-(tick() - result)).." seconds.")
				end
			else
				local success, result = protectedCall(function()
					return dataStore:RemoveAsync(player.UserId)
				end)
			end
		end
	else
		warn(result)
	end
end

players.PlayerAdded:Connect(onPlayerAdded)

Just posting it here as well.

2 Likes