From 15c48d457a60e2f1f1a117945b98c6cc8b7a2200 Mon Sep 17 00:00:00 2001 From: Josh Roy <10731363+JRoy@users.noreply.github.com> Date: Mon, 27 Mar 2023 23:29:42 -0400 Subject: [PATCH 01/45] Add Folia providers --- Essentials/build.gradle | 2 + .../net/ess3/provider/SchedulingProvider.java | 34 ++++++ .../providers/BukkitSchedulingProvider.java | 91 +++++++++++++++ providers/FoliaProvider/build.gradle | 19 +++ .../providers/FoliaSchedulingProvider.java | 110 ++++++++++++++++++ settings.gradle.kts | 2 + 6 files changed, 258 insertions(+) create mode 100644 providers/BaseProviders/src/main/java/net/ess3/provider/SchedulingProvider.java create mode 100644 providers/BaseProviders/src/main/java/net/ess3/provider/providers/BukkitSchedulingProvider.java create mode 100644 providers/FoliaProvider/build.gradle create mode 100644 providers/FoliaProvider/src/main/java/net/ess3/provider/providers/FoliaSchedulingProvider.java diff --git a/Essentials/build.gradle b/Essentials/build.gradle index 2a72af81529..4d5495b163b 100644 --- a/Essentials/build.gradle +++ b/Essentials/build.gradle @@ -18,6 +18,7 @@ dependencies { // Providers api project(':providers:BaseProviders') api project(':providers:PaperProvider') + api project(':providers:FoliaProvider') api(project(':providers:NMSReflectionProvider')) { exclude group: "org.bukkit", module: "bukkit" } @@ -45,6 +46,7 @@ shadowJar { include (dependency('org.checkerframework:checker-qual')) include (project(':providers:BaseProviders')) include (project(':providers:PaperProvider')) + include (project(':providers:FoliaProvider')) include (project(':providers:NMSReflectionProvider')) include (project(':providers:1_8Provider')) include (project(':providers:1_12Provider')) 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..83a5a62cc37 --- /dev/null +++ b/providers/BaseProviders/src/main/java/net/ess3/provider/SchedulingProvider.java @@ -0,0 +1,34 @@ +package net.ess3.provider; + +import org.bukkit.Location; +import org.bukkit.entity.Entity; + +public interface SchedulingProvider extends Provider { + void registerInitTask(Runnable runnable); + + 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); + + interface EssentialsTask { + void cancel(); + } +} 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..887999fea1f --- /dev/null +++ b/providers/BaseProviders/src/main/java/net/ess3/provider/providers/BukkitSchedulingProvider.java @@ -0,0 +1,91 @@ +package net.ess3.provider.providers; + +import net.ess3.provider.SchedulingProvider; +import org.bukkit.Location; +import org.bukkit.entity.Entity; +import org.bukkit.plugin.Plugin; +import org.bukkit.scheduler.BukkitTask; + +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 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 String getDescription() { + return "Bukkit Scheduling Provider"; + } +} diff --git a/providers/FoliaProvider/build.gradle b/providers/FoliaProvider/build.gradle new file mode 100644 index 00000000000..42ae0f072b3 --- /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.19.4-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..bb4697727af --- /dev/null +++ b/providers/FoliaProvider/src/main/java/net/ess3/provider/providers/FoliaSchedulingProvider.java @@ -0,0 +1,110 @@ +package net.ess3.provider.providers; + +import io.papermc.paper.threadedregions.RegionizedServerInitEvent; +import io.papermc.paper.threadedregions.scheduler.ScheduledTask; +import net.ess3.provider.SchedulingProvider; +import org.bukkit.Location; +import org.bukkit.entity.Entity; +import org.bukkit.event.EventHandler; +import org.bukkit.plugin.Plugin; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.TimeUnit; + +public class FoliaSchedulingProvider implements SchedulingProvider { + private final Plugin plugin; + private List initTasks = new ArrayList<>(); + + public FoliaSchedulingProvider(Plugin plugin) { + this.plugin = 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 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, delay * 50, TimeUnit.MICROSECONDS); + return task::cancel; + } + + @Override + public String getDescription() { + return "Folia Scheduling Provider"; + } + +} diff --git a/settings.gradle.kts b/settings.gradle.kts index f133a0cd150..d6ed8b4292b 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -21,6 +21,7 @@ dependencyResolutionManagement { content { includeGroup("net.kyori") } content { includeGroup("org.apache.logging.log4j") } } + mavenLocal() } repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) } @@ -51,5 +52,6 @@ sequenceOf( include(":providers:BaseProviders") include(":providers:NMSReflectionProvider") include(":providers:PaperProvider") +include(":providers:FoliaProvider") include(":providers:1_8Provider") include(":providers:1_12Provider") From 0e4bab24cbc62d77ff35043b553bbc01e3f680bc Mon Sep 17 00:00:00 2001 From: Josh Roy <10731363+JRoy@users.noreply.github.com> Date: Mon, 27 Mar 2023 23:31:39 -0400 Subject: [PATCH 02/45] Get like 70% of the plugin to work --- .../earth2me/essentials/AsyncTeleport.java | 6 +- .../essentials/AsyncTimedTeleport.java | 13 +- .../java/com/earth2me/essentials/Backup.java | 13 +- .../com/earth2me/essentials/Essentials.java | 75 +++- .../essentials/EssentialsBlockListener.java | 2 +- .../essentials/EssentialsEntityListener.java | 2 +- .../essentials/EssentialsPlayerListener.java | 23 +- .../earth2me/essentials/EssentialsTimer.java | 32 +- .../com/earth2me/essentials/IEssentials.java | 35 +- .../java/com/earth2me/essentials/IUser.java | 8 - .../java/com/earth2me/essentials/Jails.java | 22 - .../earth2me/essentials/RandomTeleport.java | 2 +- .../com/earth2me/essentials/Settings.java | 4 +- .../com/earth2me/essentials/Teleport.java | 403 ------------------ .../earth2me/essentials/TimedTeleport.java | 140 ------ .../java/com/earth2me/essentials/User.java | 15 +- .../com/earth2me/essentials/api/IJails.java | 11 - .../commands/Commandbalancetop.java | 2 +- .../essentials/commands/Commandbeezooka.java | 2 +- .../essentials/commands/Commandgc.java | 2 +- .../commands/Commandkittycannon.java | 2 +- .../essentials/commands/Commandseen.java | 2 +- .../essentials/commands/Commandskull.java | 2 +- .../essentials/commands/Commandsudo.java | 2 +- .../essentials/economy/EconomyLayers.java | 2 +- .../textreader/KeywordReplacer.java | 2 +- .../essentials/utils/VersionUtil.java | 10 + Essentials/src/main/resources/plugin.yml | 1 + .../src/main/resources/plugin.yml | 2 +- EssentialsChat/src/main/resources/plugin.yml | 1 + .../discord/EssentialsDiscord.java | 2 +- .../discord/JDADiscordService.java | 2 +- .../InteractionControllerImpl.java | 2 +- .../discord/util/DiscordCommandSender.java | 7 +- .../essentialsx/discord/util/DiscordUtil.java | 6 +- .../src/main/resources/plugin.yml | 1 + .../discordlink/AccountLinkManager.java | 2 +- .../listeners/LinkBukkitListener.java | 2 +- .../src/main/resources/plugin.yml | 1 + EssentialsGeoIP/src/main/resources/plugin.yml | 1 + .../src/main/resources/plugin.yml | 1 + .../spawn/EssentialsSpawnPlayerListener.java | 6 +- EssentialsSpawn/src/main/resources/plugin.yml | 1 + 43 files changed, 155 insertions(+), 717 deletions(-) delete mode 100644 Essentials/src/main/java/com/earth2me/essentials/Teleport.java delete mode 100644 Essentials/src/main/java/com/earth2me/essentials/TimedTeleport.java diff --git a/Essentials/src/main/java/com/earth2me/essentials/AsyncTeleport.java b/Essentials/src/main/java/com/earth2me/essentials/AsyncTeleport.java index 19ab4581815..26567ffaa5a 100644 --- a/Essentials/src/main/java/com/earth2me/essentials/AsyncTeleport.java +++ b/Essentials/src/main/java/com/earth2me/essentials/AsyncTeleport.java @@ -192,8 +192,7 @@ protected void nowAsync(final IUser teleportee, final ITarget target, final Tele 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); + 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. @@ -209,8 +208,7 @@ protected void nowAsync(final IUser teleportee, final ITarget target, final Tele } } 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); + PaperLib.teleportAsync(teleportee.getBase(), loc, cause); } else { if (ess.getSettings().isTeleportToCenterLocation()) { loc = LocationUtil.getRoundedDestination(loc); diff --git a/Essentials/src/main/java/com/earth2me/essentials/AsyncTimedTeleport.java b/Essentials/src/main/java/com/earth2me/essentials/AsyncTimedTeleport.java index e38ee162528..df3dc6932dc 100644 --- a/Essentials/src/main/java/com/earth2me/essentials/AsyncTimedTeleport.java +++ b/Essentials/src/main/java/com/earth2me/essentials/AsyncTimedTeleport.java @@ -2,6 +2,7 @@ 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; @@ -30,7 +31,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) { @@ -54,7 +55,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; @@ -141,16 +142,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(); if (notifyUser) { teleportOwner.sendMessage(tl("pendingTeleportCancelled")); if (timer_teleportee != null && !timer_teleportee.equals(teleportOwner.getBase().getUniqueId())) { @@ -158,7 +159,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 837170df7ca..8d2970cd3b5 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; @@ -18,7 +19,7 @@ public class Backup implements Runnable { private transient final IEssentials ess; private final AtomicBoolean pendingShutdown = new AtomicBoolean(false); private transient boolean running = false; - private transient int taskId = -1; + private transient SchedulingProvider.EssentialsTask task = null; private transient boolean active = false; private transient CompletableFuture taskLock = null; @@ -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 287446cd324..77b24ca1fb8 100644 --- a/Essentials/src/main/java/com/earth2me/essentials/Essentials.java +++ b/Essentials/src/main/java/com/earth2me/essentials/Essentials.java @@ -65,6 +65,7 @@ import net.ess3.provider.PersistentDataProvider; import net.ess3.provider.PotionMetaProvider; import net.ess3.provider.ProviderListener; +import net.ess3.provider.SchedulingProvider; import net.ess3.provider.SerializationProvider; import net.ess3.provider.ServerStateProvider; import net.ess3.provider.SignDataProvider; @@ -77,9 +78,11 @@ import net.ess3.provider.providers.BasePotionDataProvider; 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.FixedHeightWorldInfoProvider; import net.ess3.provider.providers.FlatSpawnEggProvider; +import net.ess3.provider.providers.FoliaSchedulingProvider; import net.ess3.provider.providers.LegacyItemUnbreakableProvider; import net.ess3.provider.providers.LegacyPotionMetaProvider; import net.ess3.provider.providers.LegacySpawnEggProvider; @@ -95,6 +98,7 @@ import net.ess3.provider.providers.PaperServerStateProvider; 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.block.Block; @@ -104,6 +108,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; @@ -120,8 +125,6 @@ import org.bukkit.plugin.ServicePriority; import org.bukkit.plugin.java.JavaPlugin; import org.bukkit.plugin.java.JavaPluginLoader; -import org.bukkit.scheduler.BukkitScheduler; -import org.bukkit.scheduler.BukkitTask; import java.io.File; import java.io.IOException; @@ -182,6 +185,7 @@ public class Essentials extends JavaPlugin implements net.ess3.api.IEssentials { private transient ItemUnbreakableProvider unbreakableProvider; private transient WorldInfoProvider worldInfoProvider; private transient SignDataProvider signDataProvider; + private transient SchedulingProvider schedulingProvider; private transient Kits kits; private transient RandomTeleport randomTeleport; private transient UpdateChecker updateChecker; @@ -358,6 +362,12 @@ public void onEnable() { confList.add(jails); execTimer.mark("Init(Jails)"); + if (VersionUtil.FOLIA) { + schedulingProvider = new FoliaSchedulingProvider(this); + } else { + schedulingProvider = new BukkitSchedulingProvider(this); + } + EconomyLayers.onEnable(this); //Spawner item provider only uses one but it's here for legacy... @@ -464,7 +474,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"); @@ -881,11 +891,6 @@ public void showError(final CommandSource sender, final Throwable exception, fin } } - @Override - public BukkitScheduler getScheduler() { - return this.getServer().getScheduler(); - } - @Override public IJails getJails() { return jails; @@ -1164,33 +1169,63 @@ private int broadcastMessage(final IUser sender, final String permission, final } @Override - public BukkitTask runTaskAsynchronously(final Runnable run) { - return this.getScheduler().runTaskAsynchronously(this, run); + public void scheduleInitTask(Runnable runnable) { + schedulingProvider.registerInitTask(runnable); + } + + @Override + public void runTaskAsynchronously(final Runnable run) { + schedulingProvider.runAsyncTask(run); + } + + @Override + public void runTaskLaterAsynchronously(final Runnable run, final long delay) { + schedulingProvider.runAsyncTaskLater(run, delay); + } + + @Override + public SchedulingProvider.EssentialsTask runTaskTimerAsynchronously(final Runnable run, final long delay, final long period) { + return schedulingProvider.runAsyncTaskRepeating(run, delay, period); + } + + @Override + public void scheduleEntityDelayedTask(Entity entity, Runnable run) { + schedulingProvider.runEntityTask(entity, run); + } + + @Override + public SchedulingProvider.EssentialsTask scheduleEntityDelayedTask(Entity entity, Runnable run, long delay) { + return schedulingProvider.runEntityTask(entity, run, delay); + } + + @Override + public SchedulingProvider.EssentialsTask scheduleEntityRepeatingTask(Entity entity, Runnable run, long delay, long period) { + return schedulingProvider.runEntityTaskRepeating(entity, run, delay, period); } @Override - public BukkitTask runTaskLaterAsynchronously(final Runnable run, final long delay) { - return this.getScheduler().runTaskLaterAsynchronously(this, run, delay); + public void scheduleLocationDelayedTask(Location location, Runnable run) { + schedulingProvider.runLocationalTask(location, run); } @Override - public BukkitTask runTaskTimerAsynchronously(final Runnable run, final long delay, final long period) { - return this.getScheduler().runTaskTimerAsynchronously(this, run, delay, period); + public void scheduleLocationDelayedTask(Location location, Runnable run, long delay) { + schedulingProvider.runLocationalTask(location, run, delay); } @Override - public int scheduleSyncDelayedTask(final Runnable run) { - return this.getScheduler().scheduleSyncDelayedTask(this, run); + public SchedulingProvider.EssentialsTask scheduleLocationRepeatingTask(Location location, Runnable run, long delay, long period) { + return schedulingProvider.runLocationalTaskRepeating(location, run, delay, period); } @Override - public int scheduleSyncDelayedTask(final Runnable run, final long delay) { - return this.getScheduler().scheduleSyncDelayedTask(this, run, delay); + public void scheduleGlobalDelayedTask(Runnable run, long delay) { + schedulingProvider.runGlobalLocationalTask(run, delay); } @Override - public int scheduleSyncRepeatingTask(final Runnable run, final long delay, final long period) { - return this.getScheduler().scheduleSyncRepeatingTask(this, run, delay, period); + public SchedulingProvider.EssentialsTask scheduleGlobalRepeatingTask(Runnable run, long delay, long period) { + return schedulingProvider.runGlobalLocationalTaskRepeating(run, delay, period); } @Override diff --git a/Essentials/src/main/java/com/earth2me/essentials/EssentialsBlockListener.java b/Essentials/src/main/java/com/earth2me/essentials/EssentialsBlockListener.java index c0a861ce737..aa44551174b 100644 --- a/Essentials/src/main/java/com/earth2me/essentials/EssentialsBlockListener.java +++ b/Essentials/src/main/java/com/earth2me/essentials/EssentialsBlockListener.java @@ -42,7 +42,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 a1ab286b984..26ca4433516 100644 --- a/Essentials/src/main/java/com/earth2me/essentials/EssentialsEntityListener.java +++ b/Essentials/src/main/java/com/earth2me/essentials/EssentialsEntityListener.java @@ -112,7 +112,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 c2dff750f45..d18f0d6c91d 100644 --- a/Essentials/src/main/java/com/earth2me/essentials/EssentialsPlayerListener.java +++ b/Essentials/src/main/java/com/earth2me/essentials/EssentialsPlayerListener.java @@ -15,6 +15,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.providers.BukkitCommandSendListenerProvider; import net.ess3.provider.providers.PaperCommandSendListenerProvider; import net.essentialsx.api.v2.events.AsyncUserDataLoadEvent; @@ -79,7 +80,7 @@ public class EssentialsPlayerListener implements Listener, FakeAccessor { 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; @@ -245,9 +246,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"))) { @@ -401,7 +402,7 @@ public void run() { final int motdDelay = ess.getSettings().getMotdDelay() / 50; final DelayMotdTask motdTask = new DelayMotdTask(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(); } @@ -493,7 +494,7 @@ public void run() { } } - ess.scheduleSyncDelayedTask(new DelayJoinTask()); + ess.scheduleEntityDelayedTask(player, new DelayJoinTask()); } // Makes the compass item ingame always point to the first essentials home. #EasterEgg @@ -579,7 +580,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); } } @@ -824,7 +825,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); @@ -855,7 +856,7 @@ public void run() { } } - ess.scheduleSyncDelayedTask(new PowerToolUseTask()); + ess.scheduleEntityDelayedTask(user.getBase(), new PowerToolUseTask()); } } @@ -916,7 +917,7 @@ public void onInventoryClickEvent(final InventoryClickEvent event) { } if (refreshPlayer != null) { - ess.scheduleSyncDelayedTask(refreshPlayer::updateInventory, 1); + ess.scheduleEntityDelayedTask(refreshPlayer, refreshPlayer::updateInventory, 1); } } @@ -958,7 +959,7 @@ public void onInventoryCloseEvent(final InventoryCloseEvent event) { } if (refreshPlayer != null) { - ess.scheduleSyncDelayedTask(refreshPlayer::updateInventory, 1); + ess.scheduleEntityDelayedTask(refreshPlayer, refreshPlayer::updateInventory, 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..edab1924bea 100644 --- a/Essentials/src/main/java/com/earth2me/essentials/EssentialsTimer.java +++ b/Essentials/src/main/java/com/earth2me/essentials/EssentialsTimer.java @@ -5,7 +5,6 @@ import java.util.HashSet; import java.util.Iterator; -import java.util.LinkedList; import java.util.Set; import java.util.UUID; import java.util.logging.Level; @@ -13,36 +12,19 @@ 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; - @SuppressWarnings("FieldCanBeLocal") - private final long tickInterval = 50; - private transient long lastPoll = System.nanoTime(); + private static final long maxTime = 10 * 1000000; private int skip1 = 0; private int skip2 = 0; EssentialsTimer(final IEssentials ess) { this.ess = ess; - history.add(20d); } @Override 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; + int count = 0; onlineUsers.clear(); for (final Player player : ess.getOnlinePlayers()) { @@ -99,14 +81,4 @@ public void run() { user.resetInvulnerabilityAfterTeleport(); } } - - public double getAverageTPS() { - double avg = 0; - for (final Double f : history) { - if (f != null) { - avg += f; - } - } - return avg / history.size(); - } } diff --git a/Essentials/src/main/java/com/earth2me/essentials/IEssentials.java b/Essentials/src/main/java/com/earth2me/essentials/IEssentials.java index d8916a958b6..59334158390 100644 --- a/Essentials/src/main/java/com/earth2me/essentials/IEssentials.java +++ b/Essentials/src/main/java/com/earth2me/essentials/IEssentials.java @@ -15,6 +15,7 @@ import net.ess3.provider.KnownCommandsProvider; import net.ess3.provider.MaterialTagProvider; import net.ess3.provider.PersistentDataProvider; +import net.ess3.provider.SchedulingProvider; import net.ess3.provider.SerializationProvider; import net.ess3.provider.ServerStateProvider; import net.ess3.provider.SignDataProvider; @@ -24,15 +25,15 @@ import net.ess3.provider.WorldInfoProvider; 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; @@ -82,8 +83,6 @@ public interface IEssentials extends Plugin { ISettings getSettings(); - BukkitScheduler getScheduler(); - IJails getJails(); IWarps getWarps(); @@ -98,17 +97,33 @@ 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); - BukkitTask runTaskLaterAsynchronously(Runnable run, long delay); + SchedulingProvider.EssentialsTask scheduleLocationRepeatingTask(Location location, Runnable run, long delay, long period); - BukkitTask runTaskTimerAsynchronously(Runnable run, long delay, long period); + default void scheduleGlobalDelayedTask(Runnable run) { + scheduleGlobalDelayedTask(run, 1); + } - int scheduleSyncDelayedTask(Runnable run); + void scheduleGlobalDelayedTask(Runnable run, long delay); - int scheduleSyncDelayedTask(Runnable run, long delay); + SchedulingProvider.EssentialsTask scheduleGlobalRepeatingTask(Runnable run, long delay, long period); - int scheduleSyncRepeatingTask(Runnable run, long delay, long period); + void scheduleInitTask(Runnable runnable); PermissionsHandler getPermissionsHandler(); diff --git a/Essentials/src/main/java/com/earth2me/essentials/IUser.java b/Essentials/src/main/java/com/earth2me/essentials/IUser.java index 2625b1fc8c2..46094dd064d 100644 --- a/Essentials/src/main/java/com/earth2me/essentials/IUser.java +++ b/Essentials/src/main/java/com/earth2me/essentials/IUser.java @@ -3,7 +3,6 @@ import com.earth2me.essentials.api.IAsyncTeleport; import com.earth2me.essentials.commands.IEssentialsCommand; import com.earth2me.essentials.config.entities.CommandCooldown; -import net.ess3.api.ITeleport; import net.ess3.api.MaxMoneyException; import net.ess3.api.events.AfkStatusChangeEvent; import net.essentialsx.api.v2.services.mail.MailMessage; @@ -15,7 +14,6 @@ import org.checkerframework.checker.nullness.qual.Nullable; import java.math.BigDecimal; - import java.util.ArrayList; import java.util.Date; import java.util.List; @@ -71,12 +69,6 @@ public interface IUser { @Deprecated boolean hasOutstandingTeleportRequest(); - /** - * @deprecated This API is not asynchronous. Use {@link com.earth2me.essentials.api.IAsyncTeleport IAsyncTeleport} with {@link IUser#getAsyncTeleport()} - */ - @Deprecated - ITeleport getTeleport(); - IAsyncTeleport getAsyncTeleport(); BigDecimal getMoney(); diff --git a/Essentials/src/main/java/com/earth2me/essentials/Jails.java b/Essentials/src/main/java/com/earth2me/essentials/Jails.java index 123d159f3ac..677389349f8 100644 --- a/Essentials/src/main/java/com/earth2me/essentials/Jails.java +++ b/Essentials/src/main/java/com/earth2me/essentials/Jails.java @@ -140,28 +140,6 @@ public void removeJail(String jail) throws Exception { } } - /** - * @deprecated This method does not use asynchronous teleportation. Use {@link Jails#sendToJail(IUser, String, CompletableFuture)} - */ - @SuppressWarnings("deprecation") - @Override - @Deprecated - public void sendToJail(final IUser user, String jail) throws Exception { - if (jail == null || jail.isEmpty()) { - return; - } - - jail = jail.toLowerCase(Locale.ENGLISH); - synchronized (jails) { - if (jails.containsKey(jail)) { - if (user.getBase().isOnline()) { - user.getTeleport().now(getJail(jail), false, TeleportCause.COMMAND); - } - user.setJail(jail); - } - } - } - @Override public void sendToJail(final IUser user, final String jailName, final CompletableFuture future) throws Exception { if (jailName == null || jailName.isEmpty()) { diff --git a/Essentials/src/main/java/com/earth2me/essentials/RandomTeleport.java b/Essentials/src/main/java/com/earth2me/essentials/RandomTeleport.java index f020a2cca17..f58bbef4b99 100644 --- a/Essentials/src/main/java/com/earth2me/essentials/RandomTeleport.java +++ b/Essentials/src/main/java/com/earth2me/essentials/RandomTeleport.java @@ -124,7 +124,7 @@ public CompletableFuture getRandomLocation(final Location center, fina // Prompts caching random valid locations, up to a maximum number of attempts public void cacheRandomLocations(final Location center, final double minRange, final double maxRange) { - ess.getServer().getScheduler().scheduleSyncDelayedTask(ess, () -> { + ess.scheduleLocationDelayedTask(center, () -> { for (int i = 0; i < this.getFindAttempts(); ++i) { calculateRandomLocation(center, minRange, maxRange).thenAccept(location -> { if (isValidRandomLocation(location)) { diff --git a/Essentials/src/main/java/com/earth2me/essentials/Settings.java b/Essentials/src/main/java/com/earth2me/essentials/Settings.java index c0b1c618245..f0757c79b6d 100644 --- a/Essentials/src/main/java/com/earth2me/essentials/Settings.java +++ b/Essentials/src/main/java/com/earth2me/essentials/Settings.java @@ -708,7 +708,7 @@ public void reloadConfig() { // This is 2 because Settings are reloaded twice in the startup lifecycle if (reloadCount.get() < 2) { - ess.scheduleSyncDelayedTask(() -> _addAlternativeCommand(effectiveAlias, toDisable)); + ess.scheduleGlobalDelayedTask(() -> _addAlternativeCommand(effectiveAlias, toDisable)); } else { _addAlternativeCommand(effectiveAlias, toDisable); } @@ -721,7 +721,7 @@ public void reloadConfig() { ess.getLogger().log(Level.INFO, "Syncing commands"); } if (reloadCount.get() < 2) { - ess.scheduleSyncDelayedTask(() -> ess.getSyncCommandsProvider().syncCommands()); + ess.scheduleGlobalDelayedTask(() -> ess.getSyncCommandsProvider().syncCommands()); } else { ess.getSyncCommandsProvider().syncCommands(); } diff --git a/Essentials/src/main/java/com/earth2me/essentials/Teleport.java b/Essentials/src/main/java/com/earth2me/essentials/Teleport.java deleted file mode 100644 index 6038b2e3578..00000000000 --- a/Essentials/src/main/java/com/earth2me/essentials/Teleport.java +++ /dev/null @@ -1,403 +0,0 @@ -package com.earth2me.essentials; - -import com.earth2me.essentials.utils.DateUtil; -import com.earth2me.essentials.utils.LocationUtil; -import io.papermc.lib.PaperLib; -import net.ess3.api.IEssentials; -import net.ess3.api.ITeleport; -import net.ess3.api.IUser; -import net.ess3.api.events.UserWarpEvent; -import net.ess3.api.events.teleport.PreTeleportEvent; -import net.ess3.api.events.teleport.TeleportWarmupEvent; -import org.bukkit.Bukkit; -import org.bukkit.Location; -import org.bukkit.entity.Player; -import org.bukkit.event.player.PlayerRespawnEvent; -import org.bukkit.event.player.PlayerTeleportEvent.TeleportCause; - -import java.math.BigDecimal; -import java.util.Calendar; -import java.util.GregorianCalendar; - -import static com.earth2me.essentials.I18n.tl; - -/** - * @deprecated This API is not asynchronous. Use {@link com.earth2me.essentials.AsyncTeleport AsyncTeleport} - */ -@Deprecated -public class Teleport implements ITeleport { - private final IUser teleportOwner; - private final IEssentials ess; - private TimedTeleport timedTeleport; - - private TeleportType tpType; - - @Deprecated - public Teleport(final IUser user, final IEssentials ess) { - this.teleportOwner = user; - this.ess = ess; - tpType = TeleportType.NORMAL; - } - - @Deprecated - public void cooldown(final boolean check) throws Exception { - final Calendar time = new GregorianCalendar(); - if (teleportOwner.getLastTeleportTimestamp() > 0) { - // Take the current time, and remove the delay from it. - final double cooldown = ess.getSettings().getTeleportCooldown(); - final Calendar earliestTime = new GregorianCalendar(); - earliestTime.add(Calendar.SECOND, -(int) cooldown); - earliestTime.add(Calendar.MILLISECOND, -(int) ((cooldown * 1000.0) % 1000.0)); - // This value contains the most recent time a teleportPlayer could have been used that would allow another use. - final long earliestLong = earliestTime.getTimeInMillis(); - - // When was the last teleportPlayer used? - final long lastTime = teleportOwner.getLastTeleportTimestamp(); - - if (lastTime > time.getTimeInMillis()) { - // This is to make sure time didn't get messed up on last teleportPlayer use. - // If this happens, let's give the user the benifit of the doubt. - teleportOwner.setLastTeleportTimestamp(time.getTimeInMillis()); - return; - } else if (lastTime > earliestLong - && cooldownApplies()) { - time.setTimeInMillis(lastTime); - time.add(Calendar.SECOND, (int) cooldown); - time.add(Calendar.MILLISECOND, (int) ((cooldown * 1000.0) % 1000.0)); - throw new Exception(tl("timeBeforeTeleport", DateUtil.formatDateDiff(time.getTimeInMillis()))); - } - } - // if justCheck is set, don't update lastTeleport; we're just checking - if (!check) { - teleportOwner.setLastTeleportTimestamp(time.getTimeInMillis()); - } - } - - @Deprecated - private boolean cooldownApplies() { - boolean applies = true; - final String globalBypassPerm = "essentials.teleport.cooldown.bypass"; - switch (tpType) { - case NORMAL: - applies = !teleportOwner.isAuthorized(globalBypassPerm); - break; - case BACK: - applies = !(teleportOwner.isAuthorized(globalBypassPerm) && - teleportOwner.isAuthorized("essentials.teleport.cooldown.bypass.back")); - break; - case TPA: - applies = !(teleportOwner.isAuthorized(globalBypassPerm) && - teleportOwner.isAuthorized("essentials.teleport.cooldown.bypass.tpa")); - break; - } - return applies; - } - - @Deprecated - private void warnUser(final IUser user, final double delay) { - final Calendar c = new GregorianCalendar(); - c.add(Calendar.SECOND, (int) delay); - c.add(Calendar.MILLISECOND, (int) ((delay * 1000.0) % 1000.0)); - user.sendMessage(tl("dontMoveMessage", DateUtil.formatDateDiff(c.getTimeInMillis()))); - } - - //The now function is used when you want to skip tp delay when teleporting someone to a location or player. - @Override - @Deprecated - public void now(final Location loc, final boolean cooldown, final TeleportCause cause) throws Exception { - if (cooldown) { - cooldown(false); - } - final ITarget target = new LocationTarget(loc); - now(teleportOwner, target, cause); - } - - @Override - @Deprecated - public void now(final Player entity, final boolean cooldown, final TeleportCause cause) throws Exception { - if (cooldown) { - cooldown(false); - } - final ITarget target = new PlayerTarget(entity); - now(teleportOwner, target, cause); - teleportOwner.sendMessage(tl("teleporting", target.getLocation().getWorld().getName(), target.getLocation().getBlockX(), target.getLocation().getBlockY(), target.getLocation().getBlockZ())); - } - - @Deprecated - protected void now(final IUser teleportee, final ITarget target, final TeleportCause cause) throws Exception { - cancel(false); - Location loc = target.getLocation(); - - final PreTeleportEvent event = new PreTeleportEvent(teleportee, cause, target); - Bukkit.getServer().getPluginManager().callEvent(event); - if (event.isCancelled()) { - return; - } - - if (teleportee.isAuthorized("essentials.back.onteleport")) { - teleportee.setLastLocation(); - } - - if (!teleportee.getBase().isEmpty()) { - if (!ess.getSettings().isTeleportPassengerDismount()) { - throw new Exception(tl("passengerTeleportFail")); - } - teleportee.getBase().eject(); - } - - if (LocationUtil.isBlockUnsafeForUser(ess, teleportee, loc.getWorld(), loc.getBlockX(), loc.getBlockY(), loc.getBlockZ())) { - if (ess.getSettings().isTeleportSafetyEnabled()) { - if (ess.getSettings().isForceDisableTeleportSafety()) { - PaperLib.teleportAsync(teleportee.getBase(), loc, cause); - } else { - PaperLib.teleportAsync(teleportee.getBase(), LocationUtil.getSafeDestination(ess, teleportee, loc), cause); - } - } else { - throw new Exception(tl("unsafeTeleportDestination", loc.getWorld().getName(), loc.getBlockX(), loc.getBlockY(), loc.getBlockZ())); - } - } else { - if (ess.getSettings().isForceDisableTeleportSafety()) { - PaperLib.teleportAsync(teleportee.getBase(), loc, cause); - } else { - if (ess.getSettings().isTeleportToCenterLocation()) { - loc = LocationUtil.getRoundedDestination(loc); - } - PaperLib.teleportAsync(teleportee.getBase(), loc, cause); - } - } - } - - //The teleportPlayer function is used when you want to normally teleportPlayer someone to a location or player. - //This method is nolonger used internally and will be removed. - @Deprecated - @Override - public void teleport(final Location loc, final Trade chargeFor) throws Exception { - teleport(loc, chargeFor, TeleportCause.PLUGIN); - } - - @Override - @Deprecated - public void teleport(final Location loc, final Trade chargeFor, final TeleportCause cause) throws Exception { - teleport(teleportOwner, new LocationTarget(loc), chargeFor, cause); - } - - //This is used when teleporting to a player - @Override - @Deprecated - public void teleport(final Player entity, final Trade chargeFor, final TeleportCause cause) throws Exception { - final ITarget target = new PlayerTarget(entity); - teleportOwner.sendMessage(tl("teleportToPlayer", entity.getDisplayName())); - teleport(teleportOwner, target, chargeFor, cause); - } - - //This is used when teleporting to stored location - @Override - @Deprecated - public void teleportPlayer(final IUser teleportee, final Location loc, final Trade chargeFor, final TeleportCause cause) throws Exception { - teleport(teleportee, new LocationTarget(loc), chargeFor, cause); - } - - //This is used on /tphere - @Override - @Deprecated - public void teleportPlayer(final IUser teleportee, final Player entity, final Trade chargeFor, final TeleportCause cause) throws Exception { - final ITarget target = new PlayerTarget(entity); - teleport(teleportee, target, chargeFor, cause); - teleportee.sendMessage(tl("teleporting", target.getLocation().getWorld().getName(), target.getLocation().getBlockX(), target.getLocation().getBlockY(), target.getLocation().getBlockZ())); - teleportOwner.sendMessage(tl("teleporting", target.getLocation().getWorld().getName(), target.getLocation().getBlockX(), target.getLocation().getBlockY(), target.getLocation().getBlockZ())); - } - - @Deprecated - private void teleport(final IUser teleportee, final ITarget target, final Trade chargeFor, final TeleportCause cause) throws Exception { - double delay = ess.getSettings().getTeleportDelay(); - - final TeleportWarmupEvent event = new TeleportWarmupEvent(teleportee, cause, target, delay); - Bukkit.getServer().getPluginManager().callEvent(event); - if (event.isCancelled()) { - return; - } - delay = event.getDelay(); - - Trade cashCharge = chargeFor; - - if (chargeFor != null) { - chargeFor.isAffordableFor(teleportOwner); - - //This code is to make sure that commandcosts are checked in the initial world, and not in the resulting world. - if (!chargeFor.getCommandCost(teleportOwner).equals(BigDecimal.ZERO)) { - //By converting a command cost to a regular cost, the command cost permission isn't checked when executing the charge after teleport. - cashCharge = new Trade(chargeFor.getCommandCost(teleportOwner), ess); - } - } - - cooldown(true); - if (delay <= 0 || teleportOwner.isAuthorized("essentials.teleport.timer.bypass") || teleportee.isAuthorized("essentials.teleport.timer.bypass")) { - cooldown(false); - now(teleportee, target, cause); - if (cashCharge != null) { - cashCharge.charge(teleportOwner); - } - return; - } - - cancel(false); - warnUser(teleportee, delay); - initTimer((long) (delay * 1000.0), teleportee, target, cashCharge, cause, false); - } - - @Deprecated - private void teleportOther(final IUser teleporter, final IUser teleportee, final ITarget target, final Trade chargeFor, final TeleportCause cause) throws Exception { - double delay = ess.getSettings().getTeleportDelay(); - - final TeleportWarmupEvent event = new TeleportWarmupEvent(teleporter, teleportee, cause, target, delay); - Bukkit.getServer().getPluginManager().callEvent(event); - if (event.isCancelled()) { - return; - } - delay = event.getDelay(); - - Trade cashCharge = chargeFor; - - if (teleporter != null && chargeFor != null) { - chargeFor.isAffordableFor(teleporter); - - //This code is to make sure that commandcosts are checked in the initial world, and not in the resulting world. - if (!chargeFor.getCommandCost(teleporter).equals(BigDecimal.ZERO)) { - //By converting a command cost to a regular cost, the command cost permission isn't checked when executing the charge after teleport. - cashCharge = new Trade(chargeFor.getCommandCost(teleporter), ess); - } - } - - cooldown(true); - if (delay <= 0 || teleporter == null - || teleporter.isAuthorized("essentials.teleport.timer.bypass") - || teleportOwner.isAuthorized("essentials.teleport.timer.bypass") - || teleportee.isAuthorized("essentials.teleport.timer.bypass")) { - cooldown(false); - now(teleportee, target, cause); - if (teleporter != null && cashCharge != null) { - cashCharge.charge(teleporter); - } - return; - } - - cancel(false); - warnUser(teleportee, delay); - initTimer((long) (delay * 1000.0), teleportee, target, cashCharge, cause, false); - } - - //The respawn function is a wrapper used to handle tp fallback, on /jail and /home - @Override - @Deprecated - public void respawn(final Trade chargeFor, final TeleportCause cause) throws Exception { - double delay = ess.getSettings().getTeleportDelay(); - - final TeleportWarmupEvent event = new TeleportWarmupEvent(teleportOwner, cause, null, delay); - Bukkit.getServer().getPluginManager().callEvent(event); - if (event.isCancelled()) { - return; - } - delay = event.getDelay(); - - if (chargeFor != null) { - chargeFor.isAffordableFor(teleportOwner); - } - cooldown(true); - if (delay <= 0 || teleportOwner.isAuthorized("essentials.teleport.timer.bypass")) { - cooldown(false); - respawnNow(teleportOwner, cause); - if (chargeFor != null) { - chargeFor.charge(teleportOwner); - } - return; - } - - cancel(false); - warnUser(teleportOwner, delay); - initTimer((long) (delay * 1000.0), teleportOwner, null, chargeFor, cause, true); - } - - @Deprecated - void respawnNow(final IUser teleportee, final TeleportCause cause) throws Exception { - final Player player = teleportee.getBase(); - final Location bed = player.getBedSpawnLocation(); - if (bed != null) { - now(teleportee, new LocationTarget(bed), cause); - } else { - if (ess.getSettings().isDebug()) { - ess.getLogger().info("Could not find bed spawn, forcing respawn event."); - } - final PlayerRespawnEvent pre = new PlayerRespawnEvent(player, player.getWorld().getSpawnLocation(), false); - ess.getServer().getPluginManager().callEvent(pre); - now(teleportee, new LocationTarget(pre.getRespawnLocation()), cause); - } - } - - //The warp function is a wrapper used to teleportPlayer a player to a /warp - @Override - @Deprecated - public void warp(final IUser teleportee, String warp, final Trade chargeFor, final TeleportCause cause) throws Exception { - final UserWarpEvent event = new UserWarpEvent(teleportee, warp, chargeFor); - Bukkit.getServer().getPluginManager().callEvent(event); - if (event.isCancelled()) { - return; - } - - warp = event.getWarp(); - final Location loc = ess.getWarps().getWarp(warp); - teleportee.sendMessage(tl("warpingTo", warp, loc.getWorld().getName(), loc.getBlockX(), loc.getBlockY(), loc.getBlockZ())); - if (!teleportee.equals(teleportOwner)) { - teleportOwner.sendMessage(tl("warpingTo", warp, loc.getWorld().getName(), loc.getBlockX(), loc.getBlockY(), loc.getBlockZ())); - } - teleport(teleportee, new LocationTarget(loc), chargeFor, cause); - } - - //The back function is a wrapper used to teleportPlayer a player /back to their previous location. - @Override - @Deprecated - public void back(final Trade chargeFor) throws Exception { - back(teleportOwner, chargeFor); - } - - //This function is a wrapper over the other back function for cases where another player performs back for them - @Override - @Deprecated - public void back(final IUser teleporter, final Trade chargeFor) throws Exception { - tpType = TeleportType.BACK; - final Location loc = teleportOwner.getLastLocation(); - teleportOwner.sendMessage(tl("backUsageMsg", loc.getWorld().getName(), loc.getBlockX(), loc.getBlockY(), loc.getBlockZ())); - teleportOther(teleporter, teleportOwner, new LocationTarget(loc), chargeFor, TeleportCause.COMMAND); - } - - //This function is used to throw a user back after a jail sentence - @Override - @Deprecated - public void back() throws Exception { - now(teleportOwner, new LocationTarget(teleportOwner.getLastLocation()), TeleportCause.COMMAND); - } - - @Deprecated - public void setTpType(final TeleportType tpType) { - this.tpType = tpType; - } - - //If we need to cancelTimer a pending teleportPlayer call this method - @Deprecated - private void cancel(final boolean notifyUser) { - if (timedTeleport != null) { - timedTeleport.cancelTimer(notifyUser); - timedTeleport = null; - } - } - - @Deprecated - private void initTimer(final long delay, final IUser teleportUser, final ITarget target, final Trade chargeFor, final TeleportCause cause, final boolean respawn) { - timedTeleport = new TimedTeleport(teleportOwner, ess, this, delay, teleportUser, target, chargeFor, cause, respawn); - } - - public enum TeleportType { - TPA, - BACK, - NORMAL - } -} diff --git a/Essentials/src/main/java/com/earth2me/essentials/TimedTeleport.java b/Essentials/src/main/java/com/earth2me/essentials/TimedTeleport.java deleted file mode 100644 index 9e731918b80..00000000000 --- a/Essentials/src/main/java/com/earth2me/essentials/TimedTeleport.java +++ /dev/null @@ -1,140 +0,0 @@ -package com.earth2me.essentials; - -import net.ess3.api.IEssentials; -import net.ess3.api.IUser; -import org.bukkit.Location; -import org.bukkit.event.player.PlayerTeleportEvent.TeleportCause; - -import java.util.UUID; - -import static com.earth2me.essentials.I18n.tl; - -@Deprecated -public class TimedTeleport implements Runnable { - private static final double MOVE_CONSTANT = 0.3; - private final IUser teleportOwner; - private final IEssentials ess; - private final Teleport teleport; - private final UUID timer_teleportee; - private final long timer_started; // time this task was initiated - private final long timer_delay; // how long to delay the teleportPlayer - // note that I initially stored a clone of the location for reference, but... - // when comparing locations, I got incorrect mismatches (rounding errors, looked like) - // so, the X/Y/Z values are stored instead and rounded off - private final long timer_initX; - private final long timer_initY; - private final long timer_initZ; - private final ITarget timer_teleportTarget; - private final boolean timer_respawn; - private final boolean timer_canMove; - private final Trade timer_chargeFor; - private final TeleportCause timer_cause; - private int timer_task; - private double timer_health; - - TimedTeleport(final IUser user, final IEssentials ess, final Teleport teleport, final long delay, final IUser teleportUser, final ITarget target, final Trade chargeFor, final TeleportCause cause, final boolean respawn) { - this.teleportOwner = user; - this.ess = ess; - this.teleport = teleport; - this.timer_started = System.currentTimeMillis(); - this.timer_delay = delay; - this.timer_health = teleportUser.getBase().getHealth(); - this.timer_initX = Math.round(teleportUser.getBase().getLocation().getX() * MOVE_CONSTANT); - this.timer_initY = Math.round(teleportUser.getBase().getLocation().getY() * MOVE_CONSTANT); - this.timer_initZ = Math.round(teleportUser.getBase().getLocation().getZ() * MOVE_CONSTANT); - this.timer_teleportee = teleportUser.getBase().getUniqueId(); - this.timer_teleportTarget = target; - this.timer_chargeFor = chargeFor; - this.timer_cause = cause; - this.timer_respawn = respawn; - this.timer_canMove = user.isAuthorized("essentials.teleport.timer.move"); - - timer_task = ess.runTaskTimerAsynchronously(this, 20, 20).getTaskId(); - } - - @Override - public void run() { - - if (teleportOwner == null || !teleportOwner.getBase().isOnline() || teleportOwner.getBase().getLocation() == null) { - cancelTimer(false); - return; - } - - final IUser teleportUser = ess.getUser(this.timer_teleportee); - - if (teleportUser == null || !teleportUser.getBase().isOnline()) { - cancelTimer(false); - return; - } - - final Location currLocation = teleportUser.getBase().getLocation(); - if (currLocation == null) { - cancelTimer(false); - return; - } - - if (!timer_canMove && (Math.round(currLocation.getX() * MOVE_CONSTANT) != timer_initX || Math.round(currLocation.getY() * MOVE_CONSTANT) != timer_initY || Math.round(currLocation.getZ() * MOVE_CONSTANT) != timer_initZ || teleportUser.getBase().getHealth() < timer_health)) { - // user moved, cancelTimer teleportPlayer - cancelTimer(true); - return; - } - - class DelayedTeleportTask implements Runnable { - @Override - public void run() { - - timer_health = teleportUser.getBase().getHealth(); // in case user healed, then later gets injured - final long now = System.currentTimeMillis(); - if (now > timer_started + timer_delay) { - try { - teleport.cooldown(false); - } catch (final Exception ex) { - teleportOwner.sendMessage(tl("cooldownWithMessage", ex.getMessage())); - if (teleportOwner != teleportUser) { - teleportUser.sendMessage(tl("cooldownWithMessage", ex.getMessage())); - } - } - try { - cancelTimer(false); - teleportUser.sendMessage(tl("teleportationCommencing")); - - if (timer_chargeFor != null) { - timer_chargeFor.isAffordableFor(teleportOwner); - } - if (timer_respawn) { - teleport.respawnNow(teleportUser, timer_cause); - } else { - teleport.now(teleportUser, timer_teleportTarget, timer_cause); - } - if (timer_chargeFor != null) { - timer_chargeFor.charge(teleportOwner); - } - - } catch (final Exception ex) { - ess.showError(teleportOwner.getSource(), ex, "\\ teleport"); - } - } - } - } - - ess.scheduleSyncDelayedTask(new DelayedTeleportTask()); - } - - //If we need to cancelTimer a pending teleportPlayer call this method - void cancelTimer(final boolean notifyUser) { - if (timer_task == -1) { - return; - } - try { - ess.getServer().getScheduler().cancelTask(timer_task); - if (notifyUser) { - teleportOwner.sendMessage(tl("pendingTeleportCancelled")); - if (timer_teleportee != null && !timer_teleportee.equals(teleportOwner.getBase().getUniqueId())) { - ess.getUser(timer_teleportee).sendMessage(tl("pendingTeleportCancelled")); - } - } - } finally { - timer_task = -1; - } - } -} diff --git a/Essentials/src/main/java/com/earth2me/essentials/User.java b/Essentials/src/main/java/com/earth2me/essentials/User.java index 4c63fd0be6a..b624b3097e0 100644 --- a/Essentials/src/main/java/com/earth2me/essentials/User.java +++ b/Essentials/src/main/java/com/earth2me/essentials/User.java @@ -57,7 +57,6 @@ public class User extends UserData implements Comparable, IMessageRecipien // User modules private final IMessageRecipient messageRecipient; private transient final AsyncTeleport teleport; - private transient final Teleport legacyTeleport; // User command confirmation strings private final Map confirmingPayments = new WeakHashMap<>(); @@ -97,7 +96,6 @@ public class User extends UserData implements Comparable, IMessageRecipien public User(final Player base, final IEssentials ess) { super(base, ess); teleport = new AsyncTeleport(this, ess); - legacyTeleport = new Teleport(this, ess); if (isAfk()) { afkPosition = this.getLocation(); } @@ -550,15 +548,6 @@ public AsyncTeleport getAsyncTeleport() { return teleport; } - /** - * @deprecated This API is not asynchronous. Use {@link User#getAsyncTeleport()} - */ - @Override - @Deprecated - public Teleport getTeleport() { - return legacyTeleport; - } - public long getLastOnlineActivity() { return lastOnlineActivity; } @@ -811,9 +800,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)); } } diff --git a/Essentials/src/main/java/com/earth2me/essentials/api/IJails.java b/Essentials/src/main/java/com/earth2me/essentials/api/IJails.java index 94947c9c298..f39179e3383 100644 --- a/Essentials/src/main/java/com/earth2me/essentials/api/IJails.java +++ b/Essentials/src/main/java/com/earth2me/essentials/api/IJails.java @@ -46,17 +46,6 @@ public interface IJails extends IConf { */ void removeJail(String jail) throws Exception; - /** - * Attempts to send the given user to the given jail - * - * @param user the user to send to jail - * @param jail the jail to send the user to - * @throws Exception if the user is offline or jail does not exist - * @deprecated Use {@link IJails#sendToJail(IUser, String, CompletableFuture)} - */ - @Deprecated - void sendToJail(IUser user, String jail) throws Exception; - /** * Attempts to send the given user to the given jail * 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 a0846e43dca..acdfcfc8c6e 100644 --- a/Essentials/src/main/java/com/earth2me/essentials/commands/Commandbalancetop.java +++ b/Essentials/src/main/java/com/earth2me/essentials/commands/Commandbalancetop.java @@ -38,7 +38,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 bc55ef47d7e..f06679d7304 100644 --- a/Essentials/src/main/java/com/earth2me/essentials/commands/Commandbeezooka.java +++ b/Essentials/src/main/java/com/earth2me/essentials/commands/Commandbeezooka.java @@ -25,7 +25,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 5f7392cdea3..4569bcef36f 100644 --- a/Essentials/src/main/java/com/earth2me/essentials/commands/Commandgc.java +++ b/Essentials/src/main/java/com/earth2me/essentials/commands/Commandgc.java @@ -21,7 +21,7 @@ public Commandgc() { @Override protected void run(final Server server, final CommandSource sender, final String commandLabel, final String[] args) throws Exception { - final double tps = ess.getTimer().getAverageTPS(); + final double tps = 20d; //TODO final ChatColor color; if (tps >= 18.0) { color = ChatColor.GREEN; 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 e40234f5218..6356fe6d075 100644 --- a/Essentials/src/main/java/com/earth2me/essentials/commands/Commandkittycannon.java +++ b/Essentials/src/main/java/com/earth2me/essentials/commands/Commandkittycannon.java @@ -42,7 +42,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/Commandseen.java b/Essentials/src/main/java/com/earth2me/essentials/commands/Commandseen.java index f6c3e2d0040..ba992ba878d 100644 --- a/Essentials/src/main/java/com/earth2me/essentials/commands/Commandseen.java +++ b/Essentials/src/main/java/com/earth2me/essentials/commands/Commandseen.java @@ -59,7 +59,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 4e106554848..dfd79d32def 100644 --- a/Essentials/src/main/java/com/earth2me/essentials/commands/Commandskull.java +++ b/Essentials/src/main/java/com/earth2me/essentials/commands/Commandskull.java @@ -64,7 +64,7 @@ private void editSkull(final User user, final ItemStack stack, final SkullMeta s skullMeta.setDisplayName("§fSkull of " + owner); //noinspection deprecation skullMeta.setOwner(owner); - ess.scheduleSyncDelayedTask(() -> { + ess.scheduleEntityDelayedTask(user.getBase(), () -> { stack.setItemMeta(skullMeta); if (spawn) { Inventories.addItem(user.getBase(), stack); 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 df264f4dd7b..ebdcd790045 100644 --- a/Essentials/src/main/java/com/earth2me/essentials/commands/Commandsudo.java +++ b/Essentials/src/main/java/com/earth2me/essentials/commands/Commandsudo.java @@ -55,7 +55,7 @@ public void run() { } } - ess.scheduleSyncDelayedTask(new SudoCommandTask()); + ess.scheduleEntityDelayedTask(user.getBase(), new SudoCommandTask()); } } } 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/textreader/KeywordReplacer.java b/Essentials/src/main/java/com/earth2me/essentials/textreader/KeywordReplacer.java index f53d62940f3..87db4347c52 100644 --- a/Essentials/src/main/java/com/earth2me/essentials/textreader/KeywordReplacer.java +++ b/Essentials/src/main/java/com/earth2me/essentials/textreader/KeywordReplacer.java @@ -341,7 +341,7 @@ private String replaceLine(String line, final String fullMatch, final String[] m } break; case TPS: - replacer = NumberUtil.formatDouble(ess.getTimer().getAverageTPS()); + replacer = NumberUtil.formatDouble(20d); //todo break; case UPTIME: replacer = DateUtil.formatDateDiff(ManagementFactory.getRuntimeMXBean().getStartTime()); 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 e61786346d2..576ffe3a768 100644 --- a/Essentials/src/main/java/com/earth2me/essentials/utils/VersionUtil.java +++ b/Essentials/src/main/java/com/earth2me/essentials/utils/VersionUtil.java @@ -42,10 +42,20 @@ 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); public static final boolean PRE_FLATTENING = VersionUtil.getServerBukkitVersion().isLowerThan(VersionUtil.v1_13_0_R01); + public static final boolean FOLIA; private static final Map unsupportedServerClasses; static { + boolean isFolia; + try { + Class.forName("io.papermc.paper.threadedregions.scheduler.AsyncScheduler"); + isFolia = true; + } catch (Throwable ignored) { + isFolia = false; + } + FOLIA = isFolia; + final ImmutableMap.Builder builder = new ImmutableMap.Builder<>(); // Yatopia - Extremely volatile patch set; diff --git a/Essentials/src/main/resources/plugin.yml b/Essentials/src/main/resources/plugin.yml index 3faa7e5bb13..79c668edb33 100644 --- a/Essentials/src/main/resources/plugin.yml +++ b/Essentials/src/main/resources/plugin.yml @@ -8,6 +8,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 f7dac0e8942..1558a519cea 100644 --- a/EssentialsAntiBuild/src/main/resources/plugin.yml +++ b/EssentialsAntiBuild/src/main/resources/plugin.yml @@ -8,7 +8,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.exempt.protect: default: false diff --git a/EssentialsChat/src/main/resources/plugin.yml b/EssentialsChat/src/main/resources/plugin.yml index ab1d85c5a36..7fa9d802c05 100644 --- a/EssentialsChat/src/main/resources/plugin.yml +++ b/EssentialsChat/src/main/resources/plugin.yml @@ -8,6 +8,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/discord/EssentialsDiscord.java b/EssentialsDiscord/src/main/java/net/essentialsx/discord/EssentialsDiscord.java index ca288a580c2..a1f47d1ecbf 100644 --- a/EssentialsDiscord/src/main/java/net/essentialsx/discord/EssentialsDiscord.java +++ b/EssentialsDiscord/src/main/java/net/essentialsx/discord/EssentialsDiscord.java @@ -57,7 +57,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, tl("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 9ce07b6fe1f..31dbf794674 100644 --- a/EssentialsDiscord/src/main/java/net/essentialsx/discord/JDADiscordService.java +++ b/EssentialsDiscord/src/main/java/net/essentialsx/discord/JDADiscordService.java @@ -229,7 +229,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); } 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 28bb8c9603d..0cea35a9f10 100644 --- a/EssentialsDiscord/src/main/java/net/essentialsx/discord/interactions/InteractionControllerImpl.java +++ b/EssentialsDiscord/src/main/java/net/essentialsx/discord/interactions/InteractionControllerImpl.java @@ -61,7 +61,7 @@ public void onSlashCommand(@NotNull SlashCommandEvent event) { interactionEvent.reply(tl("noAccessCommand")); return; } - jda.getPlugin().getEss().scheduleSyncDelayedTask(() -> command.onCommand(interactionEvent)); + //jda.getPlugin().getEss().scheduleSyncDelayedTask(() -> command.onCommand(interactionEvent)); //todo } @Override 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 dfe95366279..baf4052ff6c 100644 --- a/EssentialsDiscord/src/main/java/net/essentialsx/discord/util/DiscordUtil.java +++ b/EssentialsDiscord/src/main/java/net/essentialsx/discord/util/DiscordUtil.java @@ -214,10 +214,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)); - } + jda.getPlugin().getEss().scheduleGlobalDelayedTask(() -> Bukkit.getPluginManager().callEvent(event)); } } diff --git a/EssentialsDiscord/src/main/resources/plugin.yml b/EssentialsDiscord/src/main/resources/plugin.yml index fad076db5fe..93e6c56b8cd 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: true 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 10b33d9d513..bc98d44646e 100644 --- a/EssentialsDiscordLink/src/main/java/net/essentialsx/discordlink/AccountLinkManager.java +++ b/EssentialsDiscordLink/src/main/java/net/essentialsx/discordlink/AccountLinkManager.java @@ -139,7 +139,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/listeners/LinkBukkitListener.java b/EssentialsDiscordLink/src/main/java/net/essentialsx/discordlink/listeners/LinkBukkitListener.java index 4ea071ff67e..8a1450e2a93 100644 --- a/EssentialsDiscordLink/src/main/java/net/essentialsx/discordlink/listeners/LinkBukkitListener.java +++ b/EssentialsDiscordLink/src/main/java/net/essentialsx/discordlink/listeners/LinkBukkitListener.java @@ -156,7 +156,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/resources/plugin.yml b/EssentialsDiscordLink/src/main/resources/plugin.yml index 49df5f7f3d0..aa0568eabd7 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: true 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 2ddee7b1201..6a52547ffad 100644 --- a/EssentialsGeoIP/src/main/resources/plugin.yml +++ b/EssentialsGeoIP/src/main/resources/plugin.yml @@ -8,3 +8,4 @@ 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 diff --git a/EssentialsProtect/src/main/resources/plugin.yml b/EssentialsProtect/src/main/resources/plugin.yml index 65764be4a41..25340332643 100644 --- a/EssentialsProtect/src/main/resources/plugin.yml +++ b/EssentialsProtect/src/main/resources/plugin.yml @@ -8,3 +8,4 @@ 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 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 59d8b4efeed..a1d8d72ac84 100644 --- a/EssentialsSpawn/src/main/java/com/earth2me/essentials/spawn/EssentialsSpawnPlayerListener.java +++ b/EssentialsSpawn/src/main/java/com/earth2me/essentials/spawn/EssentialsSpawnPlayerListener.java @@ -83,7 +83,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; @@ -104,10 +104,10 @@ private void delayedJoin(final Player player) { final User user = ess.getUser(player); if (!"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 e0a7b770d05..3746812f451 100644 --- a/EssentialsSpawn/src/main/resources/plugin.yml +++ b/EssentialsSpawn/src/main/resources/plugin.yml @@ -8,6 +8,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. From 07fbf930015d6cdc7ee4765f0248957713972a2b Mon Sep 17 00:00:00 2001 From: Josh Roy <10731363+JRoy@users.noreply.github.com> Date: Tue, 28 Mar 2023 21:23:27 -0400 Subject: [PATCH 03/45] Ensure nuke commands runs on correct threads --- .../essentials/commands/Commandnuke.java | 20 ++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) 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 f7bf8a33c5f..54da6594ab4 100644 --- a/Essentials/src/main/java/com/earth2me/essentials/commands/Commandnuke.java +++ b/Essentials/src/main/java/com/earth2me/essentials/commands/Commandnuke.java @@ -38,17 +38,19 @@ protected void run(final Server server, final CommandSource sender, final String if (player == null) { continue; } - player.sendMessage(tl("nuke")); - final Location loc = player.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(player, () -> { + player.sendMessage(tl("nuke")); + final Location loc = player.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)); + } } } - } + }); } } From 20882940c52ce2b12c01f9a31b66a769dae3c95f Mon Sep 17 00:00:00 2001 From: Josh Roy <10731363+JRoy@users.noreply.github.com> Date: Tue, 28 Mar 2023 21:30:51 -0400 Subject: [PATCH 04/45] Actually fire init tasks in Folia --- .../net/ess3/provider/providers/FoliaSchedulingProvider.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) 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 index bb4697727af..9ec92f9333d 100644 --- a/providers/FoliaProvider/src/main/java/net/ess3/provider/providers/FoliaSchedulingProvider.java +++ b/providers/FoliaProvider/src/main/java/net/ess3/provider/providers/FoliaSchedulingProvider.java @@ -6,18 +6,20 @@ 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; -public class FoliaSchedulingProvider implements SchedulingProvider { +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 From a649f978c5000f488558d579664d6c3221931252 Mon Sep 17 00:00:00 2001 From: Josh Roy <10731363+JRoy@users.noreply.github.com> Date: Tue, 28 Mar 2023 21:57:24 -0400 Subject: [PATCH 05/45] Add some thread checking helper methods pending PaperMC/Folia#5 for global region helpers --- .../java/net/ess3/provider/SchedulingProvider.java | 4 ++++ .../provider/providers/BukkitSchedulingProvider.java | 10 ++++++++++ .../provider/providers/FoliaSchedulingProvider.java | 10 ++++++++++ 3 files changed, 24 insertions(+) diff --git a/providers/BaseProviders/src/main/java/net/ess3/provider/SchedulingProvider.java b/providers/BaseProviders/src/main/java/net/ess3/provider/SchedulingProvider.java index 83a5a62cc37..0b4ca9c631c 100644 --- a/providers/BaseProviders/src/main/java/net/ess3/provider/SchedulingProvider.java +++ b/providers/BaseProviders/src/main/java/net/ess3/provider/SchedulingProvider.java @@ -6,6 +6,10 @@ public interface SchedulingProvider extends Provider { void registerInitTask(Runnable runnable); + boolean isEntityThread(Entity entity); + + boolean isRegionThread(Location location); + void runEntityTask(Entity entity, Runnable runnable); EssentialsTask runEntityTask(Entity entity, Runnable runnable, long delay); 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 index 887999fea1f..94433986d19 100644 --- a/providers/BaseProviders/src/main/java/net/ess3/provider/providers/BukkitSchedulingProvider.java +++ b/providers/BaseProviders/src/main/java/net/ess3/provider/providers/BukkitSchedulingProvider.java @@ -18,6 +18,16 @@ 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 void runEntityTask(Entity entity, Runnable runnable) { runEntityTask(entity, runnable, 1); 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 index 9ec92f9333d..4ffdf8e93d7 100644 --- a/providers/FoliaProvider/src/main/java/net/ess3/provider/providers/FoliaSchedulingProvider.java +++ b/providers/FoliaProvider/src/main/java/net/ess3/provider/providers/FoliaSchedulingProvider.java @@ -38,6 +38,16 @@ public void registerInitTask(Runnable runnable) { 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 void runEntityTask(Entity entity, Runnable runnable) { runEntityTask(entity, runnable, 1); From 04683ee43b40f58b7c8565e9ca7fb0eeb561ac1b Mon Sep 17 00:00:00 2001 From: Josh Roy <10731363+JRoy@users.noreply.github.com> Date: Wed, 29 Mar 2023 19:57:57 -0400 Subject: [PATCH 06/45] Fix async entity dismounts --- .../java/com/earth2me/essentials/AsyncTeleport.java | 9 +++++---- .../main/java/com/earth2me/essentials/Essentials.java | 10 ++++++++++ .../main/java/com/earth2me/essentials/IEssentials.java | 4 ++++ 3 files changed, 19 insertions(+), 4 deletions(-) diff --git a/Essentials/src/main/java/com/earth2me/essentials/AsyncTeleport.java b/Essentials/src/main/java/com/earth2me/essentials/AsyncTeleport.java index 26567ffaa5a..16a10e1ef2f 100644 --- a/Essentials/src/main/java/com/earth2me/essentials/AsyncTeleport.java +++ b/Essentials/src/main/java/com/earth2me/essentials/AsyncTeleport.java @@ -13,6 +13,7 @@ import net.ess3.api.events.teleport.TeleportWarmupEvent; import org.bukkit.Bukkit; import org.bukkit.Location; +import org.bukkit.entity.Entity; import org.bukkit.entity.Player; import org.bukkit.event.player.PlayerRespawnEvent; import org.bukkit.event.player.PlayerTeleportEvent.TeleportCause; @@ -141,13 +142,13 @@ public void nowUnsafe(Location loc, TeleportCause cause, CompletableFuture taskLock = new CompletableFuture<>(); - Bukkit.getScheduler().runTask(ess, () -> { + ess.scheduleEntityDelayedTask(entity, () -> { runnable.run(); taskLock.complete(new Object()); }); @@ -171,7 +172,7 @@ protected void nowAsync(final IUser teleportee, final ITarget target, final Tele } try { - runOnMain(() -> teleportee.getBase().eject()); //EntityDismountEvent requires a sync context. + runOnEntity(teleportee.getBase(), () -> teleportee.getBase().eject()); //EntityDismountEvent requires a sync context. } catch (final ExecutionException | InterruptedException e) { future.completeExceptionally(e); return; diff --git a/Essentials/src/main/java/com/earth2me/essentials/Essentials.java b/Essentials/src/main/java/com/earth2me/essentials/Essentials.java index 77b24ca1fb8..aaf11837d0e 100644 --- a/Essentials/src/main/java/com/earth2me/essentials/Essentials.java +++ b/Essentials/src/main/java/com/earth2me/essentials/Essentials.java @@ -1228,6 +1228,16 @@ public SchedulingProvider.EssentialsTask scheduleGlobalRepeatingTask(Runnable ru return schedulingProvider.runGlobalLocationalTaskRepeating(run, delay, period); } + @Override + public boolean isEntityThread(Entity entity) { + return schedulingProvider.isEntityThread(entity); + } + + @Override + public boolean isRegionThread(Location location) { + return schedulingProvider.isRegionThread(location); + } + @Override public PermissionsHandler getPermissionsHandler() { return permissionsHandler; diff --git a/Essentials/src/main/java/com/earth2me/essentials/IEssentials.java b/Essentials/src/main/java/com/earth2me/essentials/IEssentials.java index 59334158390..0f0c51a2db7 100644 --- a/Essentials/src/main/java/com/earth2me/essentials/IEssentials.java +++ b/Essentials/src/main/java/com/earth2me/essentials/IEssentials.java @@ -125,6 +125,10 @@ default void scheduleGlobalDelayedTask(Runnable run) { void scheduleInitTask(Runnable runnable); + boolean isEntityThread(Entity entity); + + boolean isRegionThread(Location location); + PermissionsHandler getPermissionsHandler(); AlternativeCommandsHandler getAlternativeCommandsHandler(); From 1d93ebfddd77f6614bc53e56eed7de16d98d974e Mon Sep 17 00:00:00 2001 From: Josh Roy <10731363+JRoy@users.noreply.github.com> Date: Wed, 29 Mar 2023 19:59:30 -0400 Subject: [PATCH 07/45] Fix moo --- .../com/earth2me/essentials/commands/Commandessentials.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Essentials/src/main/java/com/earth2me/essentials/commands/Commandessentials.java b/Essentials/src/main/java/com/earth2me/essentials/commands/Commandessentials.java index 6fa2873afa2..2e3ae8b96bc 100644 --- a/Essentials/src/main/java/com/earth2me/essentials/commands/Commandessentials.java +++ b/Essentials/src/main/java/com/earth2me/essentials/commands/Commandessentials.java @@ -502,8 +502,10 @@ private void runMoo(final Server server, final CommandSource sender, final Strin ess.getLogger().info(s); } for (final Player player : ess.getOnlinePlayers()) { - player.sendMessage(PLAYER_MOO); - player.playSound(player.getLocation(), MOO_SOUND, 1, 1.0f); + ess.scheduleEntityDelayedTask(player, () -> { + player.sendMessage(PLAYER_MOO); + player.playSound(player.getLocation(), MOO_SOUND, 1, 1.0f); + }); } } else { if (sender.isPlayer()) { From 05a8aa88838a22675306aebf35c51609c8530e6b Mon Sep 17 00:00:00 2001 From: Josh Roy <10731363+JRoy@users.noreply.github.com> Date: Thu, 30 Mar 2023 13:06:35 -0400 Subject: [PATCH 08/45] Switch to Paper repo --- settings.gradle.kts | 1 - 1 file changed, 1 deletion(-) diff --git a/settings.gradle.kts b/settings.gradle.kts index d6ed8b4292b..65e89d2d9e0 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -21,7 +21,6 @@ dependencyResolutionManagement { content { includeGroup("net.kyori") } content { includeGroup("org.apache.logging.log4j") } } - mavenLocal() } repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) } From 9a5ac3cad849bc7e1df56b5e9978bdcfadcacc41 Mon Sep 17 00:00:00 2001 From: Josh Roy <10731363+JRoy@users.noreply.github.com> Date: Thu, 30 Mar 2023 13:20:29 -0400 Subject: [PATCH 09/45] Fix tests --- Essentials/src/main/java/com/earth2me/essentials/Essentials.java | 1 + 1 file changed, 1 insertion(+) diff --git a/Essentials/src/main/java/com/earth2me/essentials/Essentials.java b/Essentials/src/main/java/com/earth2me/essentials/Essentials.java index aaf11837d0e..f7bac6ea2d0 100644 --- a/Essentials/src/main/java/com/earth2me/essentials/Essentials.java +++ b/Essentials/src/main/java/com/earth2me/essentials/Essentials.java @@ -213,6 +213,7 @@ public ISettings getSettings() { public void setupForTesting(final Server server) throws IOException, InvalidDescriptionException { LOGGER = new BaseLoggerProvider(this, BUKKIT_LOGGER); + schedulingProvider = new BukkitSchedulingProvider(this); final File dataFolder = File.createTempFile("essentialstest", ""); if (!dataFolder.delete()) { throw new IOException(); From 4ff9286a799148fc6110a62b5d2541e99ed68813 Mon Sep 17 00:00:00 2001 From: Josh Roy <10731363+JRoy@users.noreply.github.com> Date: Thu, 30 Mar 2023 13:28:41 -0400 Subject: [PATCH 10/45] Move over some discord things --- .../discord/listeners/DiscordCommandDispatcher.java | 2 +- .../net/essentialsx/discord/util/ConsoleInjector.java | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) 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 20fa1ff0a96..2ddbf8d6c65 100644 --- a/EssentialsDiscord/src/main/java/net/essentialsx/discord/listeners/DiscordCommandDispatcher.java +++ b/EssentialsDiscord/src/main/java/net/essentialsx/discord/listeners/DiscordCommandDispatcher.java @@ -25,7 +25,7 @@ public void onGuildMessageReceived(@NotNull GuildMessageReceivedEvent 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 3d2502770aa..5546fe849a5 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.BlockingQueue; @@ -26,14 +26,14 @@ public class ConsoleInjector extends AbstractAppender { private final JDADiscordService jda; private final BlockingQueue messageQueue = new LinkedBlockingQueue<>(); - 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) { @@ -47,7 +47,7 @@ public ConsoleInjector(JDADiscordService jda) { if (buffer.length() != 0) { sendMessage(buffer.toString()); } - }, 20, 40).getTaskId(); + }, 20, 40); } private void sendMessage(String content) { @@ -92,7 +92,7 @@ public void append(LogEvent event) { public void remove() { ((Logger) LogManager.getRootLogger()).removeAppender(this); - Bukkit.getScheduler().cancelTask(taskId); + task.cancel(); messageQueue.clear(); removed = true; } From 2d8cc827b9f3046cc4ef2f50032cffbcc96c7a9a Mon Sep 17 00:00:00 2001 From: Josh Roy <10731363+JRoy@users.noreply.github.com> Date: Thu, 30 Mar 2023 13:31:16 -0400 Subject: [PATCH 11/45] Add method for checking the global thread --- .../src/main/java/com/earth2me/essentials/Essentials.java | 5 +++++ .../src/main/java/com/earth2me/essentials/IEssentials.java | 2 ++ .../src/main/java/net/ess3/provider/SchedulingProvider.java | 2 ++ .../ess3/provider/providers/BukkitSchedulingProvider.java | 6 ++++++ .../ess3/provider/providers/FoliaSchedulingProvider.java | 5 +++++ 5 files changed, 20 insertions(+) diff --git a/Essentials/src/main/java/com/earth2me/essentials/Essentials.java b/Essentials/src/main/java/com/earth2me/essentials/Essentials.java index f7bac6ea2d0..1a3dcae411d 100644 --- a/Essentials/src/main/java/com/earth2me/essentials/Essentials.java +++ b/Essentials/src/main/java/com/earth2me/essentials/Essentials.java @@ -1239,6 +1239,11 @@ public boolean isRegionThread(Location location) { return schedulingProvider.isRegionThread(location); } + @Override + public boolean isGlobalThread() { + return schedulingProvider.isGlobalThread(); + } + @Override public PermissionsHandler getPermissionsHandler() { return permissionsHandler; diff --git a/Essentials/src/main/java/com/earth2me/essentials/IEssentials.java b/Essentials/src/main/java/com/earth2me/essentials/IEssentials.java index 0f0c51a2db7..375aa77a181 100644 --- a/Essentials/src/main/java/com/earth2me/essentials/IEssentials.java +++ b/Essentials/src/main/java/com/earth2me/essentials/IEssentials.java @@ -129,6 +129,8 @@ default void scheduleGlobalDelayedTask(Runnable run) { boolean isRegionThread(Location location); + boolean isGlobalThread(); + PermissionsHandler getPermissionsHandler(); AlternativeCommandsHandler getAlternativeCommandsHandler(); diff --git a/providers/BaseProviders/src/main/java/net/ess3/provider/SchedulingProvider.java b/providers/BaseProviders/src/main/java/net/ess3/provider/SchedulingProvider.java index 0b4ca9c631c..8206a16f213 100644 --- a/providers/BaseProviders/src/main/java/net/ess3/provider/SchedulingProvider.java +++ b/providers/BaseProviders/src/main/java/net/ess3/provider/SchedulingProvider.java @@ -10,6 +10,8 @@ public interface SchedulingProvider extends Provider { boolean isRegionThread(Location location); + boolean isGlobalThread(); + void runEntityTask(Entity entity, Runnable runnable); EssentialsTask runEntityTask(Entity entity, Runnable runnable, long delay); 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 index 94433986d19..ac21c00405a 100644 --- a/providers/BaseProviders/src/main/java/net/ess3/provider/providers/BukkitSchedulingProvider.java +++ b/providers/BaseProviders/src/main/java/net/ess3/provider/providers/BukkitSchedulingProvider.java @@ -1,6 +1,7 @@ package net.ess3.provider.providers; import net.ess3.provider.SchedulingProvider; +import org.bukkit.Bukkit; import org.bukkit.Location; import org.bukkit.entity.Entity; import org.bukkit.plugin.Plugin; @@ -28,6 +29,11 @@ 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); 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 index 4ffdf8e93d7..0cc884506cd 100644 --- a/providers/FoliaProvider/src/main/java/net/ess3/provider/providers/FoliaSchedulingProvider.java +++ b/providers/FoliaProvider/src/main/java/net/ess3/provider/providers/FoliaSchedulingProvider.java @@ -48,6 +48,11 @@ 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); From 7216fddfbe4694dc4aa1119ed48df10dcb9604b1 Mon Sep 17 00:00:00 2001 From: Josh Roy <10731363+JRoy@users.noreply.github.com> Date: Thu, 30 Mar 2023 13:40:13 -0400 Subject: [PATCH 12/45] Add helper methods for ensuring thread contexts --- .../earth2me/essentials/AsyncTeleport.java | 18 +----- .../com/earth2me/essentials/Essentials.java | 59 +++++++++++++++++++ .../com/earth2me/essentials/IEssentials.java | 6 ++ 3 files changed, 67 insertions(+), 16 deletions(-) diff --git a/Essentials/src/main/java/com/earth2me/essentials/AsyncTeleport.java b/Essentials/src/main/java/com/earth2me/essentials/AsyncTeleport.java index 16a10e1ef2f..25a334df03b 100644 --- a/Essentials/src/main/java/com/earth2me/essentials/AsyncTeleport.java +++ b/Essentials/src/main/java/com/earth2me/essentials/AsyncTeleport.java @@ -13,7 +13,6 @@ import net.ess3.api.events.teleport.TeleportWarmupEvent; import org.bukkit.Bukkit; import org.bukkit.Location; -import org.bukkit.entity.Entity; import org.bukkit.entity.Player; import org.bukkit.event.player.PlayerRespawnEvent; import org.bukkit.event.player.PlayerTeleportEvent.TeleportCause; @@ -142,19 +141,6 @@ public void nowUnsafe(Location loc, TeleportCause cause, CompletableFuture taskLock = new CompletableFuture<>(); - ess.scheduleEntityDelayedTask(entity, () -> { - 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); @@ -172,8 +158,8 @@ protected void nowAsync(final IUser teleportee, final ITarget target, final Tele } try { - runOnEntity(teleportee.getBase(), () -> 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; } diff --git a/Essentials/src/main/java/com/earth2me/essentials/Essentials.java b/Essentials/src/main/java/com/earth2me/essentials/Essentials.java index 1a3dcae411d..a3477c6fce0 100644 --- a/Essentials/src/main/java/com/earth2me/essentials/Essentials.java +++ b/Essentials/src/main/java/com/earth2me/essentials/Essentials.java @@ -138,6 +138,8 @@ import java.util.Map; 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; @@ -1244,6 +1246,63 @@ public boolean isGlobalThread() { return schedulingProvider.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 public PermissionsHandler getPermissionsHandler() { return permissionsHandler; diff --git a/Essentials/src/main/java/com/earth2me/essentials/IEssentials.java b/Essentials/src/main/java/com/earth2me/essentials/IEssentials.java index 375aa77a181..4c1c22c164c 100644 --- a/Essentials/src/main/java/com/earth2me/essentials/IEssentials.java +++ b/Essentials/src/main/java/com/earth2me/essentials/IEssentials.java @@ -131,6 +131,12 @@ default void scheduleGlobalDelayedTask(Runnable run) { boolean isGlobalThread(); + void ensureEntity(Entity entity, Runnable runnable); + + void ensureRegion(Location location, Runnable runnable); + + void ensureGlobal(Runnable runnable); + PermissionsHandler getPermissionsHandler(); AlternativeCommandsHandler getAlternativeCommandsHandler(); From 2ba1eb0cf9f896af7168d2ea24a721f04414c953 Mon Sep 17 00:00:00 2001 From: Josh Roy <10731363+JRoy@users.noreply.github.com> Date: Sat, 1 Apr 2023 17:42:38 -0400 Subject: [PATCH 13/45] fix "Fix moo" --- .../com/earth2me/essentials/commands/Commandessentials.java | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/Essentials/src/main/java/com/earth2me/essentials/commands/Commandessentials.java b/Essentials/src/main/java/com/earth2me/essentials/commands/Commandessentials.java index 2e3ae8b96bc..6fa2873afa2 100644 --- a/Essentials/src/main/java/com/earth2me/essentials/commands/Commandessentials.java +++ b/Essentials/src/main/java/com/earth2me/essentials/commands/Commandessentials.java @@ -502,10 +502,8 @@ private void runMoo(final Server server, final CommandSource sender, final Strin ess.getLogger().info(s); } for (final Player player : ess.getOnlinePlayers()) { - ess.scheduleEntityDelayedTask(player, () -> { - player.sendMessage(PLAYER_MOO); - player.playSound(player.getLocation(), MOO_SOUND, 1, 1.0f); - }); + player.sendMessage(PLAYER_MOO); + player.playSound(player.getLocation(), MOO_SOUND, 1, 1.0f); } } else { if (sender.isPlayer()) { From 7279be7850d6fbf8161ae83233403e585e56ed00 Mon Sep 17 00:00:00 2001 From: Josh Roy <10731363+JRoy@users.noreply.github.com> Date: Sun, 2 Apr 2023 20:58:56 -0400 Subject: [PATCH 14/45] allow DiscordMessageEvent calls from any context --- .../api/v2/events/discord/DiscordMessageEvent.java | 4 ++++ .../java/net/essentialsx/discord/JDADiscordService.java | 6 +----- .../main/java/net/essentialsx/discord/util/DiscordUtil.java | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) 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/JDADiscordService.java b/EssentialsDiscord/src/main/java/net/essentialsx/discord/JDADiscordService.java index 31dbf794674..9de36d81e45 100644 --- a/EssentialsDiscord/src/main/java/net/essentialsx/discord/JDADiscordService.java +++ b/EssentialsDiscord/src/main/java/net/essentialsx/discord/JDADiscordService.java @@ -258,11 +258,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/util/DiscordUtil.java b/EssentialsDiscord/src/main/java/net/essentialsx/discord/util/DiscordUtil.java index baf4052ff6c..33737e9161b 100644 --- a/EssentialsDiscord/src/main/java/net/essentialsx/discord/util/DiscordUtil.java +++ b/EssentialsDiscord/src/main/java/net/essentialsx/discord/util/DiscordUtil.java @@ -214,6 +214,6 @@ public static void dispatchDiscordMessage(final JDADiscordService jda, final Mes return; } - jda.getPlugin().getEss().scheduleGlobalDelayedTask(() -> Bukkit.getPluginManager().callEvent(event)); + Bukkit.getPluginManager().callEvent(event); } } From 4cb861badc015067482fa80287986e91726fc15a Mon Sep 17 00:00:00 2001 From: Josh Roy <10731363+JRoy@users.noreply.github.com> Date: Sun, 2 Apr 2023 21:11:55 -0400 Subject: [PATCH 15/45] Run interaction commands async for now (hopefully okay) --- .../discord/interactions/InteractionControllerImpl.java | 3 ++- .../discord/interactions/commands/ExecuteCommand.java | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) 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 0cea35a9f10..533c5575cf2 100644 --- a/EssentialsDiscord/src/main/java/net/essentialsx/discord/interactions/InteractionControllerImpl.java +++ b/EssentialsDiscord/src/main/java/net/essentialsx/discord/interactions/InteractionControllerImpl.java @@ -61,7 +61,8 @@ public void onSlashCommand(@NotNull SlashCommandEvent event) { interactionEvent.reply(tl("noAccessCommand")); return; } - //jda.getPlugin().getEss().scheduleSyncDelayedTask(() -> command.onCommand(interactionEvent)); //todo + + 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 0a856b53237..cd258931099 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.reply(tl("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) { From ec5007c07eb7e4338bc22c70d5fd8a1a398d21dd Mon Sep 17 00:00:00 2001 From: Josh Roy <10731363+JRoy@users.noreply.github.com> Date: Wed, 12 Apr 2023 18:45:22 -0400 Subject: [PATCH 16/45] fix wrong period/timeunit for folia scheduling --- .../net/ess3/provider/providers/FoliaSchedulingProvider.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 index 0cc884506cd..0e5e538f507 100644 --- a/providers/FoliaProvider/src/main/java/net/ess3/provider/providers/FoliaSchedulingProvider.java +++ b/providers/FoliaProvider/src/main/java/net/ess3/provider/providers/FoliaSchedulingProvider.java @@ -115,7 +115,7 @@ public void runAsyncTaskLater(Runnable runnable, long delay) { @Override public EssentialsTask runAsyncTaskRepeating(Runnable runnable, long delay, long period) { - final ScheduledTask task = plugin.getServer().getAsyncScheduler().runAtFixedRate(plugin, scheduledTask -> runnable.run(), delay, delay * 50, TimeUnit.MICROSECONDS); + final ScheduledTask task = plugin.getServer().getAsyncScheduler().runAtFixedRate(plugin, scheduledTask -> runnable.run(), delay, period * 50, TimeUnit.MILLISECONDS); return task::cancel; } From 4256909eee82fca4c0675edda01723dacf2e3a63 Mon Sep 17 00:00:00 2001 From: Josh Roy <10731363+JRoy@users.noreply.github.com> Date: Wed, 26 Apr 2023 21:28:33 -0400 Subject: [PATCH 17/45] Fix folia init tasks never getting called LOLW --- .../src/main/java/com/earth2me/essentials/Essentials.java | 6 +----- .../com/earth2me/essentials/commands/Commandessentials.java | 1 + Essentials/src/main/resources/messages.properties | 1 + 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/Essentials/src/main/java/com/earth2me/essentials/Essentials.java b/Essentials/src/main/java/com/earth2me/essentials/Essentials.java index a3477c6fce0..319245334b2 100644 --- a/Essentials/src/main/java/com/earth2me/essentials/Essentials.java +++ b/Essentials/src/main/java/com/earth2me/essentials/Essentials.java @@ -467,6 +467,7 @@ public void onEnable() { } execTimer.mark("Init(Providers)"); + registerListeners(getServer().getPluginManager()); reload(); // The item spawn blacklist is loaded with all other settings, before the item @@ -526,8 +527,6 @@ public void saveConfig() { } private void registerListeners(final PluginManager pm) { - HandlerList.unregisterAll(this); - if (getSettings().isDebug()) { LOGGER.log(Level.INFO, "Registering Listeners"); } @@ -629,9 +628,6 @@ public void reload() { command.setUsage(tl(commandName + "CommandUsage")); } } - - final PluginManager pm = getServer().getPluginManager(); - registerListeners(pm); } private IEssentialsCommand loadCommand(final String path, final String name, final IEssentialsModule module, final ClassLoader classLoader) throws Exception { diff --git a/Essentials/src/main/java/com/earth2me/essentials/commands/Commandessentials.java b/Essentials/src/main/java/com/earth2me/essentials/commands/Commandessentials.java index 6fa2873afa2..3b108b2044e 100644 --- a/Essentials/src/main/java/com/earth2me/essentials/commands/Commandessentials.java +++ b/Essentials/src/main/java/com/earth2me/essentials/commands/Commandessentials.java @@ -728,6 +728,7 @@ private void runVersion(final Server server, final CommandSource sender, final S sender.sendMessage(tl(serverMessageKey, "Server", server.getBukkitVersion() + " " + server.getVersion())); sender.sendMessage(tl(serverMessageKey, "Brand", server.getName())); + sender.sendMessage(tl("versionOutputFlags", "FOLIA:" + VersionUtil.FOLIA + ",FLAT:" + VersionUtil.PRE_FLATTENING + ",SUPSTAT:" + VersionUtil.getServerSupportStatus().name())); sender.sendMessage(tl("versionOutputFine", "EssentialsX", essVer)); for (final Plugin plugin : pm.getPlugins()) { diff --git a/Essentials/src/main/resources/messages.properties b/Essentials/src/main/resources/messages.properties index 46567df00eb..526f839f4b7 100644 --- a/Essentials/src/main/resources/messages.properties +++ b/Essentials/src/main/resources/messages.properties @@ -1483,6 +1483,7 @@ versionErrorPlayer=\u00a76Error while checking EssentialsX version information! versionFetching=\u00a76Fetching version information... versionOutputVaultMissing=\u00a74Vault is not installed. Chat and permissions may not work. versionOutputFine=\u00a76{0} version: \u00a7a{1} +versionOutputFlags=\u00a76Feature Flags: \u00a7a{0} versionOutputWarn=\u00a76{0} version: \u00a7c{1} versionOutputUnsupported=\u00a7d{0} \u00a76version: \u00a7d{1} versionOutputUnsupportedPlugins=\u00a76You are running \u00a7dunsupported plugins\u00a76! From 1a108b5e2e6864197a6292e4e3c254f112c49da3 Mon Sep 17 00:00:00 2001 From: Josh Roy <10731363+JRoy@users.noreply.github.com> Date: Thu, 27 Jul 2023 19:35:45 -0400 Subject: [PATCH 18/45] Bump folia version --- providers/FoliaProvider/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/providers/FoliaProvider/build.gradle b/providers/FoliaProvider/build.gradle index 42ae0f072b3..fe183290bd8 100644 --- a/providers/FoliaProvider/build.gradle +++ b/providers/FoliaProvider/build.gradle @@ -10,7 +10,7 @@ dependencies { implementation(project(':providers:BaseProviders')) { exclude(module: 'spigot-api') } - compileOnly 'dev.folia:folia-api:1.19.4-R0.1-SNAPSHOT' + compileOnly 'dev.folia:folia-api:1.20.1-R0.1-SNAPSHOT' } essentials { From d4ebaf61d4549d64b15845a1b1cb4b058e7e7687 Mon Sep 17 00:00:00 2001 From: Josh Roy <10731363+JRoy@users.noreply.github.com> Date: Thu, 27 Jul 2023 19:38:32 -0400 Subject: [PATCH 19/45] Fix weather command --- .../essentials/commands/Commandweather.java | 34 +++++++++++-------- 1 file changed, 19 insertions(+), 15 deletions(-) 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 1dc9b41981d..ab7de23b28a 100644 --- a/Essentials/src/main/java/com/earth2me/essentials/commands/Commandweather.java +++ b/Essentials/src/main/java/com/earth2me/essentials/commands/Commandweather.java @@ -31,16 +31,18 @@ public void run(final Server server, final User user, final String commandLabel, isStorm = args[0].equalsIgnoreCase("storm"); } - final World world = user.getWorld(); + ess.scheduleEntityDelayedTask(user.getBase(), () -> { + final World world = user.getWorld(); - if (args.length > 1) { + if (args.length > 1) { + world.setStorm(isStorm); + world.setWeatherDuration(Integer.parseInt(args[1]) * 20); + user.sendMessage(isStorm ? tl("weatherStormFor", world.getName(), args[1]) : tl("weatherSunFor", world.getName(), args[1])); + return; + } world.setStorm(isStorm); - world.setWeatherDuration(Integer.parseInt(args[1]) * 20); - user.sendMessage(isStorm ? tl("weatherStormFor", world.getName(), args[1]) : tl("weatherSunFor", world.getName(), args[1])); - return; - } - world.setStorm(isStorm); - user.sendMessage(isStorm ? tl("weatherStorm", world.getName()) : tl("weatherSun", world.getName())); + user.sendMessage(isStorm ? tl("weatherStorm", world.getName()) : tl("weatherSun", world.getName())); + }); } @Override @@ -55,14 +57,16 @@ protected void run(final Server server, final CommandSource sender, final String throw new Exception(tl("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.sendMessage(isStorm ? tl("weatherStormFor", world.getName(), args[2]) : tl("weatherSunFor", world.getName(), args[2])); + return; + } world.setStorm(isStorm); - world.setWeatherDuration(Integer.parseInt(args[2]) * 20); - sender.sendMessage(isStorm ? tl("weatherStormFor", world.getName(), args[2]) : tl("weatherSunFor", world.getName(), args[2])); - return; - } - world.setStorm(isStorm); - sender.sendMessage(isStorm ? tl("weatherStorm", world.getName()) : tl("weatherSun", world.getName())); + sender.sendMessage(isStorm ? tl("weatherStorm", world.getName()) : tl("weatherSun", world.getName())); + }); } @Override From 0c8bc16c3d8433b1e3c1d40ac18f1a6044414120 Mon Sep 17 00:00:00 2001 From: Josh Roy <10731363+JRoy@users.noreply.github.com> Date: Thu, 27 Jul 2023 19:59:56 -0400 Subject: [PATCH 20/45] Fix time command --- .../earth2me/essentials/commands/Commandtime.java | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) 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 06c86b2da8c..3537d693cf5 100644 --- a/Essentials/src/main/java/com/earth2me/essentials/commands/Commandtime.java +++ b/Essentials/src/main/java/com/earth2me/essentials/commands/Commandtime.java @@ -81,15 +81,17 @@ 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) { - long time = world.getTime(); - if (!add) { - time -= time % 24000; - } - world.setTime(time + (add ? 0 : 24000) + timeTick); joiner.add(world.getName()); + ess.scheduleLocationDelayedTask(world.getSpawnLocation(), () -> { + long time = world.getTime(); + if (!timeAdd) { + time -= time % 24000; + } + world.setTime(time + (timeAdd ? 0 : 24000) + timeTick); + }); } - sender.sendMessage(tl(add ? "timeWorldAdd" : "timeWorldSet", DescParseTickFormat.formatTicks(timeTick), joiner.toString())); } From d73ed137fae07a27929e68cc07b512128c83caf9 Mon Sep 17 00:00:00 2001 From: Josh Roy <10731363+JRoy@users.noreply.github.com> Date: Tue, 22 Aug 2023 12:12:33 -0400 Subject: [PATCH 21/45] Fix time/weather commands --- .../main/java/com/earth2me/essentials/commands/Commandtime.java | 2 +- .../java/com/earth2me/essentials/commands/Commandweather.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) 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 3537d693cf5..7e437339c90 100644 --- a/Essentials/src/main/java/com/earth2me/essentials/commands/Commandtime.java +++ b/Essentials/src/main/java/com/earth2me/essentials/commands/Commandtime.java @@ -84,7 +84,7 @@ public void run(final Server server, final CommandSource sender, final String co final boolean timeAdd = add; for (final World world : worlds) { joiner.add(world.getName()); - ess.scheduleLocationDelayedTask(world.getSpawnLocation(), () -> { + ess.scheduleGlobalDelayedTask(() -> { long time = world.getTime(); if (!timeAdd) { time -= time % 24000; 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 ab7de23b28a..cfcc845baa5 100644 --- a/Essentials/src/main/java/com/earth2me/essentials/commands/Commandweather.java +++ b/Essentials/src/main/java/com/earth2me/essentials/commands/Commandweather.java @@ -31,7 +31,7 @@ public void run(final Server server, final User user, final String commandLabel, isStorm = args[0].equalsIgnoreCase("storm"); } - ess.scheduleEntityDelayedTask(user.getBase(), () -> { + ess.scheduleGlobalDelayedTask(() -> { final World world = user.getWorld(); if (args.length > 1) { From 00e35092add56e7c77d67c8459c4df1fa4b3838c Mon Sep 17 00:00:00 2001 From: Warrior <50800980+Warriorrrr@users.noreply.github.com> Date: Fri, 13 Feb 2026 19:59:33 +0100 Subject: [PATCH 22/45] Update scheduling providers to work with new provider system --- .../java/com/earth2me/essentials/Essentials.java | 2 +- .../earth2me/essentials/utils/VersionUtil.java | 2 +- .../providers/BukkitSchedulingProvider.java | 7 ++----- .../providers/FoliaSchedulingProvider.java | 15 +++++++++++---- 4 files changed, 15 insertions(+), 11 deletions(-) diff --git a/Essentials/src/main/java/com/earth2me/essentials/Essentials.java b/Essentials/src/main/java/com/earth2me/essentials/Essentials.java index fe6970d6f2f..89e9c1995d3 100644 --- a/Essentials/src/main/java/com/earth2me/essentials/Essentials.java +++ b/Essentials/src/main/java/com/earth2me/essentials/Essentials.java @@ -396,7 +396,7 @@ public void onEnable() { // Scheduling provider providerFactory.registerProvider(FoliaSchedulingProvider.class, BukkitSchedulingProvider.class); - if (!TESTING) { + if (!TESTING || true) { // FIXME: The scheduling provider is necessary for tests to pass without adding checks for TESTING everywhere providerFactory.finalizeRegistration(); } 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 3721312cf63..8c2f5fb0ad7 100644 --- a/Essentials/src/main/java/com/earth2me/essentials/utils/VersionUtil.java +++ b/Essentials/src/main/java/com/earth2me/essentials/utils/VersionUtil.java @@ -56,7 +56,7 @@ public final class VersionUtil { static { boolean isFolia; try { - Class.forName("io.papermc.paper.threadedregions.scheduler.AsyncScheduler"); + Class.forName("io.papermc.paper.threadedregions.RegionizedServer"); isFolia = true; } catch (Throwable ignored) { isFolia = false; 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 index ac21c00405a..f24928c918b 100644 --- a/providers/BaseProviders/src/main/java/net/ess3/provider/providers/BukkitSchedulingProvider.java +++ b/providers/BaseProviders/src/main/java/net/ess3/provider/providers/BukkitSchedulingProvider.java @@ -1,12 +1,14 @@ 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; @@ -99,9 +101,4 @@ public EssentialsTask runAsyncTaskRepeating(Runnable runnable, long delay, long final BukkitTask task = plugin.getServer().getScheduler().runTaskTimerAsynchronously(plugin, runnable, delay, period); return task::cancel; } - - @Override - public String getDescription() { - return "Bukkit Scheduling Provider"; - } } 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 index 0e5e538f507..7adf2c0c80c 100644 --- a/providers/FoliaProvider/src/main/java/net/ess3/provider/providers/FoliaSchedulingProvider.java +++ b/providers/FoliaProvider/src/main/java/net/ess3/provider/providers/FoliaSchedulingProvider.java @@ -3,6 +3,8 @@ 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; @@ -13,6 +15,7 @@ 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<>(); @@ -119,9 +122,13 @@ public EssentialsTask runAsyncTaskRepeating(Runnable runnable, long delay, long return task::cancel; } - @Override - public String getDescription() { - return "Folia Scheduling Provider"; + @ProviderTest + public static boolean test() { + try { + Class.forName("io.papermc.paper.threadedregions.RegionizedServer"); + return true; + } catch (ClassNotFoundException e) { + return false; + } } - } From b30021321a6044415030ca8c0797c47c49e75cc3 Mon Sep 17 00:00:00 2001 From: Warrior <50800980+Warriorrrr@users.noreply.github.com> Date: Fri, 13 Feb 2026 20:12:00 +0100 Subject: [PATCH 23/45] Move economy layers initialization to after providers EconomyLayers relies on the scheduler to be initialized to enable properly --- .../src/main/java/com/earth2me/essentials/Essentials.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Essentials/src/main/java/com/earth2me/essentials/Essentials.java b/Essentials/src/main/java/com/earth2me/essentials/Essentials.java index 89e9c1995d3..3d91cfeb11f 100644 --- a/Essentials/src/main/java/com/earth2me/essentials/Essentials.java +++ b/Essentials/src/main/java/com/earth2me/essentials/Essentials.java @@ -321,9 +321,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); @@ -417,6 +414,10 @@ public void onEnable() { } execTimer.mark("Init(Providers)"); + + EconomyLayers.onEnable(this); + execTimer.mark("Init(EconomyLayers)"); + registerListeners(getServer().getPluginManager()); reload(); From 63dcfa23338f67d08588cfd6a18fc3c5309ce8ed Mon Sep 17 00:00:00 2001 From: JRoy <10731363+JRoy@users.noreply.github.com> Date: Sat, 28 Feb 2026 15:20:36 -0800 Subject: [PATCH 24/45] sure --- .../src/main/java/com/earth2me/essentials/Essentials.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Essentials/src/main/java/com/earth2me/essentials/Essentials.java b/Essentials/src/main/java/com/earth2me/essentials/Essentials.java index 921f218bc53..dd6dbb94e55 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; @@ -49,7 +50,6 @@ 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; @@ -108,10 +108,9 @@ import net.ess3.provider.providers.PrehistoricPotionMetaProvider; import net.essentialsx.api.v2.services.BalanceTop; import net.essentialsx.api.v2.services.mail.MailService; -import org.bukkit.Location; import net.kyori.adventure.platform.bukkit.BukkitAudiences; -import net.kyori.adventure.text.Component; import org.bukkit.Bukkit; +import org.bukkit.Location; import org.bukkit.Server; import org.bukkit.World; import org.bukkit.block.Block; From e7461bfd38ae4e2c5e67c2e01138788350b25076 Mon Sep 17 00:00:00 2001 From: Warrior <50800980+Warriorrrr@users.noreply.github.com> Date: Thu, 16 Apr 2026 10:09:00 +0200 Subject: [PATCH 25/45] Run kill command on entity thread --- .../essentials/commands/Commandkill.java | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) 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 From 853ac1ea4b8acba0c62c535352ce024353b41067 Mon Sep 17 00:00:00 2001 From: Warrior <50800980+Warriorrrr@users.noreply.github.com> Date: Thu, 16 Apr 2026 10:36:27 +0200 Subject: [PATCH 26/45] Fix merge artifact (?) --- .../src/main/java/com/earth2me/essentials/Essentials.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/Essentials/src/main/java/com/earth2me/essentials/Essentials.java b/Essentials/src/main/java/com/earth2me/essentials/Essentials.java index dd6dbb94e55..644910b3f76 100644 --- a/Essentials/src/main/java/com/earth2me/essentials/Essentials.java +++ b/Essentials/src/main/java/com/earth2me/essentials/Essentials.java @@ -108,7 +108,6 @@ import net.ess3.provider.providers.PrehistoricPotionMetaProvider; import net.essentialsx.api.v2.services.BalanceTop; import net.essentialsx.api.v2.services.mail.MailService; -import net.kyori.adventure.platform.bukkit.BukkitAudiences; import org.bukkit.Bukkit; import org.bukkit.Location; import org.bukkit.Server; @@ -612,8 +611,7 @@ public void reload() { } } - AdventureUtil.setEss(this); - bukkitAudience = BukkitAudiences.create(this); + initAdventureFacet(); } private void initAdventureFacet() { From bb0f9f3c5121714f1c774cbbc79b75fae3bc9786 Mon Sep 17 00:00:00 2001 From: Warrior <50800980+Warriorrrr@users.noreply.github.com> Date: Thu, 16 Apr 2026 10:40:29 +0200 Subject: [PATCH 27/45] Fix retrieving tile entity count for gc command --- .../com/earth2me/essentials/Essentials.java | 5 +++++ .../essentials/commands/Commandgc.java | 16 +++------------ .../WorldTileEntityCountProvider.java | 7 +++++++ .../BukkitWorldTileEntityCountProvider.java | 20 +++++++++++++++++++ .../PaperWorldTileEntityCountProvider.java | 13 ++++++++++++ 5 files changed, 48 insertions(+), 13 deletions(-) create mode 100644 providers/BaseProviders/src/main/java/net/ess3/provider/WorldTileEntityCountProvider.java create mode 100644 providers/BaseProviders/src/main/java/net/ess3/provider/providers/BukkitWorldTileEntityCountProvider.java create mode 100644 providers/PaperProvider/src/main/java/net/ess3/provider/providers/PaperWorldTileEntityCountProvider.java diff --git a/Essentials/src/main/java/com/earth2me/essentials/Essentials.java b/Essentials/src/main/java/com/earth2me/essentials/Essentials.java index 644910b3f76..e2a176b9046 100644 --- a/Essentials/src/main/java/com/earth2me/essentials/Essentials.java +++ b/Essentials/src/main/java/com/earth2me/essentials/Essentials.java @@ -77,6 +77,7 @@ 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; @@ -105,6 +106,7 @@ 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; @@ -396,6 +398,9 @@ 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); 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 1a06c6f3925..db9a1ccff59 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/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/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/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(); + } +} From eb9b7dc74ca4ba6c60a2f2ad989e3194e25dfb02 Mon Sep 17 00:00:00 2001 From: Warrior <50800980+Warriorrrr@users.noreply.github.com> Date: Thu, 16 Apr 2026 10:51:32 +0200 Subject: [PATCH 28/45] Modify thunder on global thread --- .../com/earth2me/essentials/commands/Commandthunder.java | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) 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 ce678d0540a..b829956f986 100644 --- a/Essentials/src/main/java/com/earth2me/essentials/commands/Commandthunder.java +++ b/Essentials/src/main/java/com/earth2me/essentials/commands/Commandthunder.java @@ -22,13 +22,15 @@ public void run(final Server server, final User user, final String commandLabel, final World world = user.getWorld(); final boolean setThunder = args[0].equalsIgnoreCase("true"); if (args.length == 1) { - world.setThundering(setThunder); + ess.scheduleGlobalDelayedTask(() -> world.setThundering(setThunder)); user.sendTl("thunder", setThunder ? user.playerTl("enabled") : user.playerTl("disabled")); 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", setThunder ? user.playerTl("enabled") : user.playerTl("disabled"), Integer.parseInt(args[1])); } From 736171a3aef44a10294e55021051af75ccf95d13 Mon Sep 17 00:00:00 2001 From: Warrior <50800980+Warriorrrr@users.noreply.github.com> Date: Thu, 16 Apr 2026 10:51:43 +0200 Subject: [PATCH 29/45] Run smite with entity scheduler --- .../essentials/commands/Commandlightning.java | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) 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..dc3909aa383 100644 --- a/Essentials/src/main/java/com/earth2me/essentials/commands/Commandlightning.java +++ b/Essentials/src/main/java/com/earth2me/essentials/commands/Commandlightning.java @@ -33,15 +33,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); } From 48c8085baa39c7e67f2d7c385a901c2d74e9dc3d Mon Sep 17 00:00:00 2001 From: Claude Sonnet 5 Date: Sun, 5 Jul 2026 17:01:46 +0300 Subject: [PATCH 30/45] Fix ConsoleInjector.java: remove dangling reference to 2.x-deleted rate-limit fields --- .../discord/util/ConsoleInjector.java | 18 ------------------ 1 file changed, 18 deletions(-) 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 a5674457582..a03cff09ed6 100644 --- a/EssentialsDiscord/src/main/java/net/essentialsx/discord/util/ConsoleInjector.java +++ b/EssentialsDiscord/src/main/java/net/essentialsx/discord/util/ConsoleInjector.java @@ -37,24 +37,6 @@ public ConsoleInjector(JDADiscordService jda) { this.jda = jda; ((Logger) LogManager.getRootLogger()).addAppender(this); task = jda.getPlugin().getEss().runTaskTimerAsynchronously(() -> { - // Check to see if we're supposed to be backing off, preform backoff if the case. - if (recentRateLimit.get() < 0) { - if (totalBackoffEvents.get() * 20 >= jda.getSettings().getConsoleSkipDelay() * 60) { - logger.warning("EssXBackoff: Reached console skip delay, attempt to skip"); - jda.getConsoleWebhook().abandonRequests(); - messageQueue.clear(); - totalBackoffEvents.set(0); - recentRateLimit.set(0); - lastRateLimitTime.set(0); - return; - } - - final int backoff = recentRateLimit.incrementAndGet(); - if (jda.isDebug()) { - logger.warning("EssXBackoff: Webhook backoff in progress, skipping queue processing. Resuming in " + Math.abs(backoff) + " cycles."); - } - return; - } final StringBuilder buffer = new StringBuilder(); String curLine; while ((curLine = messageQueue.peek()) != null) { From 83b4afea1681ca963f830c23a029b5ea481e4d96 Mon Sep 17 00:00:00 2001 From: Rawad Date: Sun, 5 Jul 2026 17:11:29 +0300 Subject: [PATCH 31/45] Fix baseline compile errors after 2.x merge RoleSyncManager (EssentialsDiscordLink) declared its scheduled task as BukkitTask, but IEssentials#runTaskTimerAsynchronously on folia-support already returns SchedulingProvider.EssentialsTask. The 2.x merge pulled this class in without updating it to the Folia-side scheduler abstraction, causing a compile error. Switched the field type to SchedulingProvider.EssentialsTask, matching the existing pattern in EssentialsDiscord's DiscordCommandSender. --- .../net/essentialsx/discordlink/rolesync/RoleSyncManager.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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) { From a49694466e0a1f9918b2ebb94e884c0f990979f7 Mon Sep 17 00:00:00 2001 From: Claude Sonnet 5 Date: Mon, 6 Jul 2026 10:30:44 +0300 Subject: [PATCH 32/45] Disable Discord integration on Folia EssentialsDiscord's JDA/netty-thread interaction bridge (JDA gateway thread -> scheduleGlobalDelayedTask -> Bukkit.dispatchCommand) is not Folia-safe and was left broken by the prior refactor/folia WIP. Rather than attempt a fix, disable both EssentialsDiscord and EssentialsDiscordLink outright on Folia with a clear log message, and correct their plugin.yml folia-supported flags to false to match. --- .../main/java/net/essentialsx/discord/EssentialsDiscord.java | 5 +++++ EssentialsDiscord/src/main/resources/plugin.yml | 2 +- .../net/essentialsx/discordlink/EssentialsDiscordLink.java | 5 +++++ EssentialsDiscordLink/src/main/resources/plugin.yml | 2 +- 4 files changed, 12 insertions(+), 2 deletions(-) diff --git a/EssentialsDiscord/src/main/java/net/essentialsx/discord/EssentialsDiscord.java b/EssentialsDiscord/src/main/java/net/essentialsx/discord/EssentialsDiscord.java index 61c54e56cf0..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()) { diff --git a/EssentialsDiscord/src/main/resources/plugin.yml b/EssentialsDiscord/src/main/resources/plugin.yml index d903782d36c..de0c5a6c310 100644 --- a/EssentialsDiscord/src/main/resources/plugin.yml +++ b/EssentialsDiscord/src/main/resources/plugin.yml @@ -8,7 +8,7 @@ authors: [mdcfe, JRoy, pop4959, Glare] depend: [Essentials] softdepend: [EssentialsChat, PlaceholderAPI] api-version: 1.13 -folia-supported: true +folia-supported: false commands: discordbroadcast: description: Broadcasts a message to the specified Discord channel. 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/resources/plugin.yml b/EssentialsDiscordLink/src/main/resources/plugin.yml index 2aa180da0d2..b124d16fe56 100644 --- a/EssentialsDiscordLink/src/main/resources/plugin.yml +++ b/EssentialsDiscordLink/src/main/resources/plugin.yml @@ -7,7 +7,7 @@ description: EssentialsX Discord addon which allows you link your Minecraft and authors: [JRoy] depend: [EssentialsDiscord] api-version: 1.13 -folia-supported: true +folia-supported: false commands: link: description: Generates a code to link your Minecraft account to Discord. From db1bbd9daa8e9b07b5dcfed846716cadcd761736 Mon Sep 17 00:00:00 2001 From: Claude Sonnet 5 Date: Mon, 6 Jul 2026 10:31:23 +0300 Subject: [PATCH 33/45] Disable XMPP integration on Folia EssentialsXMPP is an optional external chat-bridge module, same category as Discord, and was not part of this port's core scope. Disable it on Folia with the same VersionUtil.FOLIA guard pattern used for EssentialsDiscord/EssentialsDiscordLink, and declare folia-supported: false explicitly. --- .../java/com/earth2me/essentials/xmpp/EssentialsXMPP.java | 5 +++++ EssentialsXMPP/src/main/resources/plugin.yml | 1 + 2 files changed, 6 insertions(+) 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. From 37b092dc0bab7a7c80af4e30ad3534ea184858ef Mon Sep 17 00:00:00 2001 From: Claude Sonnet 5 Date: Mon, 6 Jul 2026 10:32:17 +0300 Subject: [PATCH 34/45] Fix /nyan command's Folia-unsafe BukkitRunnable TuneRunnable extended BukkitRunnable and was started via runTaskTimer(ess, 20, 2), a direct legacy Bukkit scheduler call that throws at runtime on Folia. Replaced with a plain TuneTicker driven by ess.scheduleGlobalRepeatingTask, and dispatched each online player's playSound call through ess.scheduleEntityDelayedTask so per-player work runs on that player's own region thread rather than the global thread that drives the tune. --- .../commands/essentials/NyanCommand.java | 42 +++++++++++-------- 1 file changed, 25 insertions(+), 17 deletions(-) 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; } } } From db417229a605ea0fb224367a5ca4d5fc79344e83 Mon Sep 17 00:00:00 2001 From: Claude Sonnet 5 Date: Mon, 6 Jul 2026 10:34:41 +0300 Subject: [PATCH 35/45] Always dispatch command-map reload mutations onto the global thread Settings.reloadConfig() only wrapped its command-map mutations (_addAlternativeCommand, syncCommandsProvider.syncCommands) in scheduleGlobalDelayedTask during the plugin's first two startup reloads; every later /essentials reload ran them synchronously on whichever thread invoked the command, which on Folia is the executor's own region thread rather than the global thread these mutations require. Removed the reloadCount special-casing so both always schedule onto the global thread regardless of when the reload happens. A third, unrelated reloadCount.get() < 2 guard remains at line 847 (startup-only plugin re-registration logic that already unconditionally dispatches via scheduleGlobalDelayedTask within that branch) and was intentionally left untouched, as it is not the same-thread-unsafe defect this commit addresses. --- .../main/java/com/earth2me/essentials/Settings.java | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/Essentials/src/main/java/com/earth2me/essentials/Settings.java b/Essentials/src/main/java/com/earth2me/essentials/Settings.java index 891d32fa1da..a7322d74249 100644 --- a/Essentials/src/main/java/com/earth2me/essentials/Settings.java +++ b/Essentials/src/main/java/com/earth2me/essentials/Settings.java @@ -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.scheduleGlobalDelayedTask(() -> _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.scheduleGlobalDelayedTask(syncCommandsProvider::syncCommands); - } else { - syncCommandsProvider.syncCommands(); - } + ess.scheduleGlobalDelayedTask(syncCommandsProvider::syncCommands); } } From 471abd46d7acfd82f5c8bdb1c43fab2c2c9f13ff Mon Sep 17 00:00:00 2001 From: Claude Sonnet 5 Date: Mon, 6 Jul 2026 10:35:27 +0300 Subject: [PATCH 36/45] Make Backup's shared fields volatile for cross-thread visibility Backup's own repeating task and its async-completion callback both run on Folia's single global region thread, so they don't race with each other. But Essentials.java's onDisable() reads getBackup().getTaskLock() during shutdown, which isn't guaranteed to run on that same thread. Plain fields gave no visibility guarantee for that read; marking running/task/active/taskLock volatile fixes the gap without needing to touch the scheduling design. --- .../src/main/java/com/earth2me/essentials/Backup.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Essentials/src/main/java/com/earth2me/essentials/Backup.java b/Essentials/src/main/java/com/earth2me/essentials/Backup.java index 8ea06ded7ca..883f16f88fe 100644 --- a/Essentials/src/main/java/com/earth2me/essentials/Backup.java +++ b/Essentials/src/main/java/com/earth2me/essentials/Backup.java @@ -18,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 SchedulingProvider.EssentialsTask task = null; - 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; From 70c8cecb69c278bb8f25d47123aa254463d7c814 Mon Sep 17 00:00:00 2001 From: Claude Sonnet 5 Date: Mon, 6 Jul 2026 10:52:38 +0300 Subject: [PATCH 37/45] Dispatch /antioch's TNT spawn onto the target location's region thread LocationUtil.getTarget ray-traces up to 300 blocks to find where the player is looking, then the command spawned a TNTPrimed entity at that location directly on the command-executing thread. A ray-traced point 300 blocks away is not guaranteed to be owned by that thread's current Folia region. Wrapped the spawn call in ess.scheduleLocationDelayedTask(loc, ...), the same primitive already used identically in Commandweather.java for a location-targeted world mutation. --- .../java/com/earth2me/essentials/commands/Commandantioch.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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)); } } From 8bf901fef9db0a1ecce76fa13eedd38e4fe01358 Mon Sep 17 00:00:00 2001 From: Claude Sonnet 5 Date: Mon, 6 Jul 2026 10:54:57 +0300 Subject: [PATCH 38/45] Fix /lightning's self-strike branch to dispatch onto the strike's own region The self-strike branch ran strikeLightning directly on the command thread against a block ray-traced up to 600 blocks away, with no dispatch at all -- inconsistent with the 'others' branch three lines below, which correctly wraps its strike in ess.scheduleEntityDelayedTask. Entity-dispatch isn't the right analog here though: the 'others' branch works because the target player's entity thread and the strike location coincide, but the self-strike target is an arbitrary distant block. Used ess.scheduleLocationDelayedTask(target, ...) instead, targeting the resolved strike location directly -- the same location-based primitive already used in Commandweather.java and Commandantioch.java. --- Essentials/src/main/java/com/earth2me/essentials/User.java | 4 ++-- .../com/earth2me/essentials/commands/Commandlightning.java | 4 +++- .../earth2me/essentials/config/holders/UserConfigHolder.java | 4 ++-- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/Essentials/src/main/java/com/earth2me/essentials/User.java b/Essentials/src/main/java/com/earth2me/essentials/User.java index 946f681409f..bb05c78cf47 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; 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 dc3909aa383..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(); 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; From 8bd35e22fc6761742e1b5afa4d98b85b65644a68 Mon Sep 17 00:00:00 2001 From: Claude Sonnet 5 Date: Mon, 6 Jul 2026 10:57:53 +0300 Subject: [PATCH 39/45] Dispatch /spawner's block read+mutation onto the target's region thread Commandspawner ray-traced up to 300 blocks, then read and mutated the targeted spawner block's state directly on the command thread -- a Folia thread-affinity violation for any target not owned by that thread's current region. Wrapped the block-type check and the mutation in ess.ensureRegion(target, ...), the same blocking region-thread rendezvous already used via ensureEntity in AsyncTeleport.java. Since Runnable can't declare checked exceptions, capture the resulting TranslatableException in an AtomicReference and rethrow it on the calling thread after ensureRegion returns, so existing error-message behavior for players/console is preserved. As a side effect the block-type check now happens atomically with the mutation instead of as an earlier separate read, which changes error message priority when both the target block and the mob name are invalid (mob-name error now takes precedence) -- a minor UX ordering change, not a functional regression. --- .../essentials/commands/Commandspawner.java | 44 +++++++++++-------- 1 file changed, 26 insertions(+), 18 deletions(-) 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); - } } From 3610655c7ebfd870623beb141559c1b0696793cd Mon Sep 17 00:00:00 2001 From: Claude Sonnet 5 Date: Mon, 6 Jul 2026 11:00:04 +0300 Subject: [PATCH 40/45] Dispatch EssentialsTimer's AFK-kick and notify loop through scheduleEntityDelayedTask checkActivity(), called by EssentialsTimer from Folia's global thread, called this.getBase().kickPlayer(...) directly and looped over every online user calling user.sendTl(...) directly - both are entity mutations issued from the wrong thread on Folia. Wrap both in ess.scheduleEntityDelayedTask so each runs on its target player's own region thread, matching the same pattern Phase 2A's NyanCommand fix and the existing Commandlightning.java already use for per-player side effects inside a loop over online players. --- Essentials/src/main/java/com/earth2me/essentials/User.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Essentials/src/main/java/com/earth2me/essentials/User.java b/Essentials/src/main/java/com/earth2me/essentials/User.java index bb05c78cf47..53043e2ed14 100644 --- a/Essentials/src/main/java/com/earth2me/essentials/User.java +++ b/Essentials/src/main/java/com/earth2me/essentials/User.java @@ -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 { From 75a2666a9eed600d5dec8bd8a44a7ef225e8fc8d Mon Sep 17 00:00:00 2001 From: Claude Sonnet 5 Date: Mon, 6 Jul 2026 11:00:15 +0300 Subject: [PATCH 41/45] Resolve RandomTeleport's FIXME: dispatch Y-lookup onto the region thread calculateRandomLocation carried its own FIXME acknowledging that its PaperLib.getChunkAtAsync(...).thenApply(...) callback read/wrote block-derived state (getNetherYAt, getHighestBlockYAt) with no guarantee of running on the target location's owning region thread -- backing every /tpr invocation, not just cache misses. Replaced the thenApply transform with an explicit CompletableFuture completed from inside ess.scheduleLocationDelayedTask(location, ...), the same primitive used in Commandweather.java and Commandantioch.java. As a verified side effect, attemptRandomLocation's own unguarded location.getBlock().getBiome() read (via isExcludedBiome, not previously flagged) now also runs on the correct region thread, since its thenAccept continuation was already chained onto this future before completion and CompletableFuture.complete() runs already-attached synchronous continuations on the completing thread. --- .../earth2me/essentials/RandomTeleport.java | 21 ++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/Essentials/src/main/java/com/earth2me/essentials/RandomTeleport.java b/Essentials/src/main/java/com/earth2me/essentials/RandomTeleport.java index 1b169896809..c88425bebd2 100644 --- a/Essentials/src/main/java/com/earth2me/essentials/RandomTeleport.java +++ b/Essentials/src/main/java/com/earth2me/essentials/RandomTeleport.java @@ -213,14 +213,21 @@ private CompletableFuture calculateRandomLocation(final Location cente 360 * RANDOM.nextFloat() - 180, 0 ); - return PaperLib.getChunkAtAsync(location).thenApply(chunk -> { // FIXME: use thenApplyAsync + an Executor that runs tasks on the correct region thread to be fully safe. - if (World.Environment.NETHER.equals(center.getWorld().getEnvironment())) { - location.setY(getNetherYAt(location)); - } else { - location.setY(center.getWorld().getHighestBlockYAt(location) + HIGHEST_BLOCK_Y_OFFSET); - } - return location; + final CompletableFuture future = new CompletableFuture<>(); + PaperLib.getChunkAtAsync(location).thenAccept(chunk -> { + 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; } // Returns an appropriate elevation for a given location in the nether, or MIN_VALUE if none is found From 3d0877f64c26c7ca1149cba7b8dbc1f05e757b5d Mon Sep 17 00:00:00 2001 From: Claude Sonnet 5 Date: Mon, 6 Jul 2026 11:03:07 +0300 Subject: [PATCH 42/45] Dispatch AsyncTeleport.nowAsync's safety check + teleport onto the destination's region thread nowAsync backs every teleport-driven command in the plugin (/tp, /tpa, /home, /warp, /spawn, /back, and via RandomTeleport /tpr). Its PaperLib.getChunkAtAsync(...).thenAccept(...) callback read block state via LocationUtil.isBlockUnsafeForUser and called PaperLib.teleportAsync with no guarantee the callback ran on the destination's owning Folia region thread. Wrapped the callback body in ess.scheduleLocationDelayedTask(targetLoc, ...), the same primitive used in Commandweather.java, Commandantioch.java, Commandlightning.java, and RandomTeleport.java earlier in this plan. LocationUtil.java itself required no change -- its isBlockUnsafe/isBlockAboveAir/isBlockDamaging methods are plain synchronous block readers with no scheduling logic; the bug was entirely in this call site not dispatching before invoking them. --- .../earth2me/essentials/AsyncTeleport.java | 50 ++++++++++--------- 1 file changed, 26 insertions(+), 24 deletions(-) diff --git a/Essentials/src/main/java/com/earth2me/essentials/AsyncTeleport.java b/Essentials/src/main/java/com/earth2me/essentials/AsyncTeleport.java index 0c5cd69db1f..43e98149b5d 100644 --- a/Essentials/src/main/java/com/earth2me/essentials/AsyncTeleport.java +++ b/Essentials/src/main/java/com/earth2me/essentials/AsyncTeleport.java @@ -173,36 +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()) { + 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 { 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; + 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); } - } else { - future.completeExceptionally(new TranslatableException("unsafeTeleportDestination", loc.getWorld().getName(), loc.getBlockX(), loc.getBlockY(), loc.getBlockZ())); - return; - } - } else { - 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); } - } - future.complete(true); + future.complete(true); + }); }).exceptionally(th -> { future.completeExceptionally(th); return null; From 2baadbcd706d4a2a4d6085f0f5d9a4ff6923fb3d Mon Sep 17 00:00:00 2001 From: Claude Sonnet 5 Date: Mon, 6 Jul 2026 11:03:50 +0300 Subject: [PATCH 43/45] Restore real TPS calculation for /tps and the {TPS} placeholder Commandgc.java and KeywordReplacer.java both hardcoded tps = 20d with a TODO since commit 0e4bab24c deleted EssentialsTimer.getAverageTPS() and its supporting history/lastPoll state during the original Folia port. Restore the original self-timed measurement of EssentialsTimer's own global-repeating-task firing interval rather than building new per-region MSPT tracking: the per-region getTPS(Location) API that would require doesn't exist in this project's pinned folia-api:1.20.1-R0.1-SNAPSHOT dependency (confirmed via javap against that exact jar), and bumping that dependency is a separate, riskier change outside this plan's scope. The restored history buffer is now read cross-thread (by /tps and by chat placeholder resolution) where the original single-threaded plugin never had to worry about that, so recordTick()/getAverageTPS() are both synchronized to guard it. This reports the global region's own tick health, not a true whole-server aggregate across every per-player region - a known, documented limitation, not a hidden one. --- .../earth2me/essentials/EssentialsTimer.java | 46 +++++++++++++++++++ .../essentials/commands/Commandgc.java | 2 +- .../textreader/KeywordReplacer.java | 2 +- 3 files changed, 48 insertions(+), 2 deletions(-) diff --git a/Essentials/src/main/java/com/earth2me/essentials/EssentialsTimer.java b/Essentials/src/main/java/com/earth2me/essentials/EssentialsTimer.java index edab1924bea..19b12ab0985 100644 --- a/Essentials/src/main/java/com/earth2me/essentials/EssentialsTimer.java +++ b/Essentials/src/main/java/com/earth2me/essentials/EssentialsTimer.java @@ -5,6 +5,7 @@ import java.util.HashSet; import java.util.Iterator; +import java.util.LinkedList; import java.util.Set; import java.util.UUID; import java.util.logging.Level; @@ -12,18 +13,24 @@ 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 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(); private int skip1 = 0; private int skip2 = 0; EssentialsTimer(final IEssentials ess) { this.ess = ess; + history.add(20d); } @Override public void run() { final long startTime = System.nanoTime(); final long currentTime = System.currentTimeMillis(); + recordTick(startTime); int count = 0; onlineUsers.clear(); @@ -81,4 +88,43 @@ public void run() { user.resetInvulnerabilityAfterTeleport(); } } + + /** + * 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) { + avg += f; + } + } + return avg / history.size(); + } } 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 db9a1ccff59..78444d2fbdb 100644 --- a/Essentials/src/main/java/com/earth2me/essentials/commands/Commandgc.java +++ b/Essentials/src/main/java/com/earth2me/essentials/commands/Commandgc.java @@ -18,7 +18,7 @@ public Commandgc() { @Override protected void run(final Server server, final CommandSource sender, final String commandLabel, final String[] args) throws Exception { - final double tps = 20d; //TODO + final double tps = ess.getTimer().getAverageTPS(); final ChatColor color; if (tps >= 18.0) { color = ChatColor.GREEN; diff --git a/Essentials/src/main/java/com/earth2me/essentials/textreader/KeywordReplacer.java b/Essentials/src/main/java/com/earth2me/essentials/textreader/KeywordReplacer.java index b70ae685a47..065e8eab1e6 100644 --- a/Essentials/src/main/java/com/earth2me/essentials/textreader/KeywordReplacer.java +++ b/Essentials/src/main/java/com/earth2me/essentials/textreader/KeywordReplacer.java @@ -346,7 +346,7 @@ private String replaceLine(String line, final String fullMatch, final String[] m } break; case TPS: - replacer = NumberUtil.formatDouble(20d); //todo + replacer = NumberUtil.formatDouble(ess.getTimer().getAverageTPS()); break; case UPTIME: replacer = DateUtil.formatDateDiff(ManagementFactory.getRuntimeMXBean().getStartTime()); From be8a8b90d1599c92b0d4c723cfcc5324d21cb88e Mon Sep 17 00:00:00 2001 From: Claude Sonnet 5 Date: Mon, 6 Jul 2026 11:42:39 +0300 Subject: [PATCH 44/45] Fix onDisable() crash on Folia: legacy scheduler.cancelTasks() is unsupported Discovered via live testing on a real Folia 1.21.8 server (not caught by any compile-check): Essentials.onDisable() called getServer().getScheduler() .cancelTasks(this) directly, which throws UnsupportedOperationException on Folia's CraftScheduler stub every time the plugin disables (including on every server stop/reload). Reproduced live, confirmed via stack trace at Essentials.java:606 -> CraftScheduler.cancelTasks -> CraftScheduler.handle. Added SchedulingProvider.cancelAllTasks(), implemented per-platform: BukkitSchedulingProvider delegates to the same legacy call (unchanged behavior off Folia); FoliaSchedulingProvider uses the Folia-safe GlobalRegionScheduler.cancelTasks(plugin) + AsyncScheduler.cancelTasks(plugin) (both verified to exist on the actual pinned folia-api jar via javap). Region-owned entity/location tasks don't need a separate cancel, since RegionShutdownThread's own halt sequence already tears those down during plugin disable. Essentials.java now calls the abstraction instead of the raw legacy scheduler. --- .../main/java/com/earth2me/essentials/Essentials.java | 2 +- .../java/net/ess3/provider/SchedulingProvider.java | 2 ++ .../provider/providers/BukkitSchedulingProvider.java | 5 +++++ .../provider/providers/FoliaSchedulingProvider.java | 10 ++++++++++ 4 files changed, 18 insertions(+), 1 deletion(-) diff --git a/Essentials/src/main/java/com/earth2me/essentials/Essentials.java b/Essentials/src/main/java/com/earth2me/essentials/Essentials.java index 8536c268b8d..3bcb2de6296 100644 --- a/Essentials/src/main/java/com/earth2me/essentials/Essentials.java +++ b/Essentials/src/main/java/com/earth2me/essentials/Essentials.java @@ -603,7 +603,7 @@ public void onDisable() { EssentialsConfiguration.shutdownExecutor(); PasteUtil.shutdownExecutor(); - getServer().getScheduler().cancelTasks(this); + provider(SchedulingProvider.class).cancelAllTasks(); HandlerList.unregisterAll(this); } diff --git a/providers/BaseProviders/src/main/java/net/ess3/provider/SchedulingProvider.java b/providers/BaseProviders/src/main/java/net/ess3/provider/SchedulingProvider.java index 8206a16f213..e5123a93291 100644 --- a/providers/BaseProviders/src/main/java/net/ess3/provider/SchedulingProvider.java +++ b/providers/BaseProviders/src/main/java/net/ess3/provider/SchedulingProvider.java @@ -34,6 +34,8 @@ public interface SchedulingProvider extends Provider { 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/providers/BukkitSchedulingProvider.java b/providers/BaseProviders/src/main/java/net/ess3/provider/providers/BukkitSchedulingProvider.java index f24928c918b..4a038339ce5 100644 --- a/providers/BaseProviders/src/main/java/net/ess3/provider/providers/BukkitSchedulingProvider.java +++ b/providers/BaseProviders/src/main/java/net/ess3/provider/providers/BukkitSchedulingProvider.java @@ -101,4 +101,9 @@ public EssentialsTask runAsyncTaskRepeating(Runnable runnable, long delay, long 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/FoliaProvider/src/main/java/net/ess3/provider/providers/FoliaSchedulingProvider.java b/providers/FoliaProvider/src/main/java/net/ess3/provider/providers/FoliaSchedulingProvider.java index 7adf2c0c80c..86cde8e2ba9 100644 --- a/providers/FoliaProvider/src/main/java/net/ess3/provider/providers/FoliaSchedulingProvider.java +++ b/providers/FoliaProvider/src/main/java/net/ess3/provider/providers/FoliaSchedulingProvider.java @@ -122,6 +122,16 @@ public EssentialsTask runAsyncTaskRepeating(Runnable runnable, long delay, long 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 { From cf50889b778258eecd5c5a28e5f15232588d5a95 Mon Sep 17 00:00:00 2001 From: Claude Sonnet 5 Date: Wed, 8 Jul 2026 00:21:17 +0300 Subject: [PATCH 45/45] Fix PaperLib misdetecting Minecraft 26.x, causing Folia teleport/bed-spawn crashes PaperLib 1.0.6's version regex only matches a single-digit MC major version, so it silently fails to detect anything on the new 26.x scheme and falls back to legacy synchronous teleport/bed-spawn/chunk handlers. Those violate Folia's region-threading model, causing crashes on join (bed spawn lookup) and teleport commands. Installs a custom PaperLib Environment that reselects the correct handlers using VersionUtil's own (already 26.x-aware) version parsing instead. Co-Authored-By: Claude Sonnet 5 --- .../com/earth2me/essentials/Essentials.java | 5 +++ .../utils/ModernPaperEnvironment.java | 34 +++++++++++++++++++ 2 files changed, 39 insertions(+) create mode 100644 Essentials/src/main/java/com/earth2me/essentials/utils/ModernPaperEnvironment.java diff --git a/Essentials/src/main/java/com/earth2me/essentials/Essentials.java b/Essentials/src/main/java/com/earth2me/essentials/Essentials.java index 3bcb2de6296..e4e230a7942 100644 --- a/Essentials/src/main/java/com/earth2me/essentials/Essentials.java +++ b/Essentials/src/main/java/com/earth2me/essentials/Essentials.java @@ -47,6 +47,7 @@ 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; @@ -268,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")) { 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. + } + } + } +}