命令执行器
本页面专门介绍 ArgumentBuilder 类中的 executes(...) 方法。
检查 executes 方法
executes 方法定义如下:
public T executes(Command<S> command);
Command<S> 接口被声明为 FunctionalInterface。这意味着我们不必传入实现该接口的类,而可以直接传入一个 lambda 表达式。
@FunctionalInterface
public interface Command<S> {
int SINGLE_SUCCESS = 1;
int run(CommandContext<S> ctx) throws CommandSyntaxException;
}
我们的 lambda 有一个参数并返回一个整数。这本质上就是接口中定义的 run 方法。唯一的参数 CommandContext<S> 是我们获取
所有关于执行该命令的发送者和所有命令参数信息的地方。它有很多方法,但对我们最有用的是 S getSource() 和 V getArgument(String, Class<V>)。
我们在参数和字面量章节中简要介绍了 getArgument(...),简单来说,这是我们可以检索参数的方法。稍后会有更具体的例子。
你应该主要注意 getSource() 方法的泛型参数 S。这是命令源的类型。对于 executes 方法,这个类型始终是 CommandSourceStack。
该类本身有三个方法:Location getLocation()、CommandSender getSender() 和 @Nullable Entity getExecutor()。
其中最常用的方法是 getSender(),因为它是实际运行命令的命令发送者。对于命令的目标,你应该使用 getExecutor(),
这在命令是通过 /execute as <entity> run <our_command> 运行时很重要。这不是必需的,但被视为良好实践。
示例:Flyspeed 命令
在参数和字面量章节中,我们简要声明了一个带有范围浮点参数的 /flyspeed 命令的结构。
但该命令实际上并没有设置执行玩家的飞行速度。为了做到这一点,我们需要在其上添加一个执行器,像这样:
Commands.literal("flyspeed")
.then(Commands.argument("speed", FloatArgumentType.floatArg(0, 1.0f))
.executes(ctx -> {
float speed = FloatArgumentType.getFloat(ctx, "speed"); // 检索速度参数
CommandSender sender = ctx.getSource().getSender(); // 检索命令发送者
Entity executor = ctx.getSource().getExecutor(); // 检索命令执行者,可能与发送者相同也可能不同
// 检查执行者是否为玩家,因为只能设置玩家的飞行速度
if (!(executor instanceof Player player)) {
// 如果非玩家试图设置自己的飞行速度
sender.sendPlainMessage("只有玩家才能飞行!");
return Command.SINGLE_SUCCESS;
}
// 设置玩家的速度
player.setFlySpeed(speed);
if (sender == executor) {
// 如果玩家自己执行了命令
player.sendPlainMessage("成功将你的飞行速度设置为 " + speed);
return Command.SINGLE_SUCCESS;
}
// 如果速度是由其他发送者设置的(比如使用 /execute)
sender.sendRichMessage("成功将 <playername> 的飞行速度设置为 " + speed, Placeholder.component("playername", player.name()));
player.sendPlainMessage("你的飞行速度已被设置为 " + speed);
return Command.SINGLE_SUCCESS;
})
);
解释
这里有很多内容需要解释,让我们从上到下逐一分析:
前几行定义了一个 /flyspeed 命令根,带有一个名为"speed"的浮点参数,该参数只允许 0 到 1 之间的值。
然后我们向参数分支添加一个执行子句并通过运行 FloatArgumentType.getFloat 检索速度参数。
注意高亮的行。我们首先从 CommandContext<CommandSourceStack> 中检索 CommandSourceStack,然后最终检索其发送者和执行者。
CommandSender 是一个接口,它声明了 sendMessage(...)、getServer() 和 getName() 方法。它被所有实体(包括玩家)
和 ConsoleCommandSender(当控制台执行命令时使用)实现。
接下来我们检查我们的执行者对象是否也是 Player 接口的实例。如果执行者为 null,这将为 false,这就是为什么我们不需要 null 检查。
如果表达式评估为 true,我们得到一个新的 player 变量,它代表命令执行者的实际服务器玩家。
接下来,我们使用从玩家提供的浮点参数中检索的值设置玩家的飞行速度,并向他们发送一条消息确认操作。 建议总是发送一条确认消息表明命令是否成功,因为否则玩家可能会对命令"不工作"感到困惑。 如果执行者不是玩家,我们可以发送一种错误消息。在我们的例子中,我们假设发送者是控制台,因为实体通常不会尝试发送这样的命令。
最后,我们只需从 lambda 语句返回一个返回值。由于我们的命令成功了,我们可以返回 Command.SINGLE_SUCCESS,其值为 1。
别忘了关闭所有的大括号!
现在运行命令可以正常工作:
我们甚至可以使用 /execute as 作为另一个玩家运行它:

逻辑分离
有时,如果命令太大或出于个人偏好,你可能不想在 executes 方法中放置你的逻辑代码,因为由于缩进量的原因可能难以阅读。 在这种情况下,我们可以不在 lambda 语句中定义逻辑,而是使用方法引用。为此,我们可以直接将方法引用传递给 executes 方法。 这可能看起来像这样:
public class FlightSpeedCommand {
public static LiteralArgumentBuilder<CommandSourceStack> createCommand() {
return Commands.literal("flyspeed")
.then(Commands.argument("speed", FloatArgumentType.floatArg(0, 1.0f))
.executes(FlightSpeedCommand::runFlySpeedLogic)
);
}
private static int runFlySpeedLogic(CommandContext<CommandSourceStack> ctx) {
float speed = FloatArgumentType.getFloat(ctx, "speed"); // 检索速度参数
CommandSender sender = ctx.getSource().getSender(); // 检索命令发送者
Entity executor = ctx.getSource().getExecutor(); // 检索命令执行者,可能与发送者相同也可能不同
// 检查执行者是否为玩家,因为只能设置玩家的飞行速度
if (!(executor instanceof Player player)) {
// 如果非玩家试图设置自己的飞行速度
sender.sendPlainMessage("只有玩家才能飞行!");
return Command.SINGLE_SUCCESS;
}
// 设置玩家的速度
player.setFlySpeed(speed);
if (sender == executor) {
// 如果玩家自己执行了命令
player.sendPlainMessage("成功将你的飞行速度设置为 " + speed);
return Command.SINGLE_SUCCESS;
}
// 如果速度是由其他发送者设置的(比如使用 /execute)
sender.sendRichMessage("成功将 <playername> 的飞行速度设置为 " + speed, Placeholder.component("playername", player.name()));
player.sendPlainMessage("你的飞行速度已被设置为 " + speed);
return Command.SINGLE_SUCCESS;
}
}