跳到主要内容

比较 Brigadier 和 Bukkit 命令

实验性

Paper 的命令系统仍处于实验阶段,未来可能会发生变化。

注册命令

旧的 Bukkit 方式

要注册 Bukkit 命令,你需要定义一个继承 BukkitCommand 并实现 execute(...)tabComplete(...) 方法的类。这可能看起来像这样:

BukkitPartyCommand.java
package your.package.name;

import org.bukkit.Bukkit;
import org.bukkit.command.CommandSender;
import org.bukkit.command.defaults.BukkitCommand;
import org.bukkit.entity.Player;
import org.jspecify.annotations.NullMarked;

import java.util.List;

@NullMarked
public class BukkitPartyCommand extends BukkitCommand {
public BukkitPartyCommand(String name, String description, String usageMessage, List<String> aliases) {
super(name, description, usageMessage, aliases);
}

@Override
public boolean execute(CommandSender sender, String commandLabel, String[] args) {
if (args.length == 0) {
sender.sendPlainMessage("请提供一个玩家!");
return false;
}

final Player targetPlayer = Bukkit.getPlayer(args[0]);
if (targetPlayer == null) {
sender.sendPlainMessage("请提供一个有效的玩家!");
return false;
}

targetPlayer.sendPlainMessage(sender.getName() + " 开始和你一起开派对!");
sender.sendPlainMessage("你现在正在和 " + targetPlayer.getName() + " 一起开派对!");
return true;
}

@Override
public List<String> tabComplete(CommandSender sender, String alias, String[] args) throws IllegalArgumentException {
if (args.length == 1) {
return Bukkit.getOnlinePlayers().stream().map(Player::getName).toList();
}

return List.of();
}
}

之后,你可以这样定义你的命令:

PluginClass.java
this.getServer().getCommandMap().register(
this.getName().toLowerCase(),
new BukkitPartyCommand("bukkitparty", "开一个派对", "/bukkitparty <player>", List.of())
);

如你所见,你必须进行大量的手动检查才能注册一个非常简单的命令。但是 Brigadier API 是如何 做到这一点的呢?

新的 Paper 方式

首先,我们需要获取一个 LiteralCommandNode<CommandSourceStack>。这是一个特殊的 Brigadier 类,它持有某种命令树。 在我们的例子中,它是我们命令的根。我们可以通过运行 Commands.literal(final String literal) 来做到这一点,它返回一个 LiteralArgumentBuilder<CommandSourceStack>,在这里我们可以定义一些参数和执行器。一旦我们完成,我们可以调用 LiteralArgumentBuilder#build() 来获取我们构建的 LiteralCommandNode,然后我们可以注册它。这一开始听起来很复杂, 但一旦你看到它的实际应用,就不那么可怕了:

PaperPartyCommand.java
public static LiteralCommandNode<CommandSourceStack> createCommand(final String commandName) {
return Commands.literal(commandName)
.then(Commands.argument("target", ArgumentTypes.player())
.executes(ctx -> {
final PlayerSelectorArgumentResolver playerSelector = ctx.getArgument("target", PlayerSelectorArgumentResolver.class);
final Player targetPlayer = playerSelector.resolve(ctx.getSource()).getFirst();
final CommandSender sender = ctx.getSource().getSender();

targetPlayer.sendPlainMessage(sender.getName() + " 开始和你一起开派对!");
sender.sendPlainMessage("你现在正在和 " + targetPlayer.getName() + " 一起开派对!");

return Command.SINGLE_SUCCESS;
}))
.build();
}

每个 .then(...) 在我们的树中定义一个新分支,它可以是字面量(Commands.literal(String))或参数 (Commands.argument(String, ArgumentType<T>))。每个分支可以定义也可以不定义一个 .executes(Command) 执行器。这就是 所有逻辑发生的地方。

我们将在不同的页面中更详细地了解这一点,但现在,我们如何注册它呢?Paper 使用 LifecycleEventManager 系统。 简而言之,这是一种注册命令(或标签)的方式,这些命令会在服务器每次重新加载其资源时加载,比如使用 /reload。 注册我们的命令看起来像这样:

PluginClass.java
this.getLifecycleManager().registerEventHandler(LifecycleEvents.COMMANDS, commands -> {
commands.registrar().register(PaperPartyCommand.createCommand("paperparty"), "来一场愉快的派对");
});

大功告成!如你所见,这两个命令做的是同样的事情: