How do i approach making a cooldown sanity check for melee system?

Hello, I’m making a melee system using Raycast Hitbox Module from TeamSwordphin.

Right now, My script is handling the cooldown in the local script with no actual sanity check in server.

I thought it would be vulnerable to exploit, So i wanted to make a server check for a cooldown.

If i completely handle cooldown on server, Player would be able to play animation in any condition, Which i don’t want it to be.

I want idea of what kind of method should i use to implement it.

Local Script:

local raycastHitbox = require(game.ReplicatedStorage:WaitForChild("RaycastHitboxV4"))

local tool = script.Parent
local handle = tool:WaitForChild("Handle")

local player = game.Players.LocalPlayer

local char = player.Character

if not char or not char.Parent then
	char = player.CharacterAdded:Wait()
end

local hum = char:WaitForChild("Humanoid")

local remoteEvent = tool:WaitForChild("RemoteEvent")

local cdTime = 1
local lastUseTime = 0
local step = 1

local hitbox
local hitWait

local swingSFX = handle:WaitForChild("Swing")
local equipSFX = handle:WaitForChild("Equip")

local idleAnim = hum:LoadAnimation(script:WaitForChild("Idle"))
local attack1Anim = hum:LoadAnimation(script:WaitForChild("Attack1"))
local attack2Anim = hum:LoadAnimation(script:WaitForChild("Attack2"))
local equipAnim = hum:LoadAnimation(script:WaitForChild("Equip"))

tool.Activated:Connect(function()
	if hum.Health > 0 and not equipAnim.IsPlaying then
		local currentTime = os.clock()

		if currentTime - lastUseTime >= cdTime then
			lastUseTime = currentTime

			if step == 1 then
				attack1Anim:Play()
				
				step = 2
			elseif step == 2 then
				attack2Anim:Play()
				
				step = 1
			end

			swingSFX:Play()

			local params = RaycastParams.new()
			params.FilterDescendantsInstances = {char}
			params.FilterType = Enum.RaycastFilterType.Exclude

			hitbox = raycastHitbox.new(handle)
			hitbox.RaycastParams = params

			hitbox.OnHit:Connect(function(hit, victimHum)
				remoteEvent:FireServer(hit, victimHum)
			end)

			hitbox:HitStart()

			hitWait = task.delay(0.5, function()
				hitbox:HitStop()
			end)
		end
	end
end)

tool.Equipped:Connect(function()
	idleAnim:Play()
	equipAnim:Play()
	
	equipSFX:Play()
end)

tool.Unequipped:Connect(function()
	if hitbox then
		hitbox:HitStop()

		if hitWait then
			task.cancel(hitWait)
		end
	end
	
	swingSFX:Stop()
	equipSFX:Stop()
	
	if idleAnim then
		idleAnim:Stop()
	end
	
	if attack1Anim then
		attack1Anim:Stop()
	end
	
	if attack2Anim then
		attack2Anim:Stop()
	end
	
	if equipAnim then
		equipAnim:Stop()
	end
end)

Server Script:

local debris = game:GetService("Debris")
local serverscriptservice = game:GetService("ServerScriptService")
local replicatedstorage = game:GetService("ReplicatedStorage")

local tool = script.Parent
local handle = tool.Handle

local remoteEvent = tool.RemoteEvent

local hitSFX = handle.Hit

local config = {
	Damage = 57,
	MagnitudeRange = 15,
}

remoteEvent.OnServerEvent:Connect(function(player, hit, victimHum)
	local char = player.Character or player.CharacterAdded:Wait()
	local hum = char.Humanoid
	
	local victimChar = victimHum.Parent

	if victimHum.Health > 0 then
		local magnitude = (char.HumanoidRootPart.Position - victimChar.HumanoidRootPart.Position).Magnitude
		
		if magnitude <= config.MagnitudeRange then
			victimHum:TakeDamage(config.Damage)

			hitSFX:Play()

			if not victimHum:FindFirstChild("creator") then
				local creator = Instance.new("ObjectValue")
				creator.Name = "creator"
				creator.Value = player

				debris:AddItem(creator,5)

				creator.Parent = victimHum
			end

			local bloodEffect = replicatedstorage.Miscs.BloodEffect.Blood:Clone()

			if char:FindFirstChild("Torso") then
				bloodEffect.Parent = victimChar.Torso
			else
				bloodEffect.Parent = victimChar.UpperTorso
			end

			debris:AddItem(bloodEffect, 0.5)
		else
			warn("Too far to give damage.")
		end
	end
end)

I usually have a small buffer on the server (~0.12s) where the cooldown is less than the one on the client. (do a cooldown on the client and server if you arent already)

There’s other ways to do it, but I just have it to where if the cooldown reaches that 0.12s threshold, it skips the remaining time and sets the cooldown to 0

1 Like

I’m not sure if i did it correct, But i rewrite it similar to like what you said:

local debris = game:GetService("Debris")
local serverscriptservice = game:GetService("ServerScriptService")
local replicatedstorage = game:GetService("ReplicatedStorage")

local tool = script.Parent
local handle = tool.Handle

local remoteEvent = tool.RemoteEvent

local hitSFX = handle.Hit

local cooldown = false

local config = {
	Damage = 57,
	MagnitudeRange = 15,
	ServerBuffer = 1 - 0.12
}

remoteEvent.OnServerEvent:Connect(function(player, hit, victimHum)
	local char = player.Character or player.CharacterAdded:Wait()
	local hum = char.Humanoid
	
	local victimChar = victimHum.Parent

	if victimHum.Health > 0 then
		local magnitude = (char.HumanoidRootPart.Position - victimChar.HumanoidRootPart.Position).Magnitude
		
		if magnitude <= config.MagnitudeRange and cooldown == false then
			cooldown = true
			
			victimHum:TakeDamage(config.Damage)

			hitSFX:Play()

			if not victimHum:FindFirstChild("creator") then
				local creator = Instance.new("ObjectValue")
				creator.Name = "creator"
				creator.Value = player

				debris:AddItem(creator,5)

				creator.Parent = victimHum
			end

			local bloodEffect = replicatedstorage.Miscs.BloodEffect.Blood:Clone()

			if char:FindFirstChild("Torso") then
				bloodEffect.Parent = victimChar.Torso
			else
				bloodEffect.Parent = victimChar.UpperTorso
			end

			debris:AddItem(bloodEffect, 0.5)
			
			task.delay(config.ServerBuffer, function()
				cooldown = false
			end)
		else
			warn("Too far to give damage.")
		end
	end
end)
1 Like

hypothetically that should work

if you want a more robust system, you might expose some kind of cooldown NumberValue that can be accessed/modified by any script, and decrease it every frame
simple example:

CooldownObject = Instance.new("NumberValue") -- probably want to have one stored in a folder/database somewhere
serverBuffer = 0.12

CooldownObject.Changed:Connect(function(newValue)
	if newValue <= serverBuffer then
		CooldownObject.Value = 0
	end
end)

game:GetService("RunService").Heartbeat:Connect(function(deltaTime) -- you probably only want to be decreasing it whenever its not already 0, you can figure out the logic for that
	if CooldownObject.Value > 0 then
		CooldownObject.Value = math.max(0, CooldownObject.Value - deltaTime) -- make sure cooldown doesn't go below 0
	end
end)


function Attack()
	if CooldownObject.Value == 0 then
		-- blah blah
	end
end

(lots of room for improvement)

1 Like

It has worked well, Thanks for helping!

1 Like