How do I add a wait() or something similar without halting the script?

I have a client script that has all the cooldowns and remote triggers for combat moves, but the problem is, moves can be used at the same time, I have a solution to this by adding a check to see if another move is being used by adding a boolean to the script, but there’s no way to implement a wait() to add a cooldown before another move can be used, is there a workaround to this?

Why can’t you use os.time() to save the last time a move was used, then every time you try to use a move, you compare current time to last used time

Use coroutine code blocks, coroutine code blocks are new Threads that will not affect the code outside the function

Example:

local NewThread = coroutine.create(function()
  print("New Thread")
  wait(3)
  print("Delayed print on new Thread")
end)
	
print("Print Before Coroutine")
coroutine.resume(NewThread)
print("Print After Coroutine")
  • You can also create multiple Coroutine Threads

So if I understood you correctly, it would be used like this?

	if Input.KeyCode == Enum.KeyCode.H then
		isMove1OnCooldown = true
		ClientCooldown = true
		
		local Move1Coroutine = coroutine.create(function()
			wait(2.5)
			ClientCooldown = false
			
		end)
		
		coroutine.resume(Move1Coroutine)

		Move1Remote:FireServer()

If ure trying to make an CoolDown/Debounce system, try making like this:

local DebounceDelay = 3; 
local Debounce = false;

if Input.KeyCode == Enum.KeyCode.H then
	if not Debounce then
		Debounce = true
		-- Fire Server
	end
end

while true do
	wait(DebounceDelay)
	Debounce = false;
end   

If u want to make a global debounce to moves, you can make a string value instance on the player and manage it on each script

The while loop would create a halt as there is still code below it, I have a cooldown system in place, however it’s just an actual cooldown for the move itself instead of a cooldown to use another move after using a move.

UserInputService.InputBegan:Connect(function(Input, isTyping)
	if isTyping or isMove1OnCooldown or game:GetService("Players").LocalPlayer.Character.Humanoid.Health <= 0 or ClientCooldown == true then
		return

	end

	if Input.KeyCode == Enum.KeyCode.H then
		isMove1OnCooldown = true
		ClientCooldown = true
		
		local Move1Coroutine = coroutine.create(function()
			wait(2.5)
			ClientCooldown = false
			
		end)
		
		coroutine.resume(Move1Coroutine)

		Move1:FireServer()

I also do not want a global debounce to moves, I just want an inbetween move cooldown on the client.

I’m not sure I fully understand what you’re asking, but if you want a debounce that works across all moves, you can do something like the following:

--!strict

local Players = game:GetService("Players")
local UserInputService = game:GetService("UserInputService")
local ReplicatedStorage = game:GetService("ReplicatedStorage")

local remotesByActionKey = {
	[Enum.KeyCode.H] = ReplicatedStorage.Remotes.Move1,
	[Enum.KeyCode.G] = ReplicatedStorage.Remotes.Move2,
}

local SECONDS_BETWEEN_MOVES = 2

local localPlayer = Players.LocalPlayer
local lastActionAt = 0

local function isAlive()
	local character = localPlayer.Character
	local humanoid = character and character:FindFirstChildOfClass("Humanoid")
	return humanoid and humanoid.Health > 0
end

local function canDoAction(actionKey: Enum.KeyCode)
	local isValidMove = remotesByActionKey[actionKey]
	local enoughTimePassed = time() - lastActionAt >= SECONDS_BETWEEN_MOVES

	return isValidMove and enoughTimePassed and isAlive()
end

-- Returns true if the action completed, otherwise false
local function tryAction(actionKey: Enum.KeyCode)
	if not canDoAction(actionKey) then
		return false
	end
	lastActionAt = time()

	remotesByActionKey[actionKey]:FireServer()

	return true
end

UserInputService.InputBegan:Connect(function(input: InputObject, gameProcessedEvent: boolean)
	if gameProcessedEvent then
		return
	end

	if input.UserInputType == Enum.UserInputType.Keyboard then
		local success = tryAction(input.KeyCode)
	end
end)

Note: It is important to have the same cooldown on the server to avoid exploiters directly firing the remotes to avoid the client cooldown.

Then insert the while loop that manages the Debounce state on a corountine:

local DebounceDelay = 3; 
local Debounce = false;

if Input.KeyCode == Enum.KeyCode.H then
	if not Debounce then
		Debounce = true
		-- Fire Server
	end
end

local CoroutineDebounceThread = coroutine.create(function()
	while true do
		wait(DebounceDelay)
		Debounce = false;
	end   
end)

coroutine.resume(CoroutineDebounceThread)

print("The code below the coroutine will not be affected")

This is fine but you’re going to encounter situations in which the ‘Debounce’ state variable is toggled in quick succession.

This code has a problem where the debounce can get flipped back to false in anywhere from 0-3 seconds. That is, your debounce is not guaranteed to be 3 seconds long. This is because your debounce thread is running on a separate thread, so the timer doesn’t start at the same time as the debounce value changes.This shouldn’t be marked as the solution.

If you really wanted to use coroutines for some reason, you could do something like this:

Using coroutines
local UserInputService = game:GetService("UserInputService")
local DEBOUNCE_SECONDS = 3; 
local isDebouncing = false;

local startDebounceCountdown = coroutine.wrap(function()
	while true do
		isDebouncing = true
		print("Starting debounce")
		task.wait(DEBOUNCE_SECONDS)
		isDebouncing = false;
		print("Debounce finished")
		coroutine.yield()
	end   
end)

UserInputService.InputBegan:Connect(function(input: InputObject, gameProcessedEvent: boolean)
	if gameProcessedEvent then
		return
	end
	if input.UserInputType == Enum.UserInputType.Keyboard then
		if input.KeyCode == Enum.KeyCode.H then
			if isDebouncing then
				print("Still debouncing")
				return
			end

			startDebounceCountdown()

			-- Fire Server
			print("Firing server")
		end
	end
end)

But that’s totally unnecessary since the InputBegan event starts a new thread anyway.
You could also accomplish this without coroutines using the task library, like so:

Using task.delay:
local UserInputService = game:GetService("UserInputService")
local DEBOUNCE_SECONDS = 3; 
local isDebouncing = false;

UserInputService.InputBegan:Connect(function(input: InputObject, gameProcessedEvent: boolean)
	if gameProcessedEvent then
		return
	end
	if input.UserInputType == Enum.UserInputType.Keyboard then
		if input.KeyCode == Enum.KeyCode.H then
			if isDebouncing then
				print("Still debouncing")
				return
			end
			
			isDebouncing = true
			print("Starting debounce")
			task.delay(DEBOUNCE_SECONDS, function()
				isDebouncing = false
				print("Debounce finished")
			end)

			-- Fire Server
			print("Firing server")
		end
	end
end)

The solution I posted earlier that uses a timestamp check is also a complete solution.

1 Like

You’re right, I never thought of using this approach.