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
zh_cn:tutorial:commands [2021/09/17 05:00] – external edit 127.0.0.1zh_cn:tutorial:commands [2023/11/19 02:02] (current) – [可以注册客户端命令吗?] solidblock
Line 1: Line 1:
- 
 条款:本文中的代码适用于“Creative Commons Zero v1.0 Universial”条款,允许您将文中的代码示例用于自己的模组中。 条款:本文中的代码适用于“Creative Commons Zero v1.0 Universial”条款,允许您将文中的代码示例用于自己的模组中。
  
-注: 本文翻译自英文版[[tutorial:commands]],翻译未完成,且部分翻译可能不准确+注: 本文翻译自英文版[[tutorial:commands]]。
  
 ====== 创建命令 ====== ====== 创建命令 ======
  
-创建命令允许模组开发者添加可以使用命令实现的功能。本教程将会教你如何注册命令,以及Brigadier的基本命令结构。+创建命令允许模组开发者添加可以使用命令实现的功能。本教程将会教你如何注册命令,以及 Brigadier 的基本命令结构。
  
-注意:本文的所有代码都用于1.14.4的。部分yarn映射可能改变了,不过代码应该可用。+===== Brigadier 什么? =====
  
-===== Brigadier是什么? =====+Brigadier 是由 Mojang 写的用于 Minecraft 的命令解析器和派发器。Brigadier 是基于树的命令库,可以建立参数和命令的树。
  
-Brigadier由Mojang写的用于Minecraft的命令解析器和派发器。Brigadier是基于树命令库,可以建立参数与命令的数。+是 Brigadier 的源代码:https://github.com/Mojang/brigadier
  
-这是Brigadier的源代码:https://github.com/Mojang/brigadier+===== Command 接口 =====
  
-===== 命令是什么? =====+在 Minecraft 中,''com.mojang.brigadier.Command''是函数型接口,会运行一些特定的内容,并在有些情况下抛出 ''CommandSyntaxException''命令有一个泛型 ''S'',定义了命令的源(command source),命令源提供了命令运行的一些环境。在 Minecraft 中,命令源通常是 ''<yarn class_2168>'',代表一个服务器、命令方块、rcon 连接、玩家或者实体,有时也可以有 ''<yarn class_237>''
  
-Brigadier需要你指定''Command''以运行(run)。“命令”在brigadier是个松散属于但通常指的是命令树的退出点(exit point)即代码执行命令的地方。一个''Command''的/功能的接口。命令有通用类型''S'',定理命令源(command source)类型。命令源提供一些语境(context),命令在该语境运行。在Minecraft中,这个语境尤其是指''ServerCommandSource'',代表着一个服务器、命令方块、rcon connection、玩家或实体+''Command'' 接口中的唯一方法''run(CommandContext<S>)''接受一个 ''CommandContext<S>'' 作为唯一参数,并返回一数。命令环境(command context)存储 ''S'' 命令源,并允许你从中获取参数、查询已解析的命令节点并看到命令中的输入
  
-命令''Command''中的单个方法,''run(CommandContext<S>)''有一个仅有''CommandContext<S>''并返回整数。命令语境(command context)持有''S''的命令源,并允许你获取参、查看解析的命令节点并查看于此命令的输入。+就像其他型接口那样,命令通常用于匿名函或者方法引
  
-命令可以通过以下方式实现: +<code java> 
- +Command<ServerCommandSource> command = context -> {
-**__作为匿名函数__** +
-<code java [enable_line_numbers="true"]+
-Command<Object> command = context -> {+
     return 0;     return 0;
 }; };
 </code> </code>
  
-**__作为匿名类__** +这个整数相当于命令的结果。在 Minecraft 中。通常来说,负值表示命令执行失败,什么也不做,''0'' 表示命令被略过,正数则表示命令执行成功并做了一些事情。
-<code java [enable_line_numbers="true"]> +
-Command<Object> command = new Command<Object>() { +
-    @Override +
-    public int run(CommandContext<Object> context) { +
-        return 0+
-    } +
-+
-</code>+
  
-**__作为类来实现__** +==== ServerCommandSource 是做什么的? ==== 
-<code java [enable_line_numbers="true"]> + 
-final class XYZCommand implements Command<Object> { +''ServerCommandSource'' 提供了命令运行时的一些额外环境,这些环境拥有特定的实现,包括获取运行这个命令的实体、命令执行时所在的世界以及服务器。
-    @Override +
-    public int run(CommandContext<Object> context) { +
-        return 0; +
-    } +
-+
-</code>+
  
-**__作为方法引用__** 
 <code java [enable_line_numbers="true"]> <code java [enable_line_numbers="true"]>
-void registerCommand() { +// 获取命令源。总是生效。 
-    // 暂时忽略里,后面会解释。 +final ServerCommandSource source = ctx.getSource(); 
-    dispatcher.register(CommandManager.literal("foo")) +
-        .executes(this::execute); // 指下方的"execute"方法。 +
-}+
  
-private int execute(CommandContext<Object> context+// 未经检查,如果是由控制台或命令方块执行的,则会是 null。 
-    return 0; +final @Nullable Entity sender = source.getEntity(); 
-+
-</code>+
  
 +// 如果命令执行者不是实体,就会抛出错误。
 +// 这个的结果可能包含玩家,并且会发送反馈,告诉命令的发送者必须有一个实体。
 +// 这个方法会要求你的方法能够抛出 CommandSyntaxException。
 +// ServerCommandSource 中的 entity 选项可以返回一个 CommandBlock 实体、生物实体或者玩家。
 +final @NotNull Entity sender2 = source.getEntityOrThrow(); 
  
-''run(CommandContext)''方法可能会抛出错误''CommandSyntaxException''本教程后面会讲+// 如果命令执行者不是玩家则为 null 
 +final @Nullable ServerPlayerEntity player = source.getPlayer():
  
-可以认为命令的结果个整数。在Minecraft中这个结果代表红石中继器从命令方块得到红石信号强度或者连锁型命令块传递给其面向的方块的值。一般地,负值意味着命令失败,不执行操作。结果为0表示命令被忽略(pass)。正值表示命令成功执行+// 如果命令执行者不玩家抛出错误,并向命令的发送者发送反馈告诉他必须有一个玩家。这个法会要求你的方法能够抛出 CommandSyntaxException 
 +final @NotNull ServerPlayerEntity player = source.getPlayerOrThrow(); 
  
-===== 基本命令 =====+// 获取命令发送时发送者的坐标,以 Vec3d 的形式。这可以是实体或命令方块的位置,若为控制台则为世界重生点。 
 +final Vec3d position source.getPosition(); 
  
-下面是个不含参数的命令+// 获取命令发送者所在的世界。控制台的世界就是默认重生的世界。 
 +final ServerWorld world = source.getWorld(); 
  
-<code java [enable_line_numbers="true"]> +// 获取发送者的旋转角度,以 Vec2f 的形式。 
-dispatcher.register(CommandManager.literal("foo").executes(context -> {  +final Vec2f rotation source.getRotation(); 
-    System.out.println("调用了foo,没有参数");+
  
-    return 1; +// 访问命令运行时的 MinecraftServer 实例。 
-})); +final MinecraftServer server = source.getServer(); 
-</code>+
  
-''CommandManager.literal("foo")''会告诉bridagier,这个命令有一个节点,即为''foo''的**文字(literal)**。执行此命令只能输入''/foo'',输入''/Foo''''/FoO''''/FOO''、''/fOO''或者''/fooo''都不会执行命令+// 命令源的名称,可以是实体玩家、命令方块的名称,命令方块可以在放置之前命令,若为控制台则为“Console” 
 +final String name = source.getName(); 
  
-===== 静态导入 ===== +// 如果命令源拥有特定权限等级,则返回 true,这发送者管理员级别。(在内置服务器上玩家必须启用了作弊才能执行些命令 
-每次输入''CommandManager.literal("foo")''你都需要创建一个可能多余文字。你可以通过静态导入参数以清理你的代码库。<!-- You can clean up your codebase by use of static imports for the arguments. -->对于文字,这会将语句缩短到''literal("foo")''。这也可以用获取参数会将''StringArgumentType.getString(ctx, "string")''缩短到''getString(ctx, "string")''也适用于Minecraft自身的参数类型。 +final boolean b source.hasPermissionLevel(int level); 
- +
-下面是静态导入的一些例子: +
-<code java [enable_line_numbers="true"]> +
-// getString(ctx, "string"+
-import static com.mojang.brigadier.arguments.StringArgumentType.getString; +
-// word() +
-import static com.mojang.brigadier.arguments.StringArgumentType.word; +
- // literal("foo"+
-import static net.minecraft.server.command.CommandManager.literal; +
- // argument("bar", word()) +
-import static net.minecraft.server.command.CommandManager.argument; +
-// Import everything +
-import static net.minecraft.server.command.CommandManager.*;+
 </code> </code>
  
-意:请确保你用是CommandManager中的''literal''和''argument'',否则尝试编译时可能会出问题。+===== 册一个基本命令 =====
  
-Brigadier默认参数位于''com.mojang.brigadier.arguments''+命令可以通过 Fabric API 的 ''CommandRegistrationCallback'' 进行注册,关于如何注册回调,请参见 [[callbacks]]
  
-Minecraft默认参数于''net.minecraft.command.arguments''。CommandManager是''net.minecraft.server.command''中的+这个事件必须在你模组的初始化器中注册。这个回调有三个参数。''CommmandDispatcher<S>''注册、解析和执行命令,''S'' 是命令派发器支持的命令源的类型,通常是 ''ServerCommandSource''。第二个参数提供了注册表的抽象化,可能传入了特定的命令参数方法。第三个参数是 ''RegistrationEnvironment'',识别命令将要注册到的服务器类型
  
-==== 子命令 ====+为简化代码,建议静态导入 ''CommandManager'' 中的一些方法(参见[[#静态导入]]): 
 +<code java> 
 +import static net.minecraft.server.command.CommandManager.*; 
 +</code>
  
-要添加子命令(sub command)你需要先注册命令的第一个文字节点。+在模组初始化器中,注册最简单的命令
  
 <code java [enable_line_numbers="true"]> <code java [enable_line_numbers="true"]>
-dispatcher.register(CommandManager.literal("foo"+public class ExampleMod implements ModInitializer { 
-</code> +  @Override 
- +  public void onInitialize() { 
-要有子命令,需要将下一个节点附着于已有的节点之后。通过''then(ArgumentBuilder)''方法完成,which takes in an ''ArgumentBuilder''. +    CommandRegistrationCallback.EVENT.register((dispatcherregistryAccessenvironment-> dispatcher.register(literal("foo")
- +
-创建如下所示的命令''foo <bar>''。 +
- +
-<code java [enable_line_numbers="true", highlight_lines_extra="2"]> +
-dispatcher.register(literal("foo"+
-    .then(literal("bar")) +
-); +
-</code> +
- +
-建议你为命令添加节点时将代码缩进。通常缩进意味着命令树的节点有多深。新行也可以使得另一个节点加入时清晰明了。本教程后面会展示的命令中有不同的节点风格。 +
- +
-**现在,尝试运行命令** +
- +
-Most likely if you typed ''/foo bar'' in gamethe command will fail to run. This is because there is no code for the game to execute when all the required arguments have been met. To fix thisyou need to tell the game what to run when the command is being executed using the ''executes(Command)'' method. Below is how the command should look as an example. +
- +
-<code java [enable_line_numbers="true", highlight_lines_extra="3,4,5,6,7"]> +
-dispatcher.register(literal("foo") +
-    .then(literal("bar")+
         .executes(context -> {         .executes(context -> {
-            System.out.println("Called foo with bar");+      // 对于 1.19 之前的版本,把“Text.literal”替换为“new LiteralText”。 
 +      // 对于 1.20 之前的版本,请移除“() ->”。 
 +      context.getSource().sendFeedback(() -> Text.literal("调用 /foo,不带参数"), false);
  
-            return 1; +      return 1; 
-        }) +    }))); 
-    ) +  }
-); +
-</code> +
- +
-===== 注册命令 ===== +
- +
-可以使用''CommandRegistrationCallback''来注册一个callback从而完成注册命令。关于注册callback的信息,请参考[[zh_cn:tutorial:callbacks|callbacks的文章]]。 +
- +
-事件需要在mod的初始化器(initializer)中注册。callback有两个参数:''CommmandDispatcher<S>''用于注册、解析和执行命令,''S''是命令派发器(command dispatcher)支持的命令源的类型。第二个参数是布尔值,标识着命令注册的服务器的类型,是一个''dedicated''还是''integrated''的服务器。 +
- +
- +
-<code java [enable_line_numbers="true"]> +
-public class ExampleCommandMod implements ModInitializer { +
-    @Override +
-    public void onInitialize() { +
-        CommandRegistrationCallback.EVENT.register((dispatcher, dedicated) -> { +
-            ... +
-        }); +
-    }+
 } }
 </code> </code>
  
-你的匿名函数中,方法引用或者不管选的什么,你都注册命令。+**请确保导入了正确静态方法。**方法 ''literal'' 是 ''CommandManager.literal''也可以清楚地写 ''CommandManager.literal'' 而不是使用静态导入。''CommandManager.literal("foo")'' 告诉 brigadier,命令有一个节点,''foo'' 这个**字面的**节点
  
-<code java [enable_line_numbers="true", highlight_lines_extra="5,6,7,8"]> +在 ''sendFeedback'' 方法中,第一个参数是需要发送的文本,在 1.20 之前的版本中是 ''Text'',在 1.20 以及之后的版本是 ''Supplier<Text>''(这是为了避免在不需要的时候实例化了 ''Text'' 对象,因此请不要使用 ''Suppliers.ofInstance'' 或类似方法)。第二个参数决定了命令是否要将反馈的内容发送给其他的管理员。如果命令是//查询//一些内容,比如查询当前的时间或者某玩家的分数,则应该是 ''false''。如果命令实际上//做了些//事情,例如修改时间或者分数,那么则应该是 ''true''。如果游戏规则 ''sendCommandFeedback'' 是 false,那么你不会收到反馈。如果命令执行者被通过 ''/execute as ...'' 修改,反馈则会发送给原始的执行者。
-public class ExampleCommandMod implements ModInitializer { +
-    @Override +
-    public void onInitialize() { +
-        CommandRegistrationCallback.EVENT.register((dispatcher, dedicated) -+
-            dispatcher.register(literal("foo").executes(context -> { +
-                System.out.println("foo"); +
-                return 1; +
-            })); +
-        }); +
-    } +
-+
-</code>+
  
-有需要,可以通过检查''dedicated''来确保命令只在一个专用服务器注册+果命令失败,可以不必调用 ''sendFeedback'',而是直接抛出 ''CommandSyntaxException'' 或 ''<yarn class_2164>''。具体请参见 [[command_exceptions]]
  
 +要执行命令,必须输入 ''/foo'',这是大小写敏感的。如果输入 ''/Foo''、''/FoO''、''/FOO''、''/fOO'' 或者 ''/fooo'',命令不会运行。
  
-<code java [enable_line_numbers="true", highlight_lines_extra="5,6,7"]> +===== 注册环境 ===== 
-public class ExampleCommandMod implements ModInitializer { +如有需要,你可以确保命令仅在一些特定情形下注册例如仅在专用服务器上:
-    @Override +
-    public void onInitialize() { +
-        CommandRegistrationCallback.EVENT.register((dispatcher, dedicated) -> { +
-            if (dedicated) { +
-                TestDedicatedCommand.register(dispatcher); +
-            } +
-        }); +
-    } +
-+
-</code> +
- +
-类似地+
  
-<code java [enable_line_numbers="true", highlight_lines_extra="5,6,7"]>+<yarncode java [enable_line_numbers="true", highlight_lines_extra="5,6,7"]>
 public class ExampleCommandMod implements ModInitializer { public class ExampleCommandMod implements ModInitializer {
     @Override     @Override
     public void onInitialize() {     public void onInitialize() {
-        CommandRegistrationCallback.EVENT.register((dispatcher, dedicated) -> { +        CommandRegistrationCallback.EVENT.register((dispatcher, registryAccess, environment) -> { 
-            if (!dedicated) { +            if (environment.field_25423) { 
-                TestIntegratedCommand.register(dispatcher);+                ...;
             }             }
         });         });
     }     }
 } }
-</code>+</yarncode>
  
-===== 参数 ===== +===== 静态导入 ===== 
- +在上面的例子,使用了静态导以简化代码字面值语句会简化为 ''literal("foo")''这也适用于获取参数的把 ''StringArgumentType.getString(ctx"string")'' 简化为 ''getString(ctx, "string")''。这也适用于 Minecraft 自己的参数类型。
-Brigadier的参数会解析并检查输的参数中的错误Minecraft创建了一些特殊的参数用其自身的使用比如''EntityArgumentType''代表游戏内的实体选择器''@a, @r, @p, @e[type=!player, limit=1, distance=..2]''或者''NbtTagArgumentType''用于解析字符串化的nbt(snbt)并且确认输入的语法是正确的。 +
- +
-关于参数的更多叙述请关于[[tutorial:commands|英文页面]]中的有关内容。 +
- +
-====== 高级概念 ====== +
- +
-Below are links to the articles about more complex concepts used in brigadier. +
- +
-^ Page                                                           ^ Description                                                                     ^ +
-| [[tutorials:commands:requirements|Requirements]]               | Only allow users to execute commands in certain scenarios.                      | +
-| [[tutorials:commands:exceptions  |Exceptions]]                 | Fail execution of a command with a descriptive message and in certain contexts. |                                                                   +
-| [[tutorial:command_suggestions|Suggestions]]                | Suggesting command input for the client.                                        | +
-| [[tutorials:commands:redirects_aliases|Redirects (Aliases)]]   | Allow use of aliases to execute commands.                                       | +
-| [[tutorials:commands:redirects_chaining|Redirects (Chaining)]] | Allow commands to have repeating elements and flags.                            | +
-| [[tutorials:commands:argument_types|Custom Argument Types]]    | Parse your own arguments into your own objects.                                 | +
- +
-**TODO:** Sections are being moved to sub categories and will be added to their respective articles as they are migrated. +
- +
-====== 常见问题 ====== +
- +
-===== 为什么我的命令不编译 ===== +
- +
-There are two immediate possibilities for why this could occur. +
- +
-==== Catch or throw a CommandSyntaxException ==== +
- +
-The solution to this issue is to make the ''run'' or ''suggest'' methods throw a ''CommandSyntaxException''Brigadier will handle the checked exceptions and forward the proper error message in game for you. +
- +
-==== 通用问题 ==== +
- +
-You may have an issue with generic types once in a while. Verify you are using ''CommandManager.literal(...)'' or ''CommandManager.argument(...)'' instead ''LiteralArgumentBuilder'' or ''RequiredArgumentBuilder'' in your static imports. +
- +
-===== 可以注册客户端命令吗? ===== +
- +
-Fabric doesn't currently support client side commands. There is a [[https://github.com/CottonMC/ClientCommands|third-party mod]] by the Cotton team that adds this functionality. +
-There is an open pull request to fabric api which adds this. That will be documented on this page in the future. +
- +
-===== 不建议的操作 ===== +
- +
-A few things we don't recommendbut are possible. +
- +
-==== 可以在运行时注册命令吗? ==== +
- +
-You can do this but it is not recommended. You would get the ''CommandManager'' from the server and add anything commands you wish to it's ''CommandDispatcher''+
- +
-After that you need to send the command tree to every player again using ''CommandManager.sendCommandTree(ServerPlayerEntity)''. This is required because the client locally caches the command tree it receives during login (or when operator packets are sent) for local completions rich error messages. +
- +
-==== 可以在运行时取消注册命令吗? ==== +
- +
-You can also do this, however it is much less stable than registering commands and could cause unwanted side effects. To keep things simple, you need to use reflection on brigadier and remove the nodes. After this, you need to send the command tree to every player again using ''sendCommandTree(ServerPlayerEntity)''. If you don't send the updated command treethe client may think a command still exists, even though the server will fail execution. +
- +
----- +
- +
-====== Sorry for the mess ====== +
- +
-**__Currently this article is being migrated, so things may be a mess. Below is are the parts of the article that are yet to be migrated to the new format.__** +
- +
-Licensing from below onwards is available under the "CC Attribution-Noncommercial-Share Alike 4.0 Internationallicense. This is the current license of the other wiki articles. +
- +
-===== Requirements ===== +
- +
-Lets say you have a command you only want operators to be able to execute. This is where the ''requires'' method comes into play. The requires method has one argument of a Predicate<ServerCommandSource> which will supply a ServerCommandSource to test with and determine if the CommandSource can execute the command. +
- +
-For example this may look like the following:+
  
 +以下是一些静态导入的例子。
 <code java [enable_line_numbers="true"]> <code java [enable_line_numbers="true"]>
-dispatcher.register(literal("foo") +// getString(ctx, "string") 
- .requires(source -> source.hasPermissionLevel(4)+import static com.mojang.brigadier.arguments.StringArgumentType.getString; 
- .executes(ctx -> { +// word() 
- ctx.getSource().sendFeedback(new LiteralText("You are an operator")false); +import static com.mojang.brigadier.arguments.StringArgumentType.word; 
- return 1+ // literal("foo") 
- });+import static net.minecraft.server.command.CommandManager.literal; 
 + // argument("bar", word()
 +import static net.minecraft.server.command.CommandManager.argument
 +// Import everything in the CommandManager 
 +import static net.minecraft.server.command.CommandManager.*;
 </code> </code>
  
-This command will only execute if the Source of the command is a level 4 operator at minimum. If the predicate returns false, then the command will not execute. Also this has the side effect of not showing this command in tab completion to anyone who is not a level 4 operator.+注意:请确保使用了 ''CommandManager'' 中的 ''literal'' 和 ''argument'',而非其他类中的,否则编译时存在泛型擦除问题,因为类型参数 ''S'' 应该是 ''ServerCommandSource''。(对于客户端的命令,请使用 ''ClientCommandManager''。)
  
-Nothing prevents someone from specifying calls to permissions implementations within the ''requires'' blockJust note that if permissions change, you need to re send the command tree.+Brigadier 的默认参数位于 ''com.mojang.brigadier.arguments''
  
-===== Exceptions =====+Minecraft 的参数位于 ''net.minecraft.command.arguments''。CommandManager 位于包 ''net.minecraft.server.command'' 内。
  
-Brigadier supports command exceptions which can be used to end a command such as if an argument didn't parse properly or the command failed to execute, as well as including richer details of the failure.+===== 添加条件 =====
  
-All the exceptions from Brigadier are based on the CommandSyntaxException. The two main types of exceptions Brigadier provides are Dynamic and Simple exception types, of which you must ''create()'' the exception to throw it. These exceptions also allow you to specify the context in which the exception was thrown using ''createWithContext(ImmutableStringReader)'', which builds the error message to point to where on the inputted command line the error occured. +有时你希望命令只有管理员(OP)可以执行,这时就要用到 ''requires'' 方法。''requires'' 方法有一个参数 ''Predicate<ServerCommandSource>'',提供一个 ''ServerCommandSource'' 以检测 ''CommandSource'' 能否执行命令。
-Below is a coin flip command to show an example of exceptions in use.+
  
-<code java [enable_line_numbers="true"]> +例如:
-dispatcher.register(CommandManager.literal("coinflip"+
-    .executes(ctx -> { +
-        Random random = new Random(); +
-  +
-        if(random.nextBoolean()) { // If heads succeed. +
-            ctx.getSource().sendMessage(new TranslateableText("coin.flip.heads")) +
-            return Command.SINGLE_SUCCESS; +
-        } +
- +
-        throw new SimpleCommandExceptionType(new TranslateableText("coin.flip.tails")).create(); // Oh no tails, you lose. +
-    })); +
-</code> +
- +
-Though you are not just limited to a single type of exception as Brigadier also supplies Dynamic exceptions which take additional parameters for context.+
  
 <code java [enable_line_numbers="true"]> <code java [enable_line_numbers="true"]>
-DynamicCommandExceptionType used_name = new DynamicCommandExceptionType(name -> { +dispatcher.register(literal("foo"
-    return new LiteralText("The name: " + (Stringname + has been used"); +  .requires(source -> source.hasPermissionLevel(2)) 
-});+  .executes(ctx -> { 
 +    ctx.getSource().sendFeedback(() -> Text.literal("你是 OP"), false); 
 +    return 1
 +  });
 </code> </code>
 +此时命令只会在命令源为 2 级以上(包括命令方块)时运行,否则命令不会被注册。这样做的副作用就是,非 2 级管理员会看到命令不会被 tab 补全,这也就是为什么没有启用作弊时不能够 tab 补全大多数命令。
  
-There are more Dynamic exception types which each take a different amount of arguments into account (''Dynamic2CommandExceptionType'', ''Dynamic3CommandExceptionType'', ''Dynamic4CommandExceptionType'', ''DynamicNCommandExceptionType''). +要创建只有 4 级管理员(不包括命令方块)可以执行的命令,请使用 ''source.hasPermissionLevel(4)''
-You should remember that the Dynamic exceptions takes an object as an argument so you may have to cast the argument for your use.+
  
-===== Redirects (Aliases) =====+===== 参数 =====
  
-Redirects are Brigadier's form of aliases. Below is how Minecraft handles /msg have an alias of /tell and /w. +大多数命令都使用了参数。一些参数是可选的,也就是说如果你不提供此参数,命令仍能运行。一个节点可以有多个参数类型,但是注意有可能出现二义性,这是需要避免的。
  
-<code java [enable_line_numbers="true"]> +在这个例子中。我们添加一个整数参数。并计算整数的平方。
-public static void register(CommandDispatcher<ServerCommandSource> dispatcher) { +
-    LiteralCommandNode node = registerMain(dispatcher); // Registers main command +
-    dispatcher.register(literal("tell"+
-        .redirect(node)); // Alias 1, redirect to main command +
-    dispatcher.register(literal("w"+
-        .redirect(node)); // Alias 2, redirect to main command +
-}+
  
-public static LiteralCommandNode registerMain(CommandDispatcher<ServerCommandSourcedispatcher) { +<code java
-    return dispatcher.register(literal("msg"+    dispatcher.register(literal("mul") 
-    .then(argument("targets", EntityArgumentType.players()+        .then(argument("value", IntegerArgumentType.integer()) 
-        .then(argument("message", MessageArgumentType.message()) +            .executes(context -> { 
-            .executes(ctx -> { +              final int value = IntegerArgumentType.getInteger(context, "value"); 
-                return execute(ctx.getSource(), getPlayers(ctx, "targets"), getMessage(ctx"message")); +              final int result = value * value; 
-            })))); +              context.getSource().sendFeedback(() -> Text.literal("%s × %s = %s".formatted(valuevalue, result)), false)
-}+              return result
 +            })));
 </code> </code>
  
-The redirect tells brigadier to continue parsing the command at another command node.+在这个例子中,在 ''/mul'' 之后,你需要输入一个整数。例如,如果你输入 ''/mul 3'',会收到消息“3 × 3 = 9”。如果你输入 ''/mul'' 不带参数,命令无法正确解析。
  
-===== Redirects (Chainable Commands) ===== +注意:为了简便,''IntegerArgumentType.integer'' 和 ''IntegerArgumentType.getInteger'' 可以替换为 ''integer'' 和 ''getInteger'' 同时使用静态导入。为了显得更加清楚,这个例子不使用静态导入。
-Commands such as ''/execute as @e[type=player] in the_end run tp ~ ~ ~'' are possible because of redirectsBelow is an example of a chainable command:+
  
-<code java [enable_line_numbers="true"]+然后我们添加可选的第二个参数: 
-LiteralCommandNode<ServerCommandSource> root = dispatcher.register(literal("fabric_test")); +<code java> 
-LiteralCommandNode<ServerCommandSource> root1 = dispatcher.register(literal("fabric_test")  +    dispatcher.register(literal("mul") 
-// You can register under the same literal more than onceit will just register new parts of the branch as shown below if you register a duplicate branch an error will popup in console warning of conflicting commands but one will still work+        .then(argument("value", IntegerArgumentType.integer()) 
-    .then(literal("extra") +            .executes(context -> { 
-        .then(literal("long") +              final int value = IntegerArgumentType.getInteger(context, "value"); 
-            .redirect(root, this::lengthen)) // Return to root for chaining +              final int result = value * value; 
-        .then(literal("short"+              context.getSource().sendFeedback(() -> Text.literal("%s × %s = %s".formatted(valuevalue, result)), false); 
-            .redirect(rootthis::shorten))) // Return to root for chaining +              return result; 
-        .then(literal("command") +            }) 
-            .executes(ctx -> { +            .then(argument("value2", IntegerArgumentType.integer()
-                ctx.getSource().sendFeedback(new LiteralText("Chainable Command"), false); +                .executes(context -> { 
-                return Command.SINGLE_SUCCESS+                  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>
-The redirect can also modify the CommandSource by use of a ''redirect modifier'' which can be used for builder commands. 
  
-<code java [enable_line_numbers="true"]> +现在你可以输入一个或者两个整数了。如果给一个整数,会计算这个整数的平方。如果提供两个整数,会计算这两个整数的积。你可能发现,两次指定类似的执行内容有些不太必要。因此,我们可以创建一个在两个执行中都使用的方法。
-.redirect(rootNode, context -> { +
-    return ((ServerCommandSource) context.getSource()).withLookingAt(Vec3ArgumentType.getVec3(context, "pos")); +
-}) +
-</code>+
  
-===== What can the ServerCommandSource do? =====+<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)))))); 
 +  }
  
-A server command source provides some additional implementation specific context when a command is run. This includes the ability to get the entity that executed the command, the world the command was ran in or the server the command was run on. +  private static int executeMultiply(int value, int value2CommandContext<ServerCommandSource> context) { 
- +    final int result = value * value2; 
-<code java [enable_line_numbers="true"]> +    context.getSource().sendFeedback(() -> Text.literal("%s × %s = %s".formatted(valuevalue2result)), false); 
-final ServerCommandSource source = ctx.getSource();  +    return result; 
-// Get the source. This will always work. +  }
- +
-final Entity sender = source.getEntity();  +
-// Uncheckedmay be null if the sender was the console. +
- +
-final Entity sender2 = source.getEntityOrThrow();  +
-// Will end the command if the source of the command was not an Entity.  +
-// The result of this could contain a player. Also will send feedback telling the sender of the command that they must be an entity.  +
-// This method will require your methods to throw a CommandSyntaxException.  +
-// The entity options in ServerCommandSource could return a CommandBlock entity, any living entity or a player. +
- +
-final ServerPlayerEntity player = source.getPlayer();  +
-// Will end the command if the source of the command was not explicitly a Player. Also will send feedback telling the sender of the command that they must be a player.  This method will require your methods to throw a CommandSyntaxException +
- +
-source.getPosition();  +
-// Get's the sender's position as a Vec3 when the command was sent. This could be the location of the entity/command block or in the case of the console, the world's spawn point. +
- +
-source.getWorld();  +
-// Get's the world the sender is within. The console's world is the same as the default spawn world. +
- +
-source.getRotation();  +
-// Get's the sender's rotation as a Vec2f. +
- +
-source.getMinecraftServer();  +
-// Access to the instance of the MinecraftServer this command was ran on. +
- +
-source.getName();  +
-// The name of the command source. This could be the name of the entity, player, the name of a CommandBlock that has been renamed before being placed down or in the case of the Console, "Console" +
- +
-source.hasPermissionLevel(int level);  +
-// Returns true if the source of the command has a certain permission level. This is based on the operator status of the sender. (On an integrated serverthe player must have cheats enabled to execute these commands) +
-</code> +
- +
-===== Some example commands examples ===== +
- +
-=== Broadcast a message === +
- +
-<code java [enable_line_numbers="true"]>  +
-public static void register(CommandDispatcher<ServerCommandSource> dispatcher){ +
-    dispatcher.register(literal("broadcast") +
-        .requires(source -> source.hasPermissionLevel(2)) // Must be a game master to use the commandCommand will not show up in tab completion or execute to non operators or any operator that is permission level 1. +
-            .then(argument("color", ColorArgumentType.color()+
-                .then(argument("message", greedyString()) +
-                    .executes(ctx -> broadcast(ctx.getSource(), getColor(ctx, "color"), getString(ctx, "message")))))); // You can deal with the arguments out here and pipe them into the command. +
-+
- +
-public static int broadcast(ServerCommandSource sourceFormatting formattingString message+
-    final Text text = new LiteralText(message).formatted(formatting); +
- +
-    source.getMinecraftServer().getPlayerManager().broadcastChatMessage(textMessageType.CHAT, source.getPlayer().getUuid()); +
-    return Command.SINGLE_SUCCESS// Success+
 } }
 </code> </code>
  
-==== /giveMeDiamond ====+===== 子命令 =====
  
-First the basic code where we register "giveMeDiamond" as a literal and then an executes block to tell the dispatcher which method to run.+要添加子命令,你需要先照常注册第一个字面节点。
  
-<code java [enable_line_numbers="true"]+<code> 
-public static LiteralCommandNode register(CommandDispatcher<ServerCommandSource> dispatcher) { // You can also return a LiteralCommandNode for use with possible redirects +dispatcher.register(literal("foo"))
-    return dispatcher.register(literal("giveMeDiamond") +
-        .executes(ctx -> giveDiamond(ctx))); +
-}+
 </code> </code>
  
-Then since we only want to give to players, we check if the CommandSource is a player. But we can use ''getPlayer'' and do both at the same time and throw an error if the source is not a player.+为拥有子命令,需要把下一个节点追加到已经存在的节点后面。
  
-<code java [enable_line_numbers="true"]> +如下所示,创建命令 ''foo <bar>''
-public static int giveDiamond(CommandContext<ServerCommandSource> ctx) throws CommandSyntaxException { +
-    final ServerCommandSource source = ctx.getSource(); +
-  +
-    final PlayerEntity self = source.getPlayer(); // If not a player than the command ends +
-</code>+
  
-Then we add to the player's inventorywith a check to see if the inventory is full:+<code java [enable_line_numbers="true", highlight_lines_extra="3,4,5,6,7"]> 
 +dispatcher.register(literal("foo"
 +    .then(literal("bar"
 +        .executes(context -> { 
 +            // 对于 1.19 以下的版本,使用 ''new LiteralText''。 
 +            // 对于 1.20 以下的版本,直接使用 ''Text'对象而非 supplier。 
 +            context.getSource().sendFeedback(() -> Text.literal("调用 foo 和 bar")false);
  
-<code java [enable_line_numbers="true"]> +            return 1; 
-    if(!player.inventory.insertStack(new ItemStack(Items.DIAMOND))){ +        }
-        throw new SimpleCommandExceptionType(new TranslatableText("inventory.isfull")).create(); +    ) 
-    } +);
- +
-    return 1; +
-}+
 </code> </code>
  
-==== Antioch ==== +建议给命令添加节点时缩进你的代码,通常来说缩进对应了命令树中有多少节点的深度,每一次换行也可以看出添加了一个节点。本教程后面会展示格式化树状命令的几种可选样式。
-...lobbest thou thy Holy Hand Grenade of Antioch towards thy foe. +
-who being naughty in My sight, shall snuff it. +
- +
-Aside from the joke this command summons a primed TNT to a specified location or the location of the sender's cursor. +
- +
-First create an entry into the CommandDispatcher that takes a literal of antioch with an optional argument of the location to summon the entity at.+
  
 +类似于参数,子命令节点也可以设置为可选的。在下面这个例子中,''/foo'' 和 ''/foo bar'' 都是有效的。
 <code java [enable_line_numbers="true"]> <code java [enable_line_numbers="true"]>
-public static void register(CommandDispatcher<ServerCommandSource> dispatcher) { +dispatcher.register(literal("foo") 
-    dispatcher.register(literal("antioch") +    .executes(context -> { 
-        .then(required("location", BlockPosArgumentType.blockPos() +        context.getSource().sendFeedback(() -> Text.literal("调用 foo 不带 bar"), false); 
-            .executes(ctx -> antioch(ctx.getSource(), BlockPosArgument.getBlockPos(ctx, "location"))))+        return 1; 
-        .executes(ctx -> antioch(ctx.getSource(), null))); +    }) 
-}+    .then(literal("bar") 
 +        .executes(context -> 
 +            context.getSource().sendFeedback((-> Text.literal("调用 foo 带有 bar"), false); 
 +            return 1; 
 +        }
 +    ) 
 +);
 </code> </code>
  
-Then the creation and messages behind the joke.+====== 高级概念 ======
  
-<code java [enable_line_numbers="true"]> +以下是 brigadier 使用的更加复杂的概念的文章链接。
-public static int antioch(ServerCommandSource source, BlockPos blockPos) throws CommandSyntaxException { +
-    if(blockPos == null) { +
-        // For the case of no inputted argument we calculate the cursor position of the player or throw an error if the nearest position is too far or is outside of the world. +
-        // This class is used as an example and actually doesn't exist yet. +
-        blockPos = LocationUtil.calculateCursorOrThrow(source, source.getRotation()); +
-    }+
  
-    final TntEntity tnt = new TntEntity(source.getWorld(), blockPos.getX(), blockPos.getY(), blockPos.getZ(), null); +^ 页面 ^ 描述 ^ 
-    tnt.setFuse(3); +| [[command_exceptions  |Exceptions]]                 | 命令执行失败,并在特定的情况下留下描述性的消息。 |                                                                   
-         +| [[command_suggestions|Suggestions]]                | 为客户端建议命令的输入。                                      | 
-    source.getMinecraftServer().getPlayerManager().broadcastChatMessage(new LiteralText("...lobbest thou thy Holy Hand Grenade of Antioch towards thy foe").formatting(Formatting.RED), false); +| [[command_redirects|Redirects]]   | 允许在执行命令时使用别称或者重复元素。                              | 
-    source.getMinecraftServer().getPlayerManager().broadcastChatMessage(new LiteralText("who being naughty in My sight, shall snuff it.").formatting(Formatting.RED), false); +| [[command_argument_types|Custom Argument Types]]    | 在你自己的项目里面解析你自己的参数。                          | 
-    source.getWorld().spawnEntity(tnt); +| [[command_examples|Examples]] | 一些示例命令 |
-    return 1; +
-} +
-</code>+
  
-==== More examples coming soon ====+====== 常见问题 ======
  
-===== Custom Argument Types =====+===== 代码为什么不编译 ===== 
 +此问题可能有一些常见的原因。
  
-Brigadier has support for custom argument types and this section goes into showing how to create a simple argument type+  * **捕获或抛出 CommandSyntaxException:**''CommandSyntaxException'' 不是 ''RuntimeException'',如果抛出,则抛出的地方所在方法必须在方法签名中也抛出 ''CommandSyntaxException'',或者捕获。Brigadier 可以处理此异常,并在游戏内为你提供适当的错误消息。 
 +  * **泛型问题:**你可能遇到了泛型问题。如果你在注册服务器命令(大多数情况都是如此),确保你在静态导入中使用 ''CommandManager.literal(...)'' 或 ''CommandManager.argument(...)'' 而不是''LiteralArgumentBuilder.literal'' 或 ''RequiredArgumentBuilder.argument''。 
 +  * **检查 ''sendFeedback'' 方法:**你可能忘记了提供第二个参数(一个布尔值)。还需要注意,从 1.20 开始,第一个参数是 ''Supplier<Text>'' 而不是 ''Text''。 
 +  * **''Command'' 应该返回整数:**注册命令时,''executes'' 方法接受一个 ''Command'' 对象,通常是 lambda。这个 lambda 应该返回整数,而不是其他的类型。
  
-Warning: Custom arguments require client mod installation to be registered correctly! If you are making a server plugin, consider using existing argument type and a custom suggestions provider instead.+===== 可以注册客户端命令吗? =====
  
-For this example we will create a UuidArgumentType.+Fabric API 有个 ''ClientCommandManager'',可以注册客户端命令。代码应该仅存在于客户端的代码中。例子:
  
-First create a class which extends ''ArgumentType''. Note that ArgumentType is a generic, so the generic will define what type the ArgumentType will return 
- 
-<code java [enable_line_numbers="true"]> 
-public class UuidArgumentType implements ArgumentType<UUID> { 
-</code> 
- 
-ArgumentType requires you to implement the ''parse'' method, the type it returns will match with the Generic type. 
 <code java> <code java>
-@Override +    ClientCommandRegistrationCallback.EVENT.register((dispatcher, registryAccess-> dispatcher.register(ClientCommandManager.literal("foo_client"
-public UUID parse(StringReader readerthrows CommandSyntaxException {+        .executes(context -> { 
 +              context.getSource().sendFeedback(Text.literal("此命令是客户端执行的!")); 
 +              return 1; 
 +            } 
 +        )));
 </code> </code>
  
-This method is where all of your parsing will occurEither this method will return the object based on the arguments provided in the command line or throw a CommandSyntaxException and parsing will fail.+如果你需要在客户端命令执行过程中打开屏幕,不要直接调用 ''client.setScreen(...)'',你应该调用 ''%%client.execute(() -> client.setScreen(...))%%'',其中变量 ''client'' 可以通过 ''context.getSource().getClient()'' 获得。
  
-Next you will store the current position of the cursor, this is so you can substring out only the specific argument. This will always be at the beginning of where your argument appears on the command line.+===== 可以运行时注册命令吗? =====
  
-<code java [enable_line_numbers="true"]> +可以这么做但是不推荐,你可以从服务器中获取 ''CommandManager'' 并向里面添加你希望添加到 ''CommandDispatcher'' 中的任何内容。
-int argBeginning = reader.getCursor(); // The starting position of the cursor is at the beginning of the argument. +
-if (!reader.canRead()) { +
-    reader.skip(); +
-+
-</code>+
  
-Now we grab the entire argument. Depending on your argument type, you may have a different criteria or be similar to some arguments where detecting a ''{'' on the command line will require it to be closed. For a UUID we will just figure out what cursor position the argument ends at.+然后你需要通过 ''CommandManager.sendCommandTree(ServerPlayerEntity)'' 向每个玩家再次发送命令树,之所以要这么做,是因为客户端已经缓存了命令树并在登录过程中(或发出管理员封包时)使用,以用于本地的补全和错误消息。
  
-<code java [enable_line_numbers="true"]> +===== 可以在运行时取消注册命令吗? =====
-while (reader.canRead() && reader.peek() !' ') { // peek provides the character at the current cursor position. +
-    reader.skip(); // Tells the StringReader to move it's cursor to the next position. +
-+
-</code>+
  
-Then we will ask the StringReader what the current position of the cursor is an substring our argument out of the command line.+可以这么做,但是这更不稳定,并且可能造成未预料的副作用。为简化事情,你需要在 brigadier 中使用反射并移除这个节点,然后还需要再次使用 ''sendCommandTree(ServerPlayerEntity)'' 向每个玩家发送命令树。如果不发送更新的命令树,客户端可能还是会认为命令依然存在,即使服务器已经无法执行。
  
-<code java [enable_line_numbers="true"]>String uuidString = reader.getString().substring(argBeginning, reader.getCursor());</code> 
- 
-Now finally we check if our argument is correct and parse the specific argument to our liking, and throwing an exception if the parsing fails. 
- 
-<code java [enable_line_numbers="true"]> 
-try { 
-    UUID uuid = UUID.fromString(uuidString); // Now our actual logic. 
-    return uuid; // And we return our type, in this case the parser will consider this argument to have parsed properly and then move on. 
-    } catch (Exception ex) { 
-    // UUIDs can throw an exception when made by a string, so we catch the exception and repackage it into a CommandSyntaxException type. 
-    // Create with context tells Brigadier to supply some context to tell the user where the command failed at. 
-    // Though normal create method could be used. 
-    throw new SimpleCommandExceptionType(new LiteralText(ex.getMessage())).createWithContext(reader); 
-} 
-</code> 
- 
-The ArgumentType is done, however your client will refuse the parse the argument and throw an error. This is because the server will tell the client what argument type the command node is. And the client will not parse any argument types it does not know how to parse. To fix this we need to register an ArgumentSerializer.  
-Within your ModInitializer. For more complex argument types, you may need to create your own ArgumentSerializer. 
- 
-<code java [enable_line_numbers="true"]> 
-ArgumentTypes.register("mymod:uuid", UuidArgumentType.class, new ConstantArgumentSerializer(UuidArgumentType::uuid));  
-// The argument should be what will create the ArgumentType. 
-</code> 
- 
-And here is the whole ArgumentType: 
- 
-<file java UuidArgumentType.java [enable_line_numbers="true"]> 
- 
-import com.mojang.brigadier.StringReader; 
-import com.mojang.brigadier.arguments.ArgumentType; 
-import com.mojang.brigadier.context.CommandContext; 
-import com.mojang.brigadier.exceptions.CommandSyntaxException; 
-import com.mojang.brigadier.exceptions.SimpleCommandExceptionType; 
-import net.minecraft.text.LiteralText; 
-import net.minecraft.util.SystemUtil; 
- 
-import java.util.ArrayList; 
-import java.util.Collection; 
-import java.util.UUID; 
- 
-/** 
- * Represents an ArgumentType that will return a UUID. 
- */ 
-public class UuidArgumentType implements ArgumentType<UUID> { 
-    public static UuidArgumentType uuid() { 
-        return new UuidArgumentType(); 
-    } 
- 
-    public static <S> UUID getUuid(String name, CommandContext<S> context) { 
-        // Note that you should assume the CommandSource wrapped inside of the CommandContext will always be a generic type. 
-        // If you need to access the ServerCommandSource make sure you verify the source is a server command source before casting. 
-        return context.getArgument(name, UUID.class); 
-    } 
- 
-    private static final Collection<String> EXAMPLES = SystemUtil.consume(new ArrayList<>(), list -> { 
-        list.add("765e5d33-c991-454f-8775-b6a7a394c097"); // i509VCB: Username The_1_gamers 
-        list.add("069a79f4-44e9-4726-a5be-fca90e38aaf5"); // Notch 
-        list.add("61699b2e-d327-4a01-9f1e-0ea8c3f06bc6"); // Dinnerbone 
-    }); 
- 
-    @Override 
-    public UUID parse(StringReader reader) throws CommandSyntaxException { 
-        int argBeginning = reader.getCursor(); // The starting position of the cursor is at the beginning of the argument. 
-        if (!reader.canRead()) { 
-            reader.skip(); 
-        } 
- 
-        // Now we check the contents of the argument till either we hit the end of the command line (When canRead becomes false) 
-        // Otherwise we go till reach reach a space, which signifies the next argument 
-        while (reader.canRead() && reader.peek() != ' ') { // peek provides the character at the current cursor position. 
-            reader.skip(); // Tells the StringReader to move it's cursor to the next position. 
-        } 
- 
-        // Now we substring the specific part we want to see using the starting cursor position and the ends where the next argument starts. 
-        String uuidString = reader.getString().substring(argBeginning, reader.getCursor()); 
-        try { 
-            UUID uuid = UUID.fromString(uuidString); // Now our actual logic. 
-            return uuid; // And we return our type, in this case the parser will consider this argument to have parsed properly and then move on. 
-        } catch (Exception ex) { 
-            // UUIDs can throw an exception when made by a string, so we catch the exception and repackage it into a CommandSyntaxException type. 
-            // Create with context tells Brigadier to supply some context to tell the user where the command failed at. 
-            // Though normal create method could be used. 
-            throw new SimpleCommandExceptionType(new LiteralText(ex.getMessage())).createWithContext(reader); 
-        } 
-    } 
- 
-    @Override 
-    public Collection<String> getExamples() { // Brigadier has support to show examples for what the argument should look like, this should contain a Collection of only the argument this type will return. This is mainly used to calculate ambiguous commands which share the exact same  
-        return EXAMPLES; 
-    } 
-} 
-</file> 
zh_cn/tutorial/commands.1631854832.txt.gz · Last modified: 2021/09/17 05:00 by 127.0.0.1