Should debounces be on the server?

I understand this question has been asked a lot but I haven’t really gotten what I wanted out of those previous posts.

I’m working on a fishing system but I don’t know if I should make the debounce on the server or client. I think this will pose a threat because the client can control the debounce and change it to however they like.

I also would have to face the issue of using the client and the server if I were to transfer this over to the server because I need to raycast the mouse’s position. It would mean firing to server and client back and fourth a lot.

Should debounces always be on server?

Fishing System
local ContextActionService = game:GetService("ContextActionService")
local UserInputService = game:GetService("UserInputService")
local Players = game:GetService("Players")
local CollectionService = game:GetService("CollectionService")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local TweenService = game:GetService("TweenService")

local Remotes = ReplicatedStorage.Remotes
local Sounds = ReplicatedStorage.Sounds
local Bindables = ReplicatedStorage.Bindables
local Assets = ReplicatedStorage.Assets
local Components = Assets.Components
local Functions = ReplicatedStorage.Functions

local GUI = Assets.GUI.Fish

local Camera = workspace.CurrentCamera

local Player = Players.LocalPlayer
local Character = Player.Character or Player.CharacterAdded:Wait()
local Root = Character:WaitForChild("HumanoidRootPart")

local Params = RaycastParams.new()
Params.FilterDescendantsInstances = {Character}
Params.FilterType = Enum.RaycastFilterType.Exclude

local Fishing = false
local REWARD_TIME = 3

local GeneralInfo = TweenInfo.new(1,Enum.EasingStyle.Sine,Enum.EasingDirection.Out)
local TweeningTable = {
	["FishFrame"] = UDim2.new(0.25,0,0.375,0),
	["Sunrays"] = UDim2.new(0.375,0,0.547,0),
	["RewardLabel"] = UDim2.new(0.25,0,0.081,0)
}

local FishingRod = {}

local function Direct()
	local mouse = UserInputService:GetMouseLocation()
	local ray = Camera:ViewportPointToRay(mouse.X,mouse.Y)

	local result = workspace:Raycast(ray.Origin,ray.Direction * 25,Params)
	if result and CollectionService:HasTag(result.Instance,"Water") then
		return result.Position
	end
end

local function Cast(pos)
	local part = Instance.new("Part")
	part.Size = Vector3.one * 2
	part.Anchored = true
	part.CanCollide = false
	part.Position = pos
	part.Color = Color3.fromRGB(223, 225, 225)
	part.BottomSurface = Enum.SurfaceType.Smooth
	part.TopSurface = Enum.SurfaceType.Smooth
	part.Parent = workspace
	return part
end 

local function Beam(cast)
	local beam = Instance.new("Beam")
	local att0 = Instance.new("Attachment")
	local att1 = Instance.new("Attachment")

	beam.Attachment0 = att0
	beam.Attachment1 = att1
	att0.Parent = Character:FindFirstChild("Fishing rod").Part
	att1.Parent = cast
	beam.Parent = Root
	return beam,att0,att1
end

local function GFX(fish)

	local new = fish:Clone()
	local fake = Instance.new("Camera")
	local gui = GUI.FishGui:Clone()
	local fishframe = gui.FishFrame
	local rewardlabel = gui.RewardLabel
	local function tween()
		for i,v in pairs(TweeningTable) do
			local instance = gui:FindFirstChild(i)
			if not instance then return end
			TweenService:Create(instance,GeneralInfo,{Size = v}):Play()
			if instance.Name == "RewardLabel" then return end
			TweenService:Create(instance,GeneralInfo,{ImageTransparency = 0}):Play()
		end
	end

	fake.CameraType = Enum.CameraType.Scriptable
	fake.CFrame = CFrame.lookAt(new.Position + Vector3.new(0,0,5),new.Position)
	fishframe.CurrentCamera = fake
	rewardlabel.Text = "You earned the " .. new.Name .. "fish!"

	fake.Parent = fishframe
	new.Parent = fishframe
	gui.Parent = Player.PlayerGui
	tween()
	return gui
end

local function sound()
	Bindables.OnClient:Fire("ShortSound",Sounds.FishReward)
