Hey so, I’m trying to secure my remotes so exploiters can’t just spam them. I’m having some trouble to set up what is called a sanity check. I looked into the forum how others managed to do it, but I can’t seem to understand how it works. I tried making my own, which would enable and disable a value in the player, but I then realized it’s for the whole server and if someone will fire a remote, legitimately, they would get banned. Can someone tell me how you can check how many times a player has fired a remote in an amount of time on server?
When the OnServerEvent
is fired, store a tick()
in the table with the player’s name or user ID. Then let’s say the RemoteEvent is fired again, if there is a stored player’s tick in the table, compare it with the new tick (tick() - playersOldTick)
. Now, is the time too less or too high? If it’s too less, it means the remote event was fired so quick.
Thanks, I’ll try to play around with it, but it seems like your solution would definitely work.
Here’s what I do
local EventDebounce = {}
Event.OnServerEvent:Connect(function(player, args)
if os.time() - (EventDebounce[player.Name] or 0) >= Cooldown then
EventDebounce[player.Name] = os.time() -- again, tick() would be much better in this case
print("performing action")
end
end)
-- you could use tick() for more accuracy if you want
You could even explicitly call wait(n) from the client before firing so that if on the server, the event was fired too quick, the player could be kicked (knowing the only time the remote would fire too quick would be if an exploiter were spamming.)
-- client
local debounce
if not debounce then debounce = true
Event:FireServer(args)
wait(1.5)
debounce = not debounce
end
-- server
local Event = game:GetService("ReplicatedStorage").RemoteEvent
EventDebounce = {}
Event.OnServerEvent:Connect(function(player, args)
local lastFired = EventDebounce[player.Name]
if lastFired then -- if this isn't the first time the event was fired
if tick() - lastFired < 1.4 then print("too fast")
player:Kick("Not so fast")
return
end
end
EventDebounce[player.Name] = tick()
print("performing action")
end)
Edit: It will not be a problem if the LocalScript gets deleted, see my reply below.
The only problem is that the exploiter has full control of their client, they can delete the local script, but it can still prevent inexperienced exploiters. Thanks for the detailed reply.
That’s not actually a problem, even if the exploiter deletes the script or fires through an alternate method, we’re still logging every event on the server with tick(), and if the time between events fired is too short, the player gets kicked.
The reason I did that part on the client was so that there would be a delay between firing- and if there’s too short of a delay (meaning the LocalScript was edited), the script on the server still verifies whether the amount of time between the last event fired is greater than the cooldown specified on the client.
Remember that not all players are exploiters, and you’re going to need some sort of debounce on the client too.
Yeah, because they can’t have access to server scripts. Thanks.
Here’s a pretty basic implementation that should work pretty well. Just start each Remote.OnServerEvent
listener with
if not NetworkHandler(Player, RemoteName, MaxRequestsPerSecond) then return end
to throttle excess requests.
local DEFAULT_MAX_REQ_PER_SECOND = 20 --Default maximum requests per remote event per second
local RemoteCalls = {}
function NetworkHandler(Player, RemoteName, MaxRequestsPerSecond)
if not MaxRequestsPerSecond then MaxRequestsPerSecond = DEFAULT_MAX_REQ_PER_SECOND end
if not RemoteCalls[Player.UserId] then RemoteCalls[Player.UserId] = {} end
RemoteCalls[Player.UserId][RemoteName] = (RemoteCalls[Player.UserId][RemoteName] or 0) + 1
return RemoteCalls[Player.UserId][RemoteName] <= MaxRequestsPerSecond
end
--Clearing up memory as players leave
game.Players.PlayerRemoving:Connect(function(Player)
if RemoteCalls[Player.UserId] then
RemoteCalls[Player.UserId] = nil
end
end)
--Remote1 will allow up to 20 requests per player per second
Remote1.OnServerEvent:Connect(function(Player, data)
if not NetworkHandler(Player, "Remote1") then return end
end)
--Remote2 uses the optional max request parameter, allowing 3 requests per player per second
Remote2.OnServerEvent:Connect(function(Player, data)
if not NetworkHandler(Player, "Remote2", 3) then return end
end)
--Clear everyone's network calls every second
while wait(1) do
for i,v in pairs(RemoteCalls) do
RemoteCalls[i] = {}
end
end