CooldownMap - The EASIEST way to set up any sort of cooldowns/debounce!

https://www.roblox.com/library/6541280076/CooldownMap

Managing lots of cooldowns can be a pain, so I made this module to make it easier.

Despite how easy it is to learn, I find that setting up cooldowns in scripts for things such as abilities, spawns, etc can become really messy if you have more than 3 cooldowns, and you often end up repeating the same bits of code over and over throughout the script.

Along with this, often developers use wait(n) and a debounce to control cooldowns. This does work in practice, however the default thread scheduler isn’t great for precise cooldowns like this, and you’re better off saving time() or tick().

CooldownMap, a very simple module, provides a solution to that. It’s so lightweight I can fit it in this devforum post!

To use this, simple require the module and append .new(), as such

local cooldownMap = require(game.ReplicatedStorage.Modules.CooldownMap).new()

From there, you can use it in a tool, like such :

local Tool = script.Parent
local cooldownMap = require(game.ReplicatedStorage.Modules.CooldownMap).new()

Tool.Activated:Connect(function()
	local timeleft = cooldownMap:Check("Test", 2)
	if timeleft then
		print("Still on cooldown! Time left : " .. )
	else
		warn("Tool activated!")
	end
end)

Example 2 :

So that was pretty simple, so simple that, you wouldn’t even really need this module. But what if you have something a bit more complicated? Here’s an example of what that could look like, originally typed for one of my friends as an example of how to use this module.

In this example, it assumes that it’s going into some sort of jojo game (there are a lot of those on Roblox…), where you have a stand toggle key, and some other attacks. This example applies a global cooldown after toggling the stand, making it so player’s can’t toggle and then attack immediately while the toggle animation is playing. (Of course there would be a lot more to it than this, like not allowing the player to attack if they don’t have their stand out).

local COOLDOWN_TOGGLE = 1
local COOLDOWN_BARRAGE = 4
local COOLDOWN_GLOBAL = 0.5

local KeyAbilities = {

  [Enum.KeyCode.E] = function()
    if CooldownMap:Check("Global") then return end
    if CooldownMap:Check("Barrage", COOLDOWN_BARRAGE) then return end
    print("Ora ora!")
  end,

  [Enum.KeyCode.Q] = function()
    if CooldownMap:Check("StandSummon", COOLDOWN_TOGGLE) then return end
    CooldownMap:Set("Global", COOLDOWN_GLOBAL) -- lets the animation play first
    print("Stand toggled...")
  end,

}

UserInputService.InputBegan:Connect(function(userInput, gameProcessed)
  if gameProcessed then return end
  if KeyAbilities[userInput.KeyCode] then
    KeyAbilities[userInput.KeyCode]()
  end
end)

Of course there are many other ways you can implement this, but if I had to give some tips, I would advise the following :

  • If you are checking a cooldown first on the client, then playing an animation, visual, and then firing a remote event, I recommend making the cooldown on the server smaller (just by 0.05 seconds is enough really) to prevent any small time discrepancies from causing nothing to happen on the server, while the client thinks it should have. (I read over this again and feel like clarifying, this only happens if a player sends input during a lag spike, causing the client on the cooldown to be out of sync with the one on the server. However under most circumstances, unless the player is triggering the cooldown immediately one after the next, this usually shouldn’t be a problem.)

  • If you want to just check a cooldown without modifying it, simply do not specify a time parameter and it will return true/false without editing any cooldowns.

  • For more complex implemenations like gui buttons that display a cooldown ticker, I recommend a seperate implementation using RunService.Heartbeat delta and subtracting from a seperate value to be used for a visual ticker or color change in said button.

  • In most implementations, adding the cooldown is as simple as doing if cooldownMap:Check("Example", 1) then return end

  • CooldownMap supports any datatype, but I recommend using strings for readability. Instances will work but can be memory leaked by tables, so be careful with that!

  • For checking cooldowns related to player interaction, I recommend concatenating the player’s UserId with a string saying what the cooldown is for. This could range from anything to a hitbox cooldown to a trade request.

Source :

local CooldownMap = {}
CooldownMap.__index = CooldownMap

local clock = os.clock

function CooldownMap:Check(name, length)
	if (not self._cooldowns[name]) or (self._cooldowns[name] < clock()) then
		if length then
			self._cooldowns[name] = clock() + length
		end
		return false
	else
		return self._cooldowns[name] - clock()
	end
end

function CooldownMap:Set(name, length)
	self._cooldowns[name] = clock() + length
end

function CooldownMap:Destroy()
	for k, v in pairs(self._cooldowns) do
		self._cooldowns[k] = nil
	end
	for k, v in pairs(self) do
		self[k] = nil
	end
end

function CooldownMap.new()
	return setmetatable({_cooldowns = {}}, CooldownMap)
end

return CooldownMap

Update Log :

  • v1.1
    – Now uses os.clock() instead of time()
    – Added Destroy clean-up function
    – Now returns time left on cooldown instead of true

Foreword :

Yes, I know making a cooldown or a debounce is easy. The point of this is to make it CLEANER, not easier. If you have a game where the player has up to 8 different abilities (like a lot of jojo/anime fighting games on Roblox nowadays), using a module like this will reduce the your script length and increase readability immensely.

A lot of devs also use wait(n) for their cooldowns, which like I said at the top of the post, can be very problematic on slow servers, since wait isn’t guaranteed to wait the correct amount of time.

18 Likes

this seems really unnessacary. i get wait can have a problem but im pretty sure corutine still is fine.

1 Like

Please read the foreword, I tried to explain all of this. Co routines are great but are a different functionality of Lua altogether and aren’t great for cooldowns if you’re just coroutine wrapping your waits.

9 Likes

Is os.clock really more precise than time? I ran a test script and this is the output:

189154.0308115 104.95833883528
-- ^ os.clock  ^ time

You can see that time has more decimals than os.clock by 4 decimal places

3 Likes

you said wait(n) isnt exact… But doesnt task.wait() fix that problem?

This isnt getting enough credit, thanks man!!!

Why are you banned? And thanks for the cooldown module Ill learn from it and make my own even better.

do this

function CooldownMap:Destroy()
	setmetatable(self, nil)
end