Tutorial Creating player cooldowns

Discussion in 'Resources' started by Dragonphase, Aug 24, 2014.

Thread Status:
Not open for further replies.
  1. Offline

    Dragonphase

    A lot of people seem to think that in order to simulate a cooldown for players (a length of time one must wait in order to repeat a certain action), they have to use the BukkitScheduler's runTaskLater method. The issue with this is that, although it will work, it is extremely inefficient and there is a much cleaner way of going about it. This tutorial is going to teach you how to properly store and use cooldowns for players.

    The first thing we'll do is create an abstract class which can be used to store the name and cooldown we want to apply.

    Delayable.java:

    Code:java
    1. import org.bukkit.configuration.serialization.ConfigurationSerializable;
    2.  
    3. import java.util.UUID;
    4.  
    5. public abstract class Delayable implements ConfigurationSerializable {
    6.  
    7. private UUID uniqueId;
    8.  
    9. private String name;
    10. private long cooldown;
    11.  
    12. public Delayable(String name, long cooldown) {
    13. this(name, cooldown, UUID.randomUUID());
    14. }
    15.  
    16. public Delayable(String name, long cooldown, UUID uniqueId) {
    17. setName(name);
    18. setCooldown(cooldown);
    19. setUniqueId(uniqueId);
    20. }
    21.  
    22. public UUID getUniqueId() {
    23. return uniqueId;
    24. }
    25.  
    26. private void setUniqueId(UUID uniqueId) {
    27. this.uniqueId = uniqueId;
    28. }
    29.  
    30. public String getName() {
    31. return name;
    32. }
    33.  
    34. public void setName(String name) {
    35. this.name = name;
    36. }
    37.  
    38. public long getCooldown() {
    39. return cooldown;
    40. }
    41.  
    42. public void setCooldown(long cooldown) {
    43. this.cooldown = cooldown;
    44. }
    45. }
    46.  


    Because it's abstract, we can't instantiate it. The Delayable class is merely a base class that is used to store information that all subclasses will have, for example, a name and a cooldown. I've also included a UUID, which will be extremely useful to store in our HashMap of cooldowns. I've even made it implement ConfigurationSerializable, since it would come in handy for this tutorial anyways. ConfigurationSerializable lets us store and retrieve information related to a custom object via .yml configuration files.

    The next thing we want to do is create our Kit class. The reason I'm using a Kit is because it's a fairly common topic within the Bukkit community. You can easily adapt this to suit your needs, and create a DelayedCommand class for example, instead.

    Kit.java:

    Code:java
    1. import org.bukkit.inventory.ItemStack;
    2.  
    3. import java.util.*;
    4.  
    5. public class Kit extends Delayable {
    6.  
    7. private ItemStack[] contents;
    8.  
    9. public Kit(String name, long cooldown, ItemStack[] contents) {
    10. super(name, cooldown);
    11. setContents(contents);
    12. }
    13.  
    14. public Kit(String name, long cooldown, ItemStack[] contents, UUID uuid) {
    15. super(name, cooldown, uuid);
    16. setContents(contents);
    17. }
    18.  
    19. public ItemStack[] getContents() {
    20. return contents;
    21. }
    22.  
    23. public void setContents(ItemStack[] contents) {
    24. this.contents = contents;
    25. }
    26.  
    27. @Override
    28. public Map<String, Object> serialize() {
    29. Map<String, Object> result = new HashMap<String, Object>();
    30.  
    31. result.put("uuid", getUniqueId());
    32. result.put("name", getName());
    33. result.put("cooldown", getCooldown());
    34. result.put("contents", getContents());
    35.  
    36. return result;
    37. }
    38.  
    39. public static Kit deserialize(Map<String, Object> args) {
    40. String name = (String) args.get("name");
    41. Long cooldown = (Long) args.get("cooldown");
    42.  
    43. List<ItemStack> itemList = (ArrayList<ItemStack>) args.get("contents");
    44. ItemStack[] contents = itemList.toArray(new ItemStack[itemList.size()]);
    45.  
    46. UUID uniqueId = UUID.fromString((String) args.get("uuid"));
    47.  
    48. return new Kit(name, cooldown, contents, uniqueId);
    49. }
    50. }
    51.  


    As you can see, it's a really basic class which stores an ItemStack array. Since the abstract class Delayable implements ConfigurationSerializable, we need to implement the methods here.

    The next thing we need to do is create a class for Players that stores all of their current cooldowns.

    DelayedPlayer.java:

    Code:java
    1. import org.bukkit.configuration.serialization.ConfigurationSerializable;
    2. import org.bukkit.entity.Player;
    3.  
    4. import java.util.HashMap;
    5. import java.util.Map;
    6. import java.util.UUID;
    7.  
    8. public class DelayedPlayer implements ConfigurationSerializable{
    9.  
    10. private UUID playerUniqueId;
    11.  
    12. private HashMap<UUID, Long> delays;
    13.  
    14. public DelayedPlayer(Player player) {
    15. playerUniqueId = player.getUniqueId();
    16. delays = new HashMap<UUID, Long>();
    17. }
    18.  
    19. public DelayedPlayer(UUID uuid, HashMap<UUID, Long> delays) {
    20. this.playerUniqueId = uuid;
    21. this.delays = new HashMap<UUID, Long>(delays);
    22. }
    23.  
    24. public UUID getUniqueId() {
    25. return playerUniqueId;
    26. }
    27.  
    28. public HashMap<UUID, Long> getDelays() {
    29. return delays;
    30. }
    31.  
    32. public void addDelay(Delayable delayable) {
    33. getDelays().put(delayable.getUniqueId(), System.currentTimeMillis());
    34. }
    35.  
    36. public void removeDelay(Delayable delayable) {
    37. if (getDelays().containsKey(delayable.getUniqueId())) {
    38. getDelays().remove(delayable.getUniqueId());
    39. }
    40. }
    41.  
    42. public boolean isPlayerDelayed(Delayable delayable) {
    43. boolean delayed = false;
    44.  
    45. if (getDelays().containsKey(delayable.getUniqueId())) {
    46. delayed = System.currentTimeMillis() - getDelays().get(delayable.getUniqueId()) < delayable.getCooldown();
    47.  
    48. if (!delayed) {
    49. removeDelay(delayable);
    50. }
    51. }
    52.  
    53. return delayed;
    54. }
    55.  
    56. public Long getRemainingTime(Delayable delayable){
    57. if (!isPlayerDelayed(delayable)) return 0L;
    58.  
    59. return delayable.getCooldown() - (System.currentTimeMillis() - getDelays().get(delayable.getUniqueId()));
    60. }
    61.  
    62. @Override
    63. public Map<String, Object> serialize() {
    64. Map<String, Object> result = new HashMap<String, Object>();
    65.  
    66. Map<String, Long> delays = new HashMap<String, Long>();
    67.  
    68. for (UUID uuid : getDelays().keySet()) {
    69. delays.put(uuid.toString(), getDelays().get(uuid));
    70. }
    71.  
    72. result.put("uuid", getUniqueId());
    73. result.put("delays", delays);
    74.  
    75. return result;
    76. }
    77.  
    78. public static DelayedPlayer deserialize(Map<String, Object> args) {
    79. UUID uniqueId = UUID.fromString((String) args.get("uuid"));
    80.  
    81. HashMap<String, Long> stringDelays = args.get("delays") != null && args.get("delays") instanceof HashMap
    82. ? (HashMap<String, Long>) args.get("delays")
    83. : new HashMap<String, Long>();
    84. HashMap<UUID, Long> delays = new HashMap<UUID, Long>();
    85.  
    86. for (String uuid : stringDelays.keySet()) {
    87. delays.put(UUID.fromString(uuid), stringDelays.get(uuid));
    88. }
    89.  
    90. return new DelayedPlayer(uniqueId, delays);
    91. }
    92. }
    93.  


    Since ConfigurationSerialization won't allow us to store raw UUIDs, we have to store the delays with the key as a String using uuid.toString(). This class is designed so that we can easily store and retrieve a list of DelayedPlayers via the ConfigurationSerializable interface, which will come in useful when you want cooldowns to persist through server reloads and restarts.

    The final thing we need to do is create a class to manage all of our delayed players.

    CooldownManager.java:

    Code:java
    1. import org.bukkit.entity.Player;
    2. import org.bukkit.plugin.java.JavaPlugin;
    3.  
    4. import java.util.ArrayList;
    5. import java.util.List;
    6.  
    7. public class CooldownManager {
    8.  
    9. private JavaPlugin plugin;
    10.  
    11. private List<DelayedPlayer> delayedPlayers;
    12.  
    13. @SuppressWarnings("unchecked")
    14. public CooldownManager(JavaPlugin instance) {
    15. plugin = instance;
    16.  
    17. delayedPlayers = new ArrayList<DelayedPlayer>();
    18.  
    19. if (plugin.getConfig().contains("delayed-players")) {
    20. try {
    21. delayedPlayers = (ArrayList<DelayedPlayer>) plugin.getConfig().get("delayed-players");
    22. } catch (Exception ex) {
    23. ex.printStackTrace();
    24. }
    25. }
    26. }
    27.  
    28. public List<DelayedPlayer> getDelayedPlayers() {
    29. return delayedPlayers;
    30. }
    31.  
    32. public DelayedPlayer getDelayedPlayer(Player player) {
    33. for (DelayedPlayer delayedPlayer : getDelayedPlayers()) {
    34. if (delayedPlayer.getUniqueId().equals(player.getUniqueId())) {
    35. return delayedPlayer;
    36. }
    37. }
    38.  
    39. DelayedPlayer delayedPlayer = new DelayedPlayer(player);
    40. getDelayedPlayers().add(delayedPlayer);
    41.  
    42. return delayedPlayer;
    43. }
    44.  
    45. }
    46.  


    To use the CooldownManager, you simply need to declare one in your Main class and instantiate it in your onEnable method, like so. You should also register all classes that implement ConfigurationSerializable with ConfigurationSerialization:

    Code:java
    1. private CooldownManager cooldownManager;
    2.  
    3. public void onEnable() {
    4. registerConfigurationSerializables();
    5.  
    6. cooldownManager = new CooldownManager(this);
    7. }
    8.  
    9. private void registerConfigurationSerializables() {
    10. ConfigurationSerialization.registerClass(Kit.class);
    11. ConfigurationSerialization.registerClass(DelayedPlayer.class);
    12. }
    13.  
    14. public CooldownManager getCooldownManager() {
    15. return cooldownManager;
    16. }
    17.  


    It might seem like a lot of code for a simple cooldown, but it's extremely versatile and lets you manage more than one cooldown for more than one object, as long as the object implements the Delayable class. Here's how you would go about creating a Kit with a specific cooldown, and adding that cooldown to that player. The kit has a cooldown of 60000 milliseconds, or 60 seconds:

    Code:java
    1. Kit kit = new Kit("My awesome kit", 60000, new ItemStack[]{new ItemStack(Material.DIAMOND)});
    2. player.getInventory().addItem(kit.getContents());
    3.  
    4. DelayedPlayer delayedPlayer = plugin.getCooldownManager().getDelayedPlayer(player);
    5. delayedPlayer.addDelay(kit);
    6.  
    7. if (delayedPlayer.isPlayerDelayed(kit)) {
    8. player.sendMessage("You are delayed for this kit.");
    9. }
    10.  


    Finally, as an added bonus, most of you would be wondering how to retrieve the remaining time left on a cooldown for a specific Delayable object in a more readable format, rather than in milliseconds. For this, I have created a simple Utility class that you can use, which is available here:

    Time Utility class

    To use it, you would do something along the lines of this:

    Code:java
    1. String remainingTime = Utils.getTime(delayedPlayer.getRemainingTime(kit), false);
    2.  
    3. player.sendMessage("You must wait " + remainingTime);


    If I missed something out, please feel free to comment. I should apologize for this thread being extremely long.

    EDIT by Moderator: merged posts, please use the edit button instead of double posting.
     
    Last edited by a moderator: Jun 10, 2016
    Goblom and TheMintyMate like this.
  2. Offline

    ABDTGaming

    Nice tutorial! I'm glad people are trying to get others away from using DelayedTasks
     
  3. Offline

    Regablith

    ABDTGaming DelayedTasks are love, DelayedTasks are lyfe ^_^

    But anyways, very nice! :)
     
  4. Offline

    PandazNWafflez

    Regablith no they aren't, they slow down servers.
     
Thread Status:
Not open for further replies.

Share This Page