Dash and Punch System Breaks When I Spam the Inputs

  1. What do you want to achieve?
    I want to make a combat/punch system along with a dash, you shouldn’t be able to dash and punch at the same time and vice versa.

  2. What is the issue?
    Whenever a player spams the punch and dash in my game it can break the scripts, causing no dash cooldown, no punch cooldown, and other errors.

  3. What solutions have you tried so far?
    I’ve searched the table errors and haven’t found anything too helpful. I also tried to add checks for the table being nil but I don’t really know what I’m doing with that so no help.

The entire systems are comprised of four scripts, two local scripts in StarterPlayerScripts and two scripts in ServerScriptService. Both systems use remote functions to handle debounce server-sided.

CombatScript located in ServerScriptService

--Services
local RS = game:GetService("ReplicatedStorage")

--Variables
local Damage = 5
local Cooldown0 = 0.375
local Cooldown1 = 2
local Debounce = {}
local Remotes = RS:WaitForChild("Remotes")
local Remote = Remotes.PunchFunction
local Remote1 = Remotes.HitEvent

--Functions
local function attackDebounce(Character)
	Character:SetAttribute("IsAttacking", true)
	task.wait(Cooldown0)
	Character:SetAttribute("IsAttacking", false)
end

Remote.OnServerInvoke = function(Player, moveOrder)
	local Character = Player.Character

	if Character:GetAttribute("IsAttacking") == true or table.find(Debounce, Player.Name) ~= nil or Character:GetAttribute("IsDashing") == true then
		print("Server Received!")
		return true
	else
		print("Server Received!")
		if moveOrder == 4 then
			task.spawn(function()
				table.insert(Debounce, Player.Name)
				print("Inserted "..Player.Name.." Into Combat Debounce")
				print(Debounce)
				task.wait(Cooldown1)
				Debounce[table.find(Debounce, Player.Name)] = nil
				print("Made Combat Debounce Table Nil For "..Player.Name)
			end)
			
		else
			
			task.spawn(attackDebounce, Character)
			
		end
		
		return false
		
	end
end

local function damageDebounce(Character, Cooldown)
	Character:SetAttribute("CanTakeDamage", false)
	task.wait(Cooldown)
	Character:SetAttribute("CanTakeDamage", true)
end

local function onServerEvent(Player, hitParts, moveOrder)
	for i, v in pairs(hitParts) do
		local Character = v.Parent
		local Humanoid = Character:FindFirstChild("Humanoid")
		if Humanoid ~= nil then
			if v.Parent:GetAttribute("CanTakeDamage") == false then
				--Already hit
			else
				--Hit
				Humanoid:TakeDamage(Damage)
				
				task.spawn(damageDebounce, Character, Cooldown0)
			end
		end
	end
end

--Events
Remote1.OnServerEvent:Connect(onServerEvent)

DashScript located in ServerScriptService

--Services
local RS = game:GetService("ReplicatedStorage")

--Variables
local Cooldown0 = 0.3
local Cooldown1 = 2
local Debounce = {}
local Remotes = RS:WaitForChild("Remotes")
local Remote = Remotes.RollFunction

--Functions
local function dashDebounce(Player, Character)
	table.insert(Debounce, Player.Name)
	print("Inserted "..Player.Name.." Into Dash Debounce")
	print(Debounce)
	Character:SetAttribute("IsDashing", true)
	task.wait(Cooldown0)
	Character:SetAttribute("IsDashing", false)
	task.wait(Cooldown1)
	Debounce[table.find(Debounce, Player.Name)] = nil
	print("Made Dash Debounce Table Nil For "..Player.Name)
end

Remote.OnServerInvoke = function(Player)
	local Character = Player.Character

	if Character:GetAttribute("IsDashing") == true or table.find(Debounce, Player.Name) ~= nil or Character:GetAttribute("IsAttacking") == true then
		print("Server Received!")
		return true
	else
		print("Server Received!")
		task.spawn(dashDebounce, Player, Character)

		return false
		
	end
end

CombatSystem located in StarterPlayerScripts

--Services
local Players = game:GetService("Players")
local CAS = game:GetService("ContextActionService")
local RS = game:GetService("ReplicatedStorage")
local workSpace = game:GetService("Workspace")
local RunService = game:GetService("RunService")

--Variables
local Player = Players.LocalPlayer
local Character = Player.Character or Player.CharacterAdded:Wait()
local keyBind = Enum.UserInputType.MouseButton1
local Remotes = RS:WaitForChild("Remotes")
local Remote = Remotes.PunchFunction
local Remote1 = Remotes.HitEvent
local Hitboxes = RS:WaitForChild("Hitboxes")
local Animations = {
	script.PunchAnimation1,
	script.PunchAnimation2,
	script.PunchAnimation3,
	script.PunchAnimation4
}
local moveOrder = 1
local ttl = 0.3
local timeElapsed = time()
local partsToIgnore = {Character}
local overlapParams = OverlapParams.new()
overlapParams.FilterType = Enum.RaycastFilterType.Exclude

