From 0e706d36c4a7e660cd57d12a36a1f44a6b6add0a Mon Sep 17 00:00:00 2001 From: William Date: Mon, 17 Jun 2024 21:07:09 +0100 Subject: [PATCH] refactor: use Uniform for native command support (#323) * refactor: use Uniform for commands * refactor: remove commodore * fix: update Uniform, fix commands * fix: bump uniform, fix commands on fabric * feat: use new Uniform command permission system * test: target 1.21 --- bukkit/build.gradle | 6 +- .../william278/husksync/BukkitHuskSync.java | 23 +- .../husksync/command/BrigadierUtil.java | 60 ---- .../husksync/command/BukkitCommand.java | 164 --------- .../resources/commodore/enderchest.commodore | 3 - .../resources/commodore/husksync.commodore | 6 - .../resources/commodore/inventory.commodore | 3 - .../resources/commodore/userdata.commodore | 35 -- common/build.gradle | 2 + .../net/william278/husksync/HuskSync.java | 39 +- .../william278/husksync/command/Command.java | 94 ----- .../husksync/command/EnderChestCommand.java | 32 +- .../husksync/command/Executable.java | 29 -- .../husksync/command/HuskSyncCommand.java | 336 +++++++++--------- .../husksync/command/InventoryCommand.java | 32 +- .../husksync/command/ItemsCommand.java | 126 +++---- .../net/william278/husksync/command/Node.java | 105 ------ .../husksync/command/PluginCommand.java | 127 +++++++ .../husksync/command/TabProvider.java | 50 --- .../husksync/command/UserDataCommand.java | 258 ++++++++------ .../william278/husksync/config/Settings.java | 3 - .../william278/husksync/user/CommandUser.java | 2 +- .../william278/husksync/user/ConsoleUser.java | 2 + fabric/build.gradle | 5 +- .../william278/husksync/FabricHuskSync.java | 61 ++-- .../husksync/command/FabricCommand.java | 153 -------- .../william278/husksync/data/FabricData.java | 14 +- .../listener/FabricEventListener.java | 6 +- .../william278/husksync/user/FabricUser.java | 2 +- paper/build.gradle | 4 +- .../william278/husksync/PaperHuskSync.java | 7 + test/spin_network.py | 2 +- 32 files changed, 623 insertions(+), 1168 deletions(-) delete mode 100644 bukkit/src/main/java/net/william278/husksync/command/BrigadierUtil.java delete mode 100644 bukkit/src/main/java/net/william278/husksync/command/BukkitCommand.java delete mode 100644 bukkit/src/main/resources/commodore/enderchest.commodore delete mode 100644 bukkit/src/main/resources/commodore/husksync.commodore delete mode 100644 bukkit/src/main/resources/commodore/inventory.commodore delete mode 100644 bukkit/src/main/resources/commodore/userdata.commodore delete mode 100644 common/src/main/java/net/william278/husksync/command/Command.java delete mode 100644 common/src/main/java/net/william278/husksync/command/Executable.java delete mode 100644 common/src/main/java/net/william278/husksync/command/Node.java create mode 100644 common/src/main/java/net/william278/husksync/command/PluginCommand.java delete mode 100644 common/src/main/java/net/william278/husksync/command/TabProvider.java delete mode 100644 fabric/src/main/java/net/william278/husksync/command/FabricCommand.java diff --git a/bukkit/build.gradle b/bukkit/build.gradle index 301fd61b..1b86cf2f 100644 --- a/bukkit/build.gradle +++ b/bukkit/build.gradle @@ -1,12 +1,12 @@ dependencies { implementation project(path: ':common') - implementation 'org.bstats:bstats-bukkit:3.0.2' + implementation 'net.william278.uniform:uniform-bukkit:1.1' implementation 'net.william278:mpdbdataconverter:1.0.1' implementation 'net.william278:hsldataconverter:1.0' implementation 'net.william278:mapdataapi:1.0.3' implementation 'net.william278:andjam:1.0.2' - implementation 'me.lucko:commodore:2.2' + implementation 'org.bstats:bstats-bukkit:3.0.2' implementation 'net.kyori:adventure-platform-bukkit:4.3.3' implementation 'dev.triumphteam:triumph-gui:3.1.10' implementation 'space.arim.morepaperlib:morepaperlib:0.4.4' @@ -42,6 +42,7 @@ shadowJar { relocate 'org.intellij', 'net.william278.husksync.libraries' relocate 'com.zaxxer', 'net.william278.husksync.libraries' relocate 'de.exlll', 'net.william278.husksync.libraries' + relocate 'net.william278.uniform', 'net.william278.husksync.libraries.uniform' relocate 'net.william278.desertwell', 'net.william278.husksync.libraries.desertwell' relocate 'net.william278.paginedown', 'net.william278.husksync.libraries.paginedown' relocate 'net.william278.mapdataapi', 'net.william278.husksync.libraries.mapdataapi' @@ -51,7 +52,6 @@ shadowJar { relocate 'org.json', 'net.william278.husksync.libraries.json' relocate 'net.querz', 'net.william278.husksync.libraries.nbtparser' relocate 'net.roxeez', 'net.william278.husksync.libraries' - relocate 'me.lucko.commodore', 'net.william278.husksync.libraries.commodore' relocate 'org.bstats', 'net.william278.husksync.libraries.bstats' relocate 'dev.triumphteam.gui', 'net.william278.husksync.libraries.triumphgui' relocate 'space.arim.morepaperlib', 'net.william278.husksync.libraries.paperlib' diff --git a/bukkit/src/main/java/net/william278/husksync/BukkitHuskSync.java b/bukkit/src/main/java/net/william278/husksync/BukkitHuskSync.java index 48d96c49..702597dd 100644 --- a/bukkit/src/main/java/net/william278/husksync/BukkitHuskSync.java +++ b/bukkit/src/main/java/net/william278/husksync/BukkitHuskSync.java @@ -34,7 +34,7 @@ import net.william278.husksync.adapter.DataAdapter; import net.william278.husksync.adapter.GsonAdapter; import net.william278.husksync.adapter.SnappyGsonAdapter; import net.william278.husksync.api.BukkitHuskSyncAPI; -import net.william278.husksync.command.BukkitCommand; +import net.william278.husksync.command.PluginCommand; import net.william278.husksync.config.Locales; import net.william278.husksync.config.Server; import net.william278.husksync.config.Settings; @@ -57,6 +57,8 @@ import net.william278.husksync.util.BukkitLegacyConverter; import net.william278.husksync.util.BukkitMapPersister; import net.william278.husksync.util.BukkitTask; import net.william278.husksync.util.LegacyConverter; +import net.william278.uniform.Uniform; +import net.william278.uniform.bukkit.BukkitUniform; import org.bstats.bukkit.Metrics; import org.bukkit.entity.Player; import org.bukkit.map.MapView; @@ -64,7 +66,6 @@ import org.bukkit.plugin.Plugin; import org.bukkit.plugin.java.JavaPlugin; import org.jetbrains.annotations.NotNull; import space.arim.morepaperlib.MorePaperLib; -import space.arim.morepaperlib.commands.CommandRegistration; import space.arim.morepaperlib.scheduling.AsynchronousScheduler; import space.arim.morepaperlib.scheduling.AttachedScheduler; import space.arim.morepaperlib.scheduling.GracefulScheduling; @@ -135,6 +136,10 @@ public class BukkitHuskSync extends JavaPlugin implements HuskSync, BukkitTask.S @Override public void onEnable() { this.audiences = BukkitAudiences.create(this); + + // Register commands + initialize("commands", (plugin) -> getUniform().register(PluginCommand.Type.create(this))); + // Prepare data adapter initialize("data adapter", (plugin) -> { if (settings.getSynchronization().isCompressData()) { @@ -196,9 +201,6 @@ public class BukkitHuskSync extends JavaPlugin implements HuskSync, BukkitTask.S // Register events initialize("events", (plugin) -> eventListener.onEnable()); - // Register commands - initialize("commands", (plugin) -> BukkitCommand.Type.registerCommands(this)); - // Register plugin hooks initialize("hooks", (plugin) -> { if (isDependencyLoaded("Plan") && getSettings().isEnablePlanHook()) { @@ -264,6 +266,12 @@ public class BukkitHuskSync extends JavaPlugin implements HuskSync, BukkitTask.S this.dataSyncer = dataSyncer; } + @Override + @NotNull + public Uniform getUniform() { + return BukkitUniform.getInstance(this); + } + @NotNull @Override public Map getPlayerCustomDataStore(@NotNull OnlineUser user) { @@ -352,11 +360,6 @@ public class BukkitHuskSync extends JavaPlugin implements HuskSync, BukkitTask.S return getScheduler().entitySpecificScheduler(((BukkitUser) user).getPlayer()); } - @NotNull - public CommandRegistration getCommandRegistrar() { - return paperLib.commandRegistration(); - } - @Override @NotNull public Path getConfigDirectory() { diff --git a/bukkit/src/main/java/net/william278/husksync/command/BrigadierUtil.java b/bukkit/src/main/java/net/william278/husksync/command/BrigadierUtil.java deleted file mode 100644 index 8b35ac38..00000000 --- a/bukkit/src/main/java/net/william278/husksync/command/BrigadierUtil.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * This file is part of HuskSync, licensed under the Apache License 2.0. - * - * Copyright (c) William278 - * Copyright (c) contributors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package net.william278.husksync.command; - -import me.lucko.commodore.CommodoreProvider; -import me.lucko.commodore.file.CommodoreFileReader; -import net.william278.husksync.BukkitHuskSync; -import org.jetbrains.annotations.NotNull; - -import java.io.IOException; -import java.io.InputStream; -import java.util.logging.Level; - -public class BrigadierUtil { - - /** - * Uses commodore to register command completions. - * - * @param plugin instance of the registering Bukkit plugin - * @param bukkitCommand the Bukkit PluginCommand to register completions for - * @param command the {@link Command} to register completions for - */ - protected static void registerCommodore(@NotNull BukkitHuskSync plugin, - @NotNull org.bukkit.command.Command bukkitCommand, - @NotNull Command command) { - final InputStream commodoreFile = plugin.getResource( - "commodore/" + bukkitCommand.getName() + ".commodore" - ); - if (commodoreFile == null) { - return; - } - try { - CommodoreProvider.getCommodore(plugin).register(bukkitCommand, - CommodoreFileReader.INSTANCE.parse(commodoreFile), - player -> player.hasPermission(command.getPermission())); - } catch (IOException e) { - plugin.log(Level.SEVERE, String.format( - "Failed to read command commodore completions for %s", bukkitCommand.getName()), e - ); - } - } - -} diff --git a/bukkit/src/main/java/net/william278/husksync/command/BukkitCommand.java b/bukkit/src/main/java/net/william278/husksync/command/BukkitCommand.java deleted file mode 100644 index 8b130d39..00000000 --- a/bukkit/src/main/java/net/william278/husksync/command/BukkitCommand.java +++ /dev/null @@ -1,164 +0,0 @@ -/* - * This file is part of HuskSync, licensed under the Apache License 2.0. - * - * Copyright (c) William278 - * Copyright (c) contributors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package net.william278.husksync.command; - - -import me.lucko.commodore.CommodoreProvider; -import net.william278.husksync.BukkitHuskSync; -import net.william278.husksync.user.BukkitUser; -import net.william278.husksync.user.CommandUser; -import org.bukkit.command.CommandSender; -import org.bukkit.entity.Player; -import org.bukkit.permissions.Permission; -import org.bukkit.permissions.PermissionDefault; -import org.bukkit.plugin.PluginManager; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.util.*; -import java.util.function.Function; - -public class BukkitCommand extends org.bukkit.command.Command { - - private final BukkitHuskSync plugin; - private final Command command; - - public BukkitCommand(@NotNull Command command, @NotNull BukkitHuskSync plugin) { - super(command.getName(), command.getDescription(), command.getUsage(), command.getAliases()); - this.command = command; - this.plugin = plugin; - } - - @Override - public boolean execute(@NotNull CommandSender sender, @NotNull String commandLabel, @NotNull String[] args) { - this.command.onExecuted(sender instanceof Player p ? BukkitUser.adapt(p, plugin) : plugin.getConsole(), args); - return true; - } - - @NotNull - @Override - public List tabComplete(@NotNull CommandSender sender, @NotNull String alias, - @NotNull String[] args) throws IllegalArgumentException { - if (!(this.command instanceof TabProvider provider)) { - return List.of(); - } - final CommandUser user = sender instanceof Player p ? BukkitUser.adapt(p, plugin) : plugin.getConsole(); - if (getPermission() == null || user.hasPermission(getPermission())) { - return provider.getSuggestions(user, args); - } - return List.of(); - } - - public void register() { - // Register with bukkit - plugin.getCommandRegistrar().getServerCommandMap().register("husksync", this); - - // Register permissions - BukkitCommand.addPermission( - plugin, - command.getPermission(), - command.getUsage(), - BukkitCommand.getPermissionDefault(command.isOperatorCommand()) - ); - final List childNodes = command.getAdditionalPermissions() - .entrySet().stream() - .map((entry) -> BukkitCommand.addPermission( - plugin, - entry.getKey(), - "", - BukkitCommand.getPermissionDefault(entry.getValue())) - ) - .filter(Objects::nonNull) - .toList(); - if (!childNodes.isEmpty()) { - BukkitCommand.addPermission( - plugin, - command.getPermission("*"), - command.getUsage(), - PermissionDefault.FALSE, - childNodes.toArray(new Permission[0]) - ); - } - - // Register commodore TAB completion - if (CommodoreProvider.isSupported() && plugin.getSettings().isBrigadierTabCompletion()) { - BrigadierUtil.registerCommodore(plugin, this, command); - } - } - - @Nullable - protected static Permission addPermission(@NotNull BukkitHuskSync plugin, @NotNull String node, - @NotNull String description, @NotNull PermissionDefault permissionDefault, - @NotNull Permission... children) { - final Map childNodes = Arrays.stream(children) - .map(Permission::getName) - .collect(HashMap::new, (map, child) -> map.put(child, true), HashMap::putAll); - - final PluginManager manager = plugin.getServer().getPluginManager(); - if (manager.getPermission(node) != null) { - return null; - } - - Permission permission; - if (description.isEmpty()) { - permission = new Permission(node, permissionDefault, childNodes); - } else { - permission = new Permission(node, description, permissionDefault, childNodes); - } - manager.addPermission(permission); - - return permission; - } - - @NotNull - protected static PermissionDefault getPermissionDefault(boolean isOperatorCommand) { - return isOperatorCommand ? PermissionDefault.OP : PermissionDefault.TRUE; - } - - /** - * Commands available on the Bukkit HuskSync implementation - */ - public enum Type { - - HUSKSYNC_COMMAND(HuskSyncCommand::new), - USERDATA_COMMAND(UserDataCommand::new), - INVENTORY_COMMAND(InventoryCommand::new), - ENDER_CHEST_COMMAND(EnderChestCommand::new); - - public final Function commandSupplier; - - Type(@NotNull Function supplier) { - this.commandSupplier = supplier; - } - - @NotNull - public Command createCommand(@NotNull BukkitHuskSync plugin) { - return commandSupplier.apply(plugin); - } - - public static void registerCommands(@NotNull BukkitHuskSync plugin) { - Arrays.stream(values()) - .map((type) -> type.createCommand(plugin)) - .forEach((command) -> new BukkitCommand(command, plugin).register()); - } - - - } -} diff --git a/bukkit/src/main/resources/commodore/enderchest.commodore b/bukkit/src/main/resources/commodore/enderchest.commodore deleted file mode 100644 index 8bd4c96f..00000000 --- a/bukkit/src/main/resources/commodore/enderchest.commodore +++ /dev/null @@ -1,3 +0,0 @@ -inventory { - name brigadier:string single_word; -} \ No newline at end of file diff --git a/bukkit/src/main/resources/commodore/husksync.commodore b/bukkit/src/main/resources/commodore/husksync.commodore deleted file mode 100644 index 9acdaaa3..00000000 --- a/bukkit/src/main/resources/commodore/husksync.commodore +++ /dev/null @@ -1,6 +0,0 @@ -husksync { - update; - about; - status; - reload; -} \ No newline at end of file diff --git a/bukkit/src/main/resources/commodore/inventory.commodore b/bukkit/src/main/resources/commodore/inventory.commodore deleted file mode 100644 index 9904e908..00000000 --- a/bukkit/src/main/resources/commodore/inventory.commodore +++ /dev/null @@ -1,3 +0,0 @@ -enderchest { - name brigadier:string single_word; -} \ No newline at end of file diff --git a/bukkit/src/main/resources/commodore/userdata.commodore b/bukkit/src/main/resources/commodore/userdata.commodore deleted file mode 100644 index 408f0262..00000000 --- a/bukkit/src/main/resources/commodore/userdata.commodore +++ /dev/null @@ -1,35 +0,0 @@ -userdata { - view { - name brigadier:string single_word { - version brigadier:string single_word; - } - } - list { - name brigadier:string single_word { - page brigadier:integer; - } - } - delete { - name brigadier:string single_word { - version brigadier:string single_word; - } - } - restore { - name brigadier:string single_word { - version brigadier:string single_word; - } - } - pin { - name brigadier:string single_word { - version brigadier:string single_word; - } - } - dump { - name brigadier:string single_word { - version brigadier:string single_word { - web; - file; - } - } - } -} \ No newline at end of file diff --git a/common/build.gradle b/common/build.gradle index a81fc971..bde5c6b1 100644 --- a/common/build.gradle +++ b/common/build.gradle @@ -16,6 +16,8 @@ dependencies { exclude module: 'slf4j-api' } + compileOnly 'net.william278.uniform:uniform-common:1.1' + compileOnly 'com.mojang:brigadier:1.1.8' compileOnly 'org.projectlombok:lombok:1.18.32' compileOnly 'org.jetbrains:annotations:24.1.0' compileOnly 'net.kyori:adventure-api:4.17.0' diff --git a/common/src/main/java/net/william278/husksync/HuskSync.java b/common/src/main/java/net/william278/husksync/HuskSync.java index 189e8cc2..b77aa80d 100644 --- a/common/src/main/java/net/william278/husksync/HuskSync.java +++ b/common/src/main/java/net/william278/husksync/HuskSync.java @@ -41,6 +41,7 @@ import net.william278.husksync.user.ConsoleUser; import net.william278.husksync.user.OnlineUser; import net.william278.husksync.util.LegacyConverter; import net.william278.husksync.util.Task; +import net.william278.uniform.Uniform; import org.jetbrains.annotations.NotNull; import java.io.InputStream; @@ -111,6 +112,14 @@ public interface HuskSync extends Task.Supplier, EventDispatcher, ConfigProvider */ void setDataSyncer(@NotNull DataSyncer dataSyncer); + /** + * Get the uniform command provider + * + * @return the command provider + */ + @NotNull + Uniform getUniform(); + /** * Returns a list of available data {@link Migrator}s * @@ -256,10 +265,10 @@ public interface HuskSync extends Task.Supplier, EventDispatcher, ConfigProvider @NotNull default UpdateChecker getUpdateChecker() { return UpdateChecker.builder() - .currentVersion(getPluginVersion()) - .endpoint(UpdateChecker.Endpoint.SPIGOT) - .resource(Integer.toString(SPIGOT_RESOURCE_ID)) - .build(); + .currentVersion(getPluginVersion()) + .endpoint(UpdateChecker.Endpoint.SPIGOT) + .resource(Integer.toString(SPIGOT_RESOURCE_ID)) + .build(); } default void checkForUpdates() { @@ -267,8 +276,8 @@ public interface HuskSync extends Task.Supplier, EventDispatcher, ConfigProvider getUpdateChecker().check().thenAccept(checked -> { if (!checked.isUpToDate()) { log(Level.WARNING, String.format( - "A new version of HuskSync is available: v%s (running v%s)", - checked.getLatestVersion(), getPluginVersion()) + "A new version of HuskSync is available: v%s (running v%s)", + checked.getLatestVersion(), getPluginVersion()) ); } }); @@ -311,15 +320,15 @@ public interface HuskSync extends Task.Supplier, EventDispatcher, ConfigProvider final class FailedToLoadException extends IllegalStateException { private static final String FORMAT = """ - HuskSync has failed to load! The plugin will not be enabled and no data will be synchronized. - Please make sure the plugin has been setup correctly (https://william278.net/docs/husksync/setup): - - 1) Make sure you've entered your MySQL, MariaDB or MongoDB database details correctly in config.yml - 2) Make sure your Redis server details are also correct in config.yml - 3) Make sure your config is up-to-date (https://william278.net/docs/husksync/config-file) - 4) Check the error below for more details - - Caused by: %s"""; + HuskSync has failed to load! The plugin will not be enabled and no data will be synchronized. + Please make sure the plugin has been setup correctly (https://william278.net/docs/husksync/setup): + + 1) Make sure you've entered your MySQL, MariaDB or MongoDB database details correctly in config.yml + 2) Make sure your Redis server details are also correct in config.yml + 3) Make sure your config is up-to-date (https://william278.net/docs/husksync/config-file) + 4) Check the error below for more details + + Caused by: %s"""; FailedToLoadException(@NotNull String message, @NotNull Throwable cause) { super(String.format(FORMAT, message), cause); diff --git a/common/src/main/java/net/william278/husksync/command/Command.java b/common/src/main/java/net/william278/husksync/command/Command.java deleted file mode 100644 index 48a802fa..00000000 --- a/common/src/main/java/net/william278/husksync/command/Command.java +++ /dev/null @@ -1,94 +0,0 @@ -/* - * This file is part of HuskSync, licensed under the Apache License 2.0. - * - * Copyright (c) William278 - * Copyright (c) contributors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package net.william278.husksync.command; - -import com.google.common.collect.Maps; -import net.william278.husksync.HuskSync; -import net.william278.husksync.user.CommandUser; -import org.jetbrains.annotations.NotNull; - -import java.util.List; -import java.util.Map; - -public abstract class Command extends Node { - - private final String usage; - private final Map additionalPermissions; - - protected Command(@NotNull String name, @NotNull List aliases, @NotNull String usage, - @NotNull HuskSync plugin) { - super(name, aliases, plugin); - this.usage = usage; - this.additionalPermissions = Maps.newHashMap(); - } - - @Override - public final void onExecuted(@NotNull CommandUser executor, @NotNull String[] args) { - if (!executor.hasPermission(getPermission())) { - plugin.getLocales().getLocale("error_no_permission") - .ifPresent(executor::sendMessage); - return; - } - plugin.runAsync(() -> this.execute(executor, args)); - } - - public abstract void execute(@NotNull CommandUser executor, @NotNull String[] args); - - @NotNull - protected String[] removeFirstArg(@NotNull String[] args) { - if (args.length <= 1) { - return new String[0]; - } - String[] newArgs = new String[args.length - 1]; - System.arraycopy(args, 1, newArgs, 0, args.length - 1); - return newArgs; - } - - @NotNull - public final String getRawUsage() { - return usage; - } - - @NotNull - public final String getUsage() { - return "/" + getName() + " " + getRawUsage(); - } - - public final void addAdditionalPermissions(@NotNull Map permissions) { - permissions.forEach((permission, value) -> this.additionalPermissions.put(getPermission(permission), value)); - } - - @NotNull - public final Map getAdditionalPermissions() { - return additionalPermissions; - } - - @NotNull - public String getDescription() { - return plugin.getLocales().getRawLocale(getName() + "_command_description") - .orElse(getUsage()); - } - - @NotNull - public final HuskSync getPlugin() { - return plugin; - } - -} \ No newline at end of file diff --git a/common/src/main/java/net/william278/husksync/command/EnderChestCommand.java b/common/src/main/java/net/william278/husksync/command/EnderChestCommand.java index 4c361918..5a87e4f6 100644 --- a/common/src/main/java/net/william278/husksync/command/EnderChestCommand.java +++ b/common/src/main/java/net/william278/husksync/command/EnderChestCommand.java @@ -37,7 +37,7 @@ import java.util.Optional; public class EnderChestCommand extends ItemsCommand { public EnderChestCommand(@NotNull HuskSync plugin) { - super(plugin, List.of("enderchest", "echest", "openechest")); + super("enderchest", List.of("echest", "openechest"), plugin); } @Override @@ -46,29 +46,29 @@ public class EnderChestCommand extends ItemsCommand { final Optional optionalEnderChest = snapshot.getEnderChest(); if (optionalEnderChest.isEmpty()) { plugin.getLocales().getLocale("error_no_data_to_display") - .ifPresent(viewer::sendMessage); + .ifPresent(viewer::sendMessage); return; } // Display opening message plugin.getLocales().getLocale("ender_chest_viewer_opened", user.getUsername(), - snapshot.getTimestamp().format(DateTimeFormatter - .ofLocalizedDateTime(FormatStyle.MEDIUM, FormatStyle.SHORT))) - .ifPresent(viewer::sendMessage); + snapshot.getTimestamp().format(DateTimeFormatter + .ofLocalizedDateTime(FormatStyle.MEDIUM, FormatStyle.SHORT))) + .ifPresent(viewer::sendMessage); // Show GUI final Data.Items.EnderChest enderChest = optionalEnderChest.get(); viewer.showGui( - enderChest, - plugin.getLocales().getLocale("ender_chest_viewer_menu_title", user.getUsername()) - .orElse(new MineDown(String.format("%s's Ender Chest", user.getUsername()))), - allowEdit, - enderChest.getSlotCount(), - (itemsOnClose) -> { - if (allowEdit && !enderChest.equals(itemsOnClose)) { - plugin.runAsync(() -> this.updateItems(viewer, itemsOnClose, user)); - } + enderChest, + plugin.getLocales().getLocale("ender_chest_viewer_menu_title", user.getUsername()) + .orElse(new MineDown(String.format("%s's Ender Chest", user.getUsername()))), + allowEdit, + enderChest.getSlotCount(), + (itemsOnClose) -> { + if (allowEdit && !enderChest.equals(itemsOnClose)) { + plugin.runAsync(() -> this.updateItems(viewer, itemsOnClose, user)); } + } ); } @@ -78,7 +78,7 @@ public class EnderChestCommand extends ItemsCommand { final Optional latestData = plugin.getDatabase().getLatestSnapshot(holder); if (latestData.isEmpty()) { plugin.getLocales().getLocale("error_no_data_to_display") - .ifPresent(viewer::sendMessage); + .ifPresent(viewer::sendMessage); return; } @@ -88,7 +88,7 @@ public class EnderChestCommand extends ItemsCommand { data.getEnderChest().ifPresent(enderChest -> enderChest.setContents(items)); data.setSaveCause(DataSnapshot.SaveCause.ENDERCHEST_COMMAND); data.setPinned( - plugin.getSettings().getSynchronization().doAutoPin(DataSnapshot.SaveCause.ENDERCHEST_COMMAND) + plugin.getSettings().getSynchronization().doAutoPin(DataSnapshot.SaveCause.ENDERCHEST_COMMAND) ); }); diff --git a/common/src/main/java/net/william278/husksync/command/Executable.java b/common/src/main/java/net/william278/husksync/command/Executable.java deleted file mode 100644 index cc7890a7..00000000 --- a/common/src/main/java/net/william278/husksync/command/Executable.java +++ /dev/null @@ -1,29 +0,0 @@ -/* - * This file is part of HuskSync, licensed under the Apache License 2.0. - * - * Copyright (c) William278 - * Copyright (c) contributors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package net.william278.husksync.command; - -import net.william278.husksync.user.CommandUser; -import org.jetbrains.annotations.NotNull; - -public interface Executable { - - void onExecuted(@NotNull CommandUser executor, @NotNull String[] args); - -} diff --git a/common/src/main/java/net/william278/husksync/command/HuskSyncCommand.java b/common/src/main/java/net/william278/husksync/command/HuskSyncCommand.java index ecd09b4e..77cbc3c7 100644 --- a/common/src/main/java/net/william278/husksync/command/HuskSyncCommand.java +++ b/common/src/main/java/net/william278/husksync/command/HuskSyncCommand.java @@ -19,6 +19,8 @@ package net.william278.husksync.command; +import com.mojang.brigadier.context.CommandContext; +import com.mojang.brigadier.exceptions.CommandSyntaxException; import de.themoep.minedown.adventure.MineDown; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.JoinConfiguration; @@ -31,229 +33,219 @@ import net.william278.husksync.HuskSync; import net.william278.husksync.database.Database; import net.william278.husksync.migrator.Migrator; import net.william278.husksync.user.CommandUser; -import net.william278.husksync.user.OnlineUser; +import net.william278.uniform.BaseCommand; +import net.william278.uniform.CommandProvider; +import net.william278.uniform.Permission; +import net.william278.uniform.element.ArgumentElement; import org.apache.commons.text.WordUtils; import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import java.util.*; +import java.util.Arrays; +import java.util.List; import java.util.function.Function; import java.util.logging.Level; import java.util.stream.Collectors; -public class HuskSyncCommand extends Command implements TabProvider { - - private static final Map SUB_COMMANDS = Map.of( - "about", false, - "status", true, - "reload", true, - "migrate", true, - "update", true - ); +public class HuskSyncCommand extends PluginCommand { private final UpdateChecker updateChecker; private final AboutMenu aboutMenu; public HuskSyncCommand(@NotNull HuskSync plugin) { - super("husksync", List.of(), "[" + String.join("|", SUB_COMMANDS.keySet()) + "]", plugin); - addAdditionalPermissions(SUB_COMMANDS); - + super("husksync", List.of(), Permission.Default.TRUE, plugin); this.updateChecker = plugin.getUpdateChecker(); this.aboutMenu = AboutMenu.builder() - .title(Component.text("HuskSync")) - .description(Component.text("A modern, cross-server player data synchronization system")) - .version(plugin.getPluginVersion()) - .credits("Author", - AboutMenu.Credit.of("William278").description("Click to visit website").url("https://william278.net")) - .credits("Contributors", - AboutMenu.Credit.of("HarvelsX").description("Code"), - AboutMenu.Credit.of("HookWoods").description("Code"), - AboutMenu.Credit.of("Preva1l").description("Code"), - AboutMenu.Credit.of("hanbings").description("Code (Fabric porting)"), - AboutMenu.Credit.of("Stampede2011").description("Code (Fabric mixins)")) - .credits("Translators", - AboutMenu.Credit.of("Namiu").description("Japanese (ja-jp)"), - AboutMenu.Credit.of("anchelthe").description("Spanish (es-es)"), - AboutMenu.Credit.of("Melonzio").description("Spanish (es-es)"), - AboutMenu.Credit.of("Ceddix").description("German (de-de)"), - AboutMenu.Credit.of("Pukejoy_1").description("Bulgarian (bg-bg)"), - AboutMenu.Credit.of("mateusneresrb").description("Brazilian Portuguese (pt-br)"), - AboutMenu.Credit.of("小蔡").description("Traditional Chinese (zh-tw)"), - AboutMenu.Credit.of("Ghost-chu").description("Simplified Chinese (zh-cn)"), - AboutMenu.Credit.of("DJelly4K").description("Simplified Chinese (zh-cn)"), - AboutMenu.Credit.of("Thourgard").description("Ukrainian (uk-ua)"), - AboutMenu.Credit.of("xF3d3").description("Italian (it-it)"), - AboutMenu.Credit.of("cada3141").description("Korean (ko-kr)"), - AboutMenu.Credit.of("Wirayuda5620").description("Indonesian (id-id)"), - AboutMenu.Credit.of("WinTone01").description("Turkish (tr-tr)"), - AboutMenu.Credit.of("IbanEtchep").description("French (fr-fr)")) - .buttons( - AboutMenu.Link.of("https://william278.net/docs/husksync").text("Documentation").icon("⛏"), - AboutMenu.Link.of("https://github.com/WiIIiam278/HuskSync/issues").text("Issues").icon("❌").color(TextColor.color(0xff9f0f)), - AboutMenu.Link.of("https://discord.gg/tVYhJfyDWG").text("Discord").icon("⭐").color(TextColor.color(0x6773f5))) - .build(); + .title(Component.text("HuskSync")) + .description(Component.text("A modern, cross-server player data synchronization system")) + .version(plugin.getPluginVersion()) + .credits("Author", + AboutMenu.Credit.of("William278").description("Click to visit website").url("https://william278.net")) + .credits("Contributors", + AboutMenu.Credit.of("HarvelsX").description("Code"), + AboutMenu.Credit.of("HookWoods").description("Code"), + AboutMenu.Credit.of("Preva1l").description("Code"), + AboutMenu.Credit.of("hanbings").description("Code (Fabric porting)"), + AboutMenu.Credit.of("Stampede2011").description("Code (Fabric mixins)")) + .credits("Translators", + AboutMenu.Credit.of("Namiu").description("Japanese (ja-jp)"), + AboutMenu.Credit.of("anchelthe").description("Spanish (es-es)"), + AboutMenu.Credit.of("Melonzio").description("Spanish (es-es)"), + AboutMenu.Credit.of("Ceddix").description("German (de-de)"), + AboutMenu.Credit.of("Pukejoy_1").description("Bulgarian (bg-bg)"), + AboutMenu.Credit.of("mateusneresrb").description("Brazilian Portuguese (pt-br)"), + AboutMenu.Credit.of("小蔡").description("Traditional Chinese (zh-tw)"), + AboutMenu.Credit.of("Ghost-chu").description("Simplified Chinese (zh-cn)"), + AboutMenu.Credit.of("DJelly4K").description("Simplified Chinese (zh-cn)"), + AboutMenu.Credit.of("Thourgard").description("Ukrainian (uk-ua)"), + AboutMenu.Credit.of("xF3d3").description("Italian (it-it)"), + AboutMenu.Credit.of("cada3141").description("Korean (ko-kr)"), + AboutMenu.Credit.of("Wirayuda5620").description("Indonesian (id-id)"), + AboutMenu.Credit.of("WinTone01").description("Turkish (tr-tr)"), + AboutMenu.Credit.of("IbanEtchep").description("French (fr-fr)")) + .buttons( + AboutMenu.Link.of("https://william278.net/docs/husksync").text("Documentation").icon("⛏"), + AboutMenu.Link.of("https://github.com/WiIIiam278/HuskSync/issues").text("Issues").icon("❌").color(TextColor.color(0xff9f0f)), + AboutMenu.Link.of("https://discord.gg/tVYhJfyDWG").text("Discord").icon("⭐").color(TextColor.color(0x6773f5))) + .build(); } @Override - public void execute(@NotNull CommandUser executor, @NotNull String[] args) { - final String subCommand = parseStringArg(args, 0).orElse("about").toLowerCase(Locale.ENGLISH); - if (SUB_COMMANDS.containsKey(subCommand) && !executor.hasPermission(getPermission(subCommand))) { - plugin.getLocales().getLocale("error_no_permission") - .ifPresent(executor::sendMessage); - return; - } + public void provide(@NotNull BaseCommand command) { + command.setDefaultExecutor((ctx) -> about(command, ctx)); + command.addSubCommand("about", (sub) -> sub.setDefaultExecutor((ctx) -> about(command, ctx))); + command.addSubCommand("status", needsOp("status"), status()); + command.addSubCommand("reload", needsOp("reload"), reload()); + command.addSubCommand("update", needsOp("update"), update()); + command.addSubCommand("migrate", migrate()); + } + + private void about(@NotNull BaseCommand c, @NotNull CommandContext ctx) { + user(c, ctx).getAudience().sendMessage(aboutMenu.toComponent()); + } + + @NotNull + private CommandProvider status() { + return (sub) -> sub.setDefaultExecutor((ctx) -> { + final CommandUser user = user(sub, ctx); + plugin.getLocales().getLocale("system_status_header").ifPresent(user::sendMessage); + user.sendMessage(Component.join( + JoinConfiguration.newlines(), + Arrays.stream(StatusLine.values()).map(s -> s.get(plugin)).toList() + )); + }); + } - switch (subCommand) { - case "about" -> executor.sendMessage(aboutMenu.toComponent()); - case "status" -> { - getPlugin().getLocales().getLocale("system_status_header").ifPresent(executor::sendMessage); - executor.sendMessage(Component.join( - JoinConfiguration.newlines(), - Arrays.stream(StatusLine.values()).map(s -> s.get(plugin)).toList() + @NotNull + private CommandProvider reload() { + return (sub) -> sub.setDefaultExecutor((ctx) -> { + final CommandUser user = user(sub, ctx); + try { + plugin.loadSettings(); + plugin.loadLocales(); + plugin.loadServer(); + plugin.getLocales().getLocale("reload_complete").ifPresent(user::sendMessage); + } catch (Throwable e) { + user.sendMessage(new MineDown( + "[Error:](#ff3300) [Failed to reload the plugin. Check console for errors.](#ff7e5e)" )); + plugin.log(Level.SEVERE, "Failed to reload the plugin", e); } - case "reload" -> { - try { - plugin.loadSettings(); - plugin.loadLocales(); - plugin.loadServer(); - plugin.getLocales().getLocale("reload_complete").ifPresent(executor::sendMessage); - } catch (Throwable e) { - executor.sendMessage(new MineDown( - "[Error:](#ff3300) [Failed to reload the plugin. Check console for errors.](#ff7e5e)" - )); - plugin.log(Level.SEVERE, "Failed to reload the plugin", e); - } - } - case "migrate" -> { - if (executor instanceof OnlineUser) { - plugin.getLocales().getLocale("error_console_command_only") - .ifPresent(executor::sendMessage); - return; - } - this.handleMigrationCommand(args); - } - case "update" -> updateChecker.check().thenAccept(checked -> { - if (checked.isUpToDate()) { - plugin.getLocales().getLocale("up_to_date", plugin.getPluginVersion().toString()) - .ifPresent(executor::sendMessage); - return; - } - plugin.getLocales().getLocale("update_available", checked.getLatestVersion().toString(), - plugin.getPluginVersion().toString()).ifPresent(executor::sendMessage); - }); - default -> plugin.getLocales().getLocale("error_invalid_syntax", getUsage()) - .ifPresent(executor::sendMessage); - } + }); } - // Handle a migration console command input - private void handleMigrationCommand(@NotNull String[] args) { - if (args.length < 2) { - plugin.log(Level.INFO, - "Please choose a migrator, then run \"husksync migrate \""); - this.logMigratorList(); - return; - } - - final Optional selectedMigrator = plugin.getAvailableMigrators().stream() - .filter(available -> available.getIdentifier().equalsIgnoreCase(args[1])) - .findFirst(); - selectedMigrator.ifPresentOrElse(migrator -> { - if (args.length < 3) { - plugin.log(Level.INFO, migrator.getHelpMenu()); + @NotNull + private CommandProvider update() { + return (sub) -> sub.setDefaultExecutor((ctx) -> updateChecker.check().thenAccept(checked -> { + final CommandUser user = user(sub, ctx); + if (checked.isUpToDate()) { + plugin.getLocales().getLocale("up_to_date", plugin.getPluginVersion().toString()) + .ifPresent(user::sendMessage); return; } - switch (args[2]) { - case "start" -> migrator.start().thenAccept(succeeded -> { + plugin.getLocales().getLocale("update_available", checked.getLatestVersion().toString(), + plugin.getPluginVersion().toString()).ifPresent(user::sendMessage); + })); + } + + @NotNull + private CommandProvider migrate() { + return (sub) -> { + sub.setCondition((ctx) -> sub.getUser(ctx).isConsole()); + sub.setDefaultExecutor((ctx) -> { + plugin.log(Level.INFO, "Please choose a migrator, then run \"husksync migrate \""); + plugin.log(Level.INFO, String.format( + "List of available migrators:\nMigrator ID / Migrator Name:\n%s", + plugin.getAvailableMigrators().stream() + .map(migrator -> String.format("%s - %s", migrator.getIdentifier(), migrator.getName())) + .collect(Collectors.joining("\n")) + )); + }); + sub.addSubCommand("start", (start) -> start.addSyntax((cmd) -> { + final Migrator migrator = cmd.getArgument("migrator", Migrator.class); + migrator.start().thenAccept(succeeded -> { if (succeeded) { plugin.log(Level.INFO, "Migration completed successfully!"); } else { plugin.log(Level.WARNING, "Migration failed!"); } }); - case "set" -> migrator.handleConfigurationCommand(Arrays.copyOfRange(args, 3, args.length)); - default -> plugin.log(Level.INFO, String.format( - "Invalid syntax. Console usage: \"husksync migrate %s ", args[1] - )); - } - }, () -> { - plugin.log(Level.INFO, - "Please specify a valid migrator.\n" + - "If a migrator is not available, please verify that you meet the prerequisites to use it."); - this.logMigratorList(); - }); - } - - // Log the list of available migrators - private void logMigratorList() { - plugin.log(Level.INFO, String.format( - "List of available migrators:\nMigrator ID / Migrator Name:\n%s", - plugin.getAvailableMigrators().stream() - .map(migrator -> String.format("%s - %s", migrator.getIdentifier(), migrator.getName())) - .collect(Collectors.joining("\n")) - )); + }, migrator())); + sub.addSubCommand("set", (set) -> set.addSyntax((cmd) -> { + final Migrator migrator = cmd.getArgument("migrator", Migrator.class); + final String[] args = cmd.getArgument("args", String.class).split(" "); + migrator.handleConfigurationCommand(Arrays.copyOfRange(args, 3, args.length)); + }, migrator(), BaseCommand.greedyString("args"))); + }; } - @Nullable - @Override - public List suggest(@NotNull CommandUser user, @NotNull String[] args) { - return switch (args.length) { - case 0, 1 -> SUB_COMMANDS.keySet().stream().sorted().toList(); - default -> null; - }; + @NotNull + private ArgumentElement migrator() { + return new ArgumentElement<>("migrator", reader -> { + final String id = reader.readString(); + final Migrator migrator = plugin.getAvailableMigrators().stream() + .filter(m -> m.getIdentifier().equalsIgnoreCase(id)).findFirst().orElse(null); + if (migrator == null) { + throw CommandSyntaxException.BUILT_IN_EXCEPTIONS.dispatcherUnknownArgument().createWithContext(reader); + } + return migrator; + }, (context, builder) -> { + for (Migrator material : plugin.getAvailableMigrators()) { + builder.suggest(material.getIdentifier()); + } + return builder.buildFuture(); + }); } private enum StatusLine { PLUGIN_VERSION(plugin -> Component.text("v" + plugin.getPluginVersion().toStringWithoutMetadata()) - .appendSpace().append(plugin.getPluginVersion().getMetadata().isBlank() ? Component.empty() - : Component.text("(build " + plugin.getPluginVersion().getMetadata() + ")"))), + .appendSpace().append(plugin.getPluginVersion().getMetadata().isBlank() ? Component.empty() + : Component.text("(build " + plugin.getPluginVersion().getMetadata() + ")"))), PLATFORM_TYPE(plugin -> Component.text(WordUtils.capitalizeFully(plugin.getPlatformType()))), LANGUAGE(plugin -> Component.text(plugin.getSettings().getLanguage())), MINECRAFT_VERSION(plugin -> Component.text(plugin.getMinecraftVersion().toString())), JAVA_VERSION(plugin -> Component.text(System.getProperty("java.version"))), JAVA_VENDOR(plugin -> Component.text(System.getProperty("java.vendor"))), SYNC_MODE(plugin -> Component.text(WordUtils.capitalizeFully( - plugin.getSettings().getSynchronization().getMode().toString() + plugin.getSettings().getSynchronization().getMode().toString() ))), DELAY_LATENCY(plugin -> Component.text( - plugin.getSettings().getSynchronization().getNetworkLatencyMilliseconds() + "ms" + plugin.getSettings().getSynchronization().getNetworkLatencyMilliseconds() + "ms" )), SERVER_NAME(plugin -> Component.text(plugin.getServerName())), CLUSTER_ID(plugin -> Component.text(plugin.getSettings().getClusterId().isBlank() ? "None" : plugin.getSettings().getClusterId())), DATABASE_TYPE(plugin -> - Component.text(plugin.getSettings().getDatabase().getType().getDisplayName() + - (plugin.getSettings().getDatabase().getType() == Database.Type.MONGO ? - (plugin.getSettings().getDatabase().getMongoSettings().isUsingAtlas() ? " Atlas" : "") : "")) + Component.text(plugin.getSettings().getDatabase().getType().getDisplayName() + + (plugin.getSettings().getDatabase().getType() == Database.Type.MONGO ? + (plugin.getSettings().getDatabase().getMongoSettings().isUsingAtlas() ? " Atlas" : "") : "")) ), IS_DATABASE_LOCAL(plugin -> getLocalhostBoolean(plugin.getSettings().getDatabase().getCredentials().getHost())), USING_REDIS_SENTINEL(plugin -> getBoolean( - !plugin.getSettings().getRedis().getSentinel().getMaster().isBlank() + !plugin.getSettings().getRedis().getSentinel().getMaster().isBlank() )), USING_REDIS_PASSWORD(plugin -> getBoolean( - !plugin.getSettings().getRedis().getCredentials().getPassword().isBlank() + !plugin.getSettings().getRedis().getCredentials().getPassword().isBlank() )), REDIS_USING_SSL(plugin -> getBoolean( - plugin.getSettings().getRedis().getCredentials().isUseSsl() + plugin.getSettings().getRedis().getCredentials().isUseSsl() )), IS_REDIS_LOCAL(plugin -> getLocalhostBoolean( - plugin.getSettings().getRedis().getCredentials().getHost() + plugin.getSettings().getRedis().getCredentials().getHost() )), DATA_TYPES(plugin -> Component.join( - JoinConfiguration.commas(true), - plugin.getRegisteredDataTypes().stream().map(i -> Component.textOfChildren(Component.text(i.toString()) - .appendSpace().append(Component.text(i.isEnabled() ? '✔' : '❌'))) - .color(i.isEnabled() ? NamedTextColor.GREEN : NamedTextColor.RED) - .hoverEvent(HoverEvent.showText( - Component.text(i.isEnabled() ? "Enabled" : "Disabled") - .append(Component.newline()) - .append(Component.text("Dependencies: %s".formatted(i.getDependencies() - .isEmpty() ? "(None)" : i.getDependencies().stream() - .map(d -> "%s (%s)".formatted( - d.getKey().value(), d.isRequired() ? "Required" : "Optional" - )).collect(Collectors.joining(", "))) - ).color(NamedTextColor.GRAY)) - ))).toList() + JoinConfiguration.commas(true), + plugin.getRegisteredDataTypes().stream().map(i -> Component.textOfChildren(Component.text(i.toString()) + .appendSpace().append(Component.text(i.isEnabled() ? '✔' : '❌'))) + .color(i.isEnabled() ? NamedTextColor.GREEN : NamedTextColor.RED) + .hoverEvent(HoverEvent.showText( + Component.text(i.isEnabled() ? "Enabled" : "Disabled") + .append(Component.newline()) + .append(Component.text("Dependencies: %s".formatted(i.getDependencies() + .isEmpty() ? "(None)" : i.getDependencies().stream() + .map(d -> "%s (%s)".formatted( + d.getKey().value(), d.isRequired() ? "Required" : "Optional" + )).collect(Collectors.joining(", "))) + ).color(NamedTextColor.GRAY)) + ))).toList() )); private final Function supplier; @@ -265,13 +257,13 @@ public class HuskSyncCommand extends Command implements TabProvider { @NotNull private Component get(@NotNull HuskSync plugin) { return Component - .text("•").appendSpace() - .append(Component.text( - WordUtils.capitalizeFully(name().replaceAll("_", " ")), - TextColor.color(0x848484) - )) - .append(Component.text(':')).append(Component.space().color(NamedTextColor.WHITE)) - .append(supplier.apply(plugin)); + .text("•").appendSpace() + .append(Component.text( + WordUtils.capitalizeFully(name().replaceAll("_", " ")), + TextColor.color(0x848484) + )) + .append(Component.text(':')).append(Component.space().color(NamedTextColor.WHITE)) + .append(supplier.apply(plugin)); } @NotNull @@ -282,7 +274,7 @@ public class HuskSyncCommand extends Command implements TabProvider { @NotNull private static Component getLocalhostBoolean(@NotNull String value) { return getBoolean(value.equals("127.0.0.1") || value.equals("0.0.0.0") - || value.equals("localhost") || value.equals("::1")); + || value.equals("localhost") || value.equals("::1")); } } diff --git a/common/src/main/java/net/william278/husksync/command/InventoryCommand.java b/common/src/main/java/net/william278/husksync/command/InventoryCommand.java index 5dc3847d..da685202 100644 --- a/common/src/main/java/net/william278/husksync/command/InventoryCommand.java +++ b/common/src/main/java/net/william278/husksync/command/InventoryCommand.java @@ -37,7 +37,7 @@ import java.util.Optional; public class InventoryCommand extends ItemsCommand { public InventoryCommand(@NotNull HuskSync plugin) { - super(plugin, List.of("inventory", "invsee", "openinv")); + super("inventory", List.of("invsee", "openinv"), plugin); } @Override @@ -47,29 +47,29 @@ public class InventoryCommand extends ItemsCommand { if (optionalInventory.isEmpty()) { viewer.sendMessage(new MineDown("what the FUCK is happening")); plugin.getLocales().getLocale("error_no_data_to_display") - .ifPresent(viewer::sendMessage); + .ifPresent(viewer::sendMessage); return; } // Display opening message plugin.getLocales().getLocale("inventory_viewer_opened", user.getUsername(), - snapshot.getTimestamp().format(DateTimeFormatter - .ofLocalizedDateTime(FormatStyle.MEDIUM, FormatStyle.SHORT))) - .ifPresent(viewer::sendMessage); + snapshot.getTimestamp().format(DateTimeFormatter + .ofLocalizedDateTime(FormatStyle.MEDIUM, FormatStyle.SHORT))) + .ifPresent(viewer::sendMessage); // Show GUI final Data.Items.Inventory inventory = optionalInventory.get(); viewer.showGui( - inventory, - plugin.getLocales().getLocale("inventory_viewer_menu_title", user.getUsername()) - .orElse(new MineDown(String.format("%s's Inventory", user.getUsername()))), - allowEdit, - inventory.getSlotCount(), - (itemsOnClose) -> { - if (allowEdit && !inventory.equals(itemsOnClose)) { - plugin.runAsync(() -> this.updateItems(viewer, itemsOnClose, user)); - } + inventory, + plugin.getLocales().getLocale("inventory_viewer_menu_title", user.getUsername()) + .orElse(new MineDown(String.format("%s's Inventory", user.getUsername()))), + allowEdit, + inventory.getSlotCount(), + (itemsOnClose) -> { + if (allowEdit && !inventory.equals(itemsOnClose)) { + plugin.runAsync(() -> this.updateItems(viewer, itemsOnClose, user)); } + } ); } @@ -79,7 +79,7 @@ public class InventoryCommand extends ItemsCommand { final Optional latestData = plugin.getDatabase().getLatestSnapshot(holder); if (latestData.isEmpty()) { plugin.getLocales().getLocale("error_no_data_to_display") - .ifPresent(viewer::sendMessage); + .ifPresent(viewer::sendMessage); return; } @@ -89,7 +89,7 @@ public class InventoryCommand extends ItemsCommand { data.getInventory().ifPresent(inventory -> inventory.setContents(items)); data.setSaveCause(DataSnapshot.SaveCause.INVENTORY_COMMAND); data.setPinned( - plugin.getSettings().getSynchronization().doAutoPin(DataSnapshot.SaveCause.INVENTORY_COMMAND) + plugin.getSettings().getSynchronization().doAutoPin(DataSnapshot.SaveCause.INVENTORY_COMMAND) ); }); diff --git a/common/src/main/java/net/william278/husksync/command/ItemsCommand.java b/common/src/main/java/net/william278/husksync/command/ItemsCommand.java index 0ebd1cf6..b088d311 100644 --- a/common/src/main/java/net/william278/husksync/command/ItemsCommand.java +++ b/common/src/main/java/net/william278/husksync/command/ItemsCommand.java @@ -24,102 +24,90 @@ import net.william278.husksync.data.DataSnapshot; import net.william278.husksync.user.CommandUser; import net.william278.husksync.user.OnlineUser; import net.william278.husksync.user.User; +import net.william278.uniform.BaseCommand; +import net.william278.uniform.Permission; import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; import java.util.List; -import java.util.Map; import java.util.Optional; import java.util.UUID; -public abstract class ItemsCommand extends Command implements TabProvider { +public abstract class ItemsCommand extends PluginCommand { - protected ItemsCommand(@NotNull HuskSync plugin, @NotNull List aliases) { - super(aliases.get(0), aliases.subList(1, aliases.size()), " [version_uuid]", plugin); - setOperatorCommand(true); - addAdditionalPermissions(Map.of("edit", true)); + protected ItemsCommand(@NotNull String name, @NotNull List aliases, @NotNull HuskSync plugin) { + super(name, aliases, Permission.Default.IF_OP, plugin); } - @Override - public void execute(@NotNull CommandUser executor, @NotNull String[] args) { - if (!(executor instanceof OnlineUser player)) { - plugin.getLocales().getLocale("error_in_game_command_only") + public void provide(@NotNull BaseCommand command) { + command.addSyntax((ctx) -> { + final User user = ctx.getArgument("username", User.class); + final UUID version = ctx.getArgument("version", UUID.class); + final CommandUser executor = user(command, ctx); + if (!(executor instanceof OnlineUser online)) { + plugin.getLocales().getLocale("error_in_game_command_only") .ifPresent(executor::sendMessage); - return; - } - - // Find the user to view the items for - final Optional optionalUser = parseStringArg(args, 0) - .flatMap(name -> plugin.getDatabase().getUserByName(name)); - if (optionalUser.isEmpty()) { - plugin.getLocales().getLocale( - args.length >= 1 ? "error_invalid_player" : "error_invalid_syntax", getUsage() - ).ifPresent(player::sendMessage); - return; - } - - // Show the user data - final User user = optionalUser.get(); - parseUUIDArg(args, 1).ifPresentOrElse( - version -> this.showSnapshotItems(player, user, version), - () -> this.showLatestItems(player, user) - ); + return; + } + this.showSnapshotItems(online, user, version); + }, user("username"), uuid("version")); + command.addSyntax((ctx) -> { + final User user = ctx.getArgument("username", User.class); + final CommandUser executor = user(command, ctx); + if (!(executor instanceof OnlineUser online)) { + plugin.getLocales().getLocale("error_in_game_command_only") + .ifPresent(executor::sendMessage); + return; + } + this.showLatestItems(online, user); + }, user("username")); } // View (and edit) the latest user data private void showLatestItems(@NotNull OnlineUser viewer, @NotNull User user) { plugin.getRedisManager().getUserData(user.getUuid(), user).thenAccept(data -> data - .or(() -> plugin.getDatabase().getLatestSnapshot(user)) - .or(() -> { - plugin.getLocales().getLocale("error_no_data_to_display") - .ifPresent(viewer::sendMessage); + .or(() -> plugin.getDatabase().getLatestSnapshot(user)) + .or(() -> { + plugin.getLocales().getLocale("error_no_data_to_display") + .ifPresent(viewer::sendMessage); + return Optional.empty(); + }) + .flatMap(packed -> { + if (packed.isInvalid()) { + plugin.getLocales().getLocale("error_invalid_data", packed.getInvalidReason(plugin)) + .ifPresent(viewer::sendMessage); return Optional.empty(); - }) - .flatMap(packed -> { - if (packed.isInvalid()) { - plugin.getLocales().getLocale("error_invalid_data", packed.getInvalidReason(plugin)) - .ifPresent(viewer::sendMessage); - return Optional.empty(); - } - return Optional.of(packed.unpack(plugin)); - }) - .ifPresent(snapshot -> this.showItems( - viewer, snapshot, user, viewer.hasPermission(getPermission("edit")) - ))); + } + return Optional.of(packed.unpack(plugin)); + }) + .ifPresent(snapshot -> this.showItems( + viewer, snapshot, user, viewer.hasPermission(getPermission("edit")) + ))); } // View a specific version of the user data private void showSnapshotItems(@NotNull OnlineUser viewer, @NotNull User user, @NotNull UUID version) { plugin.getDatabase().getSnapshot(user, version) - .or(() -> { - plugin.getLocales().getLocale("error_invalid_version_uuid") - .ifPresent(viewer::sendMessage); + .or(() -> { + plugin.getLocales().getLocale("error_invalid_version_uuid") + .ifPresent(viewer::sendMessage); + return Optional.empty(); + }) + .flatMap(packed -> { + if (packed.isInvalid()) { + plugin.getLocales().getLocale("error_invalid_data", packed.getInvalidReason(plugin)) + .ifPresent(viewer::sendMessage); return Optional.empty(); - }) - .flatMap(packed -> { - if (packed.isInvalid()) { - plugin.getLocales().getLocale("error_invalid_data", packed.getInvalidReason(plugin)) - .ifPresent(viewer::sendMessage); - return Optional.empty(); - } - return Optional.of(packed.unpack(plugin)); - }) - .ifPresent(snapshot -> this.showItems( - viewer, snapshot, user, false - )); + } + return Optional.of(packed.unpack(plugin)); + }) + .ifPresent(snapshot -> this.showItems( + viewer, snapshot, user, false + )); } // Show a GUI menu with the correct item data from the snapshot protected abstract void showItems(@NotNull OnlineUser viewer, @NotNull DataSnapshot.Unpacked snapshot, @NotNull User user, boolean allowEdit); - @Nullable - @Override - public List suggest(@NotNull CommandUser executor, @NotNull String[] args) { - return switch (args.length) { - case 0, 1 -> plugin.getOnlineUsers().stream().map(User::getUsername).toList(); - default -> null; - }; - } } diff --git a/common/src/main/java/net/william278/husksync/command/Node.java b/common/src/main/java/net/william278/husksync/command/Node.java deleted file mode 100644 index 86635e2f..00000000 --- a/common/src/main/java/net/william278/husksync/command/Node.java +++ /dev/null @@ -1,105 +0,0 @@ -/* - * This file is part of HuskSync, licensed under the Apache License 2.0. - * - * Copyright (c) William278 - * Copyright (c) contributors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package net.william278.husksync.command; - -import net.william278.husksync.HuskSync; -import org.jetbrains.annotations.NotNull; - -import java.util.List; -import java.util.Optional; -import java.util.StringJoiner; -import java.util.UUID; - -public abstract class Node implements Executable { - - protected static final String PERMISSION_PREFIX = "husksync.command"; - - protected final HuskSync plugin; - private final String name; - private final List aliases; - private boolean operatorCommand = false; - - protected Node(@NotNull String name, @NotNull List aliases, @NotNull HuskSync plugin) { - if (name.isBlank()) { - throw new IllegalArgumentException("Command name cannot be blank"); - } - this.name = name; - this.aliases = aliases; - this.plugin = plugin; - } - - @NotNull - public String getName() { - return name; - } - - @NotNull - public List getAliases() { - return aliases; - } - - @NotNull - public String getPermission(@NotNull String... child) { - final StringJoiner joiner = new StringJoiner(".") - .add(PERMISSION_PREFIX) - .add(getName()); - for (final String node : child) { - joiner.add(node); - } - return joiner.toString().trim(); - } - - public boolean isOperatorCommand() { - return operatorCommand; - } - - public void setOperatorCommand(boolean operatorCommand) { - this.operatorCommand = operatorCommand; - } - - protected Optional parseStringArg(@NotNull String[] args, int index) { - if (args.length > index) { - return Optional.of(args[index]); - } - return Optional.empty(); - } - - protected Optional parseIntArg(@NotNull String[] args, int index) { - return parseStringArg(args, index).flatMap(arg -> { - try { - return Optional.of(Integer.parseInt(arg)); - } catch (NumberFormatException e) { - return Optional.empty(); - } - }); - } - - protected Optional parseUUIDArg(@NotNull String[] args, int index) { - return parseStringArg(args, index).flatMap(arg -> { - try { - return Optional.of(UUID.fromString(arg)); - } catch (IllegalArgumentException e) { - return Optional.empty(); - } - }); - } - - -} \ No newline at end of file diff --git a/common/src/main/java/net/william278/husksync/command/PluginCommand.java b/common/src/main/java/net/william278/husksync/command/PluginCommand.java new file mode 100644 index 00000000..4419256d --- /dev/null +++ b/common/src/main/java/net/william278/husksync/command/PluginCommand.java @@ -0,0 +1,127 @@ +/* + * This file is part of HuskSync, licensed under the Apache License 2.0. + * + * Copyright (c) William278 + * Copyright (c) contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.william278.husksync.command; + +import com.mojang.brigadier.context.CommandContext; +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import net.william278.husksync.HuskSync; +import net.william278.husksync.user.CommandUser; +import net.william278.husksync.user.User; +import net.william278.uniform.BaseCommand; +import net.william278.uniform.Command; +import net.william278.uniform.Permission; +import net.william278.uniform.element.ArgumentElement; +import org.jetbrains.annotations.NotNull; + +import java.util.Arrays; +import java.util.List; +import java.util.UUID; +import java.util.function.Function; + +public abstract class PluginCommand extends Command { + + protected final HuskSync plugin; + + protected PluginCommand(@NotNull String name, @NotNull List aliases, + @NotNull Permission.Default permissionDefault, @NotNull HuskSync plugin) { + super(name, aliases, getDescription(plugin, name), new Permission(createPermission(name), permissionDefault)); + this.plugin = plugin; + } + + private static String getDescription(@NotNull HuskSync plugin, @NotNull String name) { + return plugin.getLocales().getRawLocale("%s_command_description".formatted(name)).orElse(""); + } + + @NotNull + private static String createPermission(@NotNull String name, @NotNull String... sub) { + return "husksync.command." + name + (sub.length > 0 ? "." + String.join(".", sub) : ""); + } + + @NotNull + protected String getPermission(@NotNull String... sub) { + return createPermission(this.getName(), sub); + } + + @NotNull + @SuppressWarnings("rawtypes") + protected CommandUser user(@NotNull BaseCommand base, @NotNull CommandContext context) { + return adapt(base.getUser(context.getSource())); + } + + @NotNull + protected Permission needsOp(@NotNull String... nodes) { + return new Permission(getPermission(nodes), Permission.Default.IF_OP); + } + + @NotNull + protected CommandUser adapt(net.william278.uniform.CommandUser user) { + return user.getUuid() == null ? plugin.getConsole() : plugin.getOnlineUser(user.getUuid()).orElseThrow(); + } + + @NotNull + protected ArgumentElement user(@NotNull String name) { + return new ArgumentElement<>(name, reader -> { + final String username = reader.readString(); + return plugin.getDatabase().getUserByName(username).orElseThrow( + () -> CommandSyntaxException.BUILT_IN_EXCEPTIONS.dispatcherUnknownArgument().createWithContext(reader) + ); + }, (context, builder) -> { + plugin.getOnlineUsers().forEach(u -> builder.suggest(u.getUsername())); + return builder.buildFuture(); + }); + } + + @NotNull + protected ArgumentElement uuid(@NotNull String name) { + return new ArgumentElement<>(name, reader -> { + try { + return UUID.fromString(reader.readString()); + } catch (IllegalArgumentException e) { + throw CommandSyntaxException.BUILT_IN_EXCEPTIONS.dispatcherUnknownArgument().createWithContext(reader); + } + }, (context, builder) -> builder.buildFuture()); + } + + public enum Type { + + HUSKSYNC_COMMAND(HuskSyncCommand::new), + USERDATA_COMMAND(UserDataCommand::new), + INVENTORY_COMMAND(InventoryCommand::new), + ENDER_CHEST_COMMAND(EnderChestCommand::new); + + public final Function commandSupplier; + + Type(@NotNull Function supplier) { + this.commandSupplier = supplier; + } + + @NotNull + public PluginCommand supply(@NotNull HuskSync plugin) { + return commandSupplier.apply(plugin); + } + + @NotNull + public static PluginCommand[] create(@NotNull HuskSync plugin) { + return Arrays.stream(values()).map(type -> type.supply(plugin)).toArray(PluginCommand[]::new); + } + + } + +} \ No newline at end of file diff --git a/common/src/main/java/net/william278/husksync/command/TabProvider.java b/common/src/main/java/net/william278/husksync/command/TabProvider.java deleted file mode 100644 index 48c2b72b..00000000 --- a/common/src/main/java/net/william278/husksync/command/TabProvider.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * This file is part of HuskSync, licensed under the Apache License 2.0. - * - * Copyright (c) William278 - * Copyright (c) contributors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package net.william278.husksync.command; - -import net.william278.husksync.user.CommandUser; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.util.List; - -public interface TabProvider { - - @Nullable - List suggest(@NotNull CommandUser user, @NotNull String[] args); - - @NotNull - default List getSuggestions(@NotNull CommandUser user, @NotNull String[] args) { - List suggestions = suggest(user, args); - if (suggestions == null) { - suggestions = List.of(); - } - return filter(suggestions, args); - } - - @NotNull - default List filter(@NotNull List suggestions, @NotNull String[] args) { - return suggestions.stream() - .filter(suggestion -> args.length == 0 || suggestion.toLowerCase() - .startsWith(args[args.length - 1].toLowerCase().trim())) - .toList(); - } - -} \ No newline at end of file diff --git a/common/src/main/java/net/william278/husksync/command/UserDataCommand.java b/common/src/main/java/net/william278/husksync/command/UserDataCommand.java index 61d64f71..0106aae3 100644 --- a/common/src/main/java/net/william278/husksync/command/UserDataCommand.java +++ b/common/src/main/java/net/william278/husksync/command/UserDataCommand.java @@ -19,6 +19,7 @@ package net.william278.husksync.command; +import com.mojang.brigadier.exceptions.CommandSyntaxException; import net.william278.husksync.HuskSync; import net.william278.husksync.data.DataSnapshot; import net.william278.husksync.redis.RedisKeyType; @@ -28,116 +29,65 @@ import net.william278.husksync.user.User; import net.william278.husksync.util.DataDumper; import net.william278.husksync.util.DataSnapshotList; import net.william278.husksync.util.DataSnapshotOverview; +import net.william278.uniform.BaseCommand; +import net.william278.uniform.CommandProvider; +import net.william278.uniform.Permission; +import net.william278.uniform.element.ArgumentElement; import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import java.util.*; +import java.util.List; +import java.util.Locale; +import java.util.Optional; +import java.util.UUID; import java.util.logging.Level; -public class UserDataCommand extends Command implements TabProvider { - - private static final Map SUB_COMMANDS = Map.of( - "view", false, - "list", false, - "delete", true, - "restore", true, - "pin", true, - "dump", true - ); +public class UserDataCommand extends PluginCommand { public UserDataCommand(@NotNull HuskSync plugin) { - super("userdata", List.of("playerdata"), String.format( - "<%s> [username] [version_uuid]", String.join("/", SUB_COMMANDS.keySet()) - ), plugin); - setOperatorCommand(true); - addAdditionalPermissions(SUB_COMMANDS); + super("userdata", List.of("playerdata"), Permission.Default.IF_OP, plugin); } @Override - public void execute(@NotNull CommandUser executor, @NotNull String[] args) { - final String subCommand = parseStringArg(args, 0).orElse("view").toLowerCase(Locale.ENGLISH); - final Optional optionalUser = parseStringArg(args, 1) - .flatMap(name -> plugin.getDatabase().getUserByName(name)) - .or(() -> parseStringArg(args, 0).flatMap(name -> plugin.getDatabase().getUserByName(name))) - .or(() -> args.length < 2 && executor instanceof User userExecutor - ? Optional.of(userExecutor) : Optional.empty()); - final Optional uuid = parseUUIDArg(args, 2).or(() -> parseUUIDArg(args, 1)); - if (optionalUser.isEmpty()) { - plugin.getLocales().getLocale("error_invalid_player") - .ifPresent(executor::sendMessage); - return; - } - - final User user = optionalUser.get(); - switch (subCommand) { - case "view" -> uuid.ifPresentOrElse( - version -> viewSnapshot(executor, user, version), - () -> viewLatestSnapshot(executor, user) - ); - case "list" -> listSnapshots( - executor, user, parseIntArg(args, 2).or(() -> parseIntArg(args, 1)).orElse(1) - ); - case "delete" -> uuid.ifPresentOrElse( - version -> deleteSnapshot(executor, user, version), - () -> plugin.getLocales().getLocale("error_invalid_syntax", - "/userdata delete ") - .ifPresent(executor::sendMessage) - ); - case "restore" -> uuid.ifPresentOrElse( - version -> restoreSnapshot(executor, user, version), - () -> plugin.getLocales().getLocale("error_invalid_syntax", - "/userdata restore ") - .ifPresent(executor::sendMessage) - ); - case "pin" -> uuid.ifPresentOrElse( - version -> pinSnapshot(executor, user, version), - () -> plugin.getLocales().getLocale("error_invalid_syntax", - "/userdata pin ") - .ifPresent(executor::sendMessage) - ); - case "dump" -> uuid.ifPresentOrElse( - version -> dumpSnapshot(executor, user, version, parseStringArg(args, 3) - .map(arg -> arg.equalsIgnoreCase("web")).orElse(false)), - () -> plugin.getLocales().getLocale("error_invalid_syntax", - "/userdata dump ") - .ifPresent(executor::sendMessage) - ); - default -> plugin.getLocales().getLocale("error_invalid_syntax", getUsage()) - .ifPresent(executor::sendMessage); - } + public void provide(@NotNull BaseCommand command) { + command.addSubCommand("view", needsOp("view"), view()); + command.addSubCommand("list", needsOp("list"), list()); + command.addSubCommand("delete", needsOp("delete"), delete()); + command.addSubCommand("restore", needsOp("restore"), restore()); + command.addSubCommand("pin", needsOp("pin"), pin()); + command.addSubCommand("dump", needsOp("dump"), dump()); } // Show the latest snapshot private void viewLatestSnapshot(@NotNull CommandUser executor, @NotNull User user) { plugin.getDatabase().getLatestSnapshot(user).ifPresentOrElse( - data -> { - if (data.isInvalid()) { - plugin.getLocales().getLocale("error_invalid_data", data.getInvalidReason(plugin)) - .ifPresent(executor::sendMessage); - return; - } - DataSnapshotOverview.of(data.unpack(plugin), data.getFileSize(plugin), user, plugin) - .show(executor); - }, - () -> plugin.getLocales().getLocale("error_no_data_to_display") - .ifPresent(executor::sendMessage) + data -> { + if (data.isInvalid()) { + plugin.getLocales().getLocale("error_invalid_data", data.getInvalidReason(plugin)) + .ifPresent(executor::sendMessage); + return; + } + DataSnapshotOverview.of(data.unpack(plugin), data.getFileSize(plugin), user, plugin) + .show(executor); + }, + () -> plugin.getLocales().getLocale("error_no_data_to_display") + .ifPresent(executor::sendMessage) ); } // Show the specified snapshot private void viewSnapshot(@NotNull CommandUser executor, @NotNull User user, @NotNull UUID version) { plugin.getDatabase().getSnapshot(user, version).ifPresentOrElse( - data -> { - if (data.isInvalid()) { - plugin.getLocales().getLocale("error_invalid_data", data.getInvalidReason(plugin)) - .ifPresent(executor::sendMessage); - return; - } - DataSnapshotOverview.of(data.unpack(plugin), data.getFileSize(plugin), user, plugin) - .show(executor); - }, - () -> plugin.getLocales().getLocale("error_invalid_version_uuid") - .ifPresent(executor::sendMessage) + data -> { + if (data.isInvalid()) { + plugin.getLocales().getLocale("error_invalid_data", data.getInvalidReason(plugin)) + .ifPresent(executor::sendMessage); + return; + } + DataSnapshotOverview.of(data.unpack(plugin), data.getFileSize(plugin), user, plugin) + .show(executor); + }, + () -> plugin.getLocales().getLocale("error_invalid_version_uuid") + .ifPresent(executor::sendMessage) ); } @@ -146,7 +96,7 @@ public class UserDataCommand extends Command implements TabProvider { final List dataList = plugin.getDatabase().getAllSnapshots(user); if (dataList.isEmpty()) { plugin.getLocales().getLocale("error_no_data_to_display") - .ifPresent(executor::sendMessage); + .ifPresent(executor::sendMessage); return; } DataSnapshotList.create(dataList, user, plugin).displayPage(executor, page); @@ -156,16 +106,16 @@ public class UserDataCommand extends Command implements TabProvider { private void deleteSnapshot(@NotNull CommandUser executor, @NotNull User user, @NotNull UUID version) { if (!plugin.getDatabase().deleteSnapshot(user, version)) { plugin.getLocales().getLocale("error_invalid_version_uuid") - .ifPresent(executor::sendMessage); + .ifPresent(executor::sendMessage); return; } plugin.getRedisManager().clearUserData(user); plugin.getLocales().getLocale("data_deleted", - version.toString().split("-")[0], - version.toString(), - user.getUsername(), - user.getUuid().toString()) - .ifPresent(executor::sendMessage); + version.toString().split("-")[0], + version.toString(), + user.getUsername(), + user.getUuid().toString()) + .ifPresent(executor::sendMessage); } // Restore a snapshot @@ -173,7 +123,7 @@ public class UserDataCommand extends Command implements TabProvider { final Optional optionalData = plugin.getDatabase().getSnapshot(user, version); if (optionalData.isEmpty()) { plugin.getLocales().getLocale("error_invalid_version_uuid") - .ifPresent(executor::sendMessage); + .ifPresent(executor::sendMessage); return; } @@ -181,14 +131,14 @@ public class UserDataCommand extends Command implements TabProvider { final DataSnapshot.Packed data = optionalData.get().copy(); if (data.isInvalid()) { plugin.getLocales().getLocale("error_invalid_data", data.getInvalidReason(plugin)) - .ifPresent(executor::sendMessage); + .ifPresent(executor::sendMessage); return; } data.edit(plugin, (unpacked -> { unpacked.getHealth().ifPresent(status -> status.setHealth(Math.max(1, status.getHealth()))); unpacked.setSaveCause(DataSnapshot.SaveCause.BACKUP_RESTORE); unpacked.setPinned( - plugin.getSettings().getSynchronization().doAutoPin(DataSnapshot.SaveCause.BACKUP_RESTORE) + plugin.getSettings().getSynchronization().doAutoPin(DataSnapshot.SaveCause.BACKUP_RESTORE) ); })); @@ -198,7 +148,7 @@ public class UserDataCommand extends Command implements TabProvider { redis.getUserData(u).ifPresent(d -> redis.setUserData(u, s, RedisKeyType.TTL_1_YEAR)); redis.sendUserDataUpdate(u, s); plugin.getLocales().getLocale("data_restored", u.getUsername(), u.getUuid().toString(), - s.getShortId(), s.getId().toString()).ifPresent(executor::sendMessage); + s.getShortId(), s.getId().toString()).ifPresent(executor::sendMessage); }); } @@ -207,7 +157,7 @@ public class UserDataCommand extends Command implements TabProvider { final Optional optionalData = plugin.getDatabase().getSnapshot(user, version); if (optionalData.isEmpty()) { plugin.getLocales().getLocale("error_invalid_version_uuid") - .ifPresent(executor::sendMessage); + .ifPresent(executor::sendMessage); return; } @@ -219,8 +169,8 @@ public class UserDataCommand extends Command implements TabProvider { plugin.getDatabase().pinSnapshot(user, data.getId()); } plugin.getLocales().getLocale(data.isPinned() ? "data_unpinned" : "data_pinned", data.getShortId(), - data.getId().toString(), user.getUsername(), user.getUuid().toString()) - .ifPresent(executor::sendMessage); + data.getId().toString(), user.getUsername(), user.getUuid().toString()) + .ifPresent(executor::sendMessage); } // Dump a snapshot @@ -228,7 +178,7 @@ public class UserDataCommand extends Command implements TabProvider { final Optional data = plugin.getDatabase().getSnapshot(user, version); if (data.isEmpty()) { plugin.getLocales().getLocale("error_invalid_version_uuid") - .ifPresent(executor::sendMessage); + .ifPresent(executor::sendMessage); return; } @@ -237,22 +187,98 @@ public class UserDataCommand extends Command implements TabProvider { final DataDumper dumper = DataDumper.create(userData, user, plugin); try { plugin.getLocales().getLocale("data_dumped", userData.getShortId(), user.getUsername(), - (webDump ? dumper.toWeb() : dumper.toFile())).ifPresent(executor::sendMessage); + (webDump ? dumper.toWeb() : dumper.toFile())).ifPresent(executor::sendMessage); } catch (Throwable e) { plugin.log(Level.SEVERE, "Failed to dump user data", e); } } - @Nullable - @Override - public List suggest(@NotNull CommandUser executor, @NotNull String[] args) { - return switch (args.length) { - case 0, 1 -> SUB_COMMANDS.keySet().stream().sorted().toList(); - case 2 -> plugin.getOnlineUsers().stream().map(User::getUsername).toList(); - case 4 -> parseStringArg(args, 0) - .map(arg -> arg.equalsIgnoreCase("dump") ? List.of("web", "file") : null) - .orElse(null); - default -> null; + @NotNull + private CommandProvider view() { + return (sub) -> { + sub.addSyntax((ctx) -> { + final User user = ctx.getArgument("username", User.class); + viewLatestSnapshot(user(sub, ctx), user); + }, user("username")); + sub.addSyntax((ctx) -> { + final User user = ctx.getArgument("username", User.class); + final UUID version = ctx.getArgument("version", UUID.class); + viewSnapshot(user(sub, ctx), user, version); + }, user("username"), uuid("version")); }; } + + @NotNull + private CommandProvider list() { + return (sub) -> { + sub.addSyntax((ctx) -> { + final User user = ctx.getArgument("username", User.class); + listSnapshots(user(sub, ctx), user, 1); + }, user("username")); + sub.addSyntax((ctx) -> { + final User user = ctx.getArgument("username", User.class); + final int page = ctx.getArgument("page", Integer.class); + listSnapshots(user(sub, ctx), user, page); + }, user("username"), BaseCommand.intNum("page", 1)); + }; + } + + @NotNull + private CommandProvider delete() { + return (sub) -> sub.addSyntax((ctx) -> { + final User user = ctx.getArgument("username", User.class); + final UUID version = ctx.getArgument("version", UUID.class); + deleteSnapshot(user(sub, ctx), user, version); + }, user("username"), uuid("version")); + } + + @NotNull + private CommandProvider restore() { + return (sub) -> sub.addSyntax((ctx) -> { + final User user = ctx.getArgument("username", User.class); + final UUID version = ctx.getArgument("version", UUID.class); + restoreSnapshot(user(sub, ctx), user, version); + }, user("username"), uuid("version")); + } + + @NotNull + private CommandProvider pin() { + return (sub) -> sub.addSyntax((ctx) -> { + final User user = ctx.getArgument("username", User.class); + final UUID version = ctx.getArgument("version", UUID.class); + pinSnapshot(user(sub, ctx), user, version); + }, user("username"), uuid("version")); + } + + @NotNull + private CommandProvider dump() { + return (sub) -> sub.addSyntax((ctx) -> { + final User user = ctx.getArgument("username", User.class); + final UUID version = ctx.getArgument("version", UUID.class); + final DumpType type = ctx.getArgument("type", DumpType.class); + dumpSnapshot(user(sub, ctx), user, version, type == DumpType.WEB); + }, user("username"), uuid("version"), dumpType()); + } + + private ArgumentElement dumpType() { + return new ArgumentElement<>("type", reader -> { + final String type = reader.readString(); + return switch (type.toLowerCase(Locale.ENGLISH)) { + case "web" -> DumpType.WEB; + case "file" -> DumpType.FILE; + default -> throw CommandSyntaxException.BUILT_IN_EXCEPTIONS + .dispatcherUnknownArgument().createWithContext(reader); + }; + }, (context, builder) -> { + builder.suggest("web"); + builder.suggest("file"); + return builder.buildFuture(); + }); + } + + enum DumpType { + WEB, + FILE + } + } diff --git a/common/src/main/java/net/william278/husksync/config/Settings.java b/common/src/main/java/net/william278/husksync/config/Settings.java index 6c2442ed..66207032 100644 --- a/common/src/main/java/net/william278/husksync/config/Settings.java +++ b/common/src/main/java/net/william278/husksync/config/Settings.java @@ -69,9 +69,6 @@ public class Settings { @Comment("Enable development debug logging") private boolean debugLogging = false; - @Comment("Whether to provide modern, rich TAB suggestions for commands (if available)") - private boolean brigadierTabCompletion = false; - @Comment({"Whether to enable the Player Analytics hook.", "Docs: https://william278.net/docs/husksync/plan-hook"}) private boolean enablePlanHook = true; diff --git a/common/src/main/java/net/william278/husksync/user/CommandUser.java b/common/src/main/java/net/william278/husksync/user/CommandUser.java index c4db8540..d01ec988 100644 --- a/common/src/main/java/net/william278/husksync/user/CommandUser.java +++ b/common/src/main/java/net/william278/husksync/user/CommandUser.java @@ -24,7 +24,7 @@ import net.kyori.adventure.audience.Audience; import net.kyori.adventure.text.Component; import org.jetbrains.annotations.NotNull; -public interface CommandUser { +public interface CommandUser { @NotNull Audience getAudience(); diff --git a/common/src/main/java/net/william278/husksync/user/ConsoleUser.java b/common/src/main/java/net/william278/husksync/user/ConsoleUser.java index 306e799c..1626bf62 100644 --- a/common/src/main/java/net/william278/husksync/user/ConsoleUser.java +++ b/common/src/main/java/net/william278/husksync/user/ConsoleUser.java @@ -42,4 +42,6 @@ public final class ConsoleUser implements CommandUser { public boolean hasPermission(@NotNull String permission) { return true; } + + } diff --git a/fabric/build.gradle b/fabric/build.gradle index 02c14559..f2264de1 100644 --- a/fabric/build.gradle +++ b/fabric/build.gradle @@ -20,7 +20,7 @@ dependencies { modImplementation include("eu.pb4:sgui:${sgui_version}") modCompileOnly "net.fabricmc.fabric-api:fabric-api:${fabric_api_version}" - // Runtime dependencies on Bukkit; "include" them on Fabric. (todo: minify JAR?) + implementation include('net.william278.uniform:uniform-fabric:1.1+1.20.1') implementation include('org.apache.commons:commons-pool2:2.12.0') implementation include("redis.clients:jedis:$jedis_version") implementation include("com.mysql:mysql-connector-j:$mysql_driver_version") @@ -33,7 +33,7 @@ dependencies { annotationProcessor 'org.projectlombok:lombok:1.18.32' - shadow project(path: ":common") + implementation project(path: ":common") } shadowJar { @@ -54,6 +54,7 @@ shadowJar { relocate 'org.intellij', 'net.william278.husksync.libraries' relocate 'com.zaxxer', 'net.william278.husksync.libraries' relocate 'de.exlll', 'net.william278.husksync.libraries' + relocate 'net.william278.uniform', 'net.william278.husksync.libraries.uniform' relocate 'net.william278.desertwell', 'net.william278.husksync.libraries.desertwell' relocate 'net.william278.paginedown', 'net.william278.husksync.libraries.paginedown' relocate 'org.json', 'net.william278.husksync.libraries.json' diff --git a/fabric/src/main/java/net/william278/husksync/FabricHuskSync.java b/fabric/src/main/java/net/william278/husksync/FabricHuskSync.java index e6a82452..fa92dc7b 100644 --- a/fabric/src/main/java/net/william278/husksync/FabricHuskSync.java +++ b/fabric/src/main/java/net/william278/husksync/FabricHuskSync.java @@ -28,7 +28,6 @@ import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; import net.fabricmc.api.DedicatedServerModInitializer; -import net.fabricmc.fabric.api.command.v2.CommandRegistrationCallback; import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents; import net.fabricmc.loader.api.FabricLoader; import net.fabricmc.loader.api.ModContainer; @@ -40,8 +39,7 @@ import net.william278.husksync.adapter.DataAdapter; import net.william278.husksync.adapter.GsonAdapter; import net.william278.husksync.adapter.SnappyGsonAdapter; import net.william278.husksync.api.FabricHuskSyncAPI; -import net.william278.husksync.command.Command; -import net.william278.husksync.command.FabricCommand; +import net.william278.husksync.command.PluginCommand; import net.william278.husksync.config.Locales; import net.william278.husksync.config.Server; import net.william278.husksync.config.Settings; @@ -62,6 +60,8 @@ import net.william278.husksync.user.FabricUser; import net.william278.husksync.user.OnlineUser; import net.william278.husksync.util.FabricTask; import net.william278.husksync.util.LegacyConverter; +import net.william278.uniform.Uniform; +import net.william278.uniform.fabric.FabricUniform; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.slf4j.Logger; @@ -74,22 +74,22 @@ import java.nio.file.Files; import java.nio.file.Path; import java.util.*; import java.util.logging.Level; -import java.util.stream.Collectors; @Getter @NoArgsConstructor public class FabricHuskSync implements DedicatedServerModInitializer, HuskSync, FabricTask.Supplier, - FabricEventDispatcher { + FabricEventDispatcher { private static final String PLATFORM_TYPE_ID = "fabric"; private final TreeMap> serializers = Maps.newTreeMap( - SerializerRegistry.DEPENDENCY_ORDER_COMPARATOR + SerializerRegistry.DEPENDENCY_ORDER_COMPARATOR ); private final Map> playerCustomDataStore = Maps.newConcurrentMap(); private final Map permissions = Maps.newHashMap(); private final List availableMigrators = Lists.newArrayList(); private final Set lockedPlayers = Sets.newConcurrentHashSet(); + private final Map playerMap = Maps.newConcurrentMap(); private Logger logger; private ModContainer mod; @@ -127,7 +127,7 @@ public class FabricHuskSync implements DedicatedServerModInitializer, HuskSync, }); // Register commands - initialize("commands", (plugin) -> this.registerCommands()); + initialize("commands", (plugin) -> getUniform().register(PluginCommand.Type.create(this))); // Load HuskSync after server startup ServerLifecycleEvents.SERVER_STARTED.register(server -> { @@ -232,12 +232,6 @@ public class FabricHuskSync implements DedicatedServerModInitializer, HuskSync, log(Level.INFO, "Successfully disabled HuskSync v" + getPluginVersion()); } - private void registerCommands() { - final List commands = FabricCommand.Type.getCommands(this); - CommandRegistrationCallback.EVENT.register((dispatcher, registry, environment) -> - commands.forEach(command -> new FabricCommand(command, this).register(dispatcher)) - ); - } @NotNull @Override @@ -253,31 +247,34 @@ public class FabricHuskSync implements DedicatedServerModInitializer, HuskSync, @Override @NotNull public Set getOnlineUsers() { - return minecraftServer.getPlayerManager().getPlayerList() - .stream().map(user -> (OnlineUser) FabricUser.adapt(user, this)) - .collect(Collectors.toSet()); + return Sets.newHashSet(playerMap.values()); } @Override @NotNull public Optional getOnlineUser(@NotNull UUID uuid) { - return Optional.ofNullable(minecraftServer.getPlayerManager().getPlayer(uuid)) - .map(user -> FabricUser.adapt(user, this)); + return Optional.ofNullable(playerMap.get(uuid)); + } + + @Override + @NotNull + public Uniform getUniform() { + return FabricUniform.getInstance(); } @Override @Nullable public InputStream getResource(@NotNull String name) { return this.mod.findPath(name) - .map(path -> { - try { - return Files.newInputStream(path); - } catch (IOException e) { - log(Level.WARNING, "Failed to load resource: " + name, e); - } - return null; - }) - .orElse(this.getClass().getClassLoader().getResourceAsStream(name)); + .map(path -> { + try { + return Files.newInputStream(path); + } catch (IOException e) { + log(Level.WARNING, "Failed to load resource: " + name, e); + } + return null; + }) + .orElse(this.getClass().getClassLoader().getResourceAsStream(name)); } @Override @@ -297,11 +294,11 @@ public class FabricHuskSync implements DedicatedServerModInitializer, HuskSync, @Override public void log(@NotNull Level level, @NotNull String message, @NotNull Throwable... throwable) { LoggingEventBuilder logEvent = logger.makeLoggingEventBuilder( - switch (level.getName()) { - case "WARNING" -> org.slf4j.event.Level.WARN; - case "SEVERE" -> org.slf4j.event.Level.ERROR; - default -> org.slf4j.event.Level.INFO; - } + switch (level.getName()) { + case "WARNING" -> org.slf4j.event.Level.WARN; + case "SEVERE" -> org.slf4j.event.Level.ERROR; + default -> org.slf4j.event.Level.INFO; + } ); if (throwable.length >= 1) { logEvent = logEvent.setCause(throwable[0]); diff --git a/fabric/src/main/java/net/william278/husksync/command/FabricCommand.java b/fabric/src/main/java/net/william278/husksync/command/FabricCommand.java deleted file mode 100644 index 14b02cf9..00000000 --- a/fabric/src/main/java/net/william278/husksync/command/FabricCommand.java +++ /dev/null @@ -1,153 +0,0 @@ -/* - * This file is part of HuskSync, licensed under the Apache License 2.0. - * - * Copyright (c) William278 - * Copyright (c) contributors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package net.william278.husksync.command; - -import com.mojang.brigadier.CommandDispatcher; -import com.mojang.brigadier.builder.LiteralArgumentBuilder; -import com.mojang.brigadier.tree.LiteralCommandNode; -import me.lucko.fabric.api.permissions.v0.PermissionCheckEvent; -import me.lucko.fabric.api.permissions.v0.Permissions; -import net.fabricmc.fabric.api.util.TriState; -import net.minecraft.server.command.ServerCommandSource; -import net.minecraft.server.network.ServerPlayerEntity; -import net.william278.husksync.FabricHuskSync; -import net.william278.husksync.HuskSync; -import net.william278.husksync.user.CommandUser; -import net.william278.husksync.user.FabricUser; -import org.jetbrains.annotations.NotNull; - -import java.util.Arrays; -import java.util.List; -import java.util.Map; -import java.util.function.Function; -import java.util.function.Predicate; - -import static com.mojang.brigadier.arguments.StringArgumentType.greedyString; -import static net.minecraft.server.command.CommandManager.argument; -import static net.minecraft.server.command.CommandManager.literal; - -public class FabricCommand { - - private final FabricHuskSync plugin; - private final Command command; - - public FabricCommand(@NotNull Command command, @NotNull FabricHuskSync plugin) { - this.command = command; - this.plugin = plugin; - } - - public void register(@NotNull CommandDispatcher dispatcher) { - // Register brigadier command - final Predicate predicate = Permissions - .require(command.getPermission(), command.isOperatorCommand() ? 3 : 0); - final LiteralArgumentBuilder builder = literal(command.getName()) - .requires(predicate).executes(getBrigadierExecutor()); - plugin.getPermissions().put(command.getPermission(), command.isOperatorCommand()); - if (!command.getRawUsage().isBlank()) { - builder.then(argument(command.getRawUsage().replaceAll("[<>\\[\\]]", ""), greedyString()) - .executes(getBrigadierExecutor()) - .suggests(getBrigadierSuggester())); - } - - // Register additional permissions - final Map permissions = command.getAdditionalPermissions(); - permissions.forEach((permission, isOp) -> plugin.getPermissions().put(permission, isOp)); - PermissionCheckEvent.EVENT.register((player, node) -> { - if (permissions.containsKey(node) && permissions.get(node) && player.hasPermissionLevel(3)) { - return TriState.TRUE; - } - return TriState.DEFAULT; - }); - - // Register aliases - final LiteralCommandNode node = dispatcher.register(builder); - dispatcher.register(literal("husksync:" + command.getName()) - .requires(predicate).executes(getBrigadierExecutor()).redirect(node)); - command.getAliases().forEach(alias -> dispatcher.register(literal(alias) - .requires(predicate).executes(getBrigadierExecutor()).redirect(node))); - } - - private com.mojang.brigadier.Command getBrigadierExecutor() { - return (context) -> { - command.onExecuted( - resolveExecutor(context.getSource()), - command.removeFirstArg(context.getInput().split(" ")) - ); - return 1; - }; - } - - private com.mojang.brigadier.suggestion.SuggestionProvider getBrigadierSuggester() { - if (!(command instanceof TabProvider provider)) { - return (context, builder) -> com.mojang.brigadier.suggestion.Suggestions.empty(); - } - return (context, builder) -> { - final String[] args = command.removeFirstArg(context.getInput().split(" ", -1)); - provider.getSuggestions(resolveExecutor(context.getSource()), args).stream() - .map(suggestion -> { - final String completedArgs = String.join(" ", args); - int lastIndex = completedArgs.lastIndexOf(" "); - if (lastIndex == -1) { - return suggestion; - } - return completedArgs.substring(0, lastIndex + 1) + suggestion; - }) - .forEach(builder::suggest); - return builder.buildFuture(); - }; - } - - private CommandUser resolveExecutor(@NotNull ServerCommandSource source) { - if (source.getEntity() instanceof ServerPlayerEntity player) { - return FabricUser.adapt(player, plugin); - } - return plugin.getConsole(); - } - - - /** - * Commands available on the Fabric HuskSync implementation. - */ - public enum Type { - - HUSKSYNC_COMMAND(HuskSyncCommand::new), - USERDATA_COMMAND(UserDataCommand::new), - INVENTORY_COMMAND(InventoryCommand::new), - ENDER_CHEST_COMMAND(EnderChestCommand::new); - - private final Function supplier; - - Type(@NotNull Function supplier) { - this.supplier = supplier; - } - - @NotNull - public Command createCommand(@NotNull HuskSync plugin) { - return supplier.apply(plugin); - } - - @NotNull - public static List getCommands(@NotNull FabricHuskSync plugin) { - return Arrays.stream(values()).map(type -> type.createCommand(plugin)).toList(); - } - - } - -} \ No newline at end of file diff --git a/fabric/src/main/java/net/william278/husksync/data/FabricData.java b/fabric/src/main/java/net/william278/husksync/data/FabricData.java index 87cbb1b4..44a82a3e 100644 --- a/fabric/src/main/java/net/william278/husksync/data/FabricData.java +++ b/fabric/src/main/java/net/william278/husksync/data/FabricData.java @@ -315,7 +315,7 @@ public abstract class FabricData implements Data { // Only save the advancement if criteria has been completed if (!awardedCriteria.isEmpty()) { - advancements.add(Advancement.adapt(advancement.getId().asString(), awardedCriteria)); + advancements.add(Advancement.adapt(advancement.getId().toString(), awardedCriteria)); } }); return new FabricData.Advancements(advancements); @@ -479,7 +479,7 @@ public abstract class FabricData implements Data { Registries.STAT_TYPE.getEntrySet().forEach(stat -> { final Registry registry = stat.getValue().getRegistry(); - final String registryId = registry.getKey().getValue().value(); + final String registryId = registry.getKey().getValue().getPath(); if (registryId.equals("custom_stat")) { return; } @@ -488,13 +488,13 @@ public abstract class FabricData implements Data { case ITEM_STAT_TYPE -> items; case ENTITY_STAT_TYPE -> entities; default -> throw new IllegalStateException("Unexpected value: %s".formatted(registryId)); - }).compute(stat.getKey().getValue().asString(), (k, v) -> v == null ? Maps.newHashMap() : v); + }).compute(stat.getKey().getValue().toString(), (k, v) -> v == null ? Maps.newHashMap() : v); registry.getEntrySet().forEach(entry -> { @SuppressWarnings({"unchecked", "rawtypes"}) final int value = player.getStatHandler() .getStat((StatType) stat.getValue(), entry.getValue()); if (value != 0) { - map.put(entry.getKey().getValue().asString(), value); + map.put(entry.getKey().getValue().toString(), value); } }); }); @@ -504,7 +504,7 @@ public abstract class FabricData implements Data { Registries.CUSTOM_STAT.getEntrySet().forEach(stat -> { final int value = player.getStatHandler().getStat(Stats.CUSTOM.getOrCreateStat(stat.getValue())); if (value != 0) { - generic.put(stat.getKey().getValue().asString(), value); + generic.put(stat.getKey().getValue().toString(), value); } }); @@ -587,7 +587,7 @@ public abstract class FabricData implements Data { -1 ))); attributes.add(new Attribute( - key.asString(), + key.toString(), instance.getBaseValue(), modifiers )); @@ -596,7 +596,7 @@ public abstract class FabricData implements Data { } public Optional getAttribute(@NotNull EntityAttribute id) { - return Optional.ofNullable(Registries.ATTRIBUTE.getId(id)).map(Identifier::asString) + return Optional.ofNullable(Registries.ATTRIBUTE.getId(id)).map(Identifier::toString) .flatMap(key -> attributes.stream().filter(attribute -> attribute.name().equals(key)).findFirst()); } diff --git a/fabric/src/main/java/net/william278/husksync/listener/FabricEventListener.java b/fabric/src/main/java/net/william278/husksync/listener/FabricEventListener.java index 2533b5dc..57f48125 100644 --- a/fabric/src/main/java/net/william278/husksync/listener/FabricEventListener.java +++ b/fabric/src/main/java/net/william278/husksync/listener/FabricEventListener.java @@ -44,6 +44,7 @@ import net.minecraft.util.hit.BlockHitResult; import net.minecraft.util.hit.EntityHitResult; import net.minecraft.util.math.BlockPos; import net.minecraft.world.World; +import net.william278.husksync.FabricHuskSync; import net.william278.husksync.HuskSync; import net.william278.husksync.config.Settings.SynchronizationSettings.SaveOnDeathSettings; import net.william278.husksync.data.FabricData; @@ -82,10 +83,13 @@ public class FabricEventListener extends EventListener implements LockedHandler private void handlePlayerJoin(@NotNull ServerPlayNetworkHandler handler, @NotNull PacketSender sender, @NotNull MinecraftServer server) { - handlePlayerJoin(FabricUser.adapt(handler.player, plugin)); + final FabricUser user = FabricUser.adapt(handler.player, plugin); + ((FabricHuskSync) plugin).getPlayerMap().put(handler.player.getUuid(), user); + handlePlayerJoin(user); } private void handlePlayerQuit(@NotNull ServerPlayNetworkHandler handler, @NotNull MinecraftServer server) { + ((FabricHuskSync) plugin).getPlayerMap().remove(handler.player.getUuid()); handlePlayerQuit(FabricUser.adapt(handler.player, plugin)); } diff --git a/fabric/src/main/java/net/william278/husksync/user/FabricUser.java b/fabric/src/main/java/net/william278/husksync/user/FabricUser.java index 33cd97db..ecb644e6 100644 --- a/fabric/src/main/java/net/william278/husksync/user/FabricUser.java +++ b/fabric/src/main/java/net/william278/husksync/user/FabricUser.java @@ -72,7 +72,7 @@ public class FabricUser extends OnlineUser implements FabricUserDataHolder { @Override public void sendToast(@NotNull MineDown title, @NotNull MineDown description, @NotNull String iconMaterial, @NotNull String backgroundType) { - player.sendActionBar(title.toComponent()); // Toasts unimplemented for now + getAudience().sendActionBar(title.toComponent()); // Toasts unimplemented for now } @Override diff --git a/paper/build.gradle b/paper/build.gradle index 83e590e9..138e6bad 100644 --- a/paper/build.gradle +++ b/paper/build.gradle @@ -2,6 +2,8 @@ dependencies { implementation project(':bukkit') compileOnly project(':common') + implementation 'net.william278.uniform:uniform-paper:1.1' + compileOnly 'io.papermc.paper:paper-api:1.19.4-R0.1-SNAPSHOT' compileOnly 'org.jetbrains:annotations:24.1.0' compileOnly 'org.projectlombok:lombok:1.18.32' @@ -24,6 +26,7 @@ shadowJar { relocate 'org.intellij', 'net.william278.husksync.libraries' relocate 'com.zaxxer', 'net.william278.husksync.libraries' relocate 'de.exlll', 'net.william278.husksync.libraries' + relocate 'net.william278.uniform', 'net.william278.husksync.libraries.uniform' relocate 'net.william278.desertwell', 'net.william278.husksync.libraries.desertwell' relocate 'net.william278.paginedown', 'net.william278.husksync.libraries.paginedown' relocate 'net.william278.mapdataapi', 'net.william278.husksync.libraries.mapdataapi' @@ -33,7 +36,6 @@ shadowJar { relocate 'org.json', 'net.william278.husksync.libraries.json' relocate 'net.querz', 'net.william278.husksync.libraries.nbtparser' relocate 'net.roxeez', 'net.william278.husksync.libraries' - relocate 'me.lucko.commodore', 'net.william278.husksync.libraries.commodore' relocate 'org.bstats', 'net.william278.husksync.libraries.bstats' relocate 'dev.triumphteam.gui', 'net.william278.husksync.libraries.triumphgui' relocate 'space.arim.morepaperlib', 'net.william278.husksync.libraries.paperlib' diff --git a/paper/src/main/java/net/william278/husksync/PaperHuskSync.java b/paper/src/main/java/net/william278/husksync/PaperHuskSync.java index 3024cc0b..5d949bfd 100644 --- a/paper/src/main/java/net/william278/husksync/PaperHuskSync.java +++ b/paper/src/main/java/net/william278/husksync/PaperHuskSync.java @@ -22,6 +22,8 @@ package net.william278.husksync; import net.kyori.adventure.audience.Audience; import net.william278.husksync.listener.BukkitEventListener; import net.william278.husksync.listener.PaperEventListener; +import net.william278.uniform.Uniform; +import net.william278.uniform.paper.PaperUniform; import org.bukkit.entity.Player; import org.jetbrains.annotations.NotNull; @@ -43,4 +45,9 @@ public class PaperHuskSync extends BukkitHuskSync { return player == null || !player.isOnline() ? Audience.empty() : player; } + @Override + @NotNull + public Uniform getUniform() { + return PaperUniform.getInstance(this); + } } diff --git a/test/spin_network.py b/test/spin_network.py index b019c2d6..94412c49 100644 --- a/test/spin_network.py +++ b/test/spin_network.py @@ -33,7 +33,7 @@ class Parameters: proxy_plugins = [] proxy_plugin_folders = [] - just_update_plugins = True + just_update_plugins = False def main(update=False):