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
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)
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