--Functions
local function updatePartsToIgnore()
	for i, v in pairs(workSpace:GetChildren()) do
		if v:IsA("Model") and v:FindFirstChild("Humanoid") ~= nil then
			--Can detect
		elseif table.find(partsToIgnore, v) ~= nil then
			--Don't add
		else
			table.insert(partsToIgnore, v)
		end
	end
	print("updatePartsToIgnore Function")
	print(partsToIgnore)
end

local function createHitbox(hitbox)
	local hitboxClone = hitbox:Clone()
	return hitboxClone
end

local function punchFunction(actionName)
	local Hitbox = createHitbox(Hitboxes:FindFirstChild(actionName.."Hitbox"))
	table.insert(partsToIgnore, Hitbox)
	print("After adding hitbox")
	print(partsToIgnore)
	print(Hitbox)
	Hitbox.Parent = workSpace
	print(Hitbox.Name)
	local Connection = RunService.Heartbeat:Connect(function()
		Hitbox.CFrame = Character.HumanoidRootPart.CFrame * CFrame.new(0, 0, -2)
		overlapParams.FilterDescendantsInstances = partsToIgnore
		local hitParts = workSpace:GetPartBoundsInBox(Hitbox.CFrame, Hitbox.Size, overlapParams)
		Remote1:FireServer(hitParts, moveOrder)
	end)
	local animationTrack = Character.Humanoid.Animator:LoadAnimation(Animations[moveOrder])
	animationTrack:Play()
	print(Animations[moveOrder])
	moveOrder += 1
	timeElapsed = time()
	task.spawn(function()
		task.wait(ttl)
		Connection:Disconnect()
		partsToIgnore[table.find(partsToIgnore, Hitbox)] = nil
		Hitbox:Destroy()
	end)
end

local function punchInput(actionName, inputState, inputObject)
	if actionName == "Punch" and inputState == Enum.UserInputState.Begin then
		local Debounce = Remote:InvokeServer(moveOrder)
		if Debounce == true then
			print("On Cooldown!")
		else
			print("Clicked!")
			if moveOrder > 4 then
				print("Reset To First Animation")
				moveOrder = 1
				punchFunction(actionName)
			elseif (time() - timeElapsed) >= 1 then
				print("Reset To First Animation")
				moveOrder = 1
				punchFunction(actionName)
			else
				print("Continue As Normal")
				punchFunction(actionName)
			end
		end
	end
end

--Events
CAS:BindAction("Punch", punchInput, false, keyBind)
workSpace.ChildAdded:Connect(updatePartsToIgnore)

DashSystem located in StarterPlayerScripts

--Services
local Players = game:GetService("Players")
local CAS = game:GetService("ContextActionService")
local RS = game:GetService("ReplicatedStorage")

--Variables
local Player = Players.LocalPlayer
local Character = Player.Character or Player.CharacterAdded:Wait()
local keyBind = Enum.KeyCode.Q
local Remotes = RS:WaitForChild("Remotes")
local Remote = Remotes.RollFunction
local Animation = script.DashAnimation
local animationTrack = Character.Humanoid.Animator:LoadAnimation(Animation)
local ttl = 0.2

--Functions
local function rollInput(actionName, inputState, inputObject)
	if actionName == "Roll" and inputState == Enum.UserInputState.Begin then
		local Debounce = Remote:InvokeServer()
		if Debounce == true then
			print("On Cooldown!")
		else
			print("Pressed Q!")
			animationTrack:Play()
			local Character = Player.Character
			local HRP = Character.HumanoidRootPart
			local lvel = Instance.new("LinearVelocity")
			lvel.Parent = HRP
			lvel.Attachment0 = HRP:FindFirstChild("RootAttachment")
			lvel.ForceLimitMode = Enum.ForceLimitMode.PerAxis
			lvel.MaxAxesForce = Vector3.new(math.huge, 0, math.huge)
			lvel.VectorVelocity = HRP.CFrame.LookVector * 75
			
			task.delay(ttl, function()
				lvel:Destroy()
			end)
		end
	end
end

--Events
CAS:BindAction("Roll", rollInput, false, keyBind)

I HAVE been able to replicate this in singleplayer, but it happens far more frequently in team test multiplayer.

Any additional info necessary just let me know.
Screenshot 2024-06-12 201203
Screenshot 2024-06-12 201215

1 Like

Have all cooldowns be done locally, then see if it works better. What happens is that the inputs and the debounce are happening on the server side via. a remote which causes a them to be slightly off causing this issue.

I’ve also had this specific problem in one of my games where I use a remote event to send certain key clicks to the server but whenever I try to have a cd on the server it’d bug out, I’m personally still looking for a better solution then just making the cd on the client but that’s the best way I currently can think of doing it.

I want to avoid making the cooldowns via the client if I could due to exploit abuse. I know it should definitely be possible to do on the server.

Alright, I can see what you mean although exploiting is pretty rare now days but its always good to be cautious.

Try some sanity checks, have the cd be dealt with on the client and what you send to the server is the value of the debounce if its false even though its suppose to be true then you know someone’s tampered with debounce.

--Server-side
local ClientDebounce = Remote:InvokeClient(Player)
if ClientDebounce == true then
-- All good
else
-- Not Good
end
1 Like

I’ve heard it’s not great practice to do Server → Client → Server with remote functions.

I ended up just using attributes instead of a table for cooldowns and it worked.

1 Like

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.