Solved Randomising Runnable Timer Length

Discussion in 'Plugin Development' started by StormCoreFilms, Jul 7, 2013.

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

    StormCoreFilms

    Hello,

    I've set up a runnable timer that prints out a message when activated (as a test). The plugin is supposed to spawn chests at random times (I've taken out the chest-spawning code for simplicity). I can get the chests to spawn at a constant time, but I am unable to get the timer to be random every time. It seems that once I set a random number for the variable it remains the same, even if I change it in the timer.

    Code:java
    1. @Override
    2. public void onEnable() {
    3. PluginDescriptionFile pdfFile = this.getDescription();
    4. this.log.info(pdfFile.getName() + " Version " + pdfFile.getVersion() + " Has Been Enabled!");
    5.  
    6. runTime = new Random().nextInt((300)+60) * 20;
    7.  
    8. this.getServer().getScheduler().runTaskTimerAsynchronously(this, new Runnable() {
    9. @Override
    10. public void run() {
    11.  
    12. ConsoleCommandSender console = getServer().getConsoleSender();
    13. console.sendMessage(ChatColor.YELLOW + "Chests respawned!");
    14.  
    15. runTime = new Random().nextInt((300)+60) * 20;
    16. }
    17.  
    18. }, 0L, (long)runTime);
    19. }


    This is all in my OnEnable. I've tried replace the 'runTime' variable at the end for just a new Random(), but it only randomises once and keeps that value.

    There's probably some simple way to accomplish this, but for the life of me, I can't see it. Any help is greatly appreciated.
     
  2. Offline

    chris4315

    StormCoreFilms

    I don't know if this works, I didn't test it but try it anyway.

    Create the variable outside of the onEnable() method and the run() method. It should be something like:

    Code:java
    1.  
    2. public static int runTime;
    3.  


    After that, you'll need to give the variable a value, so make a method like this:

    Code:java
    1.  
    2. public static void setRandomTime(){
    3. this.runTime = new Random.nextInt((300) + 60) * 20;
    4. }
    5.  


    Inside onEnable(), yet outside the Runnable, invoke the setRandomTime() method. Then, where you put down how many ticks the timer goes by, replace it with:

    Code:java
    1.  
    2. (Long) this.runTime();
    3.  


    Lastly, when run() is invoked, set the runTime() variable to null?
     
  3. Offline

    StormCoreFilms

    chris4315

    I've tried your suggestion - I was able to follow all the instructions apart from setting runTime to null (I wasn't sure how to do it, and I didn't think you could set an int to null).

    When I run it, there are no errors but it will only cycle through with an initial random variable (when the method was first started). What you've instructed me to do is quite similar to something else I've tried. I'm coming to think now that you can't change the random time and that it can only be set once and not changed again.

    I do thank you for your time, however - I'm very thankful. :)
     
  4. Offline

    tills13

    you only have it set to run once... saying

    Code:java
    1. this.getServer().getScheduler().runTaskTimerAsynchronously(this, new Runnable(), delay);


    only runs it once. You need to re-add the task to the scheduler once you've completed your current task.
     
  5. Offline

    StormCoreFilms


    It does run more than once though - if you look at the end of the runnable, it has the delay and the period. My problem is getting the period to be random every time.
     
  6. Offline

    tills13

    Whoops, I didn't see that it was a task timer.
    You're initializing the task, giving it a value for delay, changing the value in the initial function won't change the value for which it was created (the initial delay).

    sorry, give me a minute to explain it better.

    When you create the BukkitTask object, changing values used to initialize it will not change the values within the Task object. Thus, the delay will be the same in each and every "loop" of the timer.

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

    StormCoreFilms


    Damn, that's what I feared would be the case. So there would be no proper way to achieve this?

    Thanks for your help, too - you've cleared a lot up.
     
  8. Offline

    tills13

    Yeah, you have to repeated create new Tasks.
    Err, I'm working on the code, but once a task completes, just create a new one with a new delay.

    Code:java
    1. PluginDescriptionFile pdfFile = this.getDescription();
    2. this.log.info(pdfFile.getName() + " Version " + pdfFile.getVersion() + " Has Been Enabled!");
    3.  
    4. delay = new Random().nextInt((300)+60) * 20;
    5.  
    6. Runnable runnable = new Runnable() {
    7. @Override
    8. public void run() {
    9. ConsoleCommandSender console = getServer().getConsoleSender();
    10. console.sendMessage(ChatColor.YELLOW + "Chests respawned!");
    11.  
    12. delay = new Random().nextInt((300)+60) * 20;
    13. //not sure, but you might have the get rid of the completed runnable
    14. getServer().getScheduler().runTaskLaterAsynchronously(this, runnable, (long)delay);
    15. }
    16. };


    that should be something to go off of - I'm not at my work computer so I can't compile it to check...

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

    StormCoreFilms


    I understand - so it's running a new task after the other one has been completed, in such a way that I can change the time before it starts. Sounds good, so I'll have a go with it now. Thanks for the help! I'll get back soon with what happens.
     
  10. Offline

    soulofw0lf

    I would just do something like this, make a timer to spawn a random timer per chest, store the chests by name and boolean for if it should respawn or not, and have a seperate map to store the name and block location to use the final string for in the second runnable.

    Code:java
    1.  
    2. public static Map<String, Boolean> chestspawned = new HashMap<>();
    3. public static void chestTimer(){
    4. new BukkitRunnable(){
    5. @Override
    6. public void run(){
    7. for (String chests : chestsSpawned.keySet()){
    8. if (chestsSpawned.get(chests)){
    9. Integer i = (int) (Math.random() * 100) * 20;
    10. final String chestName = chests;
    11. new BukkitRunnable(){
    12. @Override
    13. public void run(){
    14. //code to spawn chests
    15. }
    16. }.runTaskLater(plugin, i);
    17. }
    18. }
    19. }
    20. }.runTaskTimer(plugin, 20, 20);
    21. }
    22.  
     
  11. Offline

    StormCoreFilms


    Doing it that way looks interesting; however, I spawn my chests by using a method which takes the position and direction the chest faces:

    Code:java
    1. foodChest(new Location(Bukkit.getWorld("world"), -2400.0, 73.0, 2160.0), "South");


    In the 'foodChest' method it checks already if the chest needs to spawn in that location or not. I'm just wondering from this perspective how I could put it into a hashmap to make your code work. Thanks for helping :)


    I'm getting a problem with that last line - when I enter 'runnable' it tells me that it 'may not have been initialized', and so I'm unsure how to fix this, because I already thought that it was initialized.

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

    tills13

    Ah, I didn't forsee that. Technically, you haven't initialized it because you're still within the declaration statement.

    Try creating a separate class for the Runnable. Do something like this:

    Code:java
    1. public class ChestRunnable implements Runnable {
    2. public INSERTMAINCLASS plugin;
    3.  
    4. public ChestRunnable(INSERTMAINCLASS plugin) {
    5. this.plugin = plugin;
    6. }
    7.  
    8. public void run() {
    9. ConsoleCommandSender console = plugin.getServer().getConsoleSender();
    10. console.sendMessage(ChatColor.YELLOW + "Chests respawned!");
    11.  
    12. int delay = new Random().nextInt((300)+60) * 20;
    13. //not sure, but you might have the get rid of the completed runnable
    14. plugin.getServer().getScheduler().runTaskLaterAsynchronously(plugin, new ChestRunnable(plugin), (long)delay);
    15. }
    16. }
     
  13. Offline

    AmShaegar

    Personally, I'd do it like this:
    1. create the field private int contdown
    2. set a random number of seconds/minutes for countdown
    3. run your timer with a period of one second/minute
    4. decrease the countdown by in the first line of your runnable
    5. check if countdown == 0
      1. if yes, then the time is over, spawn chest, set countdown to a random amount of seconds/minutes again
      2. if no, just continue(do nothing)
     
  14. Offline

    Ivan

    The easy way to do this is by throwing an if statement at the top:
    if((new Random).nextInt(4) != 0)
    return;
    but if you're very very very very very very very very very very very very very very unlucky, this can go into eternity. But that chance is 1 out of the amount of days the earth has to live multiplied by a billion.
     
  15. Offline

    StormCoreFilms


    Alright, so I've put it in a class of its own, as you said. That's fine with no errors. So, do I need to call the run() method in the OnEnable in the main class? How would I do this if the run() method isn't static? It won't let me make it static, either.
     
  16. Offline

    slayr288

  17. Offline

    soulofw0lf



    make a class to turn your chests into objects that store the direction location and name, aka

    Code:java
    1. public class ChestObject {
    2. private String chestName = "";
    3. private Location chestLoc;
    4. private String chestDirection = "";
    5. private List<ItemStack> chestContents = new ArrayList<>();
    6.  
    7. public void ChestObject(){
    8.  
    9. }
    10.  
    11. public List<ItemStack> getChestContents() {
    12. return chestContents;
    13. }
    14.  
    15. public void setChestContents(List<ItemStack> chestContents) {
    16. this.chestContents = chestContents;
    17. }
    18.  
    19. public String getChestDirection() {
    20. return chestDirection;
    21. }
    22.  
    23. public void setChestDirection(String chestDirection) {
    24. this.chestDirection = chestDirection;
    25. }
    26.  
    27. public Location getChestLoc() {
    28. return chestLoc;
    29. }
    30.  
    31. public void setChestLoc(Location chestLoc) {
    32. this.chestLoc = chestLoc;
    33. }
    34.  
    35. public String getChestName() {
    36. return chestName;
    37. }
    38.  
    39. public void setChestName(String chestName) {
    40. this.chestName = chestName;
    41. }
    42. }


    and in your main class

    Code:java
    1. public static Map<String, ChestObject> savedChests = new HashMap<>();
    2. public static Map<String, Boolean> respawnChests = new HashMap<>();
    3.  


    then in the Bukkitrunnables i gave you earlier you have it iterate through the map of booleans looking for true, and use the map of chest objects to fill in your method that you use for spawning chests.
     
  18. Offline

    tills13

    in your main class, put:
    Code:java
    1. getServer().getScheduler().runTaskLaterAsynchronously(this, new ChestRunnable(this), new Random.nextInt());
     
    StormCoreFilms likes this.
  19. Offline

    StormCoreFilms


    Wow, thanks! I'm pretty sure it works perfectly - the times the 'Chest Spawned' message is written to the console seem to be entirely random. Cheers for the help, it means a lot! I appreciate your time and willingness to answer my stupid questions :)
     
Thread Status:
Not open for further replies.

Share This Page