diff --git a/pom.xml b/pom.xml
index 772a8e0..e65e08a 100644
--- a/pom.xml
+++ b/pom.xml
@@ -36,6 +36,10 @@
spigot-repo
https://hub.spigotmc.org/nexus/content/repositories/snapshots/
+
+ papermc
+ https://repo.papermc.io/repository/maven-public/
+
@@ -46,6 +50,13 @@
1.21.3-R0.1-SNAPSHOT
provided
+
+
+ dev.folia
+ folia-api
+ 1.21.4-R0.1-SNAPSHOT
+ provided
+
@@ -93,12 +104,6 @@
bukkit-server-version
0.0.2
-
-
- community.leaf.tasks
- tasks-bukkit
- 0.0.2
-
org.bstats
diff --git a/src/main/java/community/leaf/survival/concretemixer/CauldronPowderDropListener.java b/src/main/java/community/leaf/survival/concretemixer/CauldronPowderDropListener.java
index 41cdd92..f32707b 100644
--- a/src/main/java/community/leaf/survival/concretemixer/CauldronPowderDropListener.java
+++ b/src/main/java/community/leaf/survival/concretemixer/CauldronPowderDropListener.java
@@ -1,5 +1,5 @@
/*
- * Copyright © 2022-2024, RezzedUp and Contributors
+ * Copyright © 2022-2025, RezzedUp and Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -13,8 +13,8 @@
import community.leaf.eventful.bukkit.annotations.CancelledEvents;
import community.leaf.eventful.bukkit.annotations.EventListener;
import community.leaf.survival.concretemixer.metrics.TransformationsPerHour;
+import community.leaf.survival.concretemixer.util.folia.FoliaRunnable;
import community.leaf.survival.concretemixer.util.internal.ConcreteDebug;
-import community.leaf.tasks.TaskContext;
import org.bukkit.Material;
import org.bukkit.block.Block;
import org.bukkit.block.data.BlockData;
@@ -27,7 +27,6 @@
import org.bukkit.event.entity.ItemMergeEvent;
import org.bukkit.event.player.PlayerDropItemEvent;
import org.bukkit.inventory.ItemStack;
-import org.bukkit.scheduler.BukkitTask;
import org.bukkit.util.Vector;
import pl.tlinkowski.annotation.basic.NullOr;
@@ -37,7 +36,7 @@
import java.util.UUID;
public class CauldronPowderDropListener implements Listener {
- private final Map> transformationTasksByItemUuid = new HashMap<>();
+ private final Map transformationTasksByItemUuid = new HashMap<>();
private final ConcreteMixerPlugin plugin;
private final TransformationsPerHour counter;
@@ -117,22 +116,17 @@ public void onCauldronLevelChange(CauldronLevelChangeEvent event) {
}
private boolean cancelExistingTransformation(Item item) {
- @NullOr TaskContext task =
+ @NullOr FoliaRunnable runnable =
transformationTasksByItemUuid.remove(item.getUniqueId());
- if (task != null) {
- task.cancel();
+ if (runnable != null) {
+ runnable.cancel();
return true;
}
return false;
}
- private void cancel(Item item, TaskContext task) {
- transformationTasksByItemUuid.remove(item.getUniqueId());
- task.cancel();
- }
-
private @NullOr Entity entity(@NullOr UUID uuid) {
return (uuid == null) ? null : plugin.getServer().getEntity(uuid);
}
@@ -144,113 +138,120 @@ class IterationCounter {
}
IterationCounter iterations = new IterationCounter();
-
transformationTasksByItemUuid.put(
item.getUniqueId(),
- plugin.sync().delay(2).ticks().every(2).ticks().run(task ->
+ plugin.getScheduler().runTaskTimer(item.getLocation(), new FoliaRunnable()
{
- Block cauldron = item.getLocation().getBlock();
- Material material = item.getItemStack().getType();
-
- // Outside the cauldron, dropping in ... (or not)
- if (cauldron.getType() != Material.WATER_CAULDRON) {
- iterations.outside++;
-
- // Took too long to drop in, might not even be a cauldron nearby for all we know
- if (iterations.outside > 20) {
- cancel(item, task);
- }
-
- return;
+ private void cancel(Item item) {
+ transformationTasksByItemUuid.remove(item.getUniqueId());
+ cancel();
}
-
- // Inside the cauldron
- iterations.inside++;
- boolean lowerWaterLevel = plugin.config().getOrDefault(Config.LOWER_WATER_LEVEL);
-
- // Check if player is allowed to use this specific cauldron
- // (only if water level gets lowered, since that could be considered griefing)
- if (lowerWaterLevel && entity(item.getThrower()) instanceof Player player) {
- if (!plugin.permissions().canAccessCauldron(player, cauldron)) {
- cancel(item, task);
+
+ @Override
+ public void run() {
+ Block cauldron = item.getLocation().getBlock();
+ Material material = item.getItemStack().getType();
+
+ // Outside the cauldron, dropping in ... (or not)
+ if (cauldron.getType() != Material.WATER_CAULDRON) {
+ iterations.outside++;
+
+ // Took too long to drop in, might not even be a cauldron nearby for all we know
+ if (iterations.outside > 20) {
+ cancel(item);
+ }
+
return;
}
- }
-
- // TODO: more sophisticated merging
- // Attempt to merge item stacks
- if (experimentalItemMerging && item.getItemStack().getAmount() == 1) {
- for (Entity nearby : item.getNearbyEntities(0.5, 0.5, 0.5)) {
- if (!(nearby instanceof Item cooking)) {
- continue;
- }
- if (!transformationTasksByItemUuid.containsKey(cooking.getUniqueId())) {
- continue;
- }
- if (!Objects.equals(item.getThrower(), cooking.getThrower())) {
- continue;
- }
-
- ItemStack cooked = cooking.getItemStack();
- if (cooked.getAmount() >= 64 || cooked.getType() != material) {
- continue;
+
+ // Inside the cauldron
+ iterations.inside++;
+ boolean lowerWaterLevel = plugin.config().getOrDefault(Config.LOWER_WATER_LEVEL);
+
+ // Check if player is allowed to use this specific cauldron
+ // (only if water level gets lowered, since that could be considered griefing)
+ if (lowerWaterLevel && entity(item.getThrower()) instanceof Player player) {
+ if (!plugin.permissions().canAccessCauldron(player, cauldron)) {
+ cancel(item);
+ return;
}
- if (plugin.events().call(new ItemMergeEvent(item, cooking)).isCancelled()) {
- continue; // merge cancelled, try another neighbor
+ }
+
+ // TODO: more sophisticated merging
+ // Attempt to merge item stacks
+ if (experimentalItemMerging && item.getItemStack().getAmount() == 1) {
+ for (Entity nearby : item.getNearbyEntities(0.5, 0.5, 0.5)) {
+ if (!(nearby instanceof Item cooking)) {
+ continue;
+ }
+ if (!transformationTasksByItemUuid.containsKey(cooking.getUniqueId())) {
+ continue;
+ }
+ if (!Objects.equals(item.getThrower(), cooking.getThrower())) {
+ continue;
+ }
+
+ ItemStack cooked = cooking.getItemStack();
+ if (cooked.getAmount() >= 64 || cooked.getType() != material) {
+ continue;
+ }
+ if (plugin.events().call(new ItemMergeEvent(item, cooking)).isCancelled()) {
+ continue; // merge cancelled, try another neighbor
+ }
+
+ cooked.setAmount(cooked.getAmount() + 1);
+ item.remove();
+
+ cancel(item);
+ return;
}
-
- cooked.setAmount(cooked.getAmount() + 1);
- item.remove();
-
- cancel(item, task);
+ }
+
+ if (iterations.inside == 1) {
+ item.setPickupDelay(40);
+ plugin.effects().cauldronSplashSound(item.getLocation());
+ }
+
+ if (iterations.inside < 15) {
+ plugin.effects().cauldronSplashParticles(cauldron);
return;
}
+
+ // Done with this task... it's finally time to transform the powder!
+ cancel(item);
+
+ @NullOr Concrete concrete = Concrete.ofPowder(material).orElse(null);
+ if (concrete == null) {
+ return;
+ }
+
+ ItemStack stack = item.getItemStack();
+ stack.setType(concrete.concrete());
+ item.setItemStack(stack);
+
+ item.setVelocity(new Vector(0, 0.3, 0));
+ item.setPickupDelay(10);
+
+ plugin.effects().concreteTransform(cauldron);
+ counter.transformed(stack.getAmount());
+
+ if (!lowerWaterLevel) {
+ return;
+ }
+ if (!(cauldron.getBlockData() instanceof Levelled levelled)) {
+ return;
+ }
+
+ int level = levelled.getLevel() - 1;
+
+ if (level <= 0) {
+ cauldron.setType(Material.CAULDRON);
+ } else {
+ levelled.setLevel(level);
+ cauldron.setBlockData(levelled);
+ }
}
-
- if (iterations.inside == 1) {
- item.setPickupDelay(40);
- plugin.effects().cauldronSplashSound(item.getLocation());
- }
-
- if (iterations.inside < 15) {
- plugin.effects().cauldronSplashParticles(cauldron);
- return;
- }
-
- // Done with this task... it's finally time to transform the powder!
- cancel(item, task);
-
- @NullOr Concrete concrete = Concrete.ofPowder(material).orElse(null);
- if (concrete == null) {
- return;
- }
-
- ItemStack stack = item.getItemStack();
- stack.setType(concrete.concrete());
- item.setItemStack(stack);
-
- item.setVelocity(new Vector(0, 0.3, 0));
- item.setPickupDelay(10);
-
- plugin.effects().concreteTransform(cauldron);
- counter.transformed(stack.getAmount());
-
- if (!lowerWaterLevel) {
- return;
- }
- if (!(cauldron.getBlockData() instanceof Levelled levelled)) {
- return;
- }
-
- int level = levelled.getLevel() - 1;
-
- if (level <= 0) {
- cauldron.setType(Material.CAULDRON);
- } else {
- levelled.setLevel(level);
- cauldron.setBlockData(levelled);
- }
- })
+ }, 2, 2)
);
}
diff --git a/src/main/java/community/leaf/survival/concretemixer/Concrete.java b/src/main/java/community/leaf/survival/concretemixer/Concrete.java
index ed69699..6c830c1 100644
--- a/src/main/java/community/leaf/survival/concretemixer/Concrete.java
+++ b/src/main/java/community/leaf/survival/concretemixer/Concrete.java
@@ -1,5 +1,5 @@
/*
- * Copyright © 2022-2024, RezzedUp and Contributors
+ * Copyright © 2022-2025, RezzedUp and Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
diff --git a/src/main/java/community/leaf/survival/concretemixer/ConcreteMixerCommand.java b/src/main/java/community/leaf/survival/concretemixer/ConcreteMixerCommand.java
index 98706cd..dd26f36 100644
--- a/src/main/java/community/leaf/survival/concretemixer/ConcreteMixerCommand.java
+++ b/src/main/java/community/leaf/survival/concretemixer/ConcreteMixerCommand.java
@@ -1,5 +1,5 @@
/*
- * Copyright © 2022-2024, RezzedUp and Contributors
+ * Copyright © 2022-2025, RezzedUp and Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
diff --git a/src/main/java/community/leaf/survival/concretemixer/ConcreteMixerPlugin.java b/src/main/java/community/leaf/survival/concretemixer/ConcreteMixerPlugin.java
index 49978e7..58da321 100644
--- a/src/main/java/community/leaf/survival/concretemixer/ConcreteMixerPlugin.java
+++ b/src/main/java/community/leaf/survival/concretemixer/ConcreteMixerPlugin.java
@@ -1,5 +1,5 @@
/*
- * Copyright © 2022-2024, RezzedUp and Contributors
+ * Copyright © 2022-2025, RezzedUp and Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -11,7 +11,7 @@
import community.leaf.eventful.bukkit.BukkitEventSource;
import community.leaf.survival.concretemixer.hooks.HookHandler;
import community.leaf.survival.concretemixer.metrics.TransformationsPerHour;
-import community.leaf.tasks.bukkit.BukkitTaskSource;
+import community.leaf.survival.concretemixer.util.folia.SchedulerUtils;
import org.bstats.bukkit.Metrics;
import org.bstats.charts.SingleLineChart;
import org.bukkit.command.PluginCommand;
@@ -21,7 +21,10 @@
import java.nio.file.Path;
-public class ConcreteMixerPlugin extends JavaPlugin implements BukkitEventSource, BukkitTaskSource {
+public class ConcreteMixerPlugin extends JavaPlugin implements BukkitEventSource {
+ private boolean usingFolia = false;
+
+ private @NullOr SchedulerUtils scheduler;
private @NullOr Version version;
private @NullOr Path directory;
private @NullOr Config config;
@@ -29,6 +32,17 @@ public class ConcreteMixerPlugin extends JavaPlugin implements BukkitEventSource
private @NullOr HookHandler hooks;
private @NullOr PermissionHandler permissions;
private @NullOr UpdateChecker updates;
+
+ @Override
+ public void onLoad() {
+ try {
+ Class.forName("io.papermc.paper.threadedregions.scheduler.RegionScheduler");
+ usingFolia = true;
+ } catch (ClassNotFoundException e) {
+ usingFolia = false;
+ }
+ scheduler = new SchedulerUtils(this);
+ }
@Override
public void onEnable() {
@@ -69,6 +83,14 @@ private static T initialized(@NullOr T thing) {
public Plugin plugin() {
return this;
}
+
+ public boolean isFolia() {
+ return usingFolia;
+ }
+
+ public SchedulerUtils getScheduler() {
+ return scheduler;
+ }
public Version version() {
return initialized(version);
diff --git a/src/main/java/community/leaf/survival/concretemixer/Config.java b/src/main/java/community/leaf/survival/concretemixer/Config.java
index 9f66762..d9bc4e4 100644
--- a/src/main/java/community/leaf/survival/concretemixer/Config.java
+++ b/src/main/java/community/leaf/survival/concretemixer/Config.java
@@ -1,5 +1,5 @@
/*
- * Copyright © 2022-2024, RezzedUp and Contributors
+ * Copyright © 2022-2025, RezzedUp and Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
diff --git a/src/main/java/community/leaf/survival/concretemixer/EffectHandler.java b/src/main/java/community/leaf/survival/concretemixer/EffectHandler.java
index eddd107..2791a2d 100644
--- a/src/main/java/community/leaf/survival/concretemixer/EffectHandler.java
+++ b/src/main/java/community/leaf/survival/concretemixer/EffectHandler.java
@@ -1,5 +1,5 @@
/*
- * Copyright © 2022-2024, RezzedUp and Contributors
+ * Copyright © 2022-2025, RezzedUp and Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
diff --git a/src/main/java/community/leaf/survival/concretemixer/PermissionHandler.java b/src/main/java/community/leaf/survival/concretemixer/PermissionHandler.java
index 1f87b71..6f352ae 100644
--- a/src/main/java/community/leaf/survival/concretemixer/PermissionHandler.java
+++ b/src/main/java/community/leaf/survival/concretemixer/PermissionHandler.java
@@ -1,5 +1,5 @@
/*
- * Copyright © 2022-2024, RezzedUp and Contributors
+ * Copyright © 2022-2025, RezzedUp and Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
diff --git a/src/main/java/community/leaf/survival/concretemixer/UpdateChecker.java b/src/main/java/community/leaf/survival/concretemixer/UpdateChecker.java
index 743b197..ea2ac92 100644
--- a/src/main/java/community/leaf/survival/concretemixer/UpdateChecker.java
+++ b/src/main/java/community/leaf/survival/concretemixer/UpdateChecker.java
@@ -1,5 +1,5 @@
/*
- * Copyright © 2022-2024, RezzedUp and Contributors
+ * Copyright © 2022-2025, RezzedUp and Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -16,9 +16,8 @@
import community.leaf.evergreen.bukkit.versions.MinecraftVersion;
import community.leaf.survival.concretemixer.util.Strings;
import community.leaf.survival.concretemixer.util.Versions;
-import community.leaf.tasks.TaskContext;
+import community.leaf.survival.concretemixer.util.folia.FoliaRunnable;
import net.md_5.bungee.api.ChatColor;
-import org.bukkit.scheduler.BukkitTask;
import pl.tlinkowski.annotation.basic.NullOr;
import java.net.URI;
@@ -40,7 +39,7 @@ public class UpdateChecker {
private final String userAgent;
private final URI modrinthProjectVersionsUri;
- private @NullOr TaskContext task;
+ private @NullOr FoliaRunnable runnable;
private volatile @NullOr Version latestAvailableVersion;
public UpdateChecker(ConcreteMixerPlugin plugin) {
@@ -60,7 +59,7 @@ public UpdateChecker(ConcreteMixerPlugin plugin) {
}
public boolean isRunningUpdateCheckTask() {
- return task != null && !task.isCancelled();
+ return runnable != null && !runnable.isCancelled();
}
public Optional latestAvailableVersion() {
@@ -82,16 +81,22 @@ public Optional latestUpdateUrl() {
}
public void end() {
- if (task != null) {
- task.cancel();
+ if (runnable != null) {
+ runnable.cancel();
}
}
public void reload() {
if (plugin.config().getOrDefault(Config.UPDATES)) {
- if (task == null || task.isCancelled()) {
- int duration = ThreadLocalRandom.current().nextInt(6, 12);
- this.task = plugin.async().delay(10).ticks().every(duration).hours().run(this::checkForUpdates);
+ if (runnable == null || runnable.isCancelled()) {
+ long duration = ThreadLocalRandom.current().nextInt(6, 12)*1000L*60L*60L;
+ runnable = new FoliaRunnable() {
+ @Override
+ public void run() {
+ checkForUpdates();
+ }
+ };
+ plugin.getScheduler().runTaskTimerAsynchronously(runnable, 10, duration);
}
} else {
latestAvailableVersion = null;
@@ -166,8 +171,8 @@ private void checkForUpdates() {
this.latestAvailableVersion = latest;
} catch (Exception ignored) {
}
-
- plugin.sync().run(this::notifyConsoleIfUpdateAvailable);
+
+ plugin.getScheduler().runTaskAsynchronously(this::notifyConsoleIfUpdateAvailable);
}
private void print(String text) {
diff --git a/src/main/java/community/leaf/survival/concretemixer/hooks/CauldronAccessHook.java b/src/main/java/community/leaf/survival/concretemixer/hooks/CauldronAccessHook.java
index b9b6952..3a8353d 100644
--- a/src/main/java/community/leaf/survival/concretemixer/hooks/CauldronAccessHook.java
+++ b/src/main/java/community/leaf/survival/concretemixer/hooks/CauldronAccessHook.java
@@ -1,5 +1,5 @@
/*
- * Copyright © 2022-2024, RezzedUp and Contributors
+ * Copyright © 2022-2025, RezzedUp and Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
diff --git a/src/main/java/community/leaf/survival/concretemixer/hooks/Hook.java b/src/main/java/community/leaf/survival/concretemixer/hooks/Hook.java
index f9c06e6..060d2b1 100644
--- a/src/main/java/community/leaf/survival/concretemixer/hooks/Hook.java
+++ b/src/main/java/community/leaf/survival/concretemixer/hooks/Hook.java
@@ -1,5 +1,5 @@
/*
- * Copyright © 2022-2024, RezzedUp and Contributors
+ * Copyright © 2022-2025, RezzedUp and Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
diff --git a/src/main/java/community/leaf/survival/concretemixer/hooks/HookHandler.java b/src/main/java/community/leaf/survival/concretemixer/hooks/HookHandler.java
index b27a678..7d889b4 100644
--- a/src/main/java/community/leaf/survival/concretemixer/hooks/HookHandler.java
+++ b/src/main/java/community/leaf/survival/concretemixer/hooks/HookHandler.java
@@ -1,5 +1,5 @@
/*
- * Copyright © 2022-2024, RezzedUp and Contributors
+ * Copyright © 2022-2025, RezzedUp and Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
diff --git a/src/main/java/community/leaf/survival/concretemixer/hooks/RegisteredHook.java b/src/main/java/community/leaf/survival/concretemixer/hooks/RegisteredHook.java
index 88d63cc..42010ab 100644
--- a/src/main/java/community/leaf/survival/concretemixer/hooks/RegisteredHook.java
+++ b/src/main/java/community/leaf/survival/concretemixer/hooks/RegisteredHook.java
@@ -1,5 +1,5 @@
/*
- * Copyright © 2022-2024, RezzedUp and Contributors
+ * Copyright © 2022-2025, RezzedUp and Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
diff --git a/src/main/java/community/leaf/survival/concretemixer/hooks/impl/GriefPreventionCauldronAccessHook.java b/src/main/java/community/leaf/survival/concretemixer/hooks/impl/GriefPreventionCauldronAccessHook.java
index af5de03..83a4978 100644
--- a/src/main/java/community/leaf/survival/concretemixer/hooks/impl/GriefPreventionCauldronAccessHook.java
+++ b/src/main/java/community/leaf/survival/concretemixer/hooks/impl/GriefPreventionCauldronAccessHook.java
@@ -1,5 +1,5 @@
/*
- * Copyright © 2022-2024, RezzedUp and Contributors
+ * Copyright © 2022-2025, RezzedUp and Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
diff --git a/src/main/java/community/leaf/survival/concretemixer/hooks/impl/UniversalCauldronAccessHook.java b/src/main/java/community/leaf/survival/concretemixer/hooks/impl/UniversalCauldronAccessHook.java
index ff5f53c..2e948ed 100644
--- a/src/main/java/community/leaf/survival/concretemixer/hooks/impl/UniversalCauldronAccessHook.java
+++ b/src/main/java/community/leaf/survival/concretemixer/hooks/impl/UniversalCauldronAccessHook.java
@@ -1,5 +1,5 @@
/*
- * Copyright © 2022-2024, RezzedUp and Contributors
+ * Copyright © 2022-2025, RezzedUp and Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
diff --git a/src/main/java/community/leaf/survival/concretemixer/hooks/impl/package-info.java b/src/main/java/community/leaf/survival/concretemixer/hooks/impl/package-info.java
index 1816522..1d94d43 100644
--- a/src/main/java/community/leaf/survival/concretemixer/hooks/impl/package-info.java
+++ b/src/main/java/community/leaf/survival/concretemixer/hooks/impl/package-info.java
@@ -1,5 +1,5 @@
/*
- * Copyright © 2022-2024, RezzedUp and Contributors
+ * Copyright © 2022-2025, RezzedUp and Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
diff --git a/src/main/java/community/leaf/survival/concretemixer/hooks/package-info.java b/src/main/java/community/leaf/survival/concretemixer/hooks/package-info.java
index b12a78c..8a5aee6 100644
--- a/src/main/java/community/leaf/survival/concretemixer/hooks/package-info.java
+++ b/src/main/java/community/leaf/survival/concretemixer/hooks/package-info.java
@@ -1,5 +1,5 @@
/*
- * Copyright © 2022-2024, RezzedUp and Contributors
+ * Copyright © 2022-2025, RezzedUp and Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
diff --git a/src/main/java/community/leaf/survival/concretemixer/metrics/TransformationsPerHour.java b/src/main/java/community/leaf/survival/concretemixer/metrics/TransformationsPerHour.java
index 48c7ca1..431a67c 100644
--- a/src/main/java/community/leaf/survival/concretemixer/metrics/TransformationsPerHour.java
+++ b/src/main/java/community/leaf/survival/concretemixer/metrics/TransformationsPerHour.java
@@ -1,5 +1,5 @@
/*
- * Copyright © 2022-2024, RezzedUp and Contributors
+ * Copyright © 2022-2025, RezzedUp and Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
diff --git a/src/main/java/community/leaf/survival/concretemixer/metrics/package-info.java b/src/main/java/community/leaf/survival/concretemixer/metrics/package-info.java
index f2c6711..5a234de 100644
--- a/src/main/java/community/leaf/survival/concretemixer/metrics/package-info.java
+++ b/src/main/java/community/leaf/survival/concretemixer/metrics/package-info.java
@@ -1,5 +1,5 @@
/*
- * Copyright © 2022-2024, RezzedUp and Contributors
+ * Copyright © 2022-2025, RezzedUp and Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
diff --git a/src/main/java/community/leaf/survival/concretemixer/package-info.java b/src/main/java/community/leaf/survival/concretemixer/package-info.java
index fc21617..40328a4 100644
--- a/src/main/java/community/leaf/survival/concretemixer/package-info.java
+++ b/src/main/java/community/leaf/survival/concretemixer/package-info.java
@@ -1,5 +1,5 @@
/*
- * Copyright © 2022-2024, RezzedUp and Contributors
+ * Copyright © 2022-2025, RezzedUp and Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
diff --git a/src/main/java/community/leaf/survival/concretemixer/util/Strings.java b/src/main/java/community/leaf/survival/concretemixer/util/Strings.java
index a943538..db4e8cc 100644
--- a/src/main/java/community/leaf/survival/concretemixer/util/Strings.java
+++ b/src/main/java/community/leaf/survival/concretemixer/util/Strings.java
@@ -1,5 +1,5 @@
/*
- * Copyright © 2022-2024, RezzedUp and Contributors
+ * Copyright © 2022-2025, RezzedUp and Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
diff --git a/src/main/java/community/leaf/survival/concretemixer/util/Versions.java b/src/main/java/community/leaf/survival/concretemixer/util/Versions.java
index 0c683fd..b218628 100644
--- a/src/main/java/community/leaf/survival/concretemixer/util/Versions.java
+++ b/src/main/java/community/leaf/survival/concretemixer/util/Versions.java
@@ -1,5 +1,5 @@
/*
- * Copyright © 2022-2024, RezzedUp and Contributors
+ * Copyright © 2022-2025, RezzedUp and Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
diff --git a/src/main/java/community/leaf/survival/concretemixer/util/folia/FoliaRunnable.java b/src/main/java/community/leaf/survival/concretemixer/util/folia/FoliaRunnable.java
new file mode 100644
index 0000000..7ab7a02
--- /dev/null
+++ b/src/main/java/community/leaf/survival/concretemixer/util/folia/FoliaRunnable.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright © 2022-2025, RezzedUp and Contributors
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+package community.leaf.survival.concretemixer.util.folia;
+
+import io.papermc.paper.threadedregions.scheduler.ScheduledTask;
+import org.bukkit.scheduler.BukkitRunnable;
+
+public class FoliaRunnable extends BukkitRunnable {
+
+ private ScheduledTask foliaTask;
+
+ @Override
+ public synchronized void cancel() throws IllegalStateException {
+ if(foliaTask != null) foliaTask.cancel();
+ }
+
+ @Override
+ public void run() {
+ }
+
+ public void setScheduledTask(ScheduledTask task) {
+ this.foliaTask = task;
+ }
+}
diff --git a/src/main/java/community/leaf/survival/concretemixer/util/folia/SchedulerUtils.java b/src/main/java/community/leaf/survival/concretemixer/util/folia/SchedulerUtils.java
new file mode 100644
index 0000000..768a105
--- /dev/null
+++ b/src/main/java/community/leaf/survival/concretemixer/util/folia/SchedulerUtils.java
@@ -0,0 +1,182 @@
+/*
+ * Copyright © 2022-2025, RezzedUp and Contributors
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+package community.leaf.survival.concretemixer.util.folia;
+
+import community.leaf.survival.concretemixer.ConcreteMixerPlugin;
+import io.papermc.paper.threadedregions.scheduler.GlobalRegionScheduler;
+import io.papermc.paper.threadedregions.scheduler.RegionScheduler;
+import io.papermc.paper.threadedregions.scheduler.ScheduledTask;
+import org.bukkit.Location;
+
+import java.lang.reflect.Method;
+
+/**
+ * Utility class for scheduling tasks in a Paper/Folia server.
+ */
+public class SchedulerUtils {
+
+ private final ConcreteMixerPlugin plugin;
+
+ public SchedulerUtils(ConcreteMixerPlugin plugin) {
+ this.plugin = plugin;
+ }
+
+ /**
+ * Schedules a task to run later on the main server thread.
+ * @param loc The location where the task should run, or null for the main thread.
+ * @param task The task to run.
+ * @param delay The delay in ticks before the task runs.
+ */
+ public void runTaskLater(Location loc, Runnable task, long delay) {
+ if (plugin.isFolia()) {
+ try {
+ if (loc != null) {
+ Method getRegionScheduler = plugin.getServer().getClass().getMethod("getRegionScheduler");
+ RegionScheduler regionScheduler = (RegionScheduler) getRegionScheduler.invoke(plugin.getServer());
+ regionScheduler.runDelayed(
+ plugin,
+ loc,
+ (ScheduledTask scheduledTask) -> task.run(),
+ delay
+ );
+ } else {
+ Method getGlobalScheduler = plugin.getServer().getClass().getMethod("getGlobalRegionScheduler");
+ GlobalRegionScheduler globalScheduler = (GlobalRegionScheduler) getGlobalScheduler.invoke(plugin.getServer());
+ globalScheduler.runDelayed(
+ plugin,
+ (ScheduledTask scheduledTask) -> task.run(),
+ delay
+ );
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ return;
+ }
+ plugin.getServer().getScheduler().runTaskLater(plugin, task, delay);
+ }
+
+ /**
+ * Schedules a task to run repeatedly on the main server thread.
+ *
+ * @param loc The location where the task should run, or null for the main thread.
+ * @param runnable The BukkitRunnable to run.
+ * @param delay The delay in ticks before the task runs.
+ * @param period The period in ticks between subsequent runs of the task.
+ * @return
+ */
+ public FoliaRunnable runTaskTimer(Location loc, FoliaRunnable runnable, long delay, long period) {
+ if (plugin.isFolia()) {
+ try {
+ ScheduledTask task;
+ if (loc != null) {
+ Method getRegionScheduler = plugin.getServer().getClass().getMethod("getRegionScheduler");
+ RegionScheduler regionScheduler = (RegionScheduler) getRegionScheduler.invoke(plugin.getServer());
+ task = regionScheduler.runAtFixedRate(
+ plugin,
+ loc,
+ (ScheduledTask t) -> runnable.run(),
+ delay,
+ period
+ );
+ } else {
+ Method getGlobalScheduler = plugin.getServer().getClass().getMethod("getGlobalRegionScheduler");
+ GlobalRegionScheduler globalScheduler = (GlobalRegionScheduler) getGlobalScheduler.invoke(plugin.getServer());
+ task = globalScheduler.runAtFixedRate(
+ plugin,
+ (ScheduledTask t) -> runnable.run(),
+ delay,
+ period
+ );
+ }
+ runnable.setScheduledTask(task);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ return runnable;
+ }
+ runnable.runTaskTimer(plugin, delay, period);
+ return runnable;
+ }
+
+ /**
+ * Schedules a task to run repeatedly on the main server thread asynchronously.
+ *
+ * @param runnable The FoliaRunnable to run.
+ * @param delay The delay in ticks before the task runs.
+ * @param period The period in ticks between subsequent runs of the task.
+ * @return
+ */
+ public FoliaRunnable runTaskTimerAsynchronously(FoliaRunnable runnable, long delay, long period) {
+ if (plugin.isFolia()) {
+ try {
+ Method getGlobalScheduler = plugin.getServer().getClass().getMethod("getGlobalRegionScheduler");
+ GlobalRegionScheduler globalScheduler = (GlobalRegionScheduler) getGlobalScheduler.invoke(plugin.getServer());
+ class AsyncRepeatingTask {
+ private ScheduledTask task;
+ void start(long initialDelay) {
+ task = globalScheduler.runDelayed(plugin, (ScheduledTask t) -> {
+ runnable.run();
+ start(period);
+ }, initialDelay);
+ runnable.setScheduledTask(task);
+ }
+ }
+ new AsyncRepeatingTask().start(delay);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ return runnable;
+ }
+ runnable.runTaskTimerAsynchronously(plugin, delay, period);
+ return runnable;
+ }
+
+ /**
+ * Runs a task asynchronously on the main server thread.
+ * @param task The task to run.
+ */
+ public void runTaskAsynchronously(Runnable task) {
+ if (plugin.isFolia()) {
+ try {
+ Method getGlobalScheduler = plugin.getServer().getClass().getMethod("getGlobalRegionScheduler");
+ GlobalRegionScheduler globalScheduler = (GlobalRegionScheduler) getGlobalScheduler.invoke(plugin.getServer());
+ globalScheduler.execute(plugin, task);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ return;
+ }
+ plugin.getServer().getScheduler().runTaskAsynchronously(plugin, task);
+ }
+
+ /**
+ * Runs a task on the main server thread at a specific location or globally.
+ * @param loc The location where the task should run, or null for the main thread.
+ * @param task The task to run.
+ */
+ public void runTask(Location loc, Runnable task) {
+ if (plugin.isFolia()) {
+ try {
+ if (loc != null) {
+ Method getRegionScheduler = plugin.getServer().getClass().getMethod("getRegionScheduler");
+ RegionScheduler regionScheduler = (RegionScheduler) getRegionScheduler.invoke(plugin.getServer());
+ regionScheduler.execute(plugin, loc, task);
+ } else {
+ Method getGlobalScheduler = plugin.getServer().getClass().getMethod("getGlobalRegionScheduler");
+ GlobalRegionScheduler globalScheduler = (GlobalRegionScheduler) getGlobalScheduler.invoke(plugin.getServer());
+ globalScheduler.execute(plugin, task);
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ return;
+ }
+ plugin.getServer().getScheduler().runTask(plugin, task);
+ }
+}
diff --git a/src/main/java/community/leaf/survival/concretemixer/util/internal/ConcreteDebug.java b/src/main/java/community/leaf/survival/concretemixer/util/internal/ConcreteDebug.java
index 0dab1b2..1891516 100644
--- a/src/main/java/community/leaf/survival/concretemixer/util/internal/ConcreteDebug.java
+++ b/src/main/java/community/leaf/survival/concretemixer/util/internal/ConcreteDebug.java
@@ -1,5 +1,5 @@
/*
- * Copyright © 2022-2024, RezzedUp and Contributors
+ * Copyright © 2022-2025, RezzedUp and Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
diff --git a/src/main/java/community/leaf/survival/concretemixer/util/internal/package-info.java b/src/main/java/community/leaf/survival/concretemixer/util/internal/package-info.java
index 9974a74..ce28d34 100644
--- a/src/main/java/community/leaf/survival/concretemixer/util/internal/package-info.java
+++ b/src/main/java/community/leaf/survival/concretemixer/util/internal/package-info.java
@@ -1,5 +1,5 @@
/*
- * Copyright © 2022-2024, RezzedUp and Contributors
+ * Copyright © 2022-2025, RezzedUp and Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
diff --git a/src/main/java/community/leaf/survival/concretemixer/util/package-info.java b/src/main/java/community/leaf/survival/concretemixer/util/package-info.java
index 10c2ee4..953a950 100644
--- a/src/main/java/community/leaf/survival/concretemixer/util/package-info.java
+++ b/src/main/java/community/leaf/survival/concretemixer/util/package-info.java
@@ -1,5 +1,5 @@
/*
- * Copyright © 2022-2024, RezzedUp and Contributors
+ * Copyright © 2022-2025, RezzedUp and Contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml
index f9b5f97..61ee50b 100644
--- a/src/main/resources/plugin.yml
+++ b/src/main/resources/plugin.yml
@@ -3,6 +3,7 @@ name: ${project.name}
version: ${project.version}
api-version: 1.17
softdepend: [ GriefPrevention ]
+folia-supported: true
authors: [ RezzedUp ]
description: ${project.description}