User Tools

Site Tools


zh_cn:tutorial:commands

Differences

This shows you the differences between two versions of the page.

Link to this comparison view

Both sides previous revisionPrevious revision
Next revision
Previous revision
Last revisionBoth sides next revision
zh_cn:tutorial:commands [2023/11/18 10:13] – [注册一个基本的命令] solidblockzh_cn:tutorial:commands [2023/11/19 02:01] – [子命令] solidblock
Line 1: Line 1:
- 
 条款:本文中的代码适用于“Creative Commons Zero v1.0 Universial”条款,允许您将文中的代码示例用于自己的模组中。 条款:本文中的代码适用于“Creative Commons Zero v1.0 Universial”条款,允许您将文中的代码示例用于自己的模组中。
  
Line 6: Line 5:
 ====== 创建命令 ====== ====== 创建命令 ======
  
-创建命令允许模组开发者添加可以使用命令实现的功能。本教程将会教你如何注册命令,以及Brigadier的基本命令结构。 +创建命令允许模组开发者添加可以使用命令实现的功能。本教程将会教你如何注册命令,以及 Brigadier 的基本命令结构。
- +
-注意:本文的所有代码都是用于 1.19.2 的。对于旧的版本,一些版本和映射可能不同+
  
 ===== Brigadier 是什么? ===== ===== Brigadier 是什么? =====
Line 29: Line 26:
 }; };
 </code> </code>
- 
-在原版 Minecraft 中,命令通常用作方法引用,例如原版的 ''XXXCommand'' 类中名叫 ''register'' 的静态方法。 
  
 这个整数相当于命令的结果。在 Minecraft 中。通常来说,负值表示命令执行失败,什么也不做,''0'' 表示命令被略过,正数则表示命令执行成功并做了一些事情。 这个整数相当于命令的结果。在 Minecraft 中。通常来说,负值表示命令执行失败,什么也不做,''0'' 表示命令被略过,正数则表示命令执行成功并做了一些事情。
Line 36: Line 31:
 ==== ServerCommandSource 是做什么的? ==== ==== ServerCommandSource 是做什么的? ====
  
-''ServerCommandSource'' 提供了命令运行时的一些环境,这些环境拥有特定的实现,包括获取运行这个命令的实体、命令执行时所在的世界以及服务器。+''ServerCommandSource'' 提供了命令运行时的一些外环境,这些环境拥有特定的实现,包括获取运行这个命令的实体、命令执行时所在的世界以及服务器。
  
 <code java [enable_line_numbers="true"]> <code java [enable_line_numbers="true"]>
