2

I want my code to run once every 20t when a player touches the water

package me.pgk.Listeners;

import me.pgk.PGK;
import org.bukkit.Bukkit;
import org.bukkit.Material;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerMoveEvent;


public class WaterLavaDamage implements Listener {


    private final PGK plugin;
    public WaterLavaDamage(PGK plugin) {
        this.plugin = plugin;
    }



    @EventHandler
    public void OnWater(PlayerMoveEvent e) {

        Player p = e.getPlayer();

        if (!(p.getLocation().getBlock().getType().equals(Material.WATER))) return;


        Bukkit.getScheduler().runTaskLater(plugin, () -> {
            
            //here is the code I wanted to run
            p.sendMessage("Water Damage");
            
            }, 20L);
        
        
    }
}

the OnWater(PlayerMoveEvent e) event; checks for player movement every tick (20 times every second) and I want the code inside of it to run once every 20 ticks when the player touches the water.

however when the player does so, the code runs every tick (20 times every second) after 20 ticks.

PGK
  • 23
  • 3

2 Answers2

2

The PlayerMoveEvent fires many times a second when the player moves. Even if they're moving their head, this is why the answer given by Blue Dev is making your message print many more times than expected.

Instead of creating a new task every time the player moves in water. You should use a runTaskTimer scheduler and only create it if there is not one already running for the player.

// Create a map to store the player's UUID and their task
public final Map<UUID, BukkitTask> playersBeingDamaged = new HashMap<>();

@EventHandler
public void OnWater(PlayerMoveEvent e) {
    Player p = e.getPlayer();
    // Create an optional which may contain the BukkitTask
    Optional<BukkitTask> bukkitTaskOptional = Optional.ofNullable(playersBeingDamaged.get(p.getUniqueId()));
    if (!(p.getLocation().getBlock().getType().equals(Material.WATER))) {
       // If the player is not in water and they have a task, cancel and remove it from the map
       bukkitTaskOptional.ifPresent(it -> {
           it.cancel();
           playersBeingDamaged.remove(p.getUniqueId());
        });
    } else {
       // If they're in water and have a task already, return; don't do anything more
       if(bukkitTaskOptional.isPresent())
           return;
       // Create the task for the player which will have a delay of 0 ticks and run every 20
       BukkitTask task = Bukkit.getScheduler().runTaskTimer(
               plugin,
               () -> p.sendMessage("Water Damage"),
               0L,
               20L
       );
       // Add the UUID and the task to the map
       playersBeingDamaged.put(p.getUniqueId(), task);
    }
}

If these tasks will be created often, a more efficient way would be to have a runTaskTimer running continually that iterates over a map of UUID's, get the player and their location, and then perform the action if they meet the conditions. They get removed from the map if they don't meet the conditions (e.g. in water). The PlayerMoveEvent is only used to add them to this map when they're in water.

Here's an example of that:

public class MyRunnable extends BukkitRunnable {
    @Override
    public void run() {
        for(UUID uuid : playersBeingDamaged){
            Player player = Bukkit.getPlayer(uuid);
            if(player == null || !player.getLocation().getBlock().getType().equals(Material.WATER)){
                // Player is offline OR they're not currently in water, remove them and continue
                playersBeingDamaged.remove(uuid);
                continue;
            }
            player.sendMessage("Water damage");
        }
    }
}

And this task can be started in your onEnable like so:

BukkitTask myTask = new MyRunnable().runTaskTimer(plugin, 0L, 20L);

P.S. playersBeingDamaged will need to be somewhere accessible to your runnable class and your listener.

Lucan
  • 2,907
  • 2
  • 16
  • 30
  • You're spoonfeeding basic programming concepts, but they're probably only making plugins for the end product rather than to learn anyways. Good example code though. Only thing I dislike about this is that the damage timer won't be player-specific, meaning that if they touch the water for a few moments between damage ticks, they won't get damaged. – Blue Dev Jul 10 '22 at 18:29
0

Try using scheduler#runTaskTimer instead of scheduler#runTaskLater.

    boolean isTouchingWater = true;
    Bukkit.getScheduler().runTaskTimer(plugin, new Runnable() {

        @Override
        public void run() {
            if(isTouchingWater) {
                p.sendMessage("Water Damage");
            }else {
                //cancel the task
            }
        }
        
    }, 0, 20);

You'll want to have a boolean as described so that you can cencel the task when the player is no longer touching water.

Blue Dev
  • 142
  • 8
  • I deleted #runTaskLater and replaced it with #runTaskTimer, and it spams me even more, like 150+ massage per sec. did I do it right? – PGK Jul 08 '22 at 18:40
  • @PGK Sounds like you're starting a new task timer every time `PlayerMoveEvent` is being called. I recommend making a boolean outside of the OnWater method ([which REALLY needs to be renamed onWater](https://www.javatpoint.com/java-naming-conventions)), and setting it to true when a tasktimer starts, then don't start one while the boolean is true. Once the timer gets cancelled, set the boolean to false (indicating the player is no longer in the water). – Blue Dev Jul 10 '22 at 18:24