From dde38dfbdcf35f44ea6227914ffe11c718b7e0ed Mon Sep 17 00:00:00 2001 From: PuppyTransGirl <74014559+PuppyTransGirl@users.noreply.github.com> Date: Fri, 20 Feb 2026 22:49:39 +0100 Subject: [PATCH 1/4] Add Folia support --- build.gradle.kts | 2 ++ .../menu/component/interactive/Button.java | 9 +++--- .../interactive/DoubleDropButton.java | 5 ++-- .../niveriaapi/utils/ParticleUtils.java | 9 +++--- .../toutouchien/niveriaapi/utils/Task.java | 30 +++++++++---------- src/main/resources/paper-plugin.yml | 4 ++- src/test/resources/paper-plugin.yml | 4 ++- 7 files changed, 36 insertions(+), 27 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 5f4808ec..7f3de86e 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -76,6 +76,8 @@ paperweight { addServerDependencyTo = configurations.named(JavaPlugin.COMPILE_ONLY_CONFIGURATION_NAME).map { setOf(it) } } +runPaper.folia.registerTask() + tasks { runServer { minecraftVersion(minecraftVersion) diff --git a/src/main/java/toutouchien/niveriaapi/menu/component/interactive/Button.java b/src/main/java/toutouchien/niveriaapi/menu/component/interactive/Button.java index 508005b7..d489d56d 100644 --- a/src/main/java/toutouchien/niveriaapi/menu/component/interactive/Button.java +++ b/src/main/java/toutouchien/niveriaapi/menu/component/interactive/Button.java @@ -1,6 +1,7 @@ package toutouchien.niveriaapi.menu.component.interactive; import com.google.common.base.Preconditions; +import io.papermc.paper.threadedregions.scheduler.ScheduledTask; import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import it.unimi.dsi.fastutil.objects.Object2ObjectLinkedOpenHashMap; import it.unimi.dsi.fastutil.objects.Object2ObjectMap; @@ -48,12 +49,12 @@ public class Button extends MenuComponent { @Nullable private Function> animationFrames; private int animationInterval; private boolean stopAnimationOnHide; - @Nullable private BukkitTask animationTask; + @Nullable private ScheduledTask animationTask; private int currentFrame; private int updateInterval; private boolean stopUpdatesOnHide; - @Nullable private BukkitTask updateTask; + @Nullable private ScheduledTask updateTask; /** * Constructs a new Button with the specified configuration. @@ -174,7 +175,7 @@ public Int2ObjectMap items(MenuContext context) { * @param context the menu context */ private void startAnimation(MenuContext context) { - this.animationTask = Task.syncRepeat(() -> { + this.animationTask = Task.syncRepeat(ignored -> { if (!enabled() || (this.stopAnimationOnHide && !visible())) { stopAnimation(); return; @@ -213,7 +214,7 @@ private void stopAnimation() { * @param context the menu context */ private void startUpdates(MenuContext context) { - this.updateTask = Task.syncRepeat(() -> { + this.updateTask = Task.syncRepeat(ignored -> { if (!enabled() || (this.stopUpdatesOnHide && !visible())) { stopUpdates(); return; diff --git a/src/main/java/toutouchien/niveriaapi/menu/component/interactive/DoubleDropButton.java b/src/main/java/toutouchien/niveriaapi/menu/component/interactive/DoubleDropButton.java index 42bcd211..282f7fb2 100644 --- a/src/main/java/toutouchien/niveriaapi/menu/component/interactive/DoubleDropButton.java +++ b/src/main/java/toutouchien/niveriaapi/menu/component/interactive/DoubleDropButton.java @@ -1,6 +1,7 @@ package toutouchien.niveriaapi.menu.component.interactive; import com.google.common.base.Preconditions; +import io.papermc.paper.threadedregions.scheduler.ScheduledTask; import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import it.unimi.dsi.fastutil.objects.Object2ObjectLinkedOpenHashMap; import it.unimi.dsi.fastutil.objects.Object2ObjectMap; @@ -44,7 +45,7 @@ public class DoubleDropButton extends MenuComponent { @Nullable private Sound sound; - @Nullable private BukkitTask dropTask; + @Nullable private ScheduledTask dropTask; /** * Constructs a new DoubleDropButton with the specified configuration. @@ -149,7 +150,7 @@ private void handleDropClick(NiveriaInventoryClickEvent event, MenuContext conte if (this.onDoubleDrop != null) this.onDoubleDrop.accept(event); } else { - this.dropTask = Task.syncLater(() -> { + this.dropTask = Task.syncLater(ignored -> { this.dropTask = null; render(context); }, NiveriaAPI.instance(), 3L, TimeUnit.SECONDS); diff --git a/src/main/java/toutouchien/niveriaapi/utils/ParticleUtils.java b/src/main/java/toutouchien/niveriaapi/utils/ParticleUtils.java index 02927f76..df51b718 100644 --- a/src/main/java/toutouchien/niveriaapi/utils/ParticleUtils.java +++ b/src/main/java/toutouchien/niveriaapi/utils/ParticleUtils.java @@ -1,5 +1,6 @@ package toutouchien.niveriaapi.utils; +import io.papermc.paper.threadedregions.scheduler.ScheduledTask; import net.minecraft.network.protocol.game.ClientboundLevelParticlesPacket; import org.bukkit.*; import org.bukkit.craftbukkit.CraftParticle; @@ -511,8 +512,8 @@ public static void drawCube(Location corner1, Location corner2, Particle particl * @param interval interval in ticks between spawns * @return the scheduled {@link BukkitTask} */ - public static BukkitTask followEntity(Entity entity, Plugin plugin, Particle particle, int count, double offsetX, double offsetY, double offsetZ, double speed, long durationTicks, long interval) { - BukkitTask task = Task.syncRepeat(() -> { + public static ScheduledTask followEntity(Entity entity, Plugin plugin, Particle particle, int count, double offsetX, double offsetY, double offsetZ, double speed, long durationTicks, long interval) { + ScheduledTask task = Task.syncRepeat(ignored -> { if (!entity.isValid()) return; @@ -567,7 +568,7 @@ public static void drawParametricCurve(Location center, Particle particle, Doubl public static void animateParticles(Plugin plugin, List frames, Particle particle, int count, double offsetX, double offsetY, double offsetZ, double speed, long ticksPerFrame, long durationTicks, boolean loop) { final int[] currentFrame = {0}; - BukkitTask task = Task.syncRepeat(() -> { + ScheduledTask task = Task.syncRepeat(ignored -> { if (currentFrame[0] >= frames.size() && loop) currentFrame[0] = 0; // Loop animation (optional) @@ -615,4 +616,4 @@ public static void drawGradient(Location start, Location end, int steps, DoubleF spawnParticle(particleLocation, Particle.DUST, 1, offsetX, offsetY, offsetZ, speed, dustOptions, false); } } -} \ No newline at end of file +} diff --git a/src/main/java/toutouchien/niveriaapi/utils/Task.java b/src/main/java/toutouchien/niveriaapi/utils/Task.java index 22910427..d0cd86b9 100644 --- a/src/main/java/toutouchien/niveriaapi/utils/Task.java +++ b/src/main/java/toutouchien/niveriaapi/utils/Task.java @@ -23,53 +23,53 @@ private Task() { /** * Schedules a synchronous task to run immediately. * - * @param runnable The task to run. + * @param consumer The task to run. * @param plugin The plugin scheduling the task. - * @return The scheduled BukkitTask. + * @return The scheduled ScheduledTask. */ - public static BukkitTask sync(Runnable runnable, Plugin plugin) { - Preconditions.checkNotNull(runnable, "runnable cannot be null"); + public static ScheduledTask sync(Consumer consumer, Plugin plugin) { + Preconditions.checkNotNull(consumer, "consumer cannot be null"); Preconditions.checkNotNull(plugin, "plugin cannot be null"); - return Bukkit.getScheduler().runTask(plugin, runnable); + return Bukkit.getGlobalRegionScheduler().run(plugin, consumer); } /** * Schedules a synchronous task to run after a specified delay. * - * @param runnable The task to run. + * @param consumer The task to run. * @param plugin The plugin scheduling the task. * @param delay The delay before the task is executed. * @param timeUnit The time unit of the delay. - * @return The scheduled BukkitTask. + * @return The scheduled ScheduledTask. */ - public static BukkitTask syncLater(Runnable runnable, Plugin plugin, @NonNegative long delay, TimeUnit timeUnit) { - Preconditions.checkNotNull(runnable, "runnable cannot be null"); + public static ScheduledTask syncLater(Consumer consumer, Plugin plugin, @NonNegative long delay, TimeUnit timeUnit) { + Preconditions.checkNotNull(consumer, "consumer cannot be null"); Preconditions.checkNotNull(plugin, "plugin cannot be null"); Preconditions.checkArgument(delay >= 0, "delay cannot be less than 0: %s", delay); Preconditions.checkNotNull(timeUnit, "timeUnit cannot be null"); - return Bukkit.getScheduler().runTaskLater(plugin, runnable, timeUnit.toMillis(delay) / 50); + return Bukkit.getGlobalRegionScheduler().runDelayed(plugin, consumer, timeUnit.toMillis(delay) / 50); } /** * Schedules a synchronous repeating task. * - * @param runnable The task to run. + * @param consumer The task to run. * @param plugin The plugin scheduling the task. * @param delay The delay before the first execution. * @param interval The interval between subsequent executions. * @param timeUnit The time unit of the delay and interval. - * @return The scheduled BukkitTask. + * @return The scheduled ScheduledTask. */ - public static BukkitTask syncRepeat(Runnable runnable, Plugin plugin, @NonNegative long delay, @NonNegative long interval, TimeUnit timeUnit) { - Preconditions.checkNotNull(runnable, "runnable cannot be null"); + public static ScheduledTask syncRepeat(Consumer consumer, Plugin plugin, @NonNegative long delay, @NonNegative long interval, TimeUnit timeUnit) { + Preconditions.checkNotNull(consumer, "consumer cannot be null"); Preconditions.checkNotNull(plugin, "plugin cannot be null"); Preconditions.checkArgument(delay >= 0, "delay cannot be less than 0: %s", delay); Preconditions.checkArgument(interval >= 0, "interval cannot be less than 0: %s", interval); Preconditions.checkNotNull(timeUnit, "timeUnit cannot be null"); - return Bukkit.getScheduler().runTaskTimer(plugin, runnable, timeUnit.toMillis(delay) / 50, timeUnit.toMillis(interval) / 50); + return Bukkit.getGlobalRegionScheduler().runAtFixedRate(plugin, consumer, timeUnit.toMillis(delay) / 50, timeUnit.toMillis(interval) / 50); } /** diff --git a/src/main/resources/paper-plugin.yml b/src/main/resources/paper-plugin.yml index 7f98e920..68ded3a8 100644 --- a/src/main/resources/paper-plugin.yml +++ b/src/main/resources/paper-plugin.yml @@ -8,6 +8,8 @@ load: STARTUP authors: [ Toutouchien ] website: https://niveria.fr +folia-supported: true + dependencies: server: BlueMap: @@ -44,4 +46,4 @@ dependencies: WorldGuard: load: BEFORE - required: false \ No newline at end of file + required: false diff --git a/src/test/resources/paper-plugin.yml b/src/test/resources/paper-plugin.yml index b8fdfe72..851cc7cc 100644 --- a/src/test/resources/paper-plugin.yml +++ b/src/test/resources/paper-plugin.yml @@ -6,4 +6,6 @@ bootstrapper: toutouchien.niveriaapi.NiveriaAPIBootstrap api-version: '${minMinecraftVersion}' load: STARTUP authors: [ Toutouchien ] -website: https://niveria.fr \ No newline at end of file +website: https://niveria.fr + +folia-supported: true From 157cb3158d10762967b74d12de59da423d58b4e3 Mon Sep 17 00:00:00 2001 From: PuppyTransGirl <74014559+PuppyTransGirl@users.noreply.github.com> Date: Sat, 21 Feb 2026 00:02:10 +0100 Subject: [PATCH 2/4] Improve Folia support --- .../niveriaapi/utils/ParticleUtils.java | 11 +- .../toutouchien/niveriaapi/utils/Task.java | 209 +++++++++++++++++- 2 files changed, 202 insertions(+), 18 deletions(-) diff --git a/src/main/java/toutouchien/niveriaapi/utils/ParticleUtils.java b/src/main/java/toutouchien/niveriaapi/utils/ParticleUtils.java index df51b718..5e8a4ddd 100644 --- a/src/main/java/toutouchien/niveriaapi/utils/ParticleUtils.java +++ b/src/main/java/toutouchien/niveriaapi/utils/ParticleUtils.java @@ -7,7 +7,6 @@ import org.bukkit.entity.Entity; import org.bukkit.entity.Player; import org.bukkit.plugin.Plugin; -import org.bukkit.scheduler.BukkitTask; import org.bukkit.util.Vector; import toutouchien.niveriaapi.annotations.Shivery; @@ -510,15 +509,15 @@ public static void drawCube(Location corner1, Location corner2, Particle particl * @param speed particle speed * @param durationTicks total duration in ticks, or -1 to run indefinitely * @param interval interval in ticks between spawns - * @return the scheduled {@link BukkitTask} + * @return the scheduled {@link ScheduledTask} */ public static ScheduledTask followEntity(Entity entity, Plugin plugin, Particle particle, int count, double offsetX, double offsetY, double offsetZ, double speed, long durationTicks, long interval) { - ScheduledTask task = Task.syncRepeat(ignored -> { + ScheduledTask task = Task.runRepeat(ignored -> { if (!entity.isValid()) return; spawnParticle(entity.getLocation().add(0, 1, 0), particle, count, offsetX, offsetY, offsetZ, speed, null, false); - }, plugin, 0, interval * 50L, TimeUnit.MILLISECONDS); + }, plugin, entity, 0, interval * 50L, TimeUnit.MILLISECONDS); if (durationTicks == -1) return task; @@ -568,7 +567,7 @@ public static void drawParametricCurve(Location center, Particle particle, Doubl public static void animateParticles(Plugin plugin, List frames, Particle particle, int count, double offsetX, double offsetY, double offsetZ, double speed, long ticksPerFrame, long durationTicks, boolean loop) { final int[] currentFrame = {0}; - ScheduledTask task = Task.syncRepeat(ignored -> { + ScheduledTask task = Task.runRepeat(ignored -> { if (currentFrame[0] >= frames.size() && loop) currentFrame[0] = 0; // Loop animation (optional) @@ -576,7 +575,7 @@ public static void animateParticles(Plugin plugin, List frames, Partic spawnParticle(location, particle, count, offsetX, offsetY, offsetZ, speed, null, false); currentFrame[0]++; - }, plugin, 0, ticksPerFrame * 50L, TimeUnit.MILLISECONDS); + }, plugin, frames.get(currentFrame[0]), 0, ticksPerFrame * 50L, TimeUnit.MILLISECONDS); if (durationTicks == -1) return; diff --git a/src/main/java/toutouchien/niveriaapi/utils/Task.java b/src/main/java/toutouchien/niveriaapi/utils/Task.java index d0cd86b9..deb6af27 100644 --- a/src/main/java/toutouchien/niveriaapi/utils/Task.java +++ b/src/main/java/toutouchien/niveriaapi/utils/Task.java @@ -3,8 +3,9 @@ import com.google.common.base.Preconditions; import io.papermc.paper.threadedregions.scheduler.ScheduledTask; import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.entity.Entity; import org.bukkit.plugin.Plugin; -import org.bukkit.scheduler.BukkitTask; import org.checkerframework.checker.index.qual.NonNegative; import org.jspecify.annotations.NullMarked; @@ -13,6 +14,10 @@ /** * Utility class for scheduling synchronous and asynchronous tasks in a server. + *

+ * Delays provided as (value, TimeUnit) are converted to server ticks (1 tick = 50 ms) + * using ceiling division and clamped to a minimum of 1 tick so tasks never schedule + * with zero delay. */ @NullMarked public final class Task { @@ -20,8 +25,13 @@ private Task() { throw new IllegalStateException("Utility class"); } + private static long millisToCeilTicks(long millis) { + long ticks = (millis + 49) / 50; // ceil to next tick + return Math.max(1L, ticks); + } + /** - * Schedules a synchronous task to run immediately. + * Schedules a synchronous task to run immediately (next tick). * * @param consumer The task to run. * @param plugin The plugin scheduling the task. @@ -36,6 +46,9 @@ public static ScheduledTask sync(Consumer consumer, Plugin plugin /** * Schedules a synchronous task to run after a specified delay. + *

+ * Delay is provided as (delay, timeUnit) and converted to ticks (1 tick = 50ms). + * Conversion uses ceiling and enforces a minimum of 1 tick. * * @param consumer The task to run. * @param plugin The plugin scheduling the task. @@ -46,14 +59,18 @@ public static ScheduledTask sync(Consumer consumer, Plugin plugin public static ScheduledTask syncLater(Consumer consumer, Plugin plugin, @NonNegative long delay, TimeUnit timeUnit) { Preconditions.checkNotNull(consumer, "consumer cannot be null"); Preconditions.checkNotNull(plugin, "plugin cannot be null"); - Preconditions.checkArgument(delay >= 0, "delay cannot be less than 0: %s", delay); Preconditions.checkNotNull(timeUnit, "timeUnit cannot be null"); - return Bukkit.getGlobalRegionScheduler().runDelayed(plugin, consumer, timeUnit.toMillis(delay) / 50); + long millis = timeUnit.toMillis(Math.max(1L, delay)); + long ticks = millisToCeilTicks(millis); + return Bukkit.getGlobalRegionScheduler().runDelayed(plugin, consumer, ticks); } /** * Schedules a synchronous repeating task. + *

+ * Delays and intervals are provided as (value, timeUnit) and converted to ticks (1 tick = 50ms). + * Conversion uses ceiling and enforces a minimum of 1 tick for both values. * * @param consumer The task to run. * @param plugin The plugin scheduling the task. @@ -65,11 +82,14 @@ public static ScheduledTask syncLater(Consumer consumer, Plugin p public static ScheduledTask syncRepeat(Consumer consumer, Plugin plugin, @NonNegative long delay, @NonNegative long interval, TimeUnit timeUnit) { Preconditions.checkNotNull(consumer, "consumer cannot be null"); Preconditions.checkNotNull(plugin, "plugin cannot be null"); - Preconditions.checkArgument(delay >= 0, "delay cannot be less than 0: %s", delay); - Preconditions.checkArgument(interval >= 0, "interval cannot be less than 0: %s", interval); Preconditions.checkNotNull(timeUnit, "timeUnit cannot be null"); - return Bukkit.getGlobalRegionScheduler().runAtFixedRate(plugin, consumer, timeUnit.toMillis(delay) / 50, timeUnit.toMillis(interval) / 50); + long delayMillis = timeUnit.toMillis(Math.max(1L, delay)); + long intervalMillis = timeUnit.toMillis(Math.max(1L, interval)); + long delayTicks = millisToCeilTicks(delayMillis); + long intervalTicks = millisToCeilTicks(intervalMillis); + + return Bukkit.getGlobalRegionScheduler().runAtFixedRate(plugin, consumer, delayTicks, intervalTicks); } /** @@ -88,6 +108,8 @@ public static ScheduledTask async(Consumer consumer, Plugin plugi /** * Schedules an asynchronous task to run after a specified delay. + *

+ * The provided delay is clamped to a minimum of 1 unit in the given timeUnit. * * @param consumer The task to run. * @param plugin The plugin scheduling the task. @@ -98,14 +120,16 @@ public static ScheduledTask async(Consumer consumer, Plugin plugi public static ScheduledTask asyncLater(Consumer consumer, Plugin plugin, @NonNegative long delay, TimeUnit timeUnit) { Preconditions.checkNotNull(consumer, "consumer cannot be null"); Preconditions.checkNotNull(plugin, "plugin cannot be null"); - Preconditions.checkArgument(delay >= 0, "delay cannot be less than 0: %s", delay); Preconditions.checkNotNull(timeUnit, "timeUnit cannot be null"); - return Bukkit.getAsyncScheduler().runDelayed(plugin, consumer, delay, timeUnit); + long d = Math.max(1L, delay); + return Bukkit.getAsyncScheduler().runDelayed(plugin, consumer, d, timeUnit); } /** * Schedules an asynchronous repeating task. + *

+ * The provided delay and interval are clamped to a minimum of 1 unit in the given timeUnit. * * @param consumer The task to run. * @param plugin The plugin scheduling the task. @@ -117,10 +141,171 @@ public static ScheduledTask asyncLater(Consumer consumer, Plugin public static ScheduledTask asyncRepeat(Consumer consumer, Plugin plugin, @NonNegative long delay, @NonNegative long interval, TimeUnit timeUnit) { Preconditions.checkNotNull(consumer, "consumer cannot be null"); Preconditions.checkNotNull(plugin, "plugin cannot be null"); - Preconditions.checkArgument(delay >= 0, "delay cannot be less than 0: %s", delay); - Preconditions.checkArgument(interval >= 0, "interval cannot be less than 0: %s", interval); Preconditions.checkNotNull(timeUnit, "timeUnit cannot be null"); - return Bukkit.getAsyncScheduler().runAtFixedRate(plugin, consumer, delay, interval, timeUnit); + long d = Math.max(1L, delay); + long i = Math.max(1L, interval); + return Bukkit.getAsyncScheduler().runAtFixedRate(plugin, consumer, d, i, timeUnit); + } + + /** + * Schedules a synchronous task to be executed on the region which owns the given location on the next tick. + * + * @param consumer The task to run. + * @param plugin The plugin scheduling the task. + * @param location The location whose region should run the task. + * @return The scheduled ScheduledTask. + */ + public static ScheduledTask run(Consumer consumer, Plugin plugin, Location location) { + Preconditions.checkNotNull(consumer, "consumer cannot be null"); + Preconditions.checkNotNull(plugin, "plugin cannot be null"); + Preconditions.checkNotNull(location, "location cannot be null"); + + return Bukkit.getRegionScheduler().run( + plugin, + location, + consumer + ); + } + + /** + * Schedules a synchronous task to be executed on the region which owns the given location after the specified + * delay. + *

+ * The delay is provided with a TimeUnit and converted to server ticks (1 tick = 50 ms). + * Conversion uses ceiling division and enforces a minimum of 1 tick. + * + * @param consumer The task to run. + * @param plugin The plugin scheduling the task. + * @param location The location whose region should run the task. + * @param delay The delay before execution. + * @param timeUnit The time unit of the delay. + * @return The scheduled ScheduledTask. + */ + public static ScheduledTask runDelayed(Consumer consumer, Plugin plugin, Location location, + @NonNegative long delay, TimeUnit timeUnit) { + Preconditions.checkNotNull(consumer, "consumer cannot be null"); + Preconditions.checkNotNull(plugin, "plugin cannot be null"); + Preconditions.checkNotNull(location, "location cannot be null"); + Preconditions.checkNotNull(timeUnit, "timeUnit cannot be null"); + + long millis = timeUnit.toMillis(Math.max(1L, delay)); + long ticks = millisToCeilTicks(millis); + return Bukkit.getRegionScheduler().runDelayed( + plugin, + location, + consumer, + ticks + ); + } + + /** + * Schedules a synchronous repeating task to be executed on the region which owns the given location after the + * initial delay with the specified period. + *

+ * The delays are provided with a TimeUnit and converted to server ticks (1 tick = 50 ms). + * Conversion uses ceiling division and enforces a minimum of 1 tick. + * + * @param consumer The task to run. + * @param plugin The plugin scheduling the task. + * @param location The location whose region should run the task. + * @param initialDelay The initial delay. + * @param period The period between executions. + * @param timeUnit The time unit of the delays. + * @return The scheduled ScheduledTask. + */ + public static ScheduledTask runRepeat(Consumer consumer, Plugin plugin, Location location, + @NonNegative long initialDelay, @NonNegative long period, TimeUnit timeUnit) { + Preconditions.checkNotNull(consumer, "consumer cannot be null"); + Preconditions.checkNotNull(plugin, "plugin cannot be null"); + Preconditions.checkNotNull(location, "location cannot be null"); + Preconditions.checkNotNull(timeUnit, "timeUnit cannot be null"); + + long initialMillis = timeUnit.toMillis(Math.max(1L, initialDelay)); + long periodMillis = timeUnit.toMillis(Math.max(1L, period)); + long initialTicks = millisToCeilTicks(initialMillis); + long periodTicks = millisToCeilTicks(periodMillis); + + return Bukkit.getRegionScheduler().runAtFixedRate( + plugin, + location, + consumer, + initialTicks, + periodTicks + ); + } + + /* + * Entity scheduler convenience methods (use Entity#getScheduler) + */ + + /** + * Schedules a task to execute on the region which owns the given entity on the next tick. + * + * @param consumer The task to run. + * @param plugin The plugin scheduling the task. + * @param entity The entity whose scheduler should run the task. + * @return The scheduled ScheduledTask, or null if the entity scheduler is retired/removed. + */ + public static ScheduledTask run(Consumer consumer, Plugin plugin, Entity entity) { + Preconditions.checkNotNull(consumer, "consumer cannot be null"); + Preconditions.checkNotNull(plugin, "plugin cannot be null"); + Preconditions.checkNotNull(entity, "entity cannot be null"); + + return entity.getScheduler().run(plugin, consumer, null); + } + + /** + * Schedules a task with the given delay on the region which owns the given entity. + *

+ * The delay is supplied together with a {@link TimeUnit} and will be converted to server ticks (1 tick = 50 ms) + * by this helper (ceiling conversion, minimum 1 tick). + * + * @param consumer The task to run. + * @param plugin The plugin scheduling the task. + * @param entity The entity whose scheduler should run the task. + * @param delay The delay before execution. + * @param timeUnit The time unit of the delay. + * @return The scheduled ScheduledTask, or null if the entity scheduler is retired/removed. + */ + public static ScheduledTask runDelayed(Consumer consumer, Plugin plugin, Entity entity, + @NonNegative long delay, TimeUnit timeUnit) { + Preconditions.checkNotNull(consumer, "consumer cannot be null"); + Preconditions.checkNotNull(plugin, "plugin cannot be null"); + Preconditions.checkNotNull(entity, "entity cannot be null"); + Preconditions.checkNotNull(timeUnit, "timeUnit cannot be null"); + + long millis = timeUnit.toMillis(Math.max(1L, delay)); + long ticks = millisToCeilTicks(millis); + return entity.getScheduler().runDelayed(plugin, consumer, null, ticks); + } + + /** + * Schedules a repeating task with the given initial delay and period on the region which owns the given entity. + *

+ * The delays are supplied together with a {@link TimeUnit} and will be converted to server ticks (1 tick = 50 ms) + * by this helper (ceiling conversion, minimum 1 tick). + * + * @param consumer The task to run. + * @param plugin The plugin scheduling the task. + * @param entity The entity whose scheduler should run the task. + * @param initialDelay The initial delay before the first execution. + * @param period The period between subsequent executions. + * @param timeUnit The time unit of the delays. + * @return The scheduled ScheduledTask, or null if the entity scheduler is retired/removed. + */ + public static ScheduledTask runRepeat(Consumer consumer, Plugin plugin, Entity entity, + @NonNegative long initialDelay, @NonNegative long period, TimeUnit timeUnit) { + Preconditions.checkNotNull(consumer, "consumer cannot be null"); + Preconditions.checkNotNull(plugin, "plugin cannot be null"); + Preconditions.checkNotNull(entity, "entity cannot be null"); + Preconditions.checkNotNull(timeUnit, "timeUnit cannot be null"); + + long initialMillis = timeUnit.toMillis(Math.max(1L, initialDelay)); + long periodMillis = timeUnit.toMillis(Math.max(1L, period)); + long initialTicks = millisToCeilTicks(initialMillis); + long periodTicks = millisToCeilTicks(periodMillis); + + return entity.getScheduler().runAtFixedRate(plugin, consumer, null, initialTicks, periodTicks); } } From ff793190dde5f61d46c386d0f88859f943ad0eba Mon Sep 17 00:00:00 2001 From: PuppyTransGirl <74014559+PuppyTransGirl@users.noreply.github.com> Date: Sat, 21 Feb 2026 00:13:48 +0100 Subject: [PATCH 3/4] Reviews 1 --- .../niveriaapi/utils/ParticleUtils.java | 7 +++- .../toutouchien/niveriaapi/utils/Task.java | 35 +++++++++---------- 2 files changed, 23 insertions(+), 19 deletions(-) diff --git a/src/main/java/toutouchien/niveriaapi/utils/ParticleUtils.java b/src/main/java/toutouchien/niveriaapi/utils/ParticleUtils.java index 5e8a4ddd..0d30c977 100644 --- a/src/main/java/toutouchien/niveriaapi/utils/ParticleUtils.java +++ b/src/main/java/toutouchien/niveriaapi/utils/ParticleUtils.java @@ -567,10 +567,15 @@ public static void drawParametricCurve(Location center, Particle particle, Doubl public static void animateParticles(Plugin plugin, List frames, Particle particle, int count, double offsetX, double offsetY, double offsetZ, double speed, long ticksPerFrame, long durationTicks, boolean loop) { final int[] currentFrame = {0}; - ScheduledTask task = Task.runRepeat(ignored -> { + ScheduledTask task = Task.runRepeat(t -> { if (currentFrame[0] >= frames.size() && loop) currentFrame[0] = 0; // Loop animation (optional) + if (currentFrame[0] >= frames.size()) { + t.cancel(); + return; + } + Location location = frames.get(currentFrame[0]); spawnParticle(location, particle, count, offsetX, offsetY, offsetZ, speed, null, false); diff --git a/src/main/java/toutouchien/niveriaapi/utils/Task.java b/src/main/java/toutouchien/niveriaapi/utils/Task.java index deb6af27..45fac552 100644 --- a/src/main/java/toutouchien/niveriaapi/utils/Task.java +++ b/src/main/java/toutouchien/niveriaapi/utils/Task.java @@ -6,7 +6,6 @@ import org.bukkit.Location; import org.bukkit.entity.Entity; import org.bukkit.plugin.Plugin; -import org.checkerframework.checker.index.qual.NonNegative; import org.jspecify.annotations.NullMarked; import java.util.concurrent.TimeUnit; @@ -56,12 +55,12 @@ public static ScheduledTask sync(Consumer consumer, Plugin plugin * @param timeUnit The time unit of the delay. * @return The scheduled ScheduledTask. */ - public static ScheduledTask syncLater(Consumer consumer, Plugin plugin, @NonNegative long delay, TimeUnit timeUnit) { + public static ScheduledTask syncLater(Consumer consumer, Plugin plugin, long delay, TimeUnit timeUnit) { Preconditions.checkNotNull(consumer, "consumer cannot be null"); Preconditions.checkNotNull(plugin, "plugin cannot be null"); Preconditions.checkNotNull(timeUnit, "timeUnit cannot be null"); - long millis = timeUnit.toMillis(Math.max(1L, delay)); + long millis = Math.max(1L, timeUnit.toMillis(delay)); long ticks = millisToCeilTicks(millis); return Bukkit.getGlobalRegionScheduler().runDelayed(plugin, consumer, ticks); } @@ -79,13 +78,13 @@ public static ScheduledTask syncLater(Consumer consumer, Plugin p * @param timeUnit The time unit of the delay and interval. * @return The scheduled ScheduledTask. */ - public static ScheduledTask syncRepeat(Consumer consumer, Plugin plugin, @NonNegative long delay, @NonNegative long interval, TimeUnit timeUnit) { + public static ScheduledTask syncRepeat(Consumer consumer, Plugin plugin, long delay, long interval, TimeUnit timeUnit) { Preconditions.checkNotNull(consumer, "consumer cannot be null"); Preconditions.checkNotNull(plugin, "plugin cannot be null"); Preconditions.checkNotNull(timeUnit, "timeUnit cannot be null"); - long delayMillis = timeUnit.toMillis(Math.max(1L, delay)); - long intervalMillis = timeUnit.toMillis(Math.max(1L, interval)); + long delayMillis = Math.max(1L, timeUnit.toMillis(delay)); + long intervalMillis = Math.max(1L, timeUnit.toMillis(interval)); long delayTicks = millisToCeilTicks(delayMillis); long intervalTicks = millisToCeilTicks(intervalMillis); @@ -117,7 +116,7 @@ public static ScheduledTask async(Consumer consumer, Plugin plugi * @param timeUnit The time unit of the delay. * @return The scheduled ScheduledTask. */ - public static ScheduledTask asyncLater(Consumer consumer, Plugin plugin, @NonNegative long delay, TimeUnit timeUnit) { + public static ScheduledTask asyncLater(Consumer consumer, Plugin plugin, long delay, TimeUnit timeUnit) { Preconditions.checkNotNull(consumer, "consumer cannot be null"); Preconditions.checkNotNull(plugin, "plugin cannot be null"); Preconditions.checkNotNull(timeUnit, "timeUnit cannot be null"); @@ -138,7 +137,7 @@ public static ScheduledTask asyncLater(Consumer consumer, Plugin * @param timeUnit The time unit of the delay and interval. * @return The scheduled ScheduledTask. */ - public static ScheduledTask asyncRepeat(Consumer consumer, Plugin plugin, @NonNegative long delay, @NonNegative long interval, TimeUnit timeUnit) { + public static ScheduledTask asyncRepeat(Consumer consumer, Plugin plugin, long delay, long interval, TimeUnit timeUnit) { Preconditions.checkNotNull(consumer, "consumer cannot be null"); Preconditions.checkNotNull(plugin, "plugin cannot be null"); Preconditions.checkNotNull(timeUnit, "timeUnit cannot be null"); @@ -183,13 +182,13 @@ public static ScheduledTask run(Consumer consumer, Plugin plugin, * @return The scheduled ScheduledTask. */ public static ScheduledTask runDelayed(Consumer consumer, Plugin plugin, Location location, - @NonNegative long delay, TimeUnit timeUnit) { + long delay, TimeUnit timeUnit) { Preconditions.checkNotNull(consumer, "consumer cannot be null"); Preconditions.checkNotNull(plugin, "plugin cannot be null"); Preconditions.checkNotNull(location, "location cannot be null"); Preconditions.checkNotNull(timeUnit, "timeUnit cannot be null"); - long millis = timeUnit.toMillis(Math.max(1L, delay)); + long millis = Math.max(1L, timeUnit.toMillis(delay)); long ticks = millisToCeilTicks(millis); return Bukkit.getRegionScheduler().runDelayed( plugin, @@ -215,14 +214,14 @@ public static ScheduledTask runDelayed(Consumer consumer, Plugin * @return The scheduled ScheduledTask. */ public static ScheduledTask runRepeat(Consumer consumer, Plugin plugin, Location location, - @NonNegative long initialDelay, @NonNegative long period, TimeUnit timeUnit) { + long initialDelay, long period, TimeUnit timeUnit) { Preconditions.checkNotNull(consumer, "consumer cannot be null"); Preconditions.checkNotNull(plugin, "plugin cannot be null"); Preconditions.checkNotNull(location, "location cannot be null"); Preconditions.checkNotNull(timeUnit, "timeUnit cannot be null"); - long initialMillis = timeUnit.toMillis(Math.max(1L, initialDelay)); - long periodMillis = timeUnit.toMillis(Math.max(1L, period)); + long initialMillis = Math.max(1L, timeUnit.toMillis(initialDelay)); + long periodMillis = Math.max(1L, timeUnit.toMillis(period)); long initialTicks = millisToCeilTicks(initialMillis); long periodTicks = millisToCeilTicks(periodMillis); @@ -269,13 +268,13 @@ public static ScheduledTask run(Consumer consumer, Plugin plugin, * @return The scheduled ScheduledTask, or null if the entity scheduler is retired/removed. */ public static ScheduledTask runDelayed(Consumer consumer, Plugin plugin, Entity entity, - @NonNegative long delay, TimeUnit timeUnit) { + long delay, TimeUnit timeUnit) { Preconditions.checkNotNull(consumer, "consumer cannot be null"); Preconditions.checkNotNull(plugin, "plugin cannot be null"); Preconditions.checkNotNull(entity, "entity cannot be null"); Preconditions.checkNotNull(timeUnit, "timeUnit cannot be null"); - long millis = timeUnit.toMillis(Math.max(1L, delay)); + long millis = Math.max(1L, timeUnit.toMillis(delay)); long ticks = millisToCeilTicks(millis); return entity.getScheduler().runDelayed(plugin, consumer, null, ticks); } @@ -295,14 +294,14 @@ public static ScheduledTask runDelayed(Consumer consumer, Plugin * @return The scheduled ScheduledTask, or null if the entity scheduler is retired/removed. */ public static ScheduledTask runRepeat(Consumer consumer, Plugin plugin, Entity entity, - @NonNegative long initialDelay, @NonNegative long period, TimeUnit timeUnit) { + long initialDelay, long period, TimeUnit timeUnit) { Preconditions.checkNotNull(consumer, "consumer cannot be null"); Preconditions.checkNotNull(plugin, "plugin cannot be null"); Preconditions.checkNotNull(entity, "entity cannot be null"); Preconditions.checkNotNull(timeUnit, "timeUnit cannot be null"); - long initialMillis = timeUnit.toMillis(Math.max(1L, initialDelay)); - long periodMillis = timeUnit.toMillis(Math.max(1L, period)); + long initialMillis = Math.max(1L, timeUnit.toMillis(initialDelay)); + long periodMillis = Math.max(1L, timeUnit.toMillis(period)); long initialTicks = millisToCeilTicks(initialMillis); long periodTicks = millisToCeilTicks(periodMillis); From a7ffa4f01ab798bad85f9fd92abe5139efb668f8 Mon Sep 17 00:00:00 2001 From: PuppyTransGirl <74014559+PuppyTransGirl@users.noreply.github.com> Date: Sat, 21 Feb 2026 00:15:17 +0100 Subject: [PATCH 4/4] Fixes --- src/main/java/toutouchien/niveriaapi/utils/Task.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/main/java/toutouchien/niveriaapi/utils/Task.java b/src/main/java/toutouchien/niveriaapi/utils/Task.java index 45fac552..5a05f14f 100644 --- a/src/main/java/toutouchien/niveriaapi/utils/Task.java +++ b/src/main/java/toutouchien/niveriaapi/utils/Task.java @@ -7,6 +7,7 @@ import org.bukkit.entity.Entity; import org.bukkit.plugin.Plugin; import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; import java.util.concurrent.TimeUnit; import java.util.function.Consumer; @@ -234,10 +235,6 @@ public static ScheduledTask runRepeat(Consumer consumer, Plugin p ); } - /* - * Entity scheduler convenience methods (use Entity#getScheduler) - */ - /** * Schedules a task to execute on the region which owns the given entity on the next tick. * @@ -246,6 +243,7 @@ public static ScheduledTask runRepeat(Consumer consumer, Plugin p * @param entity The entity whose scheduler should run the task. * @return The scheduled ScheduledTask, or null if the entity scheduler is retired/removed. */ + @Nullable public static ScheduledTask run(Consumer consumer, Plugin plugin, Entity entity) { Preconditions.checkNotNull(consumer, "consumer cannot be null"); Preconditions.checkNotNull(plugin, "plugin cannot be null"); @@ -267,6 +265,7 @@ public static ScheduledTask run(Consumer consumer, Plugin plugin, * @param timeUnit The time unit of the delay. * @return The scheduled ScheduledTask, or null if the entity scheduler is retired/removed. */ + @Nullable public static ScheduledTask runDelayed(Consumer consumer, Plugin plugin, Entity entity, long delay, TimeUnit timeUnit) { Preconditions.checkNotNull(consumer, "consumer cannot be null"); @@ -281,7 +280,7 @@ public static ScheduledTask runDelayed(Consumer consumer, Plugin /** * Schedules a repeating task with the given initial delay and period on the region which owns the given entity. - *

+ *

* The delays are supplied together with a {@link TimeUnit} and will be converted to server ticks (1 tick = 50 ms) * by this helper (ceiling conversion, minimum 1 tick). * @@ -293,6 +292,7 @@ public static ScheduledTask runDelayed(Consumer consumer, Plugin * @param timeUnit The time unit of the delays. * @return The scheduled ScheduledTask, or null if the entity scheduler is retired/removed. */ + @Nullable public static ScheduledTask runRepeat(Consumer consumer, Plugin plugin, Entity entity, long initialDelay, long period, TimeUnit timeUnit) { Preconditions.checkNotNull(consumer, "consumer cannot be null");