Stop exploiters from changing values

I want to make my game secure as possible but this one problem can’t be solved by me.

So, I have a local script that uses remote events with the server and does all the serverside code in there. It’s activated by using an input, however, theres a cooldown after the input and the wait is based on numbervalues (Instance) inside the character’s stats.

This makes the code vulnerable as exploiters can adjust the number values and make the cooldown shorter.

I have tried using debounces in the serverscript and local script but I couldn’t find a way to fix this insecurity.

To simplify, I want the values to not be tweaked by exploiters and still able to use number values to base the cooldown - (2 x cooldownmultiplier).

The cooldown is on the clientside as I cant think of a way to put it in the serverscript.

3 Likes

What’s the cooldiwn for? Abilities?

1 Like

It’s for an attack that generates a hitbox and damages players.

1 Like

Maybe keep track of the time that the player last sent an input to the server (I’m guessing you are using RemoteEvents for that). If the Player sends another input, check if it has been enough time for the cooldown to be over.

Sorry if this is not what you wanted at all. Can you explain in more detail?

1 Like

If a player changes a value on their end, that won’t affect the server because of something called FilteringEnabled. (Unless it’s their character because they have Network Ownership over that)

So the server will see none of the changes made by the client. They will only be reflected on the client that modified the value.

1 Like

Maybe create a Variable inside the localScript

local cooldownmultiplier = 30
local DB = false
 
if DB == false then

 DB = true

 RemoteEvent:FireServer()

 Task.Wait(2 x cooldownmultiplier )

 DB = false

end

2 Likes

Why not move the cooldown to a variable?

1 Like

Pretty sure what they mean is that an exploiter could send the input whenever they want. Without waiting.

1 Like

Which is why the cooldown check should be handled on the server

1 Like

Yeah I agree. But I think they probably want visual feedback so the client knows they cannot send the input yet.

1 Like

So you add a visual cooldown check on the client.

2 Likes

Then you can create a ModuleScript for that

1 Like

My idea is that the check on the server-side is there to prevent exploits.

Pretty sure it would be more efficient to handle the cooldown both on the client and the server for separate purposes in this case, correct me if I am wrong.

4 Likes

Okay. I have written you a ModuleScript in which you can save and retrieve various cooldowns.

I’ve left out meta tables and so on to make it not too complex and I’ve only taken 15 minutes now.

I have written you a server and client script example.

If you want it to be used only by the server, put it in ServerStorage or leave it in ReplicatedStorage and add a runService:IsServer() query in each function.

ModuleScript :

local r_storage = game:GetService("ReplicatedStorage")
local runService = game:GetService("RunService")

local AllPlayers = {}

local cooldownHandler = {}

-- Create the cooldown Table for the Plr
function cooldownHandler.New(Plr : Player)
	
	if AllPlayers[Plr.UserId] then return end
	
	AllPlayers[Plr.UserId] = {

		["cooldownmultiplier"] 		= 		{TimeToWait = 5, CurrentTask = false},
		["testmultiplier"] 				= 		{TimeToWait = 10, CurrentTask = false},

	}
	
end

-- Remove the cooldown from the Table
function cooldownHandler.RemovePlayer(Plr : Player)
	
	cooldownHandler[Plr.UserId] = nil
	
end

-- Start a Timer for the given cooldown
function cooldownHandler.StartCounter(Plr : Player, CooldownName : string)
	
	local FoundValue = AllPlayers[Plr.UserId][CooldownName]
	
	if FoundValue then
		
		local CanStartTask = AllPlayers[Plr.UserId][CooldownName].CurrentTask
		
		if CanStartTask == false then 
			
			AllPlayers[Plr.UserId][CooldownName].CurrentTask = true
			
			task.spawn(function()

				local DefaultValue = FoundValue.TimeToWait

				while AllPlayers[Plr.UserId][CooldownName].TimeToWait > 0 do

					AllPlayers[Plr.UserId][CooldownName].TimeToWait -= 1

					task.wait(1)

				end

				AllPlayers[Plr.UserId][CooldownName].TimeToWait = DefaultValue
				AllPlayers[Plr.UserId][CooldownName].CurrentTask = false

			end)
			
		else
			
			warn("Counter is running")
			
		end

	else
		
		warn("Wrong Cooldown Name : ", CooldownName)
		
	end
	
end

-- Return the Cooldown for a given plr and cooldownName
function cooldownHandler.GetCooldown(Plr : Player, CooldownName : string)
	
	local ValueToReturn = AllPlayers[Plr.UserId][CooldownName]
	
	if ValueToReturn then
		
		return AllPlayers[Plr.UserId][CooldownName]
		
	else
		
		warn("Wrong Cooldown Name : ", CooldownName)
		
	end

end

runService:IsServer()

-- Return the table of a given Player
function cooldownHandler.GetPlayer(Plr : Player)

	return AllPlayers[Plr.UserId]

end

-- Return all Players
function cooldownHandler.GetPlayers()

	return AllPlayers

end

return cooldownHandler

ServerScript :

local Players = game:GetService("Players")

local r_Storage = game:GetService("ReplicatedStorage")

local CH = require(r_Storage.CooldownHandler)

Players.PlayerAdded:Connect(function(Plr : Player)
	
	CH.New(Plr)
	
	task.wait(5)
	
	warn("Plr Data : ", CH.GetPlayer(Plr))
	warn("All Data : ", CH.GetPlayers())
	warn("GetCooldown : ", CH.GetCooldown(Plr, "cooldownmultiplier"))
	CH.StartCounter(Plr, "cooldownmultiplier")
	CH.StartCounter(Plr, "cooldownmultiplier")
	task.wait(6)
	CH.StartCounter(Plr, "cooldownmultiplier")
end)

Players.PlayerRemoving:Connect(function(Plr : Player)
	
	CH.RemovePlayer(Plr)
	
end)

r_Storage.RemoteFunction.OnServerInvoke = function(Plr : Player, Key : string, value : any)
	
	if Key == "GetPlayer" then
		
		return CH.GetPlayer(Plr)
		
	elseif Key == "GetCooldown" then
		
		return CH.GetCooldown(Plr, value)
		
	end
	
end

LocalScript :

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

local Plr = Players.LocalPlayer

task.wait(2)

warn(r_Storage.RemoteFunction:InvokeServer("GetPlayer"))
warn(r_Storage.RemoteFunction:InvokeServer("GetCooldown", "cooldownmultiplier"))

Communication between the client and the server takes place via RemoteFunctions

1 Like

It’d likely be easier to handle the cooldown via attributes if you want it easily accessible from both sides. Clients modifying attributes won’t replicate, but server modifying attributes will. Seems perfect, you can set one for each player.

1 Like

make systems on server and use client for just trigger them.
also make double check on server side

1 Like

the cooldown is based on a numbervalue in the client script so if a hacker changes the numbervalue locally it can lower the cooldown as it’s on client

Can you specify why debounces do not prevent an exploiter from sending an input too early?

1 Like

the cooldown uses wait() so the debounce is set false after the wait()

1 Like

You’re looking to fix something you’ve broken for good… Don’t use Instance values (NumberValue) for anything security-critical.

1 Like