Line 95: Line 90:
     CommandRegistrationCallback.EVENT.register((dispatcher, registryAccess, environment) -> dispatcher.register(literal("foo")     CommandRegistrationCallback.EVENT.register((dispatcher, registryAccess, environment) -> dispatcher.register(literal("foo")
         .executes(context -> {         .executes(context -> {
-      // 对于 1.19 以下的版本,把“Text.literal”替换为“new LiteralText”。 +      // 对于 1.19 之前的版本,把“Text.literal”替换为“new LiteralText”。 
-      context.getSource().sendFeedback(Text.literal("调用 /foo,不带参数。"), false); +      // 对于 1.20 之的版本,请移除“() ->”
-      // 对于 1.20 之的版本,请使用以下代码,这是为了避免在不需要反馈时也创建了文本组件对象+
       context.getSource().sendFeedback(() -> Text.literal("调用 /foo,不带参数"), false);       context.getSource().sendFeedback(() -> Text.literal("调用 /foo,不带参数"), false);
  
Line 108: Line 102:
 **请确保你导入了正确的静态方法。**方法 ''literal'' 是 ''CommandManager.literal''。你也可以清楚地写 ''CommandManager.literal'' 而不是使用静态导入。''CommandManager.literal("foo")'' 会告诉 brigadier,命令有一个节点,''foo'' 这个**字面的**节点。 **请确保你导入了正确的静态方法。**方法 ''literal'' 是 ''CommandManager.literal''。你也可以清楚地写 ''CommandManager.literal'' 而不是使用静态导入。''CommandManager.literal("foo")'' 会告诉 brigadier,命令有一个节点,''foo'' 这个**字面的**节点。
  
-在 ''sendFeedback'' 方法中,第一个参数是需要发送的文本,在 1.20 以下的版本中是 ''Text'',在 1.20 之后的版本是 ''Supplier<Text>''。第二个参数决定了命令是否要将反馈的内容发送给其他的管理员。如果命令是//查询//一些内容,比如查询当前的时间或者某玩家的分数,则应该是 ''false''。如果命令实际上//做了些//事情,例如修改时间或者分数,那么则应该是 ''true''。如果游戏规则 ''sendCommandFeedback'' 是 false,那么你不会收到反馈。+在 ''sendFeedback'' 方法中,第一个参数是需要发送的文本,在 1.20 之前的版本中是 ''Text'',在 1.20 以及之后的版本是 ''Supplier<Text>''(这是为了避免在不需要的时候实例化了 ''Text'' 对象,因此请不要使用 ''Suppliers.ofInstance'' 或类似方法)。第二个参数决定了命令是否要将反馈的内容发送给其他的管理员。如果命令是//查询//一些内容,比如查询当前的时间或者某玩家的分数,则应该是 ''false''。如果命令实际上//做了些//事情,例如修改时间或者分数,那么则应该是 ''true''。如果游戏规则 ''sendCommandFeedback'' 是 false,那么你不会收到反馈。如果命令执行者被通过 ''/execute as ...'' 修改,反馈则会发送给原始的执行者。 
 + 
 +如果命令失败,可以不必调用 ''sendFeedback'',而是直接抛出 ''CommandSyntaxException'' 或 ''<yarn class_2164>''。具体请参见 [[command_exceptions]]
  
 要执行命令,必须输入 ''/foo'',这是大小写敏感的。如果输入 ''/Foo''、''/FoO''、''/FOO''、''/fOO'' 或者 ''/fooo'',命令不会运行。 要执行命令,必须输入 ''/foo'',这是大小写敏感的。如果输入 ''/Foo''、''/FoO''、''/FOO''、''/fOO'' 或者 ''/fooo'',命令不会运行。
Line 131: Line 127:
 在上面的例子中,使用了静态导入以简化代码。对于字面值,语句会简化为 ''literal("foo")'',这也适用于获取参数的值,把 ''StringArgumentType.getString(ctx, "string")'' 简化为 ''getString(ctx, "string")''。这也适用于 Minecraft 自己的参数类型。 在上面的例子中,使用了静态导入以简化代码。对于字面值,语句会简化为 ''literal("foo")'',这也适用于获取参数的值,把 ''StringArgumentType.getString(ctx, "string")'' 简化为 ''getString(ctx, "string")''。这也适用于 Minecraft 自己的参数类型。
  
-Below is an example of some static imports:+以下是一些静态导入的例子。
 <code java [enable_line_numbers="true"]> <code java [enable_line_numbers="true"]>
 // getString(ctx, "string") // getString(ctx, "string")
Line 145: Line 141:
 </code> </code>
  
-注意:请确保使用了 ''CommandManager'' 中的 ''literall'' 和 ''argument'',而非其他类中的,否则编译时存在泛型擦除问题,因为类型参数 ''S'' 应该是 ''ServerCommandSource''。(对于客户端的命令,请使用 ''ClientCommandManager''。)+注意:请确保使用了 ''CommandManager'' 中的 ''literal'' 和 ''argument'',而非其他类中的,否则编译时存在泛型擦除问题,因为类型参数 ''S'' 应该是 ''ServerCommandSource''。(对于客户端的命令,请使用 ''ClientCommandManager''。)
  
 Brigadier 的默认参数位于 ''com.mojang.brigadier.arguments'' Brigadier 的默认参数位于 ''com.mojang.brigadier.arguments''
Line 159: Line 155:
 <code java [enable_line_numbers="true"]> <code java [enable_line_numbers="true"]>
 dispatcher.register(literal("foo") dispatcher.register(literal("foo")
-  .requires(source -> source.hasPermissionLevel(4))+  .requires(source -> source.hasPermissionLevel(2))
   .executes(ctx -> {   .executes(ctx -> {
     ctx.getSource().sendFeedback(() -> Text.literal("你是 OP"), false);     ctx.getSource().sendFeedback(() -> Text.literal("你是 OP"), false);
Line 165: Line 161:
   });   });
 </code> </code>
-此时命令只会在命令源为 级以上时运行,否则命令不会被注册。这样做的副作用就是,非 级管理员会看到命令不会被 tab 补全,这也就是为什么没有启用作弊时不能够 tab 补全大多数命令。+此时命令只会在命令源为 级以上(包括命令方块)时运行,否则命令不会被注册。这样做的副作用就是,非 级管理员会看到命令不会被 tab 补全,这也就是为什么没有启用作弊时不能够 tab 补全大多数命令。 
 + 
 +要创建只有 4 级管理员(不包括命令方块)可以执行的命令,请使用 ''source.hasPermissionLevel(4)''
  
 ===== 参数 ===== ===== 参数 =====
  
-Brigadier 中的参数会解析任何输入的参数,并检查错误Minecraft 创建了些特殊的参数类型以用于自己使用例如 ''EntityArgumentType''表示游戏内的实体选择器 ''@a, @r, @p, @e[type=!player, limit=1, distance=..2]'',又如 ''NbtTagArgumentType'',解析字符串化的 nbt(snbt)并验证输入的语法是否正确+大多数命令都使用了参数。一些参数是可选,也就是说如果你不提供此参数,命令仍能运行。一个节点可以有多个参数类型,但是注意有可能出现二义性这是需要避免的。
  
-**TODO:**详细介绍如何使用参数+在这个例子中。我们添加一个整数参数。并计算整数的平方。
  
-===== 子命令 =====+<code java> 
 +    dispatcher.register(literal("mul"
 +        .then(argument("value", IntegerArgumentType.integer()) 
 +            .executes(context -> { 
 +              final int value IntegerArgumentType.getInteger(context, "value"); 
 +              final int result value * value; 
 +              context.getSource().sendFeedback(() -> Text.literal("%s × %s %s".formatted(value, value, result)), false); 
 +              return result; 
 +            }))); 
 +</code>
  
-要添加命令,你需要先照常注册注册第一个字面节点+在这个例中,在 ''/mul'' 之后,你需要输入一个整数。例如,如果你输入 ''/mul 3'',会收到消息“3 × 3 = 9”。如果你输入 ''/mul'' 不带参数,命令无法正确解析
  
-<code java [enable_line_numbers="true"]+注意:为了简便,''IntegerArgumentType.integer'' 和 ''IntegerArgumentType.getInteger'' 可以替换为 ''integer'' 和 ''getInteger'' 同时使用静态导入。为了显得更加清楚,这个例子不使用静态导入。 
-dispatcher.register(literal("foo"))+ 
 +然后我们添加可选的第二个参数: 
 +<code java
 +    dispatcher.register(literal("mul"
 +        .then(argument("value", IntegerArgumentType.integer()) 
 +            .executes(context -> { 
 +              final int value IntegerArgumentType.getInteger(context, "value"); 
 +              final int result = value * value; 
 +              context.getSource().sendFeedback(() -Text.literal("%s × %s = %s".formatted(value, value, result)), false); 
 +              return result; 
 +            }) 
 +            .then(argument("value2", IntegerArgumentType.integer()) 
 +                .executes(context -> { 
 +                  final int value = IntegerArgumentType.getInteger(context, "value"); 
 +                  final int value2 = IntegerArgumentType.getInteger(context, "value2"); 
 +                  final int result = value * value2; 
 +                  context.getSource().sendFeedback(() -> Text.literal("%s × %s = %s".formatted(value, value2, result)), false); 
 +                  return result; 
 +                }))));
 </code> </code>
  
-为拥有子命令,需要把下一个节点追加到已经存在节点后面,这是利用 ''then(ArgumentBuilder)''现的,该方法接收一个 ''ArgumentBuilder''+现在你可以输入一个或者两个整数了。如果给一个整数,会计算这个整数平方。如果提供两个整数会计算两个整数的积。你可能发,两次指定类似执行内容有些不太必要。因此我们可以创建一个在两个执行中都使用的方法
  
-如下所示,创建命令 ''foo <bar>''+<code java> 
 +public class ExampleMod implements ModInitializer { 
 +  @Override 
 +  public void onInitialize() { 
 +    CommandRegistrationCallback.EVENT.register((dispatcher, registryAccess, environment) -> dispatcher.register(literal("mul"
 +        .then(argument("value", IntegerArgumentType.integer()) 
 +            .executes(context -> executeMultiply(IntegerArgumentType.getInteger(context, "value"), IntegerArgumentType.getInteger(context, "value"), context)) 
 +            .then(argument("value2", IntegerArgumentType.integer()) 
 +                .executes(context -> executeMultiply(IntegerArgumentType.getInteger(context, "value"), IntegerArgumentType.getInteger(context, "value2"), context)))))); 
 +  }
  
-<code java [enable_line_numbers="true"highlight_lines_extra="2"]+  private static int executeMultiply(int valueint value2, CommandContext<ServerCommandSourcecontext) { 
-dispatcher.register(literal("foo") +    final int result = value * value2; 
-    .then(literal("bar")) +    context.getSource().sendFeedback(() -> Text.literal("%s × %s = %s".formatted(value, value2, result)), false); 
-);+    return result; 
 +  } 
 +}
 </code> </code>
  
-建议给命令添加节点时缩进你的代码,通常来说缩进对应了命令树中有多少节点的深度,每一次换行也可以看出添加了一个节点。本教程后面会展示格式化树状命令的几种可选样式。+===== 子命令 =====
  
-**那就开始尝试运行命令**+要添加子命令,你需要先照常注册第一个字面节点。
  
-通常如果你在游戏内输入了 ''/foo bar''命令不会运行这是因为当所有条件都满足时,游戏没有可执行代码要解决这个问题你需要告诉游戏如何执行这个命令,方法就是使用 ''executes(Command)'' 方法因此一个命令看起来应该像下面这样:+<code> 
 +dispatcher.register(literal("foo")) 
 +</code> 
 + 
 +为拥有子命令,需要把下一个节点追加到已经存在节点后面 
 + 
 +如下所示创建命令 ''foo <bar>''
  
 <code java [enable_line_numbers="true", highlight_lines_extra="3,4,5,6,7"]> <code java [enable_line_numbers="true", highlight_lines_extra="3,4,5,6,7"]>
Line 202: Line 244:
         .executes(context -> {         .executes(context -> {
             // 对于 1.19 以下的版本,使用 ''new LiteralText''             // 对于 1.19 以下的版本,使用 ''new LiteralText''
-            context.getSource().sendMessage(Text.literal("调用 foo 和 bar"));+            // 对于 1.20 以下的版本,直接使用 ''Text'' 对象而非 supplier。 
 +            context.getSource().sendFeedback(() -> Text.literal("调用 foo 和 bar"), false);
  
             return 1;             return 1;
Line 210: Line 253:
 </code> </code>
  
 +建议给命令添加节点时缩进你的代码,通常来说缩进对应了命令树中有多少节点的深度,每一次换行也可以看出添加了一个节点。本教程后面会展示格式化树状命令的几种可选样式。
 +
 +类似于参数,子命令节点也可以设置为可选的。在下面这个例子中,''/foo'' 和 ''/foo bar'' 都是有效的。
 +<code java [enable_line_numbers="true"]>
 +dispatcher.register(literal("foo")
 +    .executes(context -> {
 +        context.getSource().sendFeedback(() -> Text.literal("调用 foo 不带 bar"), false);
 +        return 1;
 +    })
 +    .then(literal("bar")
 +        .executes(context -> {
 +            context.getSource().sendFeedback(() -> Text.literal("调用 foo 带有 bar"), false);
 +            return 1;
 +        })
 +    )
 +);
 +</code>
  
 ====== 高级概念 ====== ====== 高级概念 ======
Line 224: Line 284:
 ====== 常见问题 ====== ====== 常见问题 ======
  
-===== 命令为什么不编译 =====+===== 代码为什么不编译 ===== 
 +此问题可能有一些常见的原因。
  
-有两种可能的原因。 +  * **捕获或抛出 CommandSyntaxException:**''CommandSyntaxException'' 不是 ''RuntimeException'',如果抛出,则抛出的地方所在方法必须在方法签名中也抛出 ''CommandSyntaxException'',或者捕获。Brigadier 可以处理异常,并在游戏内你提供适当的错误消息。 
- +  * **泛型问题:**你可能遇到了泛型问题。如果你在注册服务器命令(大多数情况都是如此)确保静态导入中使用 ''CommandManager.literal(...)'' 或 ''CommandManager.argument(...)''不是''LiteralArgumentBuilder.literal'' 或 ''RequiredArgumentBuilder.argument''。 
-==== 捕获或抛出 CommandSyntaxException ==== +  * **检查 ''sendFeedback'' 方法:**你可能忘记了提供第二个参数(一个布尔值)。还需要注意,从 1.20 开始,第一个参数是 ''Supplier<Text>'' 而不是 ''Text''。 
- +  * **''Command'' 应该返回整数:**注册命令时,''executes'' 方法接受一个 ''Command'' 对象,通常是 lambda。这个 lambda 应该返回整数,而不是其他的类型
-这个问题的解决方法就是让 ''run'' 或者 ''suggest'' 方法抛出 ''CommandSyntaxException''。Brigadier 处理已检查的异常,并在游戏内你提供适当的错误消息。 +
- +
-==== 泛型问题 ==== +
- +
-你可能有时出现了泛型问题,请检查静态导入里面的是不是 ''CommandManager.literal(...)'' 或 ''CommandManager.argument(...)''非 ''LiteralArgumentBuilder'' 或 ''RequiredArgumentBuilder''+
  
 ===== 可以注册客户端命令吗? ===== ===== 可以注册客户端命令吗? =====
  
-Fabric 有个 ClientCommandManager,可以注册客户端命令。+Fabric 有个 ClientCommandManager,可以注册客户端命令。代码应该仅存在于客户端的代码中。例子:
  
-===== 黑科技 =====+<code java> 
 +    ClientCommandRegistrationCallback.EVENT.register((dispatcher, registryAccess) -> dispatcher.register(ClientCommandManager.literal("foo_client"
 +        .executes(context -> { 
 +              context.getSource().sendFeedback(Text.literal("此命令是客户端执行的!")); 
 +              return 1; 
 +            } 
 +        ))); 
 +</code>
  
-一些可但是推荐的做法:+如果你需要在客户端命令执劳教中打开屏幕,要直接调用 ''client.setScreen(...)'',你应该调用 ''%%client.execute(() -> client.setScreen(...))%%'',其中变量 ''client'' 可以通过 ''context.getSource().getClient()'' 获得。
  
-==== 可以运行时注册命令吗? ====+===== 可以运行时注册命令吗? =====
  
 可以这么做但是不推荐,你可以从服务器中获取 ''CommandManager'' 并向里面添加你希望添加到 ''CommandDispatcher'' 中的任何内容。 可以这么做但是不推荐,你可以从服务器中获取 ''CommandManager'' 并向里面添加你希望添加到 ''CommandDispatcher'' 中的任何内容。
Line 250: Line 313:
 然后你需要通过 ''CommandManager.sendCommandTree(ServerPlayerEntity)'' 向每个玩家再次发送命令树,之所以要这么做,是因为客户端已经缓存了命令树并在登录过程中(或发出管理员封包时)使用,以用于本地的补全和错误消息。 然后你需要通过 ''CommandManager.sendCommandTree(ServerPlayerEntity)'' 向每个玩家再次发送命令树,之所以要这么做,是因为客户端已经缓存了命令树并在登录过程中(或发出管理员封包时)使用,以用于本地的补全和错误消息。
  
-==== 可以在运行时取消注册命令吗? ====+===== 可以在运行时取消注册命令吗? =====
  
 可以这么做,但是这更不稳定,并且可能造成未预料的副作用。为简化事情,你需要在 brigadier 中使用反射并移除这个节点,然后还需要再次使用 ''sendCommandTree(ServerPlayerEntity)'' 向每个玩家发送命令树。如果不发送更新的命令树,客户端可能还是会认为命令依然存在,即使服务器已经无法执行。 可以这么做,但是这更不稳定,并且可能造成未预料的副作用。为简化事情,你需要在 brigadier 中使用反射并移除这个节点,然后还需要再次使用 ''sendCommandTree(ServerPlayerEntity)'' 向每个玩家发送命令树。如果不发送更新的命令树,客户端可能还是会认为命令依然存在,即使服务器已经无法执行。
  
zh_cn/tutorial/commands.txt · Last modified: 2023/11/19 02:02 by solidblock