When a player equips a tool, they must wait a short time before they can begin to use it, and there is also an attack cooldown. However, very often the client-side cooldowns don’t work (they can be bypassed by just switching between tools and clicking extremely quickly) and allow players to send requests to use the tool to the server, which are denied. This causes the player to perform a “fake” attack in which no damage or anything is done, and the player has to wait until the client-sided attack cooldown is up to use the tool again. What do I have to change to prevent this?
-- Note that I've removed everything but the code responsible for managing the cooldowns.
-- Tool local script
local debounce = false; -- Attack cooldown
local deployingTool = false;
local deployTimeMarker = time();
local attackTimeMarker = time();
local function equip()
ToolEquipped:FireServer();
deployTimeMarker = time();
deployingTool = true;
task.delay(deployTime * toolDeployMultiplier.Value, function()
deployingTool = false;
end)
end
tool.Equipped:Connect(equip);
local function activate()
local requestTime = time();
if ((requestTime - attackTimeMarker) < (attackSpeed * attackSpeedMultiplier.Value * meleeAttackSpeedMultiplier.Value) or debounce) or
(requestTime - deployTimeMarker < deployTime * toolDeployMultiplier.Value or deployingTool) then
return;
else
attackTimeMarker = requestTime;
end
debounce = true;
end
-- Tool server script
local deployingWeapon = false;
local canStartAttack = false;
local deployTimeMarker = time();
local attackTimeMarker = time();
local function equip(player)
deployTimeMarker = time();
deployingWeapon = true;
tool.Enabled = false;
weaponUnequipped = false;
task.delay(deployTime * toolDeployMultiplier.Value, function()
deployingWeapon = false;
tool.Enabled = true;
end)
end
ToolEquipped.OnServerEvent:Connect(equip);
local function activate(player)
local requestTime = time();
canStartAttack = false;
if (requestTime - attackTimeMarker) < (attackSpeed * attackSpeedMultiplier.Value * meleeAttackSpeedMultiplier.Value) + player:GetNetworkPing() or
(requestTime - deployTimeMarker < (deployTime * toolDeployMultiplier.Value) + player:GetNetworkPing() or deployingWeapon) then
warn("Sent too many requests");
return false;
else
attackTimeMarker = requestTime;
end
end
Due to potential latency issues, there may always be some kind of discrepancy between when a client activates something and when the server receives that request.
What I usually do is I use the same cooldown code on the server, but reduce to time of the cooldown by roughly half in order to compensate for ping. It’s not a perfect solution, but it’s worked for me.
You could also use the player’s ping in order to determine how long the cooldown on the server-side should be.
Either A: Start the timers on the client and on the server at the same time, but this may lead to them ending slightly off.
Option B: Countdown on the server with a value somewhere, and check that value every second to know when it reaches 0.
Option C: Countdown on the server, and send the value to the client every second. This is different from option b because, rather than having the client call the value, the server is sending it to the client.
player fires remote to server, server starts a cooldown and fires a remote to client with the time cooldown started, when client receives the remote, using workspace:GetServerTimeNow(), subtract the time with the one sent from the server to get amount of time passed since server cooldown, then subtract it from the actual cooldown time and wait that amount before ending the cooldown
Take an offset from os.time(). Send that offset to the client. Both the client and server calculate the time by doing os.time() + offset, where offset is constant and shared through a remote.
When a player equips a tool, they must wait a short time before they can begin to use it, and there is also an attack cooldown. However, very often the client-side cooldowns don’t work (they can be bypassed by just switching between tools and clicking extremely quickly) and allow players to send requests to use the tool to the server, which are denied. This causes the player to perform a “fake” attack in which no damage or anything is done, and the player has to wait until the client-sided attack cooldown is up to use the tool again. What do I have to change to prevent this?
– Note that I’ve removed everything but the code responsible for managing the cooldowns.
I’ve decided to try this suggestion first (with the deploy cooldowns), but when I take the difference between workspace:GetServerTimeNow() with os.time() from the server, it is usually greater than 1. The intended cooldown time is 0.4 seconds, so subtracting the time difference yields a negative number. Thus, task.delay ends up using a negative number for the delay. I have no idea how any of that works, but ~90% of the time, the cooldown works properly. If you spam equip and click, you can still bypass the cooldown, however. Here is the code I have so far:
-- Client
local function equip()
ToolEquipped:FireServer();
local delayBetweenServerCooldown = 0;
local deployCooldownConnection = ServerCooldown.OnClientEvent:Connect(function(serverCooldownBegin)
delayBetweenServerCooldown = workspace:GetServerTimeNow() - serverCooldownBegin;
print(delayBetweenServerCooldown);
print((deployTime * toolDeployMultiplier.Value) - delayBetweenServerCooldown)
end)
deployTimeMarker = os.time();
deployingTool = true;
task.delay(deployTime * toolDeployMultiplier.Value - delayBetweenServerCooldown, function()
deployingTool = false;
end)
if deployCooldownConnection then
deployCooldownConnection:Disconnect();
end
end
tool.Equipped:Connect(equip);
-- Server
local function equip(player)
ServerCooldown:FireClient(player, workspace:GetServerTimeNow());
deployTimeMarker = os.time();
deployingWeapon = true;
tool.Enabled = false;
weaponUnequipped = false;
task.delay(deployTime * toolDeployMultiplier.Value, function()
deployingWeapon = false;
tool.Enabled = true;
end)
end
Another issue is that the ServerCooldown remote doesn’t fire to the client when the tool is first equipped, which is really strange.