Solved BukkitRunnable for each player

Discussion in 'Plugin Development' started by Quark-Coder, Nov 15, 2022.

  1. Offline

    Quark-Coder

    I have a PlayerInteractEvent on a creeper egg. Every time when player presses the left mouse button, player should explode, there is no problem with that. But, as soon as I needed to create a cooldown for this, I have a problem. The idea was for cooldown to be related to experience: as long as the player has a level, he can use the ability. Every 10 seconds, the experience should increase by 1, to three units and stop. I used BukkitRunnable() for this, but it works as long as there is one player on the server. If the second player activates cooldown, then he interrupts the cooldown of the first player and his cooldown starts from first player ccooldown time (not from 10 seconds). I understand that i need to create a BukkitRunnable() for each player, but how do I do this? I tried using HashMap, but nothing came out - I don`t understand how to link the UUID of the players with BukkitRunnable().

    Code:
    @EventHandler
    public void onEggUse(PlayerInteractEvent e) {
         p = e.getPlayer();
         final int[] i = {10};    //cooldown seconds
                    if(cooldownIsActive == false && p.getItemInHand().getType() ==                 Material.CREEPER_SPAWN_EGG) {
                       new BukkitRunnable() {
                            public void run() {
                                cooldownIsActive = true;
                               if ((p.getLevel() != 3)) {
                                    p.sendMessage(ChatColor.RED + "Cooldown " + i[0]);
                                    i[0]--;
                                    if (i[0] == 0) {
                                        p.sendMessage(ChatColor.GREEN + "Cooldown reseted");
                                        if (p.getLevel() == 3) {
                                            this.cancel();
                                            cooldownIsActive = false;
                                            i[0] = 10;
                                        } else {
                                            p.setLevel(p.getLevel() + 1);
                                            i[0] = 10;
                                        }
                                    }
                                }
                            }
                        }.runTaskTimer(plugin, 0L, 20L);
                    }
        }
     
  2. Online

    CraftCreeper6

    The problem is that cooldownIsActive is not per player. The best way to do this is to not use BukkitRunnables at all.

    Try these steps:
    Code:
    // create cooldowns HashMap
    HashMap<UUID, float> cooldowns = ...
    
    // declare event listener
    void eggEvent(PlayerInteractEvent e)
    {
        // following sequence will return only if there's an active cooldown
        // check if the item that the player is using is a creeper spawn egg
        if (event item is creeper spawn egg)
        {
            // check to see if the player has an entry in the HashMap
            if (cooldowns contains event player)
            {
                // check to see if the cooldown is over, the value in cooldowns should be LOWER if the cooldown is over
                if (System.currentTimeMillis < value in cooldowns at player)
                {
                    // return, the cooldown is not over yet
                    return;
                }
            }
        }
    
        // if we got to here, cooldown is over, throw egg
        add player to cooldowns with System.currentTimeMillis + cooldown as the value
    }
     
  3. Offline

    Quark-Coder

    But this does not solve the problem with experience... Until the player activates the PlayerInteractEvent , the update will not occur, therefore, it will not work to implement the cooldown idea through experience.
     
  4. Online

    CraftCreeper6

    @Quark-Coder
    My bad, read over that part.

    In that case you have two options:
    - Create a BukkitRunnable alongside the code I sent above that runs every second and simply use the UUID key to find the Player, and use the float value as the "time" to set the experience level.
    - Do it how you're currently doing it but have a HashMap<UUID, Boolean> to keep track of the players that are on cooldown. Use this example to create a BukkitRunnable that cancels itself after a set number of iterations.
     
  5. Offline

    Quark-Coder

    Unfortunately, I have been trying to use HashMap for a very long time. Even now I spent 3 hours on another attempt ... but I still did not understand how it should work:
    If the trigger activates, we write the player to the map with the true statement. We perform operations with experience and cooldown, as soon as experience reaches 3, we interrupt Runnable and remove the player from the HashMap?
     
  6. Online

    CraftCreeper6

    @Quark-Coder
    Realistically you won't even need a HashMap. You can just use a List<UUID> to store which players have an active cooldown.

    Let's go through it:
    Code:
    if player in cooldowns List:
        return
    
    if player throws an egg:
        add player to cooldowns List
      
        start BukkitRunnable running once per second:
            adjust experience based on number of iterations of the runnable
          
            if the experience is some value:
                remove player from cooldowns List
                cancel runnable
     
  7. Offline

    Quark-Coder

    It works, but again anyone can interrupt the cooldown... I can't figure out why.

    Code:
                    ...
                    public List<UUID> cooldowns = new ArrayList<UUID>();
                    ...
                     if(cooldowns.contains(p.getUniqueId())){
                            return;
                    }
    
                    if(p.getItemInHand().getType() == Material.CREEPER_SPAWN_EGG){
                        cooldowns.add(p.getUniqueId());
    
                        final int[] cdTime = {10};
    
                        new BukkitRunnable() {
                            @Override
                            public void run() {
                              if(cdTime[0] > 0){
                                  p.sendMessage(ChatColor.RED + "Cooldown " + cdTime[0] + " seconds");
                                  cdTime[0]--;
                              }
    
                              if (cdTime[0] == 0){
                                  p.sendMessage(ChatColor.GREEN + "Cooldown reset!");
                                  p.setLevel(p.getLevel() + 1);
                                  cdTime[0] = 10;
                              }
    
                              if (p.getLevel() == 3) {
                                  cooldowns.remove(p.getUniqueId());
                                  cancel();
                              }
                            }
                        }.runTaskTimer(plugin,0,20);
                    }
     
  8. Online

    CraftCreeper6

    @Quark-Coder
    The issue now is cdTime. It's not declared within the BukkitRunnable so it's essentially meaningless, and it's also marked as final, which is not necessary, it also does not need to be an array.

    So, remove final, make it a regular int variable, and move it inside of the BukkitRunnable (but outside of the run method), let's see if that works for you.
     
  9. Offline

    Quark-Coder

    Done, but still interrupts...
    [​IMG]
    Code:
    if(cooldowns.contains(p.getUniqueId())){
                return;
            }
    
            if(p.getItemInHand().getType() == Material.CREEPER_SPAWN_EGG){
                cooldowns.add(p.getUniqueId());
    
                new BukkitRunnable() {
                    int cdTime = 11;
                    @Override
                    public void run() {
                        if(cdTime > 0){
                            cdTime--;
                            if (cdTime != 0) { p.sendMessage(ChatColor.RED + "Cooldown " + cdTime + " seconds");};
                        }
    
                        if (cdTime == 0){
                            p.sendMessage(ChatColor.GREEN + "Cooldown reset!");
                            p.setLevel(p.getLevel() + 1);
                            cdTime = 11;
                        }
    
                        if (p.getLevel() == 3) {
                            cooldowns.remove(p.getUniqueId());
                            cancel();
                        }
                    }
                }.runTaskTimer(plugin,0,20);
            }
     
  10. Online

    CraftCreeper6

    @Quark-Coder
    I've just tested it, look here.

    You're definitely doing something wrong, but without seeing more code I can't say what it is. Compare yours against mine and try to figure it out.
     
  11. Offline

    Quark-Coder

    I copied your code but still no results. As before, any player can interrupt the cooldown.
    I think it will really be easier if I show the project in full (except for the other two abilities).
    https://github.com/Quark-Coder/Plugin
    P.S
    Sorry for bad code. I'm new to this and learn everything by trial and error =)
    P.P.S
    And additional apologies for my English, it's not my primary language.
     
  12. Online

    CraftCreeper6

    @Quark-Coder
    Okay, here are my thoughts:
    - In CreeperEggHandler.java you are doing all of the code (lines 46 to 67) before you check if the player is on cooldown, this doesn't feel like it should be intentional. This is probably the issue.
    - Line 76, you are checking if the player is null, after using the player multiple times. Doesn't make sense to have it here, move it near the top.
    - Line 78 is a copy of line 72, why?

    Is your goal to have 3 charges? If that's the case, we need to change some things.
     
  13. Offline

    Quark-Coder

    @CraftCreeper6
    Updated this class on github
    1.Moved if(cooldowns.contains(p.getUniqueId()) to up
    2.Moved to up.
    3.My mistake :)

    And yes. The level must be held at three. If I clicked on the egg once (each click consume 1 level) - 10 seconds and +1 level (3 in general). Twice - 10 seconds one level and 10 seconds the second.
     
  14. Online

    CraftCreeper6

    @Quark-Coder
    Cool, this is going to be much more complex than before. Since you don't want simultaneous cooldowns per player, we'll probably have to outsource the runnable to a "master" runnable that goes forever, I've updated my repo. I didn't comment much so let me know if anything needs explaining. It does use HashMaps.

    One thing, line 97, you are using a 'p' variable when it may not have yet been assigned, what if an entity takes damage by TNT? You should check if p != null first.
     
    Quark-Coder likes this.
  15. Offline

    Quark-Coder

    @CraftCreeper6
    Awesome! This works great! No more interruption :) But I found a small problem - the first level consumes, but then no explosion.

    Updated on github.
     
    Last edited by a moderator: Nov 17, 2022
  16. Online

    CraftCreeper6

    @Quark-Coder
    Your problem is on line 68. You are checking if the level != 0, on the last charge, this will fail. You no longer need this check.
     
  17. Offline

    Quark-Coder

    @CraftCreeper6
    Thank you so much. Now everything works as expected =)
     
  18. Online

    CraftCreeper6

    @Quark-Coder
    Great! Remember to mark this thread as solved :)
     

Share This Page