[Tutorial] Better Cooldowns

Discussion in 'Resources' started by Loogeh, Aug 7, 2013.

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

    Loogeh

    Hello,
    I saw another tutorial on cooldowns which display the time left on it but I didn't like them that much (personal opinion) because they would create lots of tasks and don't display an accurate time. The difference is that with mine every 1 tick (customizable) the server checks all of the cooldowns which are active and compares time instead of using a repeating scheduler to subtract from an integer.

    The way I do it you only need one repeating task and it gives you an accurate time until the cooldown is completed.

    First you'll need to write out two methods. Mine are in my utilMath and utilTime classes but it doesn't matter where you put them.

    The first is a cut/trim method
    class utilMath
    Code:
        public static double trim(double untrimmeded, int decimal) {
            String format = "#.#";
     
            for(int i = 1; i < decimal; i++) {
                format = format + "#";
            }
            DecimalFormat twoDec = new DecimalFormat(format);
            return Double.valueOf(twoDec.format(untrimmeded)).doubleValue();
        }
    Next is a class to convert milliseconds into seconds, minutes, hours or days.

    TimeUnit enum
    class utilTime
    Code:
        public static enum TimeUnit {
            BEST,
            DAYS,
            HOURS,
            MINUTES,
            SECONDS,
    }
    The BEST unit will just convert milliseconds into whatever suits it best. Example 59000 milliseconds would be converted to seconds but 74000 would be converted to minutes.

    The convert method

    Code:
        public static double convert(long time, TimeUnit unit, int decPoint) {
            if(unit == TimeUnit.BEST) {
                if(time < 60000L) unit = TimeUnit.SECONDS;
                else if(time < 3600000L) unit = TimeUnit.MINUTES;
                else if(time < 86400000L) unit = TimeUnit.HOURS;
                else unit = TimeUnit.DAYS;
            }
            if(unit == TimeUnit.SECONDS) return utilMath.trim(time / 1000.0D, decPoint);
            if(unit == TimeUnit.MINUTES) return utilMath.trim(time / 60000.0D, decPoint);
            if(unit == TimeUnit.HOURS) return utilMath.trim(time / 3600000.0D, decPoint);
            if(unit == TimeUnit.DAYS) return utilMath.trim(time / 86400000.0D, decPoint);
            return utilMath.trim(time, decPoint);
        }
    Next we need to create two classes. I called mine AbilityCooldown and Cooldown but you can name them whatever you want to.

    In your first class create 4 variables like this
    class AbilityCooldown
    Code:
        public String ability = "";
        public String player = "";
        public long seconds;
        public long systime;
    The ability is just the name of the cooldown. Player and Seconds are pretty self explanatory and systime is the System.currentTimeMillis() when the cooldown was initiated.

    Next create two constructors. The first one is the main one

    Code:
        public AbilityCooldown(String player, long seconds, long systime) {
            this.player = player;
            this.seconds = seconds;
            this.systime = systime;
        }
    This one stores the cooldown

    The next constructor is later used to access a player's cooldowns.

    Code:
    public AbilityCooldown(String player) {
            this.player = player;
        }
    We also need to create a HashMap which stores each player's cooldowns

    Code:
    public HashMap<String, AbilityCooldown> cooldownMap = new HashMap<String, AbilityCooldown>();
    Now, we have to make the second class. This class creates cooldowns, get's the remaining time of a cooldown, handle's cooldowns which need to be removed and can check if a cooldown is cooling for a specific player.


    Once you've created this class the first thing you do is create a new HashMap
    class Cooldown
    Code:
    public static HashMap<String, AbilityCooldown> cooldownPlayers = new HashMap<String, AbilityCooldown>();
    This HashMap stores the second AbilityCooldown constructor.

    Within the new class file create a method called add
    This is the method which is used to add a cooldown for a player.

    Code:
        public static void add(String player, String ability, long seconds, long systime) {
            if(!cooldownPlayers.containsKey(player)) cooldownPlayers.put(player, new AbilityCooldown(player));
            if(isCooling(player, ability)) return;
            cooldownPlayers.get(player).cooldownMap.put(ability, new AbilityCooldown(player, seconds * 1000, System.currentTimeMillis()));
        }
    Then, we'll make a method for checking if a cooldown is active for a specific player.

    Code:
    public static boolean isCooling(String player, String ability) {
    if(!cooldownPlayers.containsKey(player)) return false;
    if(!cooldownPlayers.get(player).cooldownMap.containsKey(ability)) return false;
    return true;
    }
    Now the good part, calculating the time remaining in a cooldown. Only takes a quick 5 lines of code.

    Code:
        public static double getRemaining(String player, String ability) {
            if(!cooldownPlayers.containsKey(player)) return 0.0;
            if(!cooldownPlayers.get(player).cooldownMap.containsKey(ability)) return 0.0;
            return utilTime.convert((cooldownPlayers.get(player).cooldownMap.get(ability).seconds + cooldownPlayers.get(player).cooldownMap.get(ability).systime) - System.currentTimeMillis(), TimeUnit.SECONDS, 1);
        }
    This method is just used for sending a player the time remaining in their cooldown.

    Code:
        public static void coolDurMessage(Player player, String ability) {
            if(player == null) {
                return;
            }
            if(!isCooling(player.getName(), ability)) {
                return;
            }
            player.sendMessage(ChatColor.GRAY + ability + " Cooldown " + ChatColor.AQUA + getRemaining(player.getName(), ability) + " Seconds");
        }
    The method used for removing a cooldown from a player.

    Code:
        public static void removeCooldown(String player, String ability) {
            if(!cooldownPlayers.containsKey(player)) {
                return;
            }
            if(!cooldownPlayers.get(player).cooldownMap.containsKey(ability)) {
                return;
            }
            cooldownPlayers.get(player).cooldownMap.remove(ability);
            Player cPlayer = Bukkit.getPlayer(player);
            if(player != null) {
                player.sendMessage(ChatColor.GRAY + "You can now use " + ChatColor.AQUA + ability);
            }
        }
    Next, the final method!
    This method just iterates through all the cooldowns, compares the time and if it's less than or equal to 0.0 then it removes the cooldown from it's owner and notifies them.

    Code:
        public static void handleCooldowns() {
            if(cooldownPlayers.isEmpty()) {
                return;
            }
            for(Iterator<String> it = cooldownPlayers.keySet().iterator(); it.hasNext();) {
                String key = it.next();
                for(Iterator<String> iter = cooldownPlayers.get(key).cooldownMap.keySet().iterator(); iter.hasNext();) {
                    String name = iter.next();
                    if(getRemaining(key, name) <= 0.0) {
                        removeCooldown(key, name);
                    }
                }
            }
        }
    Once you've written (or copy and pasted :p) it all we still need to do one more thing. But it's easy.

    Go to your main class and create a repeating scheduler for every 1 tick. I believe you could also use every 2 ticks since my cooldowns display the decimal in tenths of a second. Inside the scheduler put the handleCooldowns() method.
    class YourMainClass (in the onEnable())
    Code:
            Bukkit.getServer().getScheduler().scheduleSyncRepeatingTask(plugin, new Runnable() {
         
                @Override
                public void run() {
                    Cooldown.handleCooldowns();
                }
            }, 1L, 1L);
    That's all the coding for the cooldowns but if you don't know how to use it then this will show you.

    Code:
                if(Cooldown.isCooling(player.getName(), "yourcooldownnamehere")) {
                    Cooldown.coolDurMessage(player, "yourcooldownnamehere");
                    return;
                }
                player.addPotionEffect(new PotionEffect(PotionEffectType.INCREASE_DAMAGE, 140, 0));
                Cooldown.add(player.getName(), "yourcooldownnamehere", 16, System.currentTimeMillis());
    You could put that inside a PlayerInteractEvent. All it does is check if the cooldown yourcooldownnamehere is cooling and if it's not then it adds the potion effect Strength 1 to the player but if it is cooling then it sends the player a message containing the cooldown name and time remaining in seconds.

    I hope i've helped people out in this tutorial. I know lots of people want to do this but just don't know how. You could also use this to help you figure out how to do temp bans (but I will say now that you cannot put a cooldown on a player which bans then because all of the cooldowns are cleared when the server reloads or restarts).

    Goodluck!

    EDIT: Added the class names for each section because it was requested + I changed all round() to trim() because of confusion

    [​IMG]

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

    bob7

    Pretty cool! I simply use mills to get the remaining time. Like system.getmills - oldmills /1000 would give me the remaining time in seconds :)
     
    dxrknez likes this.
  3. Offline

    Loogeh

    bob7 Yeah that's basically what I do but mine just has more options with units :D

    Edit: Just realized it's not exactly how I do it but the last part still is true
     
  4. Offline

    Loogeh

    SkillSam Oh sorry, while I was making this tutorial I changed the round method to trim because it's not actually rounding it, it's just trimming it so there's less decimal places. Just either change the name of the trim method to round (which isn't really an accurate description of the method) or change all the places where round is used to trim
     
  5. Offline

    SkillSam

    Ah, okay. And what about variable here?
     
  6. Offline

    Loogeh

    SkillSam That was just an example. The variable could be set to the ability variable if you want or it could be set to something else, of your choice. Sorry for the confusion, I think i'll change that
     
  7. Offline

    SkillSam

    Loogeh Oh, okay. I see. Thanks for the tutorial! :D

    Edit: Apparently, after removing the variable component and adding the ability one, now it's saying that the method sendMessage(String) is undefined for the type String...

    Edit 2: Lol, nevermind. I had to change player to cPlayer
     
  8. Offline

    xXMinecraftXx

    How do i need to get the utilMath.round() imported? I don't see any imports in Eclipse fixes ?
    Loogeh
     
  9. Offline

    Loogeh

    xXMinecraftXx Sorry, the utilMath.round() is from my code. The utilMath.round() method is just the trim() method from the top. I changed the name in this tutorial because it's not actually rounding the number, rather, it's just trimming down the length of it. I'll fix it tomorrow.
    Just change everywhere it says utilMath.round() to yourclasshere.trim().
     
  10. Offline

    ajs333

    Ok, I tried this but uhh nothing worked so can you post what it should be!
     
  11. Offline

    Dahwn

  12. Offline

    viper_monster

    Dahwn use "." instead of ","
     
  13. Offline

    Dahwn

    spoljo666
    I use that always, I didn't replace anything. It seems the server/system does it on it's own?
    -Dahwn
     
  14. Offline

    viper_monster

    Dahwn could you show us some code where you use this?
     
  15. Offline

    Dahwn

    spoljo666
    I only use the code Loogeh posted above in a PlayerInteractEvent
    Code:java
    1. if (p.getInventory().getItemInHand().getType() == Material.COAL) {
    2. if (e.getAction() == Action.RIGHT_CLICK_AIR || e.getAction() == Action.RIGHT_CLICK_BLOCK) {
    3. if ((p.getLocation().getBlock().getRelative(BlockFace.DOWN).getType() != Material.LEAVES) || (p.getLocation().getBlock().getRelative(BlockFace.DOWN).getType() != Material.LEAVES_2) || (p.getWorld().getBlockAt(new Location(p.getWorld(), p.getLocation().getX(), p.getLocation().getY()+4, p.getLocation().getZ())).getType() != Material.STONE)) {
    4.  
    5. if(Cooldown.isCooling(p.getName(), "Seismic Wave")) {
    6. Cooldown.coolDurMessage(p, "Seismic Wave");
    7. return;
    8. }
    9. Cooldown.add(p.getName(), "Seismic Wave", 15, System.currentTimeMillis());
    10. //only some of the code because I don't want to paste all!
    11.  


    -Dahwn
     
  16. Offline

    viper_monster

    Dahwn could you please post line 14 in utilMath and line 20 in utilTime classes?
     
  17. Offline

    Dahwn

    spoljo666
    Sure but I bet both are the same as posted above! xD
    utilMath line 14:
    Code:java
    1. return Double.valueOf(twoDec.format(untrimmeded)).doubleValue();


    utilTime line 20:
    Code:java
    1. if(unit == TimeUnit.SECONDS) return utilMath.trim(time / 1000.0D, decPoint);
     
  18. Offline

    Loogeh

    Dahwn That's really weird, I use this code and it still works flawlessly for me. Can you post all of your code for cooldowns please? There must be something else doing it
     
  19. Offline

    Dahwn

    Loogeh
    There you go:
    utilMath:
    Code:java
    1. package me.mm98.Util;
    2.  
    3. import java.text.DecimalFormat;
    4.  
    5. public class utilMath {
    6.  
    7. public static double trim(double untrimmeded, int decimal) {
    8. String format = "#.#";
    9.  
    10. for(int i = 1; i < decimal; i++) {
    11. format = format + "#";
    12. }
    13. DecimalFormat twoDec = new DecimalFormat(format);
    14. return Double.valueOf(twoDec.format(untrimmeded)).doubleValue();
    15. }
    16. }
    17.  

    utilTime:
    Code:java
    1. package me.mm98.Util;
    2.  
    3. public class utilTime {
    4.  
    5. public static enum TimeUnit {
    6. BEST,
    7. DAYS,
    8. HOURS,
    9. MINUTES,
    10. SECONDS,
    11. }
    12.  
    13. public static double convert(long time, TimeUnit unit, int decPoint) {
    14. if(unit == TimeUnit.BEST) {
    15. if(time < 60000L) unit = TimeUnit.SECONDS;
    16. else if(time < 3600000L) unit = TimeUnit.MINUTES;
    17. else if(time < 86400000L) unit = TimeUnit.HOURS;
    18. else unit = TimeUnit.DAYS;
    19. }
    20. if(unit == TimeUnit.SECONDS) return utilMath.trim(time / 1000.0D, decPoint);
    21. if(unit == TimeUnit.MINUTES) return utilMath.trim(time / 60000.0D, decPoint);
    22. if(unit == TimeUnit.HOURS) return utilMath.trim(time / 3600000.0D, decPoint);
    23. if(unit == TimeUnit.DAYS) return utilMath.trim(time / 86400000.0D, decPoint);
    24. return utilMath.trim(time, decPoint);
    25. }
    26.  
    27. }
    28.  

    AbilityCooldown:
    Code:java
    1. package me.mm98.Util;
    2.  
    3. import java.util.HashMap;
    4.  
    5. public class AbilityCooldown {
    6.  
    7. public String ability = "";
    8. public String player = "";
    9. public long seconds;
    10. public long systime;
    11.  
    12. public HashMap<String, AbilityCooldown> cooldownMap = new HashMap<String, AbilityCooldown>();
    13.  
    14. public AbilityCooldown(String player, long seconds, long systime) {
    15. this.player = player;
    16. this.seconds = seconds;
    17. this.systime = systime;
    18. }
    19.  
    20. public AbilityCooldown(String player) {
    21. this.player = player;
    22. }
    23.  
    24. }
    25.  

    Cooldown:
    Code:java
    1. package me.mm98.Util;
    2.  
    3. import java.util.HashMap;
    4. import java.util.Iterator;
    5.  
    6. import me.mm98.Util.utilTime.TimeUnit;
    7.  
    8. import org.bukkit.Bukkit;
    9. import org.bukkit.ChatColor;
    10. import org.bukkit.entity.Player;
    11.  
    12. public class Cooldown {
    13.  
    14. public static HashMap<String, AbilityCooldown> cooldownPlayers = new HashMap<String, AbilityCooldown>();
    15.  
    16. public static void add(String player, String ability, long seconds, long systime) {
    17. if(!cooldownPlayers.containsKey(player)) cooldownPlayers.put(player, new AbilityCooldown(player));
    18. if(isCooling(player, ability)) return;
    19. cooldownPlayers.get(player).cooldownMap.put(ability, new AbilityCooldown(player, seconds * 1000, System.currentTimeMillis()));
    20. }
    21.  
    22. public static boolean isCooling(String player, String ability) {
    23. if(!cooldownPlayers.containsKey(player)) return false;
    24. if(!cooldownPlayers.get(player).cooldownMap.containsKey(ability)) return false;
    25. return true;
    26. }
    27.  
    28. public static double getRemaining(String player, String ability) {
    29. if(!cooldownPlayers.containsKey(player)) return 0.0;
    30. if(!cooldownPlayers.get(player).cooldownMap.containsKey(ability)) return 0.0;
    31. return utilTime.convert((cooldownPlayers.get(player).cooldownMap.get(ability).seconds + cooldownPlayers.get(player).cooldownMap.get(ability).systime) - System.currentTimeMillis(), TimeUnit.SECONDS, 1);
    32. }
    33.  
    34. public static void coolDurMessage(Player player, String ability) {
    35. if(player == null) {
    36. return;
    37. }
    38. if(!isCooling(player.getName(), ability)) {
    39. return;
    40. }
    41. player.sendMessage(ChatColor.GRAY + ability + " Cooldown " + ChatColor.AQUA + getRemaining(player.getName(), ability) + " Seconds");
    42. }
    43.  
    44. public static void removeCooldown(String player, String ability) {
    45. if(!cooldownPlayers.containsKey(player)) {
    46. return;
    47. }
    48. if(!cooldownPlayers.get(player).cooldownMap.containsKey(ability)) {
    49. return;
    50. }
    51. cooldownPlayers.get(player).cooldownMap.remove(ability);
    52. Player cPlayer = Bukkit.getPlayer(player);
    53. if(player != null) {
    54. cPlayer.sendMessage(ChatColor.GRAY + "You can now use " + ChatColor.AQUA + ability);
    55. }
    56. }
    57.  
    58. public static void handleCooldowns() {
    59. if(cooldownPlayers.isEmpty()) {
    60. return;
    61. }
    62. for(Iterator<String> it = cooldownPlayers.keySet().iterator(); it.hasNext();) {
    63. String key = it.next();
    64. for(Iterator<String> iter = cooldownPlayers.get(key).cooldownMap.keySet().iterator(); iter.hasNext();) {
    65. String name = iter.next();
    66. if(getRemaining(key, name) <= 0.0) {
    67. removeCooldown(key, name);
    68. }
    69. }
    70. }
    71. }
    72. }
    73.  

    InteractListener where I use it:
    Code:java
    1. @EventHandler
    2. public void Interact(PlayerInteractEvent e) {
    3.  
    4. final Player p = e.getPlayer();
    5. PvpPlayer pvpp = Kits.getInstance().getPvpPlayer(p);
    6.  
    7. if (pvpp.getKit() == Kit.EARTHQUAKE && Kits.getInstance().allowKit(p.getName())) {
    8. if (p.getInventory().getItemInHand().getType() == Material.COAL) {
    9. if (e.getAction() == Action.RIGHT_CLICK_AIR || e.getAction() == Action.RIGHT_CLICK_BLOCK) {
    10. if ((p.getLocation().getBlock().getRelative(BlockFace.DOWN).getType() != Material.LEAVES) || (p.getLocation().getBlock().getRelative(BlockFace.DOWN).getType() != Material.LEAVES_2) || (p.getWorld().getBlockAt(new Location(p.getWorld(), p.getLocation().getX(), p.getLocation().getY()+4, p.getLocation().getZ())).getType() != Material.STONE)) {
    11.  
    12. if(Cooldown.isCooling(p.getName(), "SeismicWave")) {
    13. Cooldown.coolDurMessage(p, "SeismicWave");
    14. return;
    15. }
    16. Cooldown.add(p.getName(), "SeismicWave", 16, System.currentTimeMillis());
    17.  
    18. final Location center = p.getLocation().getBlock().getRelative(BlockFace.DOWN).getLocation();

    in onEnable():
    Code:java
    1. public void onEnable()
    2. {
    3.  
    4. Bukkit.getServer().getScheduler().scheduleSyncRepeatingTask(this, new Runnable() {
    5.  
    6. @Override
    7. public void run() {
    8. Cooldown.handleCooldowns();
    9. }
    10. }, 1L, 1L);
    11.  
    12.  

    And one of the errors I get every time a number with a decimal point gets displayed which is not zero!
    Code:
    [16:37:34 WARN]: [PlayKits] Task #2 for PlayKits v0.1 generated an exception
    java.lang.NumberFormatException: For input string: "0,5"
            at sun.misc.FloatingDecimal.readJavaFormatString(Unknown Source) ~[?:1.7
    .0_45]
            at java.lang.Double.valueOf(Unknown Source) ~[?:1.7.0_45]
            at me.mm98.Util.utilMath.trim(utilMath.java:14) ~[?:?]
            at me.mm98.Util.utilTime.convert(utilTime.java:20) ~[?:?]
            at me.mm98.Util.Cooldown.getRemaining(Cooldown.java:31) ~[?:?]
            at me.mm98.Util.Cooldown.handleCooldowns(Cooldown.java:66) ~[?:?]
            at me.mm98.PvPKits.Kits$1.run(Kits.java:165) ~[?:?]
            at org.bukkit.craftbukkit.v1_7_R1.scheduler.CraftTask.run(CraftTask.java
    :53) ~[craftbukkit.jar:git-Bukkit-1.7.2-R0.3-b3020jnks]
            at org.bukkit.craftbukkit.v1_7_R1.scheduler.CraftScheduler.mainThreadHea
    rtbeat(CraftScheduler.java:345) [craftbukkit.jar:git-Bukkit-1.7.2-R0.3-b3020jnks
    ]
            at net.minecraft.server.v1_7_R1.MinecraftServer.u(MinecraftServer.java:5
    87) [craftbukkit.jar:git-Bukkit-1.7.2-R0.3-b3020jnks]
            at net.minecraft.server.v1_7_R1.DedicatedServer.u(DedicatedServer.java:2
    50) [craftbukkit.jar:git-Bukkit-1.7.2-R0.3-b3020jnks]
            at net.minecraft.server.v1_7_R1.MinecraftServer.t(MinecraftServer.java:5
    45) [craftbukkit.jar:git-Bukkit-1.7.2-R0.3-b3020jnks]
            at net.minecraft.server.v1_7_R1.MinecraftServer.run(MinecraftServer.java
    :457) [craftbukkit.jar:git-Bukkit-1.7.2-R0.3-b3020jnks]
            at net.minecraft.server.v1_7_R1.ThreadServerApplication.run(SourceFile:6
    17) [craftbukkit.jar:git-Bukkit-1.7.2-R0.3-b3020jnks]
    If I right click the item and the cooldown is on 4.0 or 6.0 (example) I get the message. Also then it exists no error! The main system also works so that I can use the item again after the specific amount of time is over and the "you can now use this again" message also works. Just the messages between (for example) 4.0 and 5.0 don't work

    Thanks for your help :/
    -Dahwn

    Loogeh
    NVM. I did only not work on my localhost :confused: It works on the real server (Why? LOL)
    Thanks anyways!
    -Dahwn

    EDIT by Moderator: merged posts, please use the edit button instead of double posting.
     
    Last edited by a moderator: Jun 3, 2016
  20. Offline

    Loogeh

    Dahwn Huh, that's weird. I have no idea why that's happening lol
     
  21. @Dawhn
    Are you inputting 0,5 some how on your localhost...
     
  22. Offline

    Loogeh

    Might come back and rewrite this tutorial because while the code works, it is not as clean and readable as it could be.
     
  23. Offline

    Zahachos

    Loogeh WOW! Epic dude thanks!!
    I transformed it a bit to use it with a temp ban command! :)
     
Thread Status:
Not open for further replies.

Share This Page