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 ) 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 EDIT by Moderator: merged posts, please use the edit button instead of double posting.
Pretty cool! I simply use mills to get the remaining time. Like system.getmills - oldmills /1000 would give me the remaining time in seconds
bob7 Yeah that's basically what I do but mine just has more options with units Edit: Just realized it's not exactly how I do it but the last part still is true
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
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
Loogeh Oh, okay. I see. Thanks for the tutorial! 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
How do i need to get the utilMath.round() imported? I don't see any imports in Eclipse fixes ? 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().
Loogeh I tried it but it gives me a NumberFormatException for input string ("every number with decimal point")? http://gyazo.com/006e4de0ae341af4347adc893f6bdbb0 Do you know how to fix it? Because I couldn't find another library which is so good! -Dahwn
spoljo666 I use that always, I didn't replace anything. It seems the server/system does it on it's own? -Dahwn
spoljo666 I only use the code Loogeh posted above in a PlayerInteractEvent Code:java if (p.getInventory().getItemInHand().getType() == Material.COAL) { if (e.getAction() == Action.RIGHT_CLICK_AIR || e.getAction() == Action.RIGHT_CLICK_BLOCK) { 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)) { if(Cooldown.isCooling(p.getName(), "Seismic Wave")) { Cooldown.coolDurMessage(p, "Seismic Wave"); return; } Cooldown.add(p.getName(), "Seismic Wave", 15, System.currentTimeMillis());//only some of the code because I don't want to paste all! -Dahwn
spoljo666 Sure but I bet both are the same as posted above! xD utilMath line 14: Code:java return Double.valueOf(twoDec.format(untrimmeded)).doubleValue(); utilTime line 20: Code:java if(unit == TimeUnit.SECONDS) return utilMath.trim(time / 1000.0D, decPoint);
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
Loogeh There you go: utilMath: Code:java package me.mm98.Util; import java.text.DecimalFormat; public class utilMath { 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(); }} utilTime: Code:java package me.mm98.Util; public class utilTime { public static enum TimeUnit { BEST, DAYS, HOURS, MINUTES, SECONDS,} 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); } } AbilityCooldown: Code:java package me.mm98.Util; import java.util.HashMap; public class AbilityCooldown { public String ability = ""; public String player = ""; public long seconds; public long systime; public HashMap<String, AbilityCooldown> cooldownMap = new HashMap<String, AbilityCooldown>(); public AbilityCooldown(String player, long seconds, long systime) { this.player = player; this.seconds = seconds; this.systime = systime; } public AbilityCooldown(String player) { this.player = player; } } Cooldown: Code:java package me.mm98.Util; import java.util.HashMap;import java.util.Iterator; import me.mm98.Util.utilTime.TimeUnit; import org.bukkit.Bukkit;import org.bukkit.ChatColor;import org.bukkit.entity.Player; public class Cooldown { public static HashMap<String, AbilityCooldown> cooldownPlayers = new HashMap<String, AbilityCooldown>(); 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())); } 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; } 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); } 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"); } 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) { cPlayer.sendMessage(ChatColor.GRAY + "You can now use " + ChatColor.AQUA + ability); } } 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); } } } }} InteractListener where I use it: Code:java @EventHandler public void Interact(PlayerInteractEvent e) { final Player p = e.getPlayer(); PvpPlayer pvpp = Kits.getInstance().getPvpPlayer(p); if (pvpp.getKit() == Kit.EARTHQUAKE && Kits.getInstance().allowKit(p.getName())) { if (p.getInventory().getItemInHand().getType() == Material.COAL) { if (e.getAction() == Action.RIGHT_CLICK_AIR || e.getAction() == Action.RIGHT_CLICK_BLOCK) { 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)) { if(Cooldown.isCooling(p.getName(), "SeismicWave")) { Cooldown.coolDurMessage(p, "SeismicWave"); return; } Cooldown.add(p.getName(), "SeismicWave", 16, System.currentTimeMillis()); final Location center = p.getLocation().getBlock().getRelative(BlockFace.DOWN).getLocation(); in onEnable(): Code:java public void onEnable() { Bukkit.getServer().getScheduler().scheduleSyncRepeatingTask(this, new Runnable() { @Override public void run() { Cooldown.handleCooldowns(); } }, 1L, 1L); 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 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.
Might come back and rewrite this tutorial because while the code works, it is not as clean and readable as it could be.