Before you start (prerequisites)
You don’t need to have any extensive Java experience, but it helps to understand how some of the basic features work. The main point of this tutorial is to go over the method and it’s implementation, not how Java features like Singletons work. How we want it to work For these examples, we will use the command /cooldown as the feature. It can only be used once every 15 seconds.
Common Incorrect Method: Using Runnable Timer Tasks
Before we get into the most common and performance friendly cooldown system, I want to cover a commonly suggested method that you should avoid. It’s suggested fairly often because of how simple it seems at first.
In this method, your plugin has a Singleton cooldown manager:
It contains the basic methods for setting, getting, and removing from a cooldown map. Next we have the Command Executor:
As you can tell, it seems very simple. The main part of this section is that it decreases the time left before they can use the feature again every second, in the BukkitRunnable task timer. It will work just fine as it is now. This method, however, has some major issues.
For one, you should always avoid using runnables, as async tasks will create a thread for each task. Second, this means if you have several hundred players online, you will have the map updating many, many times a second (assuming the players are using it). For minigames with multiple cooldown features, there can be as many as a hundred tasks running at once, and it can become quite a mess internally.One of the largest drawbacks, however, is that you can only track seconds with this method (at the smallest).
You can’t track anything smaller, and if you wanted to you would have to change the time unit to 1/10s of a second or less, and it would need to be updated 10 times more often. Method #2: Using TimestampsRather than updating a variable every second or more, you can actually just save the last time a player used a feature, then subtract it from the current time to get how long ago the player used the feature. From there, it’s simple to make sure it has been more than x time. It looks something like this:if (current time) – (the time the featured was last used) > (delay) [do feature, update last used time]For accurate time tracking, we can use a feature in programming known as a timestamp.
Timestamps are just like what they sound like – a stamp of the time it was used at. The most accurate type is to use System time milliseconds. Basically, the timestamp is in milliseconds. More technically, how many milliseconds have passed since January 1 1960 (known as epoch).
You can use the function System.currentTimeMillis() to get the current timestamp in milliseconds.We’ll use the CooldownManager from earlier, with two changes. int (or Integer) type can’t store a variable of that size, millisecond timestamps are stored in Long type variables.
Simply replace occurrences of “Integer” or “int” with “Long” in the manager class. Second, instead of returning “0” if the map doesn’t contain the cooldown timestamp, we have to use Long.valueOf(0):
With this method, our CooldownCommand class should look like this:
Already much shorter, and the variable is only updated when the feature is actually used!You’ll notice I used a Java util method you may not have seen before – TimeUnit. TimeUnit is extremely helpful, especially if you don’t want to convert time units yourself. In the above example, we used TimeUnit.MILLISECONDS to tell TimeUnit the input variable will be in milliseconds, then used toSeconds() to get the output in seconds.
With a bit more math, you can convert to hours, subtract the hours from the initial variable, then convert the remainder to seconds. It does take some playing around with to get it down to perfection. There may be some available libraries or functions by other users that does this for you, however.