diff --git a/patches/api/0002-Region-scheduler-API.patch b/patches/api/0002-Region-scheduler-API.patch index c88800b..dd9b5a7 100644 --- a/patches/api/0002-Region-scheduler-API.patch +++ b/patches/api/0002-Region-scheduler-API.patch @@ -8,7 +8,7 @@ and a global region scheduler. diff --git a/src/main/java/io/papermc/paper/threadedregions/scheduler/AsyncScheduler.java b/src/main/java/io/papermc/paper/threadedregions/scheduler/AsyncScheduler.java new file mode 100644 -index 0000000000000000000000000000000000000000..64d1fe385d30f1f5ab82d35fe66e268da13346b1 +index 0000000000000000000000000000000000000000..66232a9f5cea31dc8046817c3c2a91695930e53f --- /dev/null +++ b/src/main/java/io/papermc/paper/threadedregions/scheduler/AsyncScheduler.java @@ -0,0 +1,50 @@ @@ -30,7 +30,7 @@ index 0000000000000000000000000000000000000000..64d1fe385d30f1f5ab82d35fe66e268d + * @param task Specified task. + * @return The {@link ScheduledTask} that represents the scheduled task. + */ -+ public @NotNull ScheduledTask runNow(@NotNull Plugin plugin, @NotNull Consumer task); ++ @NotNull ScheduledTask runNow(@NotNull Plugin plugin, @NotNull Consumer task); + + /** + * Schedules the specified task to be executed asynchronously after the time delay has passed. @@ -40,7 +40,7 @@ index 0000000000000000000000000000000000000000..64d1fe385d30f1f5ab82d35fe66e268d + * @param unit The time unit for the time delay. + * @return The {@link ScheduledTask} that represents the scheduled task. + */ -+ public @NotNull ScheduledTask runDelayed(@NotNull Plugin plugin, @NotNull Consumer task, long delay, ++ @NotNull ScheduledTask runDelayed(@NotNull Plugin plugin, @NotNull Consumer task, long delay, + @NotNull TimeUnit unit); + + /** @@ -53,18 +53,18 @@ index 0000000000000000000000000000000000000000..64d1fe385d30f1f5ab82d35fe66e268d + * @param unit The time unit for the initial delay and period. + * @return The {@link ScheduledTask} that represents the scheduled task. + */ -+ public @NotNull ScheduledTask runAtFixedRate(@NotNull Plugin plugin, @NotNull Consumer task, ++ @NotNull ScheduledTask runAtFixedRate(@NotNull Plugin plugin, @NotNull Consumer task, + long initialDelay, long period, @NotNull TimeUnit unit); + + /** + * Attempts to cancel all tasks scheduled by the specified plugin. + * @param plugin Specified plugin. + */ -+ public void cancelTasks(@NotNull Plugin plugin); ++ void cancelTasks(@NotNull Plugin plugin); +} diff --git a/src/main/java/io/papermc/paper/threadedregions/scheduler/EntityScheduler.java b/src/main/java/io/papermc/paper/threadedregions/scheduler/EntityScheduler.java new file mode 100644 -index 0000000000000000000000000000000000000000..9c4ee07a86104f3601ba6d8a911197dbe1a17102 +index 0000000000000000000000000000000000000000..017dbb27f66cbf744a67189e91c476f8d971d1e7 --- /dev/null +++ b/src/main/java/io/papermc/paper/threadedregions/scheduler/EntityScheduler.java @@ -0,0 +1,103 @@ @@ -112,7 +112,7 @@ index 0000000000000000000000000000000000000000..9c4ee07a86104f3601ba6d8a911197db + * will be invoked (but never both), or {@code false} indicating neither the run nor retired function will be invoked + * since the scheduler has been retired. + */ -+ public boolean execute(@NotNull Plugin plugin, @NotNull Runnable run, @Nullable Runnable retired, long delay); ++ boolean execute(@NotNull Plugin plugin, @NotNull Runnable run, @Nullable Runnable retired, long delay); + + /** + * Schedules a task to execute on the next tick. If the task failed to schedule because the scheduler is retired (entity @@ -129,7 +129,7 @@ index 0000000000000000000000000000000000000000..9c4ee07a86104f3601ba6d8a911197db + * @param retired Retire callback to run if the entity is retired before the run callback can be invoked, may be null. + * @return The {@link ScheduledTask} that represents the scheduled task, or {@code null} if the entity has been removed. + */ -+ public @Nullable ScheduledTask run(@NotNull Plugin plugin, @NotNull Consumer task, ++ @Nullable ScheduledTask run(@NotNull Plugin plugin, @NotNull Consumer task, + @Nullable Runnable retired); + + /** @@ -148,7 +148,7 @@ index 0000000000000000000000000000000000000000..9c4ee07a86104f3601ba6d8a911197db + * @param delayTicks The delay, in ticks. + * @return The {@link ScheduledTask} that represents the scheduled task, or {@code null} if the entity has been removed. + */ -+ public @Nullable ScheduledTask runDelayed(@NotNull Plugin plugin, @NotNull Consumer task, ++ @Nullable ScheduledTask runDelayed(@NotNull Plugin plugin, @NotNull Consumer task, + @Nullable Runnable retired, int delayTicks); + + /** @@ -168,12 +168,12 @@ index 0000000000000000000000000000000000000000..9c4ee07a86104f3601ba6d8a911197db + * @param periodTicks The period, in ticks. + * @return The {@link ScheduledTask} that represents the scheduled task, or {@code null} if the entity has been removed. + */ -+ public @Nullable ScheduledTask runAtFixedRate(@NotNull Plugin plugin, @NotNull Consumer task, ++ @Nullable ScheduledTask runAtFixedRate(@NotNull Plugin plugin, @NotNull Consumer task, + @Nullable Runnable retired, int initialDelayTicks, int periodTicks); +} diff --git a/src/main/java/io/papermc/paper/threadedregions/scheduler/GlobalRegionScheduler.java b/src/main/java/io/papermc/paper/threadedregions/scheduler/GlobalRegionScheduler.java new file mode 100644 -index 0000000000000000000000000000000000000000..f2d2565d903af90f6909319c811a49162f972e27 +index 0000000000000000000000000000000000000000..45713139678cd2bcb9a370c947a124d8f5e2e36b --- /dev/null +++ b/src/main/java/io/papermc/paper/threadedregions/scheduler/GlobalRegionScheduler.java @@ -0,0 +1,57 @@ @@ -197,7 +197,7 @@ index 0000000000000000000000000000000000000000..f2d2565d903af90f6909319c811a4916 + * @param plugin The plugin that owns the task + * @param run The task to execute + */ -+ public void execute(@NotNull Plugin plugin, @NotNull Runnable run); ++ void execute(@NotNull Plugin plugin, @NotNull Runnable run); + + /** + * Schedules a task to be executed on the global region on the next tick. @@ -205,7 +205,7 @@ index 0000000000000000000000000000000000000000..f2d2565d903af90f6909319c811a4916 + * @param task The task to execute + * @return The {@link ScheduledTask} that represents the scheduled task. + */ -+ public @NotNull ScheduledTask run(@NotNull Plugin plugin, @NotNull Consumer task); ++ @NotNull ScheduledTask run(@NotNull Plugin plugin, @NotNull Consumer task); + + /** + * Schedules a task to be executed on the global region after the specified delay in ticks. @@ -214,7 +214,7 @@ index 0000000000000000000000000000000000000000..f2d2565d903af90f6909319c811a4916 + * @param delayTicks The delay, in ticks. + * @return The {@link ScheduledTask} that represents the scheduled task. + */ -+ public @NotNull ScheduledTask runDelayed(@NotNull Plugin plugin, @NotNull Consumer task, int delayTicks); ++ @NotNull ScheduledTask runDelayed(@NotNull Plugin plugin, @NotNull Consumer task, int delayTicks); + + /** + * Schedules a repeating task to be executed on the global region after the initial delay with the @@ -225,24 +225,25 @@ index 0000000000000000000000000000000000000000..f2d2565d903af90f6909319c811a4916 + * @param periodTicks The period, in ticks. + * @return The {@link ScheduledTask} that represents the scheduled task. + */ -+ public @NotNull ScheduledTask runAtFixedRate(@NotNull Plugin plugin, @NotNull Consumer task, ++ @NotNull ScheduledTask runAtFixedRate(@NotNull Plugin plugin, @NotNull Consumer task, + int initialDelayTicks, int periodTicks); + + /** + * Attempts to cancel all tasks scheduled by the specified plugin. + * @param plugin Specified plugin. + */ -+ public void cancelTasks(@NotNull Plugin plugin); ++ void cancelTasks(@NotNull Plugin plugin); +} diff --git a/src/main/java/io/papermc/paper/threadedregions/scheduler/RegionScheduler.java b/src/main/java/io/papermc/paper/threadedregions/scheduler/RegionScheduler.java new file mode 100644 -index 0000000000000000000000000000000000000000..80baf45735fbf52a69872712cf3ae6c71739db27 +index 0000000000000000000000000000000000000000..87a267ca2cd7a751acdd1fbbfbf9c664640a9805 --- /dev/null +++ b/src/main/java/io/papermc/paper/threadedregions/scheduler/RegionScheduler.java -@@ -0,0 +1,60 @@ +@@ -0,0 +1,126 @@ +package io.papermc.paper.threadedregions.scheduler; + +import org.bukkit.Location; ++import org.bukkit.World; +import org.bukkit.entity.Entity; +import org.bukkit.plugin.Plugin; +import org.jetbrains.annotations.NotNull; @@ -261,48 +262,113 @@ index 0000000000000000000000000000000000000000..80baf45735fbf52a69872712cf3ae6c7 + + /** + * 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 ++ * @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 Location location, @NotNull Runnable run); ++ void execute(@NotNull Plugin plugin, @NotNull World world, int chunkX, int chunkZ, @NotNull Runnable 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 ++ */ ++ default void execute(@NotNull Plugin plugin, @NotNull Location location, @NotNull Runnable run) { ++ this.execute(plugin, location.getWorld(), location.getBlockX() >> 4, location.getBlockZ() >> 4, 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 location The location at which the region executing should own -+ * @param task The task to execute ++ * @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 The {@link ScheduledTask} that represents the scheduled task. + */ -+ public @NotNull ScheduledTask run(@NotNull Plugin plugin, @NotNull Location location, @NotNull Consumer task); ++ @NotNull ScheduledTask run(@NotNull Plugin plugin, @NotNull World world, int chunkX, int chunkZ, @NotNull Consumer task); ++ ++ /** ++ * 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 The {@link ScheduledTask} that represents the scheduled task. ++ */ ++ default @NotNull ScheduledTask run(@NotNull Plugin plugin, @NotNull Location location, @NotNull Consumer task) { ++ return this.run(plugin, location.getWorld(), location.getBlockX() >> 4, location.getBlockZ() >> 4, task); ++ } + + /** + * 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 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. + * @return The {@link ScheduledTask} that represents the scheduled task. + */ -+ public @NotNull ScheduledTask runDelayed(@NotNull Plugin plugin, @NotNull Location location, @NotNull Consumer task, -+ int delayTicks); ++ @NotNull ScheduledTask runDelayed(@NotNull Plugin plugin, @NotNull World world, int chunkX, int chunkZ, @NotNull Consumer task, ++ int 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. ++ * @return The {@link ScheduledTask} that represents the scheduled task. ++ */ ++ default @NotNull ScheduledTask runDelayed(@NotNull Plugin plugin, @NotNull Location location, @NotNull Consumer task, ++ int delayTicks) { ++ return this.runDelayed(plugin, location.getWorld(), location.getBlockX() >> 4, location.getBlockZ() >> 4, task, 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 location The location at which the region executing should own -+ * @param task The task to execute ++ * ++ * @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. -+ * @param periodTicks The period, in ticks. ++ * @param periodTicks The period, in ticks. + * @return The {@link ScheduledTask} that represents the scheduled task. + */ -+ public @NotNull ScheduledTask runAtFixedRate(@NotNull Plugin plugin, @NotNull Location location, @NotNull Consumer task, -+ int initialDelayTicks, int periodTicks); ++ @NotNull ScheduledTask runAtFixedRate(@NotNull Plugin plugin, @NotNull World world, int chunkX, int chunkZ, @NotNull Consumer task, ++ int initialDelayTicks, int 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. ++ * @param periodTicks The period, in ticks. ++ * @return The {@link ScheduledTask} that represents the scheduled task. ++ */ ++ default @NotNull ScheduledTask runAtFixedRate(@NotNull Plugin plugin, @NotNull Location location, @NotNull Consumer task, ++ int initialDelayTicks, int periodTicks) { ++ return this.runAtFixedRate(plugin, location.getWorld(), location.getBlockX() >> 4, location.getBlockZ() >> 4, task, initialDelayTicks, periodTicks); ++ } +} diff --git a/src/main/java/io/papermc/paper/threadedregions/scheduler/ScheduledTask.java b/src/main/java/io/papermc/paper/threadedregions/scheduler/ScheduledTask.java new file mode 100644 -index 0000000000000000000000000000000000000000..fa4ac300d3721b2d6d84b95618d3305874cb803d +index 0000000000000000000000000000000000000000..a6b50c9d8af589cc4747e14d343d2045216c249c --- /dev/null +++ b/src/main/java/io/papermc/paper/threadedregions/scheduler/ScheduledTask.java @@ -0,0 +1,112 @@ @@ -320,32 +386,32 @@ index 0000000000000000000000000000000000000000..fa4ac300d3721b2d6d84b95618d33058 + * Returns the plugin that scheduled this task. + * @return the plugin that scheduled this task. + */ -+ public @NotNull Plugin getOwningPlugin(); ++ @NotNull Plugin getOwningPlugin(); + + /** + * Returns whether this task executes on a fixed period, as opposed to executing only once. + * @return whether this task executes on a fixed period, as opposed to executing only once. + */ -+ public boolean isRepeatingTask(); ++ boolean isRepeatingTask(); + + /** + * Attempts to cancel this task, returning the result of the attempt. In all cases, if the task is currently + * being executed no attempt is made to halt the task, however any executions in the future are halted. + * @return the result of the cancellation attempt. + */ -+ public @NotNull CancelledState cancel(); ++ @NotNull CancelledState cancel(); + + /** + * Returns the current execution state of this task. + * @return the current execution state of this task. + */ -+ public @NotNull ExecutionState getExecutionState(); ++ @NotNull ExecutionState getExecutionState(); + + /** + * Returns whether the current execution state is {@link ExecutionState#CANCELLED} or {@link ExecutionState#CANCELLED_RUNNING}. + * @return whether the current execution state is {@link ExecutionState#CANCELLED} or {@link ExecutionState#CANCELLED_RUNNING}. + */ -+ public default boolean isCancelled() { ++ default boolean isCancelled() { + final ExecutionState state = this.getExecutionState(); + return state == ExecutionState.CANCELLED || state == ExecutionState.CANCELLED_RUNNING; + } @@ -353,7 +419,7 @@ index 0000000000000000000000000000000000000000..fa4ac300d3721b2d6d84b95618d33058 + /** + * Represents the result of attempting to cancel a task. + */ -+ public enum CancelledState { ++ enum CancelledState { + /** + * The task (repeating or not) has been successfully cancelled by the caller thread. The task is not executing + * currently, and it will not begin execution in the future. @@ -390,7 +456,7 @@ index 0000000000000000000000000000000000000000..fa4ac300d3721b2d6d84b95618d33058 + /** + * Represents the current execution state of the task. + */ -+ public enum ExecutionState { ++ enum ExecutionState { + /** + * The task is currently not executing, but may begin execution in the future. + */ @@ -468,7 +534,7 @@ index ac9b690fcccb60b587e5345f12f1383afd0a73a1..7986b9fcaf256e9042f6d9ddc38e8bd7 @NotNull public static Server.Spigot spigot() { diff --git a/src/main/java/org/bukkit/Server.java b/src/main/java/org/bukkit/Server.java -index 2204336d8800311b65e894739ab1b27273e7c6f2..bf02c948a50d934e94c44f4844254a45ae7cb2a5 100644 +index 2204336d8800311b65e894739ab1b27273e7c6f2..5caa00a413450dee18739f6430ffaf5095ea3036 100644 --- a/src/main/java/org/bukkit/Server.java +++ b/src/main/java/org/bukkit/Server.java @@ -2139,4 +2139,36 @@ public interface Server extends PluginMessageRecipient, net.kyori.adventure.audi diff --git a/patches/api/0005-Add-API-for-checking-ownership-of-region-by-position.patch b/patches/api/0005-Add-API-for-checking-ownership-of-region-by-position.patch index f212e61..b3032e8 100644 --- a/patches/api/0005-Add-API-for-checking-ownership-of-region-by-position.patch +++ b/patches/api/0005-Add-API-for-checking-ownership-of-region-by-position.patch @@ -116,7 +116,7 @@ index 7986b9fcaf256e9042f6d9ddc38e8bd76645cbb7..16043d6f7894182e8ff75a8faf57dedf @NotNull diff --git a/src/main/java/org/bukkit/Server.java b/src/main/java/org/bukkit/Server.java -index bf02c948a50d934e94c44f4844254a45ae7cb2a5..fea84620b25ac393be6d3253c100ddeac1983105 100644 +index 5caa00a413450dee18739f6430ffaf5095ea3036..c72eee72401e275af57d4fd67a04df1eac13954c 100644 --- a/src/main/java/org/bukkit/Server.java +++ b/src/main/java/org/bukkit/Server.java @@ -2170,5 +2170,83 @@ public interface Server extends PluginMessageRecipient, net.kyori.adventure.audi diff --git a/patches/server/0004-Threaded-Regions.patch b/patches/server/0004-Threaded-Regions.patch index 5567767..164ba85 100644 --- a/patches/server/0004-Threaded-Regions.patch +++ b/patches/server/0004-Threaded-Regions.patch @@ -9818,10 +9818,10 @@ index 0000000000000000000000000000000000000000..505329d601d56e42daa0b794092590cb +} diff --git a/src/main/java/io/papermc/paper/threadedregions/scheduler/FoliaRegionScheduler.java b/src/main/java/io/papermc/paper/threadedregions/scheduler/FoliaRegionScheduler.java new file mode 100644 -index 0000000000000000000000000000000000000000..b699e20df7a98b0dd8f783b293f4c4b47b868f2c +index 0000000000000000000000000000000000000000..7ee75fe44507f79362f15d6445e32954dcc4308c --- /dev/null +++ b/src/main/java/io/papermc/paper/threadedregions/scheduler/FoliaRegionScheduler.java -@@ -0,0 +1,430 @@ +@@ -0,0 +1,422 @@ +package io.papermc.paper.threadedregions.scheduler; + +import ca.spottedleaf.concurrentutil.util.ConcurrentUtil; @@ -9840,7 +9840,6 @@ index 0000000000000000000000000000000000000000..b699e20df7a98b0dd8f783b293f4c4b4 +import net.minecraft.server.level.TicketType; +import net.minecraft.util.Unit; +import org.bukkit.Bukkit; -+import org.bukkit.Location; +import org.bukkit.World; +import org.bukkit.craftbukkit.CraftWorld; +import org.bukkit.plugin.IllegalPluginAccessException; @@ -9854,12 +9853,13 @@ index 0000000000000000000000000000000000000000..b699e20df7a98b0dd8f783b293f4c4b4 + +public final class FoliaRegionScheduler implements RegionScheduler { + -+ private static Runnable wrap(final Plugin plugin, final Location location, final Runnable run) { ++ private static Runnable wrap(final Plugin plugin, final World world, final int chunkX, final int chunkZ, final Runnable run) { + return () -> { + try { + run.run(); + } catch (final Throwable throwable) { -+ plugin.getLogger().log(Level.WARNING, "Location task for " + plugin.getDescription().getFullName() + " at location " + location + " generated an exception", throwable); ++ plugin.getLogger().log(Level.WARNING, "Location task for " + plugin.getDescription().getFullName() ++ + " in world " + world + " at " + chunkX + ", " + chunkZ + " generated an exception", throwable); + } + }; + } @@ -9871,50 +9871,40 @@ index 0000000000000000000000000000000000000000..b699e20df7a98b0dd8f783b293f4c4b4 + } + + private static void scheduleInternalOffRegion(final LocationScheduledTask task, final int delay) { -+ final Location location = task.location; -+ if (location == null) { ++ final World world = task.world; ++ if (world == null) { + // cancelled + return; + } + -+ final World world = Validate.notNull(location.getWorld(), "Location world may not be null"); -+ -+ final int chunkX = location.getBlockX() >> 4; -+ final int chunkZ = location.getBlockZ() >> 4; -+ + RegionizedServer.getInstance().taskQueue.queueTickTaskQueue( -+ ((CraftWorld)world).getHandle(), chunkX, chunkZ, () -> { ++ ((CraftWorld) world).getHandle(), task.chunkX, task.chunkZ, () -> { + scheduleInternalOnRegion(task, delay); + } + ); + } + + @Override -+ public void execute(final Plugin plugin, final Location location, final Runnable run) { ++ public void execute(final Plugin plugin, final World world, final int chunkX, final int chunkZ, final Runnable run) { + Validate.notNull(plugin, "Plugin may not be null"); -+ Validate.notNull(location, "Location may not be null"); ++ Validate.notNull(world, "World may not be null"); + Validate.notNull(run, "Runnable may not be null"); + -+ final World world = Validate.notNull(location.getWorld(), "Location world may not be null"); -+ -+ final int chunkX = location.getBlockX() >> 4; -+ final int chunkZ = location.getBlockZ() >> 4; -+ + RegionizedServer.getInstance().taskQueue.queueTickTaskQueue( -+ ((CraftWorld)world).getHandle(), chunkX, chunkZ, wrap(plugin, location.clone(), run) ++ ((CraftWorld) world).getHandle(), chunkX, chunkZ, wrap(plugin, world, chunkX, chunkZ, run) + ); + } + + @Override -+ public ScheduledTask run(final Plugin plugin, final Location location, final Consumer task) { -+ return this.runDelayed(plugin, location, task, 1); ++ public ScheduledTask run(final Plugin plugin, final World world, final int chunkX, final int chunkZ, final Consumer task) { ++ return this.runDelayed(plugin, world, chunkX, chunkZ, task, 1); + } + + @Override -+ public ScheduledTask runDelayed(final Plugin plugin, Location location, final Consumer task, -+ final int delayTicks) { ++ public ScheduledTask runDelayed(final Plugin plugin, final World world, final int chunkX, final int chunkZ, ++ final Consumer task, final int delayTicks) { + Validate.notNull(plugin, "Plugin may not be null"); -+ Validate.notNull(location, "Location may not be null"); ++ Validate.notNull(world, "World may not be null"); + Validate.notNull(task, "Task may not be null"); + if (delayTicks <= 0) { + throw new IllegalArgumentException("Delay ticks may not be <= 0"); @@ -9924,11 +9914,9 @@ index 0000000000000000000000000000000000000000..b699e20df7a98b0dd8f783b293f4c4b4 + throw new IllegalPluginAccessException("Plugin attempted to register task while disabled"); + } + -+ location = location.clone(); ++ final LocationScheduledTask ret = new LocationScheduledTask(plugin, world, chunkX, chunkZ, -1, task); + -+ final LocationScheduledTask ret = new LocationScheduledTask(plugin, location, -1, task); -+ -+ if (Bukkit.isOwnedByCurrentRegion(location)) { ++ if (Bukkit.isOwnedByCurrentRegion(world, chunkX, chunkZ)) { + scheduleInternalOnRegion(ret, delayTicks); + } else { + scheduleInternalOffRegion(ret, delayTicks); @@ -9943,10 +9931,10 @@ index 0000000000000000000000000000000000000000..b699e20df7a98b0dd8f783b293f4c4b4 + } + + @Override -+ public ScheduledTask runAtFixedRate(final Plugin plugin, Location location, final Consumer task, -+ final int initialDelayTicks, final int periodTicks) { ++ public ScheduledTask runAtFixedRate(final Plugin plugin, final World world, final int chunkX, final int chunkZ, ++ final Consumer task, final int initialDelayTicks, final int periodTicks) { + Validate.notNull(plugin, "Plugin may not be null"); -+ Validate.notNull(location, "Location may not be null"); ++ Validate.notNull(world, "World may not be null"); + Validate.notNull(task, "Task may not be null"); + if (initialDelayTicks <= 0) { + throw new IllegalArgumentException("Initial delay ticks may not be <= 0"); @@ -9959,11 +9947,9 @@ index 0000000000000000000000000000000000000000..b699e20df7a98b0dd8f783b293f4c4b4 + throw new IllegalPluginAccessException("Plugin attempted to register task while disabled"); + } + -+ location = location.clone(); ++ final LocationScheduledTask ret = new LocationScheduledTask(plugin, world, chunkX, chunkZ, periodTicks, task); + -+ final LocationScheduledTask ret = new LocationScheduledTask(plugin, location, periodTicks, task); -+ -+ if (Bukkit.isOwnedByCurrentRegion(location)) { ++ if (Bukkit.isOwnedByCurrentRegion(world, chunkX, chunkZ)) { + scheduleInternalOnRegion(ret, initialDelayTicks); + } else { + scheduleInternalOffRegion(ret, initialDelayTicks); @@ -10054,14 +10040,14 @@ index 0000000000000000000000000000000000000000..b699e20df7a98b0dd8f783b293f4c4b4 + // note: must be on the thread that owns this scheduler + // note: delay > 0 + -+ final Location location = task.location; -+ if (location == null) { ++ final World world = task.world; ++ if (world == null) { + // cancelled + return; + } + -+ final int sectionX = (location.getBlockX() >> 4) >> TickRegions.getRegionChunkShift(); -+ final int sectionZ = (location.getBlockZ() >> 4) >> TickRegions.getRegionChunkShift(); ++ final int sectionX = task.chunkX >> TickRegions.getRegionChunkShift(); ++ final int sectionZ = task.chunkZ >> TickRegions.getRegionChunkShift(); + + final Long2ObjectOpenHashMap> section = + this.tasksByDeadlineBySection.computeIfAbsent(CoordinateUtils.getChunkKey(sectionX, sectionZ), (final long keyInMap) -> { @@ -10119,16 +10105,21 @@ index 0000000000000000000000000000000000000000..b699e20df7a98b0dd8f783b293f4c4b4 + private static final int STATE_CANCELLED = 4; + + private final Plugin plugin; -+ private Location location; ++ private final int chunkX; ++ private final int chunkZ; + private final int repeatDelay; // in ticks ++ private World world; + private Consumer run; + + private volatile int state; + private static final VarHandle STATE_HANDLE = ConcurrentUtil.getVarHandle(LocationScheduledTask.class, "state", int.class); + -+ private LocationScheduledTask(final Plugin plugin, final Location location, final int repeatDelay, final Consumer run) { ++ private LocationScheduledTask(final Plugin plugin, final World world, final int chunkX, final int chunkZ, ++ final int repeatDelay, final Consumer run) { + this.plugin = plugin; -+ this.location = location; ++ this.world = world; ++ this.chunkX = chunkX; ++ this.chunkZ = chunkZ; + this.repeatDelay = repeatDelay; + this.run = run; + } @@ -10161,7 +10152,8 @@ index 0000000000000000000000000000000000000000..b699e20df7a98b0dd8f783b293f4c4b4 + try { + this.run.accept(this); + } catch (final Throwable throwable) { -+ this.plugin.getLogger().log(Level.WARNING, "Location task for " + this.plugin.getDescription().getFullName() + " at location " + this.location + " generated an exception", throwable); ++ this.plugin.getLogger().log(Level.WARNING, "Location task for " + this.plugin.getDescription().getFullName() ++ + " in world " + world + " at " + chunkX + ", " + chunkZ + " generated an exception", throwable); + } finally { + boolean reschedule = false; + if (!repeating) { @@ -10174,7 +10166,7 @@ index 0000000000000000000000000000000000000000..b699e20df7a98b0dd8f783b293f4c4b4 + + if (!reschedule) { + this.run = null; -+ this.location = null; ++ this.world = null; + } else { + FoliaRegionScheduler.scheduleInternalOnRegion(this, this.repeatDelay); + } @@ -10199,7 +10191,7 @@ index 0000000000000000000000000000000000000000..b699e20df7a98b0dd8f783b293f4c4b4 + if (STATE_IDLE == (curr = this.compareAndExchangeStateVolatile(STATE_IDLE, STATE_CANCELLED))) { + this.state = STATE_CANCELLED; + this.run = null; -+ this.location = null; ++ this.world = null; + return CancelledState.CANCELLED_BY_CALLER; + } + // try again