Simple Arrest System in 10 minutes

You can see the original video here:

Roblox Arrest in 10 Minutes - YouTube

After lots of requests, I’ve created a tutorial for a simple arrest system which you can make yourself!

It seems as though people don’t have the time and effort to sit and listen to hour-long videos explaining each little detail of the system, so this tutorial aims to be short and snappy, getting all the key points across.

This is my first tutorial, so please be gentle :laughing: although I do plan on making more tutorials in the future.

Thanks!
-Tom :slight_smile:

Tutorial here

This tutorial is mainly a transcript, to better understand what’s going on I suggest watching the 10-minute video where all this makes a lot more sense :stuck_out_tongue:

The key thing to remember when scripting is the client-server model that Roblox uses – where the server controls all the logic and the client does the “heavy-lifting” (to a reasonable extent). So as scripters, we need to make sure that the server has complete control over the arrest system in any game.

Now remembering that any RemoteEvents and RemoteFunctions that we have are potential weaknesses in our security, it’s best to tie these down to when we really need them and only accept connections when logically connections would be made.

This way, we can stop hackers who don’t have access to the handcuff tools from telling our server to arrest someone. I’ve always found that the easiest way to manage these connections is through a dictionary, where the index is the name of a police officer. That way we can connect and disconnect these RemoteEvents whenever the player equips their handcuffs because players can’t arrest people without the handcuffs tool equipped.

local HandcuffConnections = {}

function ToolEquipped(Player, Tool)
	HandcuffConnections[Player.Name] = ArrestRemotes.HandcuffStart.OnServerEvent:Connect(function(Officer, Victim)
		ArrestSanity(Officer, Victim)
	end)
end

function ToolRemoved(Player, Tool)
	HandcuffConnections[Player.Name]:Disconnect()
	HandcuffConnections[Player.Name] = nil
end

It’s important, not only for logic reasons but for memory reasons, that we disconnect connections when we don’t need them. In case your game has a hefty number of players, we don’t want to have 100 connections listening to 100 officers all the time so it’s best to limit it where we can.

As you can see here, I’m using a Module Script which might seem scary but it’s really not. This is an example of shared data, where it’s in ReplicatedStorage and both the server and the client can access it while the client doesn’t have the ability to change anything. Here we can set our global variables instead of changing them in multiple places.

local module = {}

local MaxCuffDistance = 30 -- In studs
local MaxArrestTime = 5 -- In minutes

function module:GetMaxCuffDistance()
     return MaxCuffDistance
end

function module:GetMaxArrestTime()
    return MaxArrestTime
end

As you can see here, I’m using a Module Script which might seem scary but it’s really not. This is an example of shared data, where it’s in ReplicatedStorage and both the server and the client can access it while the client doesn’t have the ability to change anything. Here we can set our global variables instead of changing them in multiple places.

Let’s think about what we actually want to happen, a police officer clicks on a player and they get cuffed. The police officer then decides what he wants to do with the caught criminal, whether it be to send them to prison or let them go. Although it’s kinda useless because we’ll run these checks on the server, it’s a good idea to minimise network traffic as much as possible for people who are playing legit.

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

local LocalPlayer = Players.LocalPlayer

local MaxCuffDistance = require(ReplicatedStorage.Modules.MaxCuffDistance)

script.Parent.Activated:Connect(function()
	local Target = LocalPlayer:GetMouse().Target
	if Target then
		local Victim = Players:GetPlayerFromCharacter(Target.Parent)
		if Victim then
			if LocalPlayer.Character and Victim.Character then
				if (LocalPlayer.Character.Head.Position - Victim.Character.Head.Position).magnitude <= MaxCuffDistance then
					ReplicatedStorage.Remotes.Arrest.HandcuffStart:FireServer(Victim)
				end
			end
		end
	end
end)

Programming UI, in my opinion, is one of the trickiest things to do. Managing what the players can and can’t see at any moment can be quite a hassle but I think I’ve managed to figure out the best
way to do it.

Simply having an ObjectValue in the UI called something like “OpenUI” can help us track what we want the player to see. Whenever that value is changed, simply display the UI that was set. Again, you also have to think about the back button, exit buttons and all options to give players that mean they’re not stuck infinitely in this UI.

You’ll need to coordinate heavily with your UI designer, or if you’re doing it yourself you really need to think about how you’re going to program the UI when you’re making it as it can solve a lot of problems before they arise.