end

local function tweendestroy(gui)
	for i,v in gui:GetChildren() do
		if v.Name == "RewardLabel" then
			TweenService:Create(v,GeneralInfo,{TextTransparency = 1}):Play()
		else
			TweenService:Create(v,GeneralInfo,{Size = UDim2.new(0,0,0,0)}):Play()
			TweenService:Create(v,GeneralInfo,{ImageTransparency = 1}):Play()
		end
	end
	task.wait(GeneralInfo.Time)
	gui:Destroy()
end

FishingRod["Upgrade"] = function(upg)
	return function(actionName,inputState)
		if inputState == Enum.UserInputState.Begin then
			if Character:FindFirstChild("Fishing rod") and not Fishing then
				local pos = Direct()
				if pos == nil then return end
				local PULLING_TIME = math.random(2,4)
				task.spawn(function()
					local cast = Cast(pos)
					local line, string1,string2 = Beam(cast)
					Remotes.ToServer:FireServer("DisablePlayerMovement",PULLING_TIME)
					task.wait(PULLING_TIME)
					
					local fish = Functions.SelectFish:InvokeServer()
					local gfx = GFX(fish)
					sound() -- destroys in function
					Remotes.ToServer:FireServer("PlayerHasFished",fish,upg,PULLING_TIME)
					cast:Destroy()
					line:Destroy()
					string1:Destroy()
					string2:Destroy()
					Fishing = false
					task.wait(REWARD_TIME)
					tweendestroy(gfx)
				end)
			end
		end
	end
end

return FishingRod
1 Like

If a critical part of your game, that communicates from the client to the server requires debouncing, then you should add a debounce to both the client and server. The debounce on the client is to keep requests to the server to a minimum. The debounce on the server is to prevent things such as exploiters, who could easily inject localscripts to fire the event to the server.

3 Likes

Sure you can place debounces on client but make sure that it can’t be abused in any way by exploiters. I recommend putting debounces on server.

1 Like

I mean exploiters can just change the debounce time 2-4 to 0.

The problem is, if I make debounces on the server, I will have to invoke the server every time a player tries to trigger a skill or tool event.

I need access to the skill being triggered but the client will have to deliver that name and I’m not sure how that could be secure.

1 Like

You never trust any data from the client. On the server you would do what is commonly referred to as sanity checks. Sanity checks are checks on the server to validate certain data, ie the corrent data type, or that the player wanting to do something in the game have the rights to do so.

In your case (“I need access to the skill being triggered but the client will have to deliver that name and I’m not sure how that could be secure.”) you would send the name of the skill to the server, then on the server check that the player is allowed to utilize that skill, I assume that you have a datastore for storing unlocked skills? Otherwise you surely must be storing some sort of related information somewhere.

Never trust the client

Instead of a debounce the leaky bucket method should be better. It allows for more leniency on each action (remote event delays and queue buildup bursts) and prevents spam.

1 Like

I have 2 remote events in my game though which are ToServer and ToClient and I just use strings to make signals between client and server.

It really depends what sort of debounce you’re using. Sure, the client should never be trusted, but if you’re hard-coding a debounce into a script without using any object that the client can access (say a BoolValue), there’s no need to call to the server as the client cannot edit a script (this includes LocalScripts). Of course, if you’re using an object when it comes to debouncing then you should consider Client->Server calls, but if not, then it’s not necessary.

The client can edit local scripts?

The client is able to view local scripts by decoding its bytecode. It is also able to duplicate, delete, and insert local scripts into the game. The client also has the ability to replicate functions and send calls to the server with a remote event but it is unable to directly edit the code. Despite not being able to edit the code itself, the client still have some control, and this is why the client is never trusted. By hard-coding a debounce, the client will still have the ability to view it, but is unable to alter it in any way; ensuring security within the debounce.

For example, in your provided code, you are sending a remote event to re-enable player movement (from what I can tell with the argument "PlayerHasFinished"), however, the client is able to send the same call to the server to re-enable their movement as the client has the ability to send calls; defeating the purpose of the debounce as a result of having an external source → the remote event itself.

2 Likes

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.