Placing an item in a player's inventory at regular time intervals on Minecraft

Discussion in 'Plugin Development' started by caledonian26, Feb 15, 2023.

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

    caledonian26

    Hey all,

    I have the following code.

    I am trying to place an item in a player's inventory at regular time intervals (every 1 minute).

    The object 'listofitems' is a list of four items, where the first item will be placed in the player's inventory at 1 minute, the second item at 2 minutes etc.

    Once all 4 items have been placed in the player's inventory, I want the repeater to stop running.

    How can I change my code to achieve this effect?

    I would be so grateful for a helping hand!

    Code:
        @EventHandler
        public void onPlayerInteract(PlayerInteractEvent event) {
            final ItemStack item = event.getItem();
            if (item.getType() == Material.WHITE_WOOL) {
              this.stopRepeater = true;
                }
            BukkitTask task = getServer().getScheduler().runTaskTimer(this, () -> {
                     if(this.stopRepeater){
                         int count = 0;
                         while (count <= 4){
                            Material[] listofitems = {Material.WATER, Material.CACTUS, Material.CAKE, Material.SNOW};
                            int idx = count + 1;
                            Material randomItem = listofitems[idx];
                            ItemStack[] items = {new ItemStack(randomItem)};
                            Player thePlayer = event.getPlayer();
                            thePlayer.getInventory().addItem(items);
                         }
                     }
              }, 0L, 20L);
        }
    }
    
     
    Last edited: Feb 15, 2023
  2. Offline

    caledonian26

    Hey all,

    I have the following code.

    I am trying to:

    1. Assign a bukkit runnable task to a given ID
    2. Assign a player a given ID
    3. Place these two IDs into a hashmap ', where each participant is matched to their respective bukkit runnable task

    The repeating task should assign a maximum of 4 objects to a given player's inventory, assigning one object every minute.

    This means that for each player, the repeating task should last a maximum of 4 minutes and should be cancelled when the counter exceeds the length of the hashmap.

    However, I get the issue 'the local variable task may not have been initialised'.

    I know that this means that I should initialise the variable 'task', but I am not sure how to do so, given that the variable task corresponds to the bukkit runnable task?

    I would be so grateful for a helping hand!

    Code:
    Map<UUID, Integer> map = new HashMap<UUID, Integer>();
        List<ItemStack> items = java.util.Arrays.asList(
                new ItemStack(Material.WATER),
                new ItemStack(Material.COBWEB),
                new ItemStack(Material.CAKE),
                new ItemStack(Material.RED_WOOL)
            );
    
        @EventHandler
        public void on(PlayerQuitEvent event) {
        map.remove(event.getPlayer());
            }
         
        @EventHandler
        public void on(PlayerInteractEvent event) {
                final ItemStack item = event.getItem();
                if (item.getType() == Material.WHITE_WOOL) {
                    BukkitTask task = getServer().getScheduler().runTaskTimer(this, () -> {
                        if(this.stopRepeater) {
                            int counter = 0;
                            while (counter <= 4){
                               Material[] listofitems = {Material.WATER, Material.COBWEB, Material.CAKE, Material.SNOW};
                               int idx = counter;
                               Material randomItem = listofitems[idx];
                               ItemStack items = new ItemStack(randomItem);
                               Player thePlayer = event.getPlayer();
                               thePlayer.getInventory().addItem(items);
                               map.put(event.getPlayer().getUniqueId(),task.getTaskId());
                               counter ++;
                               if (counter >= map.size()) {
                                   Bukkit.getServer().getScheduler().cancelTask(task.getTaskId());
                               }
                            }
                        }
                 }, 20 * 60, 20 * 60);
                }
            }    
    }
     
    Last edited: Feb 16, 2023
  3. Offline

    Strahan

    You could do it as such
    Code:
    @EventHandler
    public void onPlayerInteract(PlayerInteractEvent event) {
      new BukkitRunnable() {
        int count = 0;
    
        @Override
        public void run() {
          while (count <= 4) {
            count++;
            Material[] listofitems = {Material.WATER, Material.CACTUS, Material.CAKE, Material.SNOW};
            int idx = count + 1;
            Material randomItem = listofitems[idx];
            ItemStack[] items = {new ItemStack(randomItem)};
            event.getPlayer().getInventory().addItem(items);
          }
          cancel();
        }
      }.runTaskTimer(this, 0L, 20L);
    }
    Just add whatever criteria is required for the task to start, as the way that is it will fire every time someone interacts with anything.

    PS I didn't really read the code initially, I just looked at the runnable. That code actually will fail in several areas. I rewrote it to give each of the items in the array, once per second.
    Code:
    @EventHandler
    public void onPlayerInteract(PlayerInteractEvent event) {
      new BukkitRunnable() {
        int count = 0;
        Material[] listofitems = {Material.WATER_BUCKET, Material.CACTUS, Material.CAKE, Material.SNOW};
    
        @Override
        public void run() {
          if (count == listofitems.length-1) cancel();
         
          Material nextItem = listofitems[count];
          ItemStack item = new ItemStack(nextItem);
          event.getPlayer().getInventory().addItem(item);
          count++;
        }
      }.runTaskTimer(this, 0L, 20L);
    }
     
    Last edited: Feb 16, 2023
  4. Offline

    caledonian26

    Thanks so much for this! Can I ask why the two lines '@override and public void run()' are necessary? And if you do listofitems.length-1 rather than listofitems.length, won't that mean that the final item is not recorded in the hash map?
     
  5. Offline

    Strahan

    No prob. The override is a notation telling it we're overring the run method of the runnable. The public void run is the method constructor for the method that performs work during the runnable iteration.

    As to the -1 thing, yes, if I had returned as well as cancelled you'd not receive item 4 however since all I do is cancel, that means it will complete this iteration of the loop and then not run again.
     
  6. Offline

    caledonian26

    Ah I see - makes perfect sense thank you! Can you also confirm that if I want to save the interaction event, the name of the player interacting and the time it occurs to a file, is this the right way to do it?

    Code:
     public void onPlayerInteract(PlayerInteractEvent event) {
         if (event.getClickedBlock().getType() == Material.LEVER) {
           
          public void logToFile() {
                 try {
                     String content = String.valueOf(new SimpleDateFormat("dd-MM-yyyy HH:mm:ss").format(new Date())) + "Player: " + event.getPlayer() + "Item: " + event.getClickedBlock().getType()  + "\n";
                      if (!getDataFolder().exists()) {
                             getDataFolder().mkdirs();
                           }
                       final File file = new File(dataFolder, "wayfindinteract.log");
                       if (!file.exists()) {
                           file.createNewFile();
                       }
                       Files.write(file.toPath(),content.getBytes(StandardCharsets.UTF_8),StandardOpenOption.APPEND);
                   } catch (Exception e) {
                       e.printStackTrace();
                   }
               }
           
          new BukkitRunnable() {
            int count = 0;
            Material[] listofitems = {Material.SPONGE, Material.COBWEB, Material.CAKE, Material.RED_WOOL};
    
            public void run() {
              if(!event.getPlayer().isOnline()) {
                    this.cancel();
                }
              if (count == listofitems.length-1) cancel();
              Material nextItem = listofitems[count];
              ItemStack item = new ItemStack(nextItem);
              event.getPlayer().getInventory().addItem(item);   
              count++;
            }
          }.runTaskTimer(this, 0, 900);
        }
        }
    }
    
     
    Last edited: Feb 17, 2023
  7. Offline

    Strahan

    Nope, not at all. You cannot embed a method within a method like that. Don't let the public void run in the runnable lead you into thinking you can put method constructors anywhere in a method. That is a special case, where the BukkitRunnable is instantiating a new class. That is not the same thing as putting a constructor in a method.

    Move the log function out of the event method and add a String to the constructor as an argument to accept the log entry.
     
  8. Offline

    caledonian26

    Thank you for your advice!! The following code now works! Very glad I managed to figure it out on my own accord before I looked at your response :)

    Code:
    package newestfile.here.newestplugin;
    
    import java.util.UUID;
    import java.nio.charset.StandardCharsets;
    import java.nio.file.StandardOpenOption;
    import java.text.SimpleDateFormat;
    import java.util.Date;
    import java.util.HashMap;
    import java.io.File;
    import org.bukkit.event.player.PlayerQuitEvent;
    import org.bukkit.event.EventHandler;
    import org.bukkit.entity.Player;
    import org.bukkit.Location;
    import org.bukkit.Material;
    import org.bukkit.event.player.PlayerInteractEvent;
    import org.bukkit.event.player.PlayerJoinEvent;
    import org.bukkit.Bukkit;
    import org.bukkit.event.Listener;
    import org.bukkit.plugin.java.JavaPlugin;
    import org.bukkit.scheduler.BukkitRunnable;
    import org.bukkit.scheduler.BukkitTask;
    import org.bukkit.inventory.ItemStack;
    import java.nio.file.Files;
    
    public class Main extends JavaPlugin implements Listener
    {
        boolean stopRepeater;
        HashMap<UUID, BukkitTask> tasks = new HashMap<>();
        private File dataFolder;
       
        public void onEnable() {
            Bukkit.getServer().getPluginManager().registerEvents(this,this);
            getLogger().info("HELLO! WELCOME TO THE TRACKER PLUGIN");
            dataFolder = new File(getDataFolder(), "data");
            dataFolder.mkdirs();
        }
       
        @EventHandler
        public void onLogin(final PlayerJoinEvent event) {
            final Player thePlayer = event.getPlayer();
            this.stopRepeater = true;
            final Location playerSpawnLocation = thePlayer.getLocation();
            getLogger().info("Welcome " + thePlayer.getName() + ". Your current position is: " + playerSpawnLocation); 
            BukkitTask task = getServer().getScheduler().runTaskTimer(this, () -> {
                if(this.stopRepeater) {
                    this.logToFile(thePlayer, thePlayer.getLocation());
                }
            }, 0L, 20L);
            tasks.put(thePlayer.getUniqueId(),task);}
        @EventHandler
        public void onQuit(final PlayerQuitEvent event) {
            Player thePlayer = event.getPlayer();
            if(!thePlayer.isOnline()) {
                this.stopRepeater = false;
                getLogger().info(String.valueOf(event.getPlayer().getName()) + " has left the game");
                BukkitTask task = tasks.remove(thePlayer.getUniqueId());
                if(task != null) {
                   task.cancel();
                }
            }
        }
    
        public void logToFile(final Player currentPlayer, final Location playerCurrentLocation) {
            try {
                String content = String.valueOf(new SimpleDateFormat("dd-MM-yyyy HH:mm:ss").format(new Date())) + " CurrentLocation(x,y,z): " + playerCurrentLocation.getBlockX() + " " + playerCurrentLocation.getBlockY() + " " + playerCurrentLocation.getBlockZ() + " Current Yaw: " + currentPlayer.getEyeLocation().getYaw() + " Current Pitch: " + currentPlayer.getEyeLocation().getPitch() + "Heading Direction:" + currentPlayer.getEyeLocation().getDirection() + "\n";
                if (!getDataFolder().exists()) {
                      getDataFolder().mkdirs();
                    }
                final File file = new File(dataFolder, currentPlayer.getName() + String.valueOf(new SimpleDateFormat("dd-MM-yyyy").format(new Date())) + ".log");
                if (!file.exists()) {
                    file.createNewFile();
                }
                Files.write(file.toPath(),content.getBytes(StandardCharsets.UTF_8),StandardOpenOption.APPEND);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    
        @EventHandler
        public void onPlayerInteract(PlayerInteractEvent event) {
         if (event.getClickedBlock().getType() == Material.LEVER) {
          try {
             String content = String.valueOf(new SimpleDateFormat("dd-MM-yyyy HH:mm:ss").format(new Date())) + " Player: " + event.getPlayer().getName() + " Material: " + event.getClickedBlock().getType() + "\n";
             if (!getDataFolder().exists()) {
                   getDataFolder().mkdirs();
                 }
             final File file = new File(dataFolder, "eventswayfind" + event.getPlayer().getName() + String.valueOf(new SimpleDateFormat("dd-MM-yyyy").format(new Date())) + ".log");
             if (!file.exists()) {
                 file.createNewFile();
             }
             Files.write(file.toPath(),content.getBytes(StandardCharsets.UTF_8),StandardOpenOption.APPEND);
          } catch(Exception e) {
                 e.printStackTrace();
             }
            
          new BukkitRunnable() {
            int count = 0;
            Material[] listofitems = {Material.SPONGE, Material.COBWEB, Material.CAKE, Material.RED_WOOL};
    
            public void run() {
              if(!event.getPlayer().isOnline()) {
                    this.cancel();
                }
              if (count == listofitems.length-1) cancel();
              Material nextItem = listofitems[count];
              ItemStack item = new ItemStack(nextItem);
              event.getPlayer().getInventory().addItem(item);    
              count++;
            }
          }.runTaskTimer(this, 0, 900);
        }
        if (event.getClickedBlock().getType() == Material.OAK_BUTTON) {
            try {
               String content = String.valueOf(new SimpleDateFormat("dd-MM-yyyy HH:mm:ss").format(new Date())) + " Player: " + event.getPlayer().getName() + " Material: " + event.getClickedBlock().getType() + "\n";
               if (!getDataFolder().exists()) {
                     getDataFolder().mkdirs();
                   }
               final File file = new File(dataFolder, "eventschase" + event.getPlayer().getName() + String.valueOf(new SimpleDateFormat("dd-MM-yyyy").format(new Date())) + ".log");
               if (!file.exists()) {
                   file.createNewFile();
               }
               Files.write(file.toPath(),content.getBytes(StandardCharsets.UTF_8),StandardOpenOption.APPEND);
            } catch(Exception e) {
                   e.printStackTrace();
               }
        }
        if (event.getClickedBlock().getType() == Material.POLISHED_BLACKSTONE_BUTTON) {
            try {
               String content = String.valueOf(new SimpleDateFormat("dd-MM-yyyy HH:mm:ss").format(new Date())) + " Player: " + event.getPlayer().getName() + " Material: " + event.getClickedBlock().getType() + "\n";
               if (!getDataFolder().exists()) {
                     getDataFolder().mkdirs();
                   }
               final File file = new File(dataFolder, "eventsfollow" + event.getPlayer().getName() + String.valueOf(new SimpleDateFormat("dd-MM-yyyy").format(new Date())) + ".log");
               if (!file.exists()) {
                   file.createNewFile();
               }
               Files.write(file.toPath(),content.getBytes(StandardCharsets.UTF_8),StandardOpenOption.APPEND);
            } catch(Exception e) {
                   e.printStackTrace();
               }
        }
        if (event.getClickedBlock().getType() == Material.STONE_PRESSURE_PLATE) {
            try {
               String content = String.valueOf(new SimpleDateFormat("dd-MM-yyyy HH:mm:ss").format(new Date())) + " Player: " + event.getPlayer().getName() + " Material: " + event.getClickedBlock().getType() + "\n";
               if (!getDataFolder().exists()) {
                     getDataFolder().mkdirs();
                   }
               final File file = new File(dataFolder, "eventsfollow" + event.getPlayer().getName() + String.valueOf(new SimpleDateFormat("dd-MM-yyyy").format(new Date())) + ".log");
               if (!file.exists()) {
                   file.createNewFile();
               }
               Files.write(file.toPath(),content.getBytes(StandardCharsets.UTF_8),StandardOpenOption.APPEND);
            } catch(Exception e) {
                   e.printStackTrace();
               }
        }
        }
        }
    
    
     
    Strahan likes this.
  9. Offline

    Strahan

    Glad to hear you got it. But there is a big problem; you have stopRepeater as a class scope single boolean, but you are manipulating it based on what players are doing. So you will end up with this scenario:

    Player A logs in
    You set stopRepeater to to true
    Runnable for Player A starts going and is logging as SR is true
    Player B logs in
    You set stopRepeater to to true
    Runnable for Player B starts going and is logging as SR is true
    Player A logs off
    You set stopRepeater to false and kill off the Player A runnable
    stopRepeater is false, so Player B's runnable also stops logging data even though they are still on

    There really is no point to the stopRunnable at all really. As you have a Player keyed collection of BukkitTasks, just have the task log to file without any special checks. Then when they quit, you just terminate the task as you are doing.

    Another problem is you are killing off the tasks when they quit; good. But you aren't cleaning the collection. You should be removing the UUID from the Map after the task is killed.

    Lastly, and this is a very slight nitpick, but the vars should be properly declared:
    Code:
    public class Main extends JavaPlugin implements Listener {
      private boolean stopRepeater;
      private Map < UUID, BukkitTask > tasks = new HashMap < > ();
      private File dataFolder;
    You used a proper protection keyword for file, but none for the other two. It isn't the end of the world; it will default but it's best to explicitly list them IMO. Also per the L in SOLID design (Liskov Substitution Principle) you should be declaring Map = HashMap not HashMap = HashMap because objects of a superclass should be replaceable with objects of its subclasses. None of this will break anything as your code is, but it's best to learn proper practices early to build "muscle memory" :)
     
  10. Offline

    caledonian26

    Thanks so much for the feedback!

    *I have removed the stop.repeater=False given that we are removing them from the hashmap anyway, have removed the 'if this.stoprepeater{' part.

    Is this what you meant?

    Code:
    public class Main extends JavaPlugin implements Listener
    {
        private boolean stopRepeater;
        private Map <UUID, BukkitTask> tasks = new HashMap<>();
        private File dataFolder;
    
        public void onEnable() {
            Bukkit.getServer().getPluginManager().registerEvents(this,this);
            getLogger().info("HELLO! WELCOME TO THE TRACKER PLUGIN");
            dataFolder = new File(getDataFolder(), "playerdata");
            dataFolder.mkdirs();
        }
    
        public void onLogin(final PlayerJoinEvent event) {
            final Player thePlayer = event.getPlayer();
            this.stopRepeater = true;
            final Location playerSpawnLocation = thePlayer.getLocation();
            getLogger().info("Welcome " + thePlayer.getName() + ". Your current position is: " + playerSpawnLocation);
           BukkitTask task = getServer().getScheduler().runTaskTimer(this, () -> {
               this.logToFile(thePlayer, thePlayer.getLocation());
            }, 0L, 20L);
            tasks.put(thePlayer.getUniqueId(),task);}
        public void onQuit(final PlayerQuitEvent event) {
            Player thePlayer = event.getPlayer();
            if(!thePlayer.isOnline()) {
                getLogger().info(String.valueOf(event.getPlayer().getName()) + " has left the game");
                BukkitTask task = tasks.remove(thePlayer.getUniqueId());
                if(task != null) {
                   task.cancel();
                }
            }
        }
     
    Last edited: Feb 20, 2023
Thread Status:
Not open for further replies.

Share This Page