diff --git a/Dynmap-3.9.jar b/Dynmap-3.9.jar new file mode 100644 index 000000000..bd004525d Binary files /dev/null and b/Dynmap-3.9.jar differ diff --git a/spigot/src/main/java/org/dynmap/bukkit/DynmapPlugin.java b/spigot/src/main/java/org/dynmap/bukkit/DynmapPlugin.java index 48c8b4fe1..06b3b7319 100644 --- a/spigot/src/main/java/org/dynmap/bukkit/DynmapPlugin.java +++ b/spigot/src/main/java/org/dynmap/bukkit/DynmapPlugin.java @@ -262,7 +262,15 @@ public int isSignAt(String wname, int x, int y, int z) { @Override public void scheduleServerTask(Runnable run, long delay) { - getServer().getScheduler().scheduleSyncDelayedTask(DynmapPlugin.this, run, delay); + if (FoliaUtil.isFolia()) { + if (delay <= 0) { + FoliaUtil.runGlobalSync(DynmapPlugin.this, run); + } else { + FoliaUtil.runGlobalDelayed(DynmapPlugin.this, run, delay); + } + } else { + getServer().getScheduler().scheduleSyncDelayedTask(DynmapPlugin.this, run, delay); + } } @Override public DynmapPlayer[] getOnlinePlayers() { @@ -292,8 +300,12 @@ public Set getIPBans() { } @Override public Future callSyncMethod(Callable task) { - if(DynmapPlugin.this.isEnabled()) + if(DynmapPlugin.this.isEnabled()) { + if (FoliaUtil.isFolia()) { + return FoliaUtil.callGlobalSync(DynmapPlugin.this, task); + } return getServer().getScheduler().callSyncMethod(DynmapPlugin.this, task); + } else return null; } @@ -372,14 +384,25 @@ public void onPlayerBedLeave(PlayerBedLeaveEvent evt) { public void onPlayerChat(AsyncPlayerChatEvent evt) { final Player p = evt.getPlayer(); final String msg = evt.getMessage(); - getServer().getScheduler().scheduleSyncDelayedTask(DynmapPlugin.this, new Runnable() { - public void run() { - DynmapPlayer dp = null; - if(p != null) - dp = new BukkitPlayer(p); - core.listenerManager.processChatEvent(EventType.PLAYER_CHAT, dp, msg); - } - }); + if (FoliaUtil.isFolia()) { + FoliaUtil.runGlobalSync(DynmapPlugin.this, new Runnable() { + public void run() { + DynmapPlayer dp = null; + if(p != null) + dp = new BukkitPlayer(p); + core.listenerManager.processChatEvent(EventType.PLAYER_CHAT, dp, msg); + } + }); + } else { + getServer().getScheduler().scheduleSyncDelayedTask(DynmapPlugin.this, new Runnable() { + public void run() { + DynmapPlayer dp = null; + if(p != null) + dp = new BukkitPlayer(p); + core.listenerManager.processChatEvent(EventType.PLAYER_CHAT, dp, msg); + } + }); + } } }, DynmapPlugin.this); break; @@ -1036,11 +1059,15 @@ public void onPluginEnabled(PluginEnableEvent evt) { tps = 20.0; perTickLimit = core.getMaxTickUseMS() * 1000000; - getServer().getScheduler().scheduleSyncRepeatingTask(this, new Runnable() { - public void run() { - processTick(); - } - }, 1, 1); + if (FoliaUtil.isFolia()) { + FoliaUtil.runGlobalTimer(this, t -> processTick(), 1, 1); + } else { + getServer().getScheduler().scheduleSyncRepeatingTask(this, new Runnable() { + public void run() { + processTick(); + } + }, 1, 1); + } } private boolean readyToEnable() { @@ -1273,12 +1300,21 @@ private void registerPlayerLoginListener() { public void onPlayerJoin(PlayerJoinEvent evt) { final DynmapPlayer dp = new BukkitPlayer(evt.getPlayer()); // Give other handlers a change to prep player (nicknames and such from Essentials) - getServer().getScheduler().scheduleSyncDelayedTask(DynmapPlugin.this, new Runnable() { - @Override - public void run() { - core.listenerManager.processPlayerEvent(EventType.PLAYER_JOIN, dp); - } - }, 2); + if (FoliaUtil.isFolia()) { + FoliaUtil.runGlobalDelayed(DynmapPlugin.this, new Runnable() { + @Override + public void run() { + core.listenerManager.processPlayerEvent(EventType.PLAYER_JOIN, dp); + } + }, 2); + } else { + getServer().getScheduler().scheduleSyncDelayedTask(DynmapPlugin.this, new Runnable() { + @Override + public void run() { + core.listenerManager.processPlayerEvent(EventType.PLAYER_JOIN, dp); + } + }, 2); + } } @EventHandler(priority=EventPriority.MONITOR, ignoreCancelled=true) public void onPlayerQuit(PlayerQuitEvent evt) { @@ -1316,7 +1352,11 @@ public void startIfNeeded() { if((blocks_to_check == null) && (blocks_to_check_accum.isEmpty() == false)) { /* More pending? */ blocks_to_check = blocks_to_check_accum; blocks_to_check_accum = new LinkedList(); - getServer().getScheduler().scheduleSyncDelayedTask(DynmapPlugin.this, this, 10); + if (FoliaUtil.isFolia()) { + FoliaUtil.runGlobalDelayed(DynmapPlugin.this, this, 10); + } else { + getServer().getScheduler().scheduleSyncDelayedTask(DynmapPlugin.this, this, 10); + } } } } diff --git a/spigot/src/main/java/org/dynmap/bukkit/FoliaUtil.java b/spigot/src/main/java/org/dynmap/bukkit/FoliaUtil.java new file mode 100644 index 000000000..6a44d600d --- /dev/null +++ b/spigot/src/main/java/org/dynmap/bukkit/FoliaUtil.java @@ -0,0 +1,114 @@ +package org.dynmap.bukkit; + +import org.bukkit.Bukkit; +import org.bukkit.plugin.Plugin; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.function.Consumer; + +/** + * Folia compatibility layer — pure reflection, no Folia API JAR needed at compile time. + * Dynmap compiles against old Spigot API; Folia scheduler methods are called at runtime only. + */ +public final class FoliaUtil { + + private static final boolean FOLIA; + + static { + boolean folia; + try { + Class.forName("io.papermc.paper.threadedregions.RegionizedServer"); + folia = true; + } catch (ClassNotFoundException ignored) { + folia = false; + } + FOLIA = folia; + } + + private FoliaUtil() {} + + public static boolean isFolia() { + return FOLIA; + } + + /** Runs {@code task} on the global region scheduler immediately. */ + public static void runGlobalSync(Plugin plugin, Runnable task) { + try { + Object s = globalScheduler(); + s.getClass().getMethod("run", Plugin.class, Consumer.class) + .invoke(s, plugin, (Consumer) t -> task.run()); + } catch (Exception e) { + e.printStackTrace(); + } + } + + /** Runs {@code task} on the global region scheduler after {@code delayTicks} ticks. */ + public static void runGlobalDelayed(Plugin plugin, Runnable task, long delayTicks) { + try { + Object s = globalScheduler(); + s.getClass().getMethod("runDelayed", Plugin.class, Consumer.class, long.class) + .invoke(s, plugin, (Consumer) t -> task.run(), delayTicks); + } catch (Exception e) { + e.printStackTrace(); + } + } + + /** Runs a repeating task on the global region scheduler; returns the opaque task handle. */ + public static Object runGlobalTimer(Plugin plugin, Consumer consumer, long initialDelay, long period) { + try { + Object s = globalScheduler(); + return s.getClass() + .getMethod("runAtFixedRate", Plugin.class, Consumer.class, long.class, long.class) + .invoke(s, plugin, consumer, initialDelay, period); + } catch (Exception e) { + e.printStackTrace(); + return null; + } + } + + /** + * Submits {@code task} to the global region scheduler and returns a Future for the result. + * Replaces {@code BukkitScheduler.callSyncMethod()} on Folia servers. + */ + public static Future callGlobalSync(Plugin plugin, java.util.concurrent.Callable task) { + CompletableFuture future = new CompletableFuture<>(); + runGlobalSync(plugin, () -> { + try { + future.complete(task.call()); + } catch (Exception e) { + future.completeExceptionally(e); + } + }); + return future; + } + + /** Cancels all tasks on both global region and async schedulers for the given plugin. */ + public static void cancelAllTasks(Plugin plugin) { + try { + Object gs = globalScheduler(); + gs.getClass().getMethod("cancelTasks", Plugin.class).invoke(gs, plugin); + } catch (Exception ignored) {} + try { + Object as = asyncScheduler(); + as.getClass().getMethod("cancelTasks", Plugin.class).invoke(as, plugin); + } catch (Exception ignored) {} + } + + /** Cancels a single opaque task handle returned by {@link #runGlobalTimer}. */ + public static void cancelTask(Object task) { + if (task == null) return; + try { + task.getClass().getMethod("cancel").invoke(task); + } catch (Exception ignored) {} + } + + private static Object globalScheduler() throws Exception { + return Bukkit.getServer().getClass().getMethod("getGlobalRegionScheduler").invoke(Bukkit.getServer()); + } + + private static Object asyncScheduler() throws Exception { + return Bukkit.getServer().getClass().getMethod("getAsyncScheduler").invoke(Bukkit.getServer()); + } +} diff --git a/spigot/src/main/resources/plugin.yml b/spigot/src/main/resources/plugin.yml index 8efeee429..131a5cb69 100644 --- a/spigot/src/main/resources/plugin.yml +++ b/spigot/src/main/resources/plugin.yml @@ -2,6 +2,7 @@ name: dynmap main: org.dynmap.bukkit.DynmapPlugin version: "${version}-${buildnumber}" api-version: 1.13 +folia-supported: true authors: [mikeprimm] website: "https://www.reddit.com/r/Dynmap/" softdepend: [ Permissions, PermissionEx, bPermissions, PermissionsBukkit, GroupManager, LuckPerms, Vault, SkinsRestorer ]