From 68c68bb9448970a9600b6f8b0816f30c97f490b6 Mon Sep 17 00:00:00 2001 From: xCodiq Date: Wed, 19 Feb 2025 01:08:50 +0100 Subject: [PATCH 1/4] feat(folia): added folia support --- build.gradle | 5 +- src/main/java/sh/okx/rankup/AutoRankup.java | 33 ++- src/main/java/sh/okx/rankup/RankupPlugin.java | 54 ++--- .../sh/okx/rankup/commands/InfoCommand.java | 2 +- .../java/sh/okx/rankup/gui/GuiListener.java | 10 +- src/main/java/sh/okx/rankup/ranks/Rank.java | 15 +- .../java/sh/okx/rankup/ranksgui/RanksGui.java | 7 +- .../okx/rankup/util/folia/AsyncScheduler.java | 140 +++++++++++++ .../rankup/util/folia/EntityScheduler.java | 135 ++++++++++++ .../okx/rankup/util/folia/FoliaScheduler.java | 130 ++++++++++++ .../util/folia/GlobalRegionScheduler.java | 125 +++++++++++ .../rankup/util/folia/RegionScheduler.java | 197 ++++++++++++++++++ .../sh/okx/rankup/util/folia/TaskWrapper.java | 64 ++++++ src/main/resources/plugin.yml | 1 + 14 files changed, 880 insertions(+), 38 deletions(-) create mode 100644 src/main/java/sh/okx/rankup/util/folia/AsyncScheduler.java create mode 100644 src/main/java/sh/okx/rankup/util/folia/EntityScheduler.java create mode 100644 src/main/java/sh/okx/rankup/util/folia/FoliaScheduler.java create mode 100644 src/main/java/sh/okx/rankup/util/folia/GlobalRegionScheduler.java create mode 100644 src/main/java/sh/okx/rankup/util/folia/RegionScheduler.java create mode 100644 src/main/java/sh/okx/rankup/util/folia/TaskWrapper.java diff --git a/build.gradle b/build.gradle index 9924186..621d910 100644 --- a/build.gradle +++ b/build.gradle @@ -23,7 +23,7 @@ java { repositories { mavenCentral() maven { - url 'https://hub.spigotmc.org/nexus/content/repositories/snapshots/' + url 'https://repo.papermc.io/repository/maven-public/' } maven { url 'https://repo.extendedclip.com/content/repositories/placeholderapi/' @@ -51,7 +51,8 @@ dependencies { implementation group: 'org.slf4j', name: 'slf4j-api', version: '1.7.30' compileOnly 'org.jetbrains:annotations:22.0.0' - compileOnly 'org.spigotmc:spigot-api:1.21.4-R0.1-SNAPSHOT' + compileOnly 'io.papermc.paper:paper-api:1.21.4-R0.1-SNAPSHOT' +// compileOnly 'org.spigotmc:spigot-api:1.21.4-R0.1-SNAPSHOT' compileOnly('com.github.Realizedd:TokenManager:3.2.4') { transitive = false } diff --git a/src/main/java/sh/okx/rankup/AutoRankup.java b/src/main/java/sh/okx/rankup/AutoRankup.java index 2c88dbd..bcdb0f8 100644 --- a/src/main/java/sh/okx/rankup/AutoRankup.java +++ b/src/main/java/sh/okx/rankup/AutoRankup.java @@ -4,11 +4,17 @@ import org.bukkit.Bukkit; import org.bukkit.entity.Player; import org.bukkit.scheduler.BukkitRunnable; +import org.bukkit.scheduler.BukkitTask; +import sh.okx.rankup.util.folia.FoliaScheduler; +import sh.okx.rankup.util.folia.TaskWrapper; @RequiredArgsConstructor -public class AutoRankup extends BukkitRunnable { +public class AutoRankup implements Runnable { private final RankupPlugin rankup; + private TaskWrapper task; + private BukkitTask bukkitTask; + @Override public void run() { if (rankup.error()) { @@ -26,4 +32,29 @@ public void run() { } } } + + public void runTaskTimer(RankupPlugin rankupPlugin, long delay, long period) { + if (FoliaScheduler.isFolia()) { + task = FoliaScheduler.getAsyncScheduler().runAtFixedRate(rankupPlugin, $ -> this.run(), delay, period); + } else { + bukkitTask = new BukkitRunnable() { + @Override + public void run() { + AutoRankup.this.run(); + } + }.runTaskTimer(rankupPlugin, delay, period); + } + } + + public boolean isCancelled() { + return task == null ? bukkitTask.isCancelled() : task.isCancelled(); + } + + public void cancel() { + if (task == null) { + bukkitTask.cancel(); + } else { + task.cancel(); + } + } } diff --git a/src/main/java/sh/okx/rankup/RankupPlugin.java b/src/main/java/sh/okx/rankup/RankupPlugin.java index 771f3d9..8fac418 100644 --- a/src/main/java/sh/okx/rankup/RankupPlugin.java +++ b/src/main/java/sh/okx/rankup/RankupPlugin.java @@ -80,6 +80,7 @@ import sh.okx.rankup.serialization.YamlDeserializer; import sh.okx.rankup.util.UpdateNotifier; import sh.okx.rankup.util.VersionChecker; +import sh.okx.rankup.util.folia.FoliaScheduler; import java.io.File; import java.io.FileNotFoundException; @@ -129,29 +130,29 @@ protected RankupPlugin(PermissionManager permissionManager, EconomyProvider econ @Override public void onEnable() { - UpdateNotifier notifier = new UpdateNotifier(new VersionChecker(this)); +// UpdateNotifier notifier = new UpdateNotifier(new VersionChecker(this)); reload(true); - if (System.getProperty("RANKUP_TEST") == null) { - Metrics metrics = new Metrics(this); - metrics.addCustomChart(new Metrics.SimplePie("confirmation", - () -> config.getString("confirmation-type", "unknown"))); - metrics.addCustomChart(new Metrics.AdvancedPie("requirements", () -> { - Map map = new HashMap<>(); - addAllRequirements(map, rankups); - if (prestiges != null) { - addAllRequirements(map, prestiges); - } - return map; - })); - metrics.addCustomChart(new Metrics.SimplePie("prestige", - () -> config.getBoolean("prestige") ? "enabled" : "disabled")); - metrics.addCustomChart(new Metrics.SimplePie("permission-rankup", - () -> config.getBoolean("permission-rankup") ? "enabled" : "disabled")); - metrics.addCustomChart(new Metrics.SimplePie("notify-update", - () -> config.getBoolean("notify-update") ? "enabled" : "disabled")); - } +// if (System.getProperty("RANKUP_TEST") == null) { +// Metrics metrics = new Metrics(this); +// metrics.addCustomChart(new Metrics.SimplePie("confirmation", +// () -> config.getString("confirmation-type", "unknown"))); +// metrics.addCustomChart(new Metrics.AdvancedPie("requirements", () -> { +// Map map = new HashMap<>(); +// addAllRequirements(map, rankups); +// if (prestiges != null) { +// addAllRequirements(map, prestiges); +// } +// return map; +// })); +// metrics.addCustomChart(new Metrics.SimplePie("prestige", +// () -> config.getBoolean("prestige") ? "enabled" : "disabled")); +// metrics.addCustomChart(new Metrics.SimplePie("permission-rankup", +// () -> config.getBoolean("permission-rankup") ? "enabled" : "disabled")); +// metrics.addCustomChart(new Metrics.SimplePie("notify-update", +// () -> config.getBoolean("notify-update") ? "enabled" : "disabled")); +// } if (config.getBoolean("ranks")) { if (config.getBoolean("ranks-gui")) { @@ -173,10 +174,10 @@ public void onEnable() { } getCommand("rankup").setExecutor(new RankupCommand(this)); - getCommand("rankup3").setExecutor(new InfoCommand(this, notifier)); + getCommand("rankup3").setExecutor(new InfoCommand(this, null)); // notifier)); getServer().getPluginManager().registerEvents(new GuiListener(this), this); - getServer().getPluginManager().registerEvents( - new JoinUpdateNotifier(notifier, () -> getConfig().getBoolean("notify-update"), "rankup.notify"), this); +// getServer().getPluginManager().registerEvents( +// new JoinUpdateNotifier(notifier, () -> getConfig().getBoolean("notify-update"), "rankup.notify"), this); placeholders = new Placeholders(this); placeholders.register(); @@ -298,10 +299,13 @@ private void loadConfigs(boolean init) { messages = YamlConfiguration.loadConfiguration(localeFile); if (init) { - Bukkit.getScheduler().runTask(this, () -> { + final Runnable runnable = () -> { refreshRanks(); error(); - }); + }; + + if (FoliaScheduler.isFolia()) FoliaScheduler.getGlobalRegionScheduler().execute(this, runnable); + else Bukkit.getScheduler().runTask(this, runnable); } else { refreshRanks(); } diff --git a/src/main/java/sh/okx/rankup/commands/InfoCommand.java b/src/main/java/sh/okx/rankup/commands/InfoCommand.java index 279fee9..2047b67 100644 --- a/src/main/java/sh/okx/rankup/commands/InfoCommand.java +++ b/src/main/java/sh/okx/rankup/commands/InfoCommand.java @@ -336,7 +336,7 @@ public boolean onCommand(CommandSender sender, Command command, String label, St } if (sender.hasPermission("rankup.checkversion")) { - notifier.notify(sender, false); + //notifier.notify(sender, false); } return true; diff --git a/src/main/java/sh/okx/rankup/gui/GuiListener.java b/src/main/java/sh/okx/rankup/gui/GuiListener.java index 841f660..97e6e63 100644 --- a/src/main/java/sh/okx/rankup/gui/GuiListener.java +++ b/src/main/java/sh/okx/rankup/gui/GuiListener.java @@ -8,6 +8,7 @@ import org.bukkit.event.inventory.InventoryClickEvent; import org.bukkit.inventory.Inventory; import sh.okx.rankup.RankupPlugin; +import sh.okx.rankup.util.folia.FoliaScheduler; @RequiredArgsConstructor public class GuiListener implements Listener { @@ -27,19 +28,22 @@ public void on(InventoryClickEvent e) { Gui gui = (Gui) inventory.getHolder(); if (gui.getRankup().isSimilar(e.getCurrentItem())) { - Bukkit.getScheduler().runTask(plugin, player::closeInventory); + if (FoliaScheduler.isFolia()) FoliaScheduler.getEntityScheduler().run(player, plugin, $ -> player.closeInventory(), null); + else Bukkit.getScheduler().runTask(plugin, () -> player.closeInventory()); if (gui.isPrestige()) { plugin.getHelper().prestige(player); } else { plugin.getHelper().rankup(player); } } else if (gui.getCancel().isSimilar(e.getCurrentItem())) { - Bukkit.getScheduler().runTask(plugin, () -> { + final Runnable runnable = () -> { player.closeInventory(); if (gui.isReturnToRanksGui()) { Bukkit.dispatchCommand(player, "ranks"); } - }); + }; + if (FoliaScheduler.isFolia()) FoliaScheduler.getEntityScheduler().run(player, plugin, $ -> runnable.run(), null); + else Bukkit.getScheduler().runTask(plugin, runnable); } } } diff --git a/src/main/java/sh/okx/rankup/ranks/Rank.java b/src/main/java/sh/okx/rankup/ranks/Rank.java index 9fed31e..0c04028 100644 --- a/src/main/java/sh/okx/rankup/ranks/Rank.java +++ b/src/main/java/sh/okx/rankup/ranks/Rank.java @@ -11,6 +11,7 @@ import sh.okx.rankup.RankupPlugin; import sh.okx.rankup.ranks.requirements.RankRequirements; import sh.okx.rankup.requirements.Requirement; +import sh.okx.rankup.util.folia.FoliaScheduler; @EqualsAndHashCode @RequiredArgsConstructor(access = AccessLevel.PROTECTED) @@ -46,12 +47,18 @@ public void applyRequirements(Player player) { } public void runCommands(Player player, Rank next) { - for (String command : commands) { - String string = plugin.newMessageBuilder(command).replacePlayer(player).replaceOldRank(this).replaceRank(next).toString(player); - Bukkit.dispatchCommand(Bukkit.getConsoleSender(), string); - } + final Runnable runnable = () -> { + for (String command : commands) { + String string = plugin.newMessageBuilder(command).replacePlayer(player).replaceOldRank(this).replaceRank(next).toString(player); + Bukkit.dispatchCommand(Bukkit.getConsoleSender(), string); + } + }; + + if (FoliaScheduler.isFolia()) FoliaScheduler.getGlobalRegionScheduler().run(plugin, $ -> runnable.run()); + else runnable.run(); } + @Override public String toString() { return "Rank{" + diff --git a/src/main/java/sh/okx/rankup/ranksgui/RanksGui.java b/src/main/java/sh/okx/rankup/ranksgui/RanksGui.java index c792865..2e73ac5 100644 --- a/src/main/java/sh/okx/rankup/ranksgui/RanksGui.java +++ b/src/main/java/sh/okx/rankup/ranksgui/RanksGui.java @@ -13,6 +13,7 @@ import sh.okx.rankup.ranks.Rank; import sh.okx.rankup.ranks.RankElement; import sh.okx.rankup.util.Colour; +import sh.okx.rankup.util.folia.FoliaScheduler; public class RanksGui { private final RankupPlugin plugin; @@ -111,10 +112,12 @@ public void click(InventoryClickEvent event) { } int slot = event.getRawSlot(); if (slot == rankupSlot) { - Bukkit.getScheduler().runTask(plugin, () -> { + final Runnable runnable = () -> { player.closeInventory(); Bukkit.dispatchCommand(player, "rankup gui"); - }); + }; + if (FoliaScheduler.isFolia()) FoliaScheduler.getEntityScheduler().run(player, plugin, $ -> runnable.run(), null); + else Bukkit.getScheduler().runTask(plugin, runnable); } } diff --git a/src/main/java/sh/okx/rankup/util/folia/AsyncScheduler.java b/src/main/java/sh/okx/rankup/util/folia/AsyncScheduler.java new file mode 100644 index 0000000..42e71e4 --- /dev/null +++ b/src/main/java/sh/okx/rankup/util/folia/AsyncScheduler.java @@ -0,0 +1,140 @@ +/* + * This file is part of packetevents - https://github.com/retrooper/packetevents + * Copyright (C) 2024 retrooper and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package sh.okx.rankup.util.folia; + +import org.bukkit.Bukkit; +import org.bukkit.plugin.Plugin; +import org.bukkit.scheduler.BukkitScheduler; +import org.jetbrains.annotations.NotNull; + +import java.util.concurrent.TimeUnit; +import java.util.function.Consumer; + +/** + * Represents a scheduler for executing tasks asynchronously. + */ +public class AsyncScheduler { + + private BukkitScheduler bukkitScheduler; + private io.papermc.paper.threadedregions.scheduler.AsyncScheduler asyncScheduler; + + protected AsyncScheduler() { + if (FoliaScheduler.isFolia) { + asyncScheduler = Bukkit.getAsyncScheduler(); + } else { + bukkitScheduler = Bukkit.getScheduler(); + } + } + + /** + * Schedules the specified task to be executed asynchronously immediately. + * + * @param plugin Plugin which owns the specified task. + * @param task Specified task. + * @return {@link TaskWrapper} instance representing a wrapped task + */ + public TaskWrapper runNow(@NotNull Plugin plugin, @NotNull Consumer task) { + if (!FoliaScheduler.isFolia) { + return new TaskWrapper(bukkitScheduler.runTaskAsynchronously(plugin, () -> task.accept(null))); + } + + return new TaskWrapper(asyncScheduler.runNow(plugin, (o) -> task.accept(null))); + } + + /** + * Schedules the specified task to be executed asynchronously after the specified delay. + * + * @param plugin Plugin which owns the specified task. + * @param task Specified task. + * @param delay The time delay to pass before the task should be executed. + * @param timeUnit The time unit for the time delay. + * @return {@link TaskWrapper} instance representing a wrapped task + */ + public TaskWrapper runDelayed(@NotNull Plugin plugin, @NotNull Consumer task, long delay, @NotNull TimeUnit timeUnit) { + if (!FoliaScheduler.isFolia) { + return new TaskWrapper(bukkitScheduler.runTaskLaterAsynchronously(plugin, () -> task.accept(null), convertTimeToTicks(delay, timeUnit))); + } + + return new TaskWrapper(asyncScheduler.runDelayed(plugin, (o) -> task.accept(null), delay, timeUnit)); + } + + /** + * Schedules the specified task to be executed asynchronously after the initial delay has passed, and then periodically executed with the specified period. + * + * @param plugin Plugin which owns the specified task. + * @param task Specified task. + * @param delay The time delay to pass before the task should be executed. + * @param period The time period between each task execution. Any value less-than 1 is treated as 1. + * @param timeUnit The time unit for the initial delay and period. + * @return {@link TaskWrapper} instance representing a wrapped task + */ + public TaskWrapper runAtFixedRate(@NotNull Plugin plugin, @NotNull Consumer task, long delay, long period, @NotNull TimeUnit timeUnit) { + if (period < 1) period = 1; + + if (!FoliaScheduler.isFolia) { + return new TaskWrapper(bukkitScheduler.runTaskTimerAsynchronously(plugin, () -> task.accept(null), convertTimeToTicks(delay, timeUnit), convertTimeToTicks(period, timeUnit))); + } + + return new TaskWrapper(asyncScheduler.runAtFixedRate(plugin, (o) -> task.accept(null), delay, period, timeUnit)); + } + + /** + * Schedules the specified task to be executed asynchronously after the initial delay has passed, and then periodically executed. + * + * @param plugin Plugin which owns the specified task. + * @param task Specified task. + * @param initialDelayTicks The time delay in ticks to pass before the task should be executed. + * @param periodTicks The time period in ticks between each task execution. Any value less-than 1 is treated as 1. + * @return {@link TaskWrapper} instance representing a wrapped task + */ + public TaskWrapper runAtFixedRate(@NotNull Plugin plugin, @NotNull Consumer task, long initialDelayTicks, long periodTicks) { + if (periodTicks < 1) periodTicks = 1; + + if (!FoliaScheduler.isFolia) { + return new TaskWrapper(bukkitScheduler.runTaskTimerAsynchronously(plugin, () -> task.accept(null), initialDelayTicks, periodTicks)); + } + + return new TaskWrapper(asyncScheduler.runAtFixedRate(plugin, (o) -> task.accept(null), initialDelayTicks * 50, periodTicks * 50, TimeUnit.MILLISECONDS)); + } + + /** + * Attempts to cancel all tasks scheduled by the specified plugin. + * + * @param plugin Specified plugin. + */ + public void cancel(@NotNull Plugin plugin) { + if (!FoliaScheduler.isFolia) { + bukkitScheduler.cancelTasks(plugin); + return; + } + + asyncScheduler.cancelTasks(plugin); + } + + /** + * Converts the specified time to ticks. + * + * @param time The time to convert. + * @param timeUnit The time unit of the time. + * @return The time converted to ticks. + */ + private long convertTimeToTicks(long time, TimeUnit timeUnit) { + return timeUnit.toMillis(time) / 50; + } +} \ No newline at end of file diff --git a/src/main/java/sh/okx/rankup/util/folia/EntityScheduler.java b/src/main/java/sh/okx/rankup/util/folia/EntityScheduler.java new file mode 100644 index 0000000..0f7952f --- /dev/null +++ b/src/main/java/sh/okx/rankup/util/folia/EntityScheduler.java @@ -0,0 +1,135 @@ +/* + * This file is part of packetevents - https://github.com/retrooper/packetevents + * Copyright (C) 2024 retrooper and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package sh.okx.rankup.util.folia; + +import org.bukkit.Bukkit; +import org.bukkit.entity.Entity; +import org.bukkit.plugin.Plugin; +import org.bukkit.scheduler.BukkitScheduler; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.function.Consumer; + +/** + * Represents a scheduler for executing entity tasks. + */ +public class EntityScheduler { + private BukkitScheduler bukkitScheduler; + + protected EntityScheduler() { + if (!FoliaScheduler.isFolia) { + bukkitScheduler = Bukkit.getScheduler(); + } + } + + /** + * Schedules a task with the given delay. If the task failed to schedule because the scheduler is retired (entity removed), then returns false. + * Otherwise, either the run callback will be invoked after the specified delay, or the retired callback will be invoked if the scheduler is retired. + * Note that the retired callback is invoked in critical code, so it should not attempt to remove the entity, + * remove other entities, load chunks, load worlds, modify ticket levels, etc. + *

+ * It is guaranteed that the run and retired callback are invoked on the region which owns the entity. + * + * @param plugin Plugin which owns the specified task. + * @param run The callback to run after the specified delay, may not be null. + * @param retired Retire callback to run if the entity is retired before the run callback can be invoked, may be null. + * @param delay The delay in ticks before the run callback is invoked. + */ + public void execute(@NotNull Entity entity, @NotNull Plugin plugin, @NotNull Runnable run, @Nullable Runnable retired, long delay) { + if (!FoliaScheduler.isFolia) { + bukkitScheduler.runTaskLater(plugin, run, delay); + return; + } + + entity.getScheduler().execute(plugin, run, retired, delay); + } + + /** + * Schedules a task to execute on the next tick. If the task failed to schedule because the scheduler is retired (entity removed), + * then returns null. Otherwise, either the task callback will be invoked after the specified delay, + * or the retired callback will be invoked if the scheduler is retired. + * Note that the retired callback is invoked in critical code, so it should not attempt to remove the entity, + * remove other entities, load chunks, load worlds, modify ticket levels, etc. + *

+ * It is guaranteed that the task and retired callback are invoked on the region which owns the entity. + * + * @param plugin The plugin that owns the task + * @param task The task to execute + * @param retired Retire callback to run if the entity is retired before the run callback can be invoked, may be null. + * @return {@link TaskWrapper} instance representing a wrapped task + */ + public TaskWrapper run(@NotNull Entity entity, @NotNull Plugin plugin, @NotNull Consumer task, @Nullable Runnable retired) { + if (!FoliaScheduler.isFolia) { + return new TaskWrapper(bukkitScheduler.runTask(plugin, () -> task.accept(null))); + } + + return new TaskWrapper(entity.getScheduler().run(plugin, (o) -> task.accept(null), retired)); + } + + /** + * Schedules a task with the given delay. If the task failed to schedule because the scheduler is retired (entity removed), + * then returns null. Otherwise, either the task callback will be invoked after the specified delay, or the retired callback will be invoked if the scheduler is retired. + * Note that the retired callback is invoked in critical code, so it should not attempt to remove the entity, + * remove other entities, load chunks, load worlds, modify ticket levels, etc. + *

+ * It is guaranteed that the task and retired callback are invoked on the region which owns the entity. + * + * @param plugin The plugin that owns the task + * @param task The task to execute + * @param retired Retire callback to run if the entity is retired before the run callback can be invoked, may be null. + * @param delayTicks The delay in ticks before the run callback is invoked. Any value less-than 1 is treated as 1. + * @return {@link TaskWrapper} instance representing a wrapped task + */ + public TaskWrapper runDelayed(@NotNull Entity entity, @NotNull Plugin plugin, @NotNull Consumer task, @Nullable Runnable retired, long delayTicks) { + if (delayTicks < 1) delayTicks = 1; + + if (!FoliaScheduler.isFolia) { + return new TaskWrapper(bukkitScheduler.runTaskLater(plugin, () -> task.accept(null), delayTicks)); + } + + return new TaskWrapper(entity.getScheduler().runDelayed(plugin, (o) -> task.accept(null), retired, delayTicks)); + } + + /** + * Schedules a repeating task with the given delay and period. If the task failed to schedule because the scheduler is retired (entity removed), + * then returns null. Otherwise, either the task callback will be invoked after the specified delay, or the retired callback will be invoked if the scheduler is retired. + * Note that the retired callback is invoked in critical code, so it should not attempt to remove the entity, + * remove other entities, load chunks, load worlds, modify ticket levels, etc. + *

+ * It is guaranteed that the task and retired callback are invoked on the region which owns the entity. + * + * @param plugin The plugin that owns the task + * @param task The task to execute + * @param retired Retire callback to run if the entity is retired before the run callback can be invoked, may be null. + * @param initialDelayTicks The initial delay, in ticks before the method is invoked. Any value less-than 1 is treated as 1. + * @param periodTicks The period, in ticks. Any value less-than 1 is treated as 1. + * @return {@link TaskWrapper} instance representing a wrapped task + */ + public TaskWrapper runAtFixedRate(@NotNull Entity entity, @NotNull Plugin plugin, @NotNull Consumer task, @Nullable Runnable retired, long initialDelayTicks, long periodTicks) { + if (initialDelayTicks < 1) initialDelayTicks = 1; + if (periodTicks < 1) periodTicks = 1; + + if (!FoliaScheduler.isFolia) { + return new TaskWrapper(bukkitScheduler.runTaskTimer(plugin, () -> task.accept(null), initialDelayTicks, periodTicks)); + } + + return new TaskWrapper(entity.getScheduler().runAtFixedRate(plugin, (o) -> task.accept(null), retired, initialDelayTicks, periodTicks)); + } +} \ No newline at end of file diff --git a/src/main/java/sh/okx/rankup/util/folia/FoliaScheduler.java b/src/main/java/sh/okx/rankup/util/folia/FoliaScheduler.java new file mode 100644 index 0000000..02133d1 --- /dev/null +++ b/src/main/java/sh/okx/rankup/util/folia/FoliaScheduler.java @@ -0,0 +1,130 @@ +/* + * This file is part of packetevents - https://github.com/retrooper/packetevents + * Copyright (C) 2024 retrooper and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package sh.okx.rankup.util.folia; + +import org.bukkit.Bukkit; +import org.bukkit.event.Event; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; +import org.bukkit.plugin.Plugin; + +/** + * Utility class to handle scheduling tasks. + * It uses Paper's threaded-regions schedulers if Folia is used, + * otherwise it falls back to the default Bukkit scheduler. + */ +public class FoliaScheduler { + static final boolean isFolia; + private static Class regionizedServerInitEventClass; + + private static AsyncScheduler asyncScheduler; + private static EntityScheduler entityScheduler; + private static GlobalRegionScheduler globalRegionScheduler; + private static RegionScheduler regionScheduler; + + static { + boolean folia; + try { + Class.forName("io.papermc.paper.threadedregions.RegionizedServer"); + folia = true; + + // Thanks for this code ViaVersion + // The class is only part of the Folia API, so we need to use reflections to get it + regionizedServerInitEventClass = (Class) Class.forName("io.papermc.paper.threadedregions.RegionizedServerInitEvent"); + } catch (ClassNotFoundException e) { + folia = false; + } + + isFolia = folia; + } + + /** + * @return Whether the server is running Folia + */ + public static boolean isFolia() { + return isFolia; + } + + /** + * Returns the async scheduler. + * + * @return async scheduler instance of {@link AsyncScheduler} + */ + public static AsyncScheduler getAsyncScheduler() { + if (asyncScheduler == null) { + asyncScheduler = new AsyncScheduler(); + } + return asyncScheduler; + } + + /** + * Returns the entity scheduler. + * + * @return entity scheduler instance of {@link EntityScheduler} + */ + public static EntityScheduler getEntityScheduler() { + if (entityScheduler == null) { + entityScheduler = new EntityScheduler(); + } + return entityScheduler; + } + + /** + * Returns the global region scheduler. + * + * @return global region scheduler instance of {@link GlobalRegionScheduler} + */ + public static GlobalRegionScheduler getGlobalRegionScheduler() { + if (globalRegionScheduler == null) { + globalRegionScheduler = new GlobalRegionScheduler(); + } + return globalRegionScheduler; + } + + /** + * Returns the region scheduler. + * + * @return region scheduler instance of {@link RegionScheduler} + */ + public static RegionScheduler getRegionScheduler() { + if (regionScheduler == null) { + regionScheduler = new RegionScheduler(); + } + return regionScheduler; + } + + /** + * Run a task after the server has finished initializing. + * Undefined behavior if called after the server has finished initializing. + *

+ * We still need to use reflections to get the server init event class, as this is only part of the Folia API. + * + * @param plugin Your plugin or PacketEvents + * @param run The task to run + */ + public static void runTaskOnInit(Plugin plugin, Runnable run) { + if (!isFolia) { + Bukkit.getScheduler().scheduleSyncDelayedTask(plugin, run); + return; + } + + Bukkit.getServer().getPluginManager().registerEvent(regionizedServerInitEventClass, new Listener() { + }, EventPriority.HIGHEST, (listener, event) -> run.run(), plugin); + } +} \ No newline at end of file diff --git a/src/main/java/sh/okx/rankup/util/folia/GlobalRegionScheduler.java b/src/main/java/sh/okx/rankup/util/folia/GlobalRegionScheduler.java new file mode 100644 index 0000000..6d736d0 --- /dev/null +++ b/src/main/java/sh/okx/rankup/util/folia/GlobalRegionScheduler.java @@ -0,0 +1,125 @@ +/* + * This file is part of packetevents - https://github.com/retrooper/packetevents + * Copyright (C) 2024 retrooper and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package sh.okx.rankup.util.folia; + +import org.bukkit.Bukkit; +import org.bukkit.plugin.Plugin; +import org.bukkit.scheduler.BukkitScheduler; +import org.jetbrains.annotations.NotNull; + +import java.util.function.Consumer; + +/** + * Represents a scheduler for executing global region tasks. + */ +public class GlobalRegionScheduler { + + private BukkitScheduler bukkitScheduler; + private io.papermc.paper.threadedregions.scheduler.GlobalRegionScheduler globalRegionScheduler; + + protected GlobalRegionScheduler() { + if (FoliaScheduler.isFolia) { + globalRegionScheduler = Bukkit.getGlobalRegionScheduler(); + } else { + bukkitScheduler = Bukkit.getScheduler(); + } + } + + /** + * Schedules a task to be executed on the global region. + * + * @param plugin The plugin that owns the task + * @param run The task to execute + */ + public void execute(@NotNull Plugin plugin, @NotNull Runnable run) { + if (!FoliaScheduler.isFolia) { + bukkitScheduler.runTask(plugin, run); + return; + } + + globalRegionScheduler.execute(plugin, run); + } + + /** + * Schedules a task to be executed on the global region. + * + * @param plugin The plugin that owns the task + * @param task The task to execute + * @return {@link TaskWrapper} instance representing a wrapped task + */ + public TaskWrapper run(@NotNull Plugin plugin, @NotNull Consumer task) { + if (!FoliaScheduler.isFolia) { + return new TaskWrapper(bukkitScheduler.runTask(plugin, () -> task.accept(null))); + } + + return new TaskWrapper(globalRegionScheduler.run(plugin, (o) -> task.accept(null))); + } + + /** + * Schedules a task to be executed on the global region after the specified delay in ticks. + * + * @param plugin The plugin that owns the task + * @param task The task to execute + * @param delay The delay, in ticks before the method is invoked. Any value less-than 1 is treated as 1. + * @return {@link TaskWrapper} instance representing a wrapped task + */ + public TaskWrapper runDelayed(@NotNull Plugin plugin, @NotNull Consumer task, long delay) { + if (delay < 1) delay = 1; + + if (!FoliaScheduler.isFolia) { + return new TaskWrapper(bukkitScheduler.runTaskLater(plugin, () -> task.accept(null), delay)); + } + + return new TaskWrapper(globalRegionScheduler.runDelayed(plugin, (o) -> task.accept(null), delay)); + } + + /** + * Schedules a repeating task to be executed on the global region after the initial delay with the specified period. + * + * @param plugin The plugin that owns the task + * @param task The task to execute + * @param initialDelayTicks The initial delay, in ticks before the method is invoked. Any value less-than 1 is treated as 1. + * @param periodTicks The period, in ticks. Any value less-than 1 is treated as 1. + * @return {@link TaskWrapper} instance representing a wrapped task + */ + public TaskWrapper runAtFixedRate(@NotNull Plugin plugin, @NotNull Consumer task, long initialDelayTicks, long periodTicks) { + if (initialDelayTicks < 1) initialDelayTicks = 1; + if (periodTicks < 1) periodTicks = 1; + + if (!FoliaScheduler.isFolia) { + return new TaskWrapper(bukkitScheduler.runTaskTimer(plugin, () -> task.accept(null), initialDelayTicks, periodTicks)); + } + + return new TaskWrapper(globalRegionScheduler.runAtFixedRate(plugin, (o) -> task.accept(null), initialDelayTicks, periodTicks)); + } + + /** + * Attempts to cancel all tasks scheduled by the specified plugin. + * + * @param plugin Specified plugin. + */ + public void cancel(@NotNull Plugin plugin) { + if (!FoliaScheduler.isFolia) { + Bukkit.getScheduler().cancelTasks(plugin); + return; + } + + globalRegionScheduler.cancelTasks(plugin); + } +} \ No newline at end of file diff --git a/src/main/java/sh/okx/rankup/util/folia/RegionScheduler.java b/src/main/java/sh/okx/rankup/util/folia/RegionScheduler.java new file mode 100644 index 0000000..c28a0d1 --- /dev/null +++ b/src/main/java/sh/okx/rankup/util/folia/RegionScheduler.java @@ -0,0 +1,197 @@ +/* + * This file is part of packetevents - https://github.com/retrooper/packetevents + * Copyright (C) 2024 retrooper and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package sh.okx.rankup.util.folia; + +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.World; +import org.bukkit.plugin.Plugin; +import org.bukkit.scheduler.BukkitScheduler; +import org.jetbrains.annotations.NotNull; + +import java.util.function.Consumer; + +/** + * Represents a scheduler for executing region tasks + */ +public class RegionScheduler { + + private BukkitScheduler bukkitScheduler; + private io.papermc.paper.threadedregions.scheduler.RegionScheduler regionScheduler; + + protected RegionScheduler() { + if (FoliaScheduler.isFolia) { + regionScheduler = Bukkit.getRegionScheduler(); + } else { + bukkitScheduler = Bukkit.getScheduler(); + } + } + + /** + * Schedules a task to be executed on the region which owns the location. + * + * @param plugin The plugin that owns the task + * @param world The world of the region that owns the task + * @param chunkX The chunk X coordinate of the region that owns the task + * @param chunkZ The chunk Z coordinate of the region that owns the task + * @param run The task to execute + */ + public void execute(@NotNull Plugin plugin, @NotNull World world, int chunkX, int chunkZ, @NotNull Runnable run) { + if (!FoliaScheduler.isFolia) { + bukkitScheduler.runTask(plugin, run); + return; + } + + regionScheduler.execute(plugin, world, chunkX, chunkZ, run); + } + + /** + * Schedules a task to be executed on the region which owns the location. + * + * @param plugin The plugin that owns the task + * @param location The location at which the region executing should own + * @param run The task to execute + */ + public void execute(@NotNull Plugin plugin, @NotNull Location location, @NotNull Runnable run) { + if (!FoliaScheduler.isFolia) { + Bukkit.getScheduler().runTask(plugin, run); + return; + } + + regionScheduler.execute(plugin, location, run); + } + + /** + * Schedules a task to be executed on the region which owns the location on the next tick. + * + * @param plugin The plugin that owns the task + * @param world The world of the region that owns the task + * @param chunkX The chunk X coordinate of the region that owns the task + * @param chunkZ The chunk Z coordinate of the region that owns the task + * @param task The task to execute + * @return {@link TaskWrapper} instance representing a wrapped task + */ + public TaskWrapper run(@NotNull Plugin plugin, @NotNull World world, int chunkX, int chunkZ, @NotNull Consumer task) { + if (!FoliaScheduler.isFolia) { + return new TaskWrapper(Bukkit.getScheduler().runTask(plugin, () -> task.accept(null))); + } + + return new TaskWrapper(regionScheduler.run(plugin, world, chunkX, chunkZ, (o) -> task.accept(null))); + } + + /** + * Schedules a task to be executed on the region which owns the location on the next tick. + * + * @param plugin The plugin that owns the task + * @param location The location at which the region executing should own + * @param task The task to execute + * @return {@link TaskWrapper} instance representing a wrapped task + */ + public TaskWrapper run(@NotNull Plugin plugin, @NotNull Location location, @NotNull Consumer task) { + if (!FoliaScheduler.isFolia) { + return new TaskWrapper(Bukkit.getScheduler().runTask(plugin, () -> task.accept(null))); + } + + return new TaskWrapper(regionScheduler.run(plugin, location, (o) -> task.accept(null))); + } + + /** + * Schedules a task to be executed on the region which owns the location after the specified delay in ticks. + * + * @param plugin The plugin that owns the task + * @param world The world of the region that owns the task + * @param chunkX The chunk X coordinate of the region that owns the task + * @param chunkZ The chunk Z coordinate of the region that owns the task + * @param task The task to execute + * @param delayTicks The delay, in ticks before the method is invoked. Any value less-than 1 is treated as 1. + * @return {@link TaskWrapper} instance representing a wrapped task + */ + public TaskWrapper runDelayed(@NotNull Plugin plugin, @NotNull World world, int chunkX, int chunkZ, @NotNull Consumer task, long delayTicks) { + if (delayTicks < 1) delayTicks = 1; + + if (!FoliaScheduler.isFolia) { + return new TaskWrapper(Bukkit.getScheduler().runTaskLater(plugin, () -> task.accept(null), delayTicks)); + } + + return new TaskWrapper(regionScheduler.runDelayed(plugin, world, chunkX, chunkZ, (o) -> task.accept(null), delayTicks)); + } + + /** + * Schedules a task to be executed on the region which owns the location after the specified delay in ticks. + * + * @param plugin The plugin that owns the task + * @param location The location at which the region executing should own + * @param task The task to execute + * @param delayTicks The delay, in ticks before the method is invoked. Any value less-than 1 is treated as 1. + * @return {@link TaskWrapper} instance representing a wrapped task + */ + public TaskWrapper runDelayed(@NotNull Plugin plugin, @NotNull Location location, @NotNull Consumer task, long delayTicks) { + if (delayTicks < 1) delayTicks = 1; + + if (!FoliaScheduler.isFolia) { + return new TaskWrapper(Bukkit.getScheduler().runTaskLater(plugin, () -> task.accept(null), delayTicks)); + } + + return new TaskWrapper(regionScheduler.runDelayed(plugin, location, (o) -> task.accept(null), delayTicks)); + } + + /** + * Schedules a repeating task to be executed on the region which owns the location after the initial delay with the specified period. + * + * @param plugin The plugin that owns the task + * @param world The world of the region that owns the task + * @param chunkX The chunk X coordinate of the region that owns the task + * @param chunkZ The chunk Z coordinate of the region that owns the task + * @param task The task to execute + * @param initialDelayTicks The initial delay, in ticks before the method is invoked. Any value less-than 1 is treated as 1. + * @param periodTicks The period, in ticks. Any value less-than 1 is treated as 1. + * @return {@link TaskWrapper} instance representing a wrapped task + */ + public TaskWrapper runAtFixedRate(@NotNull Plugin plugin, @NotNull World world, int chunkX, int chunkZ, @NotNull Consumer task, long initialDelayTicks, long periodTicks) { + if (initialDelayTicks < 1) initialDelayTicks = 1; + if (periodTicks < 1) periodTicks = 1; + + if (!FoliaScheduler.isFolia) { + return new TaskWrapper(Bukkit.getScheduler().runTaskTimer(plugin, () -> task.accept(null), initialDelayTicks, periodTicks)); + } + + return new TaskWrapper(regionScheduler.runAtFixedRate(plugin, world, chunkX, chunkZ, (o) -> task.accept(null), initialDelayTicks, periodTicks)); + } + + /** + * Schedules a repeating task to be executed on the region which owns the location after the initial delay with the specified period. + * + * @param plugin The plugin that owns the task + * @param location The location at which the region executing should own + * @param task The task to execute + * @param initialDelayTicks The initial delay, in ticks before the method is invoked. Any value less-than 1 is treated as 1. + * @param periodTicks The period, in ticks. Any value less-than 1 is treated as 1. + * @return {@link TaskWrapper} instance representing a wrapped task + */ + public TaskWrapper runAtFixedRate(@NotNull Plugin plugin, @NotNull Location location, @NotNull Consumer task, long initialDelayTicks, long periodTicks) { + if (initialDelayTicks < 1) initialDelayTicks = 1; + if (periodTicks < 1) periodTicks = 1; + + if (!FoliaScheduler.isFolia) { + return new TaskWrapper(Bukkit.getScheduler().runTaskTimer(plugin, () -> task.accept(null), initialDelayTicks, periodTicks)); + } + + return new TaskWrapper(regionScheduler.runAtFixedRate(plugin, location, (o) -> task.accept(null), initialDelayTicks, periodTicks)); + } +} \ No newline at end of file diff --git a/src/main/java/sh/okx/rankup/util/folia/TaskWrapper.java b/src/main/java/sh/okx/rankup/util/folia/TaskWrapper.java new file mode 100644 index 0000000..a339ddf --- /dev/null +++ b/src/main/java/sh/okx/rankup/util/folia/TaskWrapper.java @@ -0,0 +1,64 @@ +package sh.okx.rankup.util.folia; + +import io.papermc.paper.threadedregions.scheduler.ScheduledTask; +import org.bukkit.plugin.Plugin; +import org.bukkit.scheduler.BukkitTask; +import org.jetbrains.annotations.NotNull; + +/** + * Represents a wrapper around {@code BukkitTask} and Paper's {@code ScheduledTask}. + * This class provides a unified interface for interacting with both Bukkit's task scheduler + * and Paper's task scheduler. + */ +public class TaskWrapper { + + private BukkitTask bukkitTask; + private ScheduledTask scheduledTask; + + /** + * Constructs a new TaskWrapper around a BukkitTask. + * + * @param bukkitTask the BukkitTask to wrap + */ + public TaskWrapper(@NotNull BukkitTask bukkitTask) { + this.bukkitTask = bukkitTask; + } + + /** + * Constructs a new TaskWrapper around Paper's ScheduledTask. + * + * @param scheduledTask the ScheduledTask to wrap + */ + public TaskWrapper(@NotNull ScheduledTask scheduledTask) { + this.scheduledTask = scheduledTask; + } + + /** + * Retrieves the Plugin that owns this task. + * + * @return the owning {@link Plugin} + */ + public Plugin getOwner() { + return bukkitTask != null ? bukkitTask.getOwner() : scheduledTask.getOwningPlugin(); + } + + /** + * Checks if the task is canceled. + * + * @return true if the task is canceled, false otherwise + */ + public boolean isCancelled() { + return bukkitTask != null ? bukkitTask.isCancelled() : scheduledTask.isCancelled(); + } + + /** + * Cancels the task. If the task is running, it will be canceled. + */ + public void cancel() { + if (bukkitTask != null) { + bukkitTask.cancel(); + } else { + scheduledTask.cancel(); + } + } +} diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index d16c456..6bc766a 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -5,6 +5,7 @@ author: Okx depend: [Vault] softdepend: [PlaceholderAPI, mcMMO, AdvancedAchievements, Towny, SuperbVote, VotingPlugin, LuckPerms] api-version: 1.13 +folia-supported: true commands: rankup: From 00a56b5a72ee2fcf3fc405864a64ad6e315c4621 Mon Sep 17 00:00:00 2001 From: R00tB33rMan Date: Thu, 20 Feb 2025 22:22:32 -0500 Subject: [PATCH 2/4] Upstream & Cleanup --- build.gradle | 5 ++- src/main/java/sh/okx/rankup/RankupPlugin.java | 34 +++---------------- .../sh/okx/rankup/commands/InfoCommand.java | 8 ++--- src/main/java/sh/okx/rankup/ranks/Rank.java | 1 - 4 files changed, 11 insertions(+), 37 deletions(-) diff --git a/build.gradle b/build.gradle index 621d910..a962ffb 100644 --- a/build.gradle +++ b/build.gradle @@ -13,7 +13,7 @@ pitest { } group 'sh.okx' -version '3.15' +version '3.15.1' java { sourceCompatibility = JavaVersion.VERSION_21 @@ -45,14 +45,13 @@ repositories { dependencies { testImplementation 'org.junit.platform:junit-platform-launcher:1.8.2' testImplementation 'org.junit.jupiter:junit-jupiter-engine:5.8.2' - testImplementation 'com.github.MockBukkit:MockBukkit:v4.31.1' + testImplementation 'com.github.MockBukkit:MockBukkit:04889261630cd6f5aaebd86a576bbcd12c442ea7' implementation group: 'org.slf4j', name: 'slf4j-nop', version: '1.7.30' implementation group: 'org.slf4j', name: 'slf4j-api', version: '1.7.30' compileOnly 'org.jetbrains:annotations:22.0.0' compileOnly 'io.papermc.paper:paper-api:1.21.4-R0.1-SNAPSHOT' -// compileOnly 'org.spigotmc:spigot-api:1.21.4-R0.1-SNAPSHOT' compileOnly('com.github.Realizedd:TokenManager:3.2.4') { transitive = false } diff --git a/src/main/java/sh/okx/rankup/RankupPlugin.java b/src/main/java/sh/okx/rankup/RankupPlugin.java index 8fac418..b4510fa 100644 --- a/src/main/java/sh/okx/rankup/RankupPlugin.java +++ b/src/main/java/sh/okx/rankup/RankupPlugin.java @@ -11,8 +11,10 @@ import org.bukkit.entity.Player; import org.bukkit.event.inventory.InventoryType; import org.bukkit.inventory.InventoryView; +import org.bukkit.plugin.PluginDescriptionFile; import org.bukkit.plugin.PluginManager; import org.bukkit.plugin.java.JavaPlugin; +import org.bukkit.plugin.java.JavaPluginLoader; import sh.okx.rankup.commands.InfoCommand; import sh.okx.rankup.commands.MaxRankupCommand; import sh.okx.rankup.commands.PrestigeCommand; @@ -78,14 +80,11 @@ import sh.okx.rankup.serialization.RankSerialized; import sh.okx.rankup.serialization.ShadowDeserializer; import sh.okx.rankup.serialization.YamlDeserializer; -import sh.okx.rankup.util.UpdateNotifier; -import sh.okx.rankup.util.VersionChecker; import sh.okx.rankup.util.folia.FoliaScheduler; import java.io.File; import java.io.FileNotFoundException; import java.io.FileReader; -import java.util.HashMap; import java.util.List; import java.util.Map; @@ -123,37 +122,16 @@ public RankupPlugin() { super(); } - protected RankupPlugin(PermissionManager permissionManager, EconomyProvider economyProvider) { + protected RankupPlugin(JavaPluginLoader loader, PluginDescriptionFile description, File dataFolder, File file, PermissionManager permissionManager, EconomyProvider economyProvider) { + super(loader, description, dataFolder, file); this.permissionManager = permissionManager; this.economyProvider = economyProvider; } @Override public void onEnable() { -// UpdateNotifier notifier = new UpdateNotifier(new VersionChecker(this)); - reload(true); -// if (System.getProperty("RANKUP_TEST") == null) { -// Metrics metrics = new Metrics(this); -// metrics.addCustomChart(new Metrics.SimplePie("confirmation", -// () -> config.getString("confirmation-type", "unknown"))); -// metrics.addCustomChart(new Metrics.AdvancedPie("requirements", () -> { -// Map map = new HashMap<>(); -// addAllRequirements(map, rankups); -// if (prestiges != null) { -// addAllRequirements(map, prestiges); -// } -// return map; -// })); -// metrics.addCustomChart(new Metrics.SimplePie("prestige", -// () -> config.getBoolean("prestige") ? "enabled" : "disabled")); -// metrics.addCustomChart(new Metrics.SimplePie("permission-rankup", -// () -> config.getBoolean("permission-rankup") ? "enabled" : "disabled")); -// metrics.addCustomChart(new Metrics.SimplePie("notify-update", -// () -> config.getBoolean("notify-update") ? "enabled" : "disabled")); -// } - if (config.getBoolean("ranks")) { if (config.getBoolean("ranks-gui")) { RanksGuiListener listener = new RanksGuiListener(); @@ -174,10 +152,8 @@ public void onEnable() { } getCommand("rankup").setExecutor(new RankupCommand(this)); - getCommand("rankup3").setExecutor(new InfoCommand(this, null)); // notifier)); + getCommand("rankup3").setExecutor(new InfoCommand(this, null)); getServer().getPluginManager().registerEvents(new GuiListener(this), this); -// getServer().getPluginManager().registerEvents( -// new JoinUpdateNotifier(notifier, () -> getConfig().getBoolean("notify-update"), "rankup.notify"), this); placeholders = new Placeholders(this); placeholders.register(); diff --git a/src/main/java/sh/okx/rankup/commands/InfoCommand.java b/src/main/java/sh/okx/rankup/commands/InfoCommand.java index 2047b67..eb6b61e 100644 --- a/src/main/java/sh/okx/rankup/commands/InfoCommand.java +++ b/src/main/java/sh/okx/rankup/commands/InfoCommand.java @@ -319,11 +319,14 @@ public boolean onCommand(CommandSender sender, Command command, String label, St sender.sendMessage( ChatColor.GREEN + "" + ChatColor.BOLD + description.getName() + " " + version + ChatColor.YELLOW + " by " + ChatColor.BLUE + ChatColor.BOLD + String.join(", ", description.getAuthors())); + if (sender.hasPermission("rankup.reload")) { sender.sendMessage(ChatColor.GREEN + "/" + label + " reload " + ChatColor.YELLOW + "Reloads configuration files."); } + if (sender.hasPermission("rankup.force")) { sender.sendMessage(ChatColor.GREEN + "/" + label + " forcerankup " + ChatColor.YELLOW + "Force a player to rankup, bypassing requirements."); + if (plugin.getPrestiges() != null) { sender.sendMessage( ChatColor.GREEN + "/" + label + " forceprestige " + ChatColor.YELLOW @@ -331,14 +334,11 @@ public boolean onCommand(CommandSender sender, Command command, String label, St } sender.sendMessage(ChatColor.GREEN + "/" + label + " rankdown " + ChatColor.YELLOW + "Force a player to move down one rank."); } + if (sender.hasPermission("rankup.playtime")) { sender.sendMessage(ChatColor.GREEN + "/" + label + " playtime " + ChatColor.YELLOW + "View your playtime"); } - if (sender.hasPermission("rankup.checkversion")) { - //notifier.notify(sender, false); - } - return true; } diff --git a/src/main/java/sh/okx/rankup/ranks/Rank.java b/src/main/java/sh/okx/rankup/ranks/Rank.java index 0c04028..efb3ccd 100644 --- a/src/main/java/sh/okx/rankup/ranks/Rank.java +++ b/src/main/java/sh/okx/rankup/ranks/Rank.java @@ -58,7 +58,6 @@ public void runCommands(Player player, Rank next) { else runnable.run(); } - @Override public String toString() { return "Rank{" + From f1ab8f8aa43259e005e6cf19c59c802c643ae119 Mon Sep 17 00:00:00 2001 From: R00tB33rMan Date: Wed, 23 Apr 2025 04:39:11 -0400 Subject: [PATCH 3/4] Make VersionChecker and Metrics Folia-compatible --- src/main/java/sh/okx/rankup/Metrics.java | 55 ++++++++++--------- src/main/java/sh/okx/rankup/RankupPlugin.java | 33 +++++++++-- .../sh/okx/rankup/commands/InfoCommand.java | 4 ++ .../sh/okx/rankup/util/VersionChecker.java | 19 +++++-- .../okx/rankup/util/folia/AsyncScheduler.java | 2 +- .../rankup/util/folia/EntityScheduler.java | 2 +- .../okx/rankup/util/folia/FoliaScheduler.java | 2 +- .../util/folia/GlobalRegionScheduler.java | 2 +- .../rankup/util/folia/RegionScheduler.java | 2 +- 9 files changed, 81 insertions(+), 40 deletions(-) diff --git a/src/main/java/sh/okx/rankup/Metrics.java b/src/main/java/sh/okx/rankup/Metrics.java index c260714..60b0fa2 100644 --- a/src/main/java/sh/okx/rankup/Metrics.java +++ b/src/main/java/sh/okx/rankup/Metrics.java @@ -3,12 +3,14 @@ import com.google.gson.JsonArray; import com.google.gson.JsonObject; import com.google.gson.JsonParser; +import lombok.Getter; import org.bukkit.Bukkit; import org.bukkit.configuration.file.YamlConfiguration; import org.bukkit.entity.Player; import org.bukkit.plugin.Plugin; import org.bukkit.plugin.RegisteredServiceProvider; import org.bukkit.plugin.ServicePriority; +import sh.okx.rankup.util.folia.FoliaScheduler; import javax.net.ssl.HttpsURLConnection; import java.io.*; @@ -50,7 +52,8 @@ public class Metrics { private static final String URL = "https://bStats.org/submitData/bukkit"; // Is bStats enabled on this server? - private boolean enabled; + @Getter + private final boolean enabled; // Should failed requests be logged? private static boolean logFailedRequests; @@ -138,15 +141,6 @@ public Metrics(Plugin plugin) { } } - /** - * Checks if bStats is enabled. - * - * @return Whether bStats is enabled or not. - */ - public boolean isEnabled() { - return enabled; - } - /** * Adds a custom chart. * @@ -163,19 +157,30 @@ public void addCustomChart(CustomChart chart) { * Starts the Scheduler which submits our data every 30 minutes. */ private void startSubmitting() { - final Timer timer = new Timer(true); // We use a timer cause the Bukkit scheduler is affected by server lags - timer.scheduleAtFixedRate(new TimerTask() { - @Override - public void run() { - if (!plugin.isEnabled()) { // Plugin was disabled - timer.cancel(); - return; + if (FoliaScheduler.isFolia()) { + FoliaScheduler.getGlobalRegionScheduler().runAtFixedRate(plugin, + (ignored) -> { + if (!plugin.isEnabled()) { // Plugin was disabled + return; + } + submitData(); + }, 20L * 60 * 5, 20L * 60 * 30 + ); + } else { + final Timer timer = new Timer(true); // We use a timer cause the Bukkit scheduler is affected by server lags + timer.scheduleAtFixedRate(new TimerTask() { + @Override + public void run() { + if (!plugin.isEnabled()) { // Plugin was disabled + timer.cancel(); + return; + } + // Nevertheless we want our code to run in the Bukkit main thread, so we have to use the Bukkit scheduler + // Don't be afraid! The connection to the bStats server is still async, only the stats collection is sync ;) + Bukkit.getScheduler().runTask(plugin, () -> submitData()); } - // Nevertheless we want our code to run in the Bukkit main thread, so we have to use the Bukkit scheduler - // Don't be afraid! The connection to the bStats server is still async, only the stats collection is sync ;) - Bukkit.getScheduler().runTask(plugin, () -> submitData()); - } - }, 1000 * 60 * 5, 1000 * 60 * 30); + }, 1000 * 60 * 5, 1000 * 60 * 30); + } // Submit the data every 30 minutes, first time after 5 minutes to give other plugins enough time to start // WARNING: Changing the frequency has no effect but your plugin WILL be blocked/deleted! // WARNING: Just don't do it! @@ -257,7 +262,7 @@ private JsonObject getServerData() { } /** - * Collects the data and sends it afterwards. + * Collects the data and sends it afterward. */ private void submitData() { final JsonObject data = getServerData(); @@ -280,7 +285,7 @@ private void submitData() { Method jsonStringGetter = jsonObjectJsonSimple.getDeclaredMethod("toJSONString"); jsonStringGetter.setAccessible(true); String jsonString = (String) jsonStringGetter.invoke(plugin); - JsonObject object = new JsonParser().parse(jsonString).getAsJsonObject(); + JsonObject object = JsonParser.parseString(jsonString).getAsJsonObject(); pluginData.add(object); } } catch (ClassNotFoundException e) { @@ -330,7 +335,7 @@ private static void sendData(Plugin plugin, JsonObject data) throws Exception { throw new IllegalAccessException("This method must not be called from the main thread!"); } if (logSentData) { - plugin.getLogger().info("Sending data to bStats: " + data.toString()); + plugin.getLogger().info("Sending data to bStats: " + data); } HttpsURLConnection connection = (HttpsURLConnection) new URL(URL).openConnection(); diff --git a/src/main/java/sh/okx/rankup/RankupPlugin.java b/src/main/java/sh/okx/rankup/RankupPlugin.java index b4510fa..814f4dc 100644 --- a/src/main/java/sh/okx/rankup/RankupPlugin.java +++ b/src/main/java/sh/okx/rankup/RankupPlugin.java @@ -80,11 +80,14 @@ import sh.okx.rankup.serialization.RankSerialized; import sh.okx.rankup.serialization.ShadowDeserializer; import sh.okx.rankup.serialization.YamlDeserializer; +import sh.okx.rankup.util.UpdateNotifier; +import sh.okx.rankup.util.VersionChecker; import sh.okx.rankup.util.folia.FoliaScheduler; import java.io.File; import java.io.FileNotFoundException; import java.io.FileReader; +import java.util.HashMap; import java.util.List; import java.util.Map; @@ -130,8 +133,30 @@ protected RankupPlugin(JavaPluginLoader loader, PluginDescriptionFile descriptio @Override public void onEnable() { + UpdateNotifier notifier = new UpdateNotifier(new VersionChecker(this)); + reload(true); + if (System.getProperty("RANKUP_TEST") == null) { + Metrics metrics = new Metrics(this); + metrics.addCustomChart(new Metrics.SimplePie("confirmation", + () -> config.getString("confirmation-type", "unknown"))); + metrics.addCustomChart(new Metrics.AdvancedPie("requirements", () -> { + Map map = new HashMap<>(); + addAllRequirements(map, rankups); + if (prestiges != null) { + addAllRequirements(map, prestiges); + } + return map; + })); + metrics.addCustomChart(new Metrics.SimplePie("prestige", + () -> config.getBoolean("prestige") ? "enabled" : "disabled")); + metrics.addCustomChart(new Metrics.SimplePie("permission-rankup", + () -> config.getBoolean("permission-rankup") ? "enabled" : "disabled")); + metrics.addCustomChart(new Metrics.SimplePie("notify-update", + () -> config.getBoolean("notify-update") ? "enabled" : "disabled")); + } + if (config.getBoolean("ranks")) { if (config.getBoolean("ranks-gui")) { RanksGuiListener listener = new RanksGuiListener(); @@ -152,8 +177,10 @@ public void onEnable() { } getCommand("rankup").setExecutor(new RankupCommand(this)); - getCommand("rankup3").setExecutor(new InfoCommand(this, null)); + getCommand("rankup3").setExecutor(new InfoCommand(this, notifier)); getServer().getPluginManager().registerEvents(new GuiListener(this), this); + getServer().getPluginManager().registerEvents( + new JoinUpdateNotifier(notifier, () -> getConfig().getBoolean("notify-update"), "rankup.notify"), this); placeholders = new Placeholders(this); placeholders.register(); @@ -302,9 +329,6 @@ public void refreshRanks() { rankups = new Rankups(this, loadRankupConfig("rankups")); // check rankups are not in an infinite loop // rankups.getOrderedList(); - - - } catch (RuntimeException e) { this.errorMessage = e.getClass().getName() + ": " + e.getMessage(); e.printStackTrace(); @@ -413,6 +437,7 @@ private void registerRequirements() { requirements.addRequirements(new SuperbVoteVotesRequirement(this)); } } + private void setupEconomy() { economy = economyProvider.getEconomy(); } diff --git a/src/main/java/sh/okx/rankup/commands/InfoCommand.java b/src/main/java/sh/okx/rankup/commands/InfoCommand.java index eb6b61e..b45619d 100644 --- a/src/main/java/sh/okx/rankup/commands/InfoCommand.java +++ b/src/main/java/sh/okx/rankup/commands/InfoCommand.java @@ -339,6 +339,10 @@ public boolean onCommand(CommandSender sender, Command command, String label, St sender.sendMessage(ChatColor.GREEN + "/" + label + " playtime " + ChatColor.YELLOW + "View your playtime"); } + if (sender.hasPermission("rankup.checkversion")) { + notifier.notify(sender, false); + } + return true; } diff --git a/src/main/java/sh/okx/rankup/util/VersionChecker.java b/src/main/java/sh/okx/rankup/util/VersionChecker.java index e4d5692..520d18a 100644 --- a/src/main/java/sh/okx/rankup/util/VersionChecker.java +++ b/src/main/java/sh/okx/rankup/util/VersionChecker.java @@ -5,12 +5,15 @@ import java.io.IOException; import java.io.InputStreamReader; import java.net.URL; +import lombok.Getter; import org.bukkit.Bukkit; import org.bukkit.plugin.Plugin; +import sh.okx.rankup.util.folia.FoliaScheduler; public class VersionChecker { public static final int RESOURCE_ID = 76964; + @Getter private final Plugin plugin; private final String currentVersion; private String latestVersion; @@ -21,10 +24,6 @@ public VersionChecker(Plugin plugin) { this.plugin = plugin; } - public Plugin getPlugin() { - return plugin; - } - /** * Checks if the version checker has already made an asynchronous call to the web server to check * the version, so future checks will run instantly. @@ -48,7 +47,11 @@ public void checkVersion(VersionCheckerCallback callback) { checked = true; callback.onPreReleaseVersion(currentVersion); } else { - Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> checkVersionAsync(callback)); + if (FoliaScheduler.isFolia()) { + FoliaScheduler.getAsyncScheduler().runNow(plugin, ignored -> checkVersionAsync(callback)); + } else { + Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> checkVersionAsync(callback)); + } } } @@ -144,7 +147,11 @@ public void onFailure() { } private void doSync(Runnable r) { - Bukkit.getScheduler().scheduleSyncDelayedTask(plugin, r); + if (FoliaScheduler.isFolia()) { + FoliaScheduler.getGlobalRegionScheduler().execute(plugin, r); + } else { + Bukkit.getScheduler().scheduleSyncDelayedTask(plugin, r); + } } } } diff --git a/src/main/java/sh/okx/rankup/util/folia/AsyncScheduler.java b/src/main/java/sh/okx/rankup/util/folia/AsyncScheduler.java index 42e71e4..f291c1f 100644 --- a/src/main/java/sh/okx/rankup/util/folia/AsyncScheduler.java +++ b/src/main/java/sh/okx/rankup/util/folia/AsyncScheduler.java @@ -137,4 +137,4 @@ public void cancel(@NotNull Plugin plugin) { private long convertTimeToTicks(long time, TimeUnit timeUnit) { return timeUnit.toMillis(time) / 50; } -} \ No newline at end of file +} diff --git a/src/main/java/sh/okx/rankup/util/folia/EntityScheduler.java b/src/main/java/sh/okx/rankup/util/folia/EntityScheduler.java index 0f7952f..3d3ae38 100644 --- a/src/main/java/sh/okx/rankup/util/folia/EntityScheduler.java +++ b/src/main/java/sh/okx/rankup/util/folia/EntityScheduler.java @@ -132,4 +132,4 @@ public TaskWrapper runAtFixedRate(@NotNull Entity entity, @NotNull Plugin plugin return new TaskWrapper(entity.getScheduler().runAtFixedRate(plugin, (o) -> task.accept(null), retired, initialDelayTicks, periodTicks)); } -} \ No newline at end of file +} diff --git a/src/main/java/sh/okx/rankup/util/folia/FoliaScheduler.java b/src/main/java/sh/okx/rankup/util/folia/FoliaScheduler.java index 02133d1..6bef27a 100644 --- a/src/main/java/sh/okx/rankup/util/folia/FoliaScheduler.java +++ b/src/main/java/sh/okx/rankup/util/folia/FoliaScheduler.java @@ -127,4 +127,4 @@ public static void runTaskOnInit(Plugin plugin, Runnable run) { Bukkit.getServer().getPluginManager().registerEvent(regionizedServerInitEventClass, new Listener() { }, EventPriority.HIGHEST, (listener, event) -> run.run(), plugin); } -} \ No newline at end of file +} diff --git a/src/main/java/sh/okx/rankup/util/folia/GlobalRegionScheduler.java b/src/main/java/sh/okx/rankup/util/folia/GlobalRegionScheduler.java index 6d736d0..9e49651 100644 --- a/src/main/java/sh/okx/rankup/util/folia/GlobalRegionScheduler.java +++ b/src/main/java/sh/okx/rankup/util/folia/GlobalRegionScheduler.java @@ -122,4 +122,4 @@ public void cancel(@NotNull Plugin plugin) { globalRegionScheduler.cancelTasks(plugin); } -} \ No newline at end of file +} diff --git a/src/main/java/sh/okx/rankup/util/folia/RegionScheduler.java b/src/main/java/sh/okx/rankup/util/folia/RegionScheduler.java index c28a0d1..a010186 100644 --- a/src/main/java/sh/okx/rankup/util/folia/RegionScheduler.java +++ b/src/main/java/sh/okx/rankup/util/folia/RegionScheduler.java @@ -194,4 +194,4 @@ public TaskWrapper runAtFixedRate(@NotNull Plugin plugin, @NotNull Location loca return new TaskWrapper(regionScheduler.runAtFixedRate(plugin, location, (o) -> task.accept(null), initialDelayTicks, periodTicks)); } -} \ No newline at end of file +} From af1e71bed82f1d53698953ee9f541a9f4edd1483 Mon Sep 17 00:00:00 2001 From: R00tB33rMan Date: Mon, 26 May 2025 21:49:13 -0400 Subject: [PATCH 4/4] World may be null during shutdown on Folia, so ignore it --- src/main/java/sh/okx/rankup/RankupPlugin.java | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/main/java/sh/okx/rankup/RankupPlugin.java b/src/main/java/sh/okx/rankup/RankupPlugin.java index 814f4dc..4fd8296 100644 --- a/src/main/java/sh/okx/rankup/RankupPlugin.java +++ b/src/main/java/sh/okx/rankup/RankupPlugin.java @@ -281,15 +281,18 @@ private void addAllRequirements(Map map, RankList