diff --git a/plugins/civmodcore-paper/src/main/java/vg/civcraft/mc/civmodcore/commands/CommandHelpers.java b/plugins/civmodcore-paper/src/main/java/vg/civcraft/mc/civmodcore/commands/CommandHelpers.java new file mode 100644 index 000000000..85c8fb0cb --- /dev/null +++ b/plugins/civmodcore-paper/src/main/java/vg/civcraft/mc/civmodcore/commands/CommandHelpers.java @@ -0,0 +1,106 @@ +package vg.civcraft.mc.civmodcore.commands; + +import co.aikar.commands.CommandCompletions; +import co.aikar.commands.CommandContexts; +import co.aikar.commands.InvalidCommandArgument; +import java.util.Arrays; +import java.util.List; +import org.bukkit.Material; +import org.bukkit.command.ConsoleCommandSender; +import org.jetbrains.annotations.NotNull; +import vg.civcraft.mc.civmodcore.inventory.items.ItemUtils; + +/** + * This is a separate helper class that allows plugins to use ACF without needing to commit to {@link CommandManager} + * to get all the additional features it provides. It also allows some degree of optimisation since not all plugins + * need their own item-material completions (like CivChat2). + */ +public final class CommandHelpers { + /** + * ACF has an automatic help generator, but it's currently considered an unstable API. + * Read more. + * + *

+	 * // Somewhere in your plugin enable process
+	 * CommandHelpers.enableCommandHelp(this.commandManager);
+	 *
+	 * // Example Command
+	 * @CommandAlias("example")
+	 * public class ExampleCommand extends BaseCommand {
+	 *     @Default
+	 *     public void showHelp(final @NotNull Player sender) {
+	 *         throw new ShowCommandHelp(); // Sends a help page to the player
+	 *     }
+	 * }
+	 * 
+ */ + @SuppressWarnings("deprecation") + public static void enableCommandHelp( + final @NotNull co.aikar.commands.CommandManager manager + ) { + manager.enableUnstableAPI("help"); + } + + // ============================================================ + // Completions + // ============================================================ + + /** + * ACF already has the "@nothing" completion, but that seems less intuitive than "@none", so this adds "@none". + */ + public static void registerNoneCompletion( + final @NotNull CommandCompletions completions + ) { + completions.registerStaticCompletion( + "none", + List.of() + ); + } + + /** + * Completion for all Bukkit materials. + */ + public static void registerMaterialsCompletion( + final @NotNull CommandCompletions completions + ) { + completions.registerStaticCompletion( + "materials", + Arrays.stream(Material.values()) + .map(Enum::name) + .toList() + ); + } + + /** + * Completion for all Bukkit materials that are items, excluding Air. + */ + public static void registerItemMaterialsCompletion( + final @NotNull CommandCompletions completions + ) { + completions.registerStaticCompletion( + "itemMaterials", + Arrays.stream(Material.values()) + .filter(ItemUtils::isValidItemMaterial) + .map(Enum::name) + .toList() + ); + } + + // ============================================================ + // Contexts + // ============================================================ + + /** + * Registers a context that requires the command-sender to be the console. + */ + public static void registerConsoleSenderContext( + final @NotNull CommandContexts contexts + ) { + contexts.registerIssuerAwareContext(ConsoleCommandSender.class, (context) -> { + if (context.getIssuer().getIssuer() instanceof final ConsoleCommandSender console) { + return console; + } + throw new InvalidCommandArgument("Command can only be called from console!", false); + }); + } +} diff --git a/plugins/civmodcore-paper/src/main/java/vg/civcraft/mc/civmodcore/commands/CommandManager.java b/plugins/civmodcore-paper/src/main/java/vg/civcraft/mc/civmodcore/commands/CommandManager.java index 9ddec6910..603b920cf 100644 --- a/plugins/civmodcore-paper/src/main/java/vg/civcraft/mc/civmodcore/commands/CommandManager.java +++ b/plugins/civmodcore-paper/src/main/java/vg/civcraft/mc/civmodcore/commands/CommandManager.java @@ -8,36 +8,34 @@ import co.aikar.commands.CommandCompletions; import co.aikar.commands.CommandCompletions.CommandCompletionHandler; import co.aikar.commands.CommandContexts; import com.google.common.base.Strings; - import java.lang.reflect.Method; import java.lang.reflect.Modifier; -import java.util.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Objects; +import java.util.Set; import java.util.concurrent.ConcurrentSkipListSet; import java.util.logging.Level; import javax.annotation.Nonnull; - import org.apache.commons.lang3.reflect.FieldUtils; import org.bukkit.Bukkit; -import org.bukkit.Material; import org.bukkit.OfflinePlayer; import org.bukkit.event.EventHandler; import org.bukkit.event.EventPriority; import org.bukkit.event.Listener; import org.bukkit.event.player.PlayerLoginEvent; import org.bukkit.plugin.Plugin; -import vg.civcraft.mc.civmodcore.inventory.items.ItemUtils; import vg.civcraft.mc.civmodcore.utilities.CivLogger; /** * Command registration class wrapper around {@link BukkitCommandManager}. */ public class CommandManager extends BukkitCommandManager { - - // allMaterials and itemMaterials won't change over a run, so autocomplete lists can be prebuilt globally. - private final static List allMaterials = Arrays.stream(Material.values()).map(Enum::name).toList(); - - private final static List itemMaterials = Arrays.stream(Material.values()).filter(ItemUtils::isValidItemMaterial).map(Enum::name).toList(); - // Track players to offer quick completion where necessary. private final Set autocompletePlayerNames = new ConcurrentSkipListSet<>(); @@ -76,9 +74,9 @@ public class CommandManager extends BukkitCommandManager { } }, plugin); - registerCommands(); - registerCompletions(getCommandCompletions()); registerContexts(getCommandContexts()); + registerCompletions(getCommandCompletions()); + registerCommands(); } /** @@ -97,13 +95,14 @@ public class CommandManager extends BukkitCommandManager { * {@link #getCommandCompletions()}. */ public void registerCompletions(@Nonnull final CommandCompletions completions) { - completions.registerCompletion("none", (context) -> Collections.emptyList()); + CommandHelpers.registerNoneCompletion(completions); + CommandHelpers.registerMaterialsCompletion(completions); + CommandHelpers.registerItemMaterialsCompletion(completions); + // Completion lists are copied so outer code can modify the lists without breaking our inner contracts, // namely that all players should be searchable by completion. // Using Collections.immutableList is an alternative, but both variants aren't expensive. completions.registerAsyncCompletion("allplayers", (context) -> new ArrayList<>(autocompletePlayerNames)); - completions.registerAsyncCompletion("materials", (context) -> new ArrayList<>(allMaterials)); - completions.registerAsyncCompletion("itemMaterials", (context) -> new ArrayList<>(itemMaterials)); } /** @@ -114,6 +113,7 @@ public class CommandManager extends BukkitCommandManager { * {@link #getCommandContexts()}. */ public void registerContexts(@Nonnull final CommandContexts contexts) { + CommandHelpers.registerConsoleSenderContext(contexts); } /**