Alternatives for TimeUnit.SECONDS.sleep(1); SpigotMC/ SpigotMC event not running

392 views Asked by At

I was trying to make a Minecraft plugin while having this problem, but I thought this would be more of a Java discussion

So I tried to make a spawner that would spawn a chicken every second (for testing, it would become every minute when I'm done), but while I was testing the event doesn't seem to run (because TimeUnit.SECONDS.sleep() would block the MC thread). So may I have an alternative? The delay I'm using as for now is TimeUnit.SECONDS.sleep(*insert some number here*);
As shown here:

Note: I already tried using setTaskTimer and scheduleSyncRepeatingTask as shown in the answers, but they didn't seem to work. Is this an event issue or a spawnEntity issue?

package com.TheRealBee.Bows.Event10;


import org.bukkit.ChatColor;
import org.bukkit.Material;
import org.bukkit.entity.EntityType;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.block.BlockPlaceEvent;

import java.util.concurrent.TimeUnit;


public class EventManager10 implements Listener {
        @EventHandler

        public void onNukePlace(BlockPlaceEvent e){


            // Return if it's not TNT, doesn't have ItemMeta or doesn't have a custom dispaly name
            if(!e.getBlock().getType().equals(Material.GOLD_BLOCK) || !e.getItemInHand().hasItemMeta() || !e.getItemInHand().getItemMeta().hasDisplayName())
                return;
            // Return if the item display name is not correct
            if(!e.getItemInHand().getItemMeta().getDisplayName().equals(ChatColor.WHITE+"Spawner"))
                return;
            // Create the explosion
            try {
                for (int i = 0; i < 300000000; i++) {
                    e.getBlock().getLocation().getWorld().spawnEntity(e.getBlock().getLocation(), EntityType.CHICKEN);
                    TimeUnit.SECONDS.sleep(1);
                }
            }
            catch(InterruptedException ex)
            {
                Thread.currentThread().interrupt();
            }
            
        }
}

2

There are 2 answers

6
C1rcu1tB04rd On BEST ANSWER

You should use Bukkit.getScheduler().scheduleSyncRepeatingTask(...) in place of that for loop and TimeUnit.SECONDS.sleep

Bukkit.getScheduler().scheduleSyncRepeatingTask(plugin, new Runnable() {
    @Override
    public void run() {
        e.getBlock().getLocation().getWorld().spawnEntity(e.getBlock().getLocation(), EntityType.CHICKEN);
    }

}, 0L, 20L)

plugin should be the instance of your plugin

0L is the delay (in ticks) before the first task is run

20L is the delay (in ticks) before the next task is run

scheduleSyncRepeatingTask in the Spigot JavaDoc

As this is a spawner I assume you will want to stop spawning chickens when the block is broken. You can cancel a task using its taskID. The taskID is the integer that scheduleSyncRepeatingTask returns. You should save this taskID because you can cancel the task later (when the block breaks). To cancel the task you can use cancelTask:

Bukkit.getServer().getScheduler().cancelTask(taskID);

cancelTask in the Spigot JavaDoc

Save this taskID using for example a HashMap. When the block is place you should save the coordinates as key and the taskID as the value in the HashMap. When the block is broken (use the block break event) you should lookup the coordinates of the broken block in that HashMap. If the coordinates exist in the HashMap you should cancel the task and remove the entry from the HashMap.

3
Lucan On

As mentioned, you could use a runnable. Bukkit has baked in methods to utilise this Java feature. This is preferred as using Thread.sleep() (what TimeUnit.SECONDS.sleep() wraps) will pause the main thread in which Minecraft is running; calling that will pause everything (player movement, other plugins, world generation etc) and will cause clients to disconnect if prolonged.

To get the result you're after; a repeating action, you should use the runTaskTimer scheduler. There are a few ways to call this scheduler but the easiest by far is like so:

Bukkit.getScheduler().runTaskTimer(plugin, () -> e.getBlock().getLocation().getWorld().spawnEntity(e.getBlock().getLocation(), EntityType.CHICKEN), 0L, 100L);

Where plugin is the instance of your plugin, () -> ... is a lambda expression to call the one action we want to execute, 0L is the delay to start the task (where L represents a long) and finally 100L is 5 seconds in ticks. There are 20 ticks per second so you would perform seconds * 20 to get the ticks you want for the delay (e.g. 60 * 20 = 1200L for 1 minute).

runTaskTimer will return an instance of BukkitTask that you can store and then later cancel when you want the chickens to stop spawning.

There are a few ways to get your plugin instance but one way would be to simply pass the instance to the listener when you call it from your main, e.g.:

Bukkit.getPluginManager().registerEvents(new EventManager10(this), this);

And then you would swap out the default constructor for your own in EventManager10:


private final JavaPlugin plugin;

public EventManager10(JavaPlugin plugin){
    this.plugin = plugin;
}

Generally speaking, EventManager10 is not in line with Java naming conventions, perhaps take a read of those as it will help you as your project expands.