diff --git a/src/main/java/net/minestom/server/command/CommandManager.java b/src/main/java/net/minestom/server/command/CommandManager.java index 502d0cead..45213edd7 100644 --- a/src/main/java/net/minestom/server/command/CommandManager.java +++ b/src/main/java/net/minestom/server/command/CommandManager.java @@ -204,7 +204,7 @@ public final class CommandManager { /** * Gets the {@link DeclareCommandsPacket} for a specific player. *

- * Can be used to update the {@link Player} auto-completion list. + * Can be used to update a player auto-completion list. * * @param player the player to get the commands packet * @return the {@link DeclareCommandsPacket} for {@code player} @@ -234,7 +234,7 @@ public final class CommandManager { final CommandCondition commandCondition = command.getCondition(); if (commandCondition != null) { // Do not show command if return false - if (!commandCondition.apply(player)) { + if (!commandCondition.canUse(player, null)) { continue; } } @@ -247,7 +247,7 @@ public final class CommandManager { names.add(command.getName()); names.addAll(Arrays.asList(command.getAliases())); for (String name : names) { - createCommand(nodes, cmdChildren, name, syntaxes, rootChildren); + createCommand(player, nodes, cmdChildren, name, syntaxes, rootChildren); } } @@ -311,13 +311,15 @@ public final class CommandManager { /** * Adds the command's syntaxes to the nodes list. * + * @param sender the potential sender of the command * @param nodes the nodes of the packet * @param cmdChildren the main root of this command * @param name the name of the command (or the alias) * @param syntaxes the syntaxes of the command * @param rootChildren the children of the main node (all commands name) */ - private void createCommand(@NotNull List nodes, + private void createCommand(@NotNull CommandSender sender, + @NotNull List nodes, @NotNull IntList cmdChildren, @NotNull String name, @NotNull Collection syntaxes, @@ -334,6 +336,13 @@ public final class CommandManager { Map> storedArgumentsNodes = new HashMap<>(); for (CommandSyntax syntax : syntaxes) { + final CommandCondition commandCondition = syntax.getCommandCondition(); + if (commandCondition != null && !commandCondition.canUse(sender, null)) { + // Sender does not have the right to use this syntax, ignore it + continue; + } + + // Represent the last nodes computed in the last iteration List lastNodes = null; diff --git a/src/main/java/net/minestom/server/command/builder/Command.java b/src/main/java/net/minestom/server/command/builder/Command.java index 68807565b..811d7d6ae 100644 --- a/src/main/java/net/minestom/server/command/builder/Command.java +++ b/src/main/java/net/minestom/server/command/builder/Command.java @@ -104,16 +104,31 @@ public class Command { /** * Adds a new syntax in the command. *

- * A syntax is simply a list of arguments + * A syntax is simply a list of arguments. + * + * @param commandCondition the condition to use the syntax + * @param executor the executor to call when the syntax is successfully received + * @param args all the arguments of the syntax + * @return the created {@link CommandSyntax} + */ + public CommandSyntax addSyntax(@Nullable CommandCondition commandCondition, + @NotNull CommandExecutor executor, + @NotNull Argument... args) { + final CommandSyntax syntax = new CommandSyntax(commandCondition, executor, args); + this.syntaxes.add(syntax); + return syntax; + } + + /** + * Adds a new syntax in the command without any condition. * * @param executor the executor to call when the syntax is successfully received * @param args all the arguments of the syntax * @return the created {@link CommandSyntax} + * @see #addSyntax(CommandCondition, CommandExecutor, Argument[]) */ public CommandSyntax addSyntax(@NotNull CommandExecutor executor, @NotNull Argument... args) { - final CommandSyntax syntax = new CommandSyntax(executor, args); - this.syntaxes.add(syntax); - return syntax; + return addSyntax(null, executor, args); } /** diff --git a/src/main/java/net/minestom/server/command/builder/CommandDispatcher.java b/src/main/java/net/minestom/server/command/builder/CommandDispatcher.java index 4a3c78c88..fb812291a 100644 --- a/src/main/java/net/minestom/server/command/builder/CommandDispatcher.java +++ b/src/main/java/net/minestom/server/command/builder/CommandDispatcher.java @@ -198,6 +198,7 @@ public class CommandDispatcher { final CommandSyntax finalSyntax = findMostCorrectSyntax(validSyntaxes, syntaxesValues, executorArgs); if (finalSyntax != null) { // A fully correct syntax has been found, use it + result.syntax = finalSyntax; result.executor = finalSyntax.getExecutor(); result.arguments = executorArgs; return result; @@ -333,6 +334,8 @@ public class CommandDispatcher { private Command command; // Command Executor + private CommandSyntax syntax; + private CommandExecutor executor; private Arguments arguments; @@ -353,17 +356,27 @@ public class CommandDispatcher { public void execute(@NotNull CommandSender source, @NotNull String commandString) { // Global listener command.globalListener(source, arguments, commandString); - // Condition check + // Command condition check final CommandCondition condition = command.getCondition(); if (condition != null) { - final boolean result = condition.apply(source); + final boolean result = condition.canUse(source, commandString); if (!result) return; } // Condition is respected if (executor != null) { // An executor has been found - executor.apply(source, arguments); + + if (syntax != null) { + // The executor is from a syntax + final CommandCondition commandCondition = syntax.getCommandCondition(); + if (commandCondition == null || commandCondition.canUse(source, commandString)) { + executor.apply(source, arguments); + } + } else { + // The executor is probably the default one + executor.apply(source, arguments); + } } else if (callback != null) { // No syntax has been validated but the faulty argument with a callback has been found // Execute the faulty argument callback diff --git a/src/main/java/net/minestom/server/command/builder/CommandSyntax.java b/src/main/java/net/minestom/server/command/builder/CommandSyntax.java index 0d518a6a2..803fa9a78 100644 --- a/src/main/java/net/minestom/server/command/builder/CommandSyntax.java +++ b/src/main/java/net/minestom/server/command/builder/CommandSyntax.java @@ -1,7 +1,10 @@ package net.minestom.server.command.builder; import net.minestom.server.command.builder.arguments.Argument; +import net.minestom.server.command.builder.condition.CommandCondition; +import net.minestom.server.entity.Player; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; /** * Represents a syntax in {@link Command} @@ -9,22 +12,39 @@ import org.jetbrains.annotations.NotNull; */ public class CommandSyntax { - private final Argument[] args; + private CommandCondition commandCondition; private CommandExecutor executor; + private final Argument[] args; - protected CommandSyntax(@NotNull CommandExecutor commandExecutor, @NotNull Argument... args) { + protected CommandSyntax(@Nullable CommandCondition commandCondition, + @NotNull CommandExecutor commandExecutor, + @NotNull Argument... args) { + this.commandCondition = commandCondition; this.executor = commandExecutor; this.args = args; } /** - * Gets all the required {@link Argument} for this syntax. + * Gets the condition to use this syntax. * - * @return the required arguments + * @return this command condition, null if none */ - @NotNull - public Argument[] getArguments() { - return args; + @Nullable + public CommandCondition getCommandCondition() { + return commandCondition; + } + + /** + * Changes the command condition of this syntax. + *

+ * Be aware that changing the command condition will not automatically update players auto-completion. + * You can create a new packet containing the changes with + * {@link net.minestom.server.command.CommandManager#createDeclareCommandsPacket(Player)}. + * + * @param commandCondition the new command condition, null to remove it + */ + public void setCommandCondition(@Nullable CommandCondition commandCondition) { + this.commandCondition = commandCondition; } /** @@ -46,4 +66,14 @@ public class CommandSyntax { this.executor = executor; } + /** + * Gets all the required {@link Argument} for this syntax. + * + * @return the required arguments + */ + @NotNull + public Argument[] getArguments() { + return args; + } + } diff --git a/src/main/java/net/minestom/server/command/builder/condition/CommandCondition.java b/src/main/java/net/minestom/server/command/builder/condition/CommandCondition.java index 243872ba9..751e3ef5d 100644 --- a/src/main/java/net/minestom/server/command/builder/condition/CommandCondition.java +++ b/src/main/java/net/minestom/server/command/builder/condition/CommandCondition.java @@ -2,10 +2,29 @@ package net.minestom.server.command.builder.condition; import net.minestom.server.command.CommandSender; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; /** - * Used to know if the {@link CommandSender} is allowed to run the command. + * Used to know if the {@link CommandSender} is allowed to run the command or a specific syntax. */ +@FunctionalInterface public interface CommandCondition { - boolean apply(@NotNull CommandSender source); + + /** + * Called when the sender permission needs to be checked. + *

+ * The first time will be during player connection in order to know + * if the command/syntax should be displayed as tab-completion suggestion, + * {@code commandString} will be null in this case. + *

+ * Otherwise, {@code commandString} will never be null + * but will instead be the raw command string given by the sender. + * You should in this case warn the sender (eg by sending a message) if the condition is unsuccessful. + * + * @param source the sender of the command + * @param commandString the raw command string, + * null if the method has been called at player login + * @return true if the sender has the right to use the command, false otherwise + */ + boolean canUse(@NotNull CommandSender source, @Nullable String commandString); } diff --git a/src/test/java/demo/Main.java b/src/test/java/demo/Main.java index e49d54cb1..ba8a5f9d5 100644 --- a/src/test/java/demo/Main.java +++ b/src/test/java/demo/Main.java @@ -3,7 +3,7 @@ package demo; import demo.blocks.BurningTorchBlock; import demo.blocks.StoneBlock; import demo.blocks.UpdatableBlockDemo; -import demo.commands.*; +import demo.commands.TestCommand; import net.minestom.server.MinecraftServer; import net.minestom.server.command.CommandManager; import net.minestom.server.instance.block.BlockManager; @@ -27,14 +27,14 @@ public class Main { blockManager.registerBlockPlacementRule(new RedstonePlacementRule()); CommandManager commandManager = MinecraftServer.getCommandManager(); - commandManager.register(new EntitySelectorCommand()); commandManager.register(new TestCommand()); + /*commandManager.register(new EntitySelectorCommand()); commandManager.register(new HealthCommand()); commandManager.register(new SimpleCommand()); commandManager.register(new GamemodeCommand()); commandManager.register(new DimensionCommand()); commandManager.register(new ShutdownCommand()); - commandManager.register(new TeleportCommand()); + commandManager.register(new TeleportCommand());*/ commandManager.setUnknownCommandCallback((sender, command) -> sender.sendMessage("unknown command")); diff --git a/src/test/java/demo/PlayerInit.java b/src/test/java/demo/PlayerInit.java index 31d437949..1c1bd80cb 100644 --- a/src/test/java/demo/PlayerInit.java +++ b/src/test/java/demo/PlayerInit.java @@ -172,7 +172,7 @@ public class PlayerInit { player.addEventCallback(PlayerSpawnEvent.class, event -> { player.setGameMode(GameMode.SURVIVAL); - if(event.isFirstSpawn()){ + if (event.isFirstSpawn()) { player.teleport(new Position(0, 64f, 0)); } diff --git a/src/test/java/demo/commands/GamemodeCommand.java b/src/test/java/demo/commands/GamemodeCommand.java index 8b715a19c..3518a2e10 100644 --- a/src/test/java/demo/commands/GamemodeCommand.java +++ b/src/test/java/demo/commands/GamemodeCommand.java @@ -70,7 +70,7 @@ public class GamemodeCommand extends Command { sender.sendMessage("'" + gamemode + "' is not a valid gamemode!"); } - private boolean isAllowed(CommandSender sender) { + private boolean isAllowed(CommandSender sender, String commandString) { if (!sender.isPlayer()) { sender.sendMessage("The command is only available for player"); return false; diff --git a/src/test/java/demo/commands/HealthCommand.java b/src/test/java/demo/commands/HealthCommand.java index 93533f89f..5bbbf3668 100644 --- a/src/test/java/demo/commands/HealthCommand.java +++ b/src/test/java/demo/commands/HealthCommand.java @@ -28,7 +28,7 @@ public class HealthCommand extends Command { addSyntax(this::execute, arg0); } - private boolean condition(CommandSender sender) { + private boolean condition(CommandSender sender, String commandString) { if (!sender.isPlayer()) { sender.sendMessage("The command is only available for player"); return false; diff --git a/src/test/java/demo/commands/TestCommand.java b/src/test/java/demo/commands/TestCommand.java index 219ddc972..66fdd970c 100644 --- a/src/test/java/demo/commands/TestCommand.java +++ b/src/test/java/demo/commands/TestCommand.java @@ -17,8 +17,9 @@ public class TestCommand extends Command { //addSyntax(this::execute, dynamicWord); } - Argument test = ArgumentType.Word("test").from("hey"); - Argument num = ArgumentType.Integer("num"); + Argument test = ArgumentType.Word("wordT"); + Argument testt = ArgumentType.Word("wordTt"); + Argument test2 = ArgumentType.StringArray("array"); setDefaultExecutor((source, args) -> { System.out.println("DEFAULT"); @@ -31,7 +32,7 @@ public class TestCommand extends Command { addSyntax((source, args) -> { System.out.println(2); - }, test, num); + }, test, test2); } private void usage(CommandSender sender, Arguments arguments) {