diff --git a/Essentials/build.gradle b/Essentials/build.gradle index 5134e7daa02..7a74d036e5e 100644 --- a/Essentials/build.gradle +++ b/Essentials/build.gradle @@ -25,6 +25,7 @@ dependencies { api(project(path: ':providers:PaperProvider', configuration: 'shadow')){ exclude(module: 'paper-api') } + api project(':providers:FoliaProvider') api(project(':providers:NMSReflectionProvider')) { exclude(module: 'bukkit') } @@ -73,6 +74,7 @@ shadowJar { include (dependency('net.kyori:adventure-text-minimessage')) include (project(':providers:BaseProviders')) include (project(path: ':providers:PaperProvider', configuration: 'shadow')) + include (project(':providers:FoliaProvider')) include (project(':providers:NMSReflectionProvider')) include (project(':providers:1_8Provider')) include (project(':providers:1_12Provider')) diff --git a/Essentials/src/main/java/com/earth2me/essentials/AsyncTeleport.java b/Essentials/src/main/java/com/earth2me/essentials/AsyncTeleport.java index b5e687f2557..43e98149b5d 100644 --- a/Essentials/src/main/java/com/earth2me/essentials/AsyncTeleport.java +++ b/Essentials/src/main/java/com/earth2me/essentials/AsyncTeleport.java @@ -139,19 +139,6 @@ public void nowUnsafe(Location loc, TeleportCause cause, CompletableFuture taskLock = new CompletableFuture<>(); - Bukkit.getScheduler().runTask(ess, () -> { - runnable.run(); - taskLock.complete(new Object()); - }); - taskLock.get(); - } - protected void nowAsync(final IUser teleportee, final ITarget target, final TeleportCause cause, final CompletableFuture future) { cancel(false); @@ -169,8 +156,8 @@ protected void nowAsync(final IUser teleportee, final ITarget target, final Tele } try { - runOnMain(() -> teleportee.getBase().eject()); //EntityDismountEvent requires a sync context. - } catch (final ExecutionException | InterruptedException e) { + ess.ensureEntity(teleportee.getBase(), () -> teleportee.getBase().eject()); //EntityDismountEvent requires a sync context. + } catch (final RuntimeException e) { future.completeExceptionally(e); return; } @@ -186,38 +173,38 @@ protected void nowAsync(final IUser teleportee, final ITarget target, final Tele targetLoc.setZ(LocationUtil.getZInsideWorldBorder(targetLoc.getWorld(), targetLoc.getBlockZ())); } PaperLib.getChunkAtAsync(targetLoc.getWorld(), targetLoc.getBlockX() >> 4, targetLoc.getBlockZ() >> 4, true, true).thenAccept(chunk -> { - Location loc = targetLoc; - if (LocationUtil.isBlockUnsafeForUser(ess, teleportee, chunk.getWorld(), loc.getBlockX(), loc.getBlockY(), loc.getBlockZ())) { - if (ess.getSettings().isTeleportSafetyEnabled()) { - if (ess.getSettings().isForceDisableTeleportSafety()) { - //The chunk we're teleporting to is 100% going to be loaded here, no need to teleport async. - teleportee.getBase().teleport(loc, cause); - } else { - try { - //There's a chance the safer location is outside the loaded chunk so still teleport async here. - PaperLib.teleportAsync(teleportee.getBase(), LocationUtil.getSafeDestination(ess, teleportee, loc), cause); - } catch (final Exception e) { - future.completeExceptionally(e); - return; + ess.scheduleLocationDelayedTask(targetLoc, () -> { + Location loc = targetLoc; + if (LocationUtil.isBlockUnsafeForUser(ess, teleportee, chunk.getWorld(), loc.getBlockX(), loc.getBlockY(), loc.getBlockZ())) { + if (ess.getSettings().isTeleportSafetyEnabled()) { + if (ess.getSettings().isForceDisableTeleportSafety()) { + PaperLib.teleportAsync(teleportee.getBase(), loc, cause); + } else { + try { + //There's a chance the safer location is outside the loaded chunk so still teleport async here. + PaperLib.teleportAsync(teleportee.getBase(), LocationUtil.getSafeDestination(ess, teleportee, loc), cause); + } catch (final Exception e) { + future.completeExceptionally(e); + return; + } } + } else { + future.completeExceptionally(new TranslatableException("unsafeTeleportDestination", loc.getWorld().getName(), loc.getBlockX(), loc.getBlockY(), loc.getBlockZ())); + return; } } else { - future.completeExceptionally(new TranslatableException("unsafeTeleportDestination", loc.getWorld().getName(), loc.getBlockX(), loc.getBlockY(), loc.getBlockZ())); - return; - } - } else { - if (ess.getSettings().isForceDisableTeleportSafety()) { - //The chunk we're teleporting to is 100% going to be loaded here, no need to teleport async. - teleportee.getBase().teleport(loc, cause); - } else { - if (ess.getSettings().isTeleportToCenterLocation()) { - loc = LocationUtil.getRoundedDestination(loc); + if (ess.getSettings().isForceDisableTeleportSafety()) { + PaperLib.teleportAsync(teleportee.getBase(), loc, cause); + } else { + if (ess.getSettings().isTeleportToCenterLocation()) { + loc = LocationUtil.getRoundedDestination(loc); + } + //There's a *small* chance the rounded destination produces a location outside the loaded chunk so still teleport async here. + PaperLib.teleportAsync(teleportee.getBase(), loc, cause); } - //There's a *small* chance the rounded destination produces a location outside the loaded chunk so still teleport async here. - PaperLib.teleportAsync(teleportee.getBase(), loc, cause); } - } - future.complete(true); + future.complete(true); + }); }).exceptionally(th -> { future.completeExceptionally(th); return null; diff --git a/Essentials/src/main/java/com/earth2me/essentials/AsyncTimedTeleport.java b/Essentials/src/main/java/com/earth2me/essentials/AsyncTimedTeleport.java index d1c37423c06..b03a58bc682 100644 --- a/Essentials/src/main/java/com/earth2me/essentials/AsyncTimedTeleport.java +++ b/Essentials/src/main/java/com/earth2me/essentials/AsyncTimedTeleport.java @@ -1,13 +1,14 @@ package com.earth2me.essentials; -import java.util.UUID; -import java.util.concurrent.CompletableFuture; - +import net.ess3.api.IEssentials; +import net.ess3.api.IUser; +import net.ess3.provider.SchedulingProvider; import org.bukkit.Location; import org.bukkit.event.player.PlayerTeleportEvent.TeleportCause; -import net.ess3.api.IEssentials; -import net.ess3.api.IUser; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; + import net.essentialsx.api.v2.events.TeleportWarmupCancelledEvent; import net.essentialsx.api.v2.events.TeleportWarmupCancelledEvent.CancelReason; @@ -31,7 +32,7 @@ public class AsyncTimedTeleport implements Runnable { private final boolean timer_canMove; private final Trade timer_chargeFor; private final TeleportCause timer_cause; - private int timer_task; + private SchedulingProvider.EssentialsTask timer_task; private double timer_health; AsyncTimedTeleport(final IUser user, final IEssentials ess, final AsyncTeleport teleport, final long delay, final IUser teleportUser, final ITarget target, final Trade chargeFor, final TeleportCause cause, final boolean respawn) { @@ -55,7 +56,7 @@ public class AsyncTimedTeleport implements Runnable { this.timer_respawn = respawn; this.timer_canMove = user.isAuthorized("essentials.teleport.timer.move"); - timer_task = ess.runTaskTimerAsynchronously(this, 20, 20).getTaskId(); + timer_task = ess.runTaskTimerAsynchronously(this, 20, 20); if (future != null) { this.parentFuture = future; @@ -142,16 +143,16 @@ public void run() { } } - ess.scheduleSyncDelayedTask(new DelayedTeleportTask()); + ess.scheduleEntityDelayedTask(teleportOwner.getBase(), new DelayedTeleportTask()); } //If we need to cancelTimer a pending teleportPlayer call this method void cancelTimer(final boolean notifyUser) { - if (timer_task == -1) { + if (timer_task == null) { return; } try { - ess.getServer().getScheduler().cancelTask(timer_task); + timer_task.cancel(); final IUser teleportUser = ess.getUser(this.timer_teleportee); if (teleportUser != null && teleportUser.getBase() != null) { @@ -167,7 +168,7 @@ void cancelTimer(final boolean notifyUser) { } } } finally { - timer_task = -1; + timer_task = null; } } } diff --git a/Essentials/src/main/java/com/earth2me/essentials/Backup.java b/Essentials/src/main/java/com/earth2me/essentials/Backup.java index a6831ab3bb1..883f16f88fe 100644 --- a/Essentials/src/main/java/com/earth2me/essentials/Backup.java +++ b/Essentials/src/main/java/com/earth2me/essentials/Backup.java @@ -1,6 +1,7 @@ package com.earth2me.essentials; import net.ess3.api.IEssentials; +import net.ess3.provider.SchedulingProvider; import org.bukkit.Server; import org.bukkit.command.CommandSender; @@ -17,10 +18,10 @@ public class Backup implements Runnable { private transient final Server server; private transient final IEssentials ess; private final AtomicBoolean pendingShutdown = new AtomicBoolean(false); - private transient boolean running = false; - private transient int taskId = -1; - private transient boolean active = false; - private transient CompletableFuture taskLock = null; + private transient volatile boolean running = false; + private transient volatile SchedulingProvider.EssentialsTask task = null; + private transient volatile boolean active = false; + private transient volatile CompletableFuture taskLock = null; public Backup(final IEssentials ess) { this.ess = ess; @@ -36,10 +37,10 @@ public void onPlayerJoin() { public synchronized void stopTask() { running = false; - if (taskId != -1) { - server.getScheduler().cancelTask(taskId); + if (task != null) { + task.cancel(); } - taskId = -1; + task = null; } private synchronized void startTask() { @@ -48,7 +49,7 @@ private synchronized void startTask() { if (interval < 1200) { return; } - taskId = ess.scheduleSyncRepeatingTask(this, interval, interval); + task = ess.scheduleGlobalRepeatingTask(this, interval, interval); running = true; } } @@ -123,7 +124,7 @@ public void run() { } if (!pendingShutdown.get()) { - ess.scheduleSyncDelayedTask(new BackupEnableSaveTask()); + ess.scheduleGlobalDelayedTask(new BackupEnableSaveTask()); } } }); diff --git a/Essentials/src/main/java/com/earth2me/essentials/Essentials.java b/Essentials/src/main/java/com/earth2me/essentials/Essentials.java index 1629f8e4328..e4e230a7942 100644 --- a/Essentials/src/main/java/com/earth2me/essentials/Essentials.java +++ b/Essentials/src/main/java/com/earth2me/essentials/Essentials.java @@ -28,6 +28,7 @@ import com.earth2me.essentials.commands.NotEnoughArgumentsException; import com.earth2me.essentials.commands.PlayerNotFoundException; import com.earth2me.essentials.commands.QuietAbortException; +import com.earth2me.essentials.config.EssentialsConfiguration; import com.earth2me.essentials.economy.EconomyLayers; import com.earth2me.essentials.economy.vault.VaultEconomyProvider; import com.earth2me.essentials.items.AbstractItemDb; @@ -46,11 +47,11 @@ import com.earth2me.essentials.updatecheck.UpdateChecker; import com.earth2me.essentials.userstorage.ModernUserMap; import com.earth2me.essentials.utils.FormatUtil; +import com.earth2me.essentials.utils.ModernPaperEnvironment; import com.earth2me.essentials.utils.PasteUtil; import com.earth2me.essentials.utils.VersionUtil; import io.papermc.lib.PaperLib; import net.ess3.api.Economy; -import com.earth2me.essentials.config.EssentialsConfiguration; import net.ess3.api.IEssentials; import net.ess3.api.IItemDb; import net.ess3.api.IJails; @@ -69,15 +70,19 @@ import net.ess3.provider.KnownCommandsProvider; import net.ess3.provider.PlayerLocaleProvider; import net.ess3.provider.ProviderListener; +import net.ess3.provider.SchedulingProvider; import net.ess3.provider.ServerStateProvider; import net.ess3.provider.providers.BaseBannerDataProvider; import net.ess3.provider.providers.BaseInventoryViewProvider; import net.ess3.provider.providers.BlockMetaSpawnerItemProvider; import net.ess3.provider.providers.BukkitMaterialTagProvider; +import net.ess3.provider.providers.BukkitSchedulingProvider; import net.ess3.provider.providers.BukkitSpawnerBlockProvider; import net.ess3.provider.providers.BukkitTileEntityProvider; +import net.ess3.provider.providers.BukkitWorldTileEntityCountProvider; import net.ess3.provider.providers.FixedHeightWorldInfoProvider; import net.ess3.provider.providers.FlatSpawnEggProvider; +import net.ess3.provider.providers.FoliaSchedulingProvider; import net.ess3.provider.providers.LegacyBannerDataProvider; import net.ess3.provider.providers.LegacyBiomeNameProvider; import net.ess3.provider.providers.LegacyPatternTypeProvider; @@ -105,10 +110,12 @@ import net.ess3.provider.providers.PaperServerStateProvider; import net.ess3.provider.providers.PaperTickCountProvider; import net.ess3.provider.providers.PaperTileEntityProvider; +import net.ess3.provider.providers.PaperWorldTileEntityCountProvider; import net.ess3.provider.providers.PrehistoricPotionMetaProvider; import net.essentialsx.api.v2.services.BalanceTop; import net.essentialsx.api.v2.services.mail.MailService; import org.bukkit.Bukkit; +import org.bukkit.Location; import org.bukkit.Server; import org.bukkit.World; import org.bukkit.block.Block; @@ -118,6 +125,7 @@ import org.bukkit.command.PluginCommand; import org.bukkit.command.PluginIdentifiableCommand; import org.bukkit.command.TabCompleter; +import org.bukkit.entity.Entity; import org.bukkit.entity.Player; import org.bukkit.event.Cancellable; import org.bukkit.event.EventHandler; @@ -132,8 +140,6 @@ import org.bukkit.plugin.PluginManager; import org.bukkit.plugin.ServicePriority; import org.bukkit.plugin.java.JavaPlugin; -import org.bukkit.scheduler.BukkitScheduler; -import org.bukkit.scheduler.BukkitTask; import java.util.ArrayList; import java.util.Collection; @@ -146,6 +152,8 @@ import java.util.MissingResourceException; import java.util.Set; import java.util.UUID; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; import java.util.function.Predicate; import java.util.logging.Level; import java.util.logging.Logger; @@ -261,6 +269,10 @@ public void onEnable() { getLogger().severe(getAdventureFacet().miniToLegacy(tlLiteral("serverSnapshot"))); } + if (PaperLib.isPaper()) { + PaperLib.setCustomEnvironment(new ModernPaperEnvironment()); + } + final PluginManager pm = getServer().getPluginManager(); for (final Plugin plugin : pm.getPlugins()) { if (plugin.getDescription().getName().startsWith("Essentials") && !plugin.getDescription().getVersion().equals(this.getDescription().getVersion()) && !plugin.getDescription().getName().equals("EssentialsAntiCheat")) { @@ -327,9 +339,6 @@ public void onEnable() { confList.add(jails); execTimer.mark("Init(Jails)"); - EconomyLayers.onEnable(this); - execTimer.mark("Init(EconomyLayers)"); - // Spawner item provider only uses one, but it's here for legacy... providerFactory.registerProvider(BlockMetaSpawnerItemProvider.class); @@ -402,10 +411,16 @@ public void onEnable() { // Tile Entity Provider providerFactory.registerProvider(BukkitTileEntityProvider.class, PaperTileEntityProvider.class); + // World Tile Entity Count Provider + providerFactory.registerProvider(BukkitWorldTileEntityCountProvider.class, PaperWorldTileEntityCountProvider.class); + // Tick Count Provider providerFactory.registerProvider(PaperTickCountProvider.class); - if (!TESTING) { + // Scheduling provider + providerFactory.registerProvider(FoliaSchedulingProvider.class, BukkitSchedulingProvider.class); + + if (!TESTING || true) { // FIXME: The scheduling provider is necessary for tests to pass without adding checks for TESTING everywhere providerFactory.finalizeRegistration(); } @@ -426,6 +441,12 @@ public void onEnable() { } execTimer.mark("Init(Providers)"); + + EconomyLayers.onEnable(this); + execTimer.mark("Init(EconomyLayers)"); + + registerListeners(getServer().getPluginManager()); + initAdventureFacet(); reload(); // The item spawn blacklist is loaded with all other settings, before the item @@ -436,7 +457,7 @@ public void onEnable() { alternativeCommandsHandler = new AlternativeCommandsHandler(this); timer = new EssentialsTimer(this); - scheduleSyncRepeatingTask(timer, 1000, 50); + scheduleGlobalRepeatingTask(timer, 1000, 50); Economy.setEss(this); execTimer.mark("RegHandler"); @@ -489,8 +510,6 @@ public void saveConfig() { } private void registerListeners(final PluginManager pm) { - HandlerList.unregisterAll(this); - if (getSettings().isDebug()) { LOGGER.log(Level.INFO, "Registering Listeners"); } @@ -589,7 +608,7 @@ public void onDisable() { EssentialsConfiguration.shutdownExecutor(); PasteUtil.shutdownExecutor(); - getServer().getScheduler().cancelTasks(this); + provider(SchedulingProvider.class).cancelAllTasks(); HandlerList.unregisterAll(this); } @@ -612,9 +631,6 @@ public void reload() { } } - final PluginManager pm = getServer().getPluginManager(); - registerListeners(pm); - initAdventureFacet(); } @@ -910,11 +926,6 @@ public void showError(final CommandSource sender, final Throwable exception, fin } } - @Override - public BukkitScheduler getScheduler() { - return this.getServer().getScheduler(); - } - @Override public List getJailedPlayers() { return getUsers().getAllUserUUIDs().stream().map(this::getUser).filter(Objects::nonNull).filter(User::isJailed).map(User::getBase).filter(Objects::nonNull).collect(Collectors.toList()); @@ -1243,33 +1254,135 @@ public void broadcastTl(final IUser sender, final Predicate shouldExclude } @Override - public BukkitTask runTaskAsynchronously(final Runnable run) { - return this.getScheduler().runTaskAsynchronously(this, run); + public void scheduleInitTask(Runnable runnable) { + provider(SchedulingProvider.class).registerInitTask(runnable); + } + + @Override + public void runTaskAsynchronously(final Runnable run) { + provider(SchedulingProvider.class).runAsyncTask(run); + } + + @Override + public void runTaskLaterAsynchronously(final Runnable run, final long delay) { + provider(SchedulingProvider.class).runAsyncTaskLater(run, delay); + } + + @Override + public SchedulingProvider.EssentialsTask runTaskTimerAsynchronously(final Runnable run, final long delay, final long period) { + return provider(SchedulingProvider.class).runAsyncTaskRepeating(run, delay, period); + } + + @Override + public void scheduleEntityDelayedTask(Entity entity, Runnable run) { + provider(SchedulingProvider.class).runEntityTask(entity, run); + } + + @Override + public SchedulingProvider.EssentialsTask scheduleEntityDelayedTask(Entity entity, Runnable run, long delay) { + return provider(SchedulingProvider.class).runEntityTask(entity, run, delay); + } + + @Override + public SchedulingProvider.EssentialsTask scheduleEntityRepeatingTask(Entity entity, Runnable run, long delay, long period) { + return provider(SchedulingProvider.class).runEntityTaskRepeating(entity, run, delay, period); + } + + @Override + public void scheduleLocationDelayedTask(Location location, Runnable run) { + provider(SchedulingProvider.class).runLocationalTask(location, run); + } + + @Override + public void scheduleLocationDelayedTask(Location location, Runnable run, long delay) { + provider(SchedulingProvider.class).runLocationalTask(location, run, delay); } @Override - public BukkitTask runTaskLaterAsynchronously(final Runnable run, final long delay) { - return this.getScheduler().runTaskLaterAsynchronously(this, run, delay); + public SchedulingProvider.EssentialsTask scheduleLocationRepeatingTask(Location location, Runnable run, long delay, long period) { + return provider(SchedulingProvider.class).runLocationalTaskRepeating(location, run, delay, period); } @Override - public BukkitTask runTaskTimerAsynchronously(final Runnable run, final long delay, final long period) { - return this.getScheduler().runTaskTimerAsynchronously(this, run, delay, period); + public void scheduleGlobalDelayedTask(Runnable run, long delay) { + provider(SchedulingProvider.class).runGlobalLocationalTask(run, delay); } @Override - public int scheduleSyncDelayedTask(final Runnable run) { - return this.getScheduler().scheduleSyncDelayedTask(this, run); + public SchedulingProvider.EssentialsTask scheduleGlobalRepeatingTask(Runnable run, long delay, long period) { + return provider(SchedulingProvider.class).runGlobalLocationalTaskRepeating(run, delay, period); } @Override - public int scheduleSyncDelayedTask(final Runnable run, final long delay) { - return this.getScheduler().scheduleSyncDelayedTask(this, run, delay); + public boolean isEntityThread(Entity entity) { + return provider(SchedulingProvider.class).isEntityThread(entity); } @Override - public int scheduleSyncRepeatingTask(final Runnable run, final long delay, final long period) { - return this.getScheduler().scheduleSyncRepeatingTask(this, run, delay, period); + public boolean isRegionThread(Location location) { + return provider(SchedulingProvider.class).isRegionThread(location); + } + + @Override + public boolean isGlobalThread() { + return provider(SchedulingProvider.class).isGlobalThread(); + } + + @Override + public void ensureEntity(Entity entity, Runnable runnable) { + if (isEntityThread(entity)) { + runnable.run(); + return; + } + + final CompletableFuture taskLock = new CompletableFuture<>(); + scheduleEntityDelayedTask(entity, () -> { + runnable.run(); + taskLock.complete(new Object()); + }); + try { + taskLock.get(); + } catch (InterruptedException | ExecutionException e) { + throw new RuntimeException(e); + } + } + + @Override + public void ensureRegion(Location location, Runnable runnable) { + if (isRegionThread(location)) { + runnable.run(); + return; + } + + final CompletableFuture taskLock = new CompletableFuture<>(); + scheduleLocationDelayedTask(location, () -> { + runnable.run(); + taskLock.complete(new Object()); + }); + try { + taskLock.get(); + } catch (InterruptedException | ExecutionException e) { + throw new RuntimeException(e); + } + } + + @Override + public void ensureGlobal(Runnable runnable) { + if (isGlobalThread()) { + runnable.run(); + return; + } + + final CompletableFuture taskLock = new CompletableFuture<>(); + scheduleGlobalDelayedTask(() -> { + runnable.run(); + taskLock.complete(new Object()); + }); + try { + taskLock.get(); + } catch (InterruptedException | ExecutionException e) { + throw new RuntimeException(e); + } } @Override diff --git a/Essentials/src/main/java/com/earth2me/essentials/EssentialsBlockListener.java b/Essentials/src/main/java/com/earth2me/essentials/EssentialsBlockListener.java index 2890bc2a79e..b5626e60916 100644 --- a/Essentials/src/main/java/com/earth2me/essentials/EssentialsBlockListener.java +++ b/Essentials/src/main/java/com/earth2me/essentials/EssentialsBlockListener.java @@ -44,7 +44,7 @@ public void onBlockPlace(final BlockPlaceEvent event) { final User user = ess.getUser(event.getPlayer()); if (user.hasUnlimited(is) && user.getBase().getGameMode() == GameMode.SURVIVAL) { - ess.scheduleSyncDelayedTask(() -> { + ess.scheduleEntityDelayedTask(user.getBase(), () -> { if (is != null && is.getType() != null && !MaterialUtil.isAir(is.getType())) { final ItemStack cloneIs = is.clone(); cloneIs.setAmount(1); diff --git a/Essentials/src/main/java/com/earth2me/essentials/EssentialsEntityListener.java b/Essentials/src/main/java/com/earth2me/essentials/EssentialsEntityListener.java index 1d1fbff1eec..c396836df90 100644 --- a/Essentials/src/main/java/com/earth2me/essentials/EssentialsEntityListener.java +++ b/Essentials/src/main/java/com/earth2me/essentials/EssentialsEntityListener.java @@ -110,7 +110,7 @@ public void run() { } } - ess.scheduleSyncDelayedTask(new PowerToolInteractTask()); + ess.scheduleEntityDelayedTask(attacker.getBase(), new PowerToolInteractTask()); event.setCancelled(true); return; diff --git a/Essentials/src/main/java/com/earth2me/essentials/EssentialsPlayerListener.java b/Essentials/src/main/java/com/earth2me/essentials/EssentialsPlayerListener.java index 3b8c2f6e3f4..c267868e36f 100644 --- a/Essentials/src/main/java/com/earth2me/essentials/EssentialsPlayerListener.java +++ b/Essentials/src/main/java/com/earth2me/essentials/EssentialsPlayerListener.java @@ -22,6 +22,7 @@ import net.ess3.api.IEssentials; import net.ess3.api.events.AfkStatusChangeEvent; import net.ess3.provider.CommandSendListenerProvider; +import net.ess3.provider.SchedulingProvider; import net.ess3.provider.FormattedCommandAliasProvider; import net.ess3.provider.InventoryViewProvider; import net.ess3.provider.KnownCommandsProvider; @@ -94,7 +95,7 @@ public class EssentialsPlayerListener implements Listener { private final transient IEssentials ess; - private final ConcurrentHashMap pendingMotdTasks = new ConcurrentHashMap<>(); + private final ConcurrentHashMap pendingMotdTasks = new ConcurrentHashMap<>(); public EssentialsPlayerListener(final IEssentials parent) { this.ess = parent; @@ -289,9 +290,9 @@ public void onPlayerMove(final PlayerMoveEvent event) { public void onPlayerQuit(final PlayerQuitEvent event) { final User user = ess.getUser(event.getPlayer()); - final Integer pendingId = pendingMotdTasks.remove(user.getUUID()); - if (pendingId != null) { - ess.getScheduler().cancelTask(pendingId); + final SchedulingProvider.EssentialsTask pendingTask = pendingMotdTasks.remove(user.getUUID()); + if (pendingTask != null) { + pendingTask.cancel(); } if (hideJoinQuitMessages() || ess.getSettings().allowSilentJoinQuit() && user.isAuthorized("essentials.silentquit")) { @@ -484,7 +485,7 @@ private void joinFlow(final User user, final long currentTime, final String mess final int motdDelay = ess.getSettings().getMotdDelay() / 50; final Runnable motdTask = () -> motdFlow(user); if (motdDelay > 0) { - pendingMotdTasks.put(user.getUUID(), ess.scheduleSyncDelayedTask(motdTask, motdDelay)); + pendingMotdTasks.put(user.getUUID(), ess.scheduleEntityDelayedTask(user.getBase(), motdTask, motdDelay)); } else { motdTask.run(); } @@ -600,7 +601,7 @@ private void delayedJoin(final Player player, final String message) { dUser.updateActivity(false, AfkStatusChangeEvent.Cause.JOIN); dUser.stopTransaction(); - ess.scheduleSyncDelayedTask(() -> { + ess.scheduleEntityDelayedTask(player, () -> { final User user = ess.getUser(player); if (!user.getBase().isOnline()) { @@ -770,7 +771,7 @@ public void onPlayerBucketEmpty(final PlayerBucketEmptyEvent event) { final User user = ess.getUser(event.getPlayer()); if (user.hasUnlimited(new ItemStack(event.getBucket()))) { event.getItemStack().setType(event.getBucket()); - ess.scheduleSyncDelayedTask(user.getBase()::updateInventory); + ess.scheduleEntityDelayedTask(user.getBase(), user.getBase()::updateInventory); } } @@ -1030,7 +1031,7 @@ public void run() { } } - ess.scheduleSyncDelayedTask(new DelayedClickJumpTask()); + ess.scheduleEntityDelayedTask(user.getBase(), new DelayedClickJumpTask()); } catch (final Exception ex) { if (ess.getSettings().isDebug()) { ess.getLogger().log(Level.WARNING, ex.getMessage(), ex); @@ -1061,7 +1062,7 @@ public void run() { } } - ess.scheduleSyncDelayedTask(new PowerToolUseTask()); + ess.scheduleEntityDelayedTask(user.getBase(), new PowerToolUseTask()); } } @@ -1123,7 +1124,7 @@ public void onInventoryClickEvent(final InventoryClickEvent event) { } if (refreshPlayer != null) { - ess.scheduleSyncDelayedTask(refreshPlayer::updateInventory, 1); + ess.scheduleEntityDelayedTask(refreshPlayer, refreshPlayer::updateInventory, 1); } } @@ -1186,7 +1187,7 @@ public void onInventoryCloseEvent(final InventoryCloseEvent event) { } if (refreshPlayer != null) { - ess.scheduleSyncDelayedTask(refreshPlayer::updateInventory, 1); + ess.scheduleEntityDelayedTask(refreshPlayer, refreshPlayer::updateInventory, 1); } } @@ -1214,7 +1215,7 @@ public void onPlayerGameModeChange(final PlayerGameModeChangeEvent event) { final Player player = event.getPlayer(); if (player.isFlying() && player.getAllowFlight() && user.isAuthorized("essentials.fly")) { // The gamemode change happens after the event, so we need to delay the flight enable - ess.scheduleSyncDelayedTask(() -> { + ess.scheduleEntityDelayedTask(player, () -> { player.setAllowFlight(true); player.setFlying(true); }, 1); diff --git a/Essentials/src/main/java/com/earth2me/essentials/EssentialsTimer.java b/Essentials/src/main/java/com/earth2me/essentials/EssentialsTimer.java index fbc96e1d214..19b12ab0985 100644 --- a/Essentials/src/main/java/com/earth2me/essentials/EssentialsTimer.java +++ b/Essentials/src/main/java/com/earth2me/essentials/EssentialsTimer.java @@ -13,9 +13,8 @@ public class EssentialsTimer implements Runnable { private final transient IEssentials ess; private final transient Set onlineUsers = new HashSet<>(); // Field is necessary for hidden users - private final LinkedList history = new LinkedList<>(); - @SuppressWarnings("FieldCanBeLocal") - private final long maxTime = 10 * 1000000; + private final transient LinkedList history = new LinkedList<>(); + private static final long maxTime = 10 * 1000000; @SuppressWarnings("FieldCanBeLocal") private final long tickInterval = 50; private transient long lastPoll = System.nanoTime(); @@ -31,18 +30,8 @@ public class EssentialsTimer implements Runnable { public void run() { final long startTime = System.nanoTime(); final long currentTime = System.currentTimeMillis(); - long timeSpent = (startTime - lastPoll) / 1000; - if (timeSpent == 0) { - timeSpent = 1; - } - if (history.size() > 10) { - history.remove(); - } - final double tps = tickInterval * 1000000.0 / timeSpent; - if (tps <= 21) { - history.add(tps); - } - lastPoll = startTime; + recordTick(startTime); + int count = 0; onlineUsers.clear(); for (final Player player : ess.getOnlinePlayers()) { @@ -100,7 +89,36 @@ public void run() { } } - public double getAverageTPS() { + /** + * Records how long actually elapsed since the previous run() firing, relative to this + * task's nominal {@link #tickInterval}-tick period, as one more sample in the rolling TPS + * history. Synchronized because getAverageTPS() below can now be called from another + * thread (a player's /tps, or chat placeholder resolution) while this method is still only + * ever invoked from Folia's global region thread via the repeating task this class is + * scheduled as. + */ + private synchronized void recordTick(final long startTime) { + long timeSpent = (startTime - lastPoll) / 1000; + if (timeSpent == 0) { + timeSpent = 1; + } + if (history.size() > 10) { + history.remove(); + } + final double tps = tickInterval * 1000000.0 / timeSpent; + if (tps <= 21) { + history.add(tps); + } + lastPoll = startTime; + } + + /** + * Approximates the global region's own TPS, based on how far this task's actual firing + * interval has drifted from its nominal {@link #tickInterval}-tick period. This reflects the + * health of the global region only - a per-player region could be lagging independently + * while this stays at or near 20. + */ + public synchronized double getAverageTPS() { double avg = 0; for (final Double f : history) { if (f != null) { diff --git a/Essentials/src/main/java/com/earth2me/essentials/IEssentials.java b/Essentials/src/main/java/com/earth2me/essentials/IEssentials.java index 48a2aa2b130..f84bfea5017 100644 --- a/Essentials/src/main/java/com/earth2me/essentials/IEssentials.java +++ b/Essentials/src/main/java/com/earth2me/essentials/IEssentials.java @@ -9,18 +9,19 @@ import com.earth2me.essentials.perm.PermissionsHandler; import com.earth2me.essentials.updatecheck.UpdateChecker; import com.earth2me.essentials.userstorage.IUserMap; +import net.ess3.provider.SchedulingProvider; import net.ess3.provider.Provider; import net.essentialsx.api.v2.services.BalanceTop; import net.essentialsx.api.v2.services.mail.MailService; +import org.bukkit.Location; import org.bukkit.Server; import org.bukkit.World; import org.bukkit.command.Command; import org.bukkit.command.CommandSender; import org.bukkit.command.PluginCommand; +import org.bukkit.entity.Entity; import org.bukkit.entity.Player; import org.bukkit.plugin.Plugin; -import org.bukkit.scheduler.BukkitScheduler; -import org.bukkit.scheduler.BukkitTask; import java.util.Collection; import java.util.List; @@ -80,8 +81,6 @@ public interface IEssentials extends Plugin { ISettings getSettings(); - BukkitScheduler getScheduler(); - List getJailedPlayers(); IJails getJails(); @@ -98,17 +97,45 @@ public interface IEssentials extends Plugin { UpdateChecker getUpdateChecker(); - BukkitTask runTaskAsynchronously(Runnable run); + void runTaskAsynchronously(Runnable run); + + void runTaskLaterAsynchronously(Runnable run, long delay); + + SchedulingProvider.EssentialsTask runTaskTimerAsynchronously(Runnable run, long delay, long period); + + void scheduleEntityDelayedTask(Entity entity, Runnable run); + + SchedulingProvider.EssentialsTask scheduleEntityDelayedTask(Entity entity, Runnable run, long delay); + + SchedulingProvider.EssentialsTask scheduleEntityRepeatingTask(Entity entity, Runnable run, long delay, long period); + + void scheduleLocationDelayedTask(Location location, Runnable run); + + void scheduleLocationDelayedTask(Location location, Runnable run, long delay); + + SchedulingProvider.EssentialsTask scheduleLocationRepeatingTask(Location location, Runnable run, long delay, long period); + + default void scheduleGlobalDelayedTask(Runnable run) { + scheduleGlobalDelayedTask(run, 1); + } + + void scheduleGlobalDelayedTask(Runnable run, long delay); + + SchedulingProvider.EssentialsTask scheduleGlobalRepeatingTask(Runnable run, long delay, long period); + + void scheduleInitTask(Runnable runnable); + + boolean isEntityThread(Entity entity); - BukkitTask runTaskLaterAsynchronously(Runnable run, long delay); + boolean isRegionThread(Location location); - BukkitTask runTaskTimerAsynchronously(Runnable run, long delay, long period); + boolean isGlobalThread(); - int scheduleSyncDelayedTask(Runnable run); + void ensureEntity(Entity entity, Runnable runnable); - int scheduleSyncDelayedTask(Runnable run, long delay); + void ensureRegion(Location location, Runnable runnable); - int scheduleSyncRepeatingTask(Runnable run, long delay, long period); + void ensureGlobal(Runnable runnable); PermissionsHandler getPermissionsHandler(); diff --git a/Essentials/src/main/java/com/earth2me/essentials/RandomTeleport.java b/Essentials/src/main/java/com/earth2me/essentials/RandomTeleport.java index 86513571c41..c88425bebd2 100644 --- a/Essentials/src/main/java/com/earth2me/essentials/RandomTeleport.java +++ b/Essentials/src/main/java/com/earth2me/essentials/RandomTeleport.java @@ -156,7 +156,7 @@ public CompletableFuture getRandomLocation(final Location center, fina // Prompts caching random valid locations, up to a maximum number of attempts. public void cacheRandomLocations(final String name) { - ess.getServer().getScheduler().scheduleSyncDelayedTask(ess, () -> { + ess.runTaskAsynchronously(() -> { for (int i = 0; i < this.getFindAttempts(); ++i) { calculateRandomLocation(getCenter(name), getMinRange(name), getMaxRange(name)).thenAccept(location -> { if (isValidRandomLocation(location)) { @@ -186,7 +186,6 @@ private CompletableFuture attemptRandomLocation(final int attempts, fi // Calculates a random location asynchronously. private CompletableFuture calculateRandomLocation(final Location center, final double minRange, final double maxRange) { - final CompletableFuture future = new CompletableFuture<>(); // Find an equally distributed offset by randomly rotating a point inside a rectangle about the origin final double rectX = RANDOM.nextDouble() * (maxRange - minRange) + minRange; final double rectZ = RANDOM.nextDouble() * (maxRange + minRange) - minRange; @@ -214,13 +213,19 @@ private CompletableFuture calculateRandomLocation(final Location cente 360 * RANDOM.nextFloat() - 180, 0 ); + final CompletableFuture future = new CompletableFuture<>(); PaperLib.getChunkAtAsync(location).thenAccept(chunk -> { - if (World.Environment.NETHER.equals(center.getWorld().getEnvironment())) { - location.setY(getNetherYAt(location)); - } else { - location.setY(center.getWorld().getHighestBlockYAt(location) + HIGHEST_BLOCK_Y_OFFSET); - } - future.complete(location); + ess.scheduleLocationDelayedTask(location, () -> { + if (World.Environment.NETHER.equals(center.getWorld().getEnvironment())) { + location.setY(getNetherYAt(location)); + } else { + location.setY(center.getWorld().getHighestBlockYAt(location) + HIGHEST_BLOCK_Y_OFFSET); + } + future.complete(location); + }); + }).exceptionally(th -> { + future.completeExceptionally(th); + return null; }); return future; } diff --git a/Essentials/src/main/java/com/earth2me/essentials/Settings.java b/Essentials/src/main/java/com/earth2me/essentials/Settings.java index fbec7ea904a..a7322d74249 100644 --- a/Essentials/src/main/java/com/earth2me/essentials/Settings.java +++ b/Essentials/src/main/java/com/earth2me/essentials/Settings.java @@ -847,7 +847,7 @@ public void reloadConfig() { if (reloadCount.get() < 2) { // on startup: add plugins again in case they registered commands with the new API // we need to schedule this task before any of the below tasks using _addAlternativeCommand. - ess.scheduleSyncDelayedTask(() -> { + ess.scheduleGlobalDelayedTask(() -> { for (final Plugin plugin : ess.getServer().getPluginManager().getPlugins()) { if (plugin.isEnabled()) { ess.getAlternativeCommandsHandler().addPlugin(plugin); @@ -871,12 +871,7 @@ public void reloadConfig() { disabledBukkitCommands.put(effectiveAlias, removed); } - // This is 2 because Settings are reloaded twice in the startup lifecycle - if (reloadCount.get() < 2) { - ess.scheduleSyncDelayedTask(() -> _addAlternativeCommand(effectiveAlias, toDisable)); - } else { - _addAlternativeCommand(effectiveAlias, toDisable); - } + ess.scheduleGlobalDelayedTask(() -> _addAlternativeCommand(effectiveAlias, toDisable)); mapModified = true; } } @@ -887,11 +882,7 @@ public void reloadConfig() { if (isDebug()) { ess.getLogger().log(Level.INFO, "Syncing commands"); } - if (reloadCount.get() < 2) { - ess.scheduleSyncDelayedTask(syncCommandsProvider::syncCommands); - } else { - syncCommandsProvider.syncCommands(); - } + ess.scheduleGlobalDelayedTask(syncCommandsProvider::syncCommands); } } diff --git a/Essentials/src/main/java/com/earth2me/essentials/User.java b/Essentials/src/main/java/com/earth2me/essentials/User.java index 4dd7d9b4e42..53043e2ed14 100644 --- a/Essentials/src/main/java/com/earth2me/essentials/User.java +++ b/Essentials/src/main/java/com/earth2me/essentials/User.java @@ -92,8 +92,8 @@ public class User extends UserData implements Comparable, IMessageRecipien // Timestamps private transient long lastOnlineActivity; private transient long lastThrottledAction; - private transient long lastActivity = System.currentTimeMillis(); - private transient long teleportInvulnerabilityTimestamp = 0; + private transient volatile long lastActivity = System.currentTimeMillis(); + private transient volatile long teleportInvulnerabilityTimestamp = 0; private long lastNotifiedAboutMailsMs; private long lastHomeConfirmationTimestamp; @@ -861,7 +861,7 @@ public void updateActivityOnInteract(final boolean broadcast) { public void updateActivityOnChat(final boolean broadcast) { if (ess.getSettings().cancelAfkOnChat()) { //Chat happens async, make sure we have a sync context - ess.scheduleSyncDelayedTask(() -> updateActivity(broadcast, AfkStatusChangeEvent.Cause.CHAT)); + ess.scheduleEntityDelayedTask(base, () -> updateActivity(broadcast, AfkStatusChangeEvent.Cause.CHAT)); } } @@ -883,11 +883,13 @@ public void checkActivity() { // If `afk-timeout-command` in config.yml is empty, use default Essentials kicking behaviour instead of executing a command. if (ess.getSettings().getAfkTimeoutCommands().isEmpty()) { - this.getBase().kickPlayer(ess.getAdventureFacet().miniToLegacy(playerTl("autoAfkKickReason", kickTime))); + ess.scheduleEntityDelayedTask(this.getBase(), () -> + this.getBase().kickPlayer(ess.getAdventureFacet().miniToLegacy(playerTl("autoAfkKickReason", kickTime)))); for (final User user : ess.getOnlineUsers()) { if (user.isAuthorized("essentials.kick.notify")) { - user.sendTl("playerKicked", Console.displayName(), getName(), user.playerTl("autoAfkKickReason", kickTime)); + ess.scheduleEntityDelayedTask(user.getBase(), () -> + user.sendTl("playerKicked", Console.displayName(), getName(), user.playerTl("autoAfkKickReason", kickTime))); } } } else { diff --git a/Essentials/src/main/java/com/earth2me/essentials/commands/Commandantioch.java b/Essentials/src/main/java/com/earth2me/essentials/commands/Commandantioch.java index e5fd889e613..cd79f397c7a 100644 --- a/Essentials/src/main/java/com/earth2me/essentials/commands/Commandantioch.java +++ b/Essentials/src/main/java/com/earth2me/essentials/commands/Commandantioch.java @@ -20,6 +20,6 @@ public void run(final Server server, final User user, final String commandLabel, } final Location loc = LocationUtil.getTarget(user.getBase()); - loc.getWorld().spawn(loc, TNTPrimed.class); + ess.scheduleLocationDelayedTask(loc, () -> loc.getWorld().spawn(loc, TNTPrimed.class)); } } diff --git a/Essentials/src/main/java/com/earth2me/essentials/commands/Commandbalancetop.java b/Essentials/src/main/java/com/earth2me/essentials/commands/Commandbalancetop.java index e11f8a17abf..3c915f1dd7b 100644 --- a/Essentials/src/main/java/com/earth2me/essentials/commands/Commandbalancetop.java +++ b/Essentials/src/main/java/com/earth2me/essentials/commands/Commandbalancetop.java @@ -44,7 +44,7 @@ private void outputCache(final CommandSource sender, final int page) { new TextPager(cache).showPage(Integer.toString(page), null, "balancetop", sender); }; if (sender.getSender() instanceof BlockCommandSender) { - ess.scheduleSyncDelayedTask(runnable); + ess.scheduleGlobalDelayedTask(runnable); } else { runnable.run(); } diff --git a/Essentials/src/main/java/com/earth2me/essentials/commands/Commandbeezooka.java b/Essentials/src/main/java/com/earth2me/essentials/commands/Commandbeezooka.java index 19e156f6643..dd9b67e94d4 100644 --- a/Essentials/src/main/java/com/earth2me/essentials/commands/Commandbeezooka.java +++ b/Essentials/src/main/java/com/earth2me/essentials/commands/Commandbeezooka.java @@ -23,7 +23,7 @@ protected void run(final Server server, final User user, final String commandLab final Entity bee = Mob.BEE.spawn(user.getWorld(), server, user.getBase().getEyeLocation()); bee.setVelocity(user.getBase().getEyeLocation().getDirection().multiply(2)); - ess.scheduleSyncDelayedTask(() -> { + ess.scheduleEntityDelayedTask(bee, () -> { final Location loc = bee.getLocation(); bee.remove(); loc.getWorld().createExplosion(loc, 0F); diff --git a/Essentials/src/main/java/com/earth2me/essentials/commands/Commandgc.java b/Essentials/src/main/java/com/earth2me/essentials/commands/Commandgc.java index b35449507e7..78444d2fbdb 100644 --- a/Essentials/src/main/java/com/earth2me/essentials/commands/Commandgc.java +++ b/Essentials/src/main/java/com/earth2me/essentials/commands/Commandgc.java @@ -3,15 +3,13 @@ import com.earth2me.essentials.CommandSource; import com.earth2me.essentials.utils.DateUtil; import com.earth2me.essentials.utils.NumberUtil; -import net.ess3.provider.TileEntityProvider; +import net.ess3.provider.WorldTileEntityCountProvider; import org.bukkit.ChatColor; -import org.bukkit.Chunk; import org.bukkit.Server; import org.bukkit.World; import java.lang.management.ManagementFactory; import java.util.List; -import java.util.logging.Level; public class Commandgc extends EssentialsCommand { public Commandgc() { @@ -37,7 +35,7 @@ protected void run(final Server server, final CommandSource sender, final String sender.sendTl("gcfree", Runtime.getRuntime().freeMemory() / 1024 / 1024); final List worlds = server.getWorlds(); - final TileEntityProvider tileEntityProvider = ess.provider(TileEntityProvider.class); + final WorldTileEntityCountProvider worldTileEntityCountProvider = ess.provider(WorldTileEntityCountProvider.class); for (final World w : worlds) { String worldType = "World"; switch (w.getEnvironment()) { @@ -49,15 +47,7 @@ protected void run(final Server server, final CommandSource sender, final String break; } - int tileEntities = 0; - - try { - for (final Chunk chunk : w.getLoadedChunks()) { - tileEntities += tileEntityProvider.getTileEntities(chunk).length; - } - } catch (final java.lang.ClassCastException ex) { - ess.getLogger().log(Level.SEVERE, "Corrupted chunk data on world " + w, ex); - } + final int tileEntities = worldTileEntityCountProvider.getTileEntityCount(w); sender.sendTl("gcWorld", worldType, w.getName(), w.getLoadedChunks().length, w.getEntities().size(), tileEntities); } diff --git a/Essentials/src/main/java/com/earth2me/essentials/commands/Commandkill.java b/Essentials/src/main/java/com/earth2me/essentials/commands/Commandkill.java index 80b9983bba2..50e21f0851f 100644 --- a/Essentials/src/main/java/com/earth2me/essentials/commands/Commandkill.java +++ b/Essentials/src/main/java/com/earth2me/essentials/commands/Commandkill.java @@ -32,13 +32,15 @@ protected void updatePlayer(final Server server, final CommandSource sender, fin } final DamageEventProvider provider = ess.provider(DamageEventProvider.class); - final EntityDamageEvent ede = provider.callDamageEvent(matchPlayer, sender.isPlayer() && sender.getPlayer().getName().equals(matchPlayer.getName()) ? EntityDamageEvent.DamageCause.SUICIDE : EntityDamageEvent.DamageCause.CUSTOM, Float.MAX_VALUE); - if (ede.isCancelled() && sender.isPlayer() && !ess.getUser(sender.getPlayer()).isAuthorized("essentials.kill.force")) { - return; - } - ede.getEntity().setLastDamageCause(ede); - matchPlayer.setHealth(0); - sender.sendTl("kill", matchPlayer.getDisplayName()); + ess.scheduleEntityDelayedTask(matchPlayer, () -> { + final EntityDamageEvent ede = provider.callDamageEvent(matchPlayer, sender.isPlayer() && sender.getPlayer().getName().equals(matchPlayer.getName()) ? EntityDamageEvent.DamageCause.SUICIDE : EntityDamageEvent.DamageCause.CUSTOM, Float.MAX_VALUE); + if (ede.isCancelled() && sender.isPlayer() && !ess.getUser(sender.getPlayer()).isAuthorized("essentials.kill.force")) { + return; + } + ede.getEntity().setLastDamageCause(ede); + matchPlayer.setHealth(0); + sender.sendTl("kill", matchPlayer.getDisplayName()); + }); } @Override diff --git a/Essentials/src/main/java/com/earth2me/essentials/commands/Commandkittycannon.java b/Essentials/src/main/java/com/earth2me/essentials/commands/Commandkittycannon.java index 18397f82f37..1e0bb783c44 100644 --- a/Essentials/src/main/java/com/earth2me/essentials/commands/Commandkittycannon.java +++ b/Essentials/src/main/java/com/earth2me/essentials/commands/Commandkittycannon.java @@ -49,7 +49,7 @@ private static Entity spawnCat(final Server server, final User user) throws Mob. @Override protected void run(final Server server, final User user, final String commandLabel, final String[] args) throws Exception { final Entity ocelot = Mob.CAT.getType() == null ? spawnOcelot(server, user) : spawnCat(server, user); - ess.scheduleSyncDelayedTask(() -> { + ess.scheduleEntityDelayedTask(ocelot, () -> { final Location loc = ocelot.getLocation(); ocelot.remove(); loc.getWorld().createExplosion(loc, 0F); diff --git a/Essentials/src/main/java/com/earth2me/essentials/commands/Commandlightning.java b/Essentials/src/main/java/com/earth2me/essentials/commands/Commandlightning.java index 046923c684a..71a670d0e35 100644 --- a/Essentials/src/main/java/com/earth2me/essentials/commands/Commandlightning.java +++ b/Essentials/src/main/java/com/earth2me/essentials/commands/Commandlightning.java @@ -3,6 +3,7 @@ import com.earth2me.essentials.CommandSource; import com.earth2me.essentials.User; import com.google.common.collect.Lists; +import org.bukkit.Location; import org.bukkit.Server; import org.bukkit.entity.LightningStrike; @@ -18,7 +19,8 @@ public Commandlightning() { public void run(final Server server, final CommandSource sender, final String commandLabel, final String[] args) throws Exception { if (args.length == 0 || !sender.isAuthorized("essentials.lightning.others")) { if (sender.isPlayer()) { - sender.getPlayer().getWorld().strikeLightning(sender.getUser().getTargetBlock(600).getLocation()); + final Location target = sender.getUser().getTargetBlock(600).getLocation(); + ess.scheduleLocationDelayedTask(target, () -> target.getWorld().strikeLightning(target)); return; } throw new NotEnoughArgumentsException(); @@ -33,15 +35,17 @@ public void run(final Server server, final CommandSource sender, final String co } final int finalPower = power; loopOnlinePlayersConsumer(server, sender, false, true, args[0], player -> { - sender.sendTl("lightningUse", player.getDisplayName()); - final LightningStrike strike = player.getBase().getWorld().strikeLightningEffect(player.getBase().getLocation()); + ess.scheduleEntityDelayedTask(player.getBase(), () -> { + sender.sendTl("lightningUse", player.getDisplayName()); + final LightningStrike strike = player.getBase().getWorld().strikeLightningEffect(player.getBase().getLocation()); - if (!player.isGodModeEnabled()) { - player.getBase().damage(finalPower, strike); - } - if (ess.getSettings().warnOnSmite()) { - player.sendTl("lightningSmited"); - } + if (!player.isGodModeEnabled()) { + player.getBase().damage(finalPower, strike); + } + if (ess.getSettings().warnOnSmite()) { + player.sendTl("lightningSmited"); + } + }); }); loopOnlinePlayers(server, sender, true, true, args[0], null); } diff --git a/Essentials/src/main/java/com/earth2me/essentials/commands/Commandnuke.java b/Essentials/src/main/java/com/earth2me/essentials/commands/Commandnuke.java index 58ae8da0f71..a2f25d30eb6 100644 --- a/Essentials/src/main/java/com/earth2me/essentials/commands/Commandnuke.java +++ b/Essentials/src/main/java/com/earth2me/essentials/commands/Commandnuke.java @@ -35,17 +35,20 @@ protected void run(final Server server, final CommandSource sender, final String if (user == null) { continue; } - user.sendTl("nuke"); - final Location loc = user.getLocation(); - final World world = loc.getWorld(); - if (world != null) { - for (int x = -10; x <= 10; x += 5) { - for (int z = -10; z <= 10; z += 5) { - final TNTPrimed entity = world.spawn(new Location(world, loc.getBlockX() + x, world.getHighestBlockYAt(loc) + 64, loc.getBlockZ() + z), TNTPrimed.class); - entity.setMetadata(NUKE_META_KEY, new FixedMetadataValue(ess, true)); + + ess.scheduleEntityDelayedTask(user.getBase(), () -> { + user.sendTl("nuke"); + final Location loc = user.getLocation(); + final World world = loc.getWorld(); + if (world != null) { + for (int x = -10; x <= 10; x += 5) { + for (int z = -10; z <= 10; z += 5) { + final TNTPrimed entity = world.spawn(new Location(world, loc.getBlockX() + x, world.getHighestBlockYAt(loc) + 64, loc.getBlockZ() + z), TNTPrimed.class); + entity.setMetadata(NUKE_META_KEY, new FixedMetadataValue(ess, true)); + } } } - } + }); } } diff --git a/Essentials/src/main/java/com/earth2me/essentials/commands/Commandseen.java b/Essentials/src/main/java/com/earth2me/essentials/commands/Commandseen.java index a9a851ad6a3..99348d987a7 100644 --- a/Essentials/src/main/java/com/earth2me/essentials/commands/Commandseen.java +++ b/Essentials/src/main/java/com/earth2me/essentials/commands/Commandseen.java @@ -61,7 +61,7 @@ protected void run(final Server server, final CommandSource sender, final String return; } } - ess.getScheduler().runTaskAsynchronously(ess, new Runnable() { + ess.runTaskAsynchronously(new Runnable() { @Override public void run() { final User userFromBukkit = ess.getUsers().getUser(args[0]); diff --git a/Essentials/src/main/java/com/earth2me/essentials/commands/Commandskull.java b/Essentials/src/main/java/com/earth2me/essentials/commands/Commandskull.java index b279b477c5c..2d7ee39cef6 100644 --- a/Essentials/src/main/java/com/earth2me/essentials/commands/Commandskull.java +++ b/Essentials/src/main/java/com/earth2me/essentials/commands/Commandskull.java @@ -159,7 +159,7 @@ private void editSkull(final User user, final User receive, final ItemStack stac final String shortNameFinal = shortOwnerName; - ess.scheduleSyncDelayedTask(() -> { + ess.scheduleEntityDelayedTask(user.getBase(), () -> { stack.setItemMeta(skullMeta); if (spawn) { Inventories.addItem(receive.getBase(), stack); diff --git a/Essentials/src/main/java/com/earth2me/essentials/commands/Commandspawner.java b/Essentials/src/main/java/com/earth2me/essentials/commands/Commandspawner.java index bf5ef7d98c7..792052f4e48 100644 --- a/Essentials/src/main/java/com/earth2me/essentials/commands/Commandspawner.java +++ b/Essentials/src/main/java/com/earth2me/essentials/commands/Commandspawner.java @@ -15,6 +15,7 @@ import org.bukkit.block.CreatureSpawner; import java.util.Locale; +import java.util.concurrent.atomic.AtomicReference; public class Commandspawner extends EssentialsCommand { @@ -32,10 +33,6 @@ protected void run(final Server server, final User user, final String commandLab final Location target = LocationUtil.getTarget(user.getBase()); - if (target.getBlock().getType() != MOB_SPAWNER) { - throw new TranslatableException("mobSpawnTarget"); - } - final String name = args[0]; int delay = 0; @@ -54,25 +51,36 @@ protected void run(final Server server, final User user, final String commandLab if (args.length > 1 && NumberUtil.isInt(args[1]) && user.isAuthorized("essentials.spawner.delay")) { delay = Integer.parseInt(args[1]); } + final int finalDelay = delay; final Trade charge = new Trade("spawner-" + mob.name.toLowerCase(Locale.ENGLISH), ess); charge.isAffordableFor(user); - try { - final CreatureSpawner spawner = (CreatureSpawner) target.getBlock().getState(); - spawner.setSpawnedType(mob.getType()); - if (delay > 0) { - final SpawnerBlockProvider spawnerBlockProvider = ess.provider(SpawnerBlockProvider.class); - spawnerBlockProvider.setMinSpawnDelay(spawner, 1); - spawnerBlockProvider.setMaxSpawnDelay(spawner, Integer.MAX_VALUE); - spawnerBlockProvider.setMinSpawnDelay(spawner, delay); - spawnerBlockProvider.setMaxSpawnDelay(spawner, delay); + + final AtomicReference spawnError = new AtomicReference<>(); + ess.ensureRegion(target, () -> { + if (target.getBlock().getType() != MOB_SPAWNER) { + spawnError.set(new TranslatableException("mobSpawnTarget")); + return; } - spawner.setDelay(delay); - spawner.update(); - } catch (final Throwable ex) { - throw new TranslatableException(ex, "mobSpawnError"); + try { + final CreatureSpawner spawner = (CreatureSpawner) target.getBlock().getState(); + spawner.setSpawnedType(mob.getType()); + if (finalDelay > 0) { + final SpawnerBlockProvider spawnerBlockProvider = ess.provider(SpawnerBlockProvider.class); + spawnerBlockProvider.setMinSpawnDelay(spawner, 1); + spawnerBlockProvider.setMaxSpawnDelay(spawner, Integer.MAX_VALUE); + spawnerBlockProvider.setMinSpawnDelay(spawner, finalDelay); + spawnerBlockProvider.setMaxSpawnDelay(spawner, finalDelay); + } + spawner.setDelay(finalDelay); + spawner.update(); + } catch (final Throwable ex) { + spawnError.set(new TranslatableException(ex, "mobSpawnError")); + } + }); + if (spawnError.get() != null) { + throw spawnError.get(); } charge.charge(user); user.sendTl("setSpawner", mob.name); - } } diff --git a/Essentials/src/main/java/com/earth2me/essentials/commands/Commandsudo.java b/Essentials/src/main/java/com/earth2me/essentials/commands/Commandsudo.java index 861cf6b302f..7cdb5de3c61 100644 --- a/Essentials/src/main/java/com/earth2me/essentials/commands/Commandsudo.java +++ b/Essentials/src/main/java/com/earth2me/essentials/commands/Commandsudo.java @@ -53,7 +53,7 @@ public void run() { } } - ess.scheduleSyncDelayedTask(new SudoCommandTask()); + ess.scheduleEntityDelayedTask(user.getBase(), new SudoCommandTask()); } } } diff --git a/Essentials/src/main/java/com/earth2me/essentials/commands/Commandthunder.java b/Essentials/src/main/java/com/earth2me/essentials/commands/Commandthunder.java index 09a93328d11..4c746c974de 100644 --- a/Essentials/src/main/java/com/earth2me/essentials/commands/Commandthunder.java +++ b/Essentials/src/main/java/com/earth2me/essentials/commands/Commandthunder.java @@ -24,13 +24,15 @@ public void run(final Server server, final User user, final String commandLabel, final boolean setThunder = args[0].equalsIgnoreCase("true"); if (args.length == 1) { - world.setThundering(setThunder); + ess.scheduleGlobalDelayedTask(() -> world.setThundering(setThunder)); user.sendTl("thunder", CommonPlaceholders.enableDisable(user.getSource(), setThunder)); return; } - world.setThundering(setThunder); - world.setThunderDuration(Integer.parseInt(args[1]) * 20); + ess.scheduleGlobalDelayedTask(() -> { + world.setThundering(setThunder); + world.setThunderDuration(Integer.parseInt(args[1]) * 20); + }); user.sendTl("thunderDuration", CommonPlaceholders.enableDisable(user.getSource(), setThunder), Integer.parseInt(args[1])); } diff --git a/Essentials/src/main/java/com/earth2me/essentials/commands/Commandtime.java b/Essentials/src/main/java/com/earth2me/essentials/commands/Commandtime.java index f4ab3819368..900aeb5d5eb 100644 --- a/Essentials/src/main/java/com/earth2me/essentials/commands/Commandtime.java +++ b/Essentials/src/main/java/com/earth2me/essentials/commands/Commandtime.java @@ -84,28 +84,30 @@ public void run(final Server server, final CommandSource sender, final String co } final StringJoiner joiner = new StringJoiner(", "); + final boolean timeAdd = add; for (final World world : worlds) { - // Capture intended visible time for players with relative ptime before world time changes - final Map ptimePlayers = new HashMap<>(); - for (final Player player : world.getPlayers()) { - if (player.getPlayerTimeOffset() != 0 && player.isPlayerTimeRelative()) { - ptimePlayers.put(player, player.getPlayerTime()); + joiner.add(world.getName()); + ess.scheduleGlobalDelayedTask(() -> { + // Capture intended visible time for players with relative ptime before world time changes + final Map ptimePlayers = new HashMap<>(); + for (final Player player : world.getPlayers()) { + if (player.getPlayerTimeOffset() != 0 && player.isPlayerTimeRelative()) { + ptimePlayers.put(player, player.getPlayerTime()); + } } - } - - long time = world.getTime(); - if (!add) { - time -= time % 24000; - } - world.setTime(time + (add ? 0 : 24000) + timeTick); - // Re-apply ptime offsets so players maintain their intended visible time - final long newWorldTime = world.getTime(); - for (final Map.Entry entry : ptimePlayers.entrySet()) { - entry.getKey().setPlayerTime(entry.getValue() - newWorldTime, true); - } + long time = world.getTime(); + if (!timeAdd) { + time -= time % 24000; + } + world.setTime(time + (timeAdd ? 0 : 24000) + timeTick); - joiner.add(world.getName()); + // Re-apply ptime offsets so players maintain their intended visible time + final long newWorldTime = world.getTime(); + for (final Map.Entry entry : ptimePlayers.entrySet()) { + entry.getKey().setPlayerTime(entry.getValue() - newWorldTime, true); + } + }); } sender.sendTl(add ? "timeWorldAdd" : "timeWorldSet", DescParseTickFormat.formatTicks(timeTick), joiner.toString()); diff --git a/Essentials/src/main/java/com/earth2me/essentials/commands/Commandweather.java b/Essentials/src/main/java/com/earth2me/essentials/commands/Commandweather.java index 242f0305277..fca6f07b09b 100644 --- a/Essentials/src/main/java/com/earth2me/essentials/commands/Commandweather.java +++ b/Essentials/src/main/java/com/earth2me/essentials/commands/Commandweather.java @@ -30,16 +30,18 @@ public void run(final Server server, final User user, final String commandLabel, isStorm = args[0].equalsIgnoreCase("storm"); } - final World world = user.getWorld(); + ess.scheduleGlobalDelayedTask(() -> { + final World world = user.getWorld(); - if (args.length > 1) { + if (args.length > 1) { + world.setStorm(isStorm); + world.setWeatherDuration(Integer.parseInt(args[1]) * 20); + user.sendTl(isStorm ? "weatherStormFor" : "weatherSunFor", world.getName(), args[1]); + return; + } world.setStorm(isStorm); - world.setWeatherDuration(Integer.parseInt(args[1]) * 20); - user.sendTl(isStorm ? "weatherStormFor" : "weatherSunFor", world.getName(), args[1]); - return; - } - world.setStorm(isStorm); - user.sendTl(isStorm ? "weatherStorm" : "weatherSun", world.getName()); + user.sendTl(isStorm ? "weatherStorm" : "weatherSun", world.getName()); + }); } @Override @@ -54,14 +56,16 @@ protected void run(final Server server, final CommandSource sender, final String throw new TranslatableException("weatherInvalidWorld", args[0]); } - if (args.length > 2) { + ess.scheduleLocationDelayedTask(world.getSpawnLocation(), () -> { + if (args.length > 2) { + world.setStorm(isStorm); + world.setWeatherDuration(Integer.parseInt(args[2]) * 20); + sender.sendTl(isStorm ? "weatherStormFor" : "weatherSunFor", world.getName(), args[2]); + return; + } world.setStorm(isStorm); - world.setWeatherDuration(Integer.parseInt(args[2]) * 20); - sender.sendTl(isStorm ? "weatherStormFor" : "weatherSunFor", world.getName(), args[2]); - return; - } - world.setStorm(isStorm); - sender.sendTl(isStorm ? "weatherStorm" : "weatherSun", world.getName()); + sender.sendTl(isStorm ? "weatherStorm" : "weatherSun", world.getName()); + }); } @Override diff --git a/Essentials/src/main/java/com/earth2me/essentials/commands/essentials/NyanCommand.java b/Essentials/src/main/java/com/earth2me/essentials/commands/essentials/NyanCommand.java index 9e42fc503e9..06a2a49cb0c 100644 --- a/Essentials/src/main/java/com/earth2me/essentials/commands/essentials/NyanCommand.java +++ b/Essentials/src/main/java/com/earth2me/essentials/commands/essentials/NyanCommand.java @@ -4,9 +4,9 @@ import com.earth2me.essentials.commands.EssentialsTreeNode; import com.earth2me.essentials.utils.RegistryUtil; import com.google.common.collect.ImmutableMap; +import net.ess3.provider.SchedulingProvider; import org.bukkit.Sound; import org.bukkit.entity.Player; -import org.bukkit.scheduler.BukkitRunnable; import java.util.Collection; import java.util.Map; @@ -14,9 +14,9 @@ public class NyanCommand extends EssentialsTreeNode { private static final Sound NOTE_HARP = RegistryUtil.valueOf(Sound.class, "BLOCK_NOTE_BLOCK_HARP", "BLOCK_NOTE_HARP", "NOTE_PIANO"); - private static final String NYAN_TUNE = "1D#,1E,2F#,,2A#,1E,1D#,1E,2F#,2B,2D#,2E,2D#,2A#,2B,,2F#,,1D#,1E,2F#,2B,2C#,2A#,2B,2C#,2E,2D#,2E,2C#,,2F#,,2G#,,1D,1D#,,1C#,1D,1C#,1B,,1B,,1C#,,1D,,1D,1C#,1B,1C#,1D#,2F#,2G#,1D#,2F#,1C#,1D#,1B,1C#,1B,1D#,,2F#,,2G#,1D#,2F#,1C#,1D#,1B,1D,1D#,1D,1C#,1B,1C#,1D,,1B,1C#,1D#,2F#,1C#,1D,1C#,1B,1C#,,1B,,1C#,,2F#,,2G#,,1D,1D#,,1C#,1D,1C#,1B,,1B,,1C#,,1D,,1D,1C#,1B,1C#,1D#,2F#,2G#,1D#,2F#,1C#,1D#,1B,1C#,1B,1D#,,2F#,,2G#,1D#,2F#,1C#,1D#,1B,1D,1D#,1D,1C#,1B,1C#,1D,,1B,1C#,1D#,2F#,1C#,1D,1C#,1B,1C#,,1B,,1B,,1B,,1F#,1G#,1B,,1F#,1G#,1B,1C#,1D#,1B,1E,1D#,1E,2F#,1B,,1B,,1F#,1G#,1B,1E,1D#,1C#,1B,,,,1F#,1B,,1F#,1G#,1B,,1F#,1G#,1B,1B,1C#,1D#,1B,1F#,1G#,1F#,1B,,1B,1A#,1B,1F#,1G#,1B,1E,1D#,1E,2F#,1B,,1A#,,1B,,1F#,1G#,1B,,1F#,1G#,1B,1C#,1D#,1B,1E,1D#,1E,2F#,1B,,1B,,1F#,1G#,1B,1F#,1E,1D#,1C#,1B,,,,1F#,1B,,1F#,1G#,1B,,1F#,1G#,1B,1B,1C#,1D#,1B,1F#,1G#,1F#,1B,,1B,1A#,1B,1F#,1G#,1B,1E,1D#,1E,2F#,1B,,1A#,,1B,,1F#,1G#,1B,,1F#,1G#,1B,1C#,1D#,1B,1E,1D#,1E,2F#,1B,,1B,,1F#,1G#,1B,1F#,1E,1D#,1C#,1B,,,,1F#,1B,,1F#,1G#,1B,,1F#,1G#,1B,1B,1C#,1D#,1B,1F#,1G#,1F#,1B,,1B,1A#,1B,1F#,1G#,1B,1E,1D#,1E,2F#,1B,,1A#,,1B,,1F#,1G#,1B,,1F#,1G#,1B,1C#,1D#,1B,1E,1D#,1E,2F#,1B,,1B,,1F#,1G#,1B,1F#,1E,1D#,1C#,1B,,,,1F#,1B,,1F#,1G#,1B,,1F#,1G#,1B,1B,1C#,1D#,1B,1F#,1G#,1F#,1B,,1B,1A#,1B,1F#,1G#,1B,1E,1D#,1E,2F#,1B,,1A#,,1B,,1F#,1G#,1B,,1F#,1G#,1B,1C#,1D#,1B,1E,1D#,1E,2F#,1B,,1B,,1F#,1G#,1B,1F#,1E,1D#,1C#,1B,,,,1F#,1B,,1F#,1G#,1B,,1F#,1G#,1B,1B,1C#,1D#,1B,1F#,1G#,1F#,1B,,1B,1A#,1B,1F#,1G#,1B,1E,1D#,1E,2F#,1B,,1B,,"; + private static final String NYAN_TUNE = "1D#,1E,2F#,,2A#,1E,1D#,1E,2F#,2B,2D#,2E,2D#,2A#,2B,,2F#,,1D#,1E,2F#,2B,2C#,2A#,2B,2C#,2E,2D#,2E,2C#,,2F#,,2G#,,1D,1D#,,1C#,1D,1C#,1B,,1B,,1C#,,1D,,1D,1C#,1B,1C#,1D#,2F#,2G#,1D#,2F#,1C#,1D#,1B,1C#,1B,1D#,,2F#,,2G#,1D#,2F#,1C#,1D#,1B,1D,1D#,1D,1C#,1B,1C#,1D,,1B,1C#,1D#,2F#,1C#,1D,1C#,1B,1C#,,1B,,1C#,,2F#,,2G#,,1D,1D#,,1C#,1D,1C#,1B,,1B,,1C#,,1D,,1D,1C#,1B,1C#,1D#,2F#,2G#,1D#,2F#,1C#,1D#,1B,1C#,1B,1D#,,2F#,,2G#,1D#,2F#,1C#,1D#,1B,1D,1D#,1D,1C#,1B,1C#,1D,,1B,1C#,1D#,2F#,1C#,1D,1C#,1B,1C#,,1B,,1B,,1B,,1F#,1G#,1B,,1F#,1G#,1B,1C#,1D#,1B,1E,1D#,1E,2F#,1B,,1B,,1F#,1G#,1B,1E,1D#,1C#,1B,,,,1F#,1B,,1F#,1G#,1B,,1F#,1G#,1B,1B,1C#,1D#,1B,1F#,1G#,1F#,1B,,1B,1A#,1B,1F#,1G#,1B,1E,1D#,1E,2F#,1B,,1A#,,1B,,1F#,1G#,1B,,1F#,1G#,1B,1C#,1D#,1B,1E,1D#,1E,2F#,1B,,1B,,1F#,1G#,1B,1F#,1E,1D#,1C#,1B,,,,1F#,1B,,1F#,1G#,1B,,1F#,1G#,1B,1B,1C#,1D#,1B,1F#,1G#,1F#,1B,,1B,1A#,1B,1F#,1G#,1B,1E,1D#,1E,2F#,1B,,1A#,,1B,,1F#,1G#,1B,,1F#,1G#,1B,1C#,1D#,1B,1E,1D#,1E,2F#,1B,,1B,,1F#,1G#,1B,1F#,1E,1D#,1C#,1B,,,,1F#,1B,,1F#,1G#,1B,,1F#,1G#,1B,1B,1C#,1D#,1B,1F#,1G#,1F#,1B,,1B,1A#,1B,1F#,1G#,1B,1E,1D#,1E,2F#,1B,,1A#,,1B,,1F#,1G#,1B,,1F#,1G#,1B,1C#,1D#,1B,1E,1D#,1E,2F#,1B,,1B,,1F#,1G#,1B,1F#,1E,1D#,1C#,1B,,,,1F#,1B,,1F#,1G#,1B,,1F#,1G#,1B,1B,1C#,1D#,1B,1F#,1G#,1F#,1B,,1B,1A#,1B,1F#,1G#,1B,1E,1D#,1E,2F#,1B,,1B,,"; - private transient TuneRunnable currentTune = null; + private transient SchedulingProvider.EssentialsTask currentTune = null; public NyanCommand() { super(new String[]{"nyan", "nya"}, true); @@ -28,11 +28,15 @@ protected void run(final CommandSource sender, final String commandLabel, final currentTune.cancel(); } - currentTune = new TuneRunnable(NYAN_TUNE, NOTE_HARP, ess::getOnlinePlayers); - currentTune.runTaskTimer(ess, 20, 2); + final TuneTicker ticker = new TuneTicker(ess, NYAN_TUNE, NOTE_HARP, ess::getOnlinePlayers); + currentTune = ess.scheduleGlobalRepeatingTask(() -> { + if (!ticker.tick()) { + currentTune.cancel(); + } + }, 20, 2); } - private static class TuneRunnable extends BukkitRunnable { + private static class TuneTicker { private static final Map noteMap = ImmutableMap.builder() .put("1F#", 0.5f) .put("1G", 0.53f) @@ -60,31 +64,35 @@ private static class TuneRunnable extends BukkitRunnable { .put("2F", 1.88f) .build(); + private final com.earth2me.essentials.IEssentials ess; private final String[] tune; private final Sound sound; private final Supplier> players; private int i = 0; - TuneRunnable(final String tuneStr, final Sound sound, final Supplier> players) { + TuneTicker(final com.earth2me.essentials.IEssentials ess, final String tuneStr, final Sound sound, final Supplier> players) { + this.ess = ess; this.tune = tuneStr.split(","); this.sound = sound; this.players = players; } - @Override - public void run() { + /** + * Plays the next note to every online player and returns whether there are more notes left. + */ + boolean tick() { final String note = tune[i]; i++; - if (i >= tune.length) { - cancel(); - } - if (note == null || note.isEmpty()) { - return; - } + final boolean more = i < tune.length; - for (final Player onlinePlayer : players.get()) { - onlinePlayer.playSound(onlinePlayer.getLocation(), sound, 1, noteMap.get(note)); + if (note != null && !note.isEmpty()) { + final Float pitch = noteMap.get(note); + for (final Player onlinePlayer : players.get()) { + ess.scheduleEntityDelayedTask(onlinePlayer, () -> onlinePlayer.playSound(onlinePlayer.getLocation(), sound, 1, pitch)); + } } + + return more; } } } diff --git a/Essentials/src/main/java/com/earth2me/essentials/config/holders/UserConfigHolder.java b/Essentials/src/main/java/com/earth2me/essentials/config/holders/UserConfigHolder.java index 2e278667390..7e33086ea18 100644 --- a/Essentials/src/main/java/com/earth2me/essentials/config/holders/UserConfigHolder.java +++ b/Essentials/src/main/java/com/earth2me/essentials/config/holders/UserConfigHolder.java @@ -394,7 +394,7 @@ public void lastHeal(final long value) { this.lastheal = value; } - private long mute = 0L; + private volatile long mute = 0L; public long mute() { return this.mute; @@ -404,7 +404,7 @@ public void mute(final long value) { this.mute = value; } - private long jail = 0L; + private volatile long jail = 0L; public long jail() { return this.jail; diff --git a/Essentials/src/main/java/com/earth2me/essentials/economy/EconomyLayers.java b/Essentials/src/main/java/com/earth2me/essentials/economy/EconomyLayers.java index a4184e5588a..e1ebf5d6617 100644 --- a/Essentials/src/main/java/com/earth2me/essentials/economy/EconomyLayers.java +++ b/Essentials/src/main/java/com/earth2me/essentials/economy/EconomyLayers.java @@ -32,7 +32,7 @@ public static void init() { } public static void onEnable(final Essentials ess) { - ess.scheduleSyncDelayedTask(() -> { + ess.scheduleInitTask(() -> { serverStarted = true; for (final Plugin plugin : Bukkit.getPluginManager().getPlugins()) { if (!plugin.isEnabled()) { diff --git a/Essentials/src/main/java/com/earth2me/essentials/utils/ModernPaperEnvironment.java b/Essentials/src/main/java/com/earth2me/essentials/utils/ModernPaperEnvironment.java new file mode 100644 index 00000000000..02f474f4afb --- /dev/null +++ b/Essentials/src/main/java/com/earth2me/essentials/utils/ModernPaperEnvironment.java @@ -0,0 +1,34 @@ +package com.earth2me.essentials.utils; + +import io.papermc.lib.environments.PaperEnvironment; +import io.papermc.lib.features.asyncchunks.AsyncChunksPaper_13; +import io.papermc.lib.features.asyncchunks.AsyncChunksPaper_15; +import io.papermc.lib.features.asyncteleport.AsyncTeleportPaper_13; +import io.papermc.lib.features.bedspawnlocation.BedSpawnLocationPaper; +import org.bukkit.Location; +import org.bukkit.World; +import org.bukkit.entity.HumanEntity; + +public class ModernPaperEnvironment extends PaperEnvironment { + public ModernPaperEnvironment() { + super(); + // PaperLib 1.0.6's own version regex only matches a single-digit MC major version and + // silently zeroes out on 26.x, leaving these handlers on legacy sync fallbacks that + // crash on Folia's region threading. Re-select them using VersionUtil's own (correct, + // 26.x-aware) version parsing instead. + if (VersionUtil.getServerBukkitVersion().isHigherThanOrEqualTo(VersionUtil.v1_13_0_R01)) { + asyncChunksHandler = new AsyncChunksPaper_13(); + asyncTeleportHandler = new AsyncTeleportPaper_13(); + } + if (VersionUtil.getServerBukkitVersion().isHigherThanOrEqualTo(VersionUtil.v1_15_2_R01)) { + try { + World.class.getDeclaredMethod("getChunkAtAsyncUrgently", Location.class); + asyncChunksHandler = new AsyncChunksPaper_15(); + HumanEntity.class.getDeclaredMethod("getPotentialBedLocation"); + bedSpawnLocationHandler = new BedSpawnLocationPaper(); + } catch (NoSuchMethodException ignored) { + // Method doesn't exist on this build; keep the AsyncChunksPaper_13 handler above. + } + } + } +} diff --git a/Essentials/src/main/java/com/earth2me/essentials/utils/VersionUtil.java b/Essentials/src/main/java/com/earth2me/essentials/utils/VersionUtil.java index f8eaa65af67..ccd63bb50b0 100644 --- a/Essentials/src/main/java/com/earth2me/essentials/utils/VersionUtil.java +++ b/Essentials/src/main/java/com/earth2me/essentials/utils/VersionUtil.java @@ -53,6 +53,7 @@ public final class VersionUtil { private static final Set supportedVersions = ImmutableSet.of(v1_8_8_R01, v1_9_4_R01, v1_10_2_R01, v1_11_2_R01, v1_12_2_R01, v1_13_2_R01, v1_14_4_R01, v1_15_2_R01, v1_16_5_R01, v1_17_1_R01, v1_18_2_R01, v1_19_4_R01, v1_20_6_R01, v1_21_11_R01, v26_1_R01, v26_2_R01); public static final boolean PRE_FLATTENING = VersionUtil.getServerBukkitVersion().isLowerThan(VersionUtil.v1_13_0_R01); + public static final boolean FOLIA; public static final List officialPlugins = Arrays.asList( "EssentialsAntiBuild", @@ -78,6 +79,15 @@ public final class VersionUtil { private static final String PFX = make("8(;4>`"); static { + boolean isFolia; + try { + Class.forName("io.papermc.paper.threadedregions.RegionizedServer"); + isFolia = true; + } catch (Throwable ignored) { + isFolia = false; + } + FOLIA = isFolia; + final ImmutableMap.Builder builder = new ImmutableMap.Builder<>(); // Leaf, yet another "High Performance" fork of Paper. Not supported by EssentialsX. diff --git a/Essentials/src/main/resources/messages.properties b/Essentials/src/main/resources/messages.properties index 85f1d20dc36..568aa2a3cfa 100644 --- a/Essentials/src/main/resources/messages.properties +++ b/Essentials/src/main/resources/messages.properties @@ -1531,6 +1531,7 @@ versionErrorPlayer=Error while checking EssentialsX version information versionFetching=Fetching version information... versionOutputVaultMissing=Vault is not installed. Chat and permissions may not work. versionOutputFine={0} version\: {1} +versionOutputFlags=Feature Flags: {0} versionOutputWarn={0} version\: {1} versionOutputUnsupported={0} version\: {1} versionOutputUnsupportedPlugins=You are running unsupported plugins\! diff --git a/Essentials/src/main/resources/plugin.yml b/Essentials/src/main/resources/plugin.yml index 68496ebbbdf..125f0cd15bb 100644 --- a/Essentials/src/main/resources/plugin.yml +++ b/Essentials/src/main/resources/plugin.yml @@ -7,6 +7,7 @@ description: Provides an essential, core set of commands for Bukkit. softdepend: [Vault, LuckPerms] authors: [Zenexer, ementalo, Aelux, Brettflan, KimKandor, snowleo, ceulemans, Xeology, KHobbits, md_5, Iaccidentally, drtshock, vemacs, SupaHam, mdcfe, JRoy, pop4959] api-version: "1.13" +folia-supported: true commands: afk: description: Marks you as away-from-keyboard. diff --git a/EssentialsAntiBuild/src/main/resources/plugin.yml b/EssentialsAntiBuild/src/main/resources/plugin.yml index bbfd21dd709..33fd0595372 100644 --- a/EssentialsAntiBuild/src/main/resources/plugin.yml +++ b/EssentialsAntiBuild/src/main/resources/plugin.yml @@ -7,7 +7,7 @@ description: Provides build protection. authors: [Zenexer, ementalo, Aelux, Brettflan, KimKandor, snowleo, ceulemans, Xeology, KHobbits, Iaccidentally, drtshock, mdcfe] depend: [Essentials] api-version: "1.13" - +folia-supported: true permissions: essentials.build: description: Allows the bearer to build diff --git a/EssentialsChat/src/main/resources/plugin.yml b/EssentialsChat/src/main/resources/plugin.yml index a6b5103403a..13034630aac 100644 --- a/EssentialsChat/src/main/resources/plugin.yml +++ b/EssentialsChat/src/main/resources/plugin.yml @@ -7,6 +7,7 @@ description: Provides chat control features for Essentials. Requires Permission authors: [Zenexer, ementalo, Aelux, Brettflan, KimKandor, snowleo, ceulemans, Xeology, KHobbits, md_5, Okamosy, Iaccidentally, mdcfe, JRoy, triagonal] depend: [Essentials] api-version: 1.13 +folia-supported: true commands: toggleshout: description: Toggles whether you are talking in shout mode diff --git a/EssentialsDiscord/src/main/java/net/essentialsx/api/v2/events/discord/DiscordMessageEvent.java b/EssentialsDiscord/src/main/java/net/essentialsx/api/v2/events/discord/DiscordMessageEvent.java index c9f5f4765b3..72700c6880d 100644 --- a/EssentialsDiscord/src/main/java/net/essentialsx/api/v2/events/discord/DiscordMessageEvent.java +++ b/EssentialsDiscord/src/main/java/net/essentialsx/api/v2/events/discord/DiscordMessageEvent.java @@ -1,6 +1,7 @@ package net.essentialsx.api.v2.events.discord; import net.essentialsx.api.v2.services.discord.MessageType; +import org.bukkit.Bukkit; import org.bukkit.event.Cancellable; import org.bukkit.event.Event; import org.bukkit.event.HandlerList; @@ -10,6 +11,8 @@ /** * Fired before a message is about to be sent to a Discord channel. + *

+ * Note: This event has no guarantee of the thread it is fired on, please use {@link #isAsynchronous()}} to see if this event is off the main Bukkit thread. */ public class DiscordMessageEvent extends Event implements Cancellable { private static final HandlerList handlers = new HandlerList(); @@ -40,6 +43,7 @@ public DiscordMessageEvent(final MessageType type, final String message, final b * @param uuid The UUID of the player which caused this event or null if this wasn't a player triggered event. */ public DiscordMessageEvent(final MessageType type, final String message, final boolean allowGroupMentions, final String avatarUrl, final String name, final UUID uuid) { + super(!Bukkit.isPrimaryThread()); this.type = type; this.message = message; this.allowGroupMentions = allowGroupMentions; diff --git a/EssentialsDiscord/src/main/java/net/essentialsx/discord/EssentialsDiscord.java b/EssentialsDiscord/src/main/java/net/essentialsx/discord/EssentialsDiscord.java index 47c19f411bd..c7676ddd772 100644 --- a/EssentialsDiscord/src/main/java/net/essentialsx/discord/EssentialsDiscord.java +++ b/EssentialsDiscord/src/main/java/net/essentialsx/discord/EssentialsDiscord.java @@ -28,6 +28,11 @@ public class EssentialsDiscord extends JavaPlugin implements IEssentialsModule { @Override public void onEnable() { + if (com.earth2me.essentials.utils.VersionUtil.FOLIA) { + getLogger().warning("Discord integration is not yet supported on Folia; disabling."); + setEnabled(false); + return; + } EssentialsLogger.updatePluginLogger(this); ess = (IEssentials) getServer().getPluginManager().getPlugin("Essentials"); if (ess == null || !ess.isEnabled()) { @@ -60,7 +65,7 @@ public void onEnable() { jda = new JDADiscordService(this); try { jda.startup(); - ess.scheduleSyncDelayedTask(() -> ((InteractionControllerImpl) jda.getInteractionController()).processBatchRegistration()); + ess.scheduleInitTask(() -> ((InteractionControllerImpl) jda.getInteractionController()).processBatchRegistration()); } catch (Exception e) { getLogger().log(Level.SEVERE, ess.getAdventureFacet().miniToLegacy(tlLiteral("discordErrorLogin", e.getMessage()))); if (ess.getSettings().isDebug()) { diff --git a/EssentialsDiscord/src/main/java/net/essentialsx/discord/JDADiscordService.java b/EssentialsDiscord/src/main/java/net/essentialsx/discord/JDADiscordService.java index dc8bdd2aba9..5e5297ec27b 100644 --- a/EssentialsDiscord/src/main/java/net/essentialsx/discord/JDADiscordService.java +++ b/EssentialsDiscord/src/main/java/net/essentialsx/discord/JDADiscordService.java @@ -260,7 +260,7 @@ public void startup() throws LoginException, InterruptedException { logger.log(Level.WARNING, "Error while loading the achievement/advancement listener. You will not receive achievement/advancement notifications on Discord.", e); } - getPlugin().getEss().scheduleSyncDelayedTask(() -> DiscordUtil.dispatchDiscordMessage(JDADiscordService.this, MessageType.DefaultTypes.SERVER_START, getSettings().getStartMessage(), true, null, null, null)); + getPlugin().getEss().scheduleInitTask(() -> DiscordUtil.dispatchDiscordMessage(JDADiscordService.this, MessageType.DefaultTypes.SERVER_START, getSettings().getStartMessage(), true, null, null, null)); Bukkit.getServicesManager().register(DiscordService.class, this, plugin, ServicePriority.Normal); } @@ -289,11 +289,7 @@ public void sendMessage(MessageType type, String message, boolean allowGroupMent logger.warning("Sending message to channel \"" + type.getKey() + "\" which is an unregistered type! If you are a plugin author, you should be registering your MessageType before using them."); } final DiscordMessageEvent event = new DiscordMessageEvent(type, FormatUtil.stripFormat(message), allowGroupMentions); - if (Bukkit.getServer().isPrimaryThread()) { - Bukkit.getPluginManager().callEvent(event); - } else { - Bukkit.getScheduler().runTask(plugin, () -> Bukkit.getPluginManager().callEvent(event)); - } + Bukkit.getPluginManager().callEvent(event); } @Override diff --git a/EssentialsDiscord/src/main/java/net/essentialsx/discord/interactions/InteractionControllerImpl.java b/EssentialsDiscord/src/main/java/net/essentialsx/discord/interactions/InteractionControllerImpl.java index 26581597214..2f85aadb7b5 100644 --- a/EssentialsDiscord/src/main/java/net/essentialsx/discord/interactions/InteractionControllerImpl.java +++ b/EssentialsDiscord/src/main/java/net/essentialsx/discord/interactions/InteractionControllerImpl.java @@ -64,7 +64,8 @@ public void onSlashCommandInteraction(@NotNull SlashCommandInteractionEvent even interactionEvent.replyTl("noAccessCommand"); return; } - jda.getPlugin().getEss().scheduleSyncDelayedTask(() -> command.onCommand(interactionEvent)); + + command.onCommand(interactionEvent); } @Override diff --git a/EssentialsDiscord/src/main/java/net/essentialsx/discord/interactions/commands/ExecuteCommand.java b/EssentialsDiscord/src/main/java/net/essentialsx/discord/interactions/commands/ExecuteCommand.java index 09e1a9fad8e..512b01dc4df 100644 --- a/EssentialsDiscord/src/main/java/net/essentialsx/discord/interactions/commands/ExecuteCommand.java +++ b/EssentialsDiscord/src/main/java/net/essentialsx/discord/interactions/commands/ExecuteCommand.java @@ -22,7 +22,7 @@ public ExecuteCommand(JDADiscordService jda) { public void onCommand(final InteractionEvent event) { final String command = event.getStringArgument("command"); event.replyTl("discordCommandExecuteReply", command); - Bukkit.getScheduler().runTask(jda.getPlugin(), () -> { + jda.getPlugin().getEss().scheduleGlobalDelayedTask(() -> { try { Bukkit.dispatchCommand(new DiscordCommandSender(jda, Bukkit.getConsoleSender(), message -> event.reply(MessageUtil.sanitizeDiscordMarkdown(message))).getSender(), command); } catch (CommandException e) { diff --git a/EssentialsDiscord/src/main/java/net/essentialsx/discord/listeners/DiscordCommandDispatcher.java b/EssentialsDiscord/src/main/java/net/essentialsx/discord/listeners/DiscordCommandDispatcher.java index c735aa7bdc9..440305d917c 100644 --- a/EssentialsDiscord/src/main/java/net/essentialsx/discord/listeners/DiscordCommandDispatcher.java +++ b/EssentialsDiscord/src/main/java/net/essentialsx/discord/listeners/DiscordCommandDispatcher.java @@ -30,7 +30,7 @@ public void onMessageReceived(@NotNull MessageReceivedEvent event) { } final String command = event.getMessage().getContentRaw(); - Bukkit.getScheduler().runTask(jda.getPlugin(), () -> { + jda.getPlugin().getEss().scheduleGlobalDelayedTask(() -> { try { Bukkit.dispatchCommand(new DiscordCommandSender(jda, Bukkit.getConsoleSender(), message -> event.getMessage().reply(message).queue()).getSender(), command); diff --git a/EssentialsDiscord/src/main/java/net/essentialsx/discord/util/ConsoleInjector.java b/EssentialsDiscord/src/main/java/net/essentialsx/discord/util/ConsoleInjector.java index 266fee02937..a03cff09ed6 100644 --- a/EssentialsDiscord/src/main/java/net/essentialsx/discord/util/ConsoleInjector.java +++ b/EssentialsDiscord/src/main/java/net/essentialsx/discord/util/ConsoleInjector.java @@ -4,6 +4,7 @@ import com.google.common.base.Splitter; import net.dv8tion.jda.api.entities.Message; import net.dv8tion.jda.api.utils.TimeFormat; +import net.ess3.provider.SchedulingProvider; import net.essentialsx.discord.EssentialsDiscord; import net.essentialsx.discord.JDADiscordService; import org.apache.logging.log4j.LogManager; @@ -11,7 +12,6 @@ import org.apache.logging.log4j.core.Logger; import org.apache.logging.log4j.core.appender.AbstractAppender; import org.apache.logging.log4j.core.config.plugins.Plugin; -import org.bukkit.Bukkit; import java.time.Instant; import java.util.concurrent.ArrayBlockingQueue; @@ -29,14 +29,14 @@ public class ConsoleInjector extends AbstractAppender { private final JDADiscordService jda; private final BlockingQueue messageQueue = new ArrayBlockingQueue<>(QUEUE_CAPACITY); - private final int taskId; + private final SchedulingProvider.EssentialsTask task; private boolean removed = false; public ConsoleInjector(JDADiscordService jda) { super("EssentialsX-ConsoleInjector", null, null, false); this.jda = jda; ((Logger) LogManager.getRootLogger()).addAppender(this); - taskId = Bukkit.getScheduler().runTaskTimerAsynchronously(jda.getPlugin(), () -> { + task = jda.getPlugin().getEss().runTaskTimerAsynchronously(() -> { final StringBuilder buffer = new StringBuilder(); String curLine; while ((curLine = messageQueue.peek()) != null) { @@ -50,7 +50,7 @@ public ConsoleInjector(JDADiscordService jda) { if (buffer.length() != 0) { sendMessage(buffer.toString()); } - }, 20, 20 * QUEUE_PROCESS_PERIOD_SECONDS).getTaskId(); + }, 20, 20 * QUEUE_PROCESS_PERIOD_SECONDS); } private void sendMessage(String content) { @@ -108,7 +108,7 @@ public void append(LogEvent event) { public void remove() { ((Logger) LogManager.getRootLogger()).removeAppender(this); - Bukkit.getScheduler().cancelTask(taskId); + task.cancel(); messageQueue.clear(); if (jda.getConsoleWebhook() != null && !jda.getConsoleWebhook().isShutdown()) { jda.getConsoleWebhook().close(); diff --git a/EssentialsDiscord/src/main/java/net/essentialsx/discord/util/DiscordCommandSender.java b/EssentialsDiscord/src/main/java/net/essentialsx/discord/util/DiscordCommandSender.java index f8389c9e43b..c65f375c6d6 100644 --- a/EssentialsDiscord/src/main/java/net/essentialsx/discord/util/DiscordCommandSender.java +++ b/EssentialsDiscord/src/main/java/net/essentialsx/discord/util/DiscordCommandSender.java @@ -2,17 +2,16 @@ import com.earth2me.essentials.utils.FormatUtil; import com.earth2me.essentials.utils.VersionUtil; +import net.ess3.provider.SchedulingProvider; import net.ess3.provider.providers.BukkitSenderProvider; import net.ess3.provider.providers.PaperCommandSender; import net.essentialsx.discord.JDADiscordService; -import org.bukkit.Bukkit; import org.bukkit.command.CommandSender; import org.bukkit.command.ConsoleCommandSender; -import org.bukkit.scheduler.BukkitTask; public class DiscordCommandSender { private final CommandSender sender; - private BukkitTask task; + private SchedulingProvider.EssentialsTask task; private String responseBuffer = ""; private long lastTime = System.currentTimeMillis(); @@ -23,7 +22,7 @@ public DiscordCommandSender(JDADiscordService jda, ConsoleCommandSender sender, }; this.sender = getCustomSender(sender, hook); - task = Bukkit.getScheduler().runTaskTimerAsynchronously(jda.getPlugin(), () -> { + task = jda.getPlugin().getEss().runTaskTimerAsynchronously(() -> { if (!responseBuffer.isEmpty() && System.currentTimeMillis() - lastTime >= 1000) { callback.onMessage(responseBuffer); responseBuffer = ""; diff --git a/EssentialsDiscord/src/main/java/net/essentialsx/discord/util/DiscordUtil.java b/EssentialsDiscord/src/main/java/net/essentialsx/discord/util/DiscordUtil.java index 8686e65c4cd..59a054c8ac4 100644 --- a/EssentialsDiscord/src/main/java/net/essentialsx/discord/util/DiscordUtil.java +++ b/EssentialsDiscord/src/main/java/net/essentialsx/discord/util/DiscordUtil.java @@ -245,10 +245,6 @@ public static void dispatchDiscordMessage(final JDADiscordService jda, final Mes return; } - if (Bukkit.getServer().isPrimaryThread()) { - Bukkit.getPluginManager().callEvent(event); - } else { - Bukkit.getScheduler().runTask(jda.getPlugin(), () -> Bukkit.getPluginManager().callEvent(event)); - } + Bukkit.getPluginManager().callEvent(event); } } diff --git a/EssentialsDiscord/src/main/resources/plugin.yml b/EssentialsDiscord/src/main/resources/plugin.yml index ed6cb51d716..de0c5a6c310 100644 --- a/EssentialsDiscord/src/main/resources/plugin.yml +++ b/EssentialsDiscord/src/main/resources/plugin.yml @@ -8,6 +8,7 @@ authors: [mdcfe, JRoy, pop4959, Glare] depend: [Essentials] softdepend: [EssentialsChat, PlaceholderAPI] api-version: 1.13 +folia-supported: false commands: discordbroadcast: description: Broadcasts a message to the specified Discord channel. diff --git a/EssentialsDiscordLink/src/main/java/net/essentialsx/discordlink/AccountLinkManager.java b/EssentialsDiscordLink/src/main/java/net/essentialsx/discordlink/AccountLinkManager.java index 930bdb2ff26..9848cd8899e 100644 --- a/EssentialsDiscordLink/src/main/java/net/essentialsx/discordlink/AccountLinkManager.java +++ b/EssentialsDiscordLink/src/main/java/net/essentialsx/discordlink/AccountLinkManager.java @@ -137,7 +137,7 @@ private void ensureSync(final Runnable runnable) { runnable.run(); return; } - ess.getEss().scheduleSyncDelayedTask(runnable); + ess.getEss().scheduleGlobalDelayedTask(runnable); } private void ensureAsync(final Runnable runnable) { diff --git a/EssentialsDiscordLink/src/main/java/net/essentialsx/discordlink/EssentialsDiscordLink.java b/EssentialsDiscordLink/src/main/java/net/essentialsx/discordlink/EssentialsDiscordLink.java index 9da2bbef893..96e47e5880b 100644 --- a/EssentialsDiscordLink/src/main/java/net/essentialsx/discordlink/EssentialsDiscordLink.java +++ b/EssentialsDiscordLink/src/main/java/net/essentialsx/discordlink/EssentialsDiscordLink.java @@ -37,6 +37,11 @@ public class EssentialsDiscordLink extends JavaPlugin { @Override public void onEnable() { + if (com.earth2me.essentials.utils.VersionUtil.FOLIA) { + getLogger().warning("Discord integration is not yet supported on Folia; disabling."); + setEnabled(false); + return; + } ess = (IEssentials) getServer().getPluginManager().getPlugin("Essentials"); final EssentialsDiscord essDiscord = (EssentialsDiscord) getServer().getPluginManager().getPlugin("EssentialsDiscord"); if (ess == null || !ess.isEnabled() || essDiscord == null || !essDiscord.isEnabled()) { diff --git a/EssentialsDiscordLink/src/main/java/net/essentialsx/discordlink/listeners/LinkBukkitListener.java b/EssentialsDiscordLink/src/main/java/net/essentialsx/discordlink/listeners/LinkBukkitListener.java index 959a30adf33..d4ef844a512 100644 --- a/EssentialsDiscordLink/src/main/java/net/essentialsx/discordlink/listeners/LinkBukkitListener.java +++ b/EssentialsDiscordLink/src/main/java/net/essentialsx/discordlink/listeners/LinkBukkitListener.java @@ -163,7 +163,7 @@ public void onUserLinkStatusChange(final DiscordLinkStatusChangeEvent event) { if (Bukkit.isPrimaryThread()) { kickTask.run(); } else { - ess.getEss().scheduleSyncDelayedTask(kickTask); + ess.getEss().scheduleEntityDelayedTask(event.getUser().getBase(), kickTask); } break; } diff --git a/EssentialsDiscordLink/src/main/java/net/essentialsx/discordlink/rolesync/RoleSyncManager.java b/EssentialsDiscordLink/src/main/java/net/essentialsx/discordlink/rolesync/RoleSyncManager.java index 72353afb735..2600f64af6c 100644 --- a/EssentialsDiscordLink/src/main/java/net/essentialsx/discordlink/rolesync/RoleSyncManager.java +++ b/EssentialsDiscordLink/src/main/java/net/essentialsx/discordlink/rolesync/RoleSyncManager.java @@ -5,12 +5,12 @@ import net.essentialsx.api.v2.events.discordlink.DiscordLinkStatusChangeEvent; import net.essentialsx.api.v2.services.discord.InteractionRole; import net.essentialsx.discordlink.EssentialsDiscordLink; +import net.ess3.provider.SchedulingProvider; import org.bukkit.Bukkit; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; import org.bukkit.event.Listener; import org.bukkit.event.player.PlayerJoinEvent; -import org.bukkit.scheduler.BukkitTask; import java.util.ArrayList; import java.util.Collections; @@ -30,7 +30,7 @@ public class RoleSyncManager implements Listener { private final Map groupToRoleMap = new HashMap<>(); private final Map roleIdToGroupMap = new HashMap<>(); private final Semaphore syncSemaphore = new Semaphore(5); - private BukkitTask syncTask; + private SchedulingProvider.EssentialsTask syncTask; private int syncCursor = 0; public RoleSyncManager(final EssentialsDiscordLink ess) { diff --git a/EssentialsDiscordLink/src/main/resources/plugin.yml b/EssentialsDiscordLink/src/main/resources/plugin.yml index ff92febcbca..b124d16fe56 100644 --- a/EssentialsDiscordLink/src/main/resources/plugin.yml +++ b/EssentialsDiscordLink/src/main/resources/plugin.yml @@ -7,6 +7,7 @@ description: EssentialsX Discord addon which allows you link your Minecraft and authors: [JRoy] depend: [EssentialsDiscord] api-version: 1.13 +folia-supported: false commands: link: description: Generates a code to link your Minecraft account to Discord. diff --git a/EssentialsGeoIP/src/main/resources/plugin.yml b/EssentialsGeoIP/src/main/resources/plugin.yml index 5a675bfc6ad..03cb5e74524 100644 --- a/EssentialsGeoIP/src/main/resources/plugin.yml +++ b/EssentialsGeoIP/src/main/resources/plugin.yml @@ -7,6 +7,8 @@ description: Shows the country or city of a user on login and /whois. authors: [Zenexer, ementalo, Aelux, Brettflan, KimKandor, snowleo, ceulemans, Xeology, kjiang, pop4959] depend: [Essentials] api-version: 1.13 +folia-supported: true + permissions: essentials.geoip.show: description: Shows the country or city of a user on login and /whois. diff --git a/EssentialsProtect/src/main/resources/plugin.yml b/EssentialsProtect/src/main/resources/plugin.yml index 027249cc9d1..a8f1e3196c3 100644 --- a/EssentialsProtect/src/main/resources/plugin.yml +++ b/EssentialsProtect/src/main/resources/plugin.yml @@ -7,6 +7,8 @@ description: Provides protection for various parts of the world. authors: [Zenexer, ementalo, Aelux, Brettflan, KimKandor, snowleo, ceulemans, Xeology, KHobbits, drtshock] softdepend: [Essentials] api-version: 1.13 +folia-supported: true + permissions: essentials.protect.entitytarget.bypass: description: Allows the bearer to be targeted by entities diff --git a/EssentialsSpawn/src/main/java/com/earth2me/essentials/spawn/EssentialsSpawnPlayerListener.java b/EssentialsSpawn/src/main/java/com/earth2me/essentials/spawn/EssentialsSpawnPlayerListener.java index bf6e19492d3..09610da76ff 100644 --- a/EssentialsSpawn/src/main/java/com/earth2me/essentials/spawn/EssentialsSpawnPlayerListener.java +++ b/EssentialsSpawn/src/main/java/com/earth2me/essentials/spawn/EssentialsSpawnPlayerListener.java @@ -88,7 +88,7 @@ private void delayedJoin(final Player player) { final User user = ess.getUser(player); if (ess.getSettings().isUserInSpawnOnJoinGroup(user) && !user.isAuthorized("essentials.spawn-on-join.exempt")) { - ess.scheduleSyncDelayedTask(() -> { + ess.scheduleEntityDelayedTask(player, () -> { final Location spawn = spawns.getSpawn(user.getGroup()); if (spawn == null) { return; @@ -111,10 +111,10 @@ private void delayedJoin(final Player player) { final boolean spawnRandomly = tryRandomTeleport(user, ess.getSettings().getRandomSpawnLocation()); if (!spawnRandomly && !"none".equalsIgnoreCase(ess.getSettings().getNewbieSpawn())) { - ess.scheduleSyncDelayedTask(new NewPlayerTeleport(user), 1L); + ess.scheduleEntityDelayedTask(player, new NewPlayerTeleport(user), 1L); } - ess.scheduleSyncDelayedTask(() -> { + ess.scheduleEntityDelayedTask(player, () -> { if (!user.getBase().isOnline()) { return; } diff --git a/EssentialsSpawn/src/main/resources/plugin.yml b/EssentialsSpawn/src/main/resources/plugin.yml index 389ab58fdec..397811fc38c 100644 --- a/EssentialsSpawn/src/main/resources/plugin.yml +++ b/EssentialsSpawn/src/main/resources/plugin.yml @@ -7,6 +7,7 @@ description: Provides spawn control commands, utilizing Essentials. authors: [Zenexer, ementalo, Aelux, Brettflan, KimKandor, snowleo, ceulemans, Xeology, KHobbits, SupaHam, mdcfe, DoNotSpamPls, JRoy] depend: [Essentials] api-version: 1.13 +folia-supported: true commands: setspawn: description: Sets the spawn point to your current position. diff --git a/EssentialsXMPP/src/main/java/com/earth2me/essentials/xmpp/EssentialsXMPP.java b/EssentialsXMPP/src/main/java/com/earth2me/essentials/xmpp/EssentialsXMPP.java index e9a6216e7b5..ece987501d5 100644 --- a/EssentialsXMPP/src/main/java/com/earth2me/essentials/xmpp/EssentialsXMPP.java +++ b/EssentialsXMPP/src/main/java/com/earth2me/essentials/xmpp/EssentialsXMPP.java @@ -35,6 +35,11 @@ static void updatePresence() { @Override public void onEnable() { + if (com.earth2me.essentials.utils.VersionUtil.FOLIA) { + getLogger().warning("XMPP integration is not yet supported on Folia; disabling."); + setEnabled(false); + return; + } EssentialsLogger.updatePluginLogger(this); instance = this; diff --git a/EssentialsXMPP/src/main/resources/plugin.yml b/EssentialsXMPP/src/main/resources/plugin.yml index e267ef5e042..8dbe24bf9e9 100644 --- a/EssentialsXMPP/src/main/resources/plugin.yml +++ b/EssentialsXMPP/src/main/resources/plugin.yml @@ -7,6 +7,7 @@ description: Provides XMPP communication. authors: [snowleo, SupaHam, mdcfe] depend: [Essentials] api-version: 1.13 +folia-supported: false commands: setxmpp: description: Sets your XMPP address. diff --git a/providers/BaseProviders/src/main/java/net/ess3/provider/SchedulingProvider.java b/providers/BaseProviders/src/main/java/net/ess3/provider/SchedulingProvider.java new file mode 100644 index 00000000000..e5123a93291 --- /dev/null +++ b/providers/BaseProviders/src/main/java/net/ess3/provider/SchedulingProvider.java @@ -0,0 +1,42 @@ +package net.ess3.provider; + +import org.bukkit.Location; +import org.bukkit.entity.Entity; + +public interface SchedulingProvider extends Provider { + void registerInitTask(Runnable runnable); + + boolean isEntityThread(Entity entity); + + boolean isRegionThread(Location location); + + boolean isGlobalThread(); + + void runEntityTask(Entity entity, Runnable runnable); + + EssentialsTask runEntityTask(Entity entity, Runnable runnable, long delay); + + EssentialsTask runEntityTaskRepeating(Entity entity, Runnable runnable, long delay, long period); + + void runLocationalTask(Location location, Runnable runnable); + + void runLocationalTask(Location location, Runnable runnable, long delay); + + EssentialsTask runLocationalTaskRepeating(Location location, Runnable runnable, long delay, long period); + + void runGlobalLocationalTask(Runnable runnable, long delay); + + EssentialsTask runGlobalLocationalTaskRepeating(Runnable runnable, long delay, long period); + + void runAsyncTask(Runnable runnable); + + void runAsyncTaskLater(Runnable runnable, long delay); + + EssentialsTask runAsyncTaskRepeating(Runnable runnable, long delay, long period); + + void cancelAllTasks(); + + interface EssentialsTask { + void cancel(); + } +} diff --git a/providers/BaseProviders/src/main/java/net/ess3/provider/WorldTileEntityCountProvider.java b/providers/BaseProviders/src/main/java/net/ess3/provider/WorldTileEntityCountProvider.java new file mode 100644 index 00000000000..cfb3169e621 --- /dev/null +++ b/providers/BaseProviders/src/main/java/net/ess3/provider/WorldTileEntityCountProvider.java @@ -0,0 +1,7 @@ +package net.ess3.provider; + +import org.bukkit.World; + +public interface WorldTileEntityCountProvider extends Provider { + int getTileEntityCount(final World world); +} diff --git a/providers/BaseProviders/src/main/java/net/ess3/provider/providers/BukkitSchedulingProvider.java b/providers/BaseProviders/src/main/java/net/ess3/provider/providers/BukkitSchedulingProvider.java new file mode 100644 index 00000000000..4a038339ce5 --- /dev/null +++ b/providers/BaseProviders/src/main/java/net/ess3/provider/providers/BukkitSchedulingProvider.java @@ -0,0 +1,109 @@ +package net.ess3.provider.providers; + +import net.ess3.provider.SchedulingProvider; +import net.essentialsx.providers.ProviderData; +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.entity.Entity; +import org.bukkit.plugin.Plugin; +import org.bukkit.scheduler.BukkitTask; + +@ProviderData(description = "Bukkit Scheduling Provider") +public class BukkitSchedulingProvider implements SchedulingProvider { + private final Plugin plugin; + + public BukkitSchedulingProvider(final Plugin plugin) { + this.plugin = plugin; + } + + @Override + public void registerInitTask(Runnable runnable) { + plugin.getServer().getScheduler().scheduleSyncDelayedTask(plugin, runnable); + } + + @Override + public boolean isEntityThread(Entity entity) { + return plugin.getServer().isPrimaryThread(); + } + + @Override + public boolean isRegionThread(Location location) { + return plugin.getServer().isPrimaryThread(); + } + + @Override + public boolean isGlobalThread() { + return Bukkit.isPrimaryThread(); + } + + @Override + public void runEntityTask(Entity entity, Runnable runnable) { + runEntityTask(entity, runnable, 1); + } + + @Override + public EssentialsTask runEntityTask(Entity entity, Runnable runnable, long delay) { + return scheduleSyncTask(runnable, delay); + } + + @Override + public EssentialsTask runEntityTaskRepeating(Entity entity, Runnable runnable, long delay, long period) { + return scheduleSyncTaskRepeating(runnable, delay, period); + } + + @Override + public void runLocationalTask(Location location, Runnable runnable) { + runGlobalLocationalTask(runnable, 1); + } + + @Override + public void runLocationalTask(Location location, Runnable runnable, long delay) { + runGlobalLocationalTask(runnable, delay); + } + + @Override + public EssentialsTask runLocationalTaskRepeating(Location location, Runnable runnable, long delay, long period) { + return scheduleSyncTaskRepeating(runnable, delay, period); + } + + @Override + public void runGlobalLocationalTask(Runnable runnable, long delay) { + scheduleSyncTask(runnable, delay); + } + + @Override + public EssentialsTask runGlobalLocationalTaskRepeating(Runnable runnable, long delay, long period) { + return scheduleSyncTaskRepeating(runnable, delay, period); + } + + private EssentialsTask scheduleSyncTask(Runnable runnable, long delay) { + final int task = plugin.getServer().getScheduler().scheduleSyncDelayedTask(plugin, runnable, delay); + return () -> plugin.getServer().getScheduler().cancelTask(task); + } + + private EssentialsTask scheduleSyncTaskRepeating(Runnable runnable, long delay, long period) { + final BukkitTask task = plugin.getServer().getScheduler().runTaskTimer(plugin, runnable, delay, period); + return task::cancel; + } + + @Override + public void runAsyncTask(Runnable runnable) { + runAsyncTaskLater(runnable, 0); + } + + @Override + public void runAsyncTaskLater(Runnable runnable, long delay) { + plugin.getServer().getScheduler().runTaskLaterAsynchronously(plugin, runnable, delay); + } + + @Override + public EssentialsTask runAsyncTaskRepeating(Runnable runnable, long delay, long period) { + final BukkitTask task = plugin.getServer().getScheduler().runTaskTimerAsynchronously(plugin, runnable, delay, period); + return task::cancel; + } + + @Override + public void cancelAllTasks() { + plugin.getServer().getScheduler().cancelTasks(plugin); + } +} diff --git a/providers/BaseProviders/src/main/java/net/ess3/provider/providers/BukkitWorldTileEntityCountProvider.java b/providers/BaseProviders/src/main/java/net/ess3/provider/providers/BukkitWorldTileEntityCountProvider.java new file mode 100644 index 00000000000..d5d075e0267 --- /dev/null +++ b/providers/BaseProviders/src/main/java/net/ess3/provider/providers/BukkitWorldTileEntityCountProvider.java @@ -0,0 +1,20 @@ +package net.ess3.provider.providers; + +import net.ess3.provider.WorldTileEntityCountProvider; +import net.essentialsx.providers.ProviderData; +import org.bukkit.Chunk; +import org.bukkit.World; + +@ProviderData(description = "Bukkit World Tile Entity Count Provider") +public class BukkitWorldTileEntityCountProvider implements WorldTileEntityCountProvider { + @Override + public int getTileEntityCount(World world) { + int tileEntities = 0; + + for (final Chunk chunk : world.getLoadedChunks()) { + tileEntities += chunk.getTileEntities().length; + } + + return tileEntities; + } +} diff --git a/providers/FoliaProvider/build.gradle b/providers/FoliaProvider/build.gradle new file mode 100644 index 00000000000..fe183290bd8 --- /dev/null +++ b/providers/FoliaProvider/build.gradle @@ -0,0 +1,19 @@ +plugins { + id("essentials.base-conventions") +} + +java { + disableAutoTargetJvm() +} + +dependencies { + implementation(project(':providers:BaseProviders')) { + exclude(module: 'spigot-api') + } + compileOnly 'dev.folia:folia-api:1.20.1-R0.1-SNAPSHOT' +} + +essentials { + injectBukkitApi.set(false) + injectBstats.set(false) +} diff --git a/providers/FoliaProvider/src/main/java/net/ess3/provider/providers/FoliaSchedulingProvider.java b/providers/FoliaProvider/src/main/java/net/ess3/provider/providers/FoliaSchedulingProvider.java new file mode 100644 index 00000000000..86cde8e2ba9 --- /dev/null +++ b/providers/FoliaProvider/src/main/java/net/ess3/provider/providers/FoliaSchedulingProvider.java @@ -0,0 +1,144 @@ +package net.ess3.provider.providers; + +import io.papermc.paper.threadedregions.RegionizedServerInitEvent; +import io.papermc.paper.threadedregions.scheduler.ScheduledTask; +import net.ess3.provider.SchedulingProvider; +import net.essentialsx.providers.ProviderData; +import net.essentialsx.providers.ProviderTest; +import org.bukkit.Location; +import org.bukkit.entity.Entity; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.plugin.Plugin; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.TimeUnit; + +@ProviderData(description = "Folia Scheduling Provider") +public class FoliaSchedulingProvider implements SchedulingProvider, Listener { + private final Plugin plugin; + private List initTasks = new ArrayList<>(); + + public FoliaSchedulingProvider(Plugin plugin) { + this.plugin = plugin; + plugin.getServer().getPluginManager().registerEvents(this, plugin); + } + + @EventHandler + public void onServerInit(RegionizedServerInitEvent event) { + for (final Runnable tasks : initTasks) { + tasks.run(); + } + initTasks = null; + } + + @Override + public void registerInitTask(Runnable runnable) { + if (initTasks == null) { + throw new IllegalStateException(); + } + initTasks.add(runnable); + } + + @Override + public boolean isEntityThread(Entity entity) { + return plugin.getServer().isOwnedByCurrentRegion(entity); + } + + @Override + public boolean isRegionThread(Location location) { + return plugin.getServer().isOwnedByCurrentRegion(location); + } + + @Override + public boolean isGlobalThread() { + return plugin.getServer().isGlobalTickThread(); + } + + @Override + public void runEntityTask(Entity entity, Runnable runnable) { + runEntityTask(entity, runnable, 1); + } + + @Override + public EssentialsTask runEntityTask(Entity entity, Runnable runnable, long delay) { + final ScheduledTask task = entity.getScheduler().runDelayed(plugin, scheduledTask -> runnable.run(), null, delay); + if (task == null) { + throw new IllegalArgumentException("entity is removed!"); + } + return task::cancel; + } + + @Override + public EssentialsTask runEntityTaskRepeating(Entity entity, Runnable runnable, long delay, long period) { + final ScheduledTask task = entity.getScheduler().runAtFixedRate(plugin, scheduledTask -> runnable.run(), null, delay, period); + if (task == null) { + throw new IllegalArgumentException("entity is removed!"); + } + return task::cancel; + } + + @Override + public void runLocationalTask(Location location, Runnable runnable) { + plugin.getServer().getRegionScheduler().execute(plugin, location, runnable); + } + + @Override + public void runLocationalTask(Location location, Runnable runnable, long delay) { + plugin.getServer().getRegionScheduler().runDelayed(plugin, location, scheduledTask -> runnable.run(), delay); + } + + @Override + public EssentialsTask runLocationalTaskRepeating(Location location, Runnable runnable, long delay, long period) { + final ScheduledTask task = plugin.getServer().getRegionScheduler().runAtFixedRate(plugin, location, scheduledTask -> runnable.run(), delay, period);; + return task::cancel; + } + + @Override + public void runGlobalLocationalTask(Runnable runnable, long delay) { + plugin.getServer().getGlobalRegionScheduler().runDelayed(plugin, scheduledTask -> runnable.run(), delay); + } + + @Override + public EssentialsTask runGlobalLocationalTaskRepeating(Runnable runnable, long delay, long period) { + final ScheduledTask task = plugin.getServer().getGlobalRegionScheduler().runAtFixedRate(plugin, scheduledTask -> runnable.run(), delay, period); + return task::cancel; + } + + @Override + public void runAsyncTask(Runnable runnable) { + plugin.getServer().getAsyncScheduler().runNow(plugin, scheduledTask -> runnable.run()); + } + + @Override + public void runAsyncTaskLater(Runnable runnable, long delay) { + plugin.getServer().getAsyncScheduler().runDelayed(plugin, scheduledTask -> runnable.run(), delay * 50L, TimeUnit.MILLISECONDS); + } + + @Override + public EssentialsTask runAsyncTaskRepeating(Runnable runnable, long delay, long period) { + final ScheduledTask task = plugin.getServer().getAsyncScheduler().runAtFixedRate(plugin, scheduledTask -> runnable.run(), delay, period * 50, TimeUnit.MILLISECONDS); + return task::cancel; + } + + @Override + public void cancelAllTasks() { + // Legacy Bukkit scheduler cancellation (getServer().getScheduler().cancelTasks(plugin)) throws + // UnsupportedOperationException on Folia's CraftScheduler; the global/async schedulers below are the + // Folia-safe equivalent. Region-owned entity/location tasks are torn down by RegionShutdownThread's + // own halt sequence during plugin disable, so they don't need a separate plugin-wide cancel here. + plugin.getServer().getGlobalRegionScheduler().cancelTasks(plugin); + plugin.getServer().getAsyncScheduler().cancelTasks(plugin); + } + + @ProviderTest + public static boolean test() { + try { + Class.forName("io.papermc.paper.threadedregions.RegionizedServer"); + return true; + } catch (ClassNotFoundException e) { + return false; + } + } +} diff --git a/providers/PaperProvider/src/main/java/net/ess3/provider/providers/PaperWorldTileEntityCountProvider.java b/providers/PaperProvider/src/main/java/net/ess3/provider/providers/PaperWorldTileEntityCountProvider.java new file mode 100644 index 00000000000..8bb679447e0 --- /dev/null +++ b/providers/PaperProvider/src/main/java/net/ess3/provider/providers/PaperWorldTileEntityCountProvider.java @@ -0,0 +1,13 @@ +package net.ess3.provider.providers; + +import net.ess3.provider.WorldTileEntityCountProvider; +import net.essentialsx.providers.ProviderData; +import org.bukkit.World; + +@ProviderData(description = "Paper 1.11+ World Tile Entity Count Provider", weight = 1) +public class PaperWorldTileEntityCountProvider implements WorldTileEntityCountProvider { + @Override + public int getTileEntityCount(World world) { + return world.getTileEntityCount(); + } +} diff --git a/settings.gradle.kts b/settings.gradle.kts index fc0cf3f4435..0d7239b9f12 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -55,5 +55,6 @@ sequenceOf( include(":providers:BaseProviders") include(":providers:NMSReflectionProvider") include(":providers:PaperProvider") +include(":providers:FoliaProvider") include(":providers:1_8Provider") include(":providers:1_12Provider")