diff --git a/src/oneblock/CommandHandler.java b/src/oneblock/CommandHandler.java index d5f645a..fe7af1f 100644 --- a/src/oneblock/CommandHandler.java +++ b/src/oneblock/CommandHandler.java @@ -26,6 +26,7 @@ import oneblock.gui.GUI; import oneblock.invitation.Guest; import oneblock.invitation.Invitation; +import oneblock.utils.Scheduler; import oneblock.utils.Utils; import oneblock.worldguard.OBWorldGuard; @@ -76,15 +77,19 @@ public boolean onCommand(CommandSender sender, Command cmd, String label, String if (player == null) return false; UUID uuid = player.getUniqueId(); int X_pl = 0, Z_pl = 0; + Runnable newIsland = null; int plID = PlayerInfo.getId(uuid); if (plID == -1) { PlayerInfo inf = new PlayerInfo(uuid); plID = PlayerInfo.getFreeId(useEmptyIslands); int result[] = plugin.getIslandCoordinates(plID); X_pl = result[0]; Z_pl = result[1]; - if (plID != PlayerInfo.size()) - Island.clear(getWorld(), X_pl, getY(), Z_pl, getOffset()/4); - Island.place(getWorld(), X_pl, getY(), Z_pl); + final boolean reused = plID != PlayerInfo.size(); + final int X_isl = X_pl, Z_isl = Z_pl; + newIsland = () -> { + if (reused) Island.clear(getWorld(), X_isl, getY(), Z_isl, getOffset()/4); + Island.place(getWorld(), X_isl, getY(), Z_isl); + }; plugin.worldGuard.createRegion(uuid, X_pl, Z_pl, getOffset(), plID); PlayerInfo.set(plID, inf); if (!superlegacy) @@ -96,7 +101,13 @@ public boolean onCommand(CommandSender sender, Command cmd, String label, String } if (!plugin.enabled) plugin.runMainTask(); if (progress_bar) PlayerInfo.get(plID).bar.setVisible(true); - player.teleport(new Location(getWorld(), X_pl + 0.5, getY() + 1.2013, Z_pl + 0.5)); + final Location spawn = new Location(getWorld(), X_pl + 0.5, getY() + 1.2013, Z_pl + 0.5); + if (newIsland == null) Scheduler.teleport(player, spawn); + else { + // Build the island on its own region thread (Folia), then bring the player over. + final Runnable build = newIsland; + Scheduler.runAt(spawn, () -> { build.run(); Scheduler.teleport(player, spawn); }); + } if (OBWorldGuard.isEnabled()) plugin.worldGuard.addMember(uuid, plID); return true; } @@ -108,7 +119,7 @@ public boolean onCommand(CommandSender sender, Command cmd, String label, String sender.sendMessage(Messages.leave_not_set); return true; } - player.teleport(plugin.getLeave()); + Scheduler.teleport(player, plugin.getLeave()); return true; } case ("v"): @@ -141,7 +152,7 @@ public boolean onCommand(CommandSender sender, Command cmd, String label, String final int X_pl = result[0], Z_pl = result[1]; if (protection) Guest.list.add(new Guest(uuid, player.getUniqueId())); - player.teleport(new Location(getWorld(), X_pl + 0.5, getY() + 1.2013, Z_pl + 0.5)); + Scheduler.teleport(player, new Location(getWorld(), X_pl + 0.5, getY() + 1.2013, Z_pl + 0.5)); PlayerInfo.removeBarFor(player); return true; } @@ -212,13 +223,15 @@ public boolean onCommand(CommandSender sender, Command cmd, String label, String } if (!(member instanceof Player)) return true; Player member_ex = (Player) member; - int memberID = plugin.findNearestRegionId(member_ex.getLocation()); - if (memberID == ownerID) { - if (!member_ex.hasPermission("oneblock.set")) - member_ex.performCommand("ob j"); - info.removeBar(member_ex); - sender.sendMessage(member.getName() + Messages.kicked); - } + Scheduler.runFor(member_ex, () -> { + int memberID = plugin.findNearestRegionId(member_ex.getLocation()); + if (memberID == ownerID) { + if (!member_ex.hasPermission("oneblock.set")) + member_ex.performCommand("ob j"); + info.removeBar(member_ex); + sender.sendMessage(member_ex.getName() + Messages.kicked); + } + }); return true; } case ("accept"):{ @@ -266,7 +279,7 @@ public boolean onCommand(CommandSender sender, Command cmd, String label, String if (requirePermission(sender, "oneblock.set")) { config = YamlConfiguration.loadConfiguration(LegacyConfigSaver.file); // Loading the config.yml file before making changes. - Bukkit.getScheduler().runTaskLater(plugin, () -> { LegacyConfigSaver.Save(config); }, 2L); // Saving the config.yml file after making changes. + Scheduler.runLater(() -> { LegacyConfigSaver.Save(config); }, 2L); // Saving the config.yml file after making changes. switch (parametr) { case ("set"): { if (player == null && args.length < 6) { @@ -312,7 +325,8 @@ public boolean onCommand(CommandSender sender, Command cmd, String label, String if (!plugin.enabled) plugin.runMainTask(); - getWorld().getBlockAt(getX(), getY(), getZ()).setType(GRASS_BLOCK.get()); + Scheduler.runAt(new Location(getWorld(), getX(), getY(), getZ()), + () -> getWorld().getBlockAt(getX(), getY(), getZ()).setType(GRASS_BLOCK.get())); plugin.worldGuard.recreateRegions(); LegacyConfigSaver.Save(config); @@ -356,7 +370,7 @@ public boolean onCommand(CommandSender sender, Command cmd, String label, String // ReloadBorders is scheduled 2 ticks later so the fall-through // below can first persist `border=true/false` via the shared // bool-toggle body; the ordering is load-bearing. - Bukkit.getScheduler().runTaskLater(plugin, () -> { plugin.ReloadBorders(); }, 2L); + Scheduler.runLater(() -> { plugin.ReloadBorders(); }, 2L); // fall through to shared bool-toggle body: case ("circlemode"): case ("useemptyislands"): @@ -435,7 +449,8 @@ public boolean onCommand(CommandSender sender, Command cmd, String label, String if (progress_bar) inf.bar.setVisible(false); int result[] = plugin.getIslandCoordinates(id); - Island.clear(getWorld(), result[0], getY(), result[1], getOffset()/4); + Scheduler.runAt(new Location(getWorld(), result[0], getY(), result[1]), + () -> Island.clear(getWorld(), result[0], getY(), result[1], getOffset()/4)); sender.sendMessage(String.format("%splayer %s island is destroyed! :D", ChatColor.GREEN, args[1])); return true; } @@ -590,9 +605,11 @@ public boolean onCommand(CommandSender sender, Command cmd, String label, String UUID uuid = p.getUniqueId(); if (PlayerInfo.getId(uuid) != -1) { int result[] = plugin.getIslandCoordinates(PlayerInfo.getId(uuid)); - Island.scan(getWorld(), result[0], getY(), result[1]); - sender.sendMessage(ChatColor.GREEN + "A copy of your island has been successfully saved!"); - config.set("custom_island", Island.map()); + Scheduler.runAt(new Location(getWorld(), result[0], getY(), result[1]), () -> { + Island.scan(getWorld(), result[0], getY(), result[1]); + sender.sendMessage(ChatColor.GREEN + "A copy of your island has been successfully saved!"); + config.set("custom_island", Island.map()); + }); } else sender.sendMessage(ChatColor.RED + "You don't have an island!"); return true; diff --git a/src/oneblock/OneBlock.java b/src/oneblock/OneBlock.java index 04379eb..a77c0b5 100644 --- a/src/oneblock/OneBlock.java +++ b/src/oneblock/OneBlock.java @@ -145,6 +145,7 @@ public static String getBarTitle(Player p, int lvl) { @Override public void onEnable() { plugin = this; + Scheduler.init(this); GUI.legacy = !Utils.findMethod(SkullMeta.class, "setOwningPlayer"); final Metrics metrics = new Metrics(this, 14477); final PluginManager pluginManager = Bukkit.getPluginManager(); @@ -181,7 +182,7 @@ public void onEnable() { if (getOffset() == 0) return; - Bukkit.getScheduler().runTaskTimerAsynchronously(this, new WorldInitTask(this), 32, 80); + Scheduler.runTimerAsync(new WorldInitTask(this), 32, 80); } private Place.Type determinePlaceType(PluginManager pluginManager) { @@ -207,12 +208,12 @@ private void setupMetrics(Metrics metrics) { } public void runMainTask() { - Bukkit.getScheduler().cancelTasks(this); + Scheduler.cancelTasks(); if (getOffset() == 0) return; - Bukkit.getScheduler().runTaskTimerAsynchronously(this, new PlayerCacheRefreshTask(this), 0, 120); - Bukkit.getScheduler().runTaskTimerAsynchronously(this, new PlayerDataSaveTask(this), 200, 6000); - if (!superlegacy) Bukkit.getScheduler().runTaskTimerAsynchronously(this, new IslandParticleTask(this), 40, 40); - Bukkit.getScheduler().runTaskTimer(this, new IslandBlockGenTask(this), 40, 80); + Scheduler.runTimerAsync(new PlayerCacheRefreshTask(this), 0, 120); + Scheduler.runTimerAsync(new PlayerDataSaveTask(this), 200, 6000); + if (!superlegacy) Scheduler.runTimerAsync(new IslandParticleTask(this), 40, 40); + Scheduler.runTimer(new IslandBlockGenTask(this), 40, 80); enabled = true; if (OBWorldGuard.canUse && Bukkit.getPluginManager().isPluginEnabled("WorldGuard")) { @@ -293,7 +294,7 @@ public void UpdateBorderLocation(Player pl, Location loc) { public void UpdateBorder(final Player pl) { WorldBorder border = pl.getWorldBorder(); - Bukkit.getScheduler().runTaskLater(this, + Scheduler.runForLater(pl, () -> { pl.setWorldBorder(border); }, 10L); } @@ -301,8 +302,8 @@ public void ReloadBorders() { if (!isBorderSupported) return; World w = getWor(); if (w == null) return; - if (border) w.getPlayers().forEach(pl -> plugin.UpdateBorderLocation(pl, pl.getLocation())); - else w.getPlayers().forEach(pl -> pl.setWorldBorder(null)); + if (border) Scheduler.playersIn(w).forEach(pl -> Scheduler.runFor(pl, () -> plugin.UpdateBorderLocation(pl, pl.getLocation()))); + else Scheduler.playersIn(w).forEach(pl -> Scheduler.runFor(pl, () -> pl.setWorldBorder(null))); } public boolean isWithinIslandBounds(Location loc, int centerX, int centerZ) { diff --git a/src/oneblock/RewardManager.java b/src/oneblock/RewardManager.java index cab1548..743b534 100644 --- a/src/oneblock/RewardManager.java +++ b/src/oneblock/RewardManager.java @@ -4,6 +4,7 @@ import org.bukkit.configuration.file.YamlConfiguration; import org.bukkit.entity.Player; +import oneblock.utils.Scheduler; import oneblock.utils.Utils; import java.io.File; @@ -83,8 +84,9 @@ private void executeCommandList(Player player, List commands, Map Bukkit.dispatchCommand(Bukkit.getConsoleSender(), dispatched)); } } diff --git a/src/oneblock/events/BlockEvent.java b/src/oneblock/events/BlockEvent.java index 9030f23..3a6bf61 100644 --- a/src/oneblock/events/BlockEvent.java +++ b/src/oneblock/events/BlockEvent.java @@ -17,6 +17,7 @@ import org.bukkit.util.Vector; import oneblock.PlayerInfo; +import oneblock.utils.Scheduler; public class BlockEvent implements Listener { protected static final double DROP_TELEPORT_HEIGHT_OFFSET = 0.8; @@ -56,6 +57,6 @@ public void BlockBreak(final BlockBreakEvent e) { if (block.getX() != result[0]) return; if (block.getZ() != result[1]) return; - Bukkit.getScheduler().runTaskLater(plugin, () -> {plugin.BlockGen(result[0], result[1], plID, ponl, block);}, 1L); + Scheduler.runAtLater(block.getLocation(), () -> {plugin.BlockGen(result[0], result[1], plID, ponl, block);}, 1L); } } \ No newline at end of file diff --git a/src/oneblock/invitation/Invitation.java b/src/oneblock/invitation/Invitation.java index 659e7ce..07c8bff 100644 --- a/src/oneblock/invitation/Invitation.java +++ b/src/oneblock/invitation/Invitation.java @@ -8,6 +8,7 @@ import org.bukkit.entity.Player; import oneblock.CommandHandler; +import oneblock.utils.Scheduler; import oneblock.OneBlock; import oneblock.PlayerInfo; @@ -35,8 +36,7 @@ public static void add(UUID name, UUID to) { return; Invitation inv_ = new Invitation(name, to); Invitation.list.add(inv_); - Bukkit.getScheduler().runTaskLater(OneBlock.plugin, - () -> { Invitation.list.remove(inv_); }, 300L); + Scheduler.runLater(() -> { Invitation.list.remove(inv_); }, 300L); } public static Invitation check(UUID uuid) { diff --git a/src/oneblock/placement/Place.java b/src/oneblock/placement/Place.java index 5fc26c0..65946db 100644 --- a/src/oneblock/placement/Place.java +++ b/src/oneblock/placement/Place.java @@ -3,6 +3,8 @@ import org.bukkit.Bukkit; import org.bukkit.block.Block; +import oneblock.utils.Scheduler; + public abstract class Place { public enum Type @@ -49,7 +51,8 @@ public boolean executeCommand(Block block, String command, String playerName) { if (!playerName.isEmpty()) //if (playerName != null) dispatched = dispatched.replace("{player}", playerName); - Bukkit.dispatchCommand(Bukkit.getConsoleSender(), dispatched); + final String command_ = dispatched; + Scheduler.run(() -> Bukkit.dispatchCommand(Bukkit.getConsoleSender(), command_)); return true; } } diff --git a/src/oneblock/tasks/IslandBlockGenTask.java b/src/oneblock/tasks/IslandBlockGenTask.java index 1bd9227..3fa3b98 100644 --- a/src/oneblock/tasks/IslandBlockGenTask.java +++ b/src/oneblock/tasks/IslandBlockGenTask.java @@ -12,6 +12,7 @@ import oneblock.OneBlock; import oneblock.PlayerInfo; import oneblock.invitation.Guest; +import oneblock.utils.Scheduler; /** * Main-thread block-generation pulse: every four seconds (80 ticks) it @@ -28,34 +29,40 @@ public final class IslandBlockGenTask implements Runnable { @Override public void run() { // SubBlockGen - for (Player player : plugin.cache.getPlayers()) { - final World wor = OneBlock.getWor(); - if (player.getWorld() != wor) continue; - final UUID uuid = player.getUniqueId(); - final int result[] = plugin.cache.getIslandCoordinates(player); - final int X_pl = result[0], Z_pl = result[1], plID = result[2]; - - if (OneBlock.protection && !player.hasPermission("Oneblock.ignoreBarrier")) { - boolean CheckGuest = false; - Location loc = player.getLocation(); - PlayerInfo inf = Guest.getPlayerInfo(uuid); - if (inf != null) { - int crd[] = plugin.getIslandCoordinates(PlayerInfo.getId(inf.uuid)); - CheckGuest = plugin.isWithinIslandBounds(loc, crd[0], crd[1]); - if (!CheckGuest) Guest.remove(uuid); - } - if (!plugin.isWithinIslandBounds(loc, X_pl, Z_pl) && !CheckGuest) { - player.performCommand("ob j"); - player.sendMessage(Messages.protection); - continue; - } + for (Player player : plugin.cache.getPlayers()) + Scheduler.runFor(player, () -> pulse(player)); + } + + private void pulse(Player player) { + final World wor = OneBlock.getWor(); + if (player.getWorld() != wor) return; + final UUID uuid = player.getUniqueId(); + final int result[] = plugin.cache.getIslandCoordinates(player); + final int X_pl = result[0], Z_pl = result[1], plID = result[2]; + + if (OneBlock.protection && !player.hasPermission("Oneblock.ignoreBarrier")) { + boolean CheckGuest = false; + Location loc = player.getLocation(); + PlayerInfo inf = Guest.getPlayerInfo(uuid); + if (inf != null) { + int crd[] = plugin.getIslandCoordinates(PlayerInfo.getId(inf.uuid)); + CheckGuest = plugin.isWithinIslandBounds(loc, crd[0], crd[1]); + if (!CheckGuest) Guest.remove(uuid); + } + if (!plugin.isWithinIslandBounds(loc, X_pl, Z_pl) && !CheckGuest) { + player.performCommand("ob j"); + player.sendMessage(Messages.protection); + return; } - - final Block block = wor.getBlockAt(X_pl, OneBlock.getY(), Z_pl); - if (block.getType() != Material.AIR) continue; - if (PlayerInfo.getId(uuid) == -1) continue; - - plugin.BlockGen(X_pl, Z_pl, plID, player, block); } + + // On Folia the island block may belong to another region than the player. + Scheduler.runAt(new Location(wor, X_pl, OneBlock.getY(), Z_pl), () -> { + final Block block = wor.getBlockAt(X_pl, OneBlock.getY(), Z_pl); + if (block.getType() != Material.AIR) return; + if (PlayerInfo.getId(uuid) == -1) return; + + plugin.BlockGen(X_pl, Z_pl, plID, player, block); + }); } } diff --git a/src/oneblock/tasks/IslandParticleTask.java b/src/oneblock/tasks/IslandParticleTask.java index fa2de34..9c9b181 100644 --- a/src/oneblock/tasks/IslandParticleTask.java +++ b/src/oneblock/tasks/IslandParticleTask.java @@ -6,6 +6,7 @@ import org.bukkit.entity.Player; import oneblock.OneBlock; +import oneblock.utils.Scheduler; public final class IslandParticleTask implements Runnable { private static final double[][] PARTICLE_OFFSETS = {{0, 0}, {0, 1}, {1, 0}, {1, 1}}; @@ -25,12 +26,13 @@ public void run() { if (result == null) continue; int X_pl = result[0], Z_pl = result[1]; double baseY = OneBlock.getY() + 0.5; - - for (double[] offset : PARTICLE_OFFSETS) { - Location loc = new Location(wor, X_pl + offset[0], baseY, Z_pl + offset[1]); - wor.spawnParticle(Particle.PORTAL, loc, PARTICLE_COUNT, 0, 0, 0, 0); - } + Scheduler.runAt(new Location(wor, X_pl, baseY, Z_pl), () -> { + for (double[] offset : PARTICLE_OFFSETS) { + Location loc = new Location(wor, X_pl + offset[0], baseY, Z_pl + offset[1]); + wor.spawnParticle(Particle.PORTAL, loc, PARTICLE_COUNT, 0, 0, 0, 0); + } + }); } } } diff --git a/src/oneblock/tasks/PlayerCacheRefreshTask.java b/src/oneblock/tasks/PlayerCacheRefreshTask.java index ce1fa43..782f385 100644 --- a/src/oneblock/tasks/PlayerCacheRefreshTask.java +++ b/src/oneblock/tasks/PlayerCacheRefreshTask.java @@ -3,6 +3,7 @@ import org.bukkit.World; import oneblock.OneBlock; +import oneblock.utils.Scheduler; /** * Async-scheduled refresh of {@link oneblock.PlayerCache} contents from @@ -18,6 +19,6 @@ public final class PlayerCacheRefreshTask implements Runnable { @Override public void run() { World w = OneBlock.getWorld(); - if (w != null) plugin.cache.updateCache(w.getPlayers()); + if (w != null) plugin.cache.updateCache(Scheduler.playersIn(w)); } } diff --git a/src/oneblock/utils/Scheduler.java b/src/oneblock/utils/Scheduler.java new file mode 100644 index 0000000..3450e47 --- /dev/null +++ b/src/oneblock/utils/Scheduler.java @@ -0,0 +1,171 @@ +package oneblock.utils; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.concurrent.TimeUnit; +import java.util.function.Consumer; + +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.World; +import org.bukkit.entity.Entity; +import org.bukkit.entity.Player; +import org.bukkit.plugin.Plugin; + +/** + * Bukkit / Folia scheduling bridge. + * + *

On Folia, world and entity state may only be touched from the region + * thread that owns it, so work is routed to the global, region or entity + * schedulers. The Folia API is reached through reflection so the plugin + * keeps compiling against the plain Spigot API and running on Java 8. + * + *

Everywhere else every method behaves exactly like the pre-Folia code: + * {@link #runAt} and {@link #runFor} run immediately on the calling thread, + * and the timed methods delegate to the {@code BukkitScheduler}. + */ +public final class Scheduler { + public static final boolean FOLIA = classExists("io.papermc.paper.threadedregions.RegionizedServer"); + private static final String PKG = "io.papermc.paper.threadedregions.scheduler."; + private static final long MS_PER_TICK = 50L; + + private static Plugin plugin; + private static Object global, async, region; + private static Method globalRun, globalRunDelayed, globalRunAtFixedRate, globalCancel; + private static Method asyncRunAtFixedRate, asyncCancel; + private static Method regionRun, regionRunDelayed; + private static Method entityGetScheduler, entityRun, entityRunDelayed, teleportAsync; + + private Scheduler() {} + + public static void init(Plugin pl) { + plugin = pl; + if (!FOLIA) return; + try { + Class g = Class.forName(PKG + "GlobalRegionScheduler"); + Class a = Class.forName(PKG + "AsyncScheduler"); + Class r = Class.forName(PKG + "RegionScheduler"); + Class e = Class.forName(PKG + "EntityScheduler"); + global = Bukkit.class.getMethod("getGlobalRegionScheduler").invoke(null); + async = Bukkit.class.getMethod("getAsyncScheduler").invoke(null); + region = Bukkit.class.getMethod("getRegionScheduler").invoke(null); + globalRun = g.getMethod("run", Plugin.class, Consumer.class); + globalRunDelayed = g.getMethod("runDelayed", Plugin.class, Consumer.class, long.class); + globalRunAtFixedRate = g.getMethod("runAtFixedRate", Plugin.class, Consumer.class, long.class, long.class); + globalCancel = g.getMethod("cancelTasks", Plugin.class); + asyncRunAtFixedRate = a.getMethod("runAtFixedRate", Plugin.class, Consumer.class, long.class, long.class, TimeUnit.class); + asyncCancel = a.getMethod("cancelTasks", Plugin.class); + regionRun = r.getMethod("run", Plugin.class, Location.class, Consumer.class); + regionRunDelayed = r.getMethod("runDelayed", Plugin.class, Location.class, Consumer.class, long.class); + entityGetScheduler = Entity.class.getMethod("getScheduler"); + entityRun = e.getMethod("run", Plugin.class, Consumer.class, Runnable.class); + entityRunDelayed = e.getMethod("runDelayed", Plugin.class, Consumer.class, Runnable.class, long.class); + teleportAsync = Entity.class.getMethod("teleportAsync", Location.class); + } catch (ReflectiveOperationException ex) { + throw new IllegalStateException("Folia detected, but its scheduler API could not be resolved", ex); + } + } + + /** Server-wide work (console commands, config). Folia: global region thread. */ + public static void run(Runnable task) { + if (FOLIA) call(globalRun, global, plugin, wrap(task)); + else task.run(); + } + + public static void runLater(Runnable task, long delay) { + if (FOLIA) call(globalRunDelayed, global, plugin, wrap(task), Math.max(1, delay)); + else Bukkit.getScheduler().runTaskLater(plugin, task, delay); + } + + public static void runTimer(Runnable task, long delay, long period) { + if (FOLIA) call(globalRunAtFixedRate, global, plugin, wrap(task), Math.max(1, delay), Math.max(1, period)); + else Bukkit.getScheduler().runTaskTimer(plugin, task, delay, period); + } + + public static void runTimerAsync(Runnable task, long delay, long period) { + if (FOLIA) call(asyncRunAtFixedRate, async, plugin, wrap(task), + Math.max(1, delay) * MS_PER_TICK, Math.max(1, period) * MS_PER_TICK, TimeUnit.MILLISECONDS); + else Bukkit.getScheduler().runTaskTimerAsynchronously(plugin, task, delay, period); + } + + /** Work on blocks around {@code loc}. Folia: the region thread owning that location. */ + public static void runAt(Location loc, Runnable task) { + if (FOLIA) call(regionRun, region, plugin, loc, wrap(task)); + else task.run(); + } + + public static void runAtLater(Location loc, Runnable task, long delay) { + if (FOLIA) call(regionRunDelayed, region, plugin, loc, wrap(task), Math.max(1, delay)); + else Bukkit.getScheduler().runTaskLater(plugin, task, delay); + } + + /** Work on an entity. Folia: the entity's own thread; skipped if it was removed. */ + public static void runFor(Entity entity, Runnable task) { + if (FOLIA) call(entityRun, entityScheduler(entity), plugin, wrap(task), null); + else task.run(); + } + + public static void runForLater(Entity entity, Runnable task, long delay) { + if (FOLIA) call(entityRunDelayed, entityScheduler(entity), plugin, wrap(task), null, Math.max(1, delay)); + else Bukkit.getScheduler().runTaskLater(plugin, task, delay); + } + + /** Folia forbids synchronous teleports; {@code teleportAsync} is used there instead. */ + public static void teleport(Entity entity, Location loc) { + if (FOLIA) runFor(entity, () -> call(teleportAsync, entity, loc)); + else entity.teleport(loc); + } + + public static void cancelTasks() { + if (FOLIA) { + call(globalCancel, global, plugin); + call(asyncCancel, async, plugin); + } + else Bukkit.getScheduler().cancelTasks(plugin); + } + + /** Players in {@code world}, readable from any thread. */ + public static Collection playersIn(World world) { + if (!FOLIA) return world.getPlayers(); + List players = new ArrayList<>(); + for (Player p : Bukkit.getOnlinePlayers()) + if (world.equals(p.getWorld())) players.add(p); + return players; + } + + private static Object entityScheduler(Entity entity) { + try { + return entityGetScheduler.invoke(entity); + } catch (ReflectiveOperationException ex) { + throw new IllegalStateException(ex); + } + } + + private static Consumer wrap(Runnable task) { + return scheduledTask -> task.run(); + } + + private static Object call(Method method, Object target, Object... args) { + try { + return method.invoke(target, args); + } catch (InvocationTargetException ex) { + Throwable cause = ex.getCause(); + if (cause instanceof RuntimeException) throw (RuntimeException) cause; + throw new IllegalStateException(cause); + } catch (IllegalAccessException ex) { + throw new IllegalStateException(ex); + } + } + + private static boolean classExists(String name) { + try { + Class.forName(name); + return true; + } catch (ClassNotFoundException ex) { + return false; + } + } +} diff --git a/src/plugin.yml b/src/plugin.yml index 1602c27..2258a0f 100644 --- a/src/plugin.yml +++ b/src/plugin.yml @@ -2,6 +2,7 @@ name: Oneblock main: oneblock.OneBlock version: 1.6.3 api-version: 1.13 +folia-supported: true author: MrMarL loadbefore: ['Multiverse-Core'] softdepend: ['PlaceholderAPI', 'WorldGuard', 'ItemsAdder', 'Oraxen', 'Nexo', 'CraftEngine']