diff --git a/pom.xml b/pom.xml
index 313bd05..0551410 100644
--- a/pom.xml
+++ b/pom.xml
@@ -21,20 +21,24 @@
jitpack.io
https://jitpack.io
+
+ papermc
+ https://repo.papermc.io/repository/maven-public/
+
org.spigotmc
spigot-api
- 1.21.3-R0.1-SNAPSHOT
+ 1.21.8-R0.1-SNAPSHOT
provided
- com.github.lokka30
- MicroLib
- 3.2.1
- compile
+ dev.folia
+ folia-api
+ 1.21.8-R0.1-SNAPSHOT
+ provided
org.bstats
diff --git a/src/main/java/me/lokka30/microlib/files/JsonConfigFile.java b/src/main/java/me/lokka30/microlib/files/JsonConfigFile.java
new file mode 100644
index 0000000..9086ef9
--- /dev/null
+++ b/src/main/java/me/lokka30/microlib/files/JsonConfigFile.java
@@ -0,0 +1,82 @@
+package me.lokka30.microlib.files;
+
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.StandardOpenOption;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import org.bukkit.plugin.Plugin;
+
+public class JsonConfigFile {
+ private static final Gson GSON = (new GsonBuilder()).setPrettyPrinting().create();
+ private final Plugin plugin;
+ private final File file;
+ private final Map values;
+ private final BufferedReader reader;
+
+ public JsonConfigFile(Plugin plugin, File file) throws IOException {
+ this.values = new HashMap();
+ this.plugin = plugin;
+ this.file = file;
+ this.reader = Files.newBufferedReader(this.getPath());
+ }
+
+ public JsonConfigFile(Plugin plugin, String name) throws IOException {
+ this(plugin, new File(plugin.getDataFolder(), name));
+ }
+
+ public BufferedReader getReader() {
+ return this.reader;
+ }
+
+ public Map getValues() {
+ return this.values;
+ }
+
+ public Object get(String key) throws ClassCastException, NullPointerException {
+ return this.getValues().get(key);
+ }
+
+ public Object set(String key, Object value) throws UnsupportedOperationException, ClassCastException, NullPointerException, IllegalArgumentException {
+ return this.getValues().put(key, value);
+ }
+
+ public File getFile() {
+ return this.file;
+ }
+
+ public String getName() {
+ return this.getFile().getName();
+ }
+
+ public Path getPath() {
+ return this.getFile().toPath();
+ }
+
+ private void createIfNotExists() throws IOException {
+ if (!this.getFile().exists() || this.getFile().isDirectory()) {
+ try {
+ this.plugin.saveResource(this.getName(), false);
+ } catch (IllegalArgumentException var2) {
+ if(!this.getFile().createNewFile()) throw new IllegalArgumentException("Unable to create new JSON file!", var2);
+ }
+ }
+
+ }
+
+ public void load() throws IOException {
+ this.createIfNotExists();
+ this.getValues().putAll((Map)GSON.fromJson(this.getReader(), this.getValues().getClass()));
+ }
+
+ public void save() throws IOException {
+ String json = GSON.toJson(this.getValues());
+ Files.write(this.getPath(), Collections.singletonList(json), StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.WRITE);
+ }
+}
diff --git a/src/main/java/me/lokka30/microlib/files/YamlConfigFile.java b/src/main/java/me/lokka30/microlib/files/YamlConfigFile.java
new file mode 100644
index 0000000..cb5b80e
--- /dev/null
+++ b/src/main/java/me/lokka30/microlib/files/YamlConfigFile.java
@@ -0,0 +1,61 @@
+package me.lokka30.microlib.files;
+
+import java.io.File;
+import java.io.IOException;
+import org.bukkit.configuration.file.YamlConfiguration;
+import org.bukkit.plugin.Plugin;
+
+public class YamlConfigFile {
+ private final Plugin plugin;
+ private final File configFile;
+ private YamlConfiguration config;
+ private boolean copyDefaults;
+
+ public YamlConfigFile(Plugin plugin, File configFile) {
+ this.copyDefaults = false;
+ this.plugin = plugin;
+ this.configFile = configFile;
+ }
+
+ public YamlConfigFile(Plugin plugin, String configName) {
+ this(plugin, new File(plugin.getDataFolder(), configName));
+ }
+
+ private void createIfNotExists() throws IOException {
+ if (!this.getConfigFile().exists() || this.getConfigFile().isDirectory()) {
+ try {
+ this.plugin.saveResource(this.getName(), false);
+ } catch (IllegalArgumentException var2) {
+ if(!this.getConfigFile().createNewFile()) throw new IllegalArgumentException("Unable to create new YAML file!", var2);
+ }
+ }
+
+ }
+
+ public void load() throws IOException {
+ this.createIfNotExists();
+ this.config = YamlConfiguration.loadConfiguration(this.getConfigFile());
+ this.getConfig().options().copyDefaults(this.copyDefaults);
+ }
+
+ public void save() throws IOException {
+ this.createIfNotExists();
+ this.getConfig().save(this.getConfigFile());
+ }
+
+ public void setCopyDefaults(boolean copyDefaults) {
+ this.copyDefaults = copyDefaults;
+ }
+
+ public File getConfigFile() {
+ return this.configFile;
+ }
+
+ public YamlConfiguration getConfig() {
+ return this.config;
+ }
+
+ public String getName() {
+ return this.configFile.getName();
+ }
+}
diff --git a/src/main/java/me/lokka30/microlib/folia/FoliaRunnable.java b/src/main/java/me/lokka30/microlib/folia/FoliaRunnable.java
new file mode 100644
index 0000000..797f291
--- /dev/null
+++ b/src/main/java/me/lokka30/microlib/folia/FoliaRunnable.java
@@ -0,0 +1,52 @@
+package me.lokka30.microlib.folia;
+
+import io.papermc.paper.threadedregions.scheduler.ScheduledTask;
+import org.bukkit.scheduler.BukkitRunnable;
+import org.bukkit.scheduler.BukkitTask;
+
+/**
+ * A class for running tasks on both Bukkit and Folia. Without this class, you would need to check if the server is using Folia or not, and then run the task accordingly.
+ */
+public class FoliaRunnable extends BukkitRunnable {
+
+ private ScheduledTask foliaTask;
+ private BukkitTask bukkitTask;
+
+ /**
+ * Cancels the task if it is running.
+ */
+ @Override
+ public synchronized void cancel() throws IllegalStateException {
+ if (this.foliaTask != null) {
+ this.foliaTask.cancel();
+ this.foliaTask = null;
+ } else if (this.bukkitTask != null) {
+ // Legacy Bukkit task
+ this.bukkitTask.cancel();
+ this.bukkitTask = null;
+ }
+ }
+
+ /**
+ * This method is not used. Instead, use the run() method in the subclass.
+ */
+ @Override
+ public void run() {
+ }
+
+ /**
+ * Sets the scheduled task for Folia.
+ * @param task The {@link ScheduledTask}.
+ */
+ public void setScheduledTask(ScheduledTask task) {
+ this.foliaTask = task;
+ }
+
+ /**
+ * Sets the Bukkit task for legacy Bukkit.
+ * @param task The {@link BukkitTask}.
+ */
+ public void setBukkitTask(BukkitTask task) {
+ this.bukkitTask = task;
+ }
+}
diff --git a/src/main/java/me/lokka30/microlib/folia/SchedulerUtils.java b/src/main/java/me/lokka30/microlib/folia/SchedulerUtils.java
new file mode 100644
index 0000000..0a629e2
--- /dev/null
+++ b/src/main/java/me/lokka30/microlib/folia/SchedulerUtils.java
@@ -0,0 +1,268 @@
+package me.lokka30.microlib.folia;
+
+import io.papermc.paper.threadedregions.scheduler.GlobalRegionScheduler;
+import io.papermc.paper.threadedregions.scheduler.RegionScheduler;
+import io.papermc.paper.threadedregions.scheduler.ScheduledTask;
+import me.lokka30.commanddefender.CommandDefender;
+import org.bukkit.Location;
+import org.bukkit.plugin.java.JavaPlugin;
+import org.bukkit.scheduler.BukkitTask;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.lang.reflect.Method;
+import java.util.concurrent.Callable;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.Future;
+
+import static me.lokka30.microlib.other.VersionUtils.isRunningFolia;
+
+/**
+ * Utility class for scheduling tasks in a Paper/Folia server.
+ */
+public abstract class SchedulerUtils {
+
+ /**
+ * 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 static void runTaskLater(@Nullable Location loc, @NotNull Runnable task, long delay) {
+ JavaPlugin plugin = CommandDefender.getPlugin(CommandDefender.class);
+ if (isRunningFolia()) {
+ 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.
+ */
+ public static void runTaskTimer(@Nullable Location loc, @NotNull FoliaRunnable runnable, long delay, long period) {
+ JavaPlugin plugin = CommandDefender.getPlugin(CommandDefender.class);
+ if (isRunningFolia()) {
+ 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;
+ }
+ BukkitTask task = runnable.runTaskTimer(plugin, delay, period);
+ runnable.setBukkitTask(task);
+ }
+
+ /**
+ * Schedules a task to run later asynchronously 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 static void runTaskLaterAsynchronously(@Nullable Location loc, @NotNull Runnable task, long delay) {
+ JavaPlugin plugin = CommandDefender.getPlugin(CommandDefender.class);
+ if (isRunningFolia()) {
+ 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
+ );
+ }
+ return;
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+ plugin.getServer().getScheduler().runTaskLaterAsynchronously(plugin, task, delay);
+ }
+
+ /**
+ * 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.
+ */
+ public static void runTaskTimerAsynchronously(@NotNull FoliaRunnable runnable, long delay, long period) {
+ JavaPlugin plugin = CommandDefender.getPlugin(CommandDefender.class);
+ if (isRunningFolia()) {
+ try {
+ Method getGlobalScheduler = plugin.getServer().getClass().getMethod("getGlobalRegionScheduler");
+ GlobalRegionScheduler globalScheduler = (GlobalRegionScheduler) getGlobalScheduler.invoke(plugin.getServer());
+ class AsyncRepeatingTask {
+ void start(long initialDelay) {
+ ScheduledTask task = globalScheduler.runDelayed(plugin, (ScheduledTask t) -> {
+ runnable.run();
+ start(period);
+ }, initialDelay);
+ runnable.setScheduledTask(task);
+ }
+ }
+ new AsyncRepeatingTask().start(delay);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ return;
+ }
+ BukkitTask task = runnable.runTaskTimerAsynchronously(plugin, delay, period);
+ runnable.setBukkitTask(task);
+ }
+
+ /**
+ * Runs a task asynchronously on the main server thread.
+ * @param task The task to run.
+ */
+ public static void runTaskAsynchronously(@NotNull Runnable task) {
+ JavaPlugin plugin = CommandDefender.getPlugin(CommandDefender.class);
+ if (isRunningFolia()) {
+ 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 static void runTask(@Nullable Location loc, @NotNull Runnable task) {
+ JavaPlugin plugin = CommandDefender.getPlugin(CommandDefender.class);
+ if (isRunningFolia()) {
+ 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);
+ }
+
+ /**
+ * Calls a method synchronously 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.
+ * @return A CompletableFuture that will hold the result of the task.
+ */
+ public static CompletableFuture callSyncMethod(@Nullable Location loc, @NotNull Callable task) {
+ JavaPlugin plugin = CommandDefender.getPlugin(CommandDefender.class);
+ if (isRunningFolia()) {
+ CompletableFuture future = new CompletableFuture<>();
+ try {
+ if (loc != null) {
+ Method getRegionScheduler = plugin.getServer().getClass().getMethod("getRegionScheduler");
+ RegionScheduler regionScheduler = (RegionScheduler) getRegionScheduler.invoke(plugin.getServer());
+ regionScheduler.execute(plugin, loc, () -> {
+ try {
+ future.complete(task.call());
+ } catch (Exception e) {
+ future.completeExceptionally(e);
+ }
+ });
+ } else {
+ Method getGlobalScheduler = plugin.getServer().getClass().getMethod("getGlobalRegionScheduler");
+ GlobalRegionScheduler globalScheduler = (GlobalRegionScheduler) getGlobalScheduler.invoke(plugin.getServer());
+ globalScheduler.execute(plugin, () -> {
+ try {
+ future.complete(task.call());
+ } catch (Exception e) {
+ future.completeExceptionally(e);
+ }
+ });
+ }
+ } catch (Exception e) {
+ future.completeExceptionally(e);
+ }
+ return future;
+ }
+ CompletableFuture cf = new CompletableFuture<>();
+ try {
+ Future bukkitFuture = plugin.getServer().getScheduler().callSyncMethod(plugin, task);
+ plugin.getServer().getScheduler().runTaskAsynchronously(plugin, () -> {
+ try {
+ T result = bukkitFuture.get();
+ cf.complete(result);
+ } catch (Throwable ex) {
+ cf.completeExceptionally(ex);
+ }
+ });
+ } catch (Throwable e) {
+ cf.completeExceptionally(e);
+ }
+ return cf;
+ }
+}
diff --git a/src/main/java/me/lokka30/microlib/messaging/MessageUtils.java b/src/main/java/me/lokka30/microlib/messaging/MessageUtils.java
new file mode 100644
index 0000000..e7d5feb
--- /dev/null
+++ b/src/main/java/me/lokka30/microlib/messaging/MessageUtils.java
@@ -0,0 +1,41 @@
+package me.lokka30.microlib.messaging;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import me.lokka30.microlib.other.VersionUtils;
+import org.bukkit.Bukkit;
+import org.bukkit.ChatColor;
+import org.jetbrains.annotations.NotNull;
+
+public class MessageUtils {
+ @NotNull
+ public static String colorizeAll(String msg) {
+ return colorizeStandardCodes(colorizeHexCodes(msg));
+ }
+
+ public static String colorizeHexCodes(String msg) {
+ return colorizeHexCodes("", "", msg);
+ }
+
+ public static String colorizeHexCodes(String startTag, String endTag, String message) {
+ if (VersionUtils.isOneSixteen() && VersionUtils.isRunningSpigot()) {
+ Pattern hexPattern = Pattern.compile(startTag + "([A-Fa-f0-9]{6})" + endTag);
+ Matcher matcher = hexPattern.matcher(message);
+ StringBuffer buffer = new StringBuffer(message.length() + 32);
+
+ while(matcher.find()) {
+ String group = matcher.group(1);
+ matcher.appendReplacement(buffer, "§x§" + group.charAt(0) + '§' + group.charAt(1) + '§' + group.charAt(2) + '§' + group.charAt(3) + '§' + group.charAt(4) + '§' + group.charAt(5));
+ }
+
+ return matcher.appendTail(buffer).toString();
+ } else {
+ return message;
+ }
+ }
+
+ @NotNull
+ public static String colorizeStandardCodes(String msg) {
+ return Bukkit.getName().equalsIgnoreCase("CraftBukkit") ? ChatColor.translateAlternateColorCodes('&', msg) : net.md_5.bungee.api.ChatColor.translateAlternateColorCodes('&', msg);
+ }
+}
diff --git a/src/main/java/me/lokka30/microlib/messaging/MicroLogger.java b/src/main/java/me/lokka30/microlib/messaging/MicroLogger.java
new file mode 100644
index 0000000..56798b4
--- /dev/null
+++ b/src/main/java/me/lokka30/microlib/messaging/MicroLogger.java
@@ -0,0 +1,53 @@
+package me.lokka30.microlib.messaging;
+
+import java.util.logging.Logger;
+import me.lokka30.microlib.other.VersionUtils;
+import org.bukkit.Bukkit;
+import org.bukkit.ChatColor;
+
+public class MicroLogger {
+ final boolean serverIsSpigot;
+ private final Logger logger;
+ private String prefix;
+
+ public MicroLogger(String prefix) {
+ this.prefix = prefix;
+ this.logger = Bukkit.getLogger();
+ this.serverIsSpigot = VersionUtils.isRunningSpigot();
+ }
+
+ public String getPrefix() {
+ return this.prefix;
+ }
+
+ public void setPrefix(String prefix) {
+ this.prefix = prefix;
+ }
+
+ public void info(String message) {
+ if (this.serverIsSpigot) {
+ Bukkit.getServer().getConsoleSender().sendMessage(MessageUtils.colorizeAll(this.prefix + message));
+ } else {
+ this.logger.info(MessageUtils.colorizeAll(this.prefix + message));
+ }
+
+ }
+
+ public void warning(String message) {
+ if (this.serverIsSpigot) {
+ Bukkit.getServer().getConsoleSender().sendMessage(MessageUtils.colorizeAll(ChatColor.YELLOW + "[WARN] " + ChatColor.RESET + this.prefix + message));
+ } else {
+ this.logger.warning(MessageUtils.colorizeAll(this.prefix + message));
+ }
+
+ }
+
+ public void error(String message) {
+ if (this.serverIsSpigot) {
+ Bukkit.getServer().getConsoleSender().sendMessage(MessageUtils.colorizeAll(ChatColor.RED + "[ERROR] " + ChatColor.RESET + this.prefix + message));
+ } else {
+ this.logger.severe(MessageUtils.colorizeAll(this.prefix + message));
+ }
+
+ }
+}
diff --git a/src/main/java/me/lokka30/microlib/messaging/MultiMessage.java b/src/main/java/me/lokka30/microlib/messaging/MultiMessage.java
new file mode 100644
index 0000000..113a298
--- /dev/null
+++ b/src/main/java/me/lokka30/microlib/messaging/MultiMessage.java
@@ -0,0 +1,77 @@
+package me.lokka30.microlib.messaging;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import org.bukkit.command.CommandSender;
+import org.bukkit.entity.Player;
+import org.jetbrains.annotations.NotNull;
+
+public class MultiMessage {
+ public List content;
+ public List placeholders;
+
+ public MultiMessage(List content, List placeholders) {
+ this.content = content;
+ this.placeholders = placeholders;
+ }
+
+ public List getTranslatedContent() {
+ ArrayList translated = new ArrayList();
+ Iterator var2 = this.content.iterator();
+
+ while(var2.hasNext()) {
+ String line = (String)var2.next();
+ String translatedLine = MessageUtils.colorizeAll(line);
+
+ Placeholder placeholder;
+ for(Iterator var5 = this.placeholders.iterator(); var5.hasNext(); translatedLine = placeholder.translateInMessage(translatedLine)) {
+ placeholder = (Placeholder)var5.next();
+ }
+
+ translated.add(translatedLine);
+ }
+
+ return translated;
+ }
+
+ public List getUntranslatedContent() {
+ return this.content;
+ }
+
+ public void setContent(List content) {
+ this.content = content;
+ }
+
+ public List getPlaceholders() {
+ return this.placeholders;
+ }
+
+ public void setPlaceholders(List placeholders) {
+ this.placeholders = placeholders;
+ }
+
+ public void send(@NotNull CommandSender sender) {
+ this.getTranslatedContent().forEach(sender::sendMessage);
+ }
+
+ public void send(@NotNull Player player) {
+ this.getTranslatedContent().forEach(player::sendMessage);
+ }
+
+ public static class Placeholder {
+ public final String id;
+ public final String value;
+ public final boolean colorizeValue;
+
+ public Placeholder(String id, String value, boolean colorizeValue) {
+ this.id = id;
+ this.value = value;
+ this.colorizeValue = colorizeValue;
+ }
+
+ public String translateInMessage(String msg) {
+ return this.colorizeValue ? msg.replace("%" + this.id + "%", MessageUtils.colorizeAll(this.value)) : msg.replace("%" + this.id + "%", this.value);
+ }
+ }
+}
diff --git a/src/main/java/me/lokka30/microlib/other/UpdateChecker.java b/src/main/java/me/lokka30/microlib/other/UpdateChecker.java
new file mode 100644
index 0000000..5de8edc
--- /dev/null
+++ b/src/main/java/me/lokka30/microlib/other/UpdateChecker.java
@@ -0,0 +1,42 @@
+package me.lokka30.microlib.other;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.util.Scanner;
+import java.util.function.Consumer;
+
+import me.lokka30.microlib.folia.SchedulerUtils;
+import org.bukkit.plugin.java.JavaPlugin;
+
+public class UpdateChecker {
+ private final JavaPlugin plugin;
+ private final int resourceId;
+
+ public UpdateChecker(JavaPlugin plugin, int resourceId) {
+ this.plugin = plugin;
+ this.resourceId = resourceId;
+ }
+
+ public void getLatestVersion(Consumer consumer) {
+ SchedulerUtils.runTaskAsynchronously(() -> {
+ InputStream inputStream;
+ try {
+ inputStream = (new URL("https://api.spigotmc.org/legacy/update.php?resource=" + this.resourceId)).openStream();
+ } catch (IOException var4) {
+ var4.printStackTrace();
+ return;
+ }
+
+ Scanner scanner = new Scanner(inputStream);
+ if (scanner.hasNext()) {
+ consumer.accept(scanner.next());
+ }
+
+ });
+ }
+
+ public String getCurrentVersion() {
+ return this.plugin.getDescription().getVersion();
+ }
+}
diff --git a/src/main/java/me/lokka30/microlib/other/VersionUtils.java b/src/main/java/me/lokka30/microlib/other/VersionUtils.java
new file mode 100644
index 0000000..61d5d24
--- /dev/null
+++ b/src/main/java/me/lokka30/microlib/other/VersionUtils.java
@@ -0,0 +1,172 @@
+package me.lokka30.microlib.other;
+
+import org.bukkit.Bukkit;
+import org.bukkit.Material;
+import org.bukkit.block.Biome;
+import org.bukkit.entity.EntityType;
+
+public class VersionUtils {
+ public static MajorMinecraftVersion getMajorMinecraftVersion() {
+ if (isOneNineteen()) {
+ return MajorMinecraftVersion.ONE_NINETEEN;
+ } else if (isOneEighteen()) {
+ return MajorMinecraftVersion.ONE_EIGHTEEN;
+ } else if (isOneSeventeen()) {
+ return MajorMinecraftVersion.ONE_SEVENTEEN;
+ } else if (isOneSixteen()) {
+ return MajorMinecraftVersion.ONE_SIXTEEN;
+ } else if (isOneFifteen()) {
+ return MajorMinecraftVersion.ONE_FIFTEEN;
+ } else if (isOneFourteen()) {
+ return MajorMinecraftVersion.ONE_FOURTEEN;
+ } else if (isOneThirteen()) {
+ return MajorMinecraftVersion.ONE_THIRTEEN;
+ } else if (isOneTwelve()) {
+ return MajorMinecraftVersion.ONE_TWELVE;
+ } else if (isOneEleven()) {
+ return MajorMinecraftVersion.ONE_ELEVEN;
+ } else if (isOneTen()) {
+ return MajorMinecraftVersion.ONE_TEN;
+ } else if (isOneNine()) {
+ return MajorMinecraftVersion.ONE_NINE;
+ } else if (isOneEight()) {
+ return MajorMinecraftVersion.ONE_EIGHT;
+ } else if (isOneSeven()) {
+ return MajorMinecraftVersion.ONE_SEVEN;
+ } else {
+ return isOneSix() ? MajorMinecraftVersion.ONE_SIX : MajorMinecraftVersion.UNKNOWN;
+ }
+ }
+
+ public static boolean isOneNineteen() {
+ return hasEntityType("WARDEN");
+ }
+
+ public static boolean isOneEighteen() {
+ return hasBiome("GROVE");
+ }
+
+ public static boolean isOneSeventeen() {
+ return hasEntityType("AXOLOTL");
+ }
+
+ public static boolean isOneSixteen() {
+ return hasEntityType("PIGLIN");
+ }
+
+ public static boolean isOneFifteen() {
+ return hasEntityType("BEE");
+ }
+
+ public static boolean isOneFourteen() {
+ return hasEntityType("PILLAGER");
+ }
+
+ public static boolean isOneThirteen() {
+ return hasEntityType("TURTLE");
+ }
+
+ public static boolean isOneTwelve() {
+ return hasMaterial("WHITE_CONCRETE");
+ }
+
+ public static boolean isOneEleven() {
+ return hasMaterial("OBSERVER");
+ }
+
+ public static boolean isOneTen() {
+ return hasMaterial("MAGMA_BLOCK");
+ }
+
+ public static boolean isOneNine() {
+ return hasMaterial("END_ROD");
+ }
+
+ public static boolean isOneEight() {
+ return hasMaterial("PRISMARINE");
+ }
+
+ public static boolean isOneSeven() {
+ return hasMaterial("WHITE_STAINED_GLASS");
+ }
+
+ public static boolean isOneSix() {
+ return hasEntityType("HORSE");
+ }
+
+ public static boolean isRunningSpigot() {
+ try {
+ Class.forName("net.md_5.bungee.api.ChatColor");
+ return true;
+ } catch (ClassNotFoundException var1) {
+ return false;
+ }
+ }
+
+ public static boolean isRunningPaper() {
+ try {
+ Class.forName("com.destroystokyo.paper.ParticleBuilder");
+ return true;
+ } catch (ClassNotFoundException var1) {
+ return false;
+ }
+ }
+
+ public static boolean isRunningFolia() {
+ try {
+ Class.forName("io.papermc.paper.threadedregions.scheduler.RegionScheduler");
+ return true;
+ } catch (ClassNotFoundException var1) {
+ return false;
+ }
+ }
+
+ public static boolean isSpecific(String version) {
+ return Bukkit.getServer().getVersion().contains(version);
+ }
+
+ public static boolean hasEntityType(String entityTypeStr) {
+ try {
+ EntityType.valueOf(entityTypeStr);
+ return true;
+ } catch (IllegalArgumentException var2) {
+ return false;
+ }
+ }
+
+ public static boolean hasMaterial(String materialStr) {
+ try {
+ Material.valueOf(materialStr);
+ return true;
+ } catch (IllegalArgumentException var2) {
+ return false;
+ }
+ }
+
+ public static boolean hasBiome(String biome) {
+ try {
+ Biome.valueOf(biome);
+ return true;
+ } catch (IllegalArgumentException var2) {
+ return false;
+ }
+ }
+
+ public static enum MajorMinecraftVersion {
+ ONE_NINETEEN,
+ ONE_EIGHTEEN,
+ ONE_SEVENTEEN,
+ ONE_SIXTEEN,
+ ONE_FIFTEEN,
+ ONE_FOURTEEN,
+ ONE_THIRTEEN,
+ ONE_TWELVE,
+ ONE_ELEVEN,
+ ONE_TEN,
+ ONE_NINE,
+ ONE_EIGHT,
+ ONE_SEVEN,
+ ONE_SIX,
+ UNKNOWN;
+ }
+}
diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml
index 1a59edc..dda38f8 100644
--- a/src/main/resources/plugin.yml
+++ b/src/main/resources/plugin.yml
@@ -2,6 +2,7 @@ name: 'CommandDefender'
version: ${project.version}
authors: [ 'lokka30' ]
description: ${project.description}
+folia-supported: true
main: 'me.lokka30.commanddefender.CommandDefender'
api-version: '1.13'