Should I do debounces on the client or server

I have a very simple question. So currently, my combat system rely on client-sided debounce for more consistency. Though I believe it can lead to inaccuracies in some instances when the player has a high ping, the player finished the debounce before the server did any of its own actions.

Now my question is should I do the debounces on the client or on the server, or both ?

Example:

Client:

local db = false

function attack()
   if not db then
      db = true

      someRemote:FireServer()

      task.wait(1)
     
      db = false
   end
end

Server:

someRemote.onServerEvent:Connect(function(plr)
    task.wait(1)
    --* Do stuff here
end)

only do the debounce on the serverside, otherwise it wouldnt be fair for other players if there was an exploiter using the remote event

make attack function will just fire the remote no matter what and server will check for debounce

But I will still need a debounce on the client as there are still actions / effects spawned initially in it. Is it possible to sync those wait from 2 sides together ?

well you can do this with remote event that will tell when to activate debounce but this can be spoofed on client

You would do it on the server, since the client isn’t synced correctly with the server so I would just have it fire a remote to the server and handle the actions from there. Of course if you need the client to do something use a remote function and then just return true or false if they are able to attack.

Perhaps a possible solution is to add a short debounce on the server and also check if the server debounce is active on the client.

local db = false

function attack()
   if not db and character:GetAttribute("SDebounce") ~= true then
      db = true
      someRemote:FireServer()
      task.wait(1)
      db = false
   end
end

-- server
someRemote.onServerEvent:Connect(function(plr)
    if plr.Character:GetAttribute("SDebounce") == true then return end
    plr.Character:SetAttribute("SDebounce", true)
    task.delay(0.1, function()
       plr.Character:SetAttribute("SDebounce", false)
    end)
    task.wait(1)
    --* Do stuff here
end)

Keep the debounce on the client, but detect cheats on the server.
If the debounce is 5 seconds on the client, the server should detect if someone fires the event twice in 3 or 4 seconds. If they have, they’re obviously cheating. Kick them. This prevents them from spamming RemoteEvents, doesn’t require synchronization with the client debounce, and the looser margin for error is forgiving of latency and mistakes. A cheater isn’t going to spend the time testing the limits of the system, so you can afford to be lenient in your server side checks.

5 Likes

I recommend using a RemoteFunction as opposed to a RemoteEvent. Handle a debounce on the client which is set to true when called and false when a return value is made. On the server, handle another debounce and do your task.wait(1) there instead :+1: