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 although I do plan on making more tutorials in the future.
Thanks!
-Tom
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
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!