From 67f0b34c89f8b807d5d7e5300eb5d35cb4414268 Mon Sep 17 00:00:00 2001 From: NotAlexNoyle Date: Wed, 27 Aug 2025 11:39:08 -0400 Subject: [PATCH] Update gradle to 8.14.3 --- build.gradle.kts | 97 +++-- config/checkstyle/checkstyle.xml | 34 ++ config/formatter/eclipse-java-formatter.xml | 404 ++++++++++++++++++ gradle.properties | 4 +- gradle/wrapper/gradle-wrapper.properties | 2 +- .../emily/wets/bukkit/AbstractScheduler.java | 28 ++ .../java/ar/emily/wets/bukkit/Scheduler.java | 2 + src/main/java/ar/emily/wets/bukkit/Utils.java | 15 +- .../java/ar/emily/wets/bukkit/V5UUID.java | 19 +- .../java/ar/emily/wets/bukkit/WESpread.java | 106 ++++- .../ar/emily/wets/bukkit/WESpreadPlugin.java | 31 +- 11 files changed, 669 insertions(+), 73 deletions(-) create mode 100644 config/checkstyle/checkstyle.xml create mode 100644 config/formatter/eclipse-java-formatter.xml diff --git a/build.gradle.kts b/build.gradle.kts index 96c8643..c29ef62 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,85 +1,104 @@ +/* ------------------------------ Plugins ------------------------------ */ plugins { - id("java") // Tell gradle this is a java project. - id("java-library") // Import helper for source-based libraries. - id("com.diffplug.spotless") version "7.0.4" // Import auto-formatter. - id("com.gradleup.shadow") version "8.3.6" // Import shadow API. - eclipse // Import eclipse plugin for IDE integration. + id("java") // Import Java plugin. + id("java-library") // Import Java Library plugin. + id("com.diffplug.spotless") version "7.0.4" // Import Spotless plugin. + id("com.gradleup.shadow") version "8.3.6" // Import Shadow plugin. + id("checkstyle") // Import Checkstyle plugin. + eclipse // Import Eclipse plugin. + kotlin("jvm") version "2.1.21" // Import Kotlin JVM plugin. } +/* --------------------------- JDK / Kotlin ---------------------------- */ java { - // Declare java version. - sourceCompatibility = JavaVersion.VERSION_17 + sourceCompatibility = JavaVersion.VERSION_17 // Compile with JDK 17 compatibility. + toolchain { // Select Java toolchain. + languageVersion.set(JavaLanguageVersion.of(17)) // Use JDK 17. + vendor.set(JvmVendorSpec.GRAAL_VM) // Use GraalVM CE. + } } +kotlin { jvmToolchain(17) } + +/* ----------------------------- Metadata ------------------------------ */ group = "net.trueog.wets-og" // Declare bundle identifier. version = "1.2" // Declare plugin version (will be in .jar). val apiVersion = "1.19" +/* ----------------------------- Resources ----------------------------- */ tasks.named("processResources") { val props = mapOf("version" to version, "apiVersion" to apiVersion) - inputs.properties(props) // Indicates to rerun if version changes. - filesMatching("plugin.yml") { expand(props) } - from("LICENSE") { // Bundle license into .jars. - into("/") - } + from("LICENSE") { into("/") } // Bundle licenses into jarfiles. } +/* ---------------------------- Repos ---------------------------------- */ repositories { - mavenCentral() - gradlePluginPortal() - maven { url = uri("https://repo.purpurmc.org/snapshots") } - maven { - url = uri("https://maven.enginehub.org/repo") // Get WorldEdit API from EngineHub Repository. - } + mavenCentral() // Import the Maven Central Maven Repository. + gradlePluginPortal() // Import the Gradle Plugin Portal Maven Repository. + maven { url = uri("https://repo.purpurmc.org/snapshots") } // Import the PurpurMC Maven Repository. + maven { url = uri("https://maven.enginehub.org/repo") } // Import the EngineHub Maven Repository. } +/* ---------------------- Java project deps ---------------------------- */ dependencies { - compileOnly("org.purpurmc.purpur:purpur-api:1.19.4-R0.1-SNAPSHOT") // Declare purpur API version to be packaged. + compileOnly("org.purpurmc.purpur:purpur-api:1.19.4-R0.1-SNAPSHOT") // Declare Purpur API version to be packaged. + compileOnly("io.github.miniplaceholders:miniplaceholders-api:2.2.3") // Import MiniPlaceholders API. compileOnly("com.sk89q.worldedit:worldedit-bukkit:7.3.0-SNAPSHOT") // Import WorldEdit API. } +/* ---------------------- Reproducible jars ---------------------------- */ tasks.withType().configureEach { // Ensure reproducible .jars isPreserveFileTimestamps = false isReproducibleFileOrder = true } +/* ----------------------------- Shadow -------------------------------- */ tasks.shadowJar { + exclude("io.github.miniplaceholders.*") // Exclude the MiniPlaceholders package from being shadowed. archiveClassifier.set("") // Use empty string instead of null. minimize() } -tasks.build { - dependsOn(tasks.spotlessApply) - dependsOn(tasks.shadowJar) -} +tasks.jar { archiveClassifier.set("part") } // Applies to root jarfile only. -tasks.jar { archiveClassifier.set("part") } +tasks.build { dependsOn(tasks.spotlessApply, tasks.shadowJar) } // Build depends on spotless and shadow. +/* --------------------------- Javac opts ------------------------------- */ tasks.withType().configureEach { - options.compilerArgs.add("-parameters") - options.compilerArgs.add("-Xlint:deprecation") // Triggers deprecation warning messages. - options.encoding = "UTF-8" - options.isFork = true -} - -java { - toolchain { - languageVersion = JavaLanguageVersion.of(17) - vendor = JvmVendorSpec.GRAAL_VM - } + options.compilerArgs.add("-parameters") // Enable reflection for java code. + options.isFork = true // Run javac in its own process. + options.compilerArgs.add("-Xlint:deprecation") // Trigger deprecation warning messages. + options.encoding = "UTF-8" // Use UTF-8 file encoding. } +/* ----------------------------- Auto Formatting ------------------------ */ spotless { java { - removeUnusedImports() - palantirJavaFormat() + eclipse().configFile("config/formatter/eclipse-java-formatter.xml") // Eclipse java formatting. + leadingTabsToSpaces() // Convert leftover leading tabs to spaces. + removeUnusedImports() // Remove imports that aren't being called. } kotlinGradle { - ktfmt().kotlinlangStyle().configure { it.setMaxWidth(120) } - target("build.gradle.kts", "settings.gradle.kts") + ktfmt().kotlinlangStyle().configure { it.setMaxWidth(120) } // JetBrains Kotlin formatting. + target("build.gradle.kts", "settings.gradle.kts") // Gradle files to format. } } + +checkstyle { + toolVersion = "10.18.1" // Declare checkstyle version to use. + configFile = file("config/checkstyle/checkstyle.xml") // Point checkstyle to config file. + isIgnoreFailures = true // Don't fail the build if checkstyle does not pass. + isShowViolations = true // Show the violations in any IDE with the checkstyle plugin. +} + +tasks.named("compileJava") { + dependsOn("spotlessApply") // Run spotless before compiling with the JDK. +} + +tasks.named("spotlessCheck") { + dependsOn("spotlessApply") // Run spotless before checking if spotless ran. +} diff --git a/config/checkstyle/checkstyle.xml b/config/checkstyle/checkstyle.xml new file mode 100644 index 0000000..4c3ab95 --- /dev/null +++ b/config/checkstyle/checkstyle.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/config/formatter/eclipse-java-formatter.xml b/config/formatter/eclipse-java-formatter.xml new file mode 100644 index 0000000..a3e01a0 --- /dev/null +++ b/config/formatter/eclipse-java-formatter.xml @@ -0,0 +1,404 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/gradle.properties b/gradle.properties index 1bc8cb9..1e5932b 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,5 +1,5 @@ org.gradle.jvmargs=-Xmx1G org.gradle.parallel=false org.gradle.caching=false -group=ar.emily -version=0.0.5 +group=net.trueog.wets-og +version=0.0.6 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index ff23a68..d4081da 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.2-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/src/main/java/ar/emily/wets/bukkit/AbstractScheduler.java b/src/main/java/ar/emily/wets/bukkit/AbstractScheduler.java index 71bc8f8..54096e6 100644 --- a/src/main/java/ar/emily/wets/bukkit/AbstractScheduler.java +++ b/src/main/java/ar/emily/wets/bukkit/AbstractScheduler.java @@ -14,57 +14,81 @@ public final class AbstractScheduler implements Scheduler { private final Consumer underlyingScheduler; public AbstractScheduler(final IntSupplier currentTickGetter, final Consumer underlyingScheduler) { + this.currentTickGetter = currentTickGetter; this.underlyingScheduler = underlyingScheduler; + } public void setup() { + this.underlyingScheduler.accept(() -> { + processPendingTasks(); processTasks(); + }); + } @Override public void runPeriodically(final Consumer action, final long initialDelay, final long period) { + this.pendingTasks.add(new AbstractTask(action, this.currentTickGetter.getAsInt(), initialDelay, period)); + } public void flush() { + this.tasks.addAll(this.pendingTasks); this.pendingTasks.clear(); final Iterator it = this.tasks.iterator(); while (it.hasNext()) { + final AbstractTask task = it.next(); task.task.accept(it::remove); + } this.tasks.clear(); + } private void processPendingTasks() { + final int currentTick = this.currentTickGetter.getAsInt(); final Iterator it = this.pendingTasks.iterator(); while (it.hasNext()) { + final AbstractTask task = it.next(); if (task.registerTick + task.initialDelay >= currentTick) { + this.tasks.add(task); task.registerTick = currentTick; it.remove(); + } + } + } private void processTasks() { + final int currentTick = this.currentTickGetter.getAsInt(); final Iterator it = this.tasks.iterator(); while (it.hasNext()) { + final AbstractTask task = it.next(); if ((currentTick - task.registerTick) % task.period == 0) { + task.task.accept(it::remove); + } + } + } private static final class AbstractTask { @@ -75,10 +99,14 @@ public final class AbstractScheduler implements Scheduler { long registerTick; AbstractTask(final Consumer task, final long registerTick, final long initialDelay, final long period) { + this.task = task; this.registerTick = registerTick; this.initialDelay = Math.min(initialDelay, 1); this.period = Math.min(period, 1); + } + } + } diff --git a/src/main/java/ar/emily/wets/bukkit/Scheduler.java b/src/main/java/ar/emily/wets/bukkit/Scheduler.java index 616abcd..0e585fe 100644 --- a/src/main/java/ar/emily/wets/bukkit/Scheduler.java +++ b/src/main/java/ar/emily/wets/bukkit/Scheduler.java @@ -12,5 +12,7 @@ public interface Scheduler { interface Task { void cancel(); + } + } diff --git a/src/main/java/ar/emily/wets/bukkit/Utils.java b/src/main/java/ar/emily/wets/bukkit/Utils.java index 080225c..2e5d4a5 100644 --- a/src/main/java/ar/emily/wets/bukkit/Utils.java +++ b/src/main/java/ar/emily/wets/bukkit/Utils.java @@ -13,7 +13,9 @@ public class Utils { if (c == '&') { count++; + } + } // Create an array to store the positions of '&' characters @@ -27,33 +29,41 @@ public class Utils { if (isUpperBukkitCode(subject.charAt(i + 1))) { subject = replaceCharAtIndex(subject, (i + 1), Character.toLowerCase(subject.charAt(i + 1))); + } positions[index++] = i; + } + } String serializedText = LegacyComponentSerializer.legacySection() .serialize(LegacyComponentSerializer.legacyAmpersand().deserialize(subject)); return serializedText; + } private static boolean isUpperBukkitCode(char input) { - char[] bukkitColorCodes = {'A', 'B', 'C', 'D', 'E', 'F', 'K', 'L', 'M', 'N', 'O', 'R'}; + char[] bukkitColorCodes = { 'A', 'B', 'C', 'D', 'E', 'F', 'K', 'L', 'M', 'N', 'O', 'R' }; boolean match = false; // Loop through each character in the array. for (char c : bukkitColorCodes) { + // Check if the current character in the array is equal to the input character. if (c == input) { match = true; + } + } return match; + } private static String replaceCharAtIndex(String original, int index, char newChar) { @@ -63,9 +73,12 @@ public class Utils { // Create a new string with the replaced character return original.substring(0, index) + newChar + original.substring(index + 1); + } // If the index is invalid, return the original string return original; + } + } diff --git a/src/main/java/ar/emily/wets/bukkit/V5UUID.java b/src/main/java/ar/emily/wets/bukkit/V5UUID.java index d2c38a9..a2d40cd 100644 --- a/src/main/java/ar/emily/wets/bukkit/V5UUID.java +++ b/src/main/java/ar/emily/wets/bukkit/V5UUID.java @@ -15,27 +15,40 @@ final class V5UUID { private static final MessageDigest MD; static { + try { + MD = MessageDigest.getInstance("SHA-1"); + } catch (final NoSuchAlgorithmException ex) { + // shouldn't throw, SHA-1 is a standard algorithm that is required by Java SE // https://docs.oracle.com/en/java/javase/17/docs/specs/security/standard-names.html throw new ExceptionInInitializerError(ex); + } + } // MessageDigest isn't thread safe, that's why it's being cloned private static MessageDigest md() { + try { + return (MessageDigest) MD.clone(); + } catch (final CloneNotSupportedException ex) { + // shouldn't throw throw new RuntimeException(ex); + } + } // use V5 UUID as to not collide with V3 or V4 UUIDs, which are used by the game static UUID create(final byte[] bs) { + final var md = md(); md.update(bs); final byte[] digest = md.digest(); @@ -44,7 +57,11 @@ final class V5UUID { digest[8] &= 0x3f; // clear variant digest[8] |= (byte) 0x80; // set variant 1 return new UUID((long) AS_LONG_ARRAY.get(digest, 0), (long) AS_LONG_ARRAY.get(digest, 8)); + + } + + private V5UUID() { + } - private V5UUID() {} } diff --git a/src/main/java/ar/emily/wets/bukkit/WESpread.java b/src/main/java/ar/emily/wets/bukkit/WESpread.java index e5f4637..91ffe9c 100644 --- a/src/main/java/ar/emily/wets/bukkit/WESpread.java +++ b/src/main/java/ar/emily/wets/bukkit/WESpread.java @@ -40,33 +40,41 @@ import org.jetbrains.annotations.Nullable; public final class WESpread { - public static final UUID NON_PLAYER_ACTOR_ID = - V5UUID.create(WESpread.class.descriptorString().getBytes(StandardCharsets.UTF_8)); + public static final UUID NON_PLAYER_ACTOR_ID = V5UUID + .create(WESpread.class.descriptorString().getBytes(StandardCharsets.UTF_8)); private AbstractScheduler scheduler; File configFile = new File("plugins/WETS-OG/config.yml"); public static FileConfiguration convertFileToConfig(File file) { + FileConfiguration config = YamlConfiguration.loadConfiguration(file); try { + config.load(file); + } catch (IOException | InvalidConfigurationException error) { + error.printStackTrace(); + } return config; + } FileConfiguration configFileYAML = convertFileToConfig(configFile); public WESpread(AbstractScheduler scheduler) { + this.scheduler = scheduler; + } - private final Component INVALID_ARGUMENT = TextComponent.of( - Utils.legacySerializerAnyCase(Utils.legacySerializerAnyCase(configFileYAML.getString("ChatPrefix") + private final Component INVALID_ARGUMENT = TextComponent + .of(Utils.legacySerializerAnyCase(Utils.legacySerializerAnyCase(configFileYAML.getString("ChatPrefix") + "&cERROR: Blocks per tick must be a valid positive number!"))); private final Component COMMAND_USAGE = TextComponent.of(Utils.legacySerializerAnyCase( configFileYAML.getString("ChatPrefix") + "&6Usage: &e/wets ( | sorted | not-sorted)")); @@ -74,80 +82,109 @@ public final class WESpread { configFileYAML.getString("ChatPrefix") + "&6Block placement of new operations will now be &esorted&6.")); private final Component COMMAND_NOT_SORTED = TextComponent.of(Utils.legacySerializerAnyCase( configFileYAML.getString("ChatPrefix") + "&6Block placement of new operations will now be &eunsorted&6.")); - private final Component COMMAND_BPT_UPDATED = TextComponent.of(Utils.legacySerializerAnyCase( - configFileYAML.getString("ChatPrefix") + "&aBlocks per tick placement set to: ")); + private final Component COMMAND_BPT_UPDATED = TextComponent.of(Utils + .legacySerializerAnyCase(configFileYAML.getString("ChatPrefix") + "&aBlocks per tick placement set to: ")); private final Object2LongMap blocksPerTickMap = new Object2LongOpenHashMap<>(); private final Set actorsWhosePlacementIsNotSorted = new HashSet<>(); private long defaultBlocksPerTick = configFileYAML.getInt("DefaultBlocksPerTick"); public void playerLogout(final UUID id) { + this.blocksPerTickMap.removeLong(id); this.actorsWhosePlacementIsNotSorted.remove(id); + } public void load() { + WorldEdit.getInstance().getEventBus().register(this); + } - // We really really really want to flush the queue before WorldEdit itself unloads. + // We really really really want to flush the queue before WorldEdit itself + // unloads. @Subscribe(priority = EventHandler.Priority.VERY_EARLY) public void on(final PlatformUnreadyEvent event) { + WorldEdit.getInstance().getEventBus().unregister(this); this.defaultBlocksPerTick = Long.MAX_VALUE; this.blocksPerTickMap.clear(); this.actorsWhosePlacementIsNotSorted.clear(); this.scheduler.flush(); + } @Subscribe public void on(final EditSessionEvent event) { + if (event.getStage() == EditSession.Stage.BEFORE_CHANGE) { + final UUID actorId = Optional.ofNullable(event.getActor()) // use a set ID for non-player actors, such as console or potentially API users - .filter(Actor::isPlayer) - .map(Identifiable::getUniqueId) - .orElse(NON_PLAYER_ACTOR_ID); + .filter(Actor::isPlayer).map(Identifiable::getUniqueId).orElse(NON_PLAYER_ACTOR_ID); event.setExtent(new SchedulingExtent(event.getExtent(), actorId)); + } + } public void command(final Actor source, final List args) { + if (args.size() != 1) { + source.print(COMMAND_USAGE); return; + } final UUID id = source.isPlayer() ? source.getUniqueId() : NON_PLAYER_ACTOR_ID; final String arg = args.iterator().next(); if ("sorted".equals(arg)) { + this.actorsWhosePlacementIsNotSorted.remove(id); source.print(COMMAND_SORTED); return; + } else if ("not-sorted".equals(arg)) { + this.actorsWhosePlacementIsNotSorted.add(id); source.print(COMMAND_NOT_SORTED); return; + } try { + long blocksPerTick = Long.parseLong(arg); if (blocksPerTick < 0) { + blocksPerTick = Long.MAX_VALUE; + } + this.blocksPerTickMap.put(id, blocksPerTick); configFileYAML.set("DefaultBlocksPerTick", blocksPerTick); try { + configFileYAML.save(configFile); + } catch (IOException error) { + error.printStackTrace(); + } - source.print(COMMAND_BPT_UPDATED.append( - TextComponent.of(Utils.legacySerializerAnyCase("&e" + String.valueOf(blocksPerTick))))); + + source.print(COMMAND_BPT_UPDATED + .append(TextComponent.of(Utils.legacySerializerAnyCase("&e" + String.valueOf(blocksPerTick))))); + } catch (final NumberFormatException exception) { + source.printError(INVALID_ARGUMENT); source.print(COMMAND_USAGE); + } + } private final class SchedulingExtent extends AbstractBufferingExtent { @@ -157,67 +194,102 @@ public final class WESpread { private final boolean sorted; private SchedulingExtent(final Extent delegate, final UUID actor) { + super(delegate); this.sorted = !WESpread.this.actorsWhosePlacementIsNotSorted.contains(actor); - this.blocksPerTick = - () -> WESpread.this.blocksPerTickMap.getOrDefault(actor, WESpread.this.defaultBlocksPerTick); + this.blocksPerTick = () -> WESpread.this.blocksPerTickMap.getOrDefault(actor, + WESpread.this.defaultBlocksPerTick); + } @Override protected @Nullable BaseBlock getBufferedFullBlock(final BlockVector3 position) { + return this.buffer.computeIfAbsent(position, getExtent()::getFullBlock); + } @Override public > boolean setBlock(final BlockVector3 position, final T block) { - this.buffer.remove( - position); // workaround silly WorldEdit bug, see https://github.com/emilyy-dev/wets/issues/1 + + this.buffer.remove(position); // workaround silly WorldEdit bug, see + // https://github.com/emilyy-dev/wets/issues/1 this.buffer.put(position, block.toBaseBlock()); return true; + } @Override protected Operation commitBefore() { + final Iterator> it; { + Stream> stream = this.buffer.entrySet().stream(); if (this.sorted) { + stream = stream.sorted(Map.Entry.comparingByKey(BlockVector3.sortByCoordsYzx())); + } + it = stream.iterator(); + } return new Operation() { + boolean started = false; @Override public Operation resume(final RunContext run) { + if (!this.started && it.hasNext()) { + this.started = true; WESpread.this.scheduler.runPeriodically(this::putBlock, 1L, 1L); + } return null; + } private void putBlock(final Scheduler.Task task) { + try { + for (long i = 0; it.hasNext() && i < SchedulingExtent.this.blocksPerTick.getAsLong(); ++i) { + final var entry = it.next(); setDelegateBlock(entry.getKey(), entry.getValue()); + } + } catch (final WorldEditException exception) { + throw new RuntimeException(exception); + } finally { + if (!it.hasNext()) { + task.cancel(); + } + } + } @Override - public void cancel() {} + public void cancel() { + + } + }; + } + } + } diff --git a/src/main/java/ar/emily/wets/bukkit/WESpreadPlugin.java b/src/main/java/ar/emily/wets/bukkit/WESpreadPlugin.java index 3cd8f3a..2303529 100644 --- a/src/main/java/ar/emily/wets/bukkit/WESpreadPlugin.java +++ b/src/main/java/ar/emily/wets/bukkit/WESpreadPlugin.java @@ -17,43 +17,50 @@ public final class WESpreadPlugin extends JavaPlugin implements Listener { private static final List COMPLETIONS = List.of("sorted", "not-sorted"); - private final AbstractScheduler scheduler = new AbstractScheduler( - getServer()::getCurrentTick, task -> getServer().getScheduler().runTaskTimer(this, task, 1L, 1L)); + private final AbstractScheduler scheduler = new AbstractScheduler(getServer()::getCurrentTick, + task -> getServer().getScheduler().runTaskTimer(this, task, 1L, 1L)); private final WESpread wetsSpread = new WESpread(this.scheduler); @EventHandler public void on(final PlayerQuitEvent event) { + this.wetsSpread.playerLogout(event.getPlayer().getUniqueId()); + } @Override public void onLoad() { + this.wetsSpread.load(); + } @Override public void onEnable() { + saveDefaultConfig(); this.scheduler.setup(); getServer().getPluginManager().registerEvents(this, this); + } @Override - public boolean onCommand( - final @NotNull CommandSender sender, - final @NotNull Command command, - final @NotNull String label, - final String @NotNull [] args) { + public boolean onCommand(final @NotNull CommandSender sender, final @NotNull Command command, + final @NotNull String label, final String @NotNull [] args) + { + this.wetsSpread.command(BukkitAdapter.adapt(sender), Arrays.asList(args)); return true; + } @Override - public @NotNull List onTabComplete( - final @NotNull CommandSender sender, - final @NotNull Command command, - final @NotNull String alias, - final String @NotNull [] args) { + public @NotNull List onTabComplete(final @NotNull CommandSender sender, final @NotNull Command command, + final @NotNull String alias, final String @NotNull [] args) + { + return args.length == 1 ? StringUtil.copyPartialMatches(args[0], COMPLETIONS, new ArrayList<>(1)) : List.of(); + } + }