Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
target/
*.iml
dependency-reduced-pom.xml
.idea/
.idea/
target/
dependency-reduced-pom.xml
13 changes: 13 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,11 @@
<id>minecraft-repo</id>
<url>https://libraries.minecraft.net/</url>
</repository>

<repository>
<id>papermc</id>
<url>https://repo.papermc.io/repository/maven-public/</url>
</repository>
</repositories>

<dependencies>
Expand Down Expand Up @@ -76,6 +81,14 @@
<scope>compile</scope>
</dependency>

<!-- Folia API (for direct scheduler API calls without reflection) -->
<dependency>
<groupId>dev.folia</groupId>
<artifactId>folia-api</artifactId>
<version>1.20.4-R0.1-SNAPSHOT</version>
<scope>provided</scope>
</dependency>

<!-- Mojang Authlib (required for skulls) -->
<dependency>
<groupId>com.mojang</groupId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ public SqlStorageImpl(DatabaseConnectionSettings settings, Messages M) {
}

private void runAsync(Runnable runnable) {
Bukkit.getScheduler().runTaskAsynchronously(ChatColor.getPlugin(), runnable);
com.sulphate.chatcolor2.schedulers.SchedulerAdapter.runAsync(ChatColor.getPlugin(), runnable);
}

private boolean initialiseDatabase() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
import com.sulphate.chatcolor2.utils.GeneralUtils;
import org.bukkit.Bukkit;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.scheduler.BukkitTask;

