How to stop input spamming when cooldown is handled by server?

I’m working on combat for my game and the server is handling mostly everything at the moment besides animations. One of the issues I have is I don’t know how to add cooldowns/debounces to the client to prevent spam (i.e: rapidly replaying the same animation). Do I just let the client handle cooldown as well or should the server pass the cooldown?

This is my code for input and cooldown which is handled by the server.

-- Client
function Input:InputBegan(inputObject, gameProcessed)
	if UI:GetCurrentMenu() == "StartScreen" then PressAnyKeyToStart(inputObject) return end
	
	if self.Player.Class ~= nil then
		if inputObject.UserInputType == Keybinds.PrimaryAttack then
			Action:FireServer("PrimaryAttack",nil)
			self.Player.Animation:PlayAnimation("Hit1")
		end		
	end
end
-- Server
local function HandleAction(player,actionName,keycode)
	-- Cooldown Check
	local cooldown = classModule[actionName](player.Character,true) -- 2nd arg (check) will make the ability funciton return info such as its cooldown
	if not cooldown then return end
	local name = player.Name..actionName
	if playerCooldowns[name] and os.clock() - playerCooldowns[name] < cooldown then
		-- warn("ON COOLDOWN")
		return
	end
	
	playerCooldowns[name] = os.clock() -- Start cooldown
	classModule[actionName](player.Character) -- Do ability
end

You should use a debounce on the client side.

Add a debounce on the client. I am aware the client can edit the local script and bypass this cooldown so kick them if you receive too many events being fired within a short time (a good solution to input spamming)

How though? Since the cooldown is on the server there’s no way for the client to know when it should allow input again.

Edit:
I’ve implemented this into the server code

	if playersAttacking[player.Name] and os.clock() - playersAttacking[player.Name] < attackTime then
		warn("Already Doing An Attack")
		return
	end

This is useful as it checks if the player is doing another attack and this is actually the information I need to get on the client somehow.

A problem you might experience if you have the input cooldown on the server is that the player can spam input and cause the server to slow down and give longer response time between server and client.

The best is to have the cooldown on the client and have security check on the remote to prevent hacking/exploits.

You basically might need to remove the cooldown on the server and just move it to the client.

Yeah so I realized that the client needs some type of cooldown to have some kind of code to prevent spamming, however, I don’t think removing it from the server is necessary. Anyways, a solution I came up with was to add an attack delay although my implementation is a bit sloppy.

-- Client
function Input:InputBegan(inputObject, gameProcessed)
    if UI:GetCurrentMenu() == "StartScreen" then PressAnyKeyToStart(inputObject) return end
    
    if self.Player.Class ~= nil then        
        if inputObject.UserInputType == Keybinds.PrimaryAttack then
            local requestAttackTime = ReplicatedStorage.Remotes.RemoteFunction
            local attackTime = requestAttackTime:InvokeServer()
            if playersAttacking[game.Players.LocalPlayer.Name] and os.clock() - playersAttacking[game.Players.LocalPlayer.Name] < attackTime then
                warn("We're already attacking")
                return
            end
            playersAttacking[game.Players.LocalPlayer.Name] = os.clock()
            Action:FireServer("PrimaryAttack",nil)
            self.Player.Animation:PlayAnimation("Hit1")
            return
        end        
    end
end
-- Server
local function HandleAction(player,actionName,keycode)
    -- Cooldown Check
    local name = player.Name..actionName
    local cooldown, attackTime = classModule[actionName](player.Character,true)
    if playerCooldowns[name] and os.clock() - playerCooldowns[name] < cooldown then
        warn("On cooldown")
        return
    end
    
    -- Checks if they're doing another attack already
    if playersAttacking[player.Name] and os.clock() - playersAttacking[player.Name] < attackTime then
        -- warn("Player is already doing another action")
        return
    end
    
    playerCooldowns[name] = os.clock()
    playersAttacking[player.Name] = os.clock()
    classModule[actionName](player.Character) -- Do ability
end

RequestAttackTime.OnServerInvoke = function(player)
    return 0.7
end

Read my previous post please.

(Ahhhh)