local ReplicatedStorage = game:GetService("ReplicatedStorage")

local MaxArrestTime = require(ReplicatedStorage.Modules.MaxArrestTime)

local UI = script.Parent
local OpenUI = UI.OpenUI
local SignOn = UI.SignOn
local Initial = SignOn.Initial
local Arrest = SignOn.Arrest
local Global = SignOn.Global

OpenUI.Value = Initial

function ToggleUI(Folder, Boolean)
	for _, Descendant in pairs (Folder:GetDescendants()) do
		if Descendant:IsA("GuiObject") then
			Descendant.Visible = Boolean
		end
	end
end

OpenUI.Changed:Connect(function(NewValue)
	for _, Folder in pairs (SignOn:GetChildren()) do
		if Folder:IsA("Folder") and Folder.Name ~= "Global" and Folder ~= NewValue then
			ToggleUI(Folder, false)
		end
	end
	
	ToggleUI(NewValue, true)
end)

Initial.Detain.MouseButton1Click:Connect(function()
	OpenUI.Value = Arrest
end)

Global.Close.MouseButton1Click:Connect(function()
	ReplicatedStorage.Remotes.Arrest.Free:FireServer(UI.Victim.Value)
	UI:Destroy()
end)

Arrest.Arrest.MouseButton1Click:Connect(function()
	local Length = tonumber(Arrest.Length.Text)
	if Length then
		if Length > MaxArrestTime then
			Global.ErrorMessage.Text = "The maximum time to arrest someone is " .. tostring(MaxArrestTime) .. " minutes."
			return
		end
	else
		Global.ErrorMessage.Text = "The specified length must be a number."
		return
	end
	
	local Reason = Arrest.Reason.Text
	if Reason == "" then
		Global.ErrorMessage.Text = "Please enter a reason for the arrest."
		return
	end
	
	ReplicatedStorage.Remotes.Arrest.Arrest:FireServer(Length, Reason)
	UI:Destroy()
end)

Every good arrest needs a reason, and of course the sentence they’ll be in jail. It’s really important here to implement some type-checks to make sure everything’s dandy. It’s also a really good idea to implement some sort of feedback system, so if something errors you actually tell the player why it errored. Pushing this information to the server obviously deals with some more security issues and spoofing information to we’ll implement similar sanity checks.

Here you can see that we use the module script mentioned before. Instead of declaring the same variable twice, we can just look at what we set it to. This way, when we want to change it, we only have to change it in the module script and not in all these other scripts.

Now it’s time to set up some gameplay events. Once we’ve validated that the police officer has detained the criminal, we need to copy our UI into the police officer ready for him to act and judge, jury and executioner.

function Arrest(Officer, Victim)
	local OfficerHumanoid = Officer.Character.Humanoid
	local VictimHumanoid = Victim.Character.Humanoid
	
	local StartCuffAnimation = VictimHumanoid:LoadAnimation(Animations.StartCuff)
	local CuffAnimation = VictimHumanoid:LoadAnimation(Animations.Cuff)
	
	if not Officer.PlayerGui:FindFirstChild("Detain") then
		local DetainUI = ServerStorage.UI.Detain:Clone()
		DetainUI.Victim.Value = Victim
		DetainUI.SignOn.Global.Description.Text = "You have detained " .. Victim.Name
		DetainUI.Parent = Officer.PlayerGui
		
		FreeConnections[Victim.Name] = ArrestRemotes.Free.OnServerEvent:Connect(function(FreeOfficer, FreeVictim)
			print("Received")
			if FreeVictim == Victim and Officer == FreeOfficer then
				print("It's a match.")
				CuffAnimation:Stop()
				CuffAnimation:Destroy()

				if StartCuffAnimation then
					StartCuffAnimation:Destroy()
				end

				Free(Victim, Officer)
			else
				print(FreeVictim.Name .. " vs " .. Victim.Name)
			end
		end)

		StartCuffAnimation:Play()
		StartCuffAnimation.Stopped:Wait()

		CuffAnimation:Play()
		StartCuffAnimation:Destroy()
	end
end

Notice as well I’m using a similar tactic as handcuff connections to handle what I’m calling “Free Connections”. Essentially, I will create a function that frees the victim. Whether that be the officer deciding to not arrest the criminal, the officer getting killed, the officer leaving etc. Anything that would stop the criminal being caught in an infinite detained state. To make sure that this function isn’t clogging up space in our memory, we’re only going to be connecting this function when needed.

Also notice that we’re dealing with animations. Of course, the animations are up to you and you might not even want animations but here I’ve got a couple of animations to cuff up the criminal.

Now we move onto a critical part of this whole system. Getting the prisoner to see why they’ve been arrested is one thing, but we need to remember that they will be exposed to a piece of text that the police officer had defined.

This text could include swear words, which is strictly against the rules. We need to filter this text. Filtering this text is so important, then you could potentially be permanently banned from the website if you don’t do it. Luckily, it’s pretty straight forward.

We will be using the function “GetChatForUserAsync” because only the criminal will see that message so we should tailor the message to what their chat settings are. If the criminal is using safe chat, they will only see safe chat words – otherwise, they will only see words applicable to Roblox chat. It’s a win-win.

function Free(Player, Officer)
	print("Freeing")
	local Character = Player.Character
	if Character then
		local Humanoid = Character.Humanoid
		
		Humanoid.WalkSpeed = 16
		Humanoid.JumpPower = 50
		
		FreeConnections[Player.Name]:Disconnect()
		FreeConnections[Player.Name] = nil
		
		print("Stopping Animations")
		for _, AnimTrack in pairs (Character.Humanoid:GetPlayingAnimationTracks()) do
			print("Stopping " .. AnimTrack.Name)
			AnimTrack:Stop()
		end
	end
	
	if Officer then
		if Officer.PlayerGui:FindFirstChild("Detain") then
			Officer.PlayerGui.Detain:Destroy()
		end
	end
end

Remember before when I mentioned that we will be releasing the criminal, well here it is. We need to make sure that all animations playing on the criminal are stopped (ie the cuff animation) and we need to make sure that the criminal’s settings are all returned to normal. If you messed around with the player’s camera, you should reset it. If you messed around with the walk speeds, you should reset it. Everything like that, to make sure that if the criminal is released, they have no lasting side-effects.

Now we move onto a critical part of this whole system. Getting the prisoner to see why they’ve been arrested is one thing, but we need to remember that they will be exposed to a piece of text that the police officer had defined.

This text could include swear words, which is strictly against the rules. We need to filter this text. Filtering this text is so important, then you could potentially be permanently banned from the website if you don’t do it. Luckily, it’s pretty straight forward.

We will be using the function “GetChatForUserAsync” because only the criminal will see that message so we should tailor the message to what their chat settings are. If the criminal is using safe chat, they will only see safe chat words – otherwise, they will only see words applicable to Roblox chat. It’s a win-win.

And there you have it, folks, a completed arrest system which is pretty secure from those pesky exploiters. If you have any comments, question, feedback or whatever please leave a comment and I’ll get back to you as soon as I see it. I actually had a lot of fun making this tutorial so I’d love to do another one, so leave suggestions down below also and consider subscribing it would be really cool if you stick around. Of course, if you liked the video, you know what to do at this point I’m sure.

Thanks for watching everyone!

39 Likes

A lot people in the Discord and YouTube have requested I address a couple of questions so here’s a short FAQ:

  1. My animations aren’t working!
    Animations can be really tricky to figure out why they’re not working, it could be an issue with the animation itself, it could be a permissions issue or the code itself could be wrong. I will be doing a video on fixing your animations next when I get the time.

  2. Does this work with NPCs?
    Currently, it doesn’t work with NPCs as we’re using the Teams service in Roblox (which only applies to Player objects). However, this can be work with NPCs if you change the code from setting the team to simply teleporting the NPC to a location (which would be inside the jail) and teleporting them back out. You can do this with Model:SetPrimaryPartCFrame(CFrame).

  3. It does work when I click on the player’s hats!
    Yes, this is an issue with the code. This however can be fixed by changing the lines Player = game.Players:GetPlayerFromCharacter(hit.Parent) to:

local Player = game.Players:GetPlayerFromCharacter(hit:FindFirstAncestorOfClass("Model"))

I’m sure you can figure out why.

That’s all the FAQs - thanks for checking out the tutorial! :smiley:
-Tom

3 Likes

it doesn’t work anymore the github

5 Likes

Thanks for removing the github link, I love posts that don’t work.

9 Likes

ah yes, sending links that doesen’t work

4 Likes

Gotta do it yourself, it’ll be a hell yeah.