import java.io.File;
import java.io.IOException;
Expand All @@ -14,7 +13,7 @@ public class AutoSaveScheduler {

private final ChatColor plugin;

private BukkitTask task;
private SchedulerAdapter.TaskWrapper task;
private final ConcurrentHashMap<String, YamlConfiguration> configsToSave;
private int saveInterval;

Expand All @@ -27,7 +26,8 @@ public AutoSaveScheduler(int saveInterval) {
}

private void run() {
task = Bukkit.getScheduler().runTaskTimer(plugin, this::saveAllConfigs, (long) saveInterval * 20 * 60, (long) saveInterval * 20 * 60);
long intervalTicks = (long) saveInterval * 20 * 60;
task = SchedulerAdapter.runTimer(plugin, this::saveAllConfigs, intervalTicks, intervalTicks);
}

public void setSaveInterval(int saveInterval) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
import com.sulphate.chatcolor2.managers.ConfigsManager;
import com.sulphate.chatcolor2.managers.ConfirmationsManager;
import com.sulphate.chatcolor2.utils.Config;
import org.bukkit.Bukkit;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.entity.Player;

Expand All @@ -18,7 +17,7 @@ public class ConfirmScheduler {
private final YamlConfiguration mainConfig;

private final Player player;
private int id;
private SchedulerAdapter.TaskWrapper task;
private final Setting setting;
private final Object value;

Expand All @@ -35,14 +34,15 @@ public ConfirmScheduler(Messages M, ConfirmationsManager confirmationsManager, C
}

private void run() {
id = Bukkit.getScheduler().scheduleSyncDelayedTask(ChatColor.getPlugin(), () -> {
long delayTicks = mainConfig.getInt(Setting.CONFIRM_TIMEOUT.getConfigPath()) * 20L;
task = SchedulerAdapter.runForEntity(ChatColor.getPlugin(), player, () -> {
player.sendMessage(M.PREFIX + M.DID_NOT_CONFIRM);
confirmationsManager.removeConfirmingPlayer(player);
}, mainConfig.getInt(Setting.CONFIRM_TIMEOUT.getConfigPath()) * 20L);
}, delayTicks);
}

public void cancelScheduler() {
Bukkit.getScheduler().cancelTask(id);
task.cancel();
confirmationsManager.removeConfirmingPlayer(player);
}

Expand Down
206 changes: 206 additions & 0 deletions src/main/java/com/sulphate/chatcolor2/schedulers/SchedulerAdapter.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,206 @@
package com.sulphate.chatcolor2.schedulers;

import io.papermc.paper.threadedregions.scheduler.ScheduledTask;
import org.bukkit.Bukkit;
import org.bukkit.entity.Entity;
import org.bukkit.plugin.Plugin;

import java.util.function.Consumer;

/**
* Scheduler abstraction that provides compatibility between Paper and Folia.
* Automatically detects the server type and uses appropriate scheduling methods.
*
* Folia-specific code is isolated in a static inner class so that its classes
* are only loaded (and linked) when actually called on a Folia server. This
* avoids reflection while remaining safe on non-Folia servers where the Folia
* classes do not exist at runtime.
*/
public class SchedulerAdapter {

private static final boolean IS_FOLIA;

static {
boolean folia = false;
try {
Class.forName("io.papermc.paper.threadedregions.scheduler.AsyncScheduler");
folia = true;
} catch (ClassNotFoundException e) {
// Running on Paper/Spigot
}
IS_FOLIA = folia;
}

/**
* Check if the server is running Folia.
*/
public static boolean isFolia() {
return IS_FOLIA;
}

/**
* Runs a task repeatedly on the global region (Folia) or main thread (Paper).
*
* @param plugin The plugin instance
* @param task The task to run
* @param delayTicks Delay before first execution in ticks
* @param periodTicks Period between executions in ticks
* @return A task wrapper that can be cancelled
*/
public static TaskWrapper runTimer(Plugin plugin, Runnable task, long delayTicks, long periodTicks) {
if (IS_FOLIA) {
return FoliaScheduling.runTimer(plugin, task, delayTicks, periodTicks);
} else {
int taskId = Bukkit.getScheduler().runTaskTimer(plugin, task, delayTicks, periodTicks).getTaskId();
return new BukkitTaskWrapper(taskId);
}
}

/**
* Runs a task once after a delay on the global region (Folia) or main thread (Paper).
*
* @param plugin The plugin instance
* @param task The task to run
* @param delayTicks Delay before execution in ticks
* @return A task wrapper that can be cancelled
*/
public static TaskWrapper runLater(Plugin plugin, Runnable task, long delayTicks) {
if (IS_FOLIA) {
return FoliaScheduling.runLater(plugin, task, delayTicks);
} else {
int taskId = Bukkit.getScheduler().runTaskLater(plugin, task, delayTicks).getTaskId();
return new BukkitTaskWrapper(taskId);
}
}

/**
* Runs a task asynchronously.
*
* @param plugin The plugin instance
* @param task The task to run
*/
public static void runAsync(Plugin plugin, Runnable task) {
if (IS_FOLIA) {
FoliaScheduling.runAsync(plugin, task);
} else {
Bukkit.getScheduler().runTaskAsynchronously(plugin, task);
}
}

/**
* Runs a task on the entity's scheduler (Folia) or main thread (Paper).
* This is used for entity-specific operations.
*
* @param plugin The plugin instance
* @param entity The entity
* @param task The task to run
*/
public static void runForEntity(Plugin plugin, Entity entity, Runnable task) {
if (IS_FOLIA) {
FoliaScheduling.runForEntity(plugin, entity, task);
} else {
Bukkit.getScheduler().runTask(plugin, task);
}
}

/**
* Runs a task after a delay on the entity's scheduler (Folia) or main thread (Paper).
* This is used for player-specific delayed operations.
*
* @param plugin The plugin instance
* @param entity The entity
* @param task The task to run
* @param delayTicks Delay before execution in ticks
* @return A task wrapper that can be cancelled
*/
public static TaskWrapper runForEntity(Plugin plugin, Entity entity, Runnable task, long delayTicks) {
if (IS_FOLIA) {
return FoliaScheduling.runForEntity(plugin, entity, task, delayTicks);
} else {
int taskId = Bukkit.getScheduler().runTaskLater(plugin, task, delayTicks).getTaskId();
return new BukkitTaskWrapper(taskId);
}
}

// -------------------------------------------------------------------------
// Task wrapper interface and implementations
// -------------------------------------------------------------------------

/**
* Wrapper interface for scheduled tasks that can be cancelled.
*/
public interface TaskWrapper {
void cancel();
}

private static class BukkitTaskWrapper implements TaskWrapper {
private final int taskId;

BukkitTaskWrapper(int taskId) {
this.taskId = taskId;
}

@Override
public void cancel() {
Bukkit.getScheduler().cancelTask(taskId);
}
}

// -------------------------------------------------------------------------
// Folia-specific scheduling, isolated in its own class so that Folia types
// are only resolved by the JVM when this class is actually loaded (i.e.
// only on Folia servers).
// -------------------------------------------------------------------------

private static final class FoliaScheduling {

static TaskWrapper runTimer(Plugin plugin, Runnable task, long delayTicks, long periodTicks) {
Consumer<ScheduledTask> consumer = scheduledTask -> task.run();
ScheduledTask handle = Bukkit.getServer()
.getGlobalRegionScheduler()
.runAtFixedRate(plugin, consumer, delayTicks, periodTicks);
return new FoliaTaskWrapper(handle);
}

static TaskWrapper runLater(Plugin plugin, Runnable task, long delayTicks) {
Consumer<ScheduledTask> consumer = scheduledTask -> task.run();
ScheduledTask handle = Bukkit.getServer()
.getGlobalRegionScheduler()
.runDelayed(plugin, consumer, delayTicks);
return new FoliaTaskWrapper(handle);
}

static void runAsync(Plugin plugin, Runnable task) {
Consumer<ScheduledTask> consumer = scheduledTask -> task.run();
Bukkit.getServer()
.getAsyncScheduler()
.runNow(plugin, consumer);
}

static void runForEntity(Plugin plugin, Entity entity, Runnable task) {
Consumer<ScheduledTask> consumer = scheduledTask -> task.run();
entity.getScheduler().run(plugin, consumer, null);
}

static TaskWrapper runForEntity(Plugin plugin, Entity entity, Runnable task, long delayTicks) {
Consumer<ScheduledTask> consumer = scheduledTask -> task.run();
ScheduledTask handle = entity.getScheduler()
.runDelayed(plugin, consumer, null, delayTicks);
return new FoliaTaskWrapper(handle);
}

private static class FoliaTaskWrapper implements TaskWrapper {
private final ScheduledTask handle;

FoliaTaskWrapper(ScheduledTask handle) {
this.handle = handle;
}

@Override
public void cancel() {
handle.cancel();
}
}
}

}
1 change: 1 addition & 0 deletions src/main/resources/plugin.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ main: com.sulphate.chatcolor2.main.ChatColor
version: ${project.version}
api-version: "1.13"
author: Sulphate
folia-supported: true

softdepend: [ PlaceholderAPI ]

Expand Down