572 lines
20 KiB
Diff
572 lines
20 KiB
Diff
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
|
From: MrHua269 <wangxyper@163.com>
|
|
Date: Sun, 12 Jan 2025 10:15:53 +0800
|
|
Subject: [PATCH] Add luminol config framework
|
|
|
|
|
|
diff --git a/src/main/java/me/earthme/luminol/commands/LuminolConfigCommand.java b/src/main/java/me/earthme/luminol/commands/LuminolConfigCommand.java
|
|
new file mode 100644
|
|
index 0000000000000000000000000000000000000000..5c8745dffa80cf47e856d04d283937bda86881f8
|
|
--- /dev/null
|
|
+++ b/src/main/java/me/earthme/luminol/commands/LuminolConfigCommand.java
|
|
@@ -0,0 +1,70 @@
|
|
+package me.earthme.luminol.commands;
|
|
+
|
|
+import me.earthme.luminol.config.LuminolConfig;
|
|
+import net.kyori.adventure.text.Component;
|
|
+import net.kyori.adventure.text.format.TextColor;
|
|
+import org.bukkit.Location;
|
|
+import org.bukkit.command.Command;
|
|
+import org.bukkit.command.CommandSender;
|
|
+import org.jetbrains.annotations.NotNull;
|
|
+import org.jetbrains.annotations.Nullable;
|
|
+
|
|
+import java.util.ArrayList;
|
|
+import java.util.List;
|
|
+
|
|
+public class LuminolConfigCommand extends Command {
|
|
+ public LuminolConfigCommand(){
|
|
+ super("luminolconfig");
|
|
+ this.setPermission("luminol.commands.luminolconfig");
|
|
+ this.setDescription("Manage config file");
|
|
+ this.setUsage("/luminolconfig");
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public @NotNull List<String> tabComplete(@NotNull CommandSender sender, @NotNull String alias, @NotNull String[] args, @Nullable Location location) throws IllegalArgumentException {
|
|
+ final List<String> result = new ArrayList<>();
|
|
+
|
|
+ if (args.length == 1){
|
|
+ result.add("reload");
|
|
+ }
|
|
+
|
|
+ return result;
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public boolean execute(@NotNull CommandSender sender, @NotNull String commandLabel, @NotNull String[] args) {
|
|
+ if (!this.testPermission(sender)){
|
|
+ sender.sendMessage(Component
|
|
+ .text("No permission to execute this command!")
|
|
+ .color(TextColor.color(255,0,0))
|
|
+ );
|
|
+ }
|
|
+
|
|
+ if (args.length < 1){
|
|
+ sender.sendMessage(
|
|
+ Component
|
|
+ .text("Wrong use!\n")
|
|
+ .color(TextColor.color(255,0,0))
|
|
+ );
|
|
+ return true;
|
|
+ }
|
|
+
|
|
+ switch (args[0]){
|
|
+ case "reload" -> {
|
|
+ LuminolConfig.reloadAsync().thenAccept(nullValue -> sender.sendMessage(
|
|
+ Component
|
|
+ .text("Reloaded config file!")
|
|
+ .color(TextColor.color(0,255,0))
|
|
+ ));
|
|
+ }
|
|
+
|
|
+ default -> sender.sendMessage(
|
|
+ Component
|
|
+ .text("Unknown action!\n")
|
|
+ .color(TextColor.color(255,0,0))
|
|
+ );
|
|
+ }
|
|
+
|
|
+ return true;
|
|
+ }
|
|
+}
|
|
diff --git a/src/main/java/me/earthme/luminol/config/ConfigInfo.java b/src/main/java/me/earthme/luminol/config/ConfigInfo.java
|
|
new file mode 100644
|
|
index 0000000000000000000000000000000000000000..01b64c2cf6b437114337626c242e1da3fbdb8ead
|
|
--- /dev/null
|
|
+++ b/src/main/java/me/earthme/luminol/config/ConfigInfo.java
|
|
@@ -0,0 +1,11 @@
|
|
+package me.earthme.luminol.config;
|
|
+
|
|
+import java.lang.annotation.Retention;
|
|
+import java.lang.annotation.RetentionPolicy;
|
|
+
|
|
+@Retention(RetentionPolicy.RUNTIME)
|
|
+public @interface ConfigInfo {
|
|
+ String baseName();
|
|
+
|
|
+ String comments() default "";
|
|
+}
|
|
diff --git a/src/main/java/me/earthme/luminol/config/DoNotLoad.java b/src/main/java/me/earthme/luminol/config/DoNotLoad.java
|
|
new file mode 100644
|
|
index 0000000000000000000000000000000000000000..fffc5eb4be4b78a886f3c340bd60f3a2b0108a7d
|
|
--- /dev/null
|
|
+++ b/src/main/java/me/earthme/luminol/config/DoNotLoad.java
|
|
@@ -0,0 +1,8 @@
|
|
+package me.earthme.luminol.config;
|
|
+
|
|
+import java.lang.annotation.Retention;
|
|
+import java.lang.annotation.RetentionPolicy;
|
|
+
|
|
+@Retention(RetentionPolicy.RUNTIME)
|
|
+public @interface DoNotLoad {
|
|
+}
|
|
diff --git a/src/main/java/me/earthme/luminol/config/EnumConfigCategory.java b/src/main/java/me/earthme/luminol/config/EnumConfigCategory.java
|
|
new file mode 100644
|
|
index 0000000000000000000000000000000000000000..7b75405c468d24ed8aea5aa54ae5ac339118bf60
|
|
--- /dev/null
|
|
+++ b/src/main/java/me/earthme/luminol/config/EnumConfigCategory.java
|
|
@@ -0,0 +1,19 @@
|
|
+package me.earthme.luminol.config;
|
|
+
|
|
+public enum EnumConfigCategory {
|
|
+ OPTIMIZATIONS("optimizations"),
|
|
+ FIXES("fixes"),
|
|
+ MISC("misc"),
|
|
+ GAMEPLAY("gameplay"),
|
|
+ EXPERIMENT("experiment");
|
|
+
|
|
+ private final String baseKeyName;
|
|
+
|
|
+ EnumConfigCategory(String baseKeyName) {
|
|
+ this.baseKeyName = baseKeyName;
|
|
+ }
|
|
+
|
|
+ public String getBaseKeyName() {
|
|
+ return this.baseKeyName;
|
|
+ }
|
|
+}
|
|
diff --git a/src/main/java/me/earthme/luminol/config/HotReloadUnsupported.java b/src/main/java/me/earthme/luminol/config/HotReloadUnsupported.java
|
|
new file mode 100644
|
|
index 0000000000000000000000000000000000000000..559c11cb523c7cade34a0abfad15c988f5ad87fe
|
|
--- /dev/null
|
|
+++ b/src/main/java/me/earthme/luminol/config/HotReloadUnsupported.java
|
|
@@ -0,0 +1,8 @@
|
|
+package me.earthme.luminol.config;
|
|
+
|
|
+import java.lang.annotation.Retention;
|
|
+import java.lang.annotation.RetentionPolicy;
|
|
+
|
|
+@Retention(RetentionPolicy.RUNTIME)
|
|
+public @interface HotReloadUnsupported {
|
|
+}
|
|
diff --git a/src/main/java/me/earthme/luminol/config/IConfigModule.java b/src/main/java/me/earthme/luminol/config/IConfigModule.java
|
|
new file mode 100644
|
|
index 0000000000000000000000000000000000000000..9f6896711907ac30fe0c00130207b970007e4bb4
|
|
--- /dev/null
|
|
+++ b/src/main/java/me/earthme/luminol/config/IConfigModule.java
|
|
@@ -0,0 +1,22 @@
|
|
+package me.earthme.luminol.config;
|
|
+
|
|
+import com.electronwill.nightconfig.core.file.CommentedFileConfig;
|
|
+import org.jetbrains.annotations.NotNull;
|
|
+
|
|
+public interface IConfigModule {
|
|
+
|
|
+ EnumConfigCategory getCategory();
|
|
+
|
|
+ String getBaseName();
|
|
+
|
|
+ default void onLoaded(CommentedFileConfig configInstance) {}
|
|
+
|
|
+ default <T> T get(String keyName, T defaultValue, @NotNull CommentedFileConfig config){
|
|
+ if (!config.contains(keyName)){
|
|
+ config.set(keyName,defaultValue);
|
|
+ return defaultValue;
|
|
+ }
|
|
+
|
|
+ return config.get(keyName);
|
|
+ }
|
|
+}
|
|
diff --git a/src/main/java/me/earthme/luminol/config/LuminolConfig.java b/src/main/java/me/earthme/luminol/config/LuminolConfig.java
|
|
new file mode 100644
|
|
index 0000000000000000000000000000000000000000..526b68e184a2f6f9e38cd02995b473a943404141
|
|
--- /dev/null
|
|
+++ b/src/main/java/me/earthme/luminol/config/LuminolConfig.java
|
|
@@ -0,0 +1,226 @@
|
|
+package me.earthme.luminol.config;
|
|
+
|
|
+import com.electronwill.nightconfig.core.file.CommentedFileConfig;
|
|
+import io.papermc.paper.threadedregions.RegionizedServer;
|
|
+import me.earthme.luminol.commands.LuminolConfigCommand;
|
|
+import org.bukkit.Bukkit;
|
|
+import org.jetbrains.annotations.Contract;
|
|
+import org.jetbrains.annotations.NotNull;
|
|
+import java.io.File;
|
|
+import java.io.IOException;
|
|
+import java.lang.reflect.Field;
|
|
+import java.lang.reflect.InvocationTargetException;
|
|
+import java.lang.reflect.Modifier;
|
|
+import java.net.JarURLConnection;
|
|
+import java.net.URL;
|
|
+import java.net.URLDecoder;
|
|
+import java.nio.charset.StandardCharsets;
|
|
+import java.util.*;
|
|
+import java.util.concurrent.CompletableFuture;
|
|
+import java.util.jar.JarEntry;
|
|
+import java.util.jar.JarFile;
|
|
+import org.apache.logging.log4j.LogManager;
|
|
+import org.apache.logging.log4j.Logger;
|
|
+
|
|
+public class LuminolConfig {
|
|
+ public static final Logger logger = LogManager.getLogger();
|
|
+ private static final File baseConfigFolder = new File("luminol_config");
|
|
+ private static final File baseConfigFile = new File(baseConfigFolder,"luminol_global_config.toml");
|
|
+ private static final Set<IConfigModule> allInstanced = new HashSet<>();
|
|
+ private static CommentedFileConfig configFileInstance;
|
|
+ public static boolean alreadyInited = false;
|
|
+
|
|
+ public static void setupLatch(){
|
|
+ Bukkit.getCommandMap().register("luminolconfig","luminol",new LuminolConfigCommand());
|
|
+ alreadyInited = true;
|
|
+ }
|
|
+
|
|
+ public static void reload(){
|
|
+ RegionizedServer.ensureGlobalTickThread("Reload luminol config off global region thread!");
|
|
+
|
|
+ dropAllInstanced();
|
|
+ try {
|
|
+ preLoadConfig();
|
|
+ finalizeLoadConfig();
|
|
+ }catch (Exception e){
|
|
+ logger.error(e);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ @Contract(" -> new")
|
|
+ public static @NotNull CompletableFuture<Void> reloadAsync(){
|
|
+ return CompletableFuture.runAsync(LuminolConfig::reload,task -> RegionizedServer.getInstance().addTask(() -> {
|
|
+ try{
|
|
+ task.run();
|
|
+ }catch (Exception e){
|
|
+ logger.error(e);
|
|
+ }
|
|
+ }));
|
|
+ }
|
|
+
|
|
+ public static void dropAllInstanced(){
|
|
+ allInstanced.clear();
|
|
+ }
|
|
+
|
|
+ public static void finalizeLoadConfig() {
|
|
+ for (IConfigModule module : allInstanced) {
|
|
+ module.onLoaded(configFileInstance);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ public static void preLoadConfig() throws IOException {
|
|
+ baseConfigFolder.mkdirs();
|
|
+
|
|
+ if (!baseConfigFile.exists()){
|
|
+ baseConfigFile.createNewFile();
|
|
+ }
|
|
+
|
|
+ configFileInstance = CommentedFileConfig.ofConcurrent(baseConfigFile);
|
|
+
|
|
+ configFileInstance.load();
|
|
+
|
|
+ try {
|
|
+ instanceAllModule();
|
|
+ loadAllModules();
|
|
+ }catch (Exception e){
|
|
+ logger.error("Failed to load config modules!",e);
|
|
+ throw new RuntimeException(e);
|
|
+ }
|
|
+
|
|
+ configFileInstance.save();
|
|
+ }
|
|
+
|
|
+ private static void loadAllModules() throws IllegalAccessException {
|
|
+ for (IConfigModule instanced : allInstanced){
|
|
+ loadForSingle(instanced);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ private static void instanceAllModule() throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
|
|
+ for (Class<?> clazz : getClasses("me.earthme.luminol.config.modules")){
|
|
+ if (IConfigModule.class.isAssignableFrom(clazz)){
|
|
+ allInstanced.add((IConfigModule) clazz.getConstructor().newInstance());
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+ private static void loadForSingle(@NotNull IConfigModule singleConfigModule) throws IllegalAccessException {
|
|
+ final EnumConfigCategory category = singleConfigModule.getCategory();
|
|
+
|
|
+ Field[] fields = singleConfigModule.getClass().getDeclaredFields();
|
|
+
|
|
+ for (Field field : fields) {
|
|
+ int modifiers = field.getModifiers();
|
|
+ if (Modifier.isStatic(modifiers) && !Modifier.isFinal(modifiers)) {
|
|
+ boolean skipLoad = field.getAnnotation(DoNotLoad.class) != null || (alreadyInited && field.getAnnotation(HotReloadUnsupported.class) != null);
|
|
+ ConfigInfo configInfo = field.getAnnotation(ConfigInfo.class);
|
|
+
|
|
+ if (skipLoad || configInfo == null){
|
|
+ continue;
|
|
+ }
|
|
+
|
|
+ final String fullConfigKeyName = category.getBaseKeyName() + "." + singleConfigModule.getBaseName() + "." + configInfo.baseName();
|
|
+
|
|
+ field.setAccessible(true);
|
|
+ final Object currentValue = field.get(null);
|
|
+
|
|
+ if (!configFileInstance.contains(fullConfigKeyName)){
|
|
+ if (currentValue == null){
|
|
+ throw new UnsupportedOperationException("Config " + singleConfigModule.getBaseName() + "tried to add an null default value!");
|
|
+ }
|
|
+
|
|
+ final String comments = configInfo.comments();
|
|
+
|
|
+ if (!comments.isBlank()){
|
|
+ configFileInstance.setComment(fullConfigKeyName,comments);
|
|
+ }
|
|
+
|
|
+ configFileInstance.add(fullConfigKeyName,currentValue);
|
|
+ continue;
|
|
+ }
|
|
+
|
|
+ final Object actuallyValue = configFileInstance.get(fullConfigKeyName);
|
|
+ field.set(null,actuallyValue);
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+ public static @NotNull Set<Class<?>> getClasses(String pack) {
|
|
+ Set<Class<?>> classes = new LinkedHashSet<>();
|
|
+ String packageDirName = pack.replace('.', '/');
|
|
+ Enumeration<URL> dirs;
|
|
+
|
|
+ try {
|
|
+ dirs = Thread.currentThread().getContextClassLoader().getResources(packageDirName);
|
|
+ while (dirs.hasMoreElements()) {
|
|
+ URL url = dirs.nextElement();
|
|
+ String protocol = url.getProtocol();
|
|
+ if ("file".equals(protocol)) {
|
|
+ String filePath = URLDecoder.decode(url.getFile(), StandardCharsets.UTF_8);
|
|
+ findClassesInPackageByFile(pack, filePath, classes);
|
|
+ } else if ("jar".equals(protocol)) {
|
|
+ JarFile jar;
|
|
+ try {
|
|
+ jar = ((JarURLConnection) url.openConnection()).getJarFile();
|
|
+ Enumeration<JarEntry> entries = jar.entries();
|
|
+ findClassesInPackageByJar(pack, entries, packageDirName, classes);
|
|
+ } catch (IOException e) {
|
|
+ throw new RuntimeException(e);
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ } catch (IOException e) {
|
|
+ throw new RuntimeException(e);
|
|
+ }
|
|
+
|
|
+ return classes;
|
|
+ }
|
|
+
|
|
+ private static void findClassesInPackageByFile(String packageName, String packagePath, Set<Class<?>> classes) {
|
|
+ File dir = new File(packagePath);
|
|
+
|
|
+ if (!dir.exists() || !dir.isDirectory()) {
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ File[] dirfiles = dir.listFiles((file) -> file.isDirectory() || file.getName().endsWith(".class"));
|
|
+ if (dirfiles != null) {
|
|
+ for (File file : dirfiles) {
|
|
+ if (file.isDirectory()) {
|
|
+ findClassesInPackageByFile(packageName + "." + file.getName(), file.getAbsolutePath(), classes);
|
|
+ } else {
|
|
+ String className = file.getName().substring(0, file.getName().length() - 6);
|
|
+ try {
|
|
+ classes.add(Class.forName(packageName + '.' + className));
|
|
+ } catch (ClassNotFoundException e) {
|
|
+ throw new RuntimeException(e);
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+ private static void findClassesInPackageByJar(String packageName, Enumeration<JarEntry> entries, String packageDirName, Set<Class<?>> classes) {
|
|
+ while (entries.hasMoreElements()) {
|
|
+ JarEntry entry = entries.nextElement();
|
|
+ String name = entry.getName();
|
|
+ if (name.charAt(0) == '/') {
|
|
+ name = name.substring(1);
|
|
+ }
|
|
+ if (name.startsWith(packageDirName)) {
|
|
+ int idx = name.lastIndexOf('/');
|
|
+ if (idx != -1) {
|
|
+ packageName = name.substring(0, idx).replace('/', '.');
|
|
+ }
|
|
+ if (name.endsWith(".class") && !entry.isDirectory()) {
|
|
+ String className = name.substring(packageName.length() + 1, name.length() - 6);
|
|
+ try {
|
|
+ classes.add(Class.forName(packageName + '.' + className));
|
|
+ } catch (ClassNotFoundException e) {
|
|
+ throw new RuntimeException(e);
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+}
|
|
diff --git a/src/main/java/me/earthme/luminol/utils/NullPlugin.java b/src/main/java/me/earthme/luminol/utils/NullPlugin.java
|
|
new file mode 100644
|
|
index 0000000000000000000000000000000000000000..de94c8e39f0ae0da80d5a79af63413e287f5d190
|
|
--- /dev/null
|
|
+++ b/src/main/java/me/earthme/luminol/utils/NullPlugin.java
|
|
@@ -0,0 +1,152 @@
|
|
+package me.earthme.luminol.utils;
|
|
+
|
|
+import io.papermc.paper.plugin.configuration.PluginMeta;
|
|
+import io.papermc.paper.plugin.lifecycle.event.LifecycleEventManager;
|
|
+import org.bukkit.Server;
|
|
+import org.bukkit.command.Command;
|
|
+import org.bukkit.command.CommandSender;
|
|
+import org.bukkit.configuration.file.FileConfiguration;
|
|
+import org.bukkit.generator.BiomeProvider;
|
|
+import org.bukkit.generator.ChunkGenerator;
|
|
+import org.bukkit.plugin.*;
|
|
+import org.jetbrains.annotations.NotNull;
|
|
+import org.jetbrains.annotations.Nullable;
|
|
+
|
|
+import java.io.File;
|
|
+import java.io.InputStream;
|
|
+import java.util.List;
|
|
+import java.util.logging.Logger;
|
|
+
|
|
+public class NullPlugin extends PluginBase {
|
|
+ private boolean enabled = true;
|
|
+
|
|
+ private final String pluginName;
|
|
+ private PluginDescriptionFile pdf;
|
|
+
|
|
+ public NullPlugin() {
|
|
+ this.pluginName = "Minecraft";
|
|
+ pdf = new PluginDescriptionFile(pluginName, "1.0", "nms");
|
|
+ }
|
|
+
|
|
+ public void setEnabled(boolean enabled) {
|
|
+ this.enabled = enabled;
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public File getDataFolder() {
|
|
+ throw new UnsupportedOperationException("Not supported.");
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public PluginDescriptionFile getDescription() {
|
|
+ return pdf;
|
|
+ }
|
|
+ // Paper start
|
|
+ @Override
|
|
+ public io.papermc.paper.plugin.configuration.PluginMeta getPluginMeta() {
|
|
+ return pdf;
|
|
+ }
|
|
+ // Paper end
|
|
+
|
|
+ @Override
|
|
+ public FileConfiguration getConfig() {
|
|
+ throw new UnsupportedOperationException("Not supported.");
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public InputStream getResource(String filename) {
|
|
+ throw new UnsupportedOperationException("Not supported.");
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public void saveConfig() {
|
|
+ throw new UnsupportedOperationException("Not supported.");
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public void saveDefaultConfig() {
|
|
+ throw new UnsupportedOperationException("Not supported.");
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public void saveResource(String resourcePath, boolean replace) {
|
|
+ throw new UnsupportedOperationException("Not supported.");
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public void reloadConfig() {
|
|
+ throw new UnsupportedOperationException("Not supported.");
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public PluginLogger getLogger() {
|
|
+ throw new UnsupportedOperationException("Not supported.");
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public PluginLoader getPluginLoader() {
|
|
+ throw new UnsupportedOperationException("Not supported.");
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public Server getServer() {
|
|
+ throw new UnsupportedOperationException("Not supported.");
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public boolean isEnabled() {
|
|
+ return enabled;
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public void onDisable() {
|
|
+ throw new UnsupportedOperationException("Not supported.");
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public void onLoad() {
|
|
+ throw new UnsupportedOperationException("Not supported.");
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public void onEnable() {
|
|
+ throw new UnsupportedOperationException("Not supported.");
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public boolean isNaggable() {
|
|
+ throw new UnsupportedOperationException("Not supported.");
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public void setNaggable(boolean canNag) {
|
|
+ throw new UnsupportedOperationException("Not supported.");
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public ChunkGenerator getDefaultWorldGenerator(String worldName, String id) {
|
|
+ throw new UnsupportedOperationException("Not supported.");
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public @Nullable BiomeProvider getDefaultBiomeProvider(@NotNull String worldName, @Nullable String id) {
|
|
+ throw new UnsupportedOperationException("Not supported.");
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
|
|
+ throw new UnsupportedOperationException("Not supported.");
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public List<String> onTabComplete(CommandSender sender, Command command, String alias, String[] args) {
|
|
+ throw new UnsupportedOperationException("Not supported.");
|
|
+ }
|
|
+
|
|
+ // Paper start - lifecycle events
|
|
+ @Override
|
|
+ public @NotNull io.papermc.paper.plugin.lifecycle.event.LifecycleEventManager<org.bukkit.plugin.Plugin> getLifecycleManager() {
|
|
+ throw new UnsupportedOperationException("Not supported.");
|
|
+ }
|
|
+ // Paper end - lifecycle events
|
|
+}
|
|
\ No newline at end of file
|