0

I am programming a plugin that records the coordinates of players in minecraft throughout the game, in Java with Bukkit API.

I have used a 'repeating task' structure in my bukkit/java code below.

However, I need to 'cancel' this repeating task when a particular player logs off, as otherwise, if player 'A' logs in and logs off 50 times in a row, I will have 50 running tasks.

The idea is to stop the runnable of player 'A' if and only if player 'A' logs off (and not B).

Am I right in saying that the following code will cancel the repeating task for a particular player only when that particular player logs off?

I would be so grateful for a helping hand!

   @EventHandler
    public void onLogin(final PlayerJoinEvent event) {
        final Player thePlayer = event.getPlayer();
        this.stopRepeater = true;
        final Location playerSpawnLocation = thePlayer.getLocation();
        getLogger().info(String.valueOf(thePlayer.getName()) + " is logging in!");
        getLogger().info("Welcome " + thePlayer.getName() + ". Your current position is: " + playerSpawnLocation);
        int taskID = Bukkit.getServer().getScheduler().scheduleSyncRepeatingTask(this, () -> {
            if(this.stopRepeater) {
                this.logToFile(thePlayer, thePlayer.getLocation());
            }
        }, 0L, 20L);}
    
    @EventHandler
    public void onQuit(final PlayerQuitEvent event) {
        Player thePlayer = event.getPlayer(); 
        if(!thePlayer.isOnline()) {
            this.stopRepeater = false;  
            Bukkit.getScheduler().cancelTask(taskID); 
            getLogger().info(String.valueOf(event.getPlayer().getName()) + " has left the game");
        }
    }
Elikill58
  • 4,050
  • 24
  • 23
  • 45
Caledonian26
  • 727
  • 1
  • 10
  • 27
  • The scheduler will return a `BukkitTask` object (or similar) which has a #cancel method. You can also use `BukkitRunnable` which has a `#cancel` method within the class itself, allowing you to simply `this.cancel()` in the runnable body. Note that the `#onQuit` you've shown is scheduling a second, _new_ task and then cancelling it, you are not cancelling the original task. – Rogue Nov 30 '22 at 16:36
  • Thanks so much - please see the changed code above - shall I put 'if(!thePlayer.isOnline()) {' in the first @event? – Caledonian26 Nov 30 '22 at 16:43
  • You shouldn't check `Player#isOnline` from a `PlayerQuitEvent`, but there's a larger problem of scope: `taskID` was defined in `#onLogin`, but this doesn't make it automatically available in `#onQuit`. While a naive approach could be storing `taskID` in a field, you'll end up with each new player joining overwriting the last `taskID`. The solution here imo is to use a `Map` for the task IDs, and then removing them in `#onQuit` for the logging-off player. – Rogue Nov 30 '22 at 16:46

1 Answers1

1

You code is actually working only for one player. You should save task object for each player with a Map.

I suggest you to use runTaskTimer to have a BukkitTask, then use like this:

HashMap<UUID, BukkitTask> tasks = new HashMap<>()

When player login:

BukkitTask task = getServer().getScheduler().runTaskTimer(this, () -> doThing(), 0, 20);
tasks.put(player.getUniqueId(), task); // add in map

Finally, when player left:

BukkitTask task = tasks.remove(player.getUniqueId()); // remove from map if exist
if(task != null) { // task found
   task.cancel();
}
Elikill58
  • 4,050
  • 24
  • 23
  • 45
  • Thanks so much - should the line 'tasks.put' be changed to: tasks.put(thePlayer.getUniqueId(),task); (as I defined the player as 'thePlayer'. It also suggests changing 'BukkitTask task' to 'int Task' - not sure why? – Caledonian26 Dec 01 '22 at 16:10
  • The method [`scheduleSyncRepeatingTask`](https://hub.spigotmc.org/javadocs/spigot/org/bukkit/scheduler/BukkitScheduler.html#scheduleSyncRepeatingTask(org.bukkit.plugin.Plugin,java.lang.Runnable,long,long)) return an int, but [`runTaskTimer`](https://hub.spigotmc.org/javadocs/spigot/org/bukkit/scheduler/BukkitScheduler.html#runTaskTimer(org.bukkit.plugin.Plugin,java.lang.Runnable,long,long)) return a BukkitTask. So check which method you're using. And yes, if your variable is always `thePlayer`, use it – Elikill58 Dec 01 '22 at 17:38
  • Thanks for clarifying - so this part: HashMap tasks = new HashMap<>(); , should be placed outside the event handlers, so that both events can refer to the variable 'tasks'? :) – Caledonian26 Dec 02 '22 at 09:31
  • 1
    Yes, it should be at top of your class, below `public class MyEventClass implements Listener {` – Elikill58 Dec 02 '22 at 09:36
  • ok perfect! :) my errors are now gone - hopefully the script will work now on minecraft! thank you so much! – Caledonian26 Dec 02 '22 at 10:05
  • Withpleasure :) if it's working don't forget to select the check mark on left of my answer :) – Elikill58 Dec 02 '22 at 11:56
  • sure! all done :) – Caledonian26 Dec 02 '22 at 12:02