基础命令
对于非常简单的命令,Paper 提供了一种通过实现 BasicCommand 接口来声明 Bukkit 风格命令的方式。
这个接口有一个你必须重写的方法:
void execute(CommandSourceStack commandSourceStack, String[] args)
还有三个可选的方法,你可以选择重写,但不是必须的:
Collection<String> suggest(CommandSourceStack commandSourceStack, String[] args)boolean canUse(CommandSender sender)@Nullable String permission()
简单用法
实现 execute 方法,你的类可能看起来像这样:
package your.package.name;
import io.papermc.paper.command.brigadier.BasicCommand;
import io.papermc.paper.command.brigadier.CommandSourceStack;
import org.jspecify.annotations.NullMarked;
@NullMarked
public class YourCommand implements BasicCommand {
@Override
public void execute(CommandSourceStack commandSourceStack, String[] args) {
}
}
如果你之前见过 CommandContext<CommandSourceStack> 类,你可能会认出 execute 方法的第一个参数就是我们的 CommandContext<S> 中的泛型
参数 S,它也在 ArgumentBuilder 的 executes 方法中使用。
通过 CommandSourceStack,我们可以获取关于命令发送者的基本信息、命令发送的位置以及执行实体。
更多信息,请查看basics/command-executors。
可选方法
你可以自由选择是否实现上面提到的任何可选方法。以下是对每个方法功能的快速概述:
suggest(CommandSourceStack, String[])
这个方法返回某种 Collection<String> 并接收 CommandSourceStack 和 String[] args 作为参数。这类似于
TabCompleter 接口的 onTabComplete(CommandSender, Command, String, String[]) 方法,它用于 Bukkit 命令的 Tab 补全。
你返回的集合中的每个条目都会以与 Bukkit 命令相同的方式发送给客户端,显示为建议。
canUse(CommandSender)
通过这个方法,你可以设置来自 Brigadier 命令的基本 requires 结构。你可以在这里阅读更多相关内容。
这个方法返回一个 boolean,需要返回 true 才能让命令发送者执行该命令。
permission()
通过 permission 方法,你可以像 canUse 方法一样,设置执行和查看此命令所需的权限。
示例:广播命令
作为示例,我们可以创建一个简单的广播命令。我们首先通过创建一个实现 BasicCommand 并重写 execute 和 permission 的类开始:
package your.package.name;
import io.papermc.paper.command.brigadier.BasicCommand;
import io.papermc.paper.command.brigadier.CommandSourceStack;
import org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.Nullable;
@NullMarked
public class BroadcastCommand implements BasicCommand {
@Override
public void execute(CommandSourceStack commandSourceStack, String[] args) {
}
@Override
public @Nullable String permission() {
return "example.broadcast.use";
}
}
我们的权限设置为 example.broadcast.use。为了给自己这个权限,建议你使用像 LuckPerms 这样的插件或者直接给自己
操作员权限。你也可以将此权限默认设置为 true。关于这一点,请查看 plugin.yml 文档。
现在,在我们的 execute 方法中,我们可以获取该命令执行者的名称。如果我们找不到执行者,我们可以直接获取命令发送者的名称,像这样:
final Component name = commandSourceStack.getExecutor() != null
? commandSourceStack.getExecutor().name()
: commandSourceStack.getSender().name();
这确保我们涵盖了所有情况,甚至允许命令通过 /execute as 正确工作。
接下来,我们检索所有参数并将它们连接成一个字符串,如果他们没有定义参数(意味着 args 长度为 0),则告诉发送者需要至少一个参数才能发送广播:
if (args.length == 0) {
commandSourceStack.getSender().sendRichMessage("<red>你不能发送空的广播!");
return;
}
final String message = String.join(" ", args);
最后,我们可以构建我们的广播消息并通过 Bukkit.broadcast(Component) 发送:
final Component broadcastMessage = MiniMessage.miniMessage().deserialize(
"<red><bold>广播</red> <name> <dark_gray>»</dark_gray> <message>",
Placeholder.component("name", name),
Placeholder.unparsed("message", message)
);
Bukkit.broadcast(broadcastMessage);
大功告成!如你所见,这是一种非常简单的定义命令的方式。这是我们类的最终结果:
package your.package.name;
import io.papermc.paper.command.brigadier.BasicCommand;
import io.papermc.paper.command.brigadier.CommandSourceStack;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.minimessage.MiniMessage;
import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder;
import org.bukkit.Bukkit;
import org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.Nullable;
@NullMarked
public class BroadcastCommand implements BasicCommand {
@Override
public void execute(CommandSourceStack commandSourceStack, String[] args) {
final Component name = commandSourceStack.getExecutor() != null
? commandSourceStack.getExecutor().name()
: commandSourceStack.getSender().name();
if (args.length == 0) {
commandSourceStack.getSender().sendRichMessage("<red>你不能发送空的广播!");
return;
}
final String message = String.join(" ", args);
final Component broadcastMessage = MiniMessage.miniMessage().deserialize(
"<red><bold>广播</red> <name> <dark_gray>»</dark_gray> <message>",
Placeholder.component("name", name),
Placeholder.unparsed("message", message)
);
Bukkit.broadcast(broadcastMessage);
}
@Override
public @Nullable String permission() {
return "example.broadcast.use";
}
}
注册我们的命令看起来像这样:
@Override
public void onEnable() {
this.getLifecycleManager().registerEventHandler(LifecycleEvents.COMMANDS,
event -> event.registrar().register("broadcast", new BroadcastCommand())
);
}
这就是它在游戏中的样子:
示例:添加建议
我们的广播命令工作得很好,但它缺少建议功能。对于基于文本的命令,一种非常常见的建议是玩家名称。 为了建议玩家名称,我们可以简单地将所有在线玩家映射到他们的名称,像这样:
@Override
public Collection<String> suggest(CommandSourceStack commandSourceStack, String[] args) {
return Bukkit.getOnlinePlayers().stream().map(Player::getName).toList();
}
这很好用,但如你所见,它总是会建议所有玩家,不管用户输入什么,这有时会感觉不自然:
为了解决这个问题,我们需要做一些改变:
首先,如果没有参数,我们提前返回我们已经有的内容,因为这时我们无法按输入过滤:
if (args.length == 0) {
return Bukkit.getOnlinePlayers().stream().map(Player::getName).toList();
}
之后,我们可以在我们的流中添加一个 filter 子句,在这里我们过滤所有以当前输入开头的名称,即 args[args.length - 1]:
return Bukkit.getOnlinePlayers().stream()
.map(Player::getName)
.filter(name -> name.toLowerCase().startsWith(args[args.length - 1].toLowerCase()))
.toList();
大功告成!如你所见,建议仍然正常工作:
但当没有玩家的名字以输入开头时,它就不会提供任何建议:
最终代码
这是我们整个 BroadcastCommand 类的最终代码,包括建议功能:
package your.package.name;
import io.papermc.paper.command.brigadier.BasicCommand;
import io.papermc.paper.command.brigadier.CommandSourceStack;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.minimessage.MiniMessage;
import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.Nullable;
import java.util.Collection;
@NullMarked
public class BroadcastCommand implements BasicCommand {
@Override
public void execute(CommandSourceStack commandSourceStack, String[] args) {
final Component name = commandSourceStack.getExecutor() != null
? commandSourceStack.getExecutor().name()
: commandSourceStack.getSender().name();
if (args.length == 0) {
commandSourceStack.getSender().sendRichMessage("<red>你不能发送空的广播!");
return;
}
final String message = String.join(" ", args);
final Component broadcastMessage = MiniMessage.miniMessage().deserialize(
"<red><bold>广播</red> <name> <dark_gray>»</dark_gray> <message>",
Placeholder.component("name", name),
Placeholder.unparsed("message", message)
);
Bukkit.broadcast(broadcastMessage);
}
@Override
public @Nullable String permission() {
return "example.broadcast.use";
}
@Override
public Collection<String> suggest(CommandSourceStack commandSourceStack, String[] args) {
if (args.length == 0) {
return Bukkit.getOnlinePlayers().stream().map(Player::getName).toList();
}
return Bukkit.getOnlinePlayers().stream()
.map(Player::getName)
.filter(name -> name.toLowerCase().startsWith(args[args.length - 1].toLowerCase()))
.toList();
}
}