From fac4a4cd231bc5939b127fe8431e2aa45282bc63 Mon Sep 17 00:00:00 2001 From: DaZiDian <113085496+DaZiDian@users.noreply.github.com> Date: Wed, 24 Apr 2024 21:13:10 +0800 Subject: [PATCH 01/87] Update README.md --- README.md | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 07c79632a..93ec6e470 100644 --- a/README.md +++ b/README.md @@ -1,28 +1,28 @@ -# NeoForged Documentation +# NeoForge 文档 -This repository is used to store documentation on NeoForge, the Minecraft modding API. It also contains documentation on NeoGradle, a Gradle plugin for developing NeoForge and mods using NeoForge. +此存储库用于存储有关NeoForge的文档,NeoForge是Minecraft模组开发的API。它还包含有关NeoGradle的文档,NeoGradle是一个用于开发NeoForge和使用NeoForge的模组的Gradle插件。 -The documentation is built using [Docusaurus 2](https://docusaurus.io) +该文档使用[Docusaurus 2](https://docusaurus.io)构建 -## Contributing +## 贡献 -You can read the [contribution guidelines on the docs](https://docs.neoforged.net/contributing/). +您可以在[文档贡献指南](https://docs.neoforged.net/contributing/)中阅读贡献指南 -If you wish to contribute to the documentation, fork and clone this repository. +如果您希望为文档做出贡献,请 fork 并 clone 此存储库。 -You can run the following commands if you wish to preview the documentation website locally through the live development server. Most changes are reflected live without having to restart the server. +如果您想通过实时开发服务器在本地预览文档网站,可以运行以下命令。大多数更改都会实时反馈,无需重新启动服务器。 ```bash npm install npm run start ``` -### Building +### 构建 -If you wish to build a static version of the documentation which can be deployed, you can run the following command: +如果您希望构建可部署的静态版本的文档,您可以运行以下命令: ``` npm run build ``` -This command generates static content into the `build` directory and can be served using any static contents hosting service. +此命令会生成静态内容到`build`目录,并可以使用任何静态内容托管服务来提供服务。 From 29e29382ef87a6541315e108344cafeb5fe7f3bf Mon Sep 17 00:00:00 2001 From: DaZiDian <113085496+DaZiDian@users.noreply.github.com> Date: Wed, 24 Apr 2024 22:01:21 +0800 Subject: [PATCH 02/87] Update accesstransformers.mdx --- docs/advanced/accesstransformers.mdx | 125 +++++++++++++-------------- 1 file changed, 62 insertions(+), 63 deletions(-) diff --git a/docs/advanced/accesstransformers.mdx b/docs/advanced/accesstransformers.mdx index b8a625ff0..070244e03 100644 --- a/docs/advanced/accesstransformers.mdx +++ b/docs/advanced/accesstransformers.mdx @@ -3,21 +3,21 @@ import TabItem from '@theme/TabItem'; # Access Transformers -Access Transformers (ATs for short) allow for widening the visibility and modifying the `final` flags of classes, methods, and fields. They allow modders to access and modify otherwise inaccessible members in classes outside their control. +Access Transformers (简称 ATs) 允许开发者扩大类、方法和字段的可见性,以及修改其`final`属性。这些工具使得模组开发者能够访问和修改通常无法访问的类成员。 -The [specification document][specs] can be viewed on the NeoForged GitHub. +[规范文档][specs]可以在NeoForged的GitHub上查看。 -## Adding ATs +## 添加 ATs -Adding an Access Transformer to your mod project is as simple as adding a single line into your `build.gradle`: +将 Access Transformer 添加到您的 mod 项目与在 `build.gradle` 中添加一行代码一样简单: -Access Transformers need to be declared in both `build.gradle` and `mods.toml`: +Access Transformers 需要在 `build.gradle` 和 `mods.toml` 中声明: ```groovy -// In build.gradle: -// This block is where your mappings version is also specified +// 在 build.gradle 中: +// 此部分也是指定映射版本的地方 minecraft { accessTransformers { file('src/main/resources/META-INF/accesstransformer.cfg') @@ -26,14 +26,14 @@ minecraft { ``` ```toml -# In mods.toml: +# 在 mods.toml 中: [[accessTransformers]] file="META-INF/accesstransformer.cfg" ``` -AT files can be anywhere specified by the lines above, though NeoForge will default to searching for `META-INF/accesstransformer.cfg` if no other file is specified. +AT 文件可以位于上面几行指定的任何位置,但如果没有指定其他文件,NeoForge 将默认搜索`META-INF/accesstransformer.cfg`。 -Additionally, multiple AT files can be specified and will be applied in order. This can be useful for larger mods with multiple packages. +此外,可以指定多个 AT 文件并按顺序应用。 这对于具有多个软件包的大型模组非常有用。 ```groovy // In build.gradle: @@ -54,103 +54,102 @@ file="accesstransformer_main.cfg" file="accesstransformer_additions.cfg" ``` -After adding or modifying any Access Transformer, the Gradle project must be refreshed for the transformations to take effect. +添加或修改任何访问转换器后,必须刷新Gradle项目,以便使更改生效。 -Adding an Access Transformer to your mod project is as simple as adding a single line into your `build.gradle`: +在你的模组项目中添加一个 AT 也很简单,只需在`build.gradle`中添加一行代码: ```groovy -// This block is where your mappings version is also specified +// 这一部分也是你指定映射版本的地方 minecraft { accessTransformer = file('src/main/resources/META-INF/accesstransformer.cfg') } ``` -During development, the AT file can be anywhere specified by the line above. However, when loading in a non-development environment, NeoForge will only search for the exact path of `META-INF/accesstransformer.cfg` in your JAR file. +在开发环境中,AT文件可以放在上述指定的任何位置。然而,在非开发环境中加载时,NeoForge只会搜索你的JAR文件中 `META-INF/accesstransformer.cfg` 的确切路径。 -After adding or modifying the Access Transformer, the Gradle project must be refreshed for the transformations to take effect. +添加或修改访问转换器后,必须刷新Gradle项目,以便使更改生效。 -## Comments +## 注释 -All text after a `#` until the end of the line will be treated as a comment and will not be parsed. +`#` 之后直到行尾的所有文本都将被视为注释并且不会被解析。 -## Access Modifiers +## 访问控制修饰符 -Access modifiers specify to what new member visibility the given target will be transformed to. In decreasing order of visibility: +访问控制修饰符指明目标将被转换到的新的成员的可见性。按可见性递减的顺序排列如下: -* `public` - visible to all classes inside and outside its package -* `protected` - visible only to classes inside the package and subclasses -* `default` - visible only to classes inside the package -* `private` - visible only to inside the class +* `public` - 对包内外的所有类可见 +* `protected` - 只对包内的类和子类可见 +* `default` - 只对包内的类可见 +* `private` - 只对类的内部可见 -A special modifier `+f` and `-f` can be appended to the aforementioned modifiers to either add or remove respectively the `final` modifier, which prevents subclassing, method overriding, or field modification when applied. +特殊修饰符`+f`和`-f`可以分别添加到上述的修饰符之后,用以添加或移除`final`修饰符,当应用时可防止子类化、方法覆盖或字段修改。 :::danger -Directives only modify the method they directly reference; any overriding methods will not be access-transformed. It is advised to ensure transformed methods do not have non-transformed overrides that restrict the visibility, which will result in the JVM throwing an error. +指令只修改直接引用的方法;任何覆盖的方法都不会被转换。建议确保转换的方法没有未转换覆盖的方法,否则这会限制其可见性,并可能导致 JVM 报错。 -Examples of methods that can be safely transformed are `private` methods, `final` methods (or methods in `final` classes), and `static` methods. +可以安全转换的方法示例包括`private`方法、`final`方法(或`final`类中的方法)以及`static`方法。 ::: -## Targets and Directives +## 目标和指令 -### Classes -To target classes: +### 类 +定位类的格式如下: ``` ``` -Inner classes are denoted by combining the fully qualified name of the outer class and the name of the inner class with a `$` as separator. +内部类通过组合外部类的完全限定名和内部类名,使用`$`作为分隔符来表示。 -### Fields -To target fields: +### 字段 +定位字段的格式如下: ``` ``` -### Methods -Targeting methods require a special syntax to denote the method parameters and return type: +### 方法 +定位方法需要特殊的语法来表示方法参数和返回类型: ``` () ``` -#### Specifying Types - -Also called "descriptors": see the [Java Virtual Machine Specification, SE 8, sections 4.3.2 and 4.3.3][jvmdescriptors] for more technical details. - -* `B` - `byte`, a signed byte -* `C` - `char`, a Unicode character code point in UTF-16 -* `D` - `double`, a double-precision floating-point value -* `F` - `float`, a single-precision floating-point value -* `I` - `integer`, a 32-bit integer -* `J` - `long`, a 64-bit integer -* `S` - `short`, a signed short -* `Z` - `boolean`, a `true` or `false` value -* `[` - references one dimension of an array - * Example: `[[S` refers to `short[][]` -* `L;` - references a reference type - * Example: `Ljava/lang/String;` refers to `java.lang.String` reference type _(note the use of slashes instead of periods)_ -* `(` - references a method descriptor, parameters should be supplied here or nothing if no parameters are present - * Example: `(I)Z` refers to a method that requires an integer argument and returns a boolean -* `V` - indicates a method returns no value, can only be used at the end of a method descriptor - * Example: `()V` refers to a method that has no arguments and returns nothing - -## Examples - +#### 指定类型 + +也称为"描述符":有关更多技术细节,请参见[Java虚拟机规范,SE 8,第4.3.2节和4.3.3节]。 + +* `B` - `byte`, 有符号字节 +* `C` - `char`, Unicode字符代码点,使用UTF-16编码 +* `D` - `double`, 双精度浮点值 +* `F` - `float`, 单精度浮点值 +* `I` - `integer`,32位整数 +* `J` - `long`,64位整数 +* `S` - `short`,有符号短整数 +* `Z` - `boolean`,`true`或`false`值 +* `[` - 表示数组的一个维度 + * 示例: `[[S` 表示 `short[][]` +* `L;` - 表示引用类型 + * 示例: `Ljava/lang/String;` 表示 `java.lang.String` 引用类型 _(注意使用斜线而非点)_ +* `(` - 表示方法描述符,参数应在此处提供,如果没有参数则不提供 + * 示例: `(I)Z` 表示一个需要整型参数并返回布尔值的方法 +* `V` - 表示方法不返回值,只能在方法描述符的末尾使用 + * 示例: `()V` 表示一个没有参数并且不返回任何结果的方法 + +## 示例 ``` -# Makes public the ByteArrayToKeyFunction interface in Crypt +# 将Crypt中的ByteArrayToKeyFunction接口公开 public net.minecraft.util.Crypt$ByteArrayToKeyFunction -# Makes protected and removes the final modifier from 'random' in MinecraftServer +# 将MinecraftServer中的'random'字段设为protected并移除final修饰 protected-f net.minecraft.server.MinecraftServer random -# Makes public the 'makeExecutor' method in Util, -# accepting a String and returns an ExecutorService +# 将Util中的'makeExecutor'方法公开, +# 接受一个String并返回一个ExecutorService public net.minecraft.Util makeExecutor(Ljava/lang/String;)Ljava/util/concurrent/ExecutorService; -# Makes public the 'leastMostToIntArray' method in UUIDUtil, -# accepting two longs and returning an int[] +# 将UUIDUtil中的'leastMostToIntArray'方法公开, +# 接受两个long并返回一个int数组 public net.minecraft.core.UUIDUtil leastMostToIntArray(JJ)[I ``` From aa0697fff859835d565e5f179927058975ffb2c0 Mon Sep 17 00:00:00 2001 From: DaZiDian <113085496+DaZiDian@users.noreply.github.com> Date: Wed, 24 Apr 2024 22:04:20 +0800 Subject: [PATCH 03/87] Update accesstransformers.mdx --- docs/advanced/accesstransformers.mdx | 62 ++++++++++++++++------------ 1 file changed, 36 insertions(+), 26 deletions(-) diff --git a/docs/advanced/accesstransformers.mdx b/docs/advanced/accesstransformers.mdx index 070244e03..829efaf09 100644 --- a/docs/advanced/accesstransformers.mdx +++ b/docs/advanced/accesstransformers.mdx @@ -3,9 +3,9 @@ import TabItem from '@theme/TabItem'; # Access Transformers -Access Transformers (简称 ATs) 允许开发者扩大类、方法和字段的可见性,以及修改其`final`属性。这些工具使得模组开发者能够访问和修改通常无法访问的类成员。 +Access Transformers (简称 ATs) 允许开发者扩大类、方法和字段的可见性,以及修改其 `final` 属性。这些工具使得模组开发者能够访问和修改通常无法访问的类成员。 -[规范文档][specs]可以在NeoForged的GitHub上查看。 +[规范文档][specs]可以在 NeoForged 的 GitHub 上查看。 ## 添加 ATs @@ -31,7 +31,7 @@ minecraft { file="META-INF/accesstransformer.cfg" ``` -AT 文件可以位于上面几行指定的任何位置,但如果没有指定其他文件,NeoForge 将默认搜索`META-INF/accesstransformer.cfg`。 +AT 文件可以位于上面几行指定的任何位置,但如果没有指定其他文件,NeoForge 将默认搜索 `META-INF/accesstransformer.cfg`。 此外,可以指定多个 AT 文件并按顺序应用。 这对于具有多个软件包的大型模组非常有用。 @@ -54,11 +54,11 @@ file="accesstransformer_main.cfg" file="accesstransformer_additions.cfg" ``` -添加或修改任何访问转换器后,必须刷新Gradle项目,以便使更改生效。 +添加或修改任何访问转换器后,必须刷新 Gradle 项目,以便使更改生效。 -在你的模组项目中添加一个 AT 也很简单,只需在`build.gradle`中添加一行代码: +在你的模组项目中添加一个 AT 也很简单,只需在 `build.gradle` 中添加一行代码: ```groovy // 这一部分也是你指定映射版本的地方 @@ -67,9 +67,9 @@ minecraft { } ``` -在开发环境中,AT文件可以放在上述指定的任何位置。然而,在非开发环境中加载时,NeoForge只会搜索你的JAR文件中 `META-INF/accesstransformer.cfg` 的确切路径。 +在开发环境中,AT 文件可以放在上述指定的任何位置。然而,在非开发环境中加载时,NeoForge 只会搜索你的 JAR 文件中 `META-INF/accesstransformer.cfg` 的确切路径。 -添加或修改访问转换器后,必须刷新Gradle项目,以便使更改生效。 +添加或修改访问转换器后,必须刷新 Gradle 项目,以便使更改生效。 @@ -86,70 +86,80 @@ minecraft { * `default` - 只对包内的类可见 * `private` - 只对类的内部可见 -特殊修饰符`+f`和`-f`可以分别添加到上述的修饰符之后,用以添加或移除`final`修饰符,当应用时可防止子类化、方法覆盖或字段修改。 +特殊修饰符 `+f` 和 `-f` 可以分别添加到上述的修饰符之后,用以添加或移除 `final` 修饰符,当应用时可防止子类化、方法覆盖或字段修改。 :::danger 指令只修改直接引用的方法;任何覆盖的方法都不会被转换。建议确保转换的方法没有未转换覆盖的方法,否则这会限制其可见性,并可能导致 JVM 报错。 -可以安全转换的方法示例包括`private`方法、`final`方法(或`final`类中的方法)以及`static`方法。 +可以安全转换的方法示例包括 `private` 方法、`final` 方法(或 `final` 类中的方法)以及 `static` 方法。 ::: ## 目标和指令 ### 类 + 定位类的格式如下: + ``` ``` -内部类通过组合外部类的完全限定名和内部类名,使用`$`作为分隔符来表示。 + +内部类通过组合外部类的完全限定名和内部类名,使用 `$` 作为分隔符来表示。 ### 字段 + 定位字段的格式如下: + ``` ``` ### 方法 + 定位方法需要特殊的语法来表示方法参数和返回类型: + ``` () ``` #### 指定类型 -也称为"描述符":有关更多技术细节,请参见[Java虚拟机规范,SE 8,第4.3.2节和4.3.3节]。 +也称为"描述符":有关更多技术细节,请参见 [Java 虚拟机规范,SE 8,第 4.3.2 节和 4.3.3 节][jvmdescriptors]。 -* `B` - `byte`, 有符号字节 -* `C` - `char`, Unicode字符代码点,使用UTF-16编码 -* `D` - `double`, 双精度浮点值 -* `F` - `float`, 单精度浮点值 -* `I` - `integer`,32位整数 -* `J` - `long`,64位整数 +* `B` - `byte`,有符号字节 +* `C` - `char`,Unicode 字符代码点,使用 UTF-16 编码 +* `D` - `double`,双精度浮点值 +* `F` - `float`,单精度浮点值 +* `I` - `integer`,32 位整数 +* `J` - `long`,64 位整数 * `S` - `short`,有符号短整数 -* `Z` - `boolean`,`true`或`false`值 +* `Z` - `boolean`,`true` 或 `false` 值 * `[` - 表示数组的一个维度 * 示例: `[[S` 表示 `short[][]` * `L;` - 表示引用类型 - * 示例: `Ljava/lang/String;` 表示 `java.lang.String` 引用类型 _(注意使用斜线而非点)_ + * 示例: `Ljava/lang/String;` 表示 `java.lang.String` 引 + +用类型 _(注意使用斜线而非点)_ * `(` - 表示方法描述符,参数应在此处提供,如果没有参数则不提供 - * 示例: `(I)Z` 表示一个需要整型参数并返回布尔值的方法 + * 示例: `(I)Z` 表示一个需要整型参数并返回布尔值的方法 * `V` - 表示方法不返回值,只能在方法描述符的末尾使用 * 示例: `()V` 表示一个没有参数并且不返回任何结果的方法 ## 示例 + ``` -# 将Crypt中的ByteArrayToKeyFunction接口公开 +# 将 Crypt 中的 ByteArrayToKeyFunction 接口公开 public net.minecraft.util.Crypt$ByteArrayToKeyFunction -# 将MinecraftServer中的'random'字段设为protected并移除final修饰 +# 将 MinecraftServer 中的 'random' 字段设为 protected 并移除 final 修饰 protected-f net.minecraft.server.MinecraftServer random -# 将Util中的'makeExecutor'方法公开, -# 接受一个String并返回一个ExecutorService +# 将 Util 中的 'makeExecutor' 方法公开, +# 接受一个 String 并返回一个 ExecutorService public net.minecraft.Util makeExecutor(Ljava/lang/String;)Ljava/util/concurrent/ExecutorService; -# 将UUIDUtil中的'leastMostToIntArray'方法公开, -# 接受两个long并返回一个int数组 +# 将 UUIDUtil 中的 'leastMostToIntArray' 方法公开, +# 接受两个 long 并返回一个 int 数组 public net.minecraft.core.UUIDUtil leastMostToIntArray(JJ)[I ``` From 61e61361150882ac30f9e08e9311e9b954cff0ea Mon Sep 17 00:00:00 2001 From: DaZiDian <113085496+DaZiDian@users.noreply.github.com> Date: Thu, 25 Apr 2024 10:06:00 +0800 Subject: [PATCH 04/87] Update index.md --- neogradle/docs/index.md | 32 ++++++++++++++------------------ 1 file changed, 14 insertions(+), 18 deletions(-) diff --git a/neogradle/docs/index.md b/neogradle/docs/index.md index 85e36cf2e..8bf750c2b 100644 --- a/neogradle/docs/index.md +++ b/neogradle/docs/index.md @@ -1,48 +1,44 @@ ---- -sidebar_position: 0 ---- - -NeoGradle Documentation +NeoGradle 文档 ========================= -:::caution -Please note that this documentation may not be up to date considering the recent creation of NeoForged. +:::注意 +请注意,鉴于NeoForged的最近创建,本文档可能不是最新的。 -Until NeoGradle releases its first version, you should refer to ForgeGradle documentation. The ForgeGradle documentation for versions 6 and 5 has been archived here. +在NeoGradle发布其首个版本之前,您应该参考ForgeGradle的文档。版本6和5的ForgeGradle文档已存在这里。 ::: -This is the official documentation for [ForgeGradle], a [Gradle] plugin for developing [MinecraftForge] and mods using MinecraftForge. +这是[ForgeGradle]官方文档,一个用于使用MinecraftForge开发[MinecraftForge]和mods的[Gradle]插件。 -This documentation is _only_ for ForgeGradle, **this is not a Java, Groovy, or Gradle tutorial**. +这份文档 _仅_ 适用于ForgeGradle,**这不是Java、Groovy或Gradle教程**。 -If you would like to contribute to the docs, read [Contributing to the Docs][contributing]. +如果您想为文档贡献内容,请阅读[为文档做贡献][contributing]。 -Adding the Plugin +添加插件 ----------------- -ForgeGradle uses Gradle 8; it can be added using the `plugins` block in the `build.gradle` by adding the following information to the `settings.gradle`: +ForgeGradle 使用 Gradle 8;它可以通过在 `build.gradle` 的 `plugins` 区块中添加以下信息到 `settings.gradle` 来添加: ```gradle -// In settings.gradle +// 在 settings.gradle 中 pluginManagement { repositories { // ... - // Add the MinecraftForge maven + // 添加 MinecraftForge maven maven { url = 'https://maven.minecraftforge.net/' } } } plugins { - // Add toolchain resolver + // 添加工具链解析器 id 'org.gradle.toolchains.foojay-resolver-convention' version '0.5.0' } ``` ```gradle -// In build.gradle +// 在 build.gradle 中 plugins { - // Add the ForgeGradle plugin + // 添加 ForgeGradle 插件 id 'net.minecraftforge.gradle' version '[6.0,6.2)' // ... From e29f5f519a3105cb79c135687eec6e2440f6411b Mon Sep 17 00:00:00 2001 From: DaZiDian <113085496+DaZiDian@users.noreply.github.com> Date: Thu, 25 Apr 2024 10:10:34 +0800 Subject: [PATCH 05/87] Update 5.x_to_6.0.md --- neogradle/docs/5.x_to_6.0.md | 62 ++++++++++++++++++------------------ 1 file changed, 31 insertions(+), 31 deletions(-) diff --git a/neogradle/docs/5.x_to_6.0.md b/neogradle/docs/5.x_to_6.0.md index c727abdb1..dfb47d272 100644 --- a/neogradle/docs/5.x_to_6.0.md +++ b/neogradle/docs/5.x_to_6.0.md @@ -1,92 +1,92 @@ -# ForgeGradle 5 -> 6 Migration Primer +# ForgeGradle 5到6迁移指南 -This is a high level, non-exhaustive overview on how to migrate your buildscript from ForgeGradle 5 to 6. +这是一个从ForgeGradle 5迁移到6的高层次、非详尽的概览。 -## Add Foojay Toolchains Plugin +## 添加 Foojay 工具链插件 -Gradle now uses the [Foojay Toolchains Plugin](https://github.com/gradle/foojay-toolchains) to manage the Java toolchain. The plugin must be added to the `settings.gradle[.kts]`: +Gradle 现在使用 [Foojay 工具链插件](https://github.com/gradle/foojay-toolchains) 来管理 Java 工具链。该插件必须添加到 `settings.gradle[.kts]` 中: -With Gradle DSL: +使用 Gradle DSL: ```gradle -// In settings.gradle +// 在 settings.gradle 中 plugins { id 'org.gradle.toolchains.foojay-resolver-convention' version '0.5.0' } ``` -With Kotlin DSL: +使用 Kotlin DSL: ```kotlin -// In settings.gradle.kts +// 在 settings.gradle.kts 中 plugins { id("org.gradle.toolchains.foojay-resolver-convention") version "0.5.0" } ``` -## Update Gradle to 8.1.1 +## 更新 Gradle 至 8.1.1 -First, you need to update your Gradle Wrapper to use 8.1.1. You can find additional information regarding the migration process from 7 to 8 on [Gradle's migration guide](https://docs.gradle.org/current/userguide/upgrading_version_7.html). +首先,你需要更新你的 Gradle Wrapper 使用 8.1.1 版本。你可以在 [Gradle迁移指南](https://docs.gradle.org/current/userguide/upgrading_version_7.html) 上找到有关从 7 到 8 版本迁移的更多信息。 -This can be done in one of two ways: +这可以通过以下两种方式之一完成: ### `gradle wrapper` -The preferred option is to use the `gradle wrapper` command with the `--gradle-version` option. +首选的选项是使用 `gradle wrapper` 命令与 `--gradle-version` 选项。 ```bash -# Inside root directory of the gradle project +# 在gradle项目的根目录内 ./gradlew wrapper --gradle-version=8.1.1 ``` ### gradle-wrapper.properties -This method is not recommended, and it should only be used if the `gradlew wrapper` command above fails. +这种方法不推荐,仅当上面的 `gradlew wrapper` 命令失败时使用。 -Update the version in the `distributionUrl` property within `gradle/wrapper/gradle-wrapper.properties`. Afterwards run `./gradlew wrapper`. +更新 `gradle/wrapper/gradle-wrapper.properties` 内的 `distributionUrl` 属性中的版本。之后运行 `./gradlew wrapper`。 ```properties distributionUrl=https\://services.gradle.org/distributions/gradle-8.1.1-bin.zip ``` -## Update ForgeGradle Plugin Version +## 更新 ForgeGradle 插件版本 -After updating Gradle, update the `ForgeGradle` version in your `build.gradle[.kts]` to `[6.0,6.2)`: +更新 Gradle 后,在你的 `build.gradle[.kts]` 中更新 `ForgeGradle` 版本到 `[6.0,6.2)`: -With Gradle DSL: +使用 Gradle DSL: ```gradle -// In build.gradle +// 在 build.gradle 中 plugins { id 'net.minecraftforge.gradle' version '[6.0,6.2)' } ``` -With Kotlin DSL: +使用 Kotlin DSL: ```kotlin -// In build.gradle.kts +// 在 build.gradle.kts 中 plugins { id("net.minecraftforge.gradle") version "[6.0,6.2)" } ``` -## Remove `forceExit` from Run Configurations +## 从运行配置中移除 `forceExit` -ForgeGradle has now removed the `forceExit` property in run configurations. Changes to run tasks only apply to the current project and not any subprojects. +ForgeGradle 现在已经移除了运行配置中的 `forceExit` 属性。对运行任务的更改仅适用于当前项目而不适用于任何子项目。 -### Minor Additions, Changes, Removals +### 小的添加、更改、移除 -### Reobfuscated Classpath Inheritance +### 重混淆类路径继承 -If you were using `classpath.from` within a `reobf*` task configuration to declare extra libraries, the `reobf*` task needed to take into account; it should be migrated to the `libraries.from` property instead. +如果你在 `reobf*` 任务配置中使用了 `classpath.from` 来声明额外库,必须要考虑的 `reobf*` 任务;它应该迁移至 `libraries.from` 属性而不是。 -### Eclipse Launcher Groups +### Eclipse 启动器组 -If ForgeGradle is setup such that Eclipse should run the Gradle tasks before starting the game, you must execute the run configuration from the `Launch Group` folder. This is only true if new properties are enabled within the `minecraft` block. +如果设置了 ForgeGradle 使 Eclipse 在开始游戏之前运行 Gradle 任务,你必须从 `Launch Group` 文件夹执行运行配置。如果在 `minecraft` 块中启用了新属性,这只是正确的。 -![Eclipse launch groups demo](/img/eclipse_launch_groups.png) +![Eclipse 启动器组演示](/img/eclipse_launch_groups.png) -### Removal of `resources` and `classes` in ModConfig +### 在 ModConfig 中移除 `resources` 和 `classes` -The properties `resources` and `classes` within the `mods` block of a run configuration have been removed from ForgeGradle 6. +在 ForgeGradle 6中,运行配置的 `mods` 块内的 `resources` 和 `classes` 属性已被移除。 From 7dc792270e53b2d8a3243a62043b132e1013ad12 Mon Sep 17 00:00:00 2001 From: DaZiDian <113085496+DaZiDian@users.noreply.github.com> Date: Thu, 25 Apr 2024 10:12:26 +0800 Subject: [PATCH 06/87] Update advanced.md --- neogradle/docs/configuration/advanced.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/neogradle/docs/configuration/advanced.md b/neogradle/docs/configuration/advanced.md index d5537bb96..3c15496d9 100644 --- a/neogradle/docs/configuration/advanced.md +++ b/neogradle/docs/configuration/advanced.md @@ -1,15 +1,15 @@ -Advanced Configurations +高级配置 ======================= -ForgeGradle contains a few specific or nuanced configuration techniques depending on the complexity of your build project. +ForgeGradle 包含了一些特定的或细微的配置技术,这些技术取决于构建项目的复杂性。 -Reobfuscating Source Sets +重混淆源集 ------------------------- -By default, the `reobf*` abd `rename*` tasks only contain files on the main source set's classpath. To reobfuscate files on a different classpath, they need to be added to the `libraries` property within the task. +默认情况下,`reobf*` 和 `rename*` 任务只包含主源集类路径上的文件。要重混淆不同类路径上的文件,需要将它们添加到任务中的 `libraries` 属性。 ```gradle -// Adds another source set's classpath to 'reobf' task. +// 将另一个源集的类路径添加到 'reobf' 任务。 tasks.withType('reobfJar') { libraries.from sourceSets.api.classpath } From a9d264ce4f27687ae392b6a86c99f15cc90d5287 Mon Sep 17 00:00:00 2001 From: DaZiDian <113085496+DaZiDian@users.noreply.github.com> Date: Thu, 25 Apr 2024 10:39:19 +0800 Subject: [PATCH 07/87] Update index.mdx --- neogradle/docs/configuration/index.mdx | 76 +++++++++++++------------- 1 file changed, 37 insertions(+), 39 deletions(-) diff --git a/neogradle/docs/configuration/index.mdx b/neogradle/docs/configuration/index.mdx index 66c25d9fe..c7d6b933b 100644 --- a/neogradle/docs/configuration/index.mdx +++ b/neogradle/docs/configuration/index.mdx @@ -1,36 +1,35 @@ import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; -ForgeGradle Configurations +ForgeGradle 配置 ========================== -ForgeGradle has numerous configurations that can change how the development environment is configured. Most configurations are set using the `minecraft` block; however, some others can be specified within the `dependencies` block or modify the built `jar`, such as `reobfJar`. +ForgeGradle 提供了众多配置选项,以便改变开发环境的配置方式。大部分的配置通过 `minecraft` 代码块设置,但一些其他配置可以在 `dependencies` 代码块内指定,或修改构建的 `jar`,例如 `reobfJar`。 -Enabling Access Transformers +启用 Access Transformer ---------------------------- - -[Access Transformers][at] can widen the visibility or modify the `final` flag of Minecraft classes, methods, and fields. +[Access Transformer][at]可以扩大 Minecraft 类、方法和字段的可见性或修改它们的 `final` 标志。 - -To enable Access Transformers in the production environment, you can set `accessTransformers` to the configuration file in question: + +要在开发环境中启用 Access Transformer ,您可以在 `minecraft` 代码块中设置 `accessTransformers` 到相关的配置文件: ```gradle minecraft { // ... - // Add an Access Transformer file relative to the project's directory + // 添加一个AT文件,相对于项目目录 accessTransformers { file('src/main/resources/META-INF/accesstransformer.cfg') - // Multiple files can be specified and are applied in order + // 可以指定多个文件,按顺序应用 file('src/main/resources/accesstransformer_extras.cfg') } } ``` -In production, NeoForge will search for Access Transformer files as specified in `mods.toml`, or at `META-INF/accesstransformer.cfg` if none are specified: +在开发环境中,NeoForge 会根据 `mods.toml` 中指定的搜索AT文件,或者在没有指定的情况下搜索 `META-INF/accesstransformer.cfg`: ```toml [[accessTransformers]] @@ -43,48 +42,48 @@ file="accesstransformer_extras.cfg" - -To enable Access Transformers in the production environment, you can set `accessTransformer` to the configuration file in question: + +要在开发环境中启用AT,您可以将 `accessTransformer` 设置到相关配置文件: ```gradle minecraft { // ... - // Add an access transformer file relative to the project's directory + // 添加一个AT文件,相对于项目目录 accessTransformer = project.file('src/main/resources/META-INF/accesstransformer.cfg') } ``` :::caution -While the Access Transformer in the development environment can be read from anywhere the user specifies, in production, the file will only be read from `META-INF/accesstransformer.cfg`. +虽然在开发环境中的AT可以从用户指定的任何位置读取,在开发环境中,文件只能从 `META-INF/accesstransformer.cfg` 读取。 ::: -Human-Readable Mappings +人类可读映射 ----------------------- -Minecraft's source code is obfuscated. As such, all classes, methods, and fields have machine-generated names with no package structures. Function-local variable names, meanwhile, are turned into a snowman (`☃`) due to how the Local Variable Table is stored. It is difficult to create mods using obfuscated names as reverse engineering them is tedious, may change between versions, and may be invalid in the language itself but not in the bytecode. +Minecraft 的源代码是混淆的。因此,所有的类、方法和字段都有机器生成的名称,没有包结构。与此同时,函数本地变量名称被转换为雪人(`☃`),因为本地变量表的存储方式。使用这些混淆名称创建模组非常困难,因为给它们做逆向工程既繁琐,又可能在版本之间发生变化,且可能在语言本身中无效,但在字节码中有效。 -To address the last two issues, Forge fuzzily maps each class, method, field, and parameter to a unique identifier, known as the SRG name, via the [ForgeAutoRenamingTool][fart]. SRG mappings are used in production when the game is being run by the user client. +为了解决这最后两个问题,Forge 通过 [ForgeAutoRenamingTool][fart] (Forge 自动重命名工具),模糊地将每个类、方法、字段和参数映射到一个唯一的标识符,即 SRG 名称。当用户客户端运行游戏时,会使用 SRG 映射。 -To allow easier development, ForgeGradle allows the user to choose a mapping set to apply on top of SRG mappings, which we will refer to as human-readable mappings. ForgeGradle knows how to convert the mod jar to SRG mappings for use in production via the `reobf*` task. +为了简化开发,ForgeGradle 允许用户选择一个映射集来应用在 SRG 映射之上,我们将其称为人类可读映射。ForgeGradle 知道如何通过 `reobf*` 任务将模组 jar 转换为 SRG 映射,以便在生产中使用。 -The mapping set used can be specified by setting the `mappings` field in the `minecraft` block. The `mappings` field takes in two arguments: `channel` which is the type of the mapping set, and `version` which is the version of the mapping set to apply. +可以通过在 `minecraft` 块中设置 `mappings` 字段来指定所使用的映射集。`mappings` 字段接受两个参数:`channel` 是映射集的类型,而 `version` 是要应用的映射集的版本。 -Currently, there are three default mapping sets built into ForgeGradle: +目前,ForgeGradle 内置了三套默认的映射集: -* `official` - This uses the mapping set provided by Mojang. These mappings do not have parameter names and only exist from 1.14 onward. -* `stable` - This uses a mapping set generated by MCP. These are typically incomplete and no longer exist as of 1.17. -* `snapshot` - This also is a mapping set generated by MCP, similar to a nightly build of a program. These are also typically incomplete and no longer exist as of 1.17. +* `official` - 使用 Mojang 提供的映射集。这些映射没有参数名称,只从 1.14 版本开始存在。 +* `stable` - 使用由 MCP 生成的映射集。这些通常不完整,并且从 1.17 版本开始就不再存在。 +* `snapshot` - 这也是 MCP 生成的映射集,类似于程序的每日版构建。这些也通常不完整,并从 1.17 版本开始就不再存在。 :::info -The class names used in production are from `stable` prior to 1.17 and from `official` from 1.17 onwards. +生产中使用的类名称来自 1.17 版本之前的 `stable` 映射集,从 1.17 版本开始则来自 `official` 映射集。 ::: ```gradle mappings { - // Sets the mappings to use those from Mojang for Minecraft 1.19.4. + // 设置映射 使用 Mojang 提供的 Minecraft 1.19.4 版本映射。 mappings channel: 'official', version: '1.19.4' // ... @@ -93,50 +92,49 @@ mappings { ### Parchment -Parchment is an official project maintained by ParchmentMC which provides open, community-sourced parameter names and javadocs on top of the `official` mapping set. You can learn how setup and use the parchment mapping set on [their website][parchment]. +Parchment 是 ParchmentMC 维护的官方项目,它在 `official` 映射集之上提供了开放的、社区来源的参数名称和 javadocs。你可以在[它们的网站][parchment]上了解如何设置和使用 Parchment 映射集。 -Preparing Run Tasks +准备运行任务 ------------------- -Run tasks (`run*`) have two separate pipelines depending on whether they are executed through `gradlew` or a run configuration. By default, there are two tasks that prepare the workspace for execution: +运行任务(`run*`)有两个独立的处理流程,这取决于它们是通过 `gradlew` 还是通过运行配置执行的。默认情况下,有两个任务负责准备工作区以执行游戏: -First, there are `prepare*` tasks which are executed before `run*` tasks and ensure that mapping files are prepared for the game. The `prepare*Compile` task is typically only executed as a dependency of `run*` tasks to make sure that the game is compiled before it is run. +首先,有 `prepare*` 任务,这些任务在 `run*` 任务之前执行,以确保游戏的映射文件已准备好。`prepare*Compile` 任务通常只作为 `run*` 任务的依赖项执行,以确保在运行游戏之前先将游戏编译好。 -If your IDE is either Eclipse or IntelliJ IDEA, the run configuration can be configured to execute the `prepare*` tasks before starting the game by setting `enableEclipsePrepareRuns` or `enableIdeaPrepareRuns`, respectively, to `true`. This will allow you to invoke custom Gradle tasks before your IDE launches the game. +如果你使用的 IDE 是 Eclipse 或 IntelliJ IDEA,可以通过分别将 `enableEclipsePrepareRuns` 或 `enableIdeaPrepareRuns` 设置为 `true` 来配置运行配置,在启动游戏之前执行 `prepare*` 任务。这将允许你在 IDE 启动游戏之前调用自定义的 Gradle 任务。 ```gradle minecraft { // ... - // Enable the 'prepare*' task for run configurations + // 启用运行配置的 'prepare*' 任务 enableEclipsePrepareRuns true enableIdeaPrepareRuns true } ``` -### Copy IDE Resources +### 复制 IDE 资源 -The `copyIdeResources` property can be used to copy resources configured by the `processResources` task to the IDE's resource output directories. This allows IDE run configurations that do not invoke Gradle (IntelliJ configured to use the IDEA runner or Eclipse) to use buildscript configurable resources. Usually, you need to enable this property when you are replacing values in files like the `mods.toml`. -This only applies to Eclipse and IntelliJ IDEA via the `copyEclipseResources` and `copyIntellijResources` tasks, respectively. +`copyIdeResources` 属性可以用来将 `processResources` 任务配置的资源复制到 IDE 的资源输出目录。这允许不调用 Gradle 的 IDE 运行配置(配置为使用 IDEA 运行器的 IntelliJ 或 Eclipse)使用由构建脚本配置的资源。通常,在你需要在像 `mods.toml` 这样的文件中替换值时,你需要启用这个属性。 +这只适用于通过 `copyEclipseResources` 任务和 `copyIntellijResources` 任务,分别应用于 Eclipse 和 IntelliJ IDEA。 ```gradle minecraft { // ... - // Copies the files from 'processResources' to the IDE's resource output directories + // 从 'processResources' 任务复制文件到 IDE 的资源输出目录 copyIdeResources true } ``` -### Run Configuration Folders - -Run configurations can be sorted into folders if the `generateRunFolders` is set to `true`. This reads the `folderName` property set in the specific [run configuration][run] to determine the organizational structure. +### 运行配置文件夹 +如果将 `generateRunFolders` 设置为 `true`,则可以将运行配置排序到文件夹中。这会读取特定 [运行配置][run] 中设置的 `folderName` 属性来确定组织结构 ```gradle minecraft { // ... - // When true, run configurations will be grouped into folders by their 'folderName' + // 当设置为 true 时,运行配置将根据它们的 'folderName' 被分组到文件夹中 generateRunFolders true } ``` From 09c1ee6f3a4224fabce1fbc92743137ba5c4938e Mon Sep 17 00:00:00 2001 From: DaZiDian <113085496+DaZiDian@users.noreply.github.com> Date: Thu, 25 Apr 2024 10:48:06 +0800 Subject: [PATCH 08/87] Update runs.md --- neogradle/docs/configuration/runs.md | 112 ++++++++++++++------------- 1 file changed, 57 insertions(+), 55 deletions(-) diff --git a/neogradle/docs/configuration/runs.md b/neogradle/docs/configuration/runs.md index f06f90a04..6c797928e 100644 --- a/neogradle/docs/configuration/runs.md +++ b/neogradle/docs/configuration/runs.md @@ -1,126 +1,128 @@ -Run Configurations +运行配置 ================== -Run configurations define how an instance of the game is going to run. This includes arguments, working directories, task names, etc. Run configurations are defined within the `minecraft.runs` block. While no runs are configured by default, [Forge][userdev] does provide the configurations `client`, `server`, `data`, or `gameTestServer`. +运行配置定义了如何运行游戏实例。这包括参数、工作目录、任务名称等。运行配置在 `minecraft.runs` 块内定义。虽然默认情况下没有配置任何运行配置,但 [Forge][userdev] 提供了 `client`、`server`、`data` 或 `gameTestServer` 等配置。 ```gradle minecraft { // ... runs { - // Configure runs here + // 在此配置运行 } } ``` -Run configurations can be added similar to any `NamedDomainObjectContainer` using closures. +可以使用闭包类似于任何 `NamedDomainObjectContainer` 来添加运行配置。 ```gradle -// Inside the minecraft block +// 在 minecraft 块内 runs { - // Creates or configures the run configuration named 'client' + // 创建或配置名为 'client' 的运行配置 client { - // Configure run + // 配置运行 } } ``` -The following configurations properties are available: +以下配置属性是可用的: ```gradle -// Inside the runs block +// 在 runs 块内 client { - // The name of the Gradle run tasks, - // Defaults to 'runX' where X is the container name + // Gradle 运行任务的名称, + // 默认为 'runX',其中 X 是容器名称 taskName 'runThing' - // Sets the entrypoint of the program to launch - // Forge sets userdev main to be 'cpw.mods.bootstraplauncher.BootstrapLauncher' + // 设置要启动的程序的入口点 + // Forge 设置 userdev 主要为 'cpw.mods.bootstraplauncher.BootstrapLauncher' main 'com.example.Main' - // Sets the working directory of the config - // Defaults to './run' + // 设置配置的工作目录 + // 默认为 './run' workingDirectory 'run' - // Sets the name of the module for IntelliJ IDEA to configure for its runs - // Defaults to '.main' + // 设置 IntelliJ IDEA 的模块名称,用于配置其运行 + // 默认为 '.main' ideaModule 'example.main' - // Sets the name of the folder that the run configuration should be added to - // Defaults to the name of the project + // 设置将运行配置添加到的文件夹名称 + // 默认为项目的名称 folderName 'example' - // Sets whether this should run a Minecraft client - // If not specified, checks the following - // - Is there an environment property 'thing' that contains 'client' - // - Does the configuration name contain 'client' - // - Is main set to 'mcp.client.Start' - // - Is main set to 'net.minecraft.client.main.Main' + // 设置是否运行 Minecraft 客户端 + // 如果未指定,检查以下内容: + // - 是否存在包含 'client' 的环境属性 'thing' + // - 配置名称是否包含 'client' + // - main 是否设置为 'mcp.client.Start' + // - main 是否设置为 'net.minecraft.client.main.Main' client true - // Set the parent of this configuration to inherit from + // 设置此配置的父配置以继承 parent runs.example - // Sets the children of this configuration + // 设置此配置的子配置 children runs.child - // Merges this configuration and specifies whether to overwrite existing properties + // 合并此配置并指定是否覆盖现有属性 merge runs.server, true - // If not false, will merge the arguments of the parent with this configuration + // 如果不为 false,将合并父配置的参数到此配置 inheritArgs false - // If not false, will merge the JVM arguments of the parent with this configuration + // 如果不为 false,将合并父配置的 JVM 参数到此配置 inheritJvmArgs false - // Adds a sourceset to the classpath - // If none is specified, adds sourceSet.main + // 添加一个源集到类路径 + // 如果未指定,默认添加 sourceSet.main source sourceSets.api - // Sets an environment property for the run - // Value will be interpreted as a file or a string + // 为运行设置一个环境属性 + // 值将被解释为文件或字符串 environment 'envKey', 'value' - // Sets a system property - // Value will be interpreted as a file or a string + // 设置一个系统属性 + // 值将被解释为文件或字符串 property 'propKey', 'value' - // Sets an argument to be passed into the application - // Can specify multiple with 'args' + // 设置传递给应用程序的参数 + // 可以使用 'args' 指定多个 arg 'hello' - // Sets a JVM argument - // Can specify multiple with 'jvmArgs' + // 设置一个 JVM 参数 + // 可以使用 'jvmArgs' 指定多个 jvmArg '-Xmx2G' - // Sets a token - // Currently, the following tokens are being used: + // 设置一个令牌 + // 目前,正在使用以下令牌: // - runtime_classpath // - minecraft_classpath token 'tokenKey', 'value' - // Sets a token that's lazily initialized - // Should usually be used instead of 'token', for example when the token resolves Gradle configurations + // 设置一个延迟初始化的令牌 + // 通常应该使用 'token' 替代,例如当令牌解析 Gradle 配置时 lazyToken('lazyTokenKey') { 'value' } - // If true, compile all projects instead of for the current task - // This is only used by IntelliJ IDEA - buildAllProjects false + // 如果为 true,则编译所有项目而不仅仅是 + ```gradle +// 如果为 true,则编译所有项目,而不是仅当前任务 +// 这仅被 IntelliJ IDEA 使用 +buildAllProjects false } ``` :::tip -You can see a list of all configured userdev properties within the [MinecraftForge buildscript][buildscript]. +你可以在 [MinecraftForge 构建脚本][buildscript] 中查看所有配置的 userdev 属性的列表。 ::: -Mod Configurations ------------------- +Mod 配置 +-------- -A mod in the current environment can be added using the `mods` block within a Run configuration. Mod blocks are also `NamedDomainObjectContainer`s. +在当前环境中可以使用 Run 配置内的 `mods` 块添加mod。Mod 块也是 `NamedDomainObjectContainer`。 ```gradle -// Inside the runs block +// 在 runs 块内 client { // ... @@ -129,12 +131,12 @@ client { // ... } - // Configures the 'example' mod + // 配置 'example' mod example { - // Add a source set to a mod's sources + // 向 mod 的源添加源集 source sourceSets.main - // Merges this configuration and specifies whether to overwrite existing properties + // 合并此配置并指定是否覆盖现有属性 merge mods.other_mod, true } } From 7ffb5c8f0f63bd8ada8d73fb39618039c88e1115 Mon Sep 17 00:00:00 2001 From: DaZiDian <113085496+DaZiDian@users.noreply.github.com> Date: Thu, 25 Apr 2024 10:52:07 +0800 Subject: [PATCH 09/87] Update index.md --- neogradle/docs/dependencies/index.md | 68 ++++++++++++++++------------ 1 file changed, 39 insertions(+), 29 deletions(-) diff --git a/neogradle/docs/dependencies/index.md b/neogradle/docs/dependencies/index.md index 35dfdee7b..851670d61 100644 --- a/neogradle/docs/dependencies/index.md +++ b/neogradle/docs/dependencies/index.md @@ -1,48 +1,51 @@ -Dependencies -============ +依赖项 +==== -Dependencies are not only used to develop interoperability between mods or add additional libraries to the game, but it also determines what version of Minecraft to develop for. This will provide a quick overview on how to modify the `repositories` and `dependencies` block to add dependencies to your development environment. +依赖项不仅用于开发模组间的互操作性或向游戏添加额外的库,而且还决定了要为哪个版本的Minecraft进行开发。这将提供如何修改 `repositories` 和 `dependencies` 块以将依赖项添加到您的开发环境的快速概述。 -> This will not explain Gradle concepts in depth. It is highly recommended to read the [Gradle Dependency Management guide][guide] before continuing. +> 这将不会深入解释 Gradle 概念。强烈建议在继续之前阅读 [Gradle 依赖管理指南][guide]。 `minecraft` ----------- -The `minecraft` dependency specifies the version of Minecraft to use and must be included in the `dependencies` block. Any artifact, except artifacts which have the group `net.minecraft`, will apply any patches provided with the dependency. This typically only specifies the `net.minecraftforge:forge` artifact. +`minecraft` 依赖项指定了要使用的 Minecraft 版本,并且必须包含在 `dependencies` 块中。任何非 `net.minecraft` 组的工件都将应用随依赖项提供的任何补丁。这通常只指定 `net.minecraftforge:forge` 工件。 ```gradle dependencies { - // Version of Forge artifact is in the form '-' - // 'mc_version' is the version of Minecraft to load (e.g., 1.19.4) - // 'forge_version' is the version of Forge wanted for that Minecraft version (e.g., 45.0.23) - // Vanilla can be compiled against using 'net.minecraft:joined:' instead + // Forge 工件的版本形式为 '-' + // 'mc_version' 是要加载的 Minecraft 版本(例如,1.19.4) + // 'forge_version' 是该 Minecraft 版本所需的 Forge 版本(例如,45.0.23) + // Vanilla 可以使用 'net.minecraft:joined:' 来编译 minecraft 'net.minecraftforge:forge:1.19.4-45.0.23' } ``` -Mod Dependencies ----------------- +Mod 依赖项 +---------- -In a typical development environment, Minecraft is deobfuscated to intermediate mappings, used in production, and then transformed into whatever [human-readable mappings][mappings] the modder specified. Mod artifacts, when built, are obfuscated to production mappings (SRG), and as such, are unable to be used directly as a Gradle dependency. +在典型的开发环境中,Minecraft 被反混淆到中间映射中,用于生产,然后转换为模组制作者指定的任何[人类可读映射][mappings]。构建的 Mod 工件被混淆到生产映射(SRG)中,因此不能直接用作 Gradle 依赖项。 -As such, all mod dependencies need to be wrapped with `fg.deobf` before being added to the intended configuration. +因此,所有 Mod 依赖项在添加到预期配置之前都需要用 `fg.deobf` 包装。 + +[guide]: https://docs.gradle.org/current/userguide/dependency_management.html +[mappings]: https://github.com/MinecraftForge/MCPConfig ```gradle dependencies { - // Assume we have already specified the 'minecraft' dependency + // 假设我们已经指定了 'minecraft' 依赖项 - // Assume we have some artifact 'examplemod' that can be obtained from a specified repository + // 假设我们有一些可以从指定仓库获得的工件 'examplemod' implementation fg.deobf('com.example:examplemod:1.0') } ``` -### Local Mod Dependencies +### 本地 Mod 依赖项 -If the mod you are trying to depend on is not available on a maven repository (e.g., [Maven Central][central], [CurseMaven], [Modrinth]), you can add a mod dependency using a [flat directory] instead: +如果你试图依赖的 mod 不在 maven 仓库中可用(例如,[Maven Central][central]、[CurseMaven]、[Modrinth]),你可以使用 [flat directory] 来添加 mod 依赖项: ```gradle repositories { - // Adds the 'libs' folder in the project directory as a flat directory + // 将项目目录中的 'libs' 文件夹添加为扁平目录 flatDir { dir 'libs' } @@ -51,16 +54,16 @@ repositories { dependencies { // ... - // Given some ::: - // with an extension - // Artifacts in flat directories will be resolved in the following order: + // 给定某些 ::: + // 带有扩展名 + // 扁平目录中的工件将按以下顺序解析: // - -. // - --. // - . // - -. - // If a classifier is explicitly specified - // artifacts with the classifier will take priority: + // 如果明确指定了分类器 + // 带有分类器的工件将优先: // - examplemod-1.0-api.jar // - examplemod-api.jar // - examplemod-1.0.jar @@ -70,24 +73,31 @@ dependencies { ``` :::note -The group name can be anything but must not be empty for flat directory entries as they are not checked when resolving the artifact file. +组名可以是任何东西,但对于扁平目录条目必须非空,因为在解析工件文件时不会检查它们。 ::: -Non-Minecraft Dependencies --------------------------- +非 Minecraft 依赖项 +-------------------- + +非 Minecraft 依赖项 +------------------- -Non-Minecraft dependencies are not loaded by Forge by default in the development environment. To get Forge to recognize the non-Minecraft dependency, they must be added to the `minecraftLibrary` configuration. `minecraftLibrary` works similarly to the `implementation` configuration within Gradle, being applied during compile time and runtime. +Forge 在开发环境中默认不加载非 Minecraft 的依赖项。要让 Forge 识别非 Minecraft 依赖项,它们必须被添加到 `minecraftLibrary` 配置中。`minecraftLibrary` 的工作方式与 Gradle 中的 `implementation` 配置类似,在编译时间和运行时间都会应用。 ```gradle dependencies { // ... - // Assume there is some non-Minecraft library 'dummy-lib' + // 假设有一些非 Minecraft 库 'dummy-lib' minecraftLibrary 'com.dummy:dummy-lib:1.0' } ``` -> Non-Minecraft dependencies added to the development environment will not be included in built artifact by default! You must use [Jar-In-Jar][jij] to include the dependencies within the artifact on build. +> 默认情况下,添加到开发环境中的非 Minecraft 依赖项不会包含在构建的工件中!你必须使用 [Jar-In-Jar][jij] 在构建时将依赖项包含在工件内。 + +:::note +你创建的 Mod 在分发时,必须确保所有的依赖项都遵循其相应的许可协议,并且你在你的模组中包含它们时也符合这些许可。 +::: [guide]: https://docs.gradle.org/8.1.1/userguide/dependency_management.html [mappings]: ../configuration/index.md#human-readable-mappings From e5a191482adde26c27e13b282c3caefac31f976e Mon Sep 17 00:00:00 2001 From: DaZiDian <113085496+DaZiDian@users.noreply.github.com> Date: Thu, 25 Apr 2024 10:52:33 +0800 Subject: [PATCH 10/87] Update index.md --- neogradle/docs/dependencies/index.md | 3 --- 1 file changed, 3 deletions(-) diff --git a/neogradle/docs/dependencies/index.md b/neogradle/docs/dependencies/index.md index 851670d61..6bee91df2 100644 --- a/neogradle/docs/dependencies/index.md +++ b/neogradle/docs/dependencies/index.md @@ -76,9 +76,6 @@ dependencies { 组名可以是任何东西,但对于扁平目录条目必须非空,因为在解析工件文件时不会检查它们。 ::: -非 Minecraft 依赖项 --------------------- - 非 Minecraft 依赖项 ------------------- From 9473b24a2f905e912e3172a9fce1e5f69e8853c1 Mon Sep 17 00:00:00 2001 From: DaZiDian <113085496+DaZiDian@users.noreply.github.com> Date: Thu, 25 Apr 2024 11:01:03 +0800 Subject: [PATCH 11/87] Update jarinjar.md --- neogradle/docs/dependencies/jarinjar.md | 68 ++++++++++++------------- 1 file changed, 34 insertions(+), 34 deletions(-) diff --git a/neogradle/docs/dependencies/jarinjar.md b/neogradle/docs/dependencies/jarinjar.md index 6222a5fe0..eba23ac9b 100644 --- a/neogradle/docs/dependencies/jarinjar.md +++ b/neogradle/docs/dependencies/jarinjar.md @@ -1,102 +1,102 @@ Jar-in-Jar ========== -Jar-in-Jar is a way to load dependencies for mods from within the jars of the mods. To accomplish this, Jar-in-Jar generates a metadata json within `META-INF/jarjar/metadata.json` on build containing the artifacts to load from within the jar. +Jar-in-Jar 是一种从 Mod 的 jar 内部加载依赖项的方法。为了实现这一点,Jar-in-Jar 在构建时会在 `META-INF/jarjar/metadata.json` 中生成一个包含将从 jar 内部加载的工件的元数据 json 文件。 -Jar-in-Jar is a completely optional system which can be enabled using `jarJar#enable` before the `minecraft` block. This will include all dependencies from the `jarJar` configuration into the `jarJar` task. You can configure the task similarly to other jar tasks: +Jar-in-Jar 是一个完全可选的系统,可以使用 `jarJar#enable` 在 `minecraft` 块之前启用。这将包括所有来自 `jarJar` 配置的依赖项进入 `jarJar` 任务。您可以类似于其他 jar 任务来配置该任务: ```gradle -// In build.gradle +// 在 build.gradle 中 -// Enable the Jar-in-Jar system for your mod +// 为您的 mod 启用 Jar-in-Jar 系统 jarJar.enable() -// Configure the 'jarJar' task -// 'all' is the default classifier +// 配置 'jarJar' 任务 +// 'all' 是默认的分类器 tasks.named('jarJar') { // ... } ``` -Adding Dependencies -------------------- +添加依赖项 +------- -You can add dependencies to be included inside your jar using the `jarJar` configuration. As Jar-in-Jar is a negotiation system, all versions should supply a supported range. +您可以使用 `jarJar` 配置将依赖项添加到您的 jar 内部。由于 Jar-in-Jar 是一个协商系统,所有版本都应提供一个支持的范围。 ```gradle -// In build.gradle +// 在 build.gradle 中 dependencies { - // Compiles against and includes the highest supported version of examplelib - // between 2.0 (inclusive) and 3.0 (exclusive) + // 编译时使用 examplelib 的最高支持版本 + // 区间在 2.0(含)和 3.0(不含)之间 jarJar(group: 'com.example', name: 'examplelib', version: '[2.0,3.0)') } ``` -If you need to specify an exact version to include rather than the highest supported version in the range, you can use `jarJar#pin` within the dependency closure. In these instances, the artifact version will be used during compile time while the pinned version will be bundled inside the mod jar. +如果您需要指定一个确切的版本来包含,而不是范围内的最高支持版本,您可以在依赖项闭包内使用 `jarJar#pin`。在这些情况下,编译时将使用工件版本,而固定版本将被打包在 mod jar 内部。 ```gradle -// In build.gradle +// 在 build.gradle 中 dependencies { - // Compiles against the highest supported version of examplelib - // between 2.0 (inclusive) and 3.0 (exclusive) + // 编译时使用 examplelib 的最高支持版本 + // 区间在 2.0(含)和 3.0(不含)之间 jarJar(group: 'com.example', name: 'examplelib', version: '[2.0,3.0)') { - // Includes examplelib 2.8.0 + // 包含 examplelib 2.8.0 jarJar.pin(it, '2.8.0') } } ``` -You can additionally pin a version range while compiling against a specific version instead: +您还可以在编译时针对特定版本,同时固定一个版本范围: ```gradle -// In build.gradle +// 在 build.gradle 中 dependencies { - // Compiles against examplelib 2.8.0 + // 针对 examplelib 2.8.0 编译 jarJar(group: 'com.example', name: 'examplelib', version: '2.8.0') { - // Includes the highest supported version of examplelib - // between 2.0 (inclusive) and 3.0 (exclusive) + // 包含 examplelib 的最高支持版本 + // 在 2.0(含)到 3.0(不含)之间 jarJar.pin(it, '[2.0,3.0)') } } ``` -### Using Runtime Dependencies +### 使用运行时依赖项 -If you would like to include the runtime dependencies of your mod inside your jar, you can invoke `jarJar#fromRuntimeConfiguration` within your buildscript. If you decide to use this option, it is highly suggested to include dependency filters; otherwise, every single dependency -- including Minecraft and Forge -- will be bundled in the jar as well. To support more flexible statements, the `dependency` configuration has been added to the `jarJar` extension and task. Using this, you can specify patterns to include or exclude from the configuration: +如果您想将您的模组的运行时依赖项包含在您的 jar 中,可以在您的构建脚本中调用 `jarJar#fromRuntimeConfiguration`。如果您决定使用此选项,强烈建议包含依赖项过滤器;否则,每一个依赖项 —— 包括 Minecraft 和 Forge —— 也将被打包到 jar 中。为了支持更灵活的声明,`dependency` 配置已被添加到 `jarJar` 扩展和任务中。使用它,您可以指定要从配置中包含或排除的模式: ```gradle -// In build.gradle +// 在 build.gradle 中 -// Add runtime dependencies to jar +// 添加运行时依赖项到 jar jarJar.fromRuntimeConfiguration() // ... jarJar { - // Include or exclude dependencies here from runtime configuration + // 在此处从运行时配置中包括或排除依赖项 dependencies { - // Exclude any dependency which begins with 'com.google.gson.' + // 排除任何以 'com.google.gson.' 开头的依赖项 exclude(dependency('com.google.gson.*')) } } ``` :::tip -It is generally recommended to set at least one `include` filter when using `#fromRuntimeConfiguration`. +通常建议在使用 `#fromRuntimeConfiguration` 时设置至少一个 `include` 过滤器。 ::: -Publishing a Jar-in-Jar to Maven --------------------------------- +发布 Jar-in-Jar 到 Maven +------------------------ -For archival reasons, ForgeGradle supports publishing Jar-in-Jar artifacts to a maven of choice, similar to how the [Shadow plugin][shadow] handles it. In practices, this is not useful or recommended. +出于存档的原因,ForgeGradle 支持将 Jar-in-Jar 工件发布到选择的 Maven,类似于 [Shadow 插件][shadow] 的处理方式。实际上,这并不常用也不推荐。 ```gradle -// In build.gradle (has 'maven-publish' plugin) +// 在 build.gradle 中(已安装 'maven-publish' 插件) publications { mavenJava(MavenPublication) { - // Add standard java components and Jar-in-Jar artifact + // 添加标准的 java 组件和 Jar-in-Jar 工件 from components.java jarJar.component(it) From 57ece8ac15e0d7fdb7b227d9983b6059ffe5df5f Mon Sep 17 00:00:00 2001 From: DaZiDian <113085496+DaZiDian@users.noreply.github.com> Date: Thu, 25 Apr 2024 11:07:04 +0800 Subject: [PATCH 12/87] Update index.md --- docs/blockentities/index.md | 103 +++++++++++++++--------------------- 1 file changed, 42 insertions(+), 61 deletions(-) diff --git a/docs/blockentities/index.md b/docs/blockentities/index.md index 89ef2b872..a45c9abc2 100644 --- a/docs/blockentities/index.md +++ b/docs/blockentities/index.md @@ -1,142 +1,123 @@ -Block Entities -====== +## 注册方块实体 -`BlockEntities` are like simplified `Entities` that are bound to a Block. -They are used to store dynamic data, execute tick based tasks, and dynamic rendering. -Some examples from vanilla Minecraft would be handling of inventories on chests, smelting logic on furnaces, or area effects on beacons. -More advanced examples exist in mods, such as quarries, sorting machines, pipes, and displays. +方块实体的创建和移除是动态的,因此它们本身不是注册对象。要创建一个`BlockEntity`,你需要扩展`BlockEntity`类。相应地,另一个对象被注册以方便创建和引用动态对象的*类型*。对于`BlockEntity`,这些类型被称为`BlockEntityType`。 -:::note -`BlockEntities` aren't a solution for everything and they can cause lag when used wrongly. -When possible, try to avoid them. -::: - -## Registering - -Block Entities are created and removed dynamically and as such are not registry objects on their own. - -In order to create a `BlockEntity`, you need to extend the `BlockEntity` class. As such, another object is registered instead to easily create and refer to the *type* of the dynamic object. For a `BlockEntity`, these are known as `BlockEntityType`s. - -A `BlockEntityType` can be [registered][registration] like any other registry object. To construct a `BlockEntityType`, its builder form can be used via `BlockEntityType$Builder#of`. This takes in two arguments: a `BlockEntityType$BlockEntitySupplier` which takes in a `BlockPos` and `BlockState` to create a new instance of the associated `BlockEntity`, and a varargs of `Block`s which this `BlockEntity` can be attached to. Building the `BlockEntityType` is done by calling `BlockEntityType$Builder#build`. This takes in a `Type` which represents the type-safe reference used to refer to this registry object in a `DataFixer`. Since `DataFixer`s are an optional system to use for mods, this can be passed as `null`. +`BlockEntityType`可以像其他注册对象一样被[注册][registration]。使用`BlockEntityType.Builder#of`来构建`BlockEntityType`,它接受两个参数:一个`BlockEntityType.BlockEntitySupplier`,它接受一个`BlockPos`和`BlockState`来创建新的`BlockEntity`实例,以及一个可变数量的`Block`,这些方块可以附加到此`BlockEntity`。 ```java -// For some DeferredRegister> REGISTER +// 对于某个DeferredRegister> REGISTER public static final RegistryObject> MY_BE = REGISTER.register("mybe", () -> BlockEntityType.Builder.of(MyBE::new, validBlocks).build(null)); -// In MyBE, a BlockEntity subclass +// 在MyBE中,一个BlockEntity子类 public MyBE(BlockPos pos, BlockState state) { super(MY_BE.get(), pos, state); } ``` -## Creating a `BlockEntity` +## 创建方块实体 -To create a `BlockEntity` and attach it to a `Block`, the `EntityBlock` interface must be implemented on your `Block` subclass. The method `EntityBlock#newBlockEntity(BlockPos, BlockState)` must be implemented and return a new instance of your `BlockEntity`. +要创建一个`BlockEntity`并将其附加到一个`Block`,你的`Block`子类必须实现`EntityBlock`接口。必须实现方法`EntityBlock#newBlockEntity(BlockPos, BlockState)`并返回你的`BlockEntity`的新实例。 -## Storing Data within your `BlockEntity` +## 存储你的方块实体内的数据 -In order to save data, override the following two methods: +为了保存数据,覆盖以下两个方法: ```java BlockEntity#saveAdditional(CompoundTag tag) BlockEntity#load(CompoundTag tag) ``` -These methods are called whenever the `LevelChunk` containing the `BlockEntity` gets loaded from/saved to a tag. -Use them to read and write to the fields in your block entity class. +这些方法在包含`BlockEntity`的`LevelChunk`从标签加载/保存时调用。使用这些方法读写你的方块实体类中的字段。 :::note -Whenever your data changes, you need to call `BlockEntity#setChanged`; otherwise, the `LevelChunk` containing your `BlockEntity` might be skipped while the level is saved. +每当你的数据发生变化时,你需要调用`BlockEntity#setChanged`;否则,在级别保存时可能会跳过包含你的`BlockEntity`的`LevelChunk`。 ::: :::danger -It is important that you call the `super` methods! +调用`super`方法非常重要! -The tag names `id`, `x`, `y`, `z`, `ForgeData` and `ForgeCaps` are reserved by the `super` methods. +标签名`id`、`x`、`y`、`z`、`ForgeData`和`ForgeCaps`由`super`方法保留。 ::: -## Ticking `BlockEntities` +## `BlockEntities` 的定时器 -If you need a ticking `BlockEntity`, for example to keep track of the progress during a smelting process, another method must be implemented and overridden within `EntityBlock`: `EntityBlock#getTicker(Level, BlockState, BlockEntityType)`. This can implement different tickers depending on which logical side the user is on, or just implement one general ticker. In either case, a `BlockEntityTicker` must be returned. Since this is a functional interface, it can just take in a method representing the ticker instead: +如果你需要一个定时的方块实体,例如跟踪熔炼过程中的进度,那么必须在`EntityBlock`内实现并覆盖另一个方法:`EntityBlock#getTicker(Level, BlockState, BlockEntityType)`。这可以实现不同的定时器,取决于用户所在的逻辑侧,或者只实现一个通用定时器。无论哪种情况,都必须返回一个`BlockEntityTicker`。由于这是一个功能接口,它可以仅采用表示定时器的方法: ```java -// Inside some Block subclass +// 在某个Block子类内 @Nullable @Override public BlockEntityTicker getTicker(Level level, BlockState state, BlockEntityType type) { return type == MyBlockEntityTypes.MYBE.get() ? MyBlockEntity::tick : null; } -// Inside MyBlockEntity +// 在MyBlockEntity内 public static void tick(Level level, BlockPos pos, BlockState state, MyBlockEntity blockEntity) { - // Do stuff + // 执行任务 } ``` :::note -This method is called each tick; therefore, you should avoid having complicated calculations in here. If possible, you should make more complex calculations every X ticks. (The amount of ticks in a second may be lower then 20 (twenty) but won't be higher) +这个方法每个tick都会被调用;因此,你应该避免在这里进行复杂的计算。如果可能,你应该每X个ticks进行更复杂的计算。(一秒钟内的ticks数量可能低于20 + +,但不会更高) ::: -## Synchronizing the Data to the Client +## 将数据同步到客户端 -There are three ways of syncing data to the client: synchronizing on chunk load, on block updates, and with a custom network message. +有三种方法可以将数据同步到客户端:在LevelChunk加载时同步,在方块更新时同步,以及使用自定义网络消息同步。 -### Synchronizing on LevelChunk Load +### 在LevelChunk加载时同步 -For this you need to override +为此,你需要覆盖 ```java BlockEntity#getUpdateTag() IForgeBlockEntity#handleUpdateTag(CompoundTag tag) ``` -Again, this is pretty simple, the first method collects the data that should be sent to the client, -while the second one processes that data. If your `BlockEntity` doesn't contain much data, you might be able to use the methods out of the [Storing Data within your `BlockEntity`][storing-data] section. +第一个方法收集应该发送到客户端的数据,而第二个方法处理这些数据。如果你的`BlockEntity`不包含太多数据,你可能可以使用[存储你的方块实体内的数据][storing-data]部分中的方法。 :::caution -Synchronizing excessive/useless data for block entities can lead to network congestion. You should optimize your network usage by sending only the information the client needs when the client needs it. For instance, it is more often than not unnecessary to send the inventory of a block entity in the update tag, as this can be synchronized via its [`AbstractContainerMenu`][menu]. +同步过多/无用的方块实体数据可能导致网络拥塞。你应该优化你的网络使用,只在客户端需要时发送客户端需要的信息。例如,通常没有必要在更新标签中发送方块实体的库存,因为这可以通过其[`AbstractContainerMenu`][menu]同步。 ::: -### Synchronizing on Block Update +### 在方块更新时同步 -This method is a bit more complicated, but again you just need to override two or three methods. -Here is a tiny example implementation of it: +这种方法有点复杂,但你只需覆盖两个或三个方法。这里是它的一个小示例实现: ```java @Override public CompoundTag getUpdateTag() { CompoundTag tag = new CompoundTag(); - //Write your data into the tag + // 将你的数据写入标签 return tag; } @Override public Packet getUpdatePacket() { - // Will get tag from #getUpdateTag + // 从#getUpdateTag获取标签 return ClientboundBlockEntityDataPacket.create(this); } -// Can override IForgeBlockEntity#onDataPacket. By default, this will defer to the #load. +// 可以覆盖IForgeBlockEntity#onDataPacket。默认情况下,这将推迟到#load。 ``` -The static constructors `ClientboundBlockEntityDataPacket#create` takes: +静态构造函数`ClientboundBlockEntityDataPacket#create`接受: -* The `BlockEntity`. -* An optional function to get the `CompoundTag` from the `BlockEntity`. By default, this uses `BlockEntity#getUpdateTag`. +* `BlockEntity`。 +* 一个可选的函数,从`BlockEntity`获取`CompoundTag`。默认情况下,这使用`BlockEntity#getUpdateTag`。 -Now, to send the packet, an update notification must be given on the server. +现在,要发送数据包,服务器上必须给出更新通知。 ```java Level#sendBlockUpdated(BlockPos pos, BlockState oldState, BlockState newState, int flags) ``` -The `pos` should be your `BlockEntity`'s position. -For `oldState` and `newState`, you can pass the current `BlockState` at that position. -`flags` is a bitmask that should contain `2`, which will sync the changes to the client. See `Block` for more info as well as the rest of the flags. The flag `2` is equivalent to `Block#UPDATE_CLIENTS`. +`pos`应该是你的`BlockEntity`的位置。 +对于`oldState`和`newState`,你可以传递该位置当前的`BlockState`。 +`flags`是一个位掩码,应包含`2`,这将同步更改到客户端。有关更多信息以及其他标志,请参阅`Block`。标志`2`等同于`Block#UPDATE_CLIENTS`。 -### Synchronizing Using a Custom Network Message +### 使用自定义网络消息同步 -This way of synchronizing is probably the most complicated but is usually the most optimized, -as you can make sure that only the data you need to be synchronized is actually synchronized. -You should first check out the [`Networking`][networking] section and especially [`SimpleImpl`][simple_impl] before attempting this. -Once you've created your custom network message, you can send it to all users that have the `BlockEntity` loaded with `SimpleChannel#send(PacketDistributor$PacketTarget, MSG)`. +这种同步方式可能是最复杂的,但通常是最优化的,因为你可以确保只有你需要同步的数据实际上被同步。你应该首先查看[`Networking`][networking]部分,特别是[`SimpleImpl`][simple_impl],然后再尝试这种方式。一旦你创建了自定义网络消息,你可以使用`SimpleChannel#send(PacketDistributor$PacketTarget, MSG)`将其发送给加载了`BlockEntity`的所有用户。 :::caution -It is important that you do safety checks, the `BlockEntity` might already be destroyed/replaced when the message arrives at the player! You should also check if the chunk is loaded (`Level#hasChunkAt(BlockPos)`). +进行安全检查非常重要,当消息到达玩家时,`BlockEntity`可能已经被销毁/替换!你还应该检查块是否已加载(`Level#hasChunkAt(BlockPos)`)。 ::: [registration]: ../concepts/registries.md#methods-for-registering From 8d1bce514fd463c625d1540a4bf03b068e1d3e98 Mon Sep 17 00:00:00 2001 From: DaZiDian <113085496+DaZiDian@users.noreply.github.com> Date: Thu, 25 Apr 2024 11:07:34 +0800 Subject: [PATCH 13/87] Update index.md --- docs/blockentities/index.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/docs/blockentities/index.md b/docs/blockentities/index.md index a45c9abc2..84a2627e0 100644 --- a/docs/blockentities/index.md +++ b/docs/blockentities/index.md @@ -57,9 +57,7 @@ public static void tick(Level level, BlockPos pos, BlockState state, MyBlockEnti ``` :::note -这个方法每个tick都会被调用;因此,你应该避免在这里进行复杂的计算。如果可能,你应该每X个ticks进行更复杂的计算。(一秒钟内的ticks数量可能低于20 - -,但不会更高) +这个方法每个tick都会被调用;因此,你应该避免在这里进行复杂的计算。如果可能,你应该每X个ticks进行更复杂的计算。(一秒钟内的ticks数量可能低于20,但不会更高) ::: ## 将数据同步到客户端 From 3338c741f8c5990767f72059052b8ba219f0f8a1 Mon Sep 17 00:00:00 2001 From: DaZiDian <113085496+DaZiDian@users.noreply.github.com> Date: Thu, 25 Apr 2024 11:14:19 +0800 Subject: [PATCH 14/87] Update ber.md --- docs/blockentities/ber.md | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/docs/blockentities/ber.md b/docs/blockentities/ber.md index 0196f965d..a83d801aa 100644 --- a/docs/blockentities/ber.md +++ b/docs/blockentities/ber.md @@ -1,28 +1,28 @@ BlockEntityRenderer ================== -A `BlockEntityRenderer` or `BER` is used to render blocks in a way that cannot be represented with a static baked model (JSON, OBJ, B3D, others). A block entity renderer requires the block to have a `BlockEntity`. +`BlockEntityRenderer` 或 `BER` 用于以无法用静态烘焙模型(JSON,OBJ,B3D,其他)表示的方式渲染块。块实体渲染器要求块有一个 `BlockEntity`。 -Creating a BER +创建 BER -------------- -To create a BER, create a class that inherits from `BlockEntityRenderer`. It takes a generic argument specifying the block's `BlockEntity` class. The generic argument is used in the BER's `render` method. +要创建 BER,创建一个继承自 `BlockEntityRenderer` 的类。它需要一个泛型参数,指定块的 `BlockEntity` 类。泛型参数在 BER 的 `render` 方法中使用。 -Only one BER exists for a given `BlockEntityType`. Therefore, values that are specific to a single instance in the level should be stored in the block entity being passed to the renderer rather than in the BER itself. For example, an integer that increments every frame, if stored in the BER, will increment every frame for every block entity of this type in the level. +对于给定的 `BlockEntityType`,只存在一个 BER。因此,应将特定于等级中的单个实例的值存储在传递给渲染器的块实体中,而不是在 BER 本身中。例如,每帧递增的整数,如果存储在 BER 中,将会在该类型的等级中的每一个块实体中每帧递增。 ### `render` -This method is called every frame in order to render the block entity. +每一帧都会调用这个方法来渲染块实体。 -#### Parameters -* `blockEntity`: This is the instance of the block entity being rendered. -* `partialTick`: The amount of time, in fractions of a tick, that has passed since the last full tick. -* `poseStack`: A stack holding four-dimensional matrix entries offset to the current position of the block entity. -* `bufferSource`: A rendering buffer able to access a vertex consumer. -* `combinedLight`: An integer of the current light value on the block entity. -* `combinedOverlay`: An integer set to the current overlay of the block entity, usually `OverlayTexture#NO_OVERLAY` or 655,360. +#### 参数 +* `blockEntity`:这是正在渲染的块实体的实例。 +* `partialTick`:自上一完整 tick以来已经过去的以 tick 的分数表示的时间。 +* `poseStack`:这是一个堆栈,可以持有四维矩阵条目,这些条目可以偏移到块实体的当前位置。 +* `bufferSource`:一个渲染缓冲区,能够访问顶点消费者。 +* `combinedLight`:块实体上当前光值的整数。 +* `combinedOverlay`:一个设置为块实体当前覆盖层的整数,通常是 `OverlayTexture#NO_OVERLAY` 或 655,360。 -Registering a BER +注册 BER ----------------- -In order to register a BER, you must subscribe to the `EntityRenderersEvent$RegisterRenderers` event on the mod event bus and call `#registerBlockEntityRenderer`. +要注册 BER,你必须订阅模组事件总线上的 `EntityRenderersEvent$RegisterRenderers` 事件,并调用 `#registerBlockEntityRenderer`。 From 83f7291561175c86fafcf378499076603d05fdc1 Mon Sep 17 00:00:00 2001 From: DaZiDian <113085496+DaZiDian@users.noreply.github.com> Date: Thu, 25 Apr 2024 12:27:41 +0800 Subject: [PATCH 15/87] Update index.md --- neogradle/docs/gettingstarted/index.md | 58 +++++++++++++------------- 1 file changed, 28 insertions(+), 30 deletions(-) diff --git a/neogradle/docs/gettingstarted/index.md b/neogradle/docs/gettingstarted/index.md index f2a88690e..83d63d1db 100644 --- a/neogradle/docs/gettingstarted/index.md +++ b/neogradle/docs/gettingstarted/index.md @@ -1,42 +1,42 @@ -Getting Started with ForgeGradle -================================ +开始使用ForgeGradle +============================ -If you have never used ForgeGradle before, here is the minimum amount of information needed to get a development environment setup. +如果你之前从未使用过ForgeGradle,这里是建立开发环境所需的最少的信息。 -#### Prerequisites +#### 前提条件 -* An installation of the Java Development Kit (JDK) +* Java开发工具包(JDK)的安装 -Minecraft Versions | Java Development Kit Version -:---: | :---: -1.12 - 1.16 | [JDK 8][jdk8] -1.17 | [JDK 16][jdk16] -1.18 - 1.19 | [JDK 17][jdk17] +Minecraft版本 | Java开发工具包版本 +:---: | :---: +1.12 - 1.16 | [JDK 8][jdk8] +1.17 | [JDK 16][jdk16] +1.18 - 1.19 | [JDK 17][jdk17] -* Familiarity with an Integrated Development Environment (IDE) - * It is preferable to use one with some form of Gradle integration +* 熟悉集成开发环境(IDE) + * 最好使用具有某种形式的Gradle集成的IDE -## Setting Up ForgeGradle +## 设置ForgeGradle -1. First download a copy of the [Modder Development Kit (MDK)][mdk] from MinecraftForge and extract the zip to an empty directory. -1. Open the directory you extracted the MDK to within your IDE of choice. If your IDE integrates with Gradle, import it as a Gradle project. -1. Customize your Gradle buildscript for your mod: - 1. Set `archivesBaseName` to the desired mod id. Additionally, replace all occurrences of `examplemod` with the mod id as well. - 1. Change the `group` to your desired package name. Make sure to follow existing [naming conventions][group]. - 1. Change the `version` number to reflect the current version of your mod. It is highly recommended to use [Forge's extension on semantic versioning][semver]. +1. 首先从MinecraftForge下载[模组开发套件(MDK)][mdk],将zip文件解压到一个空的目录中。 +1. 使用你选择的IDE打开MDK解压的目录。如果你的IDE有Gradle集成,将其作为Gradle项目导入。 +1. 自定义你的Gradle构建脚本以适应你的模组: + 1. 将`archivesBaseName`设置为所需的模组ID。此外,替换所有出现的`examplemod`为你的模组ID。 + 1. 更改`group`为你想要的包名称。确保遵循现有的[命名约定][group]。 + 1. 更改`version`号以反映你模组的当前版本。强烈推荐使用[Forge关于语义版本控制的扩展][semver]。 -:::caution -Make sure that any changes to the mod id are reflected in the mods.toml and main mod class. See [Structuring Your Mod][structuring] on the Forge docs for more information. +:::警告 +确保对模组ID的任何更改都反映在mods.toml和主要模组类中。有关更多信息,请查看Forge文档上的[构建你的模组][structuring]。 ::: -2. Reload or refresh your Gradle project using your IDE. If your IDE does not have Gradle integration, run the following from a shell in your project's directory: +2. 使用IDE重新加载或刷新你的Gradle项目。如果你的IDE没有Gradle集成,运行以下命令在项目目录中的shell中: ```sh ./gradlew build --refresh-dependencies ``` -5. If your IDE is either Eclipse, IntelliJ IDEA, or Visual Studio Code, you can generate run configurations using one of the following commands, respectively: +5. 如果你的IDE是Eclipse、IntelliJ IDEA或Visual Studio Code,你可以使用以下命令生成运行配置,分别是: #### Eclipse @@ -56,14 +56,13 @@ Make sure that any changes to the mod id are reflected in the mods.toml and main ./gradlew genVSCodeRuns ``` -You can the run the client, server, etc. using one of the generated run configurations. +你可以使用生成的运行配置来运行客户端、服务器等。 -:::tip -If your IDE is not listed, you can still run the configurations using `./gradlew run*` (e.g., `runClient`, `runServer`, `runData`). You can use these commands with the supported IDEs as well. +:::小贴士 +如果你的IDE未列出,你仍然可以使用`./gradlew run*` (例如,`runClient`, `runServer`, `runData`)来运行配置。这些命令也可以在支持的IDE中使用。 ::: -Congratulations, now you have a development environment set up! - +恭喜你,现在你已经建立了一个开发环境! [jdk8]: https://adoptium.net/temurin/releases/?version=8 [jdk16]: https://adoptium.net/temurin/releases/?version=16 @@ -71,5 +70,4 @@ Congratulations, now you have a development environment set up! [mdk]: https://files.minecraftforge.net/ [group]: https://docs.oracle.com/javase/tutorial/java/package/namingpkgs.html -[semver]: https://docs.minecraftforge.net/en/latest/gettingstarted/versioning/ -[structuring]: https://docs.minecraftforge.net/en/latest/gettingstarted/structuring/ +[semver]: https:// From a0667138aa4fcf868c578e63205d444a636eb022 Mon Sep 17 00:00:00 2001 From: DaZiDian <113085496+DaZiDian@users.noreply.github.com> Date: Thu, 25 Apr 2024 12:31:04 +0800 Subject: [PATCH 16/87] Update index.md --- .../version-5.x/docs/index.md | 27 ++++++------------- 1 file changed, 8 insertions(+), 19 deletions(-) diff --git a/neogradle_versioned_docs/version-5.x/docs/index.md b/neogradle_versioned_docs/version-5.x/docs/index.md index 2fdc85cba..5b01576ab 100644 --- a/neogradle_versioned_docs/version-5.x/docs/index.md +++ b/neogradle_versioned_docs/version-5.x/docs/index.md @@ -1,38 +1,27 @@ -ForgeGradle Documentation +ForgeGradle文档 ========================= -This is the official documentation for [ForgeGradle], a [Gradle] plugin for developing [MinecraftForge] and mods using MinecraftForge. +这是[ForgeGradle]的官方文档,ForgeGradle是一个用于使用MinecraftForge开发[MinecraftForge]和模组的[Gradle]插件。 -This documentation is _only_ for ForgeGradle, **this is not a Java, Groovy, or Gradle tutorial**. +这份文档 _仅_ 针对ForgeGradle,**这不是Java、Groovy或Gradle教程**。 -If you would like to contribute to the docs, read [Contributing to the Docs][contributing]. +如果你想为文档做出贡献,请阅读[为文档做贡献][contributing]。 -Adding the Plugin +添加插件 ----------------- -ForgeGradle can be added using the `plugins` block by adding the MinecraftForge maven to the available plugin repositories: +通过在可用的插件仓库中添加MinecraftForge maven,可以使用`plugins`代码块添加ForgeGradle: ```gradle -// In settings.gradle +// 在settings.gradle文件中 pluginManagement { repositories { // ... - // Add the MinecraftForge maven + // 添加MinecraftForge maven maven { url = 'https://maven.minecraftforge.net/' } } } -``` - -```gradle -// In build.gradle -plugins { - // Add the ForgeGradle plugin - id 'net.minecraftforge.gradle' version '5.1.+' - - // ... -} -``` [ForgeGradle]: https://github.com/MinecraftForge/ForgeGradle [Gradle]: https://gradle.org/ From a2670177b82475642cbe77ee5187e5890602be85 Mon Sep 17 00:00:00 2001 From: DaZiDian <113085496+DaZiDian@users.noreply.github.com> Date: Thu, 25 Apr 2024 12:35:27 +0800 Subject: [PATCH 17/87] Update index.md --- .../version-5.x/docs/configuration/index.md | 40 +++++++++---------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/neogradle_versioned_docs/version-5.x/docs/configuration/index.md b/neogradle_versioned_docs/version-5.x/docs/configuration/index.md index 785e001f3..7c1443add 100644 --- a/neogradle_versioned_docs/version-5.x/docs/configuration/index.md +++ b/neogradle_versioned_docs/version-5.x/docs/configuration/index.md @@ -1,50 +1,50 @@ -ForgeGradle Configurations +ForgeGradle配置 ========================== -ForgeGradle has numerous configurations that can change how the development environment is configured. Most configurations are set using the `minecraft` block; however, some others can be specified within the `dependencies` block or modify the built `jar`, such as `reobfJar`. +ForgeGradle具有许多配置,可以改变开发环境的设置。大多数配置使用`minecraft`代码块设置;然而,还有一些可以在`dependencies`代码块中指定,或者修改构建好的`jar`文件,例如`reobfJar`。 -Enabling Access Transformers +启用访问转换器 ---------------------------- -[Access Transformers][at] can widen the visibility or modify the `final` flag of Minecraft classes, methods, and fields. To enable access transformers in the production environment, you can set `accessTransformer` to configuration file in question: +[访问转换器][at]可以扩大Minecraft类、方法和字段的可见性或修改`final`标志。要在开发环境中启用访问转换器,可以设置`accessTransformer`到相关的配置文件: ```gradle minecraft { // ... - // Add an access transformer file relative to the project's directory + // 添加一个相对于项目目录的访问转换器文件 accessTransformer = project.file('src/main/resources/META-INF/accesstransformer.cfg') } ``` -:::caution -While the access transformer in the development environment can be read from anywhere the user specifies, in production, the file will only be read from `META-INF/accesstransformer.cfg`. +:::警告 +虽然开发环境中的访问转换器可以从用户指定的任何地方读取,在开发环境中,文件只能从`META-INF/accesstransformer.cfg`读取。 ::: -Human-Readable Mappings +可读的映射 ----------------------- -Minecraft's source code is obfuscated. As such, all classes, methods, and fields have machine-generated names with no package structures. Function-local variable names, meanwhile, are turned into a snowman (`☃`) due to how the Local Variable Table is stored. It is difficult to create mods using obfuscated names as reverse engineering them is tedious, may change between versions, and may be invalid in the language itself but not in the bytecode. +Minecraft的源代码是混淆的。因此,所有的类、方法和字段都有机器生成的名称,没有包结构。与此同时,函数局部变量名称由于Local Variable Table的存储方式,被转化为雪人(☃)。使用混淆名创建模组是困难的,因为给它们做逆向工程是乏味的,可能在版本之间变化,并且可能导致在语言本身中是无效的,但在字节码中不是。 -To address the last two issues, Forge fuzzily maps each class, method, field, and parameter to a unique identifier, known as the SRG name, via the [ForgeAutoRenamingTool][fart]. SRG mappings are used in production when the game is being run by the user client. +为了解决后两个问题,Forge通过[ForgeAutoRenamingTool][fart]将每个类、方法、字段和参数模糊地映射到一个唯一的标识符,即SRG名称。SRG映射在用户客户端运行游戏时用于开发。 -To allow easier development, ForgeGradle allows the user to choose a mapping set to apply on top of SRG mappings, which we will refer to as human-readable mappings. ForgeGradle knows how to convert the mod jar to SRG mappings for use in production via the `reobf*` task. +为了便于开发,ForgeGradle允许用户选择一个映射集,在SRG映射的基础上应用,这里我们称之为可读的映射。ForgeGradle知道如何将模组jar转换为用于开发的SRG映射,通过`reobf*`任务。 -The mapping set used can be specified by setting the `mappings` field in the `minecraft` block. The `mappings` field takes in two arguments: `channel` which is the type of the mapping set, and `version` which is the version of the mapping set to apply. +可以通过在`minecraft`代码块中设置`mappings`字段来指定使用的映射集。`mappings`字段接受两个参数:`channel`,它是映射集的类型,以及`version`,它是要应用的映射集的版本。 -Currently, there are three default mapping sets built into ForgeGradle: +目前,默认内置到ForgeGradle中有三个映射集: -* `official` - This uses the mapping set provided by Mojang. These mappings do not have parameter names and only exist from 1.14 onward. -* `stable` - This uses a mapping set generated by MCP. These are typically incomplete and no longer exist as of 1.17. -* `snapshot` - This also is a mapping set generated by MCP, similar to a nightly build of a program. These are also typically incomplete and no longer exist as of 1.17. +* `official` - 使用由Mojang提供的映射集。这些映射没有参数名称,且只存在于1.14及更高版本。 +* `stable` - 使用由MCP生成的映射集。这些通常是不完整的,从1.17版本开始就不再存在了。 +* `snapshot` - 也是由MCP生成的映射集,类似于程序的每日版本。这些也通常是不完整的,从1.17版本开始就不再存在了。 -:::note -The class names used in production are from `stable` prior to 1.17 and from `official` from 1.17 onwards. +:::注意 +开发中使用的类名来自1.17之前的`stable`,从1.17开始来自`official`。 ::: ```gradle mappings { - // Sets the mappings to use those from Mojang for Minecraft 1.19.4. + // 设置映射,使用Mojang为Minecraft 1.19.4版本提供的映射。 mappings channel: 'official', version: '1.19.4' // ... @@ -53,7 +53,7 @@ mappings { ### Parchment -Parchment is an official project maintained by ParchmentMC which provides open, community-sourced parameter names and javadocs on top of the `official` mapping set. You can learn how setup and use the parchment mapping set on [their website][parchment]. +Parchment是由ParchmentMC维护的官方项目,在`official`映射集的基础上提供开放的、社区来源的参数名称和javadocs。你可以在[他们的网站][parchment]上了解如何设置和使用parchment映射集。 [at]: https://docs.minecraftforge.net/en/latest/advanced/accesstransformers/ [fart]: https://github.com/MinecraftForge/ForgeAutoRenamingTool From 9cb61b6ff15016bfc3fd77caf4368e2ccf6cbdee Mon Sep 17 00:00:00 2001 From: DaZiDian <113085496+DaZiDian@users.noreply.github.com> Date: Thu, 25 Apr 2024 12:37:08 +0800 Subject: [PATCH 18/87] Update runs.md --- .../version-5.x/docs/configuration/runs.md | 110 +++++++++--------- 1 file changed, 55 insertions(+), 55 deletions(-) diff --git a/neogradle_versioned_docs/version-5.x/docs/configuration/runs.md b/neogradle_versioned_docs/version-5.x/docs/configuration/runs.md index 083e8813f..74f79cef9 100644 --- a/neogradle_versioned_docs/version-5.x/docs/configuration/runs.md +++ b/neogradle_versioned_docs/version-5.x/docs/configuration/runs.md @@ -1,139 +1,139 @@ -Run Configurations +运行配置 ================== -Run configurations define how an instance of the game is going to run. This includes arguments, working directories, task names, etc. Run configurations are defined within the `minecraft.runs` block. While no runs are configured by default, [Forge][userdev] does provide the configurations `client`, `server`, `data`, or `gameTestServer`. +运行配置定义了一个游戏实例如何运行。这包括参数、工作目录、任务名等。运行配置在`minecraft.runs`块中定义。虽然默认情况下没有配置运行,但[Forge][userdev]确实提供了`client`、`server`、`data`、或`gameTestServer`的配置。 ```gradle minecraft { // ... runs { - // Configure runs here + // 在此处配置运行 } } ``` -Run configurations can be added similar to any `NamedDomainObjectContainer` using closures. +运行配置可以使用闭包类似于任何`NamedDomainObjectContainer`进行添加。 ```gradle -// Inside the minecraft block +// 在minecraft代码块内部 runs { - // Creates or configures the run configuration named 'client' + // 创建或配置名为'client'的运行配置 client { - // Configure run + // 配置运行 } } ``` -The following configurations properties are available: +以下是可用的配置属性: ```gradle -// Inside the runs block +// 在runs块内部 client { - // The name of the Gradle run tasks, - // Defaults to 'runX' where X is the container name + // Gradle运行任务的名称, + // 默认为`runX`,其中X是容器名称 taskName 'runThing' - // Sets the entrypoint of the program to launch - // Forge sets userdev main to be 'cpw.mods.bootstraplauncher.BootstrapLauncher' + // 设置要启动程序的入口点 + // Forge将userdev main设置为'cpw.mods.bootstraplauncher.BootstrapLauncher' main 'com.example.Main' - // Sets the working directory of the config - // Defaults to './run' + // 设置配置的工作目录 + // 默认为'./run' workingDirectory 'run' - // Sets the name of the module for IntelliJ IDEA to configure for its runs - // Defaults to '.main' + // 设置IntelliJ IDEA配置其运行的模块的名称 + // 默认为'.main' ideaModule 'example.main' - // Sets whether this should run a Minecraft client - // If not specified, checks the following - // - Is there an environment property 'thing' that contains 'client' - // - Does the configuration name contain 'client' - // - Is main set to 'mcp.client.Start' - // - Is main set to 'net.minecraft.client.main.Main' + // 设置是否应运行Minecraft客户端 + // 如果未指定,检查以下内容 + // - 是否存在一个环境属性'thing',包含'client' + // - 配置名称中是否包含'client' + // - main是否设置为'mcp.client.Start' + // - main是否设置为'net.minecraft.client.main.Main' client true - // Set the parent of this configuration to inherit from + // 设置此配置的父级以便继承 parent runs.example - // Sets the children of this configuration + // 设置此配置的子配置 children runs.child - // Merges this configuration and specifies whether to overwrite existing properties + // 合并此配置并指定是否覆盖现有属性 merge runs.server, true - // If not false, will merge the arguments of the parent with this configuration + // 如果不为false,将父配置的参数合并到此配置中 inheritArgs false - // If not false, will merge the JVM arguments of the parent with this configuration + // 如果不为false,将父配置的JVM参数合并到此配置中 inheritJvmArgs false - // Adds a sourceset to the classpath - // If none is specified, adds sourceSet.main + // 将源集添加到类路径中 + // 如果未指定,添加sourceSet.main source sourceSets.api - // Sets an environment property for the run - // Value will be interpreted as a file or a string + // 设置运行的环境属性 + // 值将被解释为文件或字符串 environment 'envKey', 'value' - // Sets a system property - // Value will be interpreted as a file or a string + // 设置系统属性 + // 值将被解释为文件或字符串 property 'propKey', 'value' - // Sets an argument to be passed into the application - // Can specify multiple with 'args' + // 设置传入应用程序的参数 + // 可以使用'args'指定多个 arg 'hello' - // Sets a JVM argument - // Can specify multiple with 'jvmArgs' + // 设置JVM参数 + // 可以使用'jvmArgs'指定多个 jvmArg '-Xmx2G' - // Sets a token - // Currently, the following tokens are being used: + // 设置标记 + // 目前,正在使用以下的标记: // - runtime_classpath // - minecraft_classpath token 'tokenKey', 'value' - // Sets a token that's lazily initialized - // Should usually be used instead of 'token', for example when the token resolves Gradle configurations + //设置延迟初始化的标记 + // 应该通常用于替代'token',例如当标记解析Gradle配置时 lazyToken('lazyTokenKey') { 'value' } - // If not false, Gradle will stop once the process has finished + // 如果不为false,Gradle将在进程结束后停止 forceExit true - // If true, compile all projects instead of for the current task - // This is only used by IntelliJ IDEA + // 如果为true,编译所有项目而不是当前任务 + // 此选项仅由IntelliJ IDEA使用 buildAllProjects false } ``` -:::tip -You can see a list of all configured userdev properties within the [MinecraftForge buildscript][buildscript]. +:::提示 +你可以在[MinecraftForge构建脚本][buildscript]内看到所有配置的userdev属性的列表。 ::: -Mod Configurations +模组配置 ------------------ -A mod in the current environment can be added using the `mods` block within a Run configuration. Mod blocks are also `NamedDomainObjectContainer`s. +可以使用Run配置中的`mods`块添加当前环境中的模组。模组块也是`NamedDomainObjectContainer`。 ```gradle -// Inside the runs block +// 在runs块内部 client { // ... mods { - // Configures the 'example' mod + // 配置'example'模块 example { - // Add a source set to a mod's sources - // This is recommended over manually adding classes and resources + // 将源集添加到模组的源中 + // 这比手动添加类和资源更为推荐 source sourceSets.main - // Sets the location of the mod's classes + // 设置模组类的位置 classes sourceSets.api.output - // Sets the location of the mod's resources + // 设置模组资源的位置 resources files('./my_resources') } } From 9c59015ee58f51a691268ff04a0e001eae289514 Mon Sep 17 00:00:00 2001 From: DaZiDian <113085496+DaZiDian@users.noreply.github.com> Date: Thu, 25 Apr 2024 12:40:04 +0800 Subject: [PATCH 19/87] Update index.md --- .../version-5.x/docs/dependencies/index.md | 57 +++++++++---------- 1 file changed, 27 insertions(+), 30 deletions(-) diff --git a/neogradle_versioned_docs/version-5.x/docs/dependencies/index.md b/neogradle_versioned_docs/version-5.x/docs/dependencies/index.md index 1576ec66a..9cb878bec 100644 --- a/neogradle_versioned_docs/version-5.x/docs/dependencies/index.md +++ b/neogradle_versioned_docs/version-5.x/docs/dependencies/index.md @@ -1,48 +1,48 @@ -Dependencies +依赖关系 ============ -Dependencies are not only used to develop interoperability between mods or add additional libraries to the game, but it also determines what version of Minecraft to develop for. This will provide a quick overview on how to modify the `repositories` and `dependencies` block to add dependencies to your development environment. +依赖关系不仅用于开发模组之间的互操作性或向游戏添加额外的库,而且还决定了要为哪个版本的Minecraft开发。这里会简要介绍如何修改`repositories`和`dependencies`块来向你的开发环境添加依赖。 -> This will not explain Gradle concepts in depth. It is highly recommended to read the [Gradle Dependency Management guide][guide] before continuing. +> 这不会深入解释Gradle概念。在继续之前,强烈推荐阅读[Gradle依赖管理指南][guide]。 `minecraft` ----------- -The `minecraft` dependency specifies the version of Minecraft to use and must be included in the `dependencies` block. Any artifact, except artifacts which have the group `net.minecraft`, will apply any patches provided with the dependency. This typically only specifies the `net.minecraftforge:forge` artifact. +`minecraft`依赖项指定要使用的Minecraft版本,必须包含在`dependencies`块中。任何非`net.minecraft`组的工件都将应用随依赖项提供的任何补丁。这通常仅指定`net.minecraftforge:forge`工件。 ```gradle dependencies { - // Version of Forge artifact is in the form '-' - // 'mc_version' is the version of Minecraft to load (e.g., 1.19.4) - // 'forge_version' is the version of Forge wanted for that Minecraft version (e.g., 45.0.23) - // Vanilla can be compiled against using 'net.minecraft:joined:' instead + // Forge工件的版本格式为'-' + // 'mc_version' 是要加载的Minecraft版本(例如,1.19.4) + // 'forge_version' 是该Minecraft版本所需的Forge版本(例如,45.0.23) + // 正版可以使用'net.minecraft:joined:'来编译 minecraft 'net.minecraftforge:forge:1.19.4-45.0.23' } ``` -Mod Dependencies +模组依赖项 ---------------- -In a typical development environment, Minecraft is deobfuscated to intermediate mappings, used in production, and then transformed into whatever [human-readable mappings][mappings] the modder specified. Mod artifacts, when built, are obfuscated to production mappings (SRG), and as such, are unable to be used directly as a Gradle dependency. +在典型的开发环境中,Minecraft的代码会去混淆到中间映射,这些映射用于生产,然后转换成模组开发者指定的任何[可读的映射][mappings]。构建的模组工件在生产中被混淆为SRG映射,因此不能直接用作Gradle依赖。 -As such, all mod dependencies need to be wrapped with `fg.deobf` before being added to the intended configuration. +因此,所有模组依赖项在添加到预定配置之前都需要用`fg.deobf`进行包装。 ```gradle dependencies { - // Assume we have already specified the 'minecraft' dependency + // 假设我们已经指定了`minecraft`依赖项 - // Assume we have some artifact 'examplemod' that can be obtained from a specified repository + // 假设我们有一些可以从指定仓库获得的`examplemod`工件 implementation fg.deobf('com.example:examplemod:1.0') } ``` -### Local Mod Dependencies +### 本地模组依赖项 -If the mod you are trying to depend on is not available on a maven repository (e.g., [Maven Central][central], [CurseMaven], [Modrinth]), you can add a mod dependency using a [flat directory] instead: +如果你试图依赖的模组没有在maven仓库(例如,[Maven Central][central]、[CurseMaven]、[Modrinth])上可用,你可以使用[平面目录][flat]添加模组依赖项: ```gradle repositories { - // Adds the 'libs' folder in the project directory as a flat directory + // 将项目目录中的`libs`文件夹添加为平面目录 flatDir { dir 'libs' } @@ -51,16 +51,16 @@ repositories { dependencies { // ... - // Given some ::: - // with an extension - // Artifacts in flat directories will be resolved in the following order: + // 假定有某个::: + // 且扩展名为 + // 平面目录中的工件将按以下顺序解析: // - -. // - --. // - . // - -. - // If a classifier is explicitly specified - // artifacts with the classifier will take priority: + // 如果显式指定了分类器 + // 带有分类器的工件将获得优先权: // - examplemod-1.0-api.jar // - examplemod-api.jar // - examplemod-1.0.jar @@ -69,33 +69,30 @@ dependencies { } ``` -:::note -The group name can be anything but must not be empty for flat directory entries as they are not checked when resolving the artifact file. +:::注意 +组名可以是任何东西,但对于平面目录项来说不能是空的,因为在解析工件文件时不会检查它。 ::: -Non-Minecraft Dependencies +非Minecraft依赖项 -------------------------- -Non-Minecraft dependencies are not loaded by Forge by default in the development environment. To get Forge to recognize the non-Minecraft dependency, they must be added to the `minecraftLibrary` configuration. `minecraftLibrary` works similarly to the `implementation` configuration within Gradle, being applied during compile time and runtime. +Forge默认情况下不会在开发环境中加载非Minecraft依赖项。要让Forge识别非Minecraft依赖项,它们必须被添加到`minecraftLibrary`配置中。`minecraftLibrary`的工作方式类似于Gradle中的`implementation`配置,在编译时和运行时被应用。 ```gradle dependencies { // ... - // Assume there is some non-Minecraft library 'dummy-lib' + // 假设有一些非Minecraft库'dummy-lib' minecraftLibrary 'com.dummy:dummy-lib:1.0' } ``` -> Non-Minecraft dependencies added to the development environment will not be included in built artifact by default! You must use [Jar-In-Jar][jij] to include the dependencies within the artifact on build. +> 默认情况下,添加到开发环境中的非Minecraft依赖项将不会包含在构建的工件中!你必须使用[Jar-In-Jar][jij]在构建时,在工件中包含这些依赖项。 [guide]: https://docs.gradle.org/7.6/userguide/dependency_management.html [mappings]: ../configuration/index.md#human-readable-mappings - [central]: https://central.sonatype.com/ [CurseMaven]: https://cursemaven.com/ [Modrinth]: https://docs.modrinth.com/docs/tutorials/maven/ - [flat]: https://docs.gradle.org/7.6/userguide/declaring_repositories.html#sub:flat_dir_resolver - [jij]: ./jarinjar.md From d0bf0fc7699c4dc3e4bf76bf7369b7e4f3eb694a Mon Sep 17 00:00:00 2001 From: DaZiDian <113085496+DaZiDian@users.noreply.github.com> Date: Thu, 25 Apr 2024 12:46:10 +0800 Subject: [PATCH 20/87] Update jarinjar.md --- .../version-5.x/docs/dependencies/jarinjar.md | 85 ++++++------------- 1 file changed, 27 insertions(+), 58 deletions(-) diff --git a/neogradle_versioned_docs/version-5.x/docs/dependencies/jarinjar.md b/neogradle_versioned_docs/version-5.x/docs/dependencies/jarinjar.md index 6222a5fe0..3436bf0f2 100644 --- a/neogradle_versioned_docs/version-5.x/docs/dependencies/jarinjar.md +++ b/neogradle_versioned_docs/version-5.x/docs/dependencies/jarinjar.md @@ -1,109 +1,78 @@ Jar-in-Jar ========== -Jar-in-Jar is a way to load dependencies for mods from within the jars of the mods. To accomplish this, Jar-in-Jar generates a metadata json within `META-INF/jarjar/metadata.json` on build containing the artifacts to load from within the jar. +Jar-in-Jar 是一种从模组的 jar 文件内加载依赖的方式。为了实现这一点,在构建时 Jar-in-Jar 在 `META-INF/jarjar/metadata.json` 中生成一个元数据 json 文件,其中包含要从 jar 内加载的工件。 -Jar-in-Jar is a completely optional system which can be enabled using `jarJar#enable` before the `minecraft` block. This will include all dependencies from the `jarJar` configuration into the `jarJar` task. You can configure the task similarly to other jar tasks: +Jar-in-Jar 是一个完全可选的系统,可以使用 `jarJar#enable` 在 `minecraft` 代码块之前启用。这将包括所有来自 `jarJar` 配置的依赖到 `jarJar` 任务中。你可以像其它 jar 任务一样配置这个任务: ```gradle -// In build.gradle +// 在 build.gradle 中 -// Enable the Jar-in-Jar system for your mod +// 为你的模组启用 Jar-in-Jar 系统 jarJar.enable() - -// Configure the 'jarJar' task -// 'all' is the default classifier +// 配置 'jarJar' 任务 +// 'all' 是默认的分类器 tasks.named('jarJar') { // ... } ``` -Adding Dependencies +添加依赖 ------------------- -You can add dependencies to be included inside your jar using the `jarJar` configuration. As Jar-in-Jar is a negotiation system, all versions should supply a supported range. +你可以使用 `jarJar` 配置在你的 jar 文件中包含要添加的依赖。由于 Jar-in-Jar 是一个协商系统,所有的版本都应提供一个支持的版本范围。 ```gradle -// In build.gradle +// 在 build.gradle 中 dependencies { - // Compiles against and includes the highest supported version of examplelib - // between 2.0 (inclusive) and 3.0 (exclusive) + // 编译并包含 examplelib 的最高支持版本 + // 从 2.0(包含)到 3.0(不包含) jarJar(group: 'com.example', name: 'examplelib', version: '[2.0,3.0)') } ``` -If you need to specify an exact version to include rather than the highest supported version in the range, you can use `jarJar#pin` within the dependency closure. In these instances, the artifact version will be used during compile time while the pinned version will be bundled inside the mod jar. +如果你需要在编译时指定要包含的确切版本,而不是范围内的最高支持版本,你可以在依赖闭包内使用 `jarJar#pin`。在这些情况下,将在编译时使用工件版本,而固定的版本将被打包在模组 jar 内。 ```gradle -// In build.gradle +// 在 build.gradle 中 dependencies { - // Compiles against the highest supported version of examplelib - // between 2.0 (inclusive) and 3.0 (exclusive) + // 编译时使用 examplelib 的最高支持版本 + // 从 2.0(包含)到 3.0(不包含) jarJar(group: 'com.example', name: 'examplelib', version: '[2.0,3.0)') { - // Includes examplelib 2.8.0 + // 包含 examplelib 2.8.0 jarJar.pin(it, '2.8.0') } } ``` -You can additionally pin a version range while compiling against a specific version instead: +你也可以在编译对特定版本编译的同时,固定一个版本范围: ```gradle -// In build.gradle +// 在 build.gradle 中 dependencies { - // Compiles against examplelib 2.8.0 + // 针对 examplelib 2.8.0 编译 jarJar(group: 'com.example', name: 'examplelib', version: '2.8.0') { - // Includes the highest supported version of examplelib - // between 2.0 (inclusive) and 3.0 (exclusive) + // 包含 examplelib 的最高支持版本 + // 从 2.0(包含)到 3.0(不包含) jarJar.pin(it, '[2.0,3.0)') } } ``` -### Using Runtime Dependencies +### 使用运行时依赖 -If you would like to include the runtime dependencies of your mod inside your jar, you can invoke `jarJar#fromRuntimeConfiguration` within your buildscript. If you decide to use this option, it is highly suggested to include dependency filters; otherwise, every single dependency -- including Minecraft and Forge -- will be bundled in the jar as well. To support more flexible statements, the `dependency` configuration has been added to the `jarJar` extension and task. Using this, you can specify patterns to include or exclude from the configuration: +如果你希望在你的 jar 中包含你的模组的运行时依赖,你可以在构建脚本中调用 `jarJar#fromRuntimeConfiguration`。如果你决定使用这个选项,强烈建议包含依赖过滤器;否则,包括 Minecraft 和 Forge 在内的每一个依赖项都会被打包到jar文件中。为了支持更灵活的声明,`dependency` 配置已添加到 `jarJar` 扩展和任务中。使用它,你可以指定模式来包括或排除来自配置的内容: ```gradle -// In build.gradle +// 在 build.gradle 中 -// Add runtime dependencies to jar +// 向 jar 添加运行时依赖 jarJar.fromRuntimeConfiguration() // ... jarJar { - // Include or exclude dependencies here from runtime configuration + // 在这里包含或排除运行时配置中的依赖 dependencies { - // Exclude any dependency which begins with 'com.google.gson.' - exclude(dependency('com.google.gson.*')) - } -} -``` - -:::tip -It is generally recommended to set at least one `include` filter when using `#fromRuntimeConfiguration`. -::: - -Publishing a Jar-in-Jar to Maven --------------------------------- - -For archival reasons, ForgeGradle supports publishing Jar-in-Jar artifacts to a maven of choice, similar to how the [Shadow plugin][shadow] handles it. In practices, this is not useful or recommended. - -```gradle -// In build.gradle (has 'maven-publish' plugin) - -publications { - mavenJava(MavenPublication) { - // Add standard java components and Jar-in-Jar artifact - from components.java - jarJar.component(it) - - // ... - } -} -``` - - -[shadow]: https://imperceptiblethoughts.com/shadow/getting-started/ + // 排除任何以 'com.google.gson.' 开头的 From 576b02f4df695822881bf4c26d2eaec03b94d8ca Mon Sep 17 00:00:00 2001 From: DaZiDian <113085496+DaZiDian@users.noreply.github.com> Date: Thu, 25 Apr 2024 12:49:31 +0800 Subject: [PATCH 21/87] Update index.md --- .../version-5.x/docs/gettingstarted/index.md | 54 +++++++++---------- 1 file changed, 26 insertions(+), 28 deletions(-) diff --git a/neogradle_versioned_docs/version-5.x/docs/gettingstarted/index.md b/neogradle_versioned_docs/version-5.x/docs/gettingstarted/index.md index 248d1175b..beb5ba9eb 100644 --- a/neogradle_versioned_docs/version-5.x/docs/gettingstarted/index.md +++ b/neogradle_versioned_docs/version-5.x/docs/gettingstarted/index.md @@ -1,42 +1,41 @@ -Getting Started with ForgeGradle +ForgeGradle入门 ================================ -If you have never used ForgeGradle before, here is the minimum amount of information needed to get a development environment setup. +如果你以前从未使用过ForgeGradle,这里有一些设置开发环境所需的最少内容。 -#### Prerequisites +#### 先决条件 -* An installation of the Java Development Kit (JDK) +* Java开发工具包(JDK)的安装 -Minecraft Versions | Java Development Kit Version -:---: | :---: -1.12 - 1.16 | [JDK 8][jdk8] -1.17 | [JDK 16][jdk16] -1.18 - 1.19 | [JDK 17][jdk17] +Minecraft版本 | Java开发工具包版本 +:---: | :---: +1.12 - 1.16 | [JDK 8][jdk8] +1.17 | [JDK 16][jdk16] +1.18 - 1.19 | [JDK 17][jdk17] -* Familiarity with an Integrated Development Environment (IDE) - * It is preferable to use one with some form of Gradle integration +* 熟悉一个集成开发环境(IDE) + * 最好使用带有某种形式的Gradle集成的IDE -## Setting Up ForgeGradle +## 设置ForgeGradle -1. First download a copy of the [Modder Development Kit (MDK)][mdk] from MinecraftForge and extract the zip to an empty directory. -1. Open the directory you extracted the MDK to within your IDE of choice. If your IDE integrates with Gradle, import it as a Gradle project. -1. Customize your Gradle buildscript for your mod: - 1. Set `archivesBaseName` to the desired mod id. Additionally, replace all occurrences of `examplemod` with the mod id as well. - 1. Change the `group` to your desired package name. Make sure to follow existing [naming conventions][group]. - 1. Change the `version` number to reflect the current version of your mod. It is highly recommended to use [Forge's extension on semantic versioning][semver]. +1. 首先从MinecraftForge下载[模组开发套件(MDK)][mdk]的副本,并将zip解压到一个空目录中。 +1. 在你选择的IDE中打开你解压MDK的目录。如果你的IDE与Gradle集成,将其作为一个Gradle项目导入。 +1. 自定义你的Gradle构建脚本以适合你的模组: + 1. 将`archivesBaseName`设置为所需的模组ID。此外,将所有出现的`examplemod`替换为模组ID。 + 1. 更改`group`为你想要的包名。确保遵循现有的[命名惯例][group]。 + 1. 将`version`号更改为反映你的模组的当前版本。强烈建议使用[Forge对语义版本控制的扩展][semver]。 - -:::caution -Make sure that any changes to the mod id are reflected in the mods.toml and main mod class. See [Structuring Your Mod][structuring] on the Forge docs for more information. +:::警告 +确保对模组ID的任何更改都反映在mods.toml和主模组类中。有关更多信息,请参阅Forge文档上的[结构化你的模组][structuring]。 ::: -4. Reload or refresh your Gradle project using your IDE. If your IDE does not have Gradle integration, run the following from a shell in your project's directory: +4. 使用你的IDE重新加载或刷新你的Gradle项目。如果你的IDE没有Gradle集成,以上一个shell在你项目的目录下运行以下命令: ```sh ./gradlew build --refresh-dependencies ``` -5. If your IDE is either Eclipse, IntelliJ IDEA, or Visual Studio Code, you can generate run configurations using one of the following commands, respectively: +5. 如果你的IDE是Eclipse、IntelliJ IDEA或Visual Studio Code,你可以使用下列命令之一生成运行配置: #### Eclipse @@ -56,14 +55,13 @@ Make sure that any changes to the mod id are reflected in the mods.toml and main ./gradlew genVSCodeRuns ``` -You can the run the client, server, etc. using one of the generated run configurations. +你可以使用生成的运行配置来运行客户端、服务器等。 -:::tip -If your IDE is not listed, you can still run the configurations using `./gradlew run*` (e.g., `runClient`, `runServer`, `runData`). You can use these commands with the supported IDEs as well. +:::提示 +如果你的IDE未被列出,你仍然可以使用`./gradlew run*`(例如,`runClient`, `runServer`, `runData`)运行配置。你也可以在支持的IDE中使用这些命令。 ::: -Congratulations, now you have a development environment set up! - +恭喜你,现在你已经设置了一个开发环境! [jdk8]: https://adoptium.net/temurin/releases/?version=8 [jdk16]: https://adoptium.net/temurin/releases/?version=16 From 8f1207ae91bda419c7c77a0a9f1b0d5e41ffb659 Mon Sep 17 00:00:00 2001 From: DaZiDian <113085496+DaZiDian@users.noreply.github.com> Date: Thu, 25 Apr 2024 13:11:31 +0800 Subject: [PATCH 22/87] Update index.md --- docs/blocks/index.md | 216 +++++++++++++++++++++---------------------- 1 file changed, 105 insertions(+), 111 deletions(-) diff --git a/docs/blocks/index.md b/docs/blocks/index.md index 95ed54dd3..95f1376da 100644 --- a/docs/blocks/index.md +++ b/docs/blocks/index.md @@ -1,71 +1,67 @@ -# Blocks +## 统一方块的规则 -Blocks are essential to the Minecraft world. They make up all the terrain, structures, and machines. Chances are if you are interested in making a mod, then you will want to add some blocks. This page will guide you through the creation of blocks, and some of the things you can do with them. +在开始之前,你需要明白在游戏中每个方块都只有一个。一个世界由成千上万个在不同位置引用该方块的实例组成。换句话说,同一个方块多次被显示。 -## One Block to Rule Them All +因此,一个方块只应该在[注册]期间实例化一次。一旦注册了方块,你可以根据需要使用已注册的引用。 -Before we get started, it is important to understand that there is only ever one of each block in the game. A world consists of thousands of references to that one block in different locations. In other words, the same block is just displayed a lot of times. +与大多数其他注册表不同,方块可以使用`DeferredRegister`的特殊版本,称为`DeferredRegister.Blocks`。 `DeferredRegister.Blocks`基本上就像`DeferredRegister`,但有一些细微的差别: -Due to this, a block should only ever be instantiated once, and that is during [registration]. Once the block is registered, you can then use the registered reference as needed. +- 它们是通过`DeferredRegister.createBlocks("yourmodid")`创建的,而不是通常的`DeferredRegister.create(...)`方法。 +- `#register`返回一个`DeferredBlock`,它扩展了`DeferredHolder`。 `T`是我们正在注册的方块类的类型。 +- 有一些帮助注册方块的方法。 更多详情请参见[下方]。 -Unlike most other registries, blocks can use a specialized version of `DeferredRegister`, called `DeferredRegister.Blocks`. `DeferredRegister.Blocks` acts basically like a `DeferredRegister`, but with some minor differences: - -- They are created via `DeferredRegister.createBlocks("yourmodid")` instead of the regular `DeferredRegister.create(...)` method. -- `#register` returns a `DeferredBlock`, which extends `DeferredHolder`. `T` is the type of the class of the block we are registering. -- There are a few helper methods for registering block. See [below] for more details. - -So now, let's register our blocks: +现在,让我们注册我们的方块: ```java //BLOCKS is a DeferredRegister.Blocks public static final DeferredBlock MY_BLOCK = BLOCKS.register("my_block", () -> new Block(...)); ``` -After registering the block, all references to the new `my_block` should use this constant. For example, if you want to check if the block at a given position is `my_block`, the code for that would look something like this: +注册了方块后,所有对新的`my_block`的引用应使用此常量。例如,如果你想检查给定位置的方块是否是`my_block`,那么相应的代码看起来像这样: ```java -level.getBlockState(position) // returns the blockstate placed in the given level (world) at the given position +level.getBlockState(position) //返回在给定位置放置的方块状态 //highlight-next-line .is(MyBlockRegistrationClass.MY_BLOCK.get()); ``` -This approach also has the convenient effect that `block1 == block2` works and can be used instead of Java's `equals` method (using `equals` still works, of course, but is pointless since it compares by reference anyway). +这种方法也有一个方便的效果,即`block1 == block2`有效,并且可以代替Java的`equals`方法使用(当然,使用`equals`仍然有效,但是因为它还是通过引用进行比较,所以没有意义)。 :::danger -Do not call `new Block()` outside registration! As soon as you do that, things can and will break: +不要在注册外部调用`new Block()`!一旦你那样做了,会出现问题: -- Blocks must be created while registries are unfrozen. NeoForge unfreezes registries for you and freezes them later, so registration is your time window to create blocks. -- If you try to create and/or register a block when registries are frozen again, the game will crash and report a `null` block, which can be very confusing. -- If you still manage to have a dangling block instance, the game will not recognize it while syncing and saving, and replace it with air. +- 方块必须在注册表解锁时创建。NeoForge为您解锁注册表,并稍后再冻结它们,所以注册是您创建方块的时机窗口。 +- 如果你在注册表再次冻结时尝试创建和/或注册方块,游戏将崩溃并报告一个“null”方块,这可能会非常混乱。 +- 如果你仍然设法拥有一个悬空的方块实例,游戏在同步和保存时将不能识别它,并将其替换为空气。 ::: -## Creating Blocks +## 创建方块 -As discussed before, we start by creating our `DeferredRegister.Blocks`: +如上所述,我们首先创建我们的`DeferredRegister.Blocks`: ```java public static final DeferredRegister.Blocks BLOCKS = DeferredRegister.createBlocks("yourmodid"); ``` -### Basic Blocks +### 基础方块 -For simple blocks which need no special functionality (think cobblestone, wooden planks, etc.), the `Block` class can be used directly. To do so, during registration, instantiate `Block` with a `BlockBehaviour.Properties` parameter. This `BlockBehaviour.Properties` parameter can be created using `BlockBehaviour.Properties#of`, and it can be customized by calling its methods. The most important methods for this are: +对于不需要特殊功能的简单方块(如圆石,木板等),可以直接使用`Block`类。要做到这一点,在注册期间,用`BlockBehaviour.Properties`参数实例化`Block`。可以使用`BlockBehaviour.Properties#of`创建此`BlockBehaviour.Properties`参数,并可以通过调用其方法进行定制。这其中最重要的方法是: -- `destroyTime` - Determines the time the block needs to be destroyed. - - Stone has a destroy time of 1.5, dirt has 0.5, obsidian has 50, and bedrock has -1 (unbreakable). -- `explosionResistance` - Determines the explosion resistance of the block. - - Stone has an explosion resistance of 6.0, dirt has 0.5, obsidian has 1,200, and bedrock has 3,600,000. -- `sound` - Sets the sound the block makes when it is punched, broken, or placed. - - The default value is `SoundType.STONE`. See the [Sounds page][sounds] for more details. -- `lightLevel` - Sets the light emission of the block. Accepts a function with a `BlockState` parameter that returns a value between 0 and 15. - - For example, glowstone uses `state -> 15`, and torches use `state -> 14`. -- `friction` - Sets the friction (slipperiness) of the block. - - Default value is 0.6. Ice uses 0.98. +- `destroyTime`-决定破坏方块所需的时间。 + - 石头的破坏时间为1.5,泥土为0.5,黑曜石为50,基岩为-1(不可破坏)。 +- `explosionResistance`-决定方块的抗爆性。 + - 石头的抗爆性为6.0,泥土为0.5,黑曜石为1,200,基岩为3,600,000。 +- `sound`-设置方块在被击中,打破或放置时的声音。 + - 默认值是`SoundType.STONE`。更多详细信息请参见[声音页面][sounds]。 +- `lightLevel`-设置方块的光线发射。接收一个带有`BlockState`参数的函数,返回0到15之间的值。 + 例如,萤石使用`state -> 15`,火炬使用`state -> 14`。 +- `摩擦` - 设置方块的摩擦(滑滑的程度)。 + - 默认值是0.6。冰使用0.98。 -So for example, a simple implementation would look something like this: +例如,一个简单的实现可能看起来像这样: ```java -//BLOCKS is a DeferredRegister.Blocks +// BLOCKS is a DeferredRegister.Blocks public static final DeferredBlock MY_BETTER_BLOCK = BLOCKS.register( "my_better_block", () -> new Block(BlockBehaviour.Properties.of() @@ -78,78 +74,76 @@ public static final DeferredBlock MY_BETTER_BLOCK = BLOCKS.register( )); ``` -For further documentation, see the source code of `BlockBehaviour.Properties`. For more examples, or to look at the values used by Minecraft, have a look at the `Blocks` class. +有关更多文档,请参阅`BlockBehaviour.Properties`的源代码。有关更多示例,或查看Minecraft使用的值,请查看`Blocks`类。 :::note -It is important to understand that a block in the world is not the same thing as in an inventory. What looks like a block in an inventory is actually a `BlockItem`, a special type of [item] that places a block when used. This also means that things like the creative tab or the max stack size are handled by the corresponding `BlockItem`. - -A `BlockItem` must be registered separately from the block. This is because a block does not necessarily need an item, for example if it is not meant to be collected (as is the case with fire, for example). -::: +重要的是要理解,世界中的一个方块并不同于库存中的东西。库存中看起来像方块的其实是`BlockItem`,它是一种特殊类型的[物品],在使用时会放置一个方块。这也就意味着,创造标签页或最大堆叠大小等内容都由相应的`BlockItem`处理。 -### More Functionality +`BlockItem`必须与方块单独注册。这是因为方块不一定需要一个物品,例如,如果它不能被收集 (例如火)。 +### 更多功能 -Directly using `Block` only allows for very basic blocks. If you want to add functionality, like player interaction or a different hitbox, a custom class that extends `Block` is required. The `Block` class has many methods that can be overridden to do different things; see the classes `Block`, `BlockBehaviour` and `IBlockExtension` for more information. See also the [Using blocks][usingblocks] section below for some of the most common use cases for blocks. +直接使用`Block`只能创造非常基本的方块。如果你想添加功能,像是玩家交互或不同的碰撞箱,就需要一个扩展了`Block`的自定义类。`Block`类有许多可以被重写以实现不同功能的方法;更多信息请参见`Block`、`BlockBehaviour`和`IBlockExtension`类。另外,请查看下方的[使用方块][usingblocks]部分,了解一些方块最常见的用例。 -If you want to make a block that has different variants (think a slab that has a bottom, top, and double variant), you should use [blockstates]. And finally, if you want a block that stores additional data (think a chest that stores its inventory), a [block entity][blockentities] should be used. The rule of thumb here is that if you have a finite and reasonably small amount of states (= a few hundred states at most), use blockstates, and if you have an infinite or near-infinite amount of states, use a block entity. +如果你想制作一个有不同变体的方块(想想一个有底部、顶部和双层变体的台阶),你应该使用[blockstates]。最后,如果你想要一个可以存储额外数据的方块(比如一个可以存储其库存的箱子),那么应该使用[block entity][blockentities]。这里的经验法则是,如果你有有限而且相当小的状态量(=最多几百个状态),使用blockstates;如果你有无限或近乎无限的状态量,使用方块实体。 -### `DeferredRegister.Blocks` helpers +### `DeferredRegister.Blocks` 辅助器 -We already discussed how to create a `DeferredRegister.Blocks` [above], as well as that it returns `DeferredBlock`s. Now, let's have a look at what other utilities the specialized `DeferredRegister` has to offer. Let's start with `#registerBlock`: +我们已经讨论了如何创建`DeferredRegister.Blocks`[上面],以及它返回`DeferredBlock`的内容。现在,让我们看看这个专门的`DeferredRegister`还有哪些辅助工具。我们先从`#registerBlock`开始: ```java public static final DeferredRegister.Blocks BLOCKS = DeferredRegister.createBlocks("yourmodid"); public static final DeferredBlock EXAMPLE_BLOCK = BLOCKS.registerBlock( "example_block", - Block::new, // The factory that the properties will be passed into. - BlockBehaviour.Properties.of() // The properties to use. + Block::new, // 将使用的属性传递到哪个工厂。 + BlockBehaviour.Properties.of() // 要使用的属性。 ); ``` -Internally, this will simply call `BLOCKS.register("example_block", () -> new Block(BlockBehaviour.Properties.of()))` by applying the properties parameter to the provided block factory (which is commonly the constructor). +在内部,这将简单地通过应用属性参数到所提供的方块工厂(通常是构造函数)来调用`BLOCKS.register("example_block", () -> new Block(BlockBehaviour.Properties.of()))`。 -If you want to use `Block::new`, you can leave out the factory entirely: +如果你想使用`Block::new`,可以完全不使用工厂: ```java public static final DeferredBlock EXAMPLE_BLOCK = BLOCKS.registerSimpleBlock( "example_block", - BlockBehaviour.Properties.of() // The properties to use. + BlockBehaviour.Properties.of() // 要使用的属性。 ); ``` -This does the exact same as the previous example, but is slightly shorter. Of course, if you want to use a subclass of `Block` and not `Block` itself, you will have to use the previous method instead. +这和之前的例子做的完全一样,只是稍微简洁了一些。当然,如果你想使用`Block`的子类而不是`Block`本身,你将不得不使用前面的方法。 -### Resources +### 资源 -If you register your block and place it in the world, you will find it to be missing things like a texture. This is because [textures], among others, are handled by Minecraft's resource system. To apply the texture to the block, you must provide a [model] and a [blockstate file][bsfile] that associates the block with the texture and a shape. Give the linked articles a read for more information. +当你注册你的方块并将其放置在世界中时,你会发现它缺少如纹理等内容。这是因为[纹理]等内容是由Minecraft的资源系统处理的。要将纹理应用到方块上,你必须提供一个[模型]和一个与纹理和形状关联的[方块状态文件][bsfile]。阅读链接文章以获取更多信息。 -## Using Blocks +## 使用方块 -Blocks are very rarely directly used to do things. In fact, probably two of the most common operations in all of Minecraft - getting the block at a position, and setting a block at a position - use blockstates, not blocks. The general design approach is to have the block define behavior, but have the behavior actually run through blockstates. Due to this, `BlockState`s are often passed to methods of `Block` as a parameter. For more information on how blockstates are used, and on how to get one from a block, see [Using Blockstates][usingblockstates]. +方块很少直接用来做事。实际上,可能在整个Minecraft中最常见的两个操作 - 获取位置上的方块,和设置位置上的方块 - 使用的是方块状态,而不是方块。一般的设计方法是让方块定义行为,但实际上通过方块状态来运行行为。因此,`BlockState`经常作为参数传递给`Block`的方法。有关如何使用方块状态的更多信息,以及如何从方块获取方块状态,请参见[使用方块状态][usingblockstates]。 -In several situations, multiple methods of `Block` are used at different times. The following subsections list the most common block-related pipelines. Unless specified otherwise, all methods are called on both logical sides and should return the same result on both sides. +在几种情况下,`Block`的多个方法在不同的时间被使用。以下小节列出了最常见的与方块相关的流程。除非另有说明,否则所有方法都在逻辑两侧调用,并应在两侧返回相同的结果。 -### Placing a Block +### 放置方块 -Block placement logic is called from `BlockItem#useOn` (or some subclass's implementation thereof, such as in `PlaceOnWaterBlockItem`, which is used for lily pads). For more information on how the game gets there, see the [Interaction Pipeline][interactionpipeline]. In practice, this means that as soon as a `BlockItem` is right-clicked (for example a cobblestone item), this behavior is called. +方块放置逻辑是从`BlockItem#useOn`(或其某些子类的实现,例如用于睡莲的`PlaceOnWaterBlockItem`)调用的。有关游戏如何到达这一点的更多信息,请参见[交互流程][interactionpipeline]。实际上,这意味着一旦`BlockItem`被右键点击(例如圆石物品),这个行为就被调用。 -- Several prerequisites are checked, for example that you are not in spectator mode, that all required feature flags for the block are enabled or that the target position is not outside the world border. If at least one of these checks fails, the pipeline ends. -- `Block#canBeReplaced` is called for the block currently at the position where the block is attempted to be placed. If it returns `false`, the pipeline ends. Prominent cases that return `true` here are tall grass or snow layers. -- `Block#getStateForPlacement` is called. This is where, depending on the context (which includes information like the position, the rotation and the side the block is placed on), different block states can be returned. This is useful for example for blocks that can be placed in different directions. -- `Block#canSurvive` is called with the blockstate obtained in the previous step. If it returns `false`, the pipeline ends. -- The blockstate is set into the level via a `Level#setBlock` call. - - In that `Level#setBlock` call, `Block#onPlace` is called. -- `Block#setPlacedBy` is called. +- 检查几个先决条件,例如你不是在旁观者模式下,所有要求的方块功能标志都已启用,或目标位置不在世界边界之外。如果至少有一个检查失败,流程结束。 +- 对当前位于被尝试放置方块的位置的方块调用`Block#canBeReplaced`。如果它返回`false`,流程结束。在这里返回`true`的显著案例是高草或雪层。 +- 调用`Block#getStateForPlacement`。这是根据上下文(包括位置,旋转和放置方块的侧面等信息)返回不同方块状态的地方。这对于例如可以以不同方向放置的方块很有用。 +- 用前一步获得的方块状态调用`Block#canSurvive`。如果返回`false`,流程结束。 +- 通过`Level#setBlock`调用将方块状态设置到游戏世界中。 + - 在那个`Level#setBlock`调用中,调用`Block#onPlace`。 +- 调用`Block#setPlacedBy`。 -### Breaking a Block +### 破坏方块 -Breaking a block is a bit more complex, as it requires time. The process can be roughly divided into three stages: "initiating", "mining" and "actually breaking". +破坏方块稍微复杂一些,因为它需要时间。这个过程可以大致分为三个阶段:“启动”,“挖掘”和“实际破坏”。 -- When the left mouse button is clicked, the "initiating" stage is entered. -- Now, the left mouse button needs to be held down, entering the "mining" stage. **This stage's methods are called every tick.** -- If the "continuing" stage is not interrupted (by releasing the left mouse button) and the block is broken, the "actually breaking" stage is entered. +- 当左键被点击时,进入“启动”阶段。 +- 现在,需要持续按住左键,进入“挖掘”阶段。**这个阶段的方法每个刻都会被调用。** +- 如果“继续”阶段没有被中断(通过释放左键)并且方块被打破,那么进入“实际破坏”阶段。 -Or for those who prefer pseudocode: +或者对于那些更喜欢伪代码的人: ```java leftClick(); @@ -163,65 +157,65 @@ while (leftClickIsBeingHeld()) { } ``` -The following subsections further break down these stages into actual method calls. +以下小节进一步将这些阶段分解为实际的方法调用。 -#### The "Initiating" Stage +#### “启动”阶段 -- Client-only: `InputEvent.InteractionKeyMappingTriggered` is fired with the left mouse button and the main hand. If the event is canceled, the pipeline ends. -- Several prerequisites are checked, for example that you are not in spectator mode, that all required feature flags for the `ItemStack` in your main hand are enabled or that the block in question is not outside the world border. If at least one of these checks fails, the pipeline ends. -- `PlayerInteractEvent.LeftClickBlock` is fired. If the event is canceled, the pipeline ends. - - Note that when the event is canceled on the client, no packets are sent to the server and thus no logic runs on the server. - - However, canceling this event on the server will still cause client code to run, which can lead to desyncs! -- `Block#attack` is called. +- 仅客户端:当左键和主手被触发时,会触发`InputEvent.InteractionKeyMappingTriggered`事件。如果事件被取消,流程结束。 +- 检查几个先决条件,例如你不是在旁观者模式下,主手中的`ItemStack`的所有必需功能标志都已启用,或被询问的方块不在世界边界之外。如果至少有一个检查失败,流程结束。 +- 触发`PlayerInteractEvent.LeftClickBlock`事件。如果事件被取消,流程结束。 + - 注意当事件在客户端被取消时,不会向服务器发送数据包,因此服务器上不会运行任何逻辑。 + - 然而,在服务器上取消此事件仍然会导致客户端代码运行,这可能会导致不同步! +- 调用`Block#attack`。 -#### The "Mining" Stage +#### “挖掘”阶段 -- `PlayerInteractEvent.LeftClickBlock` is fired. If the event is canceled, the pipeline moves to the "finishing" stage. - - Note that when the event is canceled on the client, no packets are sent to the server and thus no logic runs on the server. - - However, canceling this event on the server will still cause client code to run, which can lead to desyncs! -- `Block#getDestroyProgress` is called and added to the internal destroy progress counter. - - `Block#getDestroyProgress` returns a float value between 0 and 1, representing how much the destroy progress counter should be increased every tick. -- The progress overlay (cracking texture) is updated accordingly. -- If the destroy progress is greater than 1.0 (i.e. completed, i.e. the block should be broken), the "mining" stage is exited and the "actually breaking" stage is entered. +- 触发`PlayerInteractEvent.LeftClickBlock`事件。如果事件被取消,流程移动到“结束”阶段。 + - 注意当事件在客户端被取消时,不会向服务器发送数据包,因此服务器上不会运行任何逻辑。 + - 然而,在服务器上取消此事件仍然会导致客户端代码运行,这可能会导致不同步! +- 调用`Block#getDestroyProgress`并将其加到内部的破坏进度计数器上。 + - `Block#getDestroyProgress`返回一个介于0和1之间的浮点值,表示破坏进度计数器每个刻应该增加多少。 +- 相应地更新进度覆盖(破裂纹理)。 +- 如果破坏进度大于1.0(即完成,即方块应该被破坏),则退出“挖掘”阶段并进入“实际破坏”阶段。 -#### The "Actually Breaking" Stage +#### “实际破坏”阶段 -- `Item#onBlockStartBreak` is called. If it returns `true` (determining that the block should not be broken), the pipeline moves to the "finishing" stage. -- Server-only: `IBlockExtension#canHarvestBlock` is called. This determines whether the block can be harvested, i.e. broken with drops. -- `Block#onDestroyedByPlayer` is called. If it returns `false`, the pipeline moves to the "finishing" stage. In that `Block#onDestroyedByPlayer` call: - - `Block#playerWillDestroy` is called. - - The blockstate is removed from the level via a `Level#setBlock` call with `Blocks.AIR.defaultBlockState()` as the blockstate parameter. - - In that `Level#setBlock` call, `Block#onRemove` is called. -- `Block#destroy` is called. -- Server-only: If the previous call to `IBlockExtension#canHarvestBlock` returned `true`, `Block#playerDestroy` is called. -- Server-only: `IBlockExtension#getExpDrop` is called. -- Server-only: `Block#popExperience` is called with the result of the previous `IBlockExtension#getExpDrop` call, if that call returned a value greater than 0. +- 调用`Item#onBlockStartBreak`。如果它返回`true`(决定方块不应被破坏),流程移动到“结束”阶段。 +- 仅服务器:调用`IBlockExtension#canHarvestBlock`。这决定了方块是否可以被收获,即是否可以带着掉落物被破坏。 +- 调用`Block#onDestroyedByPlayer`。如果它返回`false`,流程移动到“结束”阶段。在`Block#onDestroyedByPlayer`调用中: + - 调用`Block#playerWillDestroy`。 + - 通过用`Blocks.AIR.defaultBlockState()`作为方块状态参数的`Level#setBlock`调用,从游戏世界中移除方块状态。 + - 在那个`Level#setBlock`调用中,调用`Block#onRemove`。 +- 调用`Block#destroy`。 +- 仅服务器:如果之前对`IBlockExtension#canHarvestBlock`的调用返回了`true`,则调用`Block#playerDestroy`。 +- 仅服务器:调用`IBlockExtension#getExpDrop`。 +- 仅服务器:如果之前`IBlockExtension#getExpDrop`调用的结果大于0,就调用`Block#popExperience`。 -### Ticking +### 游戏刻 -Ticking is a mechanism that updates (ticks) parts of the game every 1 / 20 seconds, or 50 milliseconds ("one tick"). Blocks provide different ticking methods that are called in different ways. +游戏刻是一种机制,它在每1/20秒或50毫秒(“一个游戏刻”)更新(游戏刻)游戏的某些部分。方块提供了不同的游戏刻方法,这些方法以不同的方式被调用。 -#### Server Ticking and Tick Scheduling +#### 服务器游戏刻和游戏刻调度 -`Block#tick` is called in two occasions: either through default [random ticking][randomtick] (see below), or through scheduled ticks. Scheduled ticks can be created through `Level#scheduleTick(BlockPos, Block, int)`, where the `int` denotes a delay. This is used in various places by vanilla, for example, the tilting mechanism of big dripleaves heavily relies on this system. Other prominent users are various redstone components. +`Block#tick`在两种情况下被调用:通过默认的[随机刻][randomtick](见下文),或通过调度的游戏刻。可以通过`Level#scheduleTick(BlockPos, Block, int)`创建调度的游戏刻,其中`int`表示延迟。这在vanilla的多个地方被使用,例如,大型滴叶的倾斜机制就严重依赖于这个系统。其他显著的使用者包括各种红石组件。 -#### Client Ticking +#### 客户端游戏刻 -`Block#animateTick` is called exclusively on the client, every frame. This is where client-only behavior, for example the torch particle spawning, happens. +`Block#animateTick`仅在客户端,每帧被调用。这是发生客户端仅行为的地方,例如火炬粒子的生成。 -#### Weather Ticking +#### 天气游戏刻 -Weather ticking is handled by `Block#handlePrecipitation` and runs independent of regular ticking. It is called only on the server, only when it is raining in some form, with a 1 in 16 chance. This is used for example by cauldrons that fill during rain or snowfall. +天气游戏刻由`Block#handlePrecipitation`处理,并独立于常规游戏刻运行。它仅在服务器上被调用,仅当以某种形式下雨时,有1/16的机会被调用。这被用于例如收集雨水或雪水的炼药锅。 -#### Random Ticking +#### 随机刻 -The random tick system runs independent of regular ticking. Random ticks must be enabled through the `BlockBehaviour.Properties` of the block by calling the `BlockBehaviour.Properties#randomTicks()` method. This enables the block to be part of the random ticking mechanic. +随机刻系统独立于常规游戏刻运行。随机刻必须通过调用`BlockBehaviour.Properties`的`BlockBehaviour.Properties#randomTicks()`方法来启用。这使得方块可以是随机刻机制的一部分。 -Random ticks occur every tick for a set amount of blocks in a chunk. That set amount is defined through the `randomTickSpeed` gamerule. With its default value of 3, every tick, 3 random blocks from the chunk are chosen. If these blocks have random ticking enabled, then their respective `Block#randomTick` methods are called. +每个刻为一个区块中设定数量的方块执行随机刻。这个设定数量是通过`randomTickSpeed`游戏规则定义的。其默认值为3,每个刻,从区块中随机选择3个方块。如果这些方块启用了随机刻,则分别调用它们的`Block#randomTick`方法。 -`Block#randomTick` by default calls `Block#tick`, which is what should normally be overridden. `Block#randomTick` should only be overridden if you specifically want different behavior for random ticking and regular (scheduled) ticking. +`Block#randomTick`默认调用`Block#tick`,这是通常应该被覆盖的。仅当你特别希望随机刻和常规(调度)游戏刻有不同行为时,才应覆盖`Block#randomTick`。 -Random ticking is used by a wide range of mechanics in Minecraft, such as plant growth, ice and snow melting, or copper oxidizing. +随机刻被Minecraft中的许多机制使用,例如植物生长、冰雪融化或铜氧化。 [above]: #one-block-to-rule-them-all [below]: #deferredregisterblocks-helpers From 533ccc4b9c358663c3e3bb3512e7bd695ad05483 Mon Sep 17 00:00:00 2001 From: DaZiDian <113085496+DaZiDian@users.noreply.github.com> Date: Thu, 25 Apr 2024 14:26:54 +0800 Subject: [PATCH 23/87] Update index.mdx --- src/pages/index.mdx | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/pages/index.mdx b/src/pages/index.mdx index a2740f0f3..dc7821bd2 100644 --- a/src/pages/index.mdx +++ b/src/pages/index.mdx @@ -1,37 +1,37 @@ import Card from "../theme/Card.tsx"; -# NeoForged Documentation +# NeoForged 文档 :::caution -Please note that this documentation may not be up to date considering the recent creation of NeoForged. +请注意,鉴于NeoForged最近的创建,本文档可能未能完全跟上最新的情况。 ::: -This is the official documentation for [NeoForged], the Minecraft modding API. +这是[Minecraft modding API NeoForged]的官方文档。 -This documentation is _only_ for NeoForged, **this is not a Java tutorial**. +此文档_仅限于_ NeoForged,**这不是Java教程**。 -If you would like to contribute to the docs, read [Contributing to the Docs][contributing]. +如果您想向文档贡献内容,请阅读[如何为文档贡献][贡献]。
[NeoForged]: https://neoforged.net -[contributing]: ./contributing +[贡献]: ./contributing From b9fea6653c792c77ff51be9baa7bf0fff0de9ebb Mon Sep 17 00:00:00 2001 From: DaZiDian <113085496+DaZiDian@users.noreply.github.com> Date: Thu, 25 Apr 2024 14:27:21 +0800 Subject: [PATCH 24/87] Update index.mdx --- src/pages/index.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/index.mdx b/src/pages/index.mdx index dc7821bd2..23d6994bf 100644 --- a/src/pages/index.mdx +++ b/src/pages/index.mdx @@ -8,7 +8,7 @@ import Card from "../theme/Card.tsx"; 这是[Minecraft modding API NeoForged]的官方文档。 -此文档_仅限于_ NeoForged,**这不是Java教程**。 +此文档 _仅限于_ NeoForged,**这不是Java教程**。 如果您想向文档贡献内容,请阅读[如何为文档贡献][贡献]。 From e50e9f372926a4fd5187e193574c9e5b16052df6 Mon Sep 17 00:00:00 2001 From: DaZiDian <113085496+DaZiDian@users.noreply.github.com> Date: Thu, 25 Apr 2024 14:38:27 +0800 Subject: [PATCH 25/87] Update contributing.mdx --- src/pages/contributing.mdx | 157 +++++++++++++++++++------------------ 1 file changed, 79 insertions(+), 78 deletions(-) diff --git a/src/pages/contributing.mdx b/src/pages/contributing.mdx index 6cee77db6..0cc345717 100644 --- a/src/pages/contributing.mdx +++ b/src/pages/contributing.mdx @@ -1,78 +1,78 @@ -# Contributing to the Documentation +# 为文档做贡献 -This is a non-exhaustive guideline for making contributions to the [NeoForged Documentation][docs] repository. Contributions can be made by forking and cloning the repository and then added via a pull request, or PR, on the [GitHub][docs]. +这是一个对[NeoForged文档][docs]仓库做出贡献的非详尽指南。贡献可以通过Fork和Clone仓库然后通过在[GitHub][docs]上添加一个拉取请求(PR)来实现。 -You can run the website locally using [npm]. It is recommended to use a Node Version Manager like [nvm] (Mac, Linux) or [nvs] (Windows) to setup and install npm and Node. From there, you can run the following commands: +你可以使用[npm]在本地运行网站。建议使用Node版本管理器,如[nvm](Mac,Linux)或[nvs](Windows),来设置并安装npm和Node。从那里,你可以运行以下命令: ```bash -nvm use # or nvs use on Windows +nvm use # 或在Windows上使用 nvs use npm install npm run start ``` -## Principles +## 原则 -This documentation is a guide to help a modder understand and implement a given concept from Minecraft or NeoForged. +这份文档旨在帮助模组开发者理解并实现来自Minecraft或NeoForged的概念。 -This documentation is **not** meant as a tutorial, allowing a modder to copy-paste the examples. If you are looking for a tutorial, there are plenty of videos and pages, which are not linked here, that you can use and follow along with. +这份文档**不**是一个教程,不允许模组开发者复制粘贴示例。如果你正在寻找一个教程,有许多视频和页面可以使用和跟随,这里没有链接。 -This documentation is also **not** meant as documentation for a class. Providing a description of an element is unavoidable when writing a guide; however, if you would like to document a class, you should contribute to [Parchment for Minecraft][parchment] or [NeoForge for NeoForged][neo]. +这份文档也**不**旨在作为一个类的文档。编写指南时提供元素的描述是不可避免的;但是,如果你想要记录一个类,你应该为[Parchment for Minecraft][parchment]或[NeoForge for NeoForged][neo]做贡献。 -Finally, this documentation is **not** meant to explain Java concepts. This documentation is intended for people who already have a solid basis in Java. If a Java concept needs to be explained to better understand the concept (such as JVM Descriptors for Access Transformers), a link should be provided to the original resource. Otherwise, if you are unfamiliar with Java, there are plenty of online resources to learn from: +最后,这份文档**不**旨在解释Java概念。这份文档是为那些已经具有扎实Java基础的人准备的。如果需要解释Java概念以更好地理解概念(如为访问转换器的JVM描述符),应提供指向原始资源的链接。否则,如果你不熟悉Java,有许多在线资源可以学习: * [JetBrains Academy][jetbrains] * [Codeacademy][codeacademy] -* [University of Helsinki][helsinki] +* [赫尔辛基大学][helsinki] * [Oracle][oracle] -* [Introduction to Programming using Java by David J. Eck][eck] +* [David J. Eck的《使用Java进行编程入门》][eck] -## Concepts +## 概念 -Each page should guide a modder on a particular concept. If the concept is too large in scope, the concept should be split into separate sub-concepts, each within its own page. For example, if you are writing a cookbook, there can be a page for each recipe, rather than a single page containing all the recipes. +每页应指导模组开发者了解特定概念。如果概念范围太大,应将概念分割成单独的子概念,每个子概念都有自己的页面。例如,如果你在写一个食谱,可以为每个食谱设置一个页面,而不是一个页面包含所有食谱。 -When describing a concept, you should first introduce what the concept is, where it is used in Minecraft, why it should be used, and how to use it. Each section within a concept should have a header. A section can also be broken into sub-sections if necessary. For example, each recipe within a cookbook can have a sub-section for ingredients and the recipe itself. +描述概念时,你应该首先介绍概念是什么,它在Minecraft中的用途,为什么应该使用它,以及如何使用它。概念中的每个部分都应该有一个标题。如果需要,一个部分也可以被分解成子部分。例如,每个食谱中的食谱可以有配料和食谱本身的子节。 -If you need to refer to other concepts, the relevant page should be linked along with a summary and/or some example to understand the application. +如果需要引用其他概念,应链接相关页面,并附上摘要和/或一些例子来理解应用。 -## Examples +## 示例 -Code examples should generally be pseudocode-like objects meant to enhance the understanding of a modder. For this documentation, pseudocode-like refers to code blocks written in the structure and syntax of the desired language with comments used as placeholders for specific logic that the modder may choose to implement themselves. The code blocks do not necessarily need to be compilable, but each line should have valid syntax and structure of the desired language. +代码示例通常应该是伪代码对象,旨在增强模组开发者的理解。对于这份文档来说,伪代码是指以所需语言的结构和语法编写的代码块,使用注释作为模组开发者可能选择自己实现的特定逻辑的占位符。代码块不一定需要编译,但每行都应该有所需语言的有效语法和结构。 -When implementing a method, it is usually specific to the desired goal a modder is trying to achieve. As a guide, this documentation aims to be somewhat agnostic to a modder's specific goal, instead covering the general use case. +实现一个方法通常特定于模组开发者试图实现的目标。作为一个指南,这份文档旨在在某种程度上对模组开发者的特定目标保持中立,而是覆盖普遍的用例。 -Let's say we are using a method called `#applyDiscount` to take some value off the current price. Not everyone will implement the same logic within the method. So, the pseudocode can leave a comment mentioning what to do instead: +例如,假设我们使用一个叫做`#applyDiscount`的方法从当前价格中减去一些值。不是每个人都会在方法内实现相同的逻辑。因此,伪代码可以留下一个评论说明代替做什么: ```java -// In some class +// 在某个类中 public float applyDiscount(float price) { float newPrice = price; - // Apply discount to newPrice + // 对newPrice应用折扣 // ... return newPrice; } ``` :::tip -If the pseudocode is not explanatory enough to understand the concept, then a full code example can be used instead. A full code example should supply dummy values and explain what they represent. +如果伪代码不足以解释概念,那么可以使用完整的代码示例代替。完整的代码示例应提供虚拟值,并解释它们代表什么。 ::: -## Minor and Patch Changes +## 小变更和补丁变更 -If a change occurs between a minor or patch versions of NeoForge, then relevant changes in the documentation should be split into separate sections or put into tabs. This maintains the accuracy of the information depending on the version the modder is currently developing for. +如果 NeoForge 在小版本或补丁版本之间发生变更,则文档中的相关变更应分割成单独的部分或放入标签中。这样可以根据模组开发者当前开发的版本保持信息的准确性。 -Tabs must be in an `.mdx` file, not an `.md` file, for proper IDE support. +标签必须在`.mdx`文件中,而不是`.md`文件中,以获得适当的 IDE 支持。 ````md import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; - - - + + + - + ```java public void latestMethod() { // ... @@ -80,10 +80,10 @@ public void latestMethod() { ``` - + - + ```java public void previousMethod() { // ... @@ -142,135 +142,135 @@ public void firstMethod() { -## Style Guide +## 编撰文档的风格指南 -This documentation uses [Docusaurus][docusaurus], which internally uses [MDX][mdx], to generate the pages. You can find more detailed information about available features on their pages. This style guide will be more focused towards common features and formatting we use in the Markdown files. +此文档采用 [Docusaurus][docusaurus] 为基础,此框架内部使用 [MDX][mdx] 生成页面。您可以在他们的页面上找到更多可用功能的详细信息。此风格指南将更专注于我们在 Markdown 文件中使用的常见功能和格式。 -### Front Matter +### 前言 -Front matter defines metadata fields which can affect how the page is rendered. This is denoted using `---`, similar to a code block. The only front matter that should usually be defined is `sidebar_position`, which determines where the page should be rendered on the sidebar. +前言定义了可以影响页面渲染方式的元数据字段。这使用 `---` 表示,类似于代码块。通常应该定义的唯一前言是 `sidebar_position`,它决定页面在侧边栏中的渲染位置。 -There are other metadata fields like `title` and `description`, but those are typically parsed from the page itself. +还有其他元数据字段如 `title` 和 `description`,但这些通常从页面本身解析。 ```md - + --- sidebar_position: 2 --- ``` -#### Categories +#### 类别 -Categories are folders within the documentation. They inherit titles and positional data from `index.md`. If an `index.md` is not within a subfolder, a `_category_.json` file should be created, specifying the `label` representing the name of the section, and `position` representing where on the sidebar it should go. +类别是文档内的文件夹。他们从 `index.md` 继承标题和位置数据。如果子文件夹内没有 `index.md`,应该创建一个 `_category_.json` 文件,指定代表部分名称的 `label` 和代表它应该在侧边栏上去哪里的 `position`。 ```json5 { - // Name of the category to display - "label": "Example Title", + // 要显示的类别名称 + "label": "示例标题", - // This will be rendered as the third element on the sidebar at the current level. + // 这将在当前级别的侧边栏中作为第三个元素渲染。 "position": 3 } ``` -### Titles +### 标题 -Titles are defined using up to six hashtags (`#`) to define each section. Titles should capitalize everything but unimportant words. +使用多达六个井号(`#`)定义每个部分的标题。标题应该将所有东西都首字母大写,但不重要的单词除外。 ```md - -# Guide For Contributing to This Documentation + +# 编撰此文档的指南 -### Building and Testing Your Mod +### 构建和测试你的Mod ``` -### Diction +### 措辞 -Spelling, grammar, and syntax should follow those in American English. Avoid using contractions in sentences; use two separate words ('is not' instead of 'isn't'). Additionally, avoid using pronouns (e.g. I, me, you) when possible, unless you need to directly refer to the reader. Demonstratives (e.g. this, that, its) should be used sparingly to avoid confusing the reader. Prefer using the actual object or noun being referred to. +拼写、语法和句法应遵循美式英语。避免使用缩写词;使用两个单词('is not' 而不是 'isn't')。另外,尽可能避免使用代词(例如 I, me, you),除非您需要直接提及读者。指示代词(例如 this, that, its)应谨慎使用,以避免使读者感到困惑。更喜欢使用所指的实际对象或名词。 -### Paragraphs +### 段落 -Paragraphs should be a continuous block, separated by a newline. Paragraphs should **not** have each sentence be on a new line. +段落应该是一个连续的块,由一个换行符分隔。段落**不**应该每个句子都在新行上。 ```md -This is my first paragraph. See how the next sentence is on the same line? You can use word wrapping in your editor to stop the line from going off the screen. +这是我的第一段。看到下一句是如何在同一行上的吗?您可以在编辑器中使用单词换行来阻止行条从屏幕上滚动。 -This is my next paragraph. It is separated by a new line. +这是我的下一段。它通过一个新行分隔。 ``` -### Indentation +### 缩进 -When indenting lines, use four spaces instead of tabs. Most markdown features require four spaces to recognize indentation, so it allows consistency across the document. +缩进行时,使用四个空格而不是制表符。大多数 markdown 功能要求四个空格来识别缩进,因此它允许整个文档保持一致性。 ```md -* Hello World - * Four Spaces In +* 你好,世界 + * 四个空格缩进 ``` -### Importance +### 重要性 -Emphasizing words should be done using **bold** or _italics_. Please use two asterisks (`**`) for bold and an underscore (`_`) for italics to make the separation in Markdown more distinct. +强调单词应使用 **粗体** 或 _斜体_。请使用两个星号(`**`)进行粗体和下划线(`_`)进行斜体,以使 Markdown 中的区分更加明显。 ```md -This is a **bolded** word. +这是一个**加粗**的单词。 -This is an _italicized_ word. +这是一个_倾斜_的单词。 ``` -### Code References +### 代码引用 -When referencing elements outside of code blocks, they should be surrounded with backticks (`` ` ``). Classes should use their simple name. Methods and fields should specify the class name followed by a `#`. If the class name is implied, the method or field can simply be prefixed with `#`. Inner classes should specify the name of the outer class followed by a `.`. +当引用代码块外的元素时,它们应该用反引号(`` ` ``)包围。类应使用它们的简单名称。方法和字段应在 `#` 之前指定类名。如果类名是隐含的,则方法或字段可以简单地以 `#` 作为前缀。内部类应在 `.` 之后指定外部类的名称。 ```md - + `MyClass` `MyClass.InnerClass` - + `MyClass#foo` `MyClass.InnerClass#bar` `#SOME_CONSTANT` ``` -Code blocks should specify the language after the triple backtick (`` ``` ``). When writing a JSON block, the JSON5 (`json5`) syntax highlighter should be used to allow comments. +代码块应在三连反引号(`` ``` ``)之后指定语言。编写 JSON 块时,应使用 JSON5(`json5`)语法高亮显示器,以允许注释。 ````md - + ```java public void run() { //... } ``` - + ```json5 { - // Comments are allowed here - "text": "Hiya" + // 这里允许注释 + "text": "嗨" } ``` ```` -### Links +### 链接 -All links should use brackets (`[]`) to refer to a link specified on the bottom of the markdown page. The second pair of brackets can be omitted if the name between the first pair of brackets is used. +所有链接应使用括号 (`[]`) 引用页面底部指定的链接。如果第一对括号中的名称被使用,则可以省略第二对括号。 ```md - -There are [two] different types of [link references][linkref]. + +这里有两种不同类型的[链接引用][linkref]。 - + [two]: https://linkrefwithoutref.donotclick [linkref]: https://linkref.donotclick ``` -### Admonitions +### 警示 -Admonitions can be specified on the page using three colons (`:::`) and by specifying its type. Admonition formatting can be found on the [Docusaurus wiki][admonition]. +可以使用三个冒号(`:::`)和指定其类型在页面上指定警示。警示的格式可以在 [Docusaurus wiki][admonition] 上找到。 ```md :::note -I'm within an admonition! +我在一个警示框内部! ::: ``` @@ -292,4 +292,5 @@ I'm within an admonition! [docusaurus]: https://docusaurus.io/docs/markdown-features [mdx]: https://mdxjs.com/guides/ + [admonition]: https://docusaurus.io/docs/markdown-features/admonitions From 93c51258768d7d29a71efac33cdd54c71bfe0023 Mon Sep 17 00:00:00 2001 From: DaZiDian <113085496+DaZiDian@users.noreply.github.com> Date: Thu, 25 Apr 2024 14:53:17 +0800 Subject: [PATCH 26/87] Update states.md --- docs/blocks/states.md | 124 +++++++++++++++++++++--------------------- 1 file changed, 61 insertions(+), 63 deletions(-) diff --git a/docs/blocks/states.md b/docs/blocks/states.md index c0829286e..9551668f3 100644 --- a/docs/blocks/states.md +++ b/docs/blocks/states.md @@ -1,14 +1,14 @@ Blockstates =========== -Often, you will find yourself in a situation where you want different states of a block. For example, a wheat crop has eight growth stages, and making a separate block for each stage feels wrong. Or you have a slab or slab-like block - one bottom state, one top state, and one state that has both. +经常会遇到想要拥有不同状态的方块的情况。例如,小麦作物有八个生长阶段,为每个阶段制作一个单独的方块感觉并不合适。或者你有一个台阶或类台阶方块 - 一个底部状态、一个顶部状态,以及一个状态同时具有两者。 -This is where blockstates come into play. Blockstates are an easy way to represent the different states a block can have, like a growth stage or a slab placement type. +这就是Blockstates发挥作用的地方。Blockstates是一种表示方块可以具有的不同状态的简便方法,如生长阶段或台阶放置类型。 -Blockstate Properties +Blockstate 属性 --------------------- -Blockstates use a system of properties. A block can have multiple properties of multiple types. For example, an end portal frame has two properties: whether it has an eye (`eye`, 2 options) and which direction it is placed in (`facing`, 4 options). So in total, the end portal frame has 8 (2 * 4) different blockstates: +Blockstates使用一套属性系统。一个方块可以具有多种类型的多个属性。例如,一个末地传送门框架有两个属性:它是否有一个眼睛(`eye`,2个选项)和放置的方向(`facing`,4个选项)。所以总的来说,末地传送门框架有8(2 * 4)个不同的Blockstates: ``` minecraft:end_portal_frame[facing=north,eye=false] @@ -21,68 +21,69 @@ minecraft:end_portal_frame[facing=south,eye=true] minecraft:end_portal_frame[facing=west,eye=true] ``` -The notation `blockid[property1=value1,property2=value,...]` is the standardized way of representing a blockstate in text form, and is used in some locations in vanilla, for example in commands. +表示法 `blockid[property1=value1,property2=value,...]` 是表示文本形式Blockstate的标准方式,并且在游戏的某些地方使用,例如在命令中。 -If your block does not have any blockstate properties defined, it still has exactly one blockstate - that is the one without any properties, since there are no properties to specify. This can be denoted as `minecraft:oak_planks[]` or simply `minecraft:oak_planks`. +如果您的方块没有定义任何Blockstate属性,它仍然有一个Blockstate - 那就是没有任何属性的那个,因为没有属性需要指定。这可以表示为 `minecraft:oak_planks[]` 或者简单的 `minecraft:oak_planks`。 -As with blocks, every `BlockState` exists exactly once in memory. This means that `==` can and should be used to compare `BlockState`s. `BlockState` is also a final class, meaning it cannot be extended. **Any functionality goes in the corresponding [Block][block] class!** +与方块一样,每个 `BlockState` 在内存中仅存在一个。这意味着可以并且应该使用 `==` 来比较 `BlockState`。`BlockState` 也是一个终极类,意味着它不能被扩展。**任何功能都在相应的[Block][block]类中!** -When to Use Blockstates +何时使用Blockstates ----------------------- -### Blockstates vs. Separate Blocks +### Blockstates vs. 独立方块 -A good rule of thumb is: **if it has a different name, it should be a separate block**. An example is making chair blocks: the direction of the chair should be a property, while the different types of wood should be separated into different blocks. So you'd have one chair block for each wood type, and each chair block has four blockstates (one for each direction). +一个好的经验法则是:**如果它有一个不同的名称,它应该是一个独立的方块**。一个例子是制作椅子方块:椅子的方向应该是一个属性,而不同类型的木头应该分成不同的方块。所以你会有一个椅子方块适用于每种木头类型,每个椅子方块有四个Blockstates(每个方向一个)。 -### Blockstates vs. [Block Entities][blockentity] +### Blockstates vs. [方块实体][blockentity] -Here, the rule of thumb is: **if you have a finite amount of states, use a blockstate, if you have an infinite or near-infinite amount of states, use a block entity.** Block entities can store arbitrary amounts of data, but are slower than blockstates. +这里的经验法则是:**如果你有一个有限的状态量,使用blockstate,如果你有一个无限或几乎无限的状态量,使用方块实体。** 方块实体可以存储任意量的数据,但比blockstates慢。 -Blockstates and block entities can be used in conjunction with one another. For example, the chest uses blockstate properties for things like the direction, whether it is waterlogged or not, or becoming a double chest, while storing the inventory, whether it is currently open or not, or interacting with hoppers is handled by a block entity. +Blockstates和方块实体可以联合使用。例如,箱子使用Blockstate属性来表示诸如方向、是否被水淹没或成为双箱子等事物,同时通过方块实体存储库存、是否当前打开或与漏斗的互动等。 -There is no definitive answer to the question "How many states are too much for a blockstate?", but we recommend that if you need more than 8-9 bits of data (i.e. more than a few hundred states), you should use a block entity instead. +没有一个明确的答案来回答“对于Blockstate来说,多少状态太多了?”的问题,但我们建议,如果您需要超过8-9比特的数据(即超过几百种状态),您应该使用方块实体代替。 -Implementing Blockstates +实现Blockstates ------------------------ -To implement a blockstate property, in your block class, create or reference a `public static final Property` constant. While you are free to make your own `Property` implementations, the vanilla code provides several convenience implementations that should cover most use cases: +要实现Blockstate属性,在您的方块类中创建或引用一个 `public static final Property` 常量。虽然您可以自由制作自己的 `Property` 实现,但游戏代码提供了几种便利实现,应该涵盖大多数用例: * `IntegerProperty` - * Implements `Property`. Defines a property that holds an integer value. Note that negative values are not supported. - * Created by calling `IntegerProperty#create(String propertyName, int minimum, int maximum)`. + * 实现 `Property`。定义一个持有整数值的属性。注意不支持负值。 + * 通过调用 `IntegerProperty#create(String propertyName, int minimum, int maximum)` 创建。 * `BooleanProperty` - * Implements `Property`. Defines a property that holds a `true` or `false` value. - * Created by calling `BooleanProperty#create(String propertyName)`. + * 实现 `Property`。定义一个持有 `true` 或 `false` 值的属性。 + * 通过调用 `BooleanProperty#create(String propertyName)` 创建。 * `EnumProperty>` - * Implements `Property`. Defines a property that can take on the values of an Enum class. - * Created by calling `EnumProperty#create(String propertyName, Class enumClass)`. - * It is also possible to use only a subset of the Enum values (e.g. 4 out of 16 `DyeColor`s), see the overloads of `EnumProperty#create`. + * 实现 `Property`。定义一个可以取枚举类值的属性。 + * 通过调用 `EnumProperty#create(String propertyName, Class enumClass)` 创建。 + * 还可以使用枚举值的子集(例如,16个`DyeColor`s中的4个),见 `EnumProperty#create` 的重载方法。 * `DirectionProperty` - * Extends `EnumProperty`. Defines a property that can take on a `Direction`. - * Created by calling `DirectionProperty#create(String propertyName)`. - * Several convenience predicates are provided. For example, to get a property that represents the cardinal directions, call `DirectionProperty.create("", Direction.Plane.HORIZONTAL)`; to get the X directions, `DirectionProperty.create("", Direction.Axis.X)`. + * `DirectionProperty` + * 扩展自 `EnumProperty`。定义了一个可以承载 `Direction`(方向)的属性。 + * 通过调用 `DirectionProperty#create(String propertyName)` 来创建。 + * 提供了几个便利的谓词方法。例如,要获取代表基本方向的属性,调用 `DirectionProperty.create("", Direction.Plane.HORIZONTAL)`;要获取X轴方向,调用 `DirectionProperty.create("", Direction.Axis.X)`。 -The class `BlockStateProperties` contains shared vanilla properties which should be used or referenced whenever possible, in place of creating your own properties. +类 `BlockStateProperties` 包含了共享的原版属性,这些属性应尽可能使用或引用,而不是创建自己的属性。 -Once you have your property constant, override `Block#createBlockStateDefinition(StateDefinition$Builder)` in your block class. In that method, call `StateDefinition.Builder#add(YOUR_PROPERTY);`. `StateDefinition.Builder#add` has a vararg parameter, so if you have multiple properties, you can add them all in one go. +一旦你有了你的属性常量,在你的方块类中重写 `Block#createBlockStateDefinition(StateDefinition$Builder)`。在该方法中,调用 `StateDefinition.Builder#add(YOUR_PROPERTY);`。`StateDefinition.Builder#add` 有一个变长参数,所以如果你有多个属性,你可以一次性添加它们所有。 -Every block will also have a default state. If nothing else is specified, the default state uses the default value of every property. You can change the default state by calling the `Block#registerDefaultState(BlockState)` method from your constructor. +每个方块还有一个默认状态。如果没有指定其他内容,缺省状态使用每个属性的默认值。你可以通过从构造函数中调用 `Block#registerDefaultState(BlockState)` 方法来更改默认状态。 -If you wish to change which `BlockState` is used when placing your block, override `Block#getStateForPlacement(BlockPlaceContext)`. This can be used to, for example, set the direction of your block depending on where the player is standing or looking when they place it. +如果你希望改变放置方块时使用的 `BlockState`,请重写 `Block#getStateForPlacement(BlockPlaceContext)`。这可以用来设置方块的方向,比如基于玩家放置时的站立位置或方向。 -To further illustrate this, this is what the relevant bits of the `EndPortalFrameBlock` class look like: +为进一步说明,这是 `EndPortalFrameBlock` 类相关部分的样子: ```java public class EndPortalFrameBlock extends Block { - // Note: It is possible to directly use the values in BlockStateProperties instead of referencing them here again. - // However, for the sake of simplicity and readability, it is recommended to add constants like this. + // 注意:直接使用 BlockStateProperties 中的值而不是在这里再次引用它们是可能的。 + // 然而,为了简单和可读性考虑,推荐像这样添加常量。 public static final DirectionProperty FACING = BlockStateProperties.FACING; public static final BooleanProperty EYE = BlockStateProperties.EYE; public EndPortalFrameBlock(BlockBehaviour.Properties pProperties) { super(pProperties); - // stateDefinition.any() returns a random BlockState from an internal set, - // we don't care because we're setting all values ourselves anyway + // stateDefinition.any() 返回一个内部集合中的随机 BlockState, + // 我们不在意,因为我们 anyway 要自己设置所有值 registerDefaultState(stateDefinition.any() .setValue(FACING, Direction.NORTH) .setValue(EYE, false) @@ -91,63 +92,60 @@ public class EndPortalFrameBlock extends Block { @Override protected void createBlockStateDefinition(StateDefinition.Builder pBuilder) { - // this is where the properties are actually added to the state + // 这里是属性实际被添加到状态的地方 pBuilder.add(FACING, EYE); } @Override @Nullable public BlockState getStateForPlacement(BlockPlaceContext pContext) { - // code that determines which state will be used when - // placing down this block, depending on the BlockPlaceContext + // 根据 BlockPlaceContext 确定放置此方块时将使用的状态的 + // 代码 } } ``` -Using Blockstates ------------------ +使用 Blockstates +---------------- -To go from `Block` to `BlockState`, call `Block#defaultBlockState()`. The default blockstate can be changed through `Block#registerDefaultState`, as described above. +要从 `Block` 转换到 `BlockState`,调用 `Block#defaultBlockState()`。可以通过 `Block#registerDefaultState` 更改默认 blockstate,如上所述。 -You can get the value of a property by calling `BlockState#getValue(Property)`, passing it the property you want to get the value of. Reusing our end portal frame example, this would look something like this: +你可以通过调用 `BlockState#getValue(Property)` 来获取一个属性的值,传递你想获取值的属性。复用我们末地传送门框架的例子,这看起来像这样: ```java -// EndPortalFrameBlock.FACING is a DirectionProperty and thus can be used to obtain a Direction from the BlockState +// EndPortalFrameBlock.FACING 是一个 DirectionProperty,因此可以用来从 BlockState 中获取一个 Direction Direction direction = endPortalFrameBlockState.getValue(EndPortalFrameBlock.FACING); ``` -If you want to get a `BlockState` with a different set of values, simply call `BlockState#setValue(Property, T)` on an existing block state with the property and its value. With our lever, this goes something like this: +如果你想获得一个具有不同值集的 `BlockState`,只需在现有的 block state 上调用 `BlockState#setValue(Property, T)` 并传入属性及其值。对于我们的杠杆来说,像这样: ```java endPortalFrameBlockState = endPortalFrameBlockState.setValue(EndPortalFrameBlock.FACING, Direction.SOUTH); ``` :::note -`BlockState`s are immutable. This means that when you call `#setValue(Property, T)`, you are not actually modifying the blockstate. Instead, a lookup is performed internally, and you are given the blockstate object you requested, which is the one and only object that exists with these exact property values. This also means that just calling `state#setValue` without saving it into a variable (for example back into `state`) does nothing. +`BlockState` 是不可变的。这意味着当你调用 `#setValue(Property, T)` 时,你实际上不是在修改 blockstate。相反,内部进行了查找,你得到了你请求的 blockstate 对象,那是唯一存在的、具有这些确切属性值的对象。这也意味着,仅仅调用 `state#setValue` 而没有将其保存到一个变量中(例如回到 `state` 中)是无效的。 ::: -To get a `BlockState` from the level, use `Level#getBlockState(BlockPos)`. +要从场景中获取一个 `BlockState`,使用 `Level#getBlockState(BlockPos)`。 ### `Level#setBlock` -To set a `BlockState` in the level, use `Level#setBlock(BlockPos, BlockState, int)`. +要在场景中设置一个 `BlockState`,使用 `Level#setBlock(BlockPos, BlockState, int)`。 -The `int` parameter deserves some extra explanation, as its meaning is not immediately obvious. It denotes what is known as update flags. +`int` 参数需要额外的解释,因为它的含义不立即明显。它表示更新标志。 -To help setting the update flags correctly, there are a number of `int` constants in `Block`, prefixed with `UPDATE_`. These constants can be bitwise-ORed together (for example `Block.UPDATE_NEIGHBORS | Block.UPDATE_CLIENTS`) if you wish to combine them. +为了正确设置更新标志,`Block` 中有一些以 `UPDATE_` 开头的 `int` 常量。这些常量可以被按位或在一起(例如 `Block.UPDATE_NEIGHBORS | Block.UPDATE_CLIENTS`)如果你希望组合它们。 -- `Block.UPDATE_NEIGHBORS` sends an update to the neighboring blocks. More specifically, it calls `Block#neighborChanged`, which calls a number of methods, most of which are redstone-related in some way. -- `Block.UPDATE_CLIENTS` syncs the block update to the client. -- `Block.UPDATE_INVISIBLE` explicitly does not update on the client. This also overrules `Block.UPDATE_CLIENTS`, causing the update to not be synced. The block is always updated on the server. -- `Block.UPDATE_IMMEDIATE` forces a re-render on the client's main thread. -- `Block.UPDATE_KNOWN_SHAPE` stops neighbor update recursion. -- `Block.UPDATE_SUPPRESS_DROPS` disables block drops for the old block at that position. -- `Block.UPDATE_MOVE_BY_PISTON` is only used by piston code to signal that the block was moved by a piston. This is mainly responsible for delaying light engine updates. -- `Block.UPDATE_ALL` is an alias for `Block.UPDATE_NEIGHBORS | Block.UPDATE_CLIENTS`. -- `Block.UPDATE_ALL_IMMEDIATE` is an alias for `Block.UPDATE_NEIGHBORS | Block.UPDATE_CLIENTS | Block.UPDATE_IMMEDIATE`. -- `Block.NONE` is an alias for `Block.UPDATE_INVISIBLE`. +- `Block.UPDATE_NEIGHBORS` 向相邻方块发送更新。更具体地说,它调用了 `Block#neighborChanged`,它调用了许多方法,其中大部分以某种方式与红石相关。 +- `Block.UPDATE_CLIENTS` 将方块更新同步到客户端。 +- `Block.UPDATE_INVISIBLE` 显式不在客户端更新。这也覆盖了 `Block.UPDATE_CLIENTS`,导致更新不被同步。方块始终在服务器上更新。 +- `Block.UPDATE_IMMEDIATE` 强制在客户端的主线程上重新渲染。 +- `Block.UPDATE_KNOWN_SHAPE` 停止邻居更新递归。 +- `Block.UPDATE_SUPPRESS_DROPS` 禁止旧方块在该位置的掉落物。 +- `Block.UPDATE_MOVE_BY_PISTON` 仅被活塞代码用于表示方块被活塞移动。这主要是为了延迟光照引擎的更新。 +- `Block.UPDATE_ALL` 是 `Block.UPDATE_NEIGHBORS | Block.UPDATE_CLIENTS` 的别名。 +- `Block.UPDATE_ALL_IMMEDIATE` 是 `Block.UPDATE_NEIGHBORS | Block.UPDATE_CLIENTS | Block.UPDATE_IMMEDIATE` 的别名。 +- `Block.NONE` 是 `Block.UPDATE_INVISIBLE` 的别名。 -There is also a convenience method `Level#setBlockAndUpdate(BlockPos pos, BlockState state)` that calls `setBlock(pos, state, Block.UPDATE_ALL)` internally. - -[block]: index.md -[blockentity]: ../blockentities/index.md +还有一个便利方法 `Level#setBlockAndUpdate(BlockPos pos, BlockState state)`,它在内部调用 `setBlock(pos, state, Block.UPDATE_ALL)`。 From 6807046349432e588b430dbb2a427a61b827825b Mon Sep 17 00:00:00 2001 From: DaZiDian <113085496+DaZiDian@users.noreply.github.com> Date: Thu, 25 Apr 2024 15:15:12 +0800 Subject: [PATCH 27/87] Update events.md --- docs/concepts/events.md | 106 ++++++++++++---------------------------- 1 file changed, 30 insertions(+), 76 deletions(-) diff --git a/docs/concepts/events.md b/docs/concepts/events.md index 64b0b6f21..83debfc15 100644 --- a/docs/concepts/events.md +++ b/docs/concepts/events.md @@ -1,16 +1,16 @@ -# Events +# 事件系统 -One of NeoForge's main features is the event system. Events are fired for various things that happen in the game. For example, there are events for when the player right clicks, when a player or another entity jumps, when blocks are rendered, when the game is loaded, etc. A modder can subscribe event handlers to each of these events, and then perform their desired behavior inside these event handlers. +NeoForge 的核心特性之一是其事件系统。在游戏中,各种事件根据游戏内的不同动作而触发。比如玩家右键点击、玩家或其他实体跳跃、方块渲染、游戏加载时等,都会触发相应的事件。模组开发者可以为这些事件编写处理函数,并在函数中实现他们期望的行为。 -Events are fired on their respective event bus. The most important bus is `NeoForge.EVENT_BUS`. Besides that, during startup, a mod bus is spawned for each loaded mod and passed into the mod's constructor. Many mod bus events are fired in parallel (as opposed to main bus events that always run on the same thread), dramatically increasing startup speed. See [below][modbus] for more information. +这些事件会在相应的事件总线上触发。其中最重要的是 `NeoForge.EVENT_BUS`。此外,在游戏启动期间,系统会为每个加载的模组生成一个独立的模组总线,并传递给模组的构造函数。许多模组总线事件是并行触发的,这与总在同一个线程上运行的主总线事件不同,这种设计显著提高了启动速度。更多细节,请参考[下文][modbus]。 -## Registering an Event Handler +## 注册事件处理函数 -There are multiple ways to register event handlers. Common for all of those ways is that every event handler is a method with a single event parameter and no result (i.e. return type `void`). +注册事件处理函数有多种方式。所有这些方式的共同点是,每个事件处理函数都是一个只接收单一事件参数并且不返回结果(即返回类型为 `void`)的方法。 ### `IEventBus#addListener` -The simplest way to register method handlers is by registering their method reference, like so: +最简单的注册方法是直接引用方法,如下所示: ```java @Mod("yourmodid") @@ -19,10 +19,10 @@ public class YourMod { NeoForge.EVENT_BUS.addListener(YourMod::onLivingJump); } - // Heals an entity by half a heart every time they jump. + // 每次实体跳跃时为其恢复半颗心的生命值。 private static void onLivingJump(LivingJumpEvent event) { Entity entity = event.getEntity(); - // Only heal on the server side + // 仅在服务器端进行治疗 if (!entity.level().isClientSide()) { entity.heal(1); } @@ -32,7 +32,7 @@ public class YourMod { ### `@SubscribeEvent` -Alternatively, event handlers can be annotation-driven by creating an event handler method and annotating it with `@SubscribeEvent`. Then, you can pass an instance of the encompassing class to the event bus, registering all `@SubscribeEvent`-annotated event handlers of that instance: +另一种方式是使用注解来驱动事件处理,为处理函数添加 `@SubscribeEvent` 注解。然后将包含该处理函数的类的实例传递给事件总线,从而注册该实例中所有带有 `@SubscribeEvent` 注解的事件处理函数: ```java public class EventHandler { @@ -53,7 +53,7 @@ public class YourMod { } ``` -You can also do it statically. Simply make all event handlers static, and instead of a class instance, pass in the class itself: +你还可以通过将所有事件处理函数设置为静态,并直接传递类本身,而不是类的实例来实现: ```java public class EventHandler { @@ -76,9 +76,9 @@ public class YourMod { ### `@Mod.EventBusSubscriber` -We can go one step further and also annotate the event handler class with `@Mod.EventBusSubscriber`. This annotation is discovered automatically by NeoForge, allowing you to remove all event-related code from the mod constructor. In essence, it is equivalent to calling `NeoForge.EVENT_BUS.register(EventHandler.class)` at the end of the mod constructor. This means that all handlers must be static, too. +我们可以进一步优化,将事件处理类标注为 `@Mod.EventBusSubscriber`。这个注解会被 NeoForge 自动识别,允许你从模组构造函数中移除所有与事件相关的代码。实际上,这等同于在模组构造结束时调用 `NeoForge.EVENT_BUS.register(EventHandler.class)`。这也意味着所有的处理函数必须设置为静态。 -While not required, it is highly recommended to specify the `modid` parameter in the annotation, in order to make debugging easier (especially when it comes to mod conflicts). +虽然不是必须的,但强烈建议在注解中指定 `modid` 参数,以便在处理模组冲突时能够更容易进行调试。 ```java @Mod.EventBusSubscriber(modid = "yourmodid") @@ -93,87 +93,41 @@ public class EventHandler { } ``` -## Event Options +### 生命周期事件 -### Fields and Methods +大多数模组总 -Fields and methods are probably the most obvious part of an event. Most events contain context for the event handler to use, such as an entity causing the event or a level the event occurs in. +线事件被称为生命周期事件。生命周期事件在每个模组的生命周期中仅在启动时运行一次。很多这类事件是并行触发的,如果你想要在主线程上运行这些事件的代码,可以使用 `#enqueueWork(Runnable runnable)` 方法将它们加入队列。 -### Hierarchy +生命周期事件通常按以下顺序进行: -In order to use the advantages of inheritance, some events do not directly extend `Event`, but one of its subclasses, for example `BlockEvent` (which contains block context for block-related events) or `EntityEvent` (which similarly contains entity context) and its subclasses `LivingEvent` (for `LivingEntity`-specific context) and `PlayerEvent` (for `Player`-specific context). These context-providing super events are `abstract` and cannot be listened to. - -:::danger -If you listen to an `abstract` event, your game will crash, as this is never what you want. You always want to listen to one of the subevents instead. -::: - -### Cancellable Events - -Some events implement the `ICancellableEvent` interface. These events can be cancelled using `#setCanceled(boolean canceled)`, and the cancellation status can be checked using `#isCanceled()`. If an event is cancelled, other event handlers for this event will not run, and some kind of behavior that is associated with "cancelling" is enabled. For example, cancelling `LivingJumpEvent` will prevent the jump. - -Event handlers can opt to explicitly receive cancelled events. This is done by setting the `receiveCanceled` parameter in `IEventBus#addListener` (or `@SubscribeEvent`, depending on your way of attaching the event handlers) to true. - -### Results - -Some events have a `Result`. A `Result` can be one of three things: `DENY` which stops the event, `ALLOW` which force-runs the event, and `DEFAULT` which uses the Vanilla behavior. The result of an event can be set by calling `Event#setResult`. Not all events have results; an event with a result will be annotated with `@HasResult`. - -:::caution -Results are deprecated and will be replaced by more specific per-event results soon. -::: - -### Priority - -Event handlers can optionally get assigned a priority. The `EventPriority` enum contains five values: `HIGHEST`, `HIGH`, `NORMAL` (default), `LOW` and `LOWEST`. Event handlers are executed from highest to lowest priority. If they have the same priority, they fire in registration order on the main bus, which is roughly related to mod load order, and in exact mod load order on the mod bus (see below). - -Priorities can be defined by setting the `priority` parameter in `IEventBus#addListener` or `@SubscribeEvent`, depending on how you attach event handlers. Note that priorities are ignored for events that are fired in parallel. - -### Sided Events - -Some events are only fired on one [side][side]. Common examples include the various render events, which are only fired on the client. Since client-only events generally need to access other client-only parts of the Minecraft codebase, they need to be registered accordingly. - -Event handlers that use `IEventBus#addListener()` should use a `FMLEnvironment.dist` check and a separate client-only class, as outlined in the article on sides. - -Event handlers that use `@Mod.EventBusSubscriber` can specify the side as the `value` parameter of the annotation, for example `@Mod.EventBusSubscriber(value = Dist.CLIENT, modid = "yourmodid")`. - -## Event Buses - -While most events are posted on the `NeoForge.EVENT_BUS`, some events are posted on the mod event bus instead. These are generally called mod bus events. Mod bus events can be distinguished from regular events by their superinterface `IModBusEvent`. - -The mod event bus is passed to you as a parameter in the mod constructor, and you can then subscribe mod bus events to it. If you use `@Mod.EventBusSubscriber`, you can also set the bus as an annotation parameter, like so: `@Mod.EventBusSubscriber(bus = Bus.MOD, modid = "yourmodid")`. The default bus is `Bus.FORGE`. - -### The Mod Lifecycle - -Most mod bus events are what is known as lifecycle events. Lifecycle events run once in every mod's lifecycle during startup. Many of them are fired in parallel; if you want to run code from one of these events on the main thread, enqueue them using `#enqueueWork(Runnable runnable)`. - -The lifecycle generally follows the following order: - -- The mod constructor is called. Register your event handlers here, or in the next step. -- All `@Mod.EventBusSubscriber`s are called. -- `FMLConstructModEvent` is fired. -- The registry events are fired, these include [`NewRegistryEvent`][newregistry], [`DataPackRegistryEvent.NewRegistry`][newdatapackregistry] and, for each registry, [`RegisterEvent`][registerevent]. -- `FMLCommonSetupEvent` is fired. This is where various miscellaneous setup happens. -- The [sided][side] setup is fired: `FMLClientSetupEvent` if on a physical client, and `FMLDedicatedServerSetupEvent` if on a physical server. -- `InterModComms` are handled (see below). -- `FMLLoadCompleteEvent` is fired. +- 调用模组构造函数。在这里或下一步注册你的事件处理函数。 +- 所有的 `@Mod.EventBusSubscriber` 被调用。 +- 触发 `FMLConstructModEvent` 事件。 +- 触发注册事件,包括 [`NewRegistryEvent`][newregistry]、[`DataPackRegistryEvent.NewRegistry`][newdatapackregistry] 以及每个注册表的 [`RegisterEvent`][registerevent]。 +- 触发 `FMLCommonSetupEvent` 事件。这是进行各种杂项设置的阶段。 +- 根据服务器类型触发侧边设置事件:如果在客户端,则为 `FMLClientSetupEvent`;如果在服务器,则为 `FMLDedicatedServerSetupEvent`。 +- 处理 `InterModComms`(详情见下文)。 +- 触发 `FMLLoadCompleteEvent` 事件。 #### `InterModComms` -`InterModComms` is a system that allows modders to send messages to other mods for compatibility features. The class holds the messages for mods, all methods are thread-safe to call. The system is mainly driven by two events: `InterModEnqueueEvent` and `InterModProcessEvent`. +`InterModComms` 是一个系统,允许模组开发者向其他模组发送消息以实现功能兼容。这个系统保存了模组的消息,所有方法都是线程安全的。主要通过两个事件推动:`InterModEnqueueEvent` 和 `InterModProcessEvent`。 -During `InterModEnqueueEvent`, you can use `InterModComms#sendTo` to send messages to other mods. These methods accept the id of the mod to send the message to, the key associated with the message data (to distinguish between different messages), and a `Supplier` holding the message data. The sender can be optionally specified as well. +在 `InterModEnqueueEvent` 期间,你可以使用 `InterModComms#sendTo` 向其他模组发送消息。这些方法接受要发送消息到的模组的 ID、与消息数据相关的键(以区分不同的消息),以及持有消息数据的 `Supplier`。发送者可以选择性指定。 -Then, during `InterModProcessEvent`, you can use `InterModComms#getMessages` to get a stream of all received messages as `IMCMessage` objects. These hold the sender of the data, the intended receiver of the data, the data key, and the supplier for the actual data. +接着,在 `InterModProcessEvent` 期间,你可以使用 `InterModComms#getMessages` 获取作为 `IMCMessage` 对象的所有接收到的消息的流。这些消息包含了数据的发送者、预期的接收者、数据键和实际数据的供应商。 -### Other Mod Bus Events +### 其他模组总线事件 -Next to the lifecycle events, there are a few miscellaneous events that are fired on the mod event bus, mostly for legacy reasons. These are generally events where you can register, set up, or initialize various things. Most of these events are not ran in parallel in contrast to the lifecycle events. A few examples: +除了生命周期事件外,还有一些其他在模组总线上触发的杂项事件,主要是出于历史原因。这些事件通常不是并行运行的,与生命周期事件相反。例如: - `RegisterColorHandlersEvent` - `ModelEvent.BakingCompleted` - `TextureStitchEvent` :::warning -Most of these events are planned to be moved to the main event bus in a future version. +计划在未来版本中将大多数这些事件转移到主事件总线上。 ::: [modbus]: #event-buses From e8f8f0f05a303a3177ba43f461438eb0f5fdc90c Mon Sep 17 00:00:00 2001 From: DaZiDian <113085496+DaZiDian@users.noreply.github.com> Date: Thu, 25 Apr 2024 15:25:33 +0800 Subject: [PATCH 28/87] Update registries.md --- docs/concepts/registries.md | 180 ++++++++++++++++++------------------ 1 file changed, 92 insertions(+), 88 deletions(-) diff --git a/docs/concepts/registries.md b/docs/concepts/registries.md index 5dea1fab0..c959c41e3 100644 --- a/docs/concepts/registries.md +++ b/docs/concepts/registries.md @@ -1,90 +1,92 @@ -# Registries +# 注册 -Registration is the process of taking the objects of a mod (such as [items][item], [blocks][block], entities, etc.) and making them known to the game. Registering things is important, as without registration the game will simply not know about these objects, which will cause unexplainable behaviors and crashes. +注册是将模组中的对象(如[物品][item]、[方块][block]、实体等)加入游戏并使其被游戏识别的过程。注册这些对象非常重要,因为如果不注册,游戏将无法识别这些对象,这将导致无法解释的行为和崩溃。 -A registry is, simply put, a wrapper around a map that maps registry names (read on) to registered objects, often called registry entries. Registry names must be unique within the same registry, but the same registry name may be present in multiple registries. The most common example for this are blocks (in the `BLOCKS` registry) that have an item form with the same registry name (in the `ITEMS` registry). +简而言之,注册表是围绕映射注册名称(下面将说明)到注册对象的映射的封装,这些注册对象通常称为注册表项。注册名在同一注册表中必须唯一,但同一注册名可以出现在多个注册表中。最常见的例子是方块(在`BLOCKS`注册表中)具有与其同名的物品形式(在`ITEMS`注册表中)。 -Every registered object has a unique name, called its registry name. The name is represented as a [`ResourceLocation`][resloc]. For example, the registry name of the dirt block is `minecraft:dirt`, and the registry name of the zombie is `minecraft:zombie`. Modded objects will of course not use the `minecraft` namespace; their mod id will be used instead. +每个注册对象都有一个唯一的名称,称为其注册名称。名称表示为[`ResourceLocation`][resloc]。例如,泥土方块的注册名称为`minecraft:dirt`,僵尸的注册名称为`minecraft:zombie`。当然,模组化对象不会使用`minecraft`命名空间;而是使用它们的模组ID。 -## Vanilla vs. Modded +## 原版与模组化 -To understand some of the design decisions that were made in NeoForge's registry system, we will first look at how Minecraft does this. We will use the block registry as an example, as most other registries work the same way. +为了理解NeoForge的注册系统中所做的一些设计决策,我们首先看看Minecraft是如何处理这一问题的。我们将使用方块注册表作为例子,因为大多数其他注册表的工作方式相同。 -Registries generally register [singletons][singleton]. This means that all registry entries exist exactly once. For example, all stone blocks you see throughout the game are actually the same stone block, displayed many times. If you need the stone block, you can get it by referencing the registered block instance. +注册表通常注册[单例][singleton]。这意味着所有注册表项实际上只存在一次。例如,你在游戏中看到的所有石块实际上都是同一个石块,被多次显示。如果你需要石块,可以通过引用已注册的方块实例来获取它。 -Minecraft registers all blocks in the `Blocks` class. Through the `register` method, `Registry#register()` is called, with the block registry at `BuiltInRegistries.BLOCK` being the first parameter. After all blocks are registered, Minecraft performs various checks based on the list of blocks, for example the self check that verifies that all blocks have a model loaded. +Minecraft在`Blocks`类中注册所有方块。通过`register`方法,调用`Registry#register()`,其中第一个参数是方块注册表`BuiltInRegistries.BLOCK`。所有方块注册完成后,Minecraft会根据方块列表进行各种检查,例如验证所有方块是否已加载模型的自检。 -The main reason all of this works is that `Blocks` is classloaded early enough by Minecraft. Mods are not automatically classloaded by Minecraft, and thus workarounds are needed. +这一切之所以能够工作,是因为`Blocks`类在Minecraft中足够早地被类加载。模组并不会被Minecraft自动类加载,因此需要一些变通方法。 -## Methods for Registering +## 注册方法 -NeoForge offers two ways to register objects: the `DeferredRegister` class, and the `RegisterEvent`. Note that the former is a wrapper around the latter, and is recommended in order to prevent mistakes. +NeoForge提供了两种注册对象的方式:`DeferredRegister`类和`RegisterEvent`。请注意,前者是后者的封装,并且为了防止错误,推荐使用。 ### `DeferredRegister` -We begin by creating our `DeferredRegister`: +我们首先创建我们的`DeferredRegister`: ```java public static final DeferredRegister BLOCKS = DeferredRegister.create( - // The registry we want to use. - // Minecraft's registries can be found in BuiltInRegistries, NeoForge's registries can be found in NeoForgeRegistries. - // Mods may also add their own registries, refer to the individual mod's documentation or source code for where to find them. + // 我们想要使用的注册表。 + // Minecraft的注册表可以在BuiltInRegistries中找到,NeoForge的注册表可以在NeoForgeRegistries中找到。 + // 模组也可以添加它们自己的注册表,具体请参考各个模组的文档或源代码。 BuiltInRegistries.BLOCKS, - // Our mod id. + // 我们的模组ID。 ExampleMod.MOD_ID ); ``` -We can then add our registry entries as static final fields (see [the article on Blocks][block] for what parameters to add in `new Block()`): +然后,我们可以将我们的注册表项添加为静态最终字段(有关在`new Block()`中添加什么参数,请参阅[关于方块的文章][block]): ```java public static final DeferredHolder EXAMPLE_BLOCK = BLOCKS.register( - "example_block" // Our registry name. - () -> new Block(...) // A supplier of the object we want to register. + "example_block" // 我们的注册名称。 + () -> new Block(...) // 我们想要注册的对象的供应商。 ); ``` -The class `DeferredHolder` holds our object. The type parameter `R` is the type of the registry we are registering to (in our case `Block`). The type parameter `T` is the type of our supplier. Since we directly register a `Block` in this example, we provide `Block` as the second parameter. If we were to register an object of a subclass of `Block`, for example `SlabBlock`, we would provide `SlabBlock` here instead. +`DeferredHolder`类持有我们的对象。类型参数`R`是我们正在注册到的注册表的类型(在这个例子中是`Block`)。类型参数`T`是我们供应商的类型。由于我们在这个例子中直接注册了一个`Block`,我们提供了`Block`作为第二个参数。如果我们 -`DeferredHolder` is a subclass of `Supplier`. To get our registered object when we need it, we can call `DeferredHolder#get()`. The fact that `DeferredHolder` extends `Supplier` also allows us to use `Supplier` as the type of our field. That way, the above code block becomes the following: +要注册一个`Block`的子类的对象,例如`SlabBlock`,我们将在此提供`SlabBlock`。 + +`DeferredHolder`是`Supplier`的子类。当我们需要时可以调用`DeferredHolder#get()`来获取我们注册的对象。`DeferredHolder`扩展`Supplier`的事实也允许我们使用`Supplier`作为我们字段的类型。这样,上面的代码块变为以下内容: ```java public static final Supplier EXAMPLE_BLOCK = BLOCKS.register( - "example_block" // Our registry name. - () -> new Block(...) // A supplier of the object we want to register. + "example_block" // 我们的注册名称。 + () -> new Block(...) // 我们想要注册的对象的供应商。 ); ``` -Be aware that a few places explicitly require a `Holder` or `DeferredHolder` and will not just accept any `Supplier`. If you need either of those two, it is best to change the type of your `Supplier` back to `Holder` or `DeferredHolder` as necessary. +请注意,有些地方明确要求使用`Holder`或`DeferredHolder`,而不仅仅接受任何`Supplier`。如果你需要其中的两者之一,最好将你的`Supplier`的类型更改回`Holder`或`DeferredHolder`。 -Finally, since the entire system is a wrapper around registry events, we need to tell the `DeferredRegister` to attach itself to the registry events as needed: +最后,由于整个系统是围绕注册事件的封装,我们需要告诉`DeferredRegister`根据需要将自己附加到注册事件上: ```java -//This is our mod constructor +// 这是我们的模组构造函数 public ExampleMod(IModEventBus bus) { - //highlight-next-line + // 高亮下一行 ExampleBlocksClass.BLOCKS.register(bus); - //Other stuff here + // 这里还有其他内容 } ``` :::info -There are specialized variants of `DeferredRegister`s for blocks and items that provide helper methods, called [`DeferredRegister.Blocks`][defregblocks] and [`DeferredRegister.Items`][defregitems], respectively. +有针对方块和物品的`DeferredRegister`的特化变体,它们提供辅助方法,分别称为[`DeferredRegister.Blocks`][defregblocks]和[`DeferredRegister.Items`][defregitems]。 ::: ### `RegisterEvent` -`RegisterEvent` is the second way to register objects. This [event][event] is fired for each registry, after the mod constructors (since those are where `DeferredRegister`s register their internal event handlers) and before the loading of configs. `RegisterEvent` is fired on the mod event bus. +`RegisterEvent`是注册对象的第二种方式。这个[事件][event]在模组构造函数之后(因为这是`DeferredRegister`注册它们内部事件处理器的地方)和加载配置之前为每个注册表触发。`RegisterEvent`在模组事件总线上触发。 ```java @SubscribeEvent public void register(RegisterEvent event) { event.register( - // This is the registry key of the registry. - // Get these from BuiltInRegistries for vanilla registries, - // or from NeoForgeRegistries.Keys for NeoForge registries. + // 这是注册表的注册键。 + // 从BuiltInRegistries获取vanilla注册表的, + // 或从NeoForgeRegistries.Keys获取NeoForge注册表的。 BuiltInRegistries.BLOCKS, - // Register your objects here. + // 在这里注册你的对象。 registry -> { registry.register(new ResourceLocation(MODID, "example_block_1"), new Block(...)); registry.register(new ResourceLocation(MODID, "example_block_2"), new Block(...)); @@ -94,29 +96,31 @@ public void register(RegisterEvent event) { } ``` -## Querying Registries +## 查询注册表 -Sometimes, you will find yourself in situations where you want to get a registered object by a given id. Or, you want to get the id of a certain registered object. Since registries are basically maps of ids (`ResourceLocation`s) to distinct objects, i.e. a reversible map, both of these operations work: +有时候,你可能会发现自己处于想要通过给定ID获取注册对象的情况,或者你想要获取某个注册对象的ID。由于注册表本质上是ID(`ResourceLocation`)到独立对象的映射,即可逆映射,这两种操作都是可行的: ```java -BuiltInRegistries.BLOCKS.get(new ResourceLocation("minecraft", "dirt")); // returns the dirt block -BuiltInRegistries.BLOCKS.getKey(Blocks.DIRT); // returns the resource location "minecraft:dirt" +BuiltInRegistries.BLOCKS.get(new ResourceLocation("minecraft", "dirt")); // 返回泥土方块 +BuiltInRegistries.BLOCKS.getKey(Blocks.DIRT); // 返回资源位置"minecraft:dirt" -// Assume that ExampleBlocksClass.EXAMPLE_BLOCK.get() is a Supplier with the id "yourmodid:example_block" -BuiltInRegistries.BLOCKS.get(new ResourceLocation("yourmodid", "example_block")); // returns the example block -BuiltInRegistries.BLOCKS.getKey(ExampleBlocksClass.EXAMPLE_BLOCK.get()); // returns the resource location "yourmodid:example_block" +// 假设ExampleBlocksClass.EXAMPLE_BLOCK.get()是具有ID"yourmodid:example_block"的Supplier +BuiltInRegistries.BLOCKS.get(new ResourceLocation("yourmodid", "example_block")); // 返回示例方块 +BuiltInRegistries.BLOCKS.getKey(ExampleBlocksClass.EXAMPLE_BLOCK.get()); // 返回资源位置"yourmodid:example_block" ``` -If you just want to check for the presence of an object, this is also possible, though only with keys: +如果你只是想检查是否存在某个对象,这也是可能的,尽管只能用键: ```java BuiltInRegistries.BLOCKS.containsKey(new ResourceLocation("minecraft", "dirt")); // true -BuiltInRegistries.BLOCKS.containsKey(new ResourceLocation("create", "brass_ingot")); // true only if Create is installed +BuiltInRegistries.BLOCKS.containsKey(new ResourceLocation("create", "brass_ingot")); // 如果安装了Create则为true ``` -As the last example shows, this is possible with any mod id, and thus a perfect way to check if a certain item from another mod exists. +正如最后一个示例所示,这适用于任何模组ID,因此是检查另一个模组中是否存在某个物品的完美方式。 + +最后,我们还可以迭代注册表中的所有条目,无论是键还 -Finally, we can also iterate over all entries in a registry, either over the keys or over the entries (entries use the Java `Map.Entry` type): +是条目(条目使用Java的`Map.Entry`类型): ```java for (ResourceLocation id : BuiltInRegistries.BLOCKS.keySet()) { @@ -128,22 +132,22 @@ for (Map.Entry entry : BuiltInRegistries.BLOCKS.entrySe ``` :::note -Query operations always use vanilla `Registry`s, not `DeferredRegister`s. This is because `DeferredRegister`s are merely registration utilities. +查询操作始终使用vanilla `Registry`,而不是`DeferredRegister`。这是因为`DeferredRegister`只是注册工具。 ::: :::danger -Query operations are only safe to use after registration has finished. **DO NOT QUERY REGISTRIES WHILE REGISTRATION IS STILL ONGOING!** +查询操作只有在注册完成后才安全使用。**不要在注册仍在进行时查询注册表!** ::: -## Custom Registries +## 自定义注册表 -Custom registries allow you to specify additional systems that addon mods for your mod may want to plug into. For example, if your mod were to add spells, you could make the spells a registry and thus allow other mods to add spells to your mod, without you having to do anything else. It also allows you to do some things, such as syncing the entries, automatically. +自定义注册表允许你指定其他模组可能想要接入的附加系统。例如,如果你的模组要添加效果,你可以使效果成为一个注册表,从而允许其他模组添加效果到你的模组中,而无需你做任何其他事情。它还允许你自动执行一些操作,如同步条目。 -Let's start by creating the [registry key][resourcekey] and the registry itself: +让我们从创建[注册表键][resourcekey]和注册表本身开始: ```java -// We use spells as an example for the registry here, without any details about what a spell actually is (as it doesn't matter). -// Of course, all mentions of spells can and should be replaced with whatever your registry actually is. +// 我们在这里使用效果作为注册表的例子,不涉及效果实际是什么(因为这不重要)。 +// 当然,所有提到的效果都可以并且应该替换为你的注册表实际是什么。 public static final ResourceKey> SPELL_REGISTRY_KEY = ResourceKey.createRegistryKey(new ResourceLocation("yourmodid", "spells")); public static final Registry SPELL_REGISTRY = new RegistryBuilder<>(SPELL_REGISTRY_KEY) // If you want to enable integer id syncing, for networking. @@ -157,7 +161,7 @@ public static final Registry SPELL_REGISTRY = new Registry .create(); ``` -Then, tell the game that the registry exists by registering them to the root registry in `NewRegistryEvent`: +然后,通过将注册表注册到 `NewRegistryEvent` 中的根注册表来告诉游戏注册表存在: ```java @SubscribeEvent @@ -166,8 +170,7 @@ static void registerRegistries(NewRegistryEvent event) { } ``` -You can now register new registry contents like with any other registry, through both `DeferredRegister` and `RegisterEvent`: - +现在,您可以像使用任何其他注册表一样,通过`DeferredRegister`和`RegisterEvent`注册新的注册表内容: ```java public static final DeferredRegister SPELLS = DeferredRegister.create("yourmodid", SPELL_REGISTRY); public static final Supplier EXAMPLE_SPELL = SPELLS.register("example_spell", () -> new Spell(...)); @@ -181,20 +184,20 @@ public static void register(RegisterEvent event) { } ``` -## Datapack Registries +# 数据包注册表 -A datapack registry (also known as a dynamic registry or, after its main use case, worldgen registry) is a special kind of registry that loads data from [datapack][datapack] JSONs (hence the name) at world load, instead of loading them when the game starts. Default datapack registries most notably include most worldgen registries, among a few others. +数据包注册表(也称为动态注册表或世界生成注册表)是一种特殊的注册表,它在世界加载时从数据包 JSON 文件中加载数据,而不是在游戏启动时加载。默认的数据包注册表主要包括大多数世界生成注册表和其他一些注册表。 -Datapack registries allow their contents to be specified in JSON files. This means that no code (other than [datagen][datagen] if you don't want to write the JSON files yourself) is necessary. Every datapack registry has a [`Codec`][codec] associated with it, which is used for serialization, and each registry's id determines its datapack path: +数据包注册表允许通过 JSON 文件指定其内容。这意味着除了数据生成工具外,不需要任何代码(如果你不想自己编写 JSON 文件的话)。每个数据包注册表都有一个与之关联的编解码器(`Codec`),用于序列化,每个注册表的 ID 决定了其数据包路径: -- Minecraft's datapack registries use the format `data/yourmodid/registrypath` (for example `data/yourmodid/worldgen/biomes`, where `worldgen/biomes` is the registry path). -- All other datapack registries (NeoForge or modded) use the format `data/yourmodid/registrynamespace/registrypath` (for example `data/yourmodid/neoforge/loot_modifiers`, where `neoforge` is the registry namespace and `loot_modifiers` is the registry path). +- Minecraft 的数据包注册表使用格式 `data/yourmodid/registrypath`(例如 `data/yourmodid/worldgen/biomes`,其中 `worldgen/biomes` 是注册表路径)。 +- 所有其他数据包注册表(NeoForge 或模组化的)使用格式 `data/yourmodid/registrynamespace/registrypath`(例如 `data/yourmodid/neoforge/loot_modifiers`,其中 `neoforge` 是注册表命名空间,`loot_modifiers` 是注册表路径)。 -Datapack registries can be obtained from a `RegistryAccess`. This `RegistryAccess` can be retrieved by calling `ServerLevel#registryAccess()` if on the server, or `Minecraft.getInstance().connection#registryAccess()` if on the client (the latter only works if you are actually connected to a world, as otherwise the connection will be null). The result of these calls can then be used like any other registry to get specific elements, or to iterate over the contents. +可以从 `RegistryAccess` 获取数据包注册表。如果在服务器上,可以通过调用 `ServerLevel#registryAccess()` 来检索此 `RegistryAccess`;如果在客户端,可以通过调用 `Minecraft.getInstance().connection#registryAccess()` 来检索(后者仅在实际连接到世界时有效,否则连接将为 null)。然后可以像使用任何其他注册表一样使用这些调用的结果来获取特定元素或遍历内容。 -### Custom Datapack Registries +### 自定义数据包注册表 -Custom datapack registries do not require a `Registry` to be constructed. Instead, they just need a registry key and at least one [`Codec`][codec] to (de-)serialize its contents. Reiterating on the spells example from before, registering our spell registry as a datapack registry looks something like this: +自定义数据包注册表不需要构建 `Registry`。相反,它们只需要一个注册表键和至少一个编解码器(`Codec`)来序列化和反序列化其内容。根据之前的法术示例,将我们的法术注册表注册为数据包注册表的过程如下所示: ```java public static final ResourceKey> SPELL_REGISTRY_KEY = ResourceKey.createRegistryKey(new ResourceLocation("yourmodid", "spells")); @@ -202,51 +205,52 @@ public static final ResourceKey> SPELL_REGISTRY_KEY = ResourceKe @SubscribeEvent public static void registerDatapackRegistries(DataPackRegistryEvent.NewRegistry event) { event.dataPackRegistry( - // The registry key. + // 注册表键。 SPELL_REGISTRY_KEY, - // The codec of the registry contents. + // 注册表内容的编解码器。 Spell.CODEC, - // The network codec of the registry contents. Often identical to the normal codec. - // May be a reduced variant of the normal codec that omits data that is not needed on the client. - // May be null. If null, registry entries will not be synced to the client at all. - // May be omitted, which is functionally identical to passing null (a method overload - // with two parameters is called that passes null to the normal three parameter method). + // 网络编解码器。通常与普通编解码器相同。 + // 可能是普通编解码器的简化版本,省略了客户端不需要的数据。 + // 可能为 null。如果为 null,则注册表条目根本不会同步到客户端。 + // 可以省略,这在功能上与传递 null 相同(调用了一个带有两个参数的方法重载,该重载向普通的三参数方法传递 null)。 Spell.CODEC ); } ``` -### Data Generation for Datapack Registries +### 数据包注册表的数据生成 -Since writing all the JSON files by hand is both tedious and error-prone, NeoForge provides a [data provider][datagenindex] to generate the JSON files for you. This works for both built-in and your own datapack registries. +由于手工编写所有 JSON 文件既繁琐又容易出错,NeoForge 提供了一个数据提供器来为你生成 JSON 文件。这适用于内置的和你自己的数据包注册表。 -First, we create a `RegistrySetBuilder` and add our entries to it (one `RegistrySetBuilder` can hold entries for multiple registries): +首先,我们创建一个 `RegistrySetBuilder` 并向其添加条目(一个 `RegistrySetBuilder` 可以包含多个注册表的条目): ```java new RegistrySetBuilder() .add(Registries.CONFIGURED_FEATURE, bootstrap -> { - // Register configured features through the bootstrap context (see below) + // 通过引导上下文注册配置特性。 }) .add(Registries.PLACED_FEATURE, bootstrap -> { - // Register placed features through the bootstrap context (see below) + // 通过引导上下文注册放置特性。 }); ``` -The `bootstrap` lambda parameter is what we actually use to register our objects. It has the type `BootstrapContext`. To register an object, we call `#register` on it, like so: +`bootstrap` lambda 参数是我们实际用来注册对象的。要注册一个对象,我们这样调用 `#register`: ```java -// The resource key of our object. +// 我们对象的资源键。 public static final ResourceKey> EXAMPLE_CONFIGURED_FEATURE = ResourceKey.create( Registries.CONFIGURED_FEATURE, - new ResourceLocation(MOD_ID, "example_configured_feature") + new ResourceLocation(MOD_ID, "example_configured_feature + +") ); new RegistrySetBuilder() .add(Registries.CONFIGURED_FEATURE, bootstrap -> { bootstrap.register( - // The resource key of our configured feature. + // 我们配置特性的资源键。 EXAMPLE_CONFIGURED_FEATURE, - // The actual configured feature. + // 实际的配置特性。 new ConfiguredFeature<>(Feature.ORE, new OreConfiguration(...)) ); }) @@ -255,7 +259,7 @@ new RegistrySetBuilder() }); ``` -The `BootstrapContext` (name is typoed as `BootstapContext` in 1.20.4 and below) can also be used to lookup entries from another registry if needed: +如果需要,`BootstrapContext`(在 1.20.4 及以下版本中名称误写为 `BootstapContext`)还可以用来从另一个注册表查找条目: ```java public static final ResourceKey> EXAMPLE_CONFIGURED_FEATURE = ResourceKey.create( @@ -274,27 +278,27 @@ new RegistrySetBuilder() .add(Registries.PLACED_FEATURE, bootstrap -> { HolderGetter> otherRegistry = bootstrap.lookup(Registries.CONFIGURED_FEATURE); bootstrap.register(EXAMPLE_PLACED_FEATURE, new PlacedFeature( - otherRegistry.getOrThrow(EXAMPLE_CONFIGURED_FEATURE), // Get the configured feature - List.of() // No-op when placement happens - replace with whatever your placement parameters are + otherRegistry.getOrThrow(EXAMPLE_CONFIGURED_FEATURE), // 获取配置特性 + List.of() // 放置发生时无操作 - 替换为你的放置参数 )); }); ``` -Finally, we use our `RegistrySetBuilder` in an actual data provider, and register that data provider to the event: +最后,我们在实际的数据提供器中使用我们的 `RegistrySetBuilder` 并将该数据提供器注册到事件中: ```java @SubscribeEvent static void onGatherData(GatherDataEvent event) { event.getGenerator().addProvider( - // Only run datapack generation when server data is being generated + // 仅在生成服务器数据时运行数据包生成 event.includeServer(), - // Create the provider + // 创建提供器 output -> new DatapackBuiltinEntriesProvider( output, event.getLookupProvider(), - // Our registry set builder to generate the data from. + // 我们的注册表集生成器来生成数据。 new RegistrySetBuilder().add(...), - // A set of mod ids we are generating. Usually only your own mod id. + // 我们正在生成的模组 ID 集合。通常只有你自己的模组 ID。 Set.of("yourmodid") ) ); From 727475b9591e3158d2ae9b7068d6b211794f2606 Mon Sep 17 00:00:00 2001 From: DaZiDian <113085496+DaZiDian@users.noreply.github.com> Date: Thu, 25 Apr 2024 15:29:16 +0800 Subject: [PATCH 29/87] Update sides.md --- docs/concepts/sides.md | 52 ++++++++++++++++++++++-------------------- 1 file changed, 27 insertions(+), 25 deletions(-) diff --git a/docs/concepts/sides.md b/docs/concepts/sides.md index 48712d430..e2154f25c 100644 --- a/docs/concepts/sides.md +++ b/docs/concepts/sides.md @@ -1,60 +1,62 @@ -# Sides +# 游戏两侧的区分 -Like many other programs, Minecraft follows a client-server concept, where the client is responsible for displaying the data, while the server is responsible for updating them. When using these terms, we have a fairly intuitive understanding of what we mean... right? +和许多其他程序一样,Minecraft 遵循客户端-服务器的概念,其中客户端负责显示数据,而服务器负责更新数据。当我们使用这些术语时,我们对其含义有一个相当直观的理解...对吗? -Turns out, not so much. A lot of the confusion stems from Minecraft having two different concepts of sides, depending on the context: the physical and the logical side. +事实证明,并非如此。很多混淆源于 Minecraft 有两种不同的“侧”的概念,这取决于上下文:物理侧和逻辑侧。 -## Logical vs. Physical Side +## 逻辑侧与物理侧 -### The Physical Side +### 物理侧 -When you open your Minecraft launcher, select a Minecraft installation and press play, you boot up a **physical client**. The word "physical" is used here in the sense of "this is a client program". This especially means that client-side functionality, such as all the rendering stuff, is available here and can be used as needed. In contrast, the **physical server**, also known as dedicated server, is what opens when you launch a Minecraft server JAR. While the Minecraft server comes with a rudimentary GUI, it is missing all client-only functionality. Most notably, this means that various client classes are missing from the server JAR. Calling these classes on the physical server will lead to missing class errors, i.e. crashes, so we need to safeguard against this. +当你打开 Minecraft 启动器,选择一个 Minecraft 安装并按下播放时,你启动了一个**物理客户端**。这里使用“物理”一词是在“这是一个客户端程序”的意义上。这尤其意味着客户端功能,如所有渲染相关的功能,都可以在这里使用。相比之下,**物理服务器**,也称为专用服务器,是在你启动一个 Minecraft 服务器 JAR 时打开的。虽然 Minecraft 服务器带有一个基本的 GUI,但它缺少所有仅限客户端的功能。最值得注意的是,各种客户端类在服务器 JAR 中缺失。在物理服务器上调用这些类将导致缺少类错误,即崩溃,因此我们需要对此进行防护。 -### The Logical Side +### 逻辑侧 -The logical side is mainly focused on the internal program structure of Minecraft. The **logical server** is where the game logic runs. Things like time and weather changing, entity ticking, entity spawning, etc. all run on the server. All kinds of data, such as inventory contents, are the server's responsibility as well. The **logical client**, on the other hand, is responsible for displaying everything there is to display. Minecraft keeps all the client code in an isolated `net.minecraft.client` package, and runs it in a separate thread called the Render Thread, while everything else is considered common (i.e. client and server) code. +逻辑侧主要关注 Minecraft 的内部程序结构。**逻辑服务器**是游戏逻辑运行的地方。如时间和天气变化、实体更新、实体生成等都在服务器上运行。所有种类的数据,如库存内容,也都是服务器的责任。另一方面,**逻辑客户端**负责显示所有需要显示的内容。Minecraft 在一个名为 `net.minecraft.client` 的独立包中保留了所有客户端代码,并在一个名为渲染线程的独立线程中运行它,而其他所有内容都被视为公共代码(即客户端和服务器代码)。 -### What's the Difference? +### 两者有何区别? -The difference between physical and logical sides is best exemplified by two scenarios: +物理侧和逻辑侧之间的区别最好通过两种情况来说明: -- The player joins a **multiplayer** world. This is fairly straightforward: The player's physical (and logical) client connects to a physical (and logical) server somewhere else - the player does not care where; so long as they can connect, that's all the client knows of, and all the client needs to know. -- The player joins a **singleplayer** world. This is where things get interesting. The player's physical client spins up a logical server and then, now in the role of the logical client, connects to that logical server on the same machine. If you are familiar with networking, you can think of it as a connection to `localhost` (only conceptually; there are no actual sockets or similar involved). +- 玩家加入一个**多人游戏**世界。这相当直接:玩家的物理(和逻辑)客户端连接到别处的一个物理(和逻辑)服务器——玩家不关心在哪里;只要他们能连接,这就是所有客户端知道的,也是所有客户端需要知道的。 +- 玩家加入一个**单人游戏**世界。这里的情况变得有趣。玩家的物理客户端启动了一个逻辑服务器,然后现在作为逻辑客户端,连接到同一台机器上的那个逻辑服务器。如果你熟悉网络,你可以把它想象为连接到`localhost`(只是概念上的;没有实际的套接字或类似的东西涉及)。 -These two scenarios also show the main problem with this: If a logical server can work with your code, that alone doesn't guarantee that a physical server will be able to work with as well. This is why you should always test with dedicated servers to check for unexpected behavior. `NoClassDefFoundError`s and `ClassNotFoundException`s due to incorrect client and server separation are among the most common errors there are in modding. Another common mistake is working with static fields and accessing them from both logical sides; this is particularly tricky because there's usually no indication that something is wrong. +这两种情况也显示了主要问题:如果一个逻辑服务器可以使用你的代码,这并不保证物理服务器也能同样使用。这就是为什么你应该始终使用专用服务器进行测试,以检查意外行为。由于客户端和服务器分离不当导致的`NoClassDefFoundError`和`ClassNotFoundException`是模组制作中最常见的错误之一。另一个常见的错误是使用静态字段,并从两个逻辑侧访问它们;这特别棘手,因为通常没有迹象表明有什么问题。 :::tip -If you need to transfer data from one side to another, you must [send a packet][networking]. +如果你需要将数据从一侧传输到另一侧,你必须[发送一个数据包 + +][networking]。 ::: -In the NeoForge codebase, the physical side is represented by an enum called `Dist`, while the logical side is represented by an enum called `LogicalSide`. +在 NeoForge 代码库中,物理侧由一个名为 `Dist` 的枚举表示,而逻辑侧由一个名为 `LogicalSide` 的枚举表示。 :::info -Historically, server JARs have had classes the client did not. This is not the case anymore in modern versions; physical servers are a subset of physical clients, if you will. +从历史上看,服务器 JAR 拥有客户端没有的类。在现代版本中,这种情况已不复存在;如果愿意,可以认为物理服务器是物理客户端的一个子集。 ::: -## Performing Side-Specific Operations +## 执行侧特定操作 ### `Level#isClientSide()` -This boolean check will be your most used way to check sides. Querying this field on a `Level` object establishes the **logical** side the level belongs to: If this field is `true`, the level is running on the logical client. If the field is `false`, the level is running on the logical server. It follows that the physical server will always contain `false` in this field, but we cannot assume that `false` implies a physical server, since this field can also be `false` for the logical server inside a physical client (i.e. a singleplayer world). +这个布尔检查将是你最常用的检查侧的方式。在 `Level` 对象上查询此字段可以确定级别所属的**逻辑**侧:如果此字段为 `true`,则级别在逻辑客户端上运行。如果字段为 `false`,则级别在逻辑服务器上运行。据此,物理服务器将始终在此字段中包含 `false`,但我们不能假设 `false` 暗示物理服务器,因为此字段也可能在物理客户端内的逻辑服务器(即单人游戏世界)中为 `false`。 -Use this check whenever you need to determine if game logic and other mechanics should be run. For example, if you want to damage the player every time they click your block, or have your machine process dirt into diamonds, you should only do so after ensuring `#isClientSide` is `false`. Applying game logic to the logical client can cause desynchronization (ghost entities, desynchronized stats, etc.) in the best case, and crashes in the worst case. +只有在需要确定是否运行游戏逻辑和其他机制时才使用此检查。例如,如果你想在玩家每次点击你的方块时对玩家造成伤害,或者让你的机器将泥土处理成钻石,你应该确保 `#isClientSide` 为 `false` 后再进行。在逻辑客户端应用游戏逻辑可能导致最好的情况下出现数据不同步(幽灵实体、不同步的统计数据等),在最坏的情况下导致崩溃。 :::tip -This check should be used as your go-to default. Whenever you have a `Level` available, use this check. +这个检查应该作为你的默认选择。每当你有一个 `Level` 可用时,就使用这个检查。 ::: ### `FMLEnvironment.dist` -`FMLEnvironment.dist` is the **physical** counterpart to a `Level#isClientSide()` check. If this field is `Dist.CLIENT`, you are on a physical client. If the field is `Dist.SERVER`, you are on a physical server. +`FMLEnvironment.dist` 是 `Level#isClientSide()` 检查的**物理**对应项。如果此字段为 `Dist.CLIENT`,你就在物理客户端上。如果字段为 `Dist.SERVER`,你就在物理服务器上。 -Checking the physical environment is important when dealing with client-only classes. All calls to client-only code should always be encased in a check for `Dist.CLIENT`, and then call to a separate class to prevent accidental classloading: +检查物理环境在处理仅限客户端的类时非常重要。所有对客户端代码的调用都应始终包含在对 `Dist.CLIENT` 的检查中,并然后调用一个单独的类以防止意外的类加载: ```java public class SomeCommonClass { public void someCommonMethod() { - //SomeClientClass will be loaded if and only if you are on a physical client + // 仅当你在物理客户端上时,SomeClientClass 才会被加载 if (FMLEnvironment.dist == Dist.CLIENT) { SomeClientClass.someClientMethod(); } @@ -62,14 +64,14 @@ public class SomeCommonClass { } public class SomeClientClass { - public void someClientMethod() { + public static void someClientMethod() { Minecraft.getInstance().whatever(); } } ``` :::tip -Mods are generally expected to work on either side. This especially means that if you are developing a client-only mod, you should verify that the mod actually runs on a physical client, and no-op in the event that it does not. +通常期望模组在任一侧都能工作。这特别意味着,如果你正在开发一个仅限客户端的模组,你应该验证该模组实际上在物理客户端上运行,并且在不运行的情况下无操作。 ::: [networking]: ../networking/index.md From dc3cb4f04cc8ed01c567ce6036904a43f407a4d7 Mon Sep 17 00:00:00 2001 From: DaZiDian <113085496+DaZiDian@users.noreply.github.com> Date: Thu, 25 Apr 2024 15:32:11 +0800 Subject: [PATCH 30/87] Update advancements.md --- docs/datagen/advancements.md | 44 ++++++++++++++++++------------------ 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/docs/datagen/advancements.md b/docs/datagen/advancements.md index 61504fb03..bca701e5c 100644 --- a/docs/datagen/advancements.md +++ b/docs/datagen/advancements.md @@ -1,24 +1,24 @@ -Advancement Generation +# 成就生成 ====================== -[Advancements] can be generated for a mod by constructing a new `AdvancementProvider` and providing `AdvancementSubProvider`s. Advancements can either be created and supplied manually or, for convenience, created using `Advancement$Builder`. The provider must be [added][datagen] to the `DataGenerator`. +[成就] 可以通过构建一个新的 `AdvancementProvider` 并提供 `AdvancementSubProvider` 来为模组生成。成就可以手动创建和提供,或者为了方便,使用 `Advancement$Builder` 创建。提供者必须[添加][datagen]到 `DataGenerator` 中。 :::note -Forge provides an extension for the `AdvancementProvider` called `ForgeAdvancementProvider` which integrates better for generating advancements. So, this documentation will use `ForgeAdvancementProvider` along with the sub provider interface `ForgeAdvancementProvider$AdvancementGenerator`. +Forge 提供了一个扩展的 `AdvancementProvider`,名为 `ForgeAdvancementProvider`,它更适合生成成就。因此,本文档将使用 `ForgeAdvancementProvider` 以及子提供者接口 `ForgeAdvancementProvider$AdvancementGenerator`。 ::: ```java -// On the MOD event bus +// 在 MOD 事件总线上 @SubscribeEvent public void gatherData(GatherDataEvent event) { event.getGenerator().addProvider( - // Tell generator to run only when server data are generating + // 仅在生成服务器数据时运行生成器 event.includeServer(), output -> new ForgeAdvancementProvider( output, event.getLookupProvider(), event.getExistingFileHelper(), - // Sub providers which generate the advancements + // 生成成就的子提供者 List.of(subProvider1, subProvider2, /*...*/) ) ); @@ -28,41 +28,41 @@ public void gatherData(GatherDataEvent event) { `ForgeAdvancementProvider$AdvancementGenerator` ----------------------------------------------- -A `ForgeAdvancementProvider$AdvancementGenerator` is responsible for generating advancements, containing a method which takes in a registry lookup, the writer (`Consumer`), and the existing file helper.. +`ForgeAdvancementProvider$AdvancementGenerator` 负责生成成就,包含一个方法,该方法接收注册表查找、写入者 (`Consumer`) 和现有文件助手。 ```java -// In some subclass of ForgeAdvancementProvider$AdvancementGenerator or as a lambda reference +// 在 ForgeAdvancementProvider$AdvancementGenerator 的某个子类中或作为 lambda 引用 @Override public void generate(HolderLookup.Provider registries, Consumer writer, ExistingFileHelper existingFileHelper) { - // Build advancements here + // 在这里构建成就 } ``` `Advancement$Builder` --------------------- -`Advancement$Builder` is a convenience implementation for creating `Advancement`s to generate. It allows the definition of the parent advancement, the display information, the rewards when the advancement has been completed, and the requirements to unlock the advancement. Only the requirements need to be specified to create an `Advancement`. +`Advancement$Builder` 是一个便利的实现,用于创建用于生成的 `Advancement`。它允许定义父成就、显示信息、完成成就时的奖励以及解锁成就的要求。只需指定要求即可创建一个 `Advancement`。 -Although not required, there are a number of methods that are important to know of: +虽然不是必需的,但有几个方法是重要的: -Method | Description +方法 | 描述 :---: | :--- -`parent` | Sets the advancement which this advancement is directly linked to. Can either specify the name of the advancement or the advancement itself if its generated by the modder. -`display` | Sets the information to display to the chat, toast, and advancement screen. -`rewards` | Sets the rewards obtained when this advancement is completed. -`addCriterion` | Adds a condition to the advancement. -`requirements` | Specifies if the conditions must all return true or at least one must return true. An additional overload can be used to mix-and-match those operations. +`parent` | 设置此成就直接链接到的成就。可以指定成就的名称或如果由模组制作者生成,则指定成就本身。 +`display` | 设置显示在聊天、弹窗和成就屏幕上的信息。 +`rewards` | 设置完成此成就时获得的奖励。 +`addCriterion` | 为成就添加条件。 +`requirements` | 指定条件是否必须全部为真,或者至少有一个为真。可以使用额外的重载来混合这些操作。 -Once an `Advancement$Builder` is ready to be built, the `#save` method should be called which takes in the writer, the registry name of the advancement, and the file helper used to check whether the supplied parent exists. +一旦 `Advancement$Builder` 准备好建造,应调用 `#save` 方法,该方法需要写入者、成就的注册名和用于检查提供的父项是否存在的文件助手。 ```java -// In some ForgeAdvancementProvider$AdvancementGenerator#generate(registries, writer, existingFileHelper) +// 在某个 ForgeAdvancementProvider$AdvancementGenerator#generate(registries, writer, existingFileHelper) 中 Advancement example = Advancement.Builder.advancement() - .addCriterion("example_criterion", triggerInstance) // How the advancement is unlocked - .save(writer, name, existingFileHelper); // Add data to builder + .addCriterion("example_criterion", triggerInstance) // 如何解锁成就 + .save(writer, name, existingFileHelper); // 将数据添加到构建器 ``` -[advancements]: ../../resources/server/advancements.md +[成就]: ../../resources/server/advancements.md [datagen]: ../index.md#data-providers [conditional]: ../../resources/server/conditional.md From e8a6dd0a5e23bfca304283ca251d6dd72c67c0bb Mon Sep 17 00:00:00 2001 From: DaZiDian <113085496+DaZiDian@users.noreply.github.com> Date: Thu, 25 Apr 2024 15:33:10 +0800 Subject: [PATCH 31/87] Update glm.md --- docs/datagen/glm.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/datagen/glm.md b/docs/datagen/glm.md index 1192b2377..e7aafcebe 100644 --- a/docs/datagen/glm.md +++ b/docs/datagen/glm.md @@ -1,23 +1,23 @@ -Global Loot Modifier Generation +# 全局战利品修改器生成 =============================== -[Global Loot Modifiers (GLMs)][glm] can be generated for a mod by subclassing `GlobalLootModifierProvider` and implementing `#start`. Each GLM can be added generated by calling `#add` and specifying the name of the modifier and the [modifier instance][instance] to be serialized. After implementation, the provider must be [added][datagen] to the `DataGenerator`. +通过继承 `GlobalLootModifierProvider` 并实现 `#start` 方法,可以为模组生成[全局战利品修改器 (GLMs)][glm]。通过调用 `#add` 并指定修改器的名称和将被序列化的[修改器实例][instance],可以添加生成每个 GLM。实现后,必须将提供者[添加][datagen]到 `DataGenerator`。 ```java -// On the MOD event bus +// 在 MOD 事件总线上 @SubscribeEvent public void gatherData(GatherDataEvent event) { event.getGenerator().addProvider( - // Tell generator to run only when server data are generating + // 通知生成器仅在生成服务器数据时运行 event.includeServer(), output -> new MyGlobalLootModifierProvider(output, MOD_ID) ); } -// In some GlobalLootModifierProvider#start +// 在某个 GlobalLootModifierProvider#start 中 this.add("example_modifier", new ExampleModifier( new LootItemCondition[] { - WeatherCheck.weather().setRaining(true).build() // Executes when raining + WeatherCheck.weather().setRaining(true).build() // 在下雨时执行 }, "val1", 10, From 77aa38091e31def0e05cd1aa3f866ecaa51f9926 Mon Sep 17 00:00:00 2001 From: DaZiDian <113085496+DaZiDian@users.noreply.github.com> Date: Thu, 25 Apr 2024 15:38:39 +0800 Subject: [PATCH 32/87] Update loottables.md --- docs/datagen/loottables.md | 84 +++++++++++++++++++------------------- 1 file changed, 43 insertions(+), 41 deletions(-) diff --git a/docs/datagen/loottables.md b/docs/datagen/loottables.md index 24bd34a29..da652a3ed 100644 --- a/docs/datagen/loottables.md +++ b/docs/datagen/loottables.md @@ -1,20 +1,20 @@ -Loot Table Generation +# 战利品表生成 ===================== -[Loot tables][loottable] can be generated for a mod by constructing a new `LootTableProvider` and providing `LootTableProvider$SubProviderEntry`s. The provider must be [added][datagen] to the `DataGenerator`. +通过构建一个新的 `LootTableProvider` 并提供 `LootTableProvider$SubProviderEntry`,可以为模组生成[战利品表][loottable]。提供者必须被[添加][datagen]到 `DataGenerator`。 ```java -// On the MOD event bus +// 在 MOD 事件总线上 @SubscribeEvent public void gatherData(GatherDataEvent event) { event.getGenerator().addProvider( - // Tell generator to run only when server data are generating + // 告诉生成器仅在生成服务器数据时运行 event.includeServer(), output -> new MyLootTableProvider( output, - // Specify registry names of tables that are required to generate, or can leave empty + // 指定需要生成的表的注册名称,或者可以留空 Collections.emptySet(), - // Sub providers which generate the loot + // 生成战利品的子提供者 List.of(subProvider1, subProvider2, /*...*/) ) ); @@ -24,122 +24,124 @@ public void gatherData(GatherDataEvent event) { `LootTableSubProvider` ---------------------- -Each `LootTableProvider$SubProviderEntry` takes in a supplied `LootTableSubProvider`, which generates the loot table, for a given `LootContextParamSet`. The `LootTableSubProvider` contains a method which takes in the writer (`BiConsumer`) to generate a table. +每个 `LootTableProvider$SubProviderEntry` 都接收一个 `LootTableSubProvider`,它为给定的 `LootContextParamSet` 生成战利品表。`LootTableSubProvider` 包含一个方法,该方法接收写入者(`BiConsumer`)来生成表。 ```java public class ExampleSubProvider implements LootTableSubProvider { - // Used to create a factory method for the wrapping Supplier + // 用于创建封装 Supplier 的工厂方法 public ExampleSubProvider() {} - // The method used to generate the loot tables + // 用于生成战利品表的方法 @Override public void generate(BiConsumer writer) { - // Generate loot tables here by calling writer#accept + // 在这里通过调用 writer#accept 生成战利品表 } } ``` -The table can then be added to `LootTableProvider#getTables` for any available `LootContextParamSet`: +然后可以将表添加到 `LootTableProvider#getTables` 中,适用于任何可用的 `LootContextParamSet`: ```java -// In the list passed into the LootTableProvider constructor +// 传递到 LootTableProvider 构造函数的列表中 new LootTableProvider.SubProviderEntry( ExampleSubProvider::new, - // Loot table generator for the 'empty' param set + // 为 'empty' 参数集生成战利品表 LootContextParamSets.EMPTY ) ``` -### `BlockLootSubProvider` and `EntityLootSubProvider` Subclasses +### `BlockLootSubProvider` 和 `EntityLootSubProvider` 子类 -For `LootContextParamSets#BLOCK` and `#ENTITY`, there are special types (`BlockLootSubProvider` and `EntityLootSubProvider` respectively) which provide additional helper methods for creating and validating that there are loot tables. +对于 `LootContextParamSets#BLOCK` 和 `#ENTITY`,有特殊类型(分别是 `BlockLootSubProvider` 和 `EntityLootSubProvider`),它们提供额外的辅助方法来创建和验证是否存在战利品表。 -The `BlockLootSubProvider`'s constructor takes in a list of items, which are explosion resistant to determine whether the loot table can be generated if a block is exploded, and a `FeatureFlagSet`, which determines whether the block is enabled so that a loot table is generated for it. +`BlockLootSubProvider` 的构造函数接受一个物品列表,用于确定如果一个方块被爆炸时是否可以生成战利品表,以及一个 `FeatureFlagSet`,用于确定是否启用了方块以便为其生成战利品表。 ```java -// In some BlockLootSubProvider subclass +// 在某个 BlockLootSubProvider 子类中 public MyBlockLootSubProvider() { super(Collections.emptySet(), FeatureFlags.REGISTRY.allFlags()); } ``` -The `EntityLootSubProvider`'s constructor takes in a `FeatureFlagSet`, which determines whether the entity type is enabled so that a loot table is generated for it. +`EntityLootSubProvider` 的构造函数接受一个 `FeatureFlagSet`,用于确定是否启用了实体类型以便为其生成战利品表。 ```java -// In some EntityLootSubProvider subclass +// 在某个 EntityLootSubProvider 子类中 public MyEntityLootSubProvider() { super(FeatureFlags.REGISTRY.allFlags()); } ``` -To use them, all registered objects must be supplied to either `BlockLootSubProvider#getKnownBlocks` and `EntityLootSubProvider#getKnownEntityTypes` respectively. These methods are to make sure all objects within the iterable has a loot table. +要使用它们,所有已注册的对象必须分别提供给 `BlockLootSubProvider#getKnownBlocks` 和 `EntityLootSubProvider#getKnownEntityTypes`。这些方法确保迭代中的所有对象都有一个战利品表。 :::tip -If `DeferredRegister` is being used to register a mod's objects, then the `#getKnown*` methods can be supplied the entries via `DeferredRegister#getEntries`: +如果使用 `DeferredRegister` 来注册模组的对象,则 `#getKnown*` 方法可以通过 `DeferredRegister#getEntries` 提供条目: ```java -// In some BlockLootSubProvider subclass for some DeferredRegister BLOCK_REGISTRAR +// 在某个 BlockLootSubProvider 子类中,用于某个 DeferredRegister BLOCK_REGISTRAR @Override protected Iterable getKnownBlocks() { - return BLOCK_REGISTRAR.getEntries() // Get all registered entries - .stream() // Stream the wrapped objects - .flatMap(RegistryObject::stream) // Get the object if available - ::iterator; // Create the iterable + return BLOCK_REGISTRAR.getEntries() // 获取所有注册的条目 + .stream() // 流式处理封装的对象 + .flatMap(RegistryObject::stream) // 如果可用,则获取对象 + ::iterator; // 创建迭代器 } ``` ::: -The loot tables themselves can be added by implementing the `#generate` method. +通过实现 `#generate` 方法可以添加战利品表。 ```java -// In some BlockLootSubProvider subclass +// 在某个 BlockLootSubProvider 子类中 @Override -public void generate() { - // Add loot tables here +public void + + generate() { + // 在这里添加战利品表 } ``` -Loot Table Builders +战利品表生成器 ------------------- -To generate loot tables, they are accepted by the `LootTableSubProvider` as a `LootTable$Builder`. Afterwards, the specified `LootContextParamSet` is set in the `LootTableProvider$SubProviderEntry` and then built via `#build`. Before being built, the builder can specify entries, conditions, and modifiers which affect how the loot table functions. +战利品表通过 `LootTableSubProvider` 接收为 `LootTable$Builder`。之后,指定的 `LootContextParamSet` 在 `LootTableProvider$SubProviderEntry` 中设置,然后通过 `#build` 构建。在构建之前,构建器可以指定条目、条件和修饰符,这些因素影响战利品表的功能。 :::note -The functionality of loot tables is so expansive that it will not be covered by this documentation in its entirety. Instead, a brief description of each component will be mentioned. The specific subtypes of each component can be found using an IDE. Their implementations will be left as an exercise to the reader. +战利品表的功能非常广泛,本文档无法全部涵盖。相反,每个组件将简要描述。每个组件的具体子类型可以使用 IDE 查找。它们的实现将留给读者作为练习。 ::: ### LootTable -Loot tables are the base object and can be transformed into the required `LootTable$Builder` using `LootTable#lootTable`. The loot table can be built with a list of pools (via `#withPool`) applied in the order they are specified along with functions (via `#apply`) to modify the resulting items of those pools. +战利品表是基本对象,可以使用 `LootTable#lootTable` 转换为所需的 `LootTable$Builder`。战利品表可以通过指定的方式构建,包括一系列池(通过 `#withPool` 应用)以及函数(通过 `#apply`),用于修改这些池的结果物品。 ### LootPool -Loot pools represents a group to perform operations and can generate a `LootPool$Builder` using `LootPool#lootPool`. Each loot pool can specify the entries (via `#add`) which define the operations in the pool, the conditions (via `#when`) which define if the operations in the pool should be performed, and functions (via `#apply`) to modify the resulting items of the entries. Each pool can be executed as many times as specified (via `#setRolls`). Additionally, bonus executions can be specified (via `#setBonusRolls`) which is modified by the luck of the executor. +战利品池代表一个执行操作的组,并可以使用 `LootPool#lootPool` 生成一个 `LootPool$Builder`。每个战利品池可以指定条目(通过 `#add`),这些条目定义池中的操作,条件(通过 `#when`)定义是否应执行池中的操作,以及函数(通过 `#apply`)修改条目的结果物品。每个池可以执行尽可能多的次数(通过 `#setRolls`)。此外,还可以指定额外的执行次数(通过 `#setBonusRolls`),这受到执行者幸运值的影响。 ### LootPoolEntryContainer -Loot entries define the operations to occur when selected, typically generating items. Each entry has an associated, [registered] `LootPoolEntryType`. They also have their own associated builders which subtype `LootPoolEntryContainer$Builder`. Multiple entries can execute at the same time (via `#append`) or sequentially until one fails (via `#then`). Additionally, entries can default to another entry on failure (via `#otherwise`). +战利品条目定义了选中时要执行的操作,通常生成物品。每个条目都有一个相关的、[已注册][registered]的 `LootPoolEntryType`。它们还有自己的相关构建器,这些构建器是 `LootPoolEntryContainer$Builder` 的子类型。多个条目可以同时执行(通过 `#append`)或顺序执行,直到一个失败(通过 `#then`)。此外,条目可以在失败时默认为另一个条目(通过 `#otherwise`)。 ### LootItemCondition -Loot conditions define requirements which need to be met for some operation to execute. Each condition has an associated, [registered] `LootItemConditionType`. They also have their own associated builders which subtype `LootItemCondition$Builder`. By default, all loot conditions specified must return true for an operation to execute. Loot conditions can also be specified such that only one must return true instead (via `#or`). Additionally, the resulting output of a condition can be inverted (via `#invert`). +战利品条件定义了执行某些操作所需满足的要求。每个条件都有一个相关的、[已注册][registered]的 `LootItemConditionType`。它们还有自己的相关构建器,这些构建器是 `LootItemCondition$Builder` 的子类型。默认情况下,指定的所有战利品条件必须为真,才能执行操作。也可以指定战利品条件,使得只需一个条件为真即可(通过 `#or`)。此外,条件的结果输出可以被反转(通过 `#invert`)。 ### LootItemFunction -Loot functions modify the result of an execution before passing it to the output. Each function has an associated, [registered] `LootItemFunctionType`. They also have their own associated builders which subtype `LootItemFunction$Builder`. +战利品函数在将执行结果传递给输出之前修改结果。每个函数都有一个相关的、[已注册][registered]的 `LootItemFunctionType`。它们还有自己的相关构建器,这些构建器是 `LootItemFunction$Builder` 的子类型。 #### NbtProvider -NBT providers are a special type of functions defined by `CopyNbtFunction`. They define where to pull tag information from. Each provider has an associated, [registered] `LootNbtProviderType`. +NBT 提供者是由 `CopyNbtFunction` 定义的特殊类型函数。它们定义了从哪里拉取标签信息。每个提供者都有一个相关的、[已注册][registered]的 `LootNbtProviderType`。 ### NumberProvider -Number providers determine how many times a loot pool executes. Each provider has an associated, [registered] `LootNumberProviderType`. +数字提供者决定战利品池执行的次数。每个提供者都有一个相关的、[已注册][registered]的 `LootNumberProviderType`。 #### ScoreboardNameProvider -Scoreboard providers are a special type of number providers defined by `ScoreboardValue`. They define the name of the scoreboard to pull the number of rolls to execute from. Each provider has an associated, [registered] `LootScoreProviderType`. +记分板提供者是由 `ScoreboardValue` 定义的一种特殊类型的数字提供者。它们定义了从哪个记分板拉取执行次数的名字。每个提供者都有一个关联的[已注册][registered]`LootScoreProviderType`。 [loottable]: ../../resources/server/loottables.md [datagen]: ../index.md#data-providers From a53dce72927f6bb1068bf53155fd84838af0055e Mon Sep 17 00:00:00 2001 From: DaZiDian <113085496+DaZiDian@users.noreply.github.com> Date: Thu, 25 Apr 2024 15:47:34 +0800 Subject: [PATCH 33/87] Update tags.md --- docs/datagen/tags.md | 102 +++++++++++++++++++++---------------------- 1 file changed, 51 insertions(+), 51 deletions(-) diff --git a/docs/datagen/tags.md b/docs/datagen/tags.md index f07ca2c9a..f0a8287ab 100644 --- a/docs/datagen/tags.md +++ b/docs/datagen/tags.md @@ -1,16 +1,16 @@ -Tag Generation +标签生成 ============== -[Tags] can be generated for a mod by subclassing `TagsProvider` and implementing `#addTags`. After implementation, the provider must be [added][datagen] to the `DataGenerator`. +通过子类化 `TagsProvider` 并实现 `#addTags` 方法,可以为一个模组生成[标签]。在实现之后,必须将提供者[添加][datagen]到 `DataGenerator` 中。 ```java -// On the MOD event bus +// 在 MOD 事件总线上 @SubscribeEvent public void gatherData(GatherDataEvent event) { event.getGenerator().addProvider( - // Tell generator to run only when server data are generating + // 仅在生成服务器数据时运行生成器 event.includeServer(), - // Extends net.neoforged.neoforge.common.data.BlockTagsProvider + // 扩展 net.neoforged.neoforge.common.data.BlockTagsProvider output -> new MyBlockTagsProvider( output, event.getLookupProvider(), @@ -24,75 +24,75 @@ public void gatherData(GatherDataEvent event) { `TagsProvider` -------------- -The tags provider has two methods used for generating tags: creating a tag with objects and other tags via `#tag`, or using tags from other object types to generate the tag data via `#getOrCreateRawBuilder`. +标签提供者有两种用于生成标签的方法:通过 `#tag` 创建带有对象和其他标签的标签,或者使用其他对象类型的标签生成标签数据通过 `#getOrCreateRawBuilder`。 :::note -Typically, a provider will not call `#getOrCreateRawBuilder` directly unless a registry contains a representation of objects from a different registry (blocks have item representations to obtain the blocks in the inventory). +通常,提供者不会直接调用 `#getOrCreateRawBuilder`,除非注册表包含来自不同注册表的对象的表示(方块有物品表示以获取存储在库存中的方块)。 ::: -When `#tag` is called, a `TagAppender` is created which acts as a chainable consumer of elements to add to the tag: +当调用 `#tag` 时,将创建一个 `TagAppender`,它作为一种可链接的元素消费者添加到标签中: -Method | Description -:---: | :--- -`add` | Adds an object to a tag through its resource key. -`addOptional` | Adds an object to a tag through its name. If the object is not present, then the object will be skipped when loading. -`addTag` | Adds a tag to a tag through its tag key. All elements within the inner tag are now a part of the outer tag. -`addOptionalTag` | Adds a tag to a tag through its name. If the tag is not present, then the tag will be skipped when loading. -`replace` | When `true`, all previously loaded entries added to this tag from other datapacks will be discarded. If a datapack is loaded after this one, then it will still append the entries to the tag. -`remove` | Removes an object or tag from a tag through its name or key. +方法 | 描述 +:---: | :--- +`add` | 通过其资源键将对象添加到标签中。 +`addOptional` | 通过其名称将对象添加到标签中。如果对象不存在,则在加载时将跳过该对象。 +`addTag` | 通过其标签键将标签添加到标签中。内部标签中的所有元素现在都是外部标签的一部分。 +`addOptionalTag` | 通过其名称将标签添加到标签中。如果标签不存在,则在加载时将跳过该标签。 +`replace` | 当为 `true` 时,将丢弃从其他数据包添加到此标签中的所有先前加载的条目。如果在加载此数据包之后加载了一个数据包,则仍将将条目附加到标签。 +`remove` | 通过其名称或键从标签中删除对象或标签。 ```java -// In some TagProvider#addTags +// 在某些 TagProvider#addTags 中 this.tag(EXAMPLE_TAG) - .add(EXAMPLE_OBJECT) // Adds an object to the tag - .addOptional(new ResourceLocation("othermod", "other_object")) // Adds an object from another mod to the tag + .add(EXAMPLE_OBJECT) // 向标签中添加对象 + .addOptional(new ResourceLocation("othermod", "other_object")) // 向标签中添加来自其他模组的对象 this.tag(EXAMPLE_TAG_2) - .addTag(EXAMPLE_TAG) // Adds a tag to the tag - .remove(EXAMPLE_OBJECT) // Removes an object from this tag + .addTag(EXAMPLE_TAG) // 向标签中添加标签 + .remove(EXAMPLE_OBJECT) // 从此标签中移除对象 ``` :::important -If the mod's tags softly depends on another mod's tags (the other mod may or may not be present at runtime), the other mods' tags should be referenced using the optional methods. +如果模组的标签在某些情况下依赖于其他模组的标签(其他模组可能在运行时存在或不存在),则应使用可选的方法引用其他模组的标签。 ::: -### Existing Providers +### 现有提供者 -Minecraft contains a few tag providers for certain registries that can be subclassed instead. Additionally, some providers contain additional helper methods to more easily create tags. +Minecraft 包含一些特定注册表的标签提供者,可以进行子类化。此外,一些提供者包含附加的辅助方法,以更轻松地创建标签。 -Registry Object Type | Tag Provider -:---: | :--- -`Block` | `BlockTagsProvider`\* -`Item` | `ItemTagsProvider` -`EntityType` | `EntityTypeTagsProvider` -`Fluid` | `FluidTagsProvider` -`GameEvent` | `GameEventTagsProvider` -`Biome` | `BiomeTagsProvider` +注册表对象类型 | 标签提供者 +:---: | :--- +`Block` | `BlockTagsProvider`\* +`Item` | `ItemTagsProvider` +`EntityType` | `EntityTypeTagsProvider` +`Fluid` | `FluidTagsProvider` +`GameEvent` | `GameEventTagsProvider` +`Biome` | `BiomeTagsProvider` `FlatLevelGeneratorPreset` | `FlatLevelGeneratorPresetTagsProvider` -`WorldPreset` | `WorldPresetTagsProvider` -`Structure` | `StructureTagsProvider` -`PoiType` | `PoiTypeTagsProvider` -`BannerPattern` | `BannerPatternTagsProvider` -`CatVariant` | `CatVariantTagsProvider` -`PaintingVariant` | `PaintingVariantTagsProvider` -`Instrument` | `InstrumentTagsProvider` -`DamageType` | `DamageTypeTagsProvider` +`WorldPreset` | `WorldPresetTagsProvider` +`Structure` | `StructureTagsProvider` +`PoiType` | `PoiTypeTagsProvider` +`BannerPattern` | `BannerPatternTagsProvider` +`CatVariant` | `CatVariantTagsProvider` +`PaintingVariant` | `PaintingVariantTagsProvider` +`Instrument` | `InstrumentTagsProvider` +`DamageType` | `DamageTypeTagsProvider` -\* `BlockTagsProvider` is a Forge added `TagsProvider`. +\* `BlockTagsProvider` 是 Forge 添加的 `TagsProvider`。 #### `ItemTagsProvider#copy` -Blocks have item representations to obtain them in the inventory. As such, many of the block tags can also be an item tag. To easily generate item tags to have the same entries as block tags, the `#copy` method can be used which takes in the block tag to copy from and the item tag to copy to. +方块具有物品表示以在库存中获取它们。因此,许多方块标签也可以是物品标签。为了轻松生成具有与方块标签相同条目的物品标签,可以使用 `#copy` 方法,该方法接受要从中复制的方块标签和要复制到的物品标签。 ```java -//In ItemTagsProvider#addTags +// 在 ItemTagsProvider#addTags 中 this.copy(EXAMPLE_BLOCK_TAG, EXAMPLE_ITEM_TAG); ``` -Custom Tag Providers +自定义标签提供者 -------------------- -A custom tag provider can be created via a `TagsProvider` subclass which takes in the registry key to generate tags for. +可以通过一个接受注册表键以生成标签的 `TagsProvider` 子类来创建自定义标签提供者。 ```java public RecipeTypeTagsProvider(PackOutput output, CompletableFuture registries, ExistingFileHelper fileHelper) { @@ -100,12 +100,12 @@ public RecipeTypeTagsProvider(PackOutput output, CompletableFuture registries, ExistingFileHelper fileHelper) { super( output, @@ -118,6 +118,6 @@ public AttributeTagsProvider(PackOutput output, CompletableFuture Date: Thu, 25 Apr 2024 15:48:04 +0800 Subject: [PATCH 34/87] Update tags.md --- docs/datagen/tags.md | 102 +++++++++++++++++++++---------------------- 1 file changed, 51 insertions(+), 51 deletions(-) diff --git a/docs/datagen/tags.md b/docs/datagen/tags.md index f0a8287ab..f07ca2c9a 100644 --- a/docs/datagen/tags.md +++ b/docs/datagen/tags.md @@ -1,16 +1,16 @@ -标签生成 +Tag Generation ============== -通过子类化 `TagsProvider` 并实现 `#addTags` 方法,可以为一个模组生成[标签]。在实现之后,必须将提供者[添加][datagen]到 `DataGenerator` 中。 +[Tags] can be generated for a mod by subclassing `TagsProvider` and implementing `#addTags`. After implementation, the provider must be [added][datagen] to the `DataGenerator`. ```java -// 在 MOD 事件总线上 +// On the MOD event bus @SubscribeEvent public void gatherData(GatherDataEvent event) { event.getGenerator().addProvider( - // 仅在生成服务器数据时运行生成器 + // Tell generator to run only when server data are generating event.includeServer(), - // 扩展 net.neoforged.neoforge.common.data.BlockTagsProvider + // Extends net.neoforged.neoforge.common.data.BlockTagsProvider output -> new MyBlockTagsProvider( output, event.getLookupProvider(), @@ -24,75 +24,75 @@ public void gatherData(GatherDataEvent event) { `TagsProvider` -------------- -标签提供者有两种用于生成标签的方法:通过 `#tag` 创建带有对象和其他标签的标签,或者使用其他对象类型的标签生成标签数据通过 `#getOrCreateRawBuilder`。 +The tags provider has two methods used for generating tags: creating a tag with objects and other tags via `#tag`, or using tags from other object types to generate the tag data via `#getOrCreateRawBuilder`. :::note -通常,提供者不会直接调用 `#getOrCreateRawBuilder`,除非注册表包含来自不同注册表的对象的表示(方块有物品表示以获取存储在库存中的方块)。 +Typically, a provider will not call `#getOrCreateRawBuilder` directly unless a registry contains a representation of objects from a different registry (blocks have item representations to obtain the blocks in the inventory). ::: -当调用 `#tag` 时,将创建一个 `TagAppender`,它作为一种可链接的元素消费者添加到标签中: +When `#tag` is called, a `TagAppender` is created which acts as a chainable consumer of elements to add to the tag: -方法 | 描述 -:---: | :--- -`add` | 通过其资源键将对象添加到标签中。 -`addOptional` | 通过其名称将对象添加到标签中。如果对象不存在,则在加载时将跳过该对象。 -`addTag` | 通过其标签键将标签添加到标签中。内部标签中的所有元素现在都是外部标签的一部分。 -`addOptionalTag` | 通过其名称将标签添加到标签中。如果标签不存在,则在加载时将跳过该标签。 -`replace` | 当为 `true` 时,将丢弃从其他数据包添加到此标签中的所有先前加载的条目。如果在加载此数据包之后加载了一个数据包,则仍将将条目附加到标签。 -`remove` | 通过其名称或键从标签中删除对象或标签。 +Method | Description +:---: | :--- +`add` | Adds an object to a tag through its resource key. +`addOptional` | Adds an object to a tag through its name. If the object is not present, then the object will be skipped when loading. +`addTag` | Adds a tag to a tag through its tag key. All elements within the inner tag are now a part of the outer tag. +`addOptionalTag` | Adds a tag to a tag through its name. If the tag is not present, then the tag will be skipped when loading. +`replace` | When `true`, all previously loaded entries added to this tag from other datapacks will be discarded. If a datapack is loaded after this one, then it will still append the entries to the tag. +`remove` | Removes an object or tag from a tag through its name or key. ```java -// 在某些 TagProvider#addTags 中 +// In some TagProvider#addTags this.tag(EXAMPLE_TAG) - .add(EXAMPLE_OBJECT) // 向标签中添加对象 - .addOptional(new ResourceLocation("othermod", "other_object")) // 向标签中添加来自其他模组的对象 + .add(EXAMPLE_OBJECT) // Adds an object to the tag + .addOptional(new ResourceLocation("othermod", "other_object")) // Adds an object from another mod to the tag this.tag(EXAMPLE_TAG_2) - .addTag(EXAMPLE_TAG) // 向标签中添加标签 - .remove(EXAMPLE_OBJECT) // 从此标签中移除对象 + .addTag(EXAMPLE_TAG) // Adds a tag to the tag + .remove(EXAMPLE_OBJECT) // Removes an object from this tag ``` :::important -如果模组的标签在某些情况下依赖于其他模组的标签(其他模组可能在运行时存在或不存在),则应使用可选的方法引用其他模组的标签。 +If the mod's tags softly depends on another mod's tags (the other mod may or may not be present at runtime), the other mods' tags should be referenced using the optional methods. ::: -### 现有提供者 +### Existing Providers -Minecraft 包含一些特定注册表的标签提供者,可以进行子类化。此外,一些提供者包含附加的辅助方法,以更轻松地创建标签。 +Minecraft contains a few tag providers for certain registries that can be subclassed instead. Additionally, some providers contain additional helper methods to more easily create tags. -注册表对象类型 | 标签提供者 -:---: | :--- -`Block` | `BlockTagsProvider`\* -`Item` | `ItemTagsProvider` -`EntityType` | `EntityTypeTagsProvider` -`Fluid` | `FluidTagsProvider` -`GameEvent` | `GameEventTagsProvider` -`Biome` | `BiomeTagsProvider` +Registry Object Type | Tag Provider +:---: | :--- +`Block` | `BlockTagsProvider`\* +`Item` | `ItemTagsProvider` +`EntityType` | `EntityTypeTagsProvider` +`Fluid` | `FluidTagsProvider` +`GameEvent` | `GameEventTagsProvider` +`Biome` | `BiomeTagsProvider` `FlatLevelGeneratorPreset` | `FlatLevelGeneratorPresetTagsProvider` -`WorldPreset` | `WorldPresetTagsProvider` -`Structure` | `StructureTagsProvider` -`PoiType` | `PoiTypeTagsProvider` -`BannerPattern` | `BannerPatternTagsProvider` -`CatVariant` | `CatVariantTagsProvider` -`PaintingVariant` | `PaintingVariantTagsProvider` -`Instrument` | `InstrumentTagsProvider` -`DamageType` | `DamageTypeTagsProvider` +`WorldPreset` | `WorldPresetTagsProvider` +`Structure` | `StructureTagsProvider` +`PoiType` | `PoiTypeTagsProvider` +`BannerPattern` | `BannerPatternTagsProvider` +`CatVariant` | `CatVariantTagsProvider` +`PaintingVariant` | `PaintingVariantTagsProvider` +`Instrument` | `InstrumentTagsProvider` +`DamageType` | `DamageTypeTagsProvider` -\* `BlockTagsProvider` 是 Forge 添加的 `TagsProvider`。 +\* `BlockTagsProvider` is a Forge added `TagsProvider`. #### `ItemTagsProvider#copy` -方块具有物品表示以在库存中获取它们。因此,许多方块标签也可以是物品标签。为了轻松生成具有与方块标签相同条目的物品标签,可以使用 `#copy` 方法,该方法接受要从中复制的方块标签和要复制到的物品标签。 +Blocks have item representations to obtain them in the inventory. As such, many of the block tags can also be an item tag. To easily generate item tags to have the same entries as block tags, the `#copy` method can be used which takes in the block tag to copy from and the item tag to copy to. ```java -// 在 ItemTagsProvider#addTags 中 +//In ItemTagsProvider#addTags this.copy(EXAMPLE_BLOCK_TAG, EXAMPLE_ITEM_TAG); ``` -自定义标签提供者 +Custom Tag Providers -------------------- -可以通过一个接受注册表键以生成标签的 `TagsProvider` 子类来创建自定义标签提供者。 +A custom tag provider can be created via a `TagsProvider` subclass which takes in the registry key to generate tags for. ```java public RecipeTypeTagsProvider(PackOutput output, CompletableFuture registries, ExistingFileHelper fileHelper) { @@ -100,12 +100,12 @@ public RecipeTypeTagsProvider(PackOutput output, CompletableFuture registries, ExistingFileHelper fileHelper) { super( output, @@ -118,6 +118,6 @@ public AttributeTagsProvider(PackOutput output, CompletableFuture Date: Thu, 25 Apr 2024 16:05:36 +0800 Subject: [PATCH 35/87] Update tags.md --- docs/datagen/tags.md | 100 +++++++++++++++++++++---------------------- 1 file changed, 50 insertions(+), 50 deletions(-) diff --git a/docs/datagen/tags.md b/docs/datagen/tags.md index f07ca2c9a..d72c35a66 100644 --- a/docs/datagen/tags.md +++ b/docs/datagen/tags.md @@ -1,16 +1,16 @@ -Tag Generation -============== +标签生成 +====== -[Tags] can be generated for a mod by subclassing `TagsProvider` and implementing `#addTags`. After implementation, the provider must be [added][datagen] to the `DataGenerator`. +通过继承`TagsProvider`并实现`#addTags`方法,可以为一个mod生成[标签]。实现后,必须将提供器[添加][datagen]到`DataGenerator`中。 ```java -// On the MOD event bus +// 在MOD事件总线上 @SubscribeEvent public void gatherData(GatherDataEvent event) { event.getGenerator().addProvider( - // Tell generator to run only when server data are generating + // 通知生成器仅在生成服务器数据时运行 event.includeServer(), - // Extends net.neoforged.neoforge.common.data.BlockTagsProvider + // 继承自net.neoforged.neoforge.common.data.BlockTagsProvider output -> new MyBlockTagsProvider( output, event.getLookupProvider(), @@ -24,75 +24,75 @@ public void gatherData(GatherDataEvent event) { `TagsProvider` -------------- -The tags provider has two methods used for generating tags: creating a tag with objects and other tags via `#tag`, or using tags from other object types to generate the tag data via `#getOrCreateRawBuilder`. +标签提供器有两种用于生成标签的方法:通过`#tag`使用对象和其他标签创建一个标签,或者通过`#getOrCreateRawBuilder`使用其他对象类型的标签来生成标签数据。 :::note -Typically, a provider will not call `#getOrCreateRawBuilder` directly unless a registry contains a representation of objects from a different registry (blocks have item representations to obtain the blocks in the inventory). +通常,除非一个注册表包含来自不同注册表的对象的表示(例如,方块有项目表示以在库存中获得方块),否则提供器不会直接调用`#getOrCreateRawBuilder`。 ::: -When `#tag` is called, a `TagAppender` is created which acts as a chainable consumer of elements to add to the tag: +调用`#tag`时,会创建一个`TagAppender`,它作为链式消费者,用于向标签添加元素: -Method | Description -:---: | :--- -`add` | Adds an object to a tag through its resource key. -`addOptional` | Adds an object to a tag through its name. If the object is not present, then the object will be skipped when loading. -`addTag` | Adds a tag to a tag through its tag key. All elements within the inner tag are now a part of the outer tag. -`addOptionalTag` | Adds a tag to a tag through its name. If the tag is not present, then the tag will be skipped when loading. -`replace` | When `true`, all previously loaded entries added to this tag from other datapacks will be discarded. If a datapack is loaded after this one, then it will still append the entries to the tag. -`remove` | Removes an object or tag from a tag through its name or key. +方法 | 描述 +:---: | :--- +`add` | 通过其资源键将一个对象添加到标签中。 +`addOptional` | 通过其名称将一个对象添加到标签中。如果对象不存在,则加载时将跳过该对象。 +`addTag` | 通过其标签键将一个标签添加到标签中。内标签中的所有元素现在都是外标签的一部分。 +`addOptionalTag` | 通过其名称将一个标签添加到标签中。如果标签不存在,则加载时将跳过该标签。 +`replace` | 当为`true`时,此标签从其他数据包添加的所有先前加载的条目将被丢弃。如果在此之后加载了一个数据包,那么它仍然会将条目追加到标签中。 +`remove` | 通过其名称或键从标签中移除一个对象或标签。 ```java -// In some TagProvider#addTags +// 在某个TagProvider#addTags中 this.tag(EXAMPLE_TAG) - .add(EXAMPLE_OBJECT) // Adds an object to the tag - .addOptional(new ResourceLocation("othermod", "other_object")) // Adds an object from another mod to the tag + .add(EXAMPLE_OBJECT) // 将一个对象添加到标签中 + .addOptional(new ResourceLocation("othermod", "other_object")) // 向标签添加来自其他mod的对象 this.tag(EXAMPLE_TAG_2) - .addTag(EXAMPLE_TAG) // Adds a tag to the tag - .remove(EXAMPLE_OBJECT) // Removes an object from this tag + .addTag(EXAMPLE_TAG) // 将一个标签添加到另一个标签 + .remove(EXAMPLE_OBJECT) // 从此标签中移除一个对象 ``` :::important -If the mod's tags softly depends on another mod's tags (the other mod may or may not be present at runtime), the other mods' tags should be referenced using the optional methods. +如果你的mod的标签对其他mod的标签有软依赖(也就是说,其他mod可能在运行时不存在),那么你应该使用可选的方法来引用其他mod的标签。 ::: -### Existing Providers +### 现有的供应商 -Minecraft contains a few tag providers for certain registries that can be subclassed instead. Additionally, some providers contain additional helper methods to more easily create tags. +Minecraft中包含了一些可以被子类化的特定注册表的标签供应商。另外,一些供应商包含了额外的辅助方法,可以更容易地创建标签。 -Registry Object Type | Tag Provider +注册对象类型 | 标签供应商 :---: | :--- -`Block` | `BlockTagsProvider`\* -`Item` | `ItemTagsProvider` -`EntityType` | `EntityTypeTagsProvider` -`Fluid` | `FluidTagsProvider` -`GameEvent` | `GameEventTagsProvider` -`Biome` | `BiomeTagsProvider` -`FlatLevelGeneratorPreset` | `FlatLevelGeneratorPresetTagsProvider` -`WorldPreset` | `WorldPresetTagsProvider` -`Structure` | `StructureTagsProvider` -`PoiType` | `PoiTypeTagsProvider` -`BannerPattern` | `BannerPatternTagsProvider` -`CatVariant` | `CatVariantTagsProvider` -`PaintingVariant` | `PaintingVariantTagsProvider` -`Instrument` | `InstrumentTagsProvider` -`DamageType` | `DamageTypeTagsProvider` - -\* `BlockTagsProvider` is a Forge added `TagsProvider`. +`方块` | `BlockTagsProvider`\* +`物品` | `ItemTagsProvider` +`实体类型` | `EntityTypeTagsProvider` +`流体` | `FluidTagsProvider` +`游戏事件` | `GameEventTagsProvider` +`生物群系` | `BiomeTagsProvider` +`平面世界生成预设` | `FlatLevelGeneratorPresetTagsProvider` +`世界预设` | `WorldPresetTagsProvider` +`结构` | `StructureTagsProvider` +`兴趣点类型` | `PoiTypeTagsProvider` +`旗帜图案` | `BannerPatternTagsProvider` +`猫的品种` | `CatVariantTagsProvider` +`画的种类` | `PaintingVariantTagsProvider` +`乐器` | `InstrumentTagsProvider` +`伤害类型` | `DamageTypeTagsProvider` + +\* `BlockTagsProvider` 是Forge所添加的 `TagsProvider`。 #### `ItemTagsProvider#copy` -Blocks have item representations to obtain them in the inventory. As such, many of the block tags can also be an item tag. To easily generate item tags to have the same entries as block tags, the `#copy` method can be used which takes in the block tag to copy from and the item tag to copy to. +方块在物品栏中有物品形式的表示。因此,许多的方块标签也可以是物品标签。为了方便地生成与方块标签具有相同条目的物品标签,可以使用 `#copy` 方法,此方法接收要复制的方块标签和要复制到的物品标签。 ```java -//In ItemTagsProvider#addTags +// 在 ItemTagsProvider#addTags 中 this.copy(EXAMPLE_BLOCK_TAG, EXAMPLE_ITEM_TAG); ``` -Custom Tag Providers +自定义标签提供者 -------------------- -A custom tag provider can be created via a `TagsProvider` subclass which takes in the registry key to generate tags for. +通过 `TagsProvider` 的子类,可以创建自定义的标签提供者,这需要传入注册键以生成标签。 ```java public RecipeTypeTagsProvider(PackOutput output, CompletableFuture registries, ExistingFileHelper fileHelper) { @@ -100,12 +100,12 @@ public RecipeTypeTagsProvider(PackOutput output, CompletableFuture registries, ExistingFileHelper fileHelper) { super( output, From d2cefb41ae6d9ac9e36be2159d616ed37c20b27f Mon Sep 17 00:00:00 2001 From: DaZiDian <113085496+DaZiDian@users.noreply.github.com> Date: Thu, 25 Apr 2024 16:17:06 +0800 Subject: [PATCH 36/87] Update recipes.md --- docs/datagen/recipes.md | 140 ++++++++++++++++++++-------------------- 1 file changed, 69 insertions(+), 71 deletions(-) diff --git a/docs/datagen/recipes.md b/docs/datagen/recipes.md index e4246eee6..0abd31642 100644 --- a/docs/datagen/recipes.md +++ b/docs/datagen/recipes.md @@ -1,16 +1,16 @@ -Recipe Generation +配方生成 ================= -Recipes can be generated for a mod by subclassing `RecipeProvider` and implementing `#buildRecipes`. A recipe is supplied for data generation once a `FinishedRecipe` view is accepted by the consumer. `FinishedRecipe`s can either be created and supplied manually or, for convenience, created using a `RecipeBuilder`. +可以通过继承 `RecipeProvider` 并实现 `#buildRecipes` 来为模组生成配方。一旦消费者接受了 `FinishedRecipe` 视图,就会为数据生成提供配方。 `FinishedRecipe` 可以手动创建和提供,或者为方便起见,使用 `RecipeBuilder` 创建。 -After implementation, the provider must be [added][datagen] to the `DataGenerator`. +实现后,必须将提供者[添加][datagen]到 `DataGenerator`。 ```java -// On the MOD event bus +// 在MOD事件总线上 @SubscribeEvent public void gatherData(GatherDataEvent event) { event.getGenerator().addProvider( - // Tell generator to run only when server data are generating + // 告诉生成器只在服务器数据生成时运行 event.includeServer(), MyRecipeProvider::new ); @@ -20,157 +20,155 @@ public void gatherData(GatherDataEvent event) { `RecipeBuilder` --------------- -`RecipeBuilder` is a convenience implementation for creating `FinishedRecipe`s to generate. It provides basic definitions for unlocking, grouping, saving, and getting the result of a recipe. This is done through `#unlockedBy`, `#group`, `#save`, and `#getResult` respectively. +`RecipeBuilder` 是创建 `FinishedRecipe` 以便于生成的便利实现。它提供了解锁、分组、保存和获取配方结果的基本定义。分别通过 `#unlockedBy`、 `#group`、 `#save` 和 `#getResult` 实现。 :::important -[`ItemStack` outputs][stack] in recipes are not supported within vanilla recipe builders. A `FinishedRecipe` must be built in a different manner for existing vanilla recipe serializers to generate this data. +在原生配方构建器中不支持配方的 [`ItemStack` 输出][stack]。必须以不同的方式构建 `FinishedRecipe`,以便现有的原生配方序列化器可以生成这些数据。 ::: :::warning -The item results being generated must have a valid `RecipeCategory` specified; otherwise, a `NullPointerException` will be thrown. +正在生成的物品结果必须有一个有效的 `RecipeCategory` 指定,否则会抛出 `NullPointerException`。 ::: -All recipe builders except for [`SpecialRecipeBuilder`] require an advancement criteria to be specified. All recipes generate a criteria unlocking the recipe if the player has used the recipe previously. However, an additional criteria must be specified that allows the player to obtain the recipe without any prior knowledge. If any of the criteria specified is true, then the played will obtain the recipe for the recipe book. +除了 [`SpecialRecipeBuilder`],所有的配方构建器都需要指定一个进步条件。所有的配方都会生成一个条件,如果玩家之前使用过这个配方,就会解锁这个配方。然而,必须指定一个额外的条件,允许玩家在没有任何先验知识的情况下获取到配方。如果指定的任何一个条件为真,那么玩家就会在配方书中获取到配方。 :::tip -Recipe criteria commonly use `InventoryChangeTrigger` to unlock their recipe when certain items are present in the user's inventory. +配方条件通常使用 `InventoryChangeTrigger` 来在用户的库存中存在某些物品时解锁配方。 ::: ### ShapedRecipeBuilder -`ShapedRecipeBuilder` is used to generate shaped recipes. The builder can be initialized via `#shaped`. The recipe group, input symbol pattern, symbol definition of ingredients, and the recipe unlock criteria can be specified before saving. +`ShapedRecipeBuilder` 用于生成有形状的配方。构建器可以通过 `#shaped` 初始化。可以在保存之前指定配方组、输入符号模式、成分的符号定义和配方解锁条件。 ```java -// In RecipeProvider#buildRecipes(writer) +// 在 RecipeProvider#buildRecipes(writer) 中 ShapedRecipeBuilder builder = ShapedRecipeBuilder.shaped(RecipeCategory.MISC, result) - .pattern("a a") // Create recipe pattern - .define('a', item) // Define what the symbol represents - .unlockedBy("criteria", criteria) // How the recipe is unlocked - .save(writer); // Add data to builder + .pattern("a a") // 创建配方模式 + .define('a', item) // 定义符号代表的物品 + .unlockedBy("criteria", criteria) // 配方的解锁方式 + .save(writer); // 将数据添加到构建器中 ``` -#### Additional Validation Checks +#### 额外的验证检查 -Shaped recipes have some additional validation checks performed before building: +在构建之前,有形状的配方会进行一些额外的验证检查: -* A pattern must be defined and take in more than one item. -* All pattern rows must be the same width. -* A symbol cannot be defined more than once. -* The space character (`' '`) is reserved for representing no item in a slot and, as such, cannot be defined. -* A pattern must use all symbols defined by the user. +* 必须定义一个模式,并且需要输入超过一个物品。 +* 所有模式行的宽度必须相同。 +* 一个符号不能被定义多次。 +* 空格字符 (`' '`) 是为了表示插槽中没有物品而保留的,因此不能被定义。 +* 模式必须使用用户定义的所有符号。 ### ShapelessRecipeBuilder -`ShapelessRecipeBuilder` is used to generate shapeless recipes. The builder can be initialized via `#shapeless`. The recipe group, input ingredients, and the recipe unlock criteria can be specified before saving. +`ShapelessRecipeBuilder` 用于生成无形状的配方。可以通过 `#shapeless` 初始化构建器。在保存之前,可以指定配方组、输入成分和配方解锁条件。 ```java -// In RecipeProvider#buildRecipes(writer) +// 在 RecipeProvider#buildRecipes(writer) 中 ShapelessRecipeBuilder builder = ShapelessRecipeBuilder.shapeless(RecipeCategory.MISC, result) - .requires(item) // Add item to the recipe - .unlockedBy("criteria", criteria) // How the recipe is unlocked - .save(writer); // Add data to builder + .requires(item) // 将物品添加到配方中 + .unlockedBy("criteria", criteria) // 配方的解锁方式 + .save(writer); // 将数据添加到构建器中 ``` ### SimpleCookingRecipeBuilder -`SimpleCookingRecipeBuilder` is used to generate smelting, blasting, smoking, and campfire cooking recipes. Additionally, custom cooking recipes using the `SimpleCookingSerializer` can also be data generated using this builder. The builder can be initialized via `#smelting`, `#blasting`, `#smoking`, `#campfireCooking`, or `#cooking` respectively. The recipe group and the recipe unlock criteria can be specified before saving. +`SimpleCookingRecipeBuilder` 用于生成熔炼、爆炸、烟熏和营火烹饪配方。此外,使用 `SimpleCookingSerializer` 的自定义烹饪配方也可以使用这个构建器进行数据生成。可以分别通过 `#smelting`、 `#blasting`、 `#smoking`、 `#campfireCooking` 或 `#cooking` 初始化构建器。在保存之前,可以指定配方组和配方解锁条件。 ```java -// In RecipeProvider#buildRecipes(writer) +// 在 RecipeProvider#buildRecipes(writer) 中 SimpleCookingRecipeBuilder builder = SimpleCookingRecipeBuilder.smelting(input, RecipeCategory.MISC, result, experience, cookingTime) - .unlockedBy("criteria", criteria) // How the recipe is unlocked - .save(writer); // Add data to builder + .unlockedBy("criteria", criteria) // 配方的解锁方式 + .save(writer); // 将数据添加到构建器中 ``` ### SingleItemRecipeBuilder -`SingleItemRecipeBuilder` is used to generate stonecutting recipes. Additionally, custom single item recipes using a serializer like `SingleItemRecipe$Serializer` can also be data generated using this builder. The builder can be initialized via `#stonecutting` or through the constructor respectively. The recipe group and the recipe unlock criteria can be specified before saving. +`SingleItemRecipeBuilder` 用于生成石切配方。此外,使用类似 `SingleItemRecipe$Serializer` 的序列化器的自定义单物品配方也可以使用这个构建器进行数据生成。可以通过 `#stonecutting` 或者构造函数分别初始化构建器。在保存之前,可以指定配方组和配方解锁条件。 ```java -// In RecipeProvider#buildRecipes(writer) +// 在 RecipeProvider#buildRecipes(writer) 中 SingleItemRecipeBuilder builder = SingleItemRecipeBuilder.stonecutting(input, RecipeCategory.MISC, result) - .unlockedBy("criteria", criteria) // How the recipe is unlocked - .save(writer); // Add data to builder + .unlockedBy("criteria", criteria) // 配方的解锁方式 + .save(writer); // 将数据添加到构建器中 ``` -Non-`RecipeBuilder` Builders +非 `RecipeBuilder` 构建器 ---------------------------- -Some recipe builders do not implement `RecipeBuilder` due to lacking features used by all previously mentioned recipes. +一些配方构建器由于缺少所有先前提到的配方使用的功能而不实现 `RecipeBuilder`。 ### SmithingTransformRecipeBuilder -`SmithingTransformRecipeBuilder` is used to generate smithing recipes which transform an item. Additionally, custom recipes using a serializer like `SmithingTransformRecipe$Serializer` can also be data generated using this builder. The builder can be initialized via `#smithing` or through the constructor respectively. The recipe unlock criteria can be specified before saving. +`SmithingTransformRecipeBuilder` 用于生成将物品转化的铁匠配方。此外,使用类似 `SmithingTransformRecipe$Serializer` 的序列化器的自定义配方也可以使用这个构建器进行数据生成。可以通过 `#smithing` 或者构造函数分别初始化构建器。在保存之前,可以指定配方解锁条件。 ```java -// In RecipeProvider#buildRecipes(writer) +// 在 RecipeProvider#buildRecipes(writer) 中 SmithingTransformRecipeBuilder builder = SmithingTransformRecipeBuilder.smithing(template, base, addition, RecipeCategory.MISC, result) - .unlocks("criteria", criteria) // How the recipe is unlocked - .save(writer, name); // Add data to builder + .unlocks("criteria", criteria) // 配方的解锁方式 + .save(writer, name); // 将数据添加到构建器中 ``` ### SmithingTrimRecipeBuilder -`SmithingTrimRecipeBuilder` is used to generate smithing recipes for armor trims. Additionally, custom upgrade recipes using a serializer like `SmithingTrimRecipe$Serializer` can also be data generated using this builder. The builder can be initialized via `#smithingTrim` or through the constructor respectively. The recipe unlock criteria can be specified before saving. +`SmithingTrimRecipeBuilder` 用于生成用于装甲修剪的铁匠配方。此外,使用类似 `SmithingTrimRecipe$Serializer` 的序列化器的自定义升级配方也可以使用这个构建器进行数据生成。可以通过 `#smithingTrim` 或者构造函数分别初始化构建器。在保存之前,可以指定配方解锁条件。 ```java -// In RecipeProvider#buildRecipes(writer) +// 在 RecipeProvider#buildRecipes(writer) 中 SmithingTrimRecipe builder = SmithingTrimRecipe.smithingTrim(template, base, addition, RecipeCategory.MISC) - .unlocks("criteria", criteria) // How the recipe is unlocked - .save(writer, name); // Add data to builder + .unlocks("criteria", criteria) // 配方的解锁方式 + .save(writer, name); // 将数据添加到构建器中 ``` ### SpecialRecipeBuilder -`SpecialRecipeBuilder` is used to generate empty JSONs for dynamic recipes that cannot easily be constrained to the recipe JSON format (dying armor, firework, etc.). The builder can be initialized via `#special`. +`SpecialRecipeBuilder` 用于为不能轻易约束到配方 JSON 格式(如染色护甲、焰火等)的动态配方生成空的 JSON。可以通过 `#special` 初始化构建器。 ```java -// In RecipeProvider#buildRecipes(writer) +// 在 RecipeProvider#buildRecipes(writer) 中 SpecialRecipeBuilder.special(dynamicRecipeSerializer) - .save(writer, name); // Add data to builder + .save(writer, name); // 将数据添加到构建器中 ``` - -Conditional Recipes +条件性配方 ------------------- -[Conditional recipes][conditional] can also be data generated via `ConditionalRecipe$Builder`. The builder can be obtained using `#builder`. +[条件性配方][conditional] 也可以通过 `ConditionalRecipe$Builder` 进行数据生成。可以使用 `#builder` 获取构建器。 -Conditions for each recipe can be specified by first calling `#addCondition` and then calling `#addRecipe` after all conditions have been specified. This process can be repeated as many times as the programmer would like. +可以先调用 `#addCondition` 来指定每个配方的条件,然后在所有条件都指定后调用 `#addRecipe`。这个过程可以重复多次,由程序员自行决定。 -After all recipes have been specified, advancements can be added for each recipe at the end using `#generateAdvancement`. Alternatively, the conditional advancement can be set using `#setAdvancement`. +在所有配方都指定之后,可以在最后使用 `#generateAdvancement` 为每个配方添加进步(advancements)。或者,可以使用 `#setAdvancement` 设置条件性进步。 ```java -// In RecipeProvider#buildRecipes(writer) +// 在 RecipeProvider#buildRecipes(writer) 中 ConditionalRecipe.builder() - // Add the conditions for the recipe + // 添加配方的条件 .addCondition(...) - // Add recipe to return when conditions are true + // 当条件为真时返回配方 .addRecipe(...) - // Add the next conditions for the next recipe + // 添加下一个配方的条件 .addCondition(...) - // Add next recipe to return when the next conditions are true + // 当下一个条件为真时返回下一个配方 .addRecipe(...) - // Create conditional advancement which uses the conditions - // and unlocking advancement in the recipes above + // 创建使用上述配方中的条件和解锁进步的条件性进步 .generateAdvancement() .build(writer, name); ``` ### IConditionBuilder -To simplify adding conditions to conditional recipes without having to construct the instances of each condition instance manually, the extended `RecipeProvider` can implement `IConditionBuilder`. The interface adds methods to easily construct condition instances. +为了简化向条件性配方添加条件,而不必手动构造每个条件实例,扩展的 `RecipeProvider` 可以实现 `IConditionBuilder`。接口添加了轻松构造条件实例的方法。 ```java -// In ConditionalRecipe$Builder#addCondition +// 在 ConditionalRecipe$Builder#addCondition 中 ( - // If either 'examplemod:example_item' - // OR 'examplemod:example_item2' exists - // AND - // NOT FALSE + // 如果 'examplemod:example_item' + // 或者 'examplemod:example_item2' 存在 + // 并且 + // 非FALSE - // Methods are defined by IConditionBuilder + // 方法由 IConditionBuilder 定义 and( or( itemExists("examplemod", "example_item"), @@ -183,13 +181,13 @@ To simplify adding conditions to conditional recipes without having to construct ) ``` -Custom Recipe Serializers +自定义配方序列化器 ------------------------- -Custom recipe serializers can be data generated by creating a builder that can construct a `FinishedRecipe`. The finished recipe encodes the recipe data and its unlocking advancement, when present, to JSON. Additionally, the name and serializer of the recipe is also specified to know where to write to and what can decode the object when loading. Once a `FinishedRecipe` is constructed, it simply needs to be passed to the `Consumer` supplied by `RecipeProvider#buildRecipes`. +通过创建一个可以构造 `FinishedRecipe` 的构建器,可以数据生成自定义配方序列化器。完成的配方将配方数据编码为JSON,并在存在时将其解锁进步也编码为JSON。此外,还需要指定配方的名称和序列化器,以知道写入位置以及加载时能解码对象的内容。一旦构造了 `FinishedRecipe`,只需将其传递给 `RecipeProvider#buildRecipes` 提供的 `Consumer`。 :::tip -`FinishedRecipe`s are flexible enough that any object transformation can be data generated, not just items. +`FinishedRecipe` 足够灵活,可以数据生成任何对象转换,不仅仅是物品。 ::: [datagen]: ../index.md#data-providers From 05b23718fa27162dcd2ecb247c0b22877448e1e6 Mon Sep 17 00:00:00 2001 From: DaZiDian <113085496+DaZiDian@users.noreply.github.com> Date: Thu, 25 Apr 2024 16:27:04 +0800 Subject: [PATCH 37/87] Update neo_maps.md --- docs/datamaps/neo_maps.md | 47 +++++++++++++++++++++++++-------------- 1 file changed, 30 insertions(+), 17 deletions(-) diff --git a/docs/datamaps/neo_maps.md b/docs/datamaps/neo_maps.md index 5a709c22c..988278dab 100644 --- a/docs/datamaps/neo_maps.md +++ b/docs/datamaps/neo_maps.md @@ -1,10 +1,13 @@ -# Built-in Data Maps -NeoForge provides a few data maps that mostly replace hardcoded in-code vanilla maps. -These data maps can be found in `NeoForgeDataMaps`, and are always *optional* to ensure compatibility with vanilla clients. +# 内置数据映射 + +NeoForge 提供了一些数据映射,这些映射主要取代硬编码的代码内普通映射。 +这些数据映射可以在 `NeoForgeDataMaps` 中找到,并且始终是*可选的*以确保与普通客户端的兼容性。 ## `neoforge:compostables` -NeoForge provides a data map that allows configuring composter values, as a replacement for `ComposterBlock#COMPOSTABLES` (which is now ignored). -This data map is located at `neoforge/data_maps/item/compostables.json` and its objects have the following structure: + +NeoForge 提供了一个允许配置 Composter 值的数据映射,作为`ComposterBlock#COMPOSTABLES`(现在已被忽略)的替代品。 +该数据映射位于`neoforge/data_maps/item/compostables.json`,其对象具有以下结构: + ```js { // A 0 to 1 (inclusive) float representing the chance that the item will update the level of the composter @@ -13,6 +16,7 @@ This data map is located at `neoforge/data_maps/item/compostables.json` and its ``` Example: + ```js { "values": { @@ -25,8 +29,10 @@ Example: ``` ## `neoforge:furnace_fuels` -NeoForge provides a data map that allows configuring item burn times. -This data map is located at `neoforge/data_maps/item/furnace_fuels.json` and its objects have the following structure: + +NeoForge 提供了一个数据映射,允许配置物品的燃烧时间。 +该数据图位于`neoforge/data_maps/item/furnace_fuels.json`,其对象具有以下结构: + ```js { // A positive integer representing the item's burn time, in ticks @@ -35,6 +41,7 @@ This data map is located at `neoforge/data_maps/item/furnace_fuels.json` and its ``` Example: + ```js { "values": { @@ -47,17 +54,19 @@ Example: ``` :::note -Other in-code methods like `IItemExtension#getBurnTime` will take priority over the data map, so it is recommended that you use the data map for simple, static burn times even in your mod so that users can configure them. +其他像 `IItemExtension#getBurnTime` 这样的内部代码方法将优先于数据映射,所以建议您在您的mod中使用数据映射来设置简单、静态的燃烧时间,这样用户可以按照他们的需求进行配置。 ::: :::warning -Vanilla adds a burn time to logs and planks in the `minecraft:logs` and `minecraft:planks` tag. However, those tags also contain Nether wood, so a removal for elements in `#minecraft:non_flammable_wood` is added. -However, the removal does not affect any values added by other packs or mods, so if you want to change the values for the wood tags you will need to add a removal for the non flammable tag yourself. + +原版游戏为 `minecraft:logs` 和 `minecraft:planks` 标签中的原木和木板添加了燃烧时间。但是,这些标签也包含了下界木材,因此添加了对 `#minecraft:non_flammable_wood` 中元素的移除。 然而,这种移除不会影响其他数据包或模组添加的任何值,所以如果您想改变木材标签的值,您需要自己添加对不可燃木材标签的移除。 + ::: ## `neoforge:parrot_imitations` -NeoForge provides a data map that allows configuring the sounds produced by parrots when they want to imitate a mob, as a replacement for `Parrot#MOB_SOUND_MAP` (which is now ignored). -This data map is located at `neoforge/data_maps/entity_type/parrot_imitations.json` and its objects have the following structure: + +NeoForge 提供了一个数据映射,允许配置鹦鹉在模仿怪物时产生的声音,这可以替代 `Parrot#MOB_SOUND_MAP`(现在已被忽视)。这个数据映射位于 `neoforge/data_maps/entity_type/parrot_imitations.json`,其对象具有以下结构: + ```js { // The ID of the sound that parrots will produce when imitating the mob @@ -66,6 +75,7 @@ This data map is located at `neoforge/data_maps/entity_type/parrot_imitations.js ``` Example: + ```js { "values": { @@ -78,8 +88,8 @@ Example: ``` ## `neoforge:raid_hero_gifts` -NeoForge provides a data map that allows configuring the gift that a villager with a certain `VillagerProfession` may gift you if you stop the raid, as a replacement for `GiveGiftToHero#GIFTS` (which is now ignored) -This data map is located at `neoforge/data_maps/villager_profession/raid_hero_gifts.json` and its objects have the following structure: + +NeoForge 提供了一个数据映射,允许配置如果你阻止了突袭,具有某个 `VillagerProfession` 的村民可能会赠送给你的礼物,这将替代 `GiveGiftToHero#GIFTS`(现在已经被忽略了)。这个数据映射位于 `neoforge/data_maps/villager_profession/raid_hero_gifts.json`,其对象具有以下结构: ```js { @@ -93,8 +103,9 @@ This data map is located at `neoforge/data_maps/villager_profession/raid_hero_gi ``` ## `neoforge:vibration_frequencies` -NeoForge provides a data map that allows configuring the shulker vibration frequencies emitted by game events, as a replacement for `VibrationSystem#VIBRATION_FREQUENCY_FOR_EVENT` (which is now ignored). -This data map is located at `neoforge/data_maps/game_event/vibration_frequencies.json` and its objects have the following structure: + +NeoForge 提供了一个数据映射,允许配置游戏事件发出的潜影贝探头频率,这将替代 `VibrationSystem#VIBRATION_FREQUENCY_FOR_EVENT`(现在已经被忽视了)。这个数据映射位于 `neoforge/data_maps/game_event/vibration_frequencies.json`,其对象具有以下结构: + ```js { // An integer between 1 and 15 (inclusive) that indicates the vibration frequency of the event @@ -103,6 +114,7 @@ This data map is located at `neoforge/data_maps/game_event/vibration_frequencies ``` Example: + ```js { "values": { @@ -112,4 +124,5 @@ Example: } } } -``` \ No newline at end of file +``` + From 2332af700d3bbb3166611423b5aa6b697fb5f86a Mon Sep 17 00:00:00 2001 From: DaZiDian <113085496+DaZiDian@users.noreply.github.com> Date: Thu, 25 Apr 2024 16:30:24 +0800 Subject: [PATCH 38/87] Update structure.md --- docs/datamaps/structure.md | 74 +++++++++++++++++++------------------- 1 file changed, 37 insertions(+), 37 deletions(-) diff --git a/docs/datamaps/structure.md b/docs/datamaps/structure.md index 431b6a4c0..36bb29e69 100644 --- a/docs/datamaps/structure.md +++ b/docs/datamaps/structure.md @@ -1,41 +1,41 @@ -# JSON Structure -For the purposes of this page, we will use a data map which is an object with two float keys: `amount` and `chance` as an example. The codec for that object can be found [here](./index.md#registration). +# JSON结构 +对于此页面,我们将使用一个数据映射作为例子,其对象具有两个浮点键:`amount` 和 `chance`。该对象的编码可以在[这里](./index.md#registration)找到。 -## Location -Data maps are loaded from a JSON file located at `mapNamespace/data_maps/registryNamespace/registryPath/mapPath.json`, where: -- `mapNamespace` is the namespace of the ID of the data map -- `mapPath` is the path of the ID of the data map -- `registryNamespace` is the namespace of the ID of the registry -- `registryPath` is the path of the ID of the registry +## 地址 +数据映射加载自位于 `mapNamespace/data_maps/registryNamespace/registryPath/mapPath.json` 的JSON文件,其中: +- `mapNamespace` 是数据映射ID的命名空间 +- `mapPath` 是数据映射ID的路径 +- `registryNamespace` 是注册表ID的命名空间 +- `registryPath` 是注册表ID的路径 :::note -The registry namespace is ommited if it is `minecraft`. +如果是 `minecraft`,则省略注册表命名空间。 ::: -Examples: -- For a data map named `mymod:drop_healing` for the `minecraft:item` registry (as in the example), the path will be `mymod/data_maps/item/drop_healing.json`. -- For a data map named `somemod:somemap` for the `minecraft:block` registry, the path will be `somemod/data_maps/block/somemap.json`. -- For a data map named `example:stuff` for the `somemod:custom` registry, the path will be `example/data_maps/somemod/custom/stuff.json`. +示例: +- 对于名为 `mymod:drop_healing` 的数据映射,用于 `minecraft:item` 注册表(如示例中),路径将是 `mymod/data_maps/item/drop_healing.json`。 +- 对于名为 `somemod:somemap` 的数据映射,用于 `minecraft:block` 注册表,路径将是 `somemod/data_maps/block/somemap.json`。 +- 对于名为 `example:stuff` 的数据映射,用于 `somemod:custom` 注册表,路径将是 `example/data_maps/somemod/custom/stuff.json`。 -## Global `replace` field -The JSON file has an optional, global `replace` field, which is similar to tags, and when `true` will remove all previously attached values of that data map. This is useful for datapacks that want to completely change the entire data map. +## 全局 `replace` 字段 +JSON文件具有一个可选的全局 `replace` 字段,类似于标签,当其为 `true` 时,将移除该数据映射的所有先前附加值。这对于想要完全改变整个数据映射的数据包非常有用。 -## Loading conditions -Data map files support [loading conditions](../resources/server/conditional) both at root-level and at entry-level through a `neoforge:conditions` array. +## 加载条件 +数据映射文件支持在根级别和条目级别通过 `neoforge:conditions` 数组支持[加载条件](../resources/server/conditional)。 -## Adding values -Values can be attached to objects using the `values` map. Each key will represent either the ID of an individual registry entry to attach the value to, or a tag key, preceeded by `#`. If it is a tag, the same value will be attached to all entries in that tag. -The key will be the object to attach. +## 添加值 +可以使用 `values` 映射将值附加到对象。每个键将代表要附加值的单个注册表条目的ID,或者一个以 `#` 开头的标签键。如果是一个标签,那么相同的值将附加到该标签的所有条目上。 +键将是要附加的对象。 ```js { "values": { - // Attach a value to the carrot item + // 为胡萝卜项附加一个值 "minecraft:carrot": { "amount": 12, "chance": 1 }, - // Attach a value to all items in the logs tag + // 将一个值附加到 logs 标签的所有项上 "#minecraft:logs": { "amount": 1, "chance": 0.1 @@ -45,15 +45,15 @@ The key will be the object to attach. ``` :::info -The above structure will invoke mergers in the case of [advanced data maps](./index.md#advanced-data-maps). If you do not want to invoke the merger for a specific object, then you will have to use a structure similar to this one: +上述结构将在[高级数据映射](./index.md#advanced-data-maps)的情况下调用合并器。如果你不想为特定的对象调用合并器,那么你将不得不使用类似于这样的结构: ```js { "values": { - // Overwrite the value of the carrot item + // 覆盖胡萝卜项的值 "minecraft:carrot": { - // highlight-next-line + // 高亮下一行 "replace": true, - // The new value will be under a value sub-object + // 新的值将在 value 子对象下 "value": { "amount": 12, "chance": 1 @@ -64,40 +64,40 @@ The above structure will invoke mergers in the case of [advanced data maps](./in ``` ::: -## Removing values +## 移除值 -A JSON file can also remove values previously attached to objects, through the use of the `remove` array: +JSON文件也可以通过使用 `remove` 数组,从对象中移除先前附加的值: ```js { - // Remove the value attached to apples and potatoes + // 移除附加到苹果和土豆的值 "remove": ["minecraft:apple", "minecraft:potato"] } ``` -The array contains a list of registry entry IDs or tags to remove the value from. +数组包含一系列要从其中移除值的注册表条目ID或标签。 :::warning -Removals happen after the values in the current JSON file have been attached, so you can use the removal feature to remove a value attached to an object through a tag: +移除操作在当前JSON文件的值被附加后进行,所以你可以使用移除功能来移除通过标签附加到对象的值: ```js { "values": { "#minecraft:logs": 12 }, - // Remove the value from the acacia log, so that all logs but acacia have the value 12 attached to them + // 从金合欢原木移除值,这样所有原木除了金合欢都将附加值 12 "remove": ["minecraft:acacia_log"] } ``` ::: :::info -In the case of [advanced data maps](./index.md#advanced-data-maps) that provide a custom remover, the arguments of the remover can be provided by transforming the `remove` array into a map. -Let's assume that the remover object is serialized as a string and removes the value with a given key for a `Map`-based data map: +在提供自定义移除器的[高级数据映射](./index.md#advanced-data-maps)的情况下,可以通过将 `remove` 数组转换为映射来提供移除器的参数。 +假设移除器对象被串行化为字符串,并且为基于 `Map` 的数据映射移除具有给定键的值: ```js { "remove": { - // The remover will be deserialized from the value (`somekey1` in this case) - // and applied to the value attached to the carrot item + // 移除器将从值(这种情况下为 `somekey1`)反串行化 + // 并应用于附加到胡萝卜项的值 "minecraft:carrot": "somekey1" } } ``` -::: \ No newline at end of file +::: From dc1a6325bdebf07a2649a8ec987657324404b34b Mon Sep 17 00:00:00 2001 From: DaZiDian <113085496+DaZiDian@users.noreply.github.com> Date: Thu, 25 Apr 2024 18:54:56 +0800 Subject: [PATCH 39/87] Update index.md --- docs/datamaps/index.md | 148 ++++++++++++++++++++--------------------- 1 file changed, 74 insertions(+), 74 deletions(-) diff --git a/docs/datamaps/index.md b/docs/datamaps/index.md index 3886d4ff6..7bfc2089a 100644 --- a/docs/datamaps/index.md +++ b/docs/datamaps/index.md @@ -1,24 +1,24 @@ -# Data Maps +# 数据映射 -A registry data map contains data-driven, reloadable objects that can be attached to a registry object. -This system allows more easily data-driving game behaviour, as they provide functionality such as syncing or conflict resolution, leading to a better and more configurable user experience. +注册表数据映射包含可附加到注册表对象的数据驱动、可重载的对象。 +这个系统允许更容易地数据驱动游戏行为,因为它们提供了如同步或冲突解决等功能,从而带来更好、更可配置的用户体验。 -You can think of tags as registry object ➜ boolean maps, while data maps are more flexible registry object ➜ object maps. +你可以将标签看作注册表对象 ➜ 布尔映射,而数据映射则是更灵活的注册表对象 ➜ 对象映射。 -A data map can be attached to both static, built-in, registries and dynamic data-driven datapack registries. +数据映射可以附加到静态的内置注册表和动态的数据驱动的数据包注册表上。 -Data maps support reloading through the use of the `/reload` command or any other means that reload server resources. +数据映射支持通过使用 `/reload` 命令或任何其他重新加载服务器资源的方法来重新加载。 -## Registration -A data map type should be statically created and then registered to the `RegisterDataMapTypesEvent` (which is fired on the [mod event bus](../concepts/events)). The `DataMapType` can be created using a `DataMapType$Builder`, through `DataMapType#builder`. +## 注册 +数据映射类型应该静态创建,然后注册到 `RegisterDataMapTypesEvent`(在 [mod事件总线](../concepts/events)上触发)。`DataMapType` 可以使用 `DataMapType$Builder` 通过 `DataMapType#builder` 创建。 -The builder provides a `synced` method which can be used to mark a data map as synced and have it sent to clients. +构建器提供了一个 `synced` 方法,可以用来标记数据映射为同步并将其发送给客户端。 -A simple `DataMapType` has two generic arguments: `R` (the type of the registry the data map is for) and `T` (the values that are being attached). A data map of `SomeObject`s that are attached to `Item`s can, as such, be represented as `DataMapType`. +一个简单的 `DataMapType` 有两个泛型参数:`R`(数据映射所针对的注册表的类型)和 `T`(被附加的值)。因此,可以将附加到 `Item` 的 `SomeObject` 的数据映射表示为 `DataMapType`。 -Data maps are serialized and deserialized using [Codecs](../datastorage/codecs.md). +数据映射使用 [编解码器](../datastorage/codecs.md) 进行序列化和反序列化。 -Let's take the following record representing the data map value as an example: +以以下表示数据映射值的记录为例: ```java public record DropHealing( float amount, float chance @@ -31,54 +31,54 @@ public record DropHealing( ``` :::warning -The value (`T`) should be an *immutable* object, as otherwise weird behaviour can be caused if the object is attached to all entries within a tag (since no copy is created). +值 (`T`) 应为 *不可变* 对象,否则如果对象附加到标签内的所有条目,则可能导致奇怪的行为(因为不会创建副本)。 ::: -For the purposes of this example, we will use this data map to heal players when they drop an item. -The `DataMapType` can be created as such: +为了本例的目的,我们将使用此数据映射在玩家丢弃物品时治疗玩家。 +`DataMapType` 可以这样创建: ```java public static final DataMapType DROP_HEALING = DataMapType.builder( new ResourceLocation("mymod:drop_healing"), Registries.ITEM, DropHealing.CODEC ).build(); ``` -and then registered to the `RegisterDataMapTypesEvent` using `RegisterDataMapTypesEvent#register`. +然后使用 `RegisterDataMapTypesEvent#register` 注册到 `RegisterDataMapTypesEvent`。 -## Syncing -A synced data map will have its values synced to clients. A data map can be marked as synced using `DataMapType$Builder#synced(Codec networkCodec, boolean mandatory)`. -The values of the data map will then be synced using the `networkCodec`. -If the `mandatory` flag is set to `true`, clients that do not support the data map (including Vanilla clients) will not be able to connect to the server, nor vice-versa. A non-mandatory data map on the other hand is optional, so it will not prevent any clients from joining. +## 同步 +同步的数据映射将会将其值同步到客户端。可以使用 `DataMapType$Builder#synced(Codec networkCodec, boolean mandatory)` 标记数据映射为同步。 +然后将使用 `networkCodec` 同步数据映射的值。 +如果 `mandatory` 标志设置为 `true`,则不支持数据映射的客户端(包括 Vanilla 客户端)将无法连接到服务器,反之亦然。另一方面,非强制性的数据映射是可选的,因此它不会阻止任何客户端加入。 :::tip -A separate network codec allows for packet sizes to be smaller, as you can choose what data to send, and in what format. Otherwise the default codec can be used. +单独的网络编解码器允许包大小更小,因为你可以选择发送哪些数据以及以什么格式发送。否则可以使用默认编解码器。 ::: -## JSON Structure and location -Data maps are loaded from a JSON file located at `mapNamespace/data_maps/registryNamespace/registryPath/mapPath.json`, where: -- `mapNamespace` is the namespace of the ID of the data map -- `mapPath` is the path of the ID of the data map -- `registryNamespace` is the namespace of the ID of the registry; if the namespace is `minecraft`, this value will be omitted -- `registryPath` is the path of the ID of the registry +## JSON结构和位置 +数据映射从位于 `mapNamespace/data_maps/registryNamespace/registryPath/mapPath.json` 的JSON文件加载,其中: +- `mapNamespace` 是数据映射ID的命名空间 +- `mapPath` 是数据映射ID的路径 +- `registryNamespace` 是注册表ID的命名空间;如果命名空间是 `minecraft`,则此值将被省略 +- `registryPath` 是注册表ID的路径 -For more information, please [check out the dedicated page](./structure.md). +更多信息,请[查看专用页面](./structure.md)。 -## Usage -As data maps can be used on any registry, they can be queried through `Holder`s, and not through the actual registry objects. -You can query a data map value using `Holder#getData(DataMapType)`. If that object doesn't have a value attached, the method will return `null`. +## 使用 +由于数据映射可以用于任何注册表,因此可以通过 `Holder` 查询它们,而不是通过实际的注册表对象。 +你可以使用 `Holder#getData(DataMapType)` 查询数据映射值。如果该对象没有附加值,方法将返回 `null`。 :::note -Only reference holders will return a value in that method. `Direct` holders will **not**. Generally, you will only encounter reference holders (which are returned by methods such as `Registry#wrapAsHolder`, `Registry#getHolder` or the different `builtInRegistryHolder` methods). +只有引用持有者会在该方法中返回值。`直接` 持有者 **不会**。通常,你只会遇到引用持有者(它们由 `Registry#wrapAsHolder`、`Registry#getHolder` 或不同的 `builtInRegistryHolder` 方法返回)。 ::: -To continue the example above, we can implement our intended behaviour as follows: +为了继续上面的示例,我们可以如下实现我们预期的行为: ```java public static void onItemDrop(final ItemTossEvent event) { final ItemStack stack = event.getEntity().getItem(); - // ItemStack has a getItemHolder method that will return a Holder which points to the item the stack is of - //highlight-next-line + // ItemStack 有一个 getItemHolder 方法,它将返回一个指向堆叠物的物品的 Holder + //高亮下一行 final DropHealing value = stack.getItemHolder().getData(DROP_HEALING); - // Since getData returns null if the item will not have a drop healing value attached, we guard against it being null + // 由于 getData 如果物品没有附加 drop healing 值将返回 null,我们保护它不为 null if (value != null) { - // And here we simply use the values + // 这里我们简单地使用值 if (event.getPlayer().level().getRandom().nextFloat() > value.chance()) { event.getPlayer().heal(value.amount()); } @@ -86,43 +86,43 @@ public static void onItemDrop(final ItemTossEvent event) { } ``` -## Advanced data maps -Advanced data maps are data maps which have additional functionality. Namely, the ability of merging values and selectively removing them, through a remover. Implementing some form of merging and removers is highly recommended for data maps whose values are collection-likes (like `Map`s or `List`s). +## 高级数据映射 +高级数据映射是具有额外功能的数据映射。即,通过移除器合并值和选择性地移除它们的能力。对于值类似于集合(如 `Map` 或 `List`)的数据映射,强烈推荐实现某种形式的合并和移除器。 -`AdvancedDataMapType` have one more generic besides `T` and `R`: `VR extends DataMapValueRemover`. This additional generic allows you to datagen remove objects with increased type safety. +`AdvancedDataMapType` 除了 `T` 和 `R` 之外还有一个泛型:`VR extends DataMapValueRemover`。这个额外的泛型允许你通过提高类型安全性来生成移除对象。 -### Creation -You create an `AdvancedDataMapType` using `AdvancedDataMapType#builder`. Unlike the normal builder, the builder returned by that method will have two more methods (`merger` and `remover`), and it will return an `AdvancedDataMapType`. Registration methods remain the same. +### 创建 +你可以使用 `AdvancedDataMapType#builder` 创建 `AdvancedDataMapType`。与普通构建器不同,该方法返回的构建器将有两个额外的方法(`merger` 和 `remover`),并且它将返回一个 `AdvancedDataMapType`。注册方法保持不变。 -### Mergers -An advanced data map can provide a `DataMapValueMerger` through `AdvancedDataMapType#merger`. This merger will be used to handle conflicts between data packs that attempt to attach a value to the same object. -The merger will be given the two conflicting values, and their sources (as an `Either, ResourceKey>` since values can be attached to all entries within a tag, not just individual entries), and is expected to return the value that will actually be attached. -Generally, mergers should simply merge the values, and should not perform "hard" overwrites unless necessary (i.e. if merging isn't possible). If a pack wants to bypass the merger, it can do so by specifying the object-level `replace` field. +### 合并器 +高级数据映射可以通过 `AdvancedDataMapType#merger` 提供一个 `DataMapValueMerger`。这个合并器将用于处理尝试将值附加到同一对象的数据包之间的冲突。 +合并器将给出两个冲突的值及其来源(作为 `Either, ResourceKey>`,因为值可以附加到标签内的所有条目,而不仅仅是个别条目),并期望返回实际附加的值。 +通常,合并器应简单地合并值,并且除非必要(即如果合并不可能),否则不应执行“硬”覆盖。如果一个包想要绕过合并器,它可以通过指定对象级别的 `replace` 字段来实现。 -Let's imagine a scenario where we have a data map that attaches integers to items: +假设我们有一个将整数附加到物品的数据映射的情况: ```java public class IntMerger implements DataMapValueMerger { @Override public Integer merge(Registry registry, Either, ResourceKey> first, Integer firstValue, Either, ResourceKey> second, Integer secondValue) { - //highlight-next-line + //高亮下一行 return firstValue + secondValue; } } ``` -The above merger will merge the values if two datapacks attach to the same object. So if the first pack attaches the value `12` to `minecraft:carrot`, and the second pack attaches the value `15` to `minecraft:carrot`, the final value will be `27`. However, if the second pack specifies the object-level `replace` field, the final value will be `15` as the merger won't be invoked. +如果两个数据包附加到同一对象,则上述合并器将合并值。所以如果第一个包将值 `12` 附加到 `minecraft:carrot`,第二个包将值 `15` 附加到 `minecraft:carrot`,最终的值将是 `27`。然而,如果第二个包指定对象级别的 `replace` 字段,最终值将是 `15`,因为不会调用合并器。 -NeoForge provides some default mergers for merging lists, sets and maps in `DataMapValueMerger`. +NeoForge 为合并列表、集合和映射提供了一些默认的合并器,位于 `DataMapValueMerger` 中。 -The default merger (`DataMapValueMerger#defaultMerger`) has the typical behaviour you'd expect from normal data packs, where the newest value (which comes from the highest datapack) overwrites the previous value. +默认合并器(`DataMapValueMerger#defaultMerger`)具有你期望的普通数据包的典型行为,其中最新的值(来自最高的数据包)将覆盖之前的值。 -### Removers -An advanced data map can provide a `DataMapValueRemover` through `AdvancedDataMapType#remover`. The remover will allow selective removals of data map values, effectively decomposition. -While by default a datapack can only remove the whole object attached to a registry entry, with a remover it can remove just speciffic values from the attached object (i.e. just the entry with a given key in the case of a map, or the entry with a specific property in the case of a list). +### 移除器 +高级数据映射可以通过 `AdvancedDataMapType#remover` 提供一个 `DataMapValueRemover`。移除器将允许选择性地移除数据映射值,有效地进行分解。 +虽然默认情况下一个数据包只能移除附加到注册表条目的整个对象,但有了移除器,它可以只从附加对象中移除特定的值(即,在映射的情况下,只移除具有给定键的项,或在列表的情况下,只移除具有特定属性的项)。 -The codec that is passed to the builder will decode remover instances. These removers will then be given the value currently attached and its source, and are expected to create a new object to replace the old value. -Alternatively, an empty `Optional` will lead to the value being completely removed. +传递给构建器的编解码器将解码移除器实例。然后这些移除器将给出当前附加的值及其来源,并期望创建一个新对象来替换旧值。 +或者,一个空的 `Optional` 将导致值被完全移除。 -An example of a remover that will remove a value with a specific key from a `Map`-based data map: +一个从基于 `Map` 的数据映射中移除具有特定键的值的移除器示例: ```java public record MapRemover(String key) implements DataMapValueRemover> { public static final Codec CODEC = Codec.STRING.xmap(MapRemover::new, MapRemover::key); @@ -136,36 +136,36 @@ public record MapRemover(String key) implements DataMapValueRemover Date: Thu, 25 Apr 2024 18:55:30 +0800 Subject: [PATCH 40/87] Update index.md --- docs/datamaps/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/datamaps/index.md b/docs/datamaps/index.md index 7bfc2089a..d368ceb3f 100644 --- a/docs/datamaps/index.md +++ b/docs/datamaps/index.md @@ -165,7 +165,7 @@ public record MapRemover(String key) implements DataMapValueRemover Date: Thu, 25 Apr 2024 18:58:37 +0800 Subject: [PATCH 41/87] Update attachments.md --- docs/datastorage/attachments.md | 90 ++++++++++++++++----------------- 1 file changed, 45 insertions(+), 45 deletions(-) diff --git a/docs/datastorage/attachments.md b/docs/datastorage/attachments.md index 6f2aace5a..408710921 100644 --- a/docs/datastorage/attachments.md +++ b/docs/datastorage/attachments.md @@ -1,103 +1,103 @@ -# Data Attachments +# 数据附件 -The data attachment system allows mods to attach and store additional data on block entities, chunks, entities, and item stacks. +数据附件系统允许模组在方块实体、区块、实体和物品堆叠上附加和存储额外数据。 -_To store additional level data, you can use [SavedData](saveddata)._ +_要存储额外的关卡数据,您可以使用 [SavedData](saveddata)。_ -## Creating an attachment type +## 创建附件类型 -To use the system, you need to register an `AttachmentType`. -The attachment type contains the following configuration: -- A default value supplier to create the instance when the data is first accessed. Also used to compare stacks that have the data with stacks that don't have it. -- An optional serializer if the attachment should be persisted. -- (If a serializer was configured) The `copyOnDeath` flag to automatically copy entity data on death (see below). -- (Advanced) (If a serializer was configured) A custom `comparator` to use when checking if the data is the same for two item stacks. +要使用系统,您需要注册一个 `AttachmentType`。 +附件类型包含以下配置: +- 当数据第一次被访问时创建实例的默认值供应器。也用于比较有数据的堆叠和没有数据的堆叠。 +- 如果附件需要持久化,则需要一个可选的序列化器。 +- (如果配置了序列化器)`copyOnDeath` 标志,用于在死亡时自动复制实体数据(见下文)。 +- (高级)(如果配置了序列化器)自定义 `comparator`,用于检查两个物品堆叠的数据是否相同。 :::tip -If you don't want your attachment to persist, do not provide a serializer. +如果您不希望您的附件持久化,不要提供序列化器。 ::: -There are a few ways to provide an attachment serializer: directly implementing `IAttachmentSerializer`, implementing `INBTSerializable` and using the static `AttachmentSerializer.serializable()` method to create the builder, or providing a codec to the builder. +有几种方法提供附件序列化器:直接实现 `IAttachmentSerializer`,实现 `INBTSerializable` 并使用静态的 `AttachmentSerializer.serializable()` 方法创建构建器,或向构建器提供编解码器。 :::warning -Avoid serialization with codecs for item stack attachments, as it is comparatively slow. +避免使用编解码器序列化物品堆叠附件,因为它相对较慢。 ::: -In any case, the attachment **must be registered** to the `NeoForgeRegistries.ATTACHMENT_TYPES` registry. Here is an example: +无论哪种方式,附件 **必须被注册** 到 `NeoForgeRegistries.ATTACHMENT_TYPES` 注册表中。以下是一个示例: ```java -// Create the DeferredRegister for attachment types +// 为附件类型创建 DeferredRegister private static final DeferredRegister> ATTACHMENT_TYPES = DeferredRegister.create(NeoForgeRegistries.ATTACHMENT_TYPES, MOD_ID); -// Serialization via INBTSerializable +// 通过 INBTSerializable 序列化 private static final Supplier> HANDLER = ATTACHMENT_TYPES.register( "handler", () -> AttachmentType.serializable(() -> new ItemStackHandler(1)).build()); -// Serialization via codec +// 通过编解码器序列化 private static final Supplier> MANA = ATTACHMENT_TYPES.register( "mana", () -> AttachmentType.builder(() -> 0).serialize(Codec.INT).build()); -// No serialization +// 无序列化 private static final Supplier> SOME_CACHE = ATTACHMENT_TYPES.register( "some_cache", () -> AttachmentType.builder(() -> new SomeCache()).build() ); -// In your mod constructor, don't forget to register the DeferredRegister to your mod bus: +// 在您的 mod 构造函数中,不要忘记将 DeferredRegister 注册到您的 mod 总线: ATTACHMENT_TYPES.register(modBus); ``` -## Using the attachment type +## 使用附件类型 -Once the attachment type is registered, it can be used on any holder object. -Calling `getData` if no data is present will attach a new default instance. +一旦附件类型注册后,它可以在任何持有对象上使用。 +如果没有数据,调用 `getData` 将附加一个新的默认实例。 ```java -// Get the ItemStackHandler if it already exists, else attach a new one: +// 如果已存在 ItemStackHandler,则获取它,否则附加一个新的: ItemStackHandler stackHandler = stack.getData(HANDLER); -// Get the current player mana if it is available, else attach 0: +// 获取当前玩家的法力值(如果有),否则附加 0: int playerMana = player.getData(MANA); -// And so on... +// 等等... ``` -If attaching a default instance is not desired, a `hasData` check can be added: +如果不希望附加一个默认实例,可以添加一个 `hasData` 检查: ```java -// Check if the stack has the HANDLER attachment before doing anything. +// 检查堆叠是否有 HANDLER 附件,然后再进行任何操作。 if (stack.hasData(HANDLER)) { ItemStackHandler stackHandler = stack.getData(HANDLER); - // Do something with stack.getData(HANDLER). + // 对 stack.getData(HANDLER) 做些什么。 } ``` -The data can also be updated with `setData`: +数据也可以用 `setData` 更新: ```java -// Increment mana by 10. +// 将法力值增加 10。 player.setData(MANA, player.getData(MANA) + 10); ``` :::important -Usually, block entities and chunks need to be marked as dirty when they are modified (with `setChanged` and `setUnsaved(true)`). This is done automatically for calls to `setData`: +通常,当修改方块实体和区块时需要将其标记为脏数据(使用 `setChanged` 和 `setUnsaved(true)`)。这对于 `setData` 的调用是自动完成的: ```java -chunk.setData(MANA, chunk.getData(MANA) + 10); // will call setUnsaved automatically +chunk.setData(MANA, chunk.getData(MANA) + 10); // 将自动调用 setUnsaved ``` -but if you modify some data that you obtained from `getData` (including a newly created default instance) then you must mark block entities and chunks as dirty explicitly: +但如果您修改了从 `getData` 获取的数据(包括新创建的默认实例),则必须显式地将方块实体和区块标记为脏数据: ```java var mana = chunk.getData(MUTABLE_MANA); mana.set(10); -chunk.setUnsaved(true); // must be done manually because we did not use setData +chunk.setUnsaved(true); // 必须手动完成,因为我们没有使用 setData ``` ::: -## Sharing data with the client -Currently, only serializable item stack attachments are synced between the client and the server. -This is done automatically. +## 与客户端共享数据 +目前,只有可序列化的物品堆叠附件在客户端和服务器之间同步。 +这是自动完成的。 -To sync block entity, chunk, or entity attachments to a client, you need to [send a packet to the client][network] yourself. -For chunks, you can use `ChunkWatchEvent.Sent` to know when to send chunk data to a player. +要将方块实体、区块或实体附件同步到客户端,你需要自己[向客户端发送数据包][network]。 +对于区块,您可以使用 `ChunkWatchEvent.Sent` 来知道何时向玩家发送区块数据。 -## Copying data on player death -By default, entity data attachments are not copied on player death. -To automatically copy an attachment on player death, set `.copyOnDeath()` in the attachment builder. +## 在玩家死亡时复制数据 +默认情况下,实体数据附件在玩家死亡时不会被复制。 +要在玩家死亡时自动复制附件,请在附件构建器中设置 `.copyOnDeath()`。 -More complex handling can be implemented via `PlayerEvent.Clone` by reading the data from the original entity and assigning it to the new entity. In this event, the `#isWasDeath` method can be used to distinguish between respawning after death and returning from the End. This is important because the data will already exist when returning from the End, so care has to be taken to not duplicate values in this case. +更复杂的处理可以通过 `PlayerEvent.Clone` 实现,通过从原始实体中读取数据并将其分配给新实体。在此事件中,可以使用 `#isWasDeath` 方法区分死亡后重生和从末地返回。这很重要,因为从末地返回时数据已经存在,因此要注意在这种情况下不要重复值。 -For example: +例如: ```java NeoForge.EVENT_BUS.register(PlayerEvent.Clone.class, event -> { if (event.isWasDeath() && event.getOriginal().hasData(MY_DATA)) { @@ -106,4 +106,4 @@ NeoForge.EVENT_BUS.register(PlayerEvent.Clone.class, event -> { }); ``` -[network]: ../networking/index.md \ No newline at end of file +[network]: ../networking/index.md From dfc419e44e5d61c2d1997fbc961ee0fdb353b5c0 Mon Sep 17 00:00:00 2001 From: DaZiDian <113085496+DaZiDian@users.noreply.github.com> Date: Thu, 25 Apr 2024 19:16:06 +0800 Subject: [PATCH 42/87] Update capabilities.md --- docs/datastorage/capabilities.md | 307 ++++++++++++++----------------- 1 file changed, 143 insertions(+), 164 deletions(-) diff --git a/docs/datastorage/capabilities.md b/docs/datastorage/capabilities.md index 94fff8c71..ecbd044a7 100644 --- a/docs/datastorage/capabilities.md +++ b/docs/datastorage/capabilities.md @@ -1,299 +1,281 @@ -# Capabilities +# 功能 -Capabilities allow exposing features in a dynamic and flexible way without having to resort to directly implementing many interfaces. +功能(Capabilities)允许以动态和灵活的方式公开特性,无需直接实现许多接口。 -In general terms, each capability provides a feature in the form of an interface. +通常来说,每个功能都以接口的形式提供一个特性。 -NeoForge adds capability support to blocks, entities, and item stacks. -This will be explained in more detail in the following sections. +NeoForge 为方块、实体和物品堆叠添加了功能支持。 +这将在以下部分中更详细地解释。 -## Why use capabilities +## 为什么使用功能 -Capabilities are designed to separate **what** a block, entity or item stack can do from **how** it does it. -If you are wondering whether capabilities are the right tool for a job, ask yourself the following questions: -1. Do I only care about **what** a block, entity or item stack can do, but not about **how** it does it? -2. Is the **what**, the behavior, only available for some blocks, entities, or item stacks, but not all of them? -3. Is the **how**, the implementation of that behavior, dependent on the specific block, entity or item stack? +功能旨在将**能做什么**与**如何做**分离开来,适用于方块、实体或物品堆叠。 +如果您正在考虑功能是否适合某项工作,请问自己以下问题: +1. 我只关心方块、实体或物品堆叠能做什么,而不关心它如何做吗? +2. 这个 **能做什么**,也就是行为,是否只对某些方块、实体或物品堆叠有效,而不是所有? +3. 这个 **如何做**,即行为的实现,是否依赖于具体的方块、实体或物品堆叠? -Here are a few examples of good capability usage: -- *"I want my fluid container to be compatible with fluid containers from other mods, but I don't know the specifics of each fluid container."* - Yes, use the `IFluidHandler` capability. -- *"I want to count how many items are in some entity, but I do not know how the entity might store them."* - Yes, use the `IItemHandler` capability. -- *"I want to fill some item stack with power, but I do not know how the item stack might store it."* - Yes, use the `IEnergyStorage` capability. -- *"I want to apply some color to whatever block a player is currently targeting, but I do not know how the block will be transformed."* - Yes. NeoForge does not provide a capability to color blocks, but you can implement one yourself. +以下是一些良好的功能使用示例: +- *“我希望我的流体容器能与其他模组的流体容器兼容,但我不知道每个流体容器的具体情况。”* - 是的,使用 `IFluidHandler` 功能。 +- *“我想计算某个实体中有多少物品,但我不知道实体可能如何存储它们。”* - 是的,使用 `IItemHandler` 功能。 +- *“我想给某个物品堆叠充能,但我不知道物品堆叠可能如何存储能量。”* - 是的,使用 `IEnergyStorage` 功能。 +- *“我想对玩家当前瞄准的任何方块应用颜色,但我不知道方块将如何变化。”* - 是的。NeoForge 没有提供给方块上色的功能,但你可以自己实现。 -Here is an example of discouraged capability usage: -- *"I want to check if an entity is within the range of my machine."* - No, use a helper method instead. +以下是不推荐使用功能的示例: +- *“我想检查某个实体是否在我的机器范围内。”* - 不,使用帮助方法代替。 -## NeoForge-provided capabilities +## NeoForge 提供的功能 -NeoForge provides capabilities for the following three interfaces: `IItemHandler`, `IFluidHandler` and `IEnergyStorage`. +NeoForge 为以下三个接口提供了功能:`IItemHandler`,`IFluidHandler` 和 `IEnergyStorage`。 -`IItemHandler` exposes an interface for handling inventory slots. The capabilities of type `IItemHandler` are: -- `Capabilities.ItemHandler.BLOCK`: automation-accessible inventory of a block (for chests, machines, etc). -- `Capabilities.ItemHandler.ENTITY`: inventory contents of an entity (extra player slots, mob/creature inventories/bags). -- `Capabilities.ItemHandler.ENTITY_AUTOMATION`: automation-accessible inventory of an entity (boats, minecarts, etc). -- `Capabilities.ItemHandler.ITEM`: contents of an item stack (portable backpacks and such). +`IItemHandler` 公开了处理库存槽的接口。`IItemHandler` 类型的功能有: +- `Capabilities.ItemHandler.BLOCK`:方块的自动化可访问库存(用于箱子、机器等)。 +- `Capabilities.ItemHandler.ENTITY`:实体的库存内容(额外的玩家槽位、怪物/生物的库存/包)。 +- `Capabilities.ItemHandler.ENTITY_AUTOMATION`:实体的自动化可访问库存(船、矿车等)。 +- `Capabilities.ItemHandler.ITEM`:物品堆叠的内容(便携背包等)。 -`IFluidHandler` exposes an interface for handling fluid inventories. The capabilities of type `IFluidHandler` are: -- `Capabilities.FluidHandler.BLOCK`: automation-accessible fluid inventory of a block. -- `Capabilities.FluidHandler.ENTITY`: fluid inventory of an entity. -- `Capabilities.FluidHandler.ITEM`: fluid inventory of an item stack. -This capability is of the special `IFluidHandlerItem` type due to the way buckets hold fluids. +`IFluidHandler` 公开了处理流体库存的接口。`IFluidHandler` 类型的功能有: +- `Capabilities.FluidHandler.BLOCK`:方块的自动化可访问流体库存。 +- `Capabilities.FluidHandler.ENTITY`:实体的流体库存。 +- `Capabilities.FluidHandler.ITEM`:物品堆叠的流体库存。 +这个功能是特殊的 `IFluidHandlerItem` 类型,因为桶装液体的方式有所不同。 -`IEnergyStorage` exposes an interface for handling energy containers. It is based on the RedstoneFlux API by TeamCoFH. The capabilities of type `IEnergyStorage` are: -- `Capabilities.EnergyStorage.BLOCK`: energy contained inside a block. -- `Capabilities.EnergyStorage.ENTITY`: energy containing inside an entity. -- `Capabilities.EnergyStorage.ITEM`: energy contained inside an item stack. +`IEnergyStorage` 公开了处理能量容器的接口。它基于 TeamCoFH 的 RedstoneFlux API。`IEnergyStorage` 类型的功能有: +- `Capabilities.EnergyStorage.BLOCK`:方块内部的能量。 +- `Capabilities.EnergyStorage.ENTITY`:实体内部的能量。 +- `Capabilities.EnergyStorage.ITEM`:物品堆叠内部的能量。 -## Creating a capability +## 创建功能 -NeoForge supports capabilities for blocks, entities, and item stacks. - -Capabilities allow looking up implementations of some APIs with some dispatching logic. The following kinds of capabilities are implemented in NeoForge: -- `BlockCapability`: capabilities for blocks and block entities; behavior depends on the specific `Block`. -- `EntityCapability`: capabilities for entities: behavior dependends on the specific `EntityType`. -- `ItemCapability`: capabilities for item stacks: behavior depends on the specific `Item`. +NeoForge为方块、实体和物品堆叠支持功能性。功能性允许在一定逻辑下查找某些API的实现。在NeoForge中实现了以下几种功能性: +- `BlockCapability`:适用于方块和方块实体的功能性;行为依赖于特定的`Block`。 +- `EntityCapability`:适用于实体的功能性;行为依赖于特定的`EntityType`。 +- `ItemCapability`:适用于物品堆叠的功能性;行为依赖于特定的`Item`。 :::tip -For compatibility with other mods, -we recommend using the capabilities provided by NeoForge in the `Capabilities` class if possible. -Otherwise, you can create your own as described in this section. +为了与其他模组兼容,如果可能的话,我们建议使用NeoForge在`Capabilities`类中提供的功能性。否则,您可以按照本节所述创建自己的功能性。 ::: -Creating a capability is a single function call, and the resulting object should be stored in a `static final` field. -The following parameters must be provided: -- The name of the capability. -Creating a capability with the same name multiple times will always return the same object. -Capabilities with different names are **completely independent**, and can be used for different purposes. -- The behavior type that is being queried. This is the `T` type parameter. -- The type for additional context in the query. This is the `C` type parameter. +创建功能性是单个函数调用,结果对象应该存储在一个`static final`字段中。必须提供以下参数: +- 功能性的名称。多次创建相同名称的功能性将始终返回相同的对象。不同名称的功能性是**完全独立的**,可以用于不同的目的。 +- 正在查询的行为类型。这是`T`类型参数。 +- 查询中的附加上下文类型。这是`C`类型参数。 -For example, here is how a capability for side-aware block `IItemHandler`s might be declared: +例如,以下是如何声明侧向感知方块`IItemHandler`的功能性: ```java public static final BlockCapability ITEM_HANDLER_BLOCK = BlockCapability.create( - // Provide a name to uniquely identify the capability. + // 提供一个名称以唯一标识功能性。 new ResourceLocation("mymod", "item_handler"), - // Provide the queried type. Here, we want to look up `IItemHandler` instances. + // 提供查询的类型。在这里,我们希望查找`IItemHandler`实例。 IItemHandler.class, - // Provide the context type. We will allow the query to receive an extra `Direction side` parameter. + // 提供上下文类型。我们将允许查询接收额外的`Direction side`参数。 Direction.class); ``` -A `@Nullable Direction` is so common for blocks that there is a dedicated helper: +对于方块来说,`@Nullable Direction`是如此常见,以至于有一个专门的助手函数: + ```java public static final BlockCapability ITEM_HANDLER_BLOCK = BlockCapability.createSided( - // Provide a name to uniquely identify the capability. + // 提供一个名称以唯一标识功能性。 new ResourceLocation("mymod", "item_handler"), - // Provide the queried type. Here, we want to look up `IItemHandler` instances. + // 提供查询的类型。在这里,我们希望查找`IItemHandler`实例。 IItemHandler.class); ``` -If no context is required, `Void` should be used. -There is also a dedicated helper for context-less capabilities: +如果不需要上下文,则应使用`Void`。对于无上下文的功能性也有专门的助手函数: + ```java public static final BlockCapability ITEM_HANDLER_NO_CONTEXT = BlockCapability.createVoid( - // Provide a name to uniquely identify the capability. + // 提供一个名称以唯一标识功能性。 new ResourceLocation("mymod", "item_handler_no_context"), - // Provide the queried type. Here, we want to look up `IItemHandler` instances. + // 提供查询的类型。在这里,我们希望查找`IItemHandler`实例。 IItemHandler.class); ``` -For entities and item stacks, similar methods exist in `EntityCapability` and `ItemCapability` respectively. +对于实体和物品堆叠,`EntityCapability`和`ItemCapability`分别存在类似的方法。 + +## 查询功能性 +一旦我们在一个静态字段中拥有了`BlockCapability`、`EntityCapability`或`ItemCapability`对象,我们就可以查询一个功能性。 -## Querying capabilities -Once we have our `BlockCapability`, `EntityCapability`, or `ItemCapability` object in a static field, we can query a capability. +对于实体和物品堆叠,我们可以尝试使用`getCapability`找到功能性的实现。如果结果是`null`,则没有可用的实现。 -For entities and item stacks, we can try to find implementations of a capability with `getCapability`. -If the result is `null`, there no implementation is available. +例如: -For example: ```java var object = entity.getCapability(CAP, context); if (object != null) { - // Use object + // 使用object } ``` ```java var object = stack.getCapability(CAP, context); if (object != null) { - // Use object + // 使用object } ``` -Block capabilities are used a bit differently because blocks without a block entity can have capabilities as well. -The query is now performed on a `level`, with the `pos`ition that we are looking for as an additional parameter: +方块功能性的使用略有不同,因为没有方块实体的方块也可以拥有功能性。现在,查询是在一个`level`上进行的,有一个我们正在寻找的`pos`位置作为附加参数: + ```java var object = level.getCapability(CAP, pos, context); if (object != null) { - // Use object + // 使用object } ``` -If the block entity and/or the block state is known, they can be passed to save on query time: +如果已知方块实体和/或方块状态,可以传递它们以节省查询时间: + ```java var object = level.getCapability(CAP, pos, blockState, blockEntity, context); if (object != null) { - // Use object + // 使用object } ``` -To give a more concrete example, here is how one might query an `IItemHandler` capability for a block, from the `Direction.NORTH` side: +为了给出一个更具体的示例,以下是如何从`Direction.NORTH`侧查询方块的`IItemHandler`功能性: + ```java IItemHandler handler = level.getCapability(Capabilities.ItemHandler.BLOCK, pos, Direction.NORTH); if (handler != null) { - // Use the handler for some item-related operation. + // 使用handler进行某些物品相关操作。 } ``` -## Block capability caching -When a capability is looked up, the system will perform the following steps under the hood: -1. Fetch block entity and block state if they were not supplied. -2. Fetch registered capability providers. (More on this below). -3. Iterate the providers and ask them if they can provide the capability. -4. One of the providers will return a capability instance, potentially allocating a new object. +当查询某个功能性时,系统会在后台执行以下步骤: +1. 如果它们没有被提供的话,获取方块实体和方块状态。 +2. 获取注册的功能性提供者。(下文会有更多相关信息) +3. 遍历提供者并询问他们是否能提供该功能性。 +4. 提供者中的一个将返回功能性实例,可能会分配一个新对象。 -The implementation is rather efficient, but for queries that are performed frequently, -for example every game tick, these steps can take a significant amount of server time. -The `BlockCapabilityCache` system provides a dramatic speedup for capabilities that are frequently queried at a given position. +尽管实现相当高效,但对于频繁进行的查询,例如每个游戏刻,这些步骤可能会占用大量服务器时间。`BlockCapabilityCache` 系统为在特定位置频繁查询的能力提供了巨大的速度提升。 :::tip -Generally, a `BlockCapabilityCache` will be created once and then stored in a field of the object performing frequent capability queries. -When and where exactly you store the cache is up to you. +通常,`BlockCapabilityCache` 会被创建一次,然后存储在执行频繁功能性查询的对象的字段中。何时何地存储缓存取决于您。 ::: -To create a cache, call `BlockCapabilityCache.create` with the capability to query, the level, the position, and the query context. +要创建缓存,请使用要查询的功能性,级别,位置和查询上下文调用 `BlockCapabilityCache.create`。 ```java -// Declare the field: +// 声明字段: private BlockCapabilityCache capCache; -// Later, for example in `onLoad` for a block entity: +// 稍后,例如在方块实体的 `onLoad` 中: this.capCache = BlockCapabilityCache.create( - Capabilities.ItemHandler.BLOCK, // capability to cache - level, // level - pos, // target position - Direction.NORTH // context + Capabilities.ItemHandler.BLOCK, // 要缓存的功能性 + level, // 世界级别 + pos, // 目标位置 + Direction.NORTH // 上下文 ); ``` -Querying the cache is then done with `getCapability()`: +然后通过 `getCapability()` 查询缓存: ```java IItemHandler handler = this.capCache.getCapability(); if (handler != null) { - // Use the handler for some item-related operation. + // 对某些与物品相关的操作使用 handler。 } ``` -**The cache is automatically cleared by the garbage collector, there is no need to unregister it.** +**缓存会被垃圾收集器自动清除,无需注销。** -It is also possible to receive notifications when the capability object changes! -This includes capabilities changing (`oldHandler != newHandler`), becoming unavailable (`null`) or becoming available again (not `null` anymore). +也可以接收到功能性对象变更的通知!这包括功能性变化(`oldHandler != newHandler`)、变得不可用(`null`)或再次变得可用(不再是 `null`)。 -The cache then needs to be created with two additional parameters: -- A validity check, that is used to determine if the cache is still valid. -In the simplest usage as a block entity field, `() -> !this.isRemoved()` will do. -- An invalidation listener, that is called when the capability changes. -This is where you can react to capability changes, removals, or appearances. +创建缓存时需要两个额外的参数: +- 一个有效性检查,用于确定缓存是否仍然有效。 +在作为方块实体字段的最简单用法中,`() -> !this.isRemoved()` 就可以了。 +- 一个失效监听器,当功能性改变时被调用。 +这是您可以对功能性变更、移除或出现做出反应的地方。 ```java -// With optional invalidation listener: +// 带有可选的失效监听器: this.capCache = BlockCapabilityCache.create( - Capabilities.ItemHandler.BLOCK, // capability to cache - level, // level - pos, // target position - Direction.NORTH, // context - () -> !this.isRemoved(), // validity check (because the cache might outlive the object it belongs to) - () -> onCapInvalidate() // invalidation listener + Capabilities.ItemHandler.BLOCK, // 要缓存的功能性 + level, // 世界级别 + pos, // 目标位置 + Direction.NORTH, // 上下文 + () -> !this.isRemoved(), // 有效性检查(因为缓存可能会比它所属的对象更久存在) + () -> onCapInvalidate() // 失效监听器 ); ``` -## Block capability invalidation +## 方块功能性失效 :::info -Invalidation is exclusive to block capabilities. Entity and item stack capabilities cannot be cached and do not need to be invalidated. +失效功能是专门针对方块功能性的。实体和物品堆叠的功能性不能被缓存,因此不需要失效处理。 ::: -To make sure that caches can correctly update their stored capability, **modders must call `level.invalidateCapabilities(pos)` whenever a capability changes, appears, or disappears**. +为了确保缓存可以正确更新它们存储的功能性,**模组开发者必须在功能性改变、出现或消失时调用 `level.invalidateCapabilities(pos)`**。 ```java -// whenever a capability changes, appears, or disappears: +// 每当一个功能性改变、出现或消失时: level.invalidateCapabilities(pos); ``` -NeoForge already handles common cases such as chunk load/unloads and block entity creation/removal, -but other cases need to be handled explicitly by modders. -For example, modders must invalidate capabilities in the following cases: -- If a previously returned capability is no longer valid. -- If a capability-providing block (without a block entity) is placed or changes state, by overriding `onPlace`. -- If a capability-providing block (without a block entity) is removed, by overriding `onRemove`. +NeoForge已经处理了常见情况,例如区块的加载/卸载和方块实体的创建/移除,但其他情况需要模组开发者明确处理。例如,模组开发者必须在以下情况中使功能性失效: +- 如果先前返回的功能性不再有效。 +- 如果放置或状态变化的功能性提供方块(没有方块实体),通过覆写 `onPlace`。 +- 如果移除的功能性提供方块(没有方块实体),通过覆写 `onRemove`。 -For a plain block example, refer to the `ComposterBlock.java` file. +对于一个简单的方块示例,参考 `ComposterBlock.java` 文件。 -For more information, refer to the javadoc of [`IBlockCapabilityProvider`][block-cap-provider]. +更多信息,请参考 [`IBlockCapabilityProvider`][block-cap-provider] 的 javadoc。 -## Registering capabilities -A capability _provider_ is what ultimately supplies a capability. -A capability provider is function that can either return a capability instance, or `null` if it cannot provide the capability. -Providers are specific to: -- the given capability that they are providing for, and -- the block instance, block entity type, entity type, or item instance that they are providing for. +## 注册功能性 +功能性*提供者*是最终提供功能性的东西。功能性提供者是一个函数,可以返回一个功能性实例,或者如果不能提供功能性,就返回 `null`。提供者特定于: +- 它们为之提供服务的给定功能性,以及 +- 它们为之提供服务的方块实例、方块实体类型、实体类型或物品实例。 -They need to be registered in the `RegisterCapabilitiesEvent`. +它们需要在 `RegisterCapabilitiesEvent` 中注册。 -Block providers are registered with `registerBlock`. For example: +方块提供者使用 `registerBlock` 进行注册。例如: ```java private static void registerCapabilities(RegisterCapabilitiesEvent event) { event.registerBlock( - Capabilities.ItemHandler.BLOCK, // capability to register for - (level, pos, state, be, side) -> , - // blocks to register for + Capabilities.ItemHandler.BLOCK, // 注册的功能性 + (level, pos, state, be, side) -> <返回 IItemHandler>, + // 注册的方块 MY_ITEM_HANDLER_BLOCK, MY_OTHER_ITEM_HANDLER_BLOCK); } ``` -In general, registration will be specific to some block entity types, so the `registerBlockEntity` helper method is provided as well: +通常,注册将特定于一些方块实体类型,因此提供了 `registerBlockEntity` 辅助方法: ```java event.registerBlockEntity( - Capabilities.ItemHandler.BLOCK, // capability to register for - MY_BLOCK_ENTITY_TYPE, // block entity type to register for - (myBlockEntity, side) -> ); + Capabilities.ItemHandler.BLOCK, // 注册的功能性 + MY_BLOCK_ENTITY_TYPE, // 注册的方块实体类型 + (myBlockEntity, side) -> <为 myBlockEntity 和 side 返回 IItemHandler>); ``` -:::danger -If the capability previously returned by a block or block entity provider is no longer valid, -**you must invalidate the caches** by calling `level.invalidateCapabilities(pos)`. -Refer to the [invalidation section][invalidation] above for more information. -::: +如果之前由方块或方块实体提供者返回的功能性不再有效,**您必须通过调用 `level.invalidateCapabilities(pos)` 来使缓存失效**。有关更多信息,请参考上文的[失效部分][invalidation]。 -Entity registration is similar, using `registerEntity`: +实体的注册类似,使用 `registerEntity`: ```java event.registerEntity( - Capabilities.ItemHandler.ENTITY, // capability to register for - MY_ENTITY_TYPE, // entity type to register for - (myEntity, context) -> ); + Capabilities.ItemHandler.ENTITY, // 要注册的功能性 + MY_ENTITY_TYPE, // 要注册的实体类型 + (myEntity, context) -> <返回 myEntity 的 IItemHandler>); ``` -Item registration is similar too. Note that the provider receives the stack: +物品的注册也类似。注意,提供者接收堆叠: ```java event.registerItem( - Capabilities.ItemHandler.ITEM, // capability to register for - (itemStack, context) -> , - // items to register for + Capabilities.ItemHandler.ITEM, // 要注册的功能性 + (itemStack, context) -> <返回 itemStack 的 IItemHandler>, + // 要注册的物品 MY_ITEM, MY_OTHER_ITEM); ``` -## Registering capabilities for all objects +## 为所有对象注册功能性 -If for some reason you need to register a provider for all blocks, entities, or items, -you will need to iterate the corresponding registry and register the provider for each object. +如果由于某种原因您需要为所有方块、实体或物品注册一个提供者,您将需要遍历相应的注册表并为每个对象注册提供者。 -For example, NeoForge uses this system to register a fluid handler capability for all `BucketItem`s (excluding subclasses): +例如,NeoForge使用这个系统为所有的 `BucketItem`(不包括子类)注册一个流体处理器功能性: ```java -// For reference, you can find this code in the `CapabilityHooks` class. +// 作为参考,您可以在 `CapabilityHooks` 类中找到这段代码。 for (Item item : BuiltInRegistries.ITEM) { if (item.getClass() == BucketItem.class) { event.registerItem(Capabilities.FluidHandler.ITEM, (stack, ctx) -> new FluidBucketWrapper(stack), item); @@ -301,20 +283,17 @@ for (Item item : BuiltInRegistries.ITEM) { } ``` -Providers are asked for a capability in the order that they are registered. -Should you want to run before a provider that NeoForge already registers for one of your objects, -register your `RegisterCapabilitiesEvent` handler with a higher priority. -For example: +按照注册的顺序请求提供者提供功能性。如果您想在NeoForge已经为您的某个对象注册的提供者之前运行,请使用更高优先级注册您的 `RegisterCapabilitiesEvent` 处理器。例如: ```java modBus.addListener(RegisterCapabilitiesEvent.class, event -> { event.registerItem( Capabilities.FluidHandler.ITEM, (stack, ctx) -> new MyCustomFluidBucketWrapper(stack), - // blocks to register for + // 要注册的方块 MY_CUSTOM_BUCKET); -}, EventPriority.HIGH); // use HIGH priority to register before NeoForge! +}, EventPriority.HIGH); // 使用 HIGH 优先级在 NeoForge 之前注册! ``` -See [`CapabilityHooks`][capability-hooks] for a list of the providers registered by NeoForge itself. +查看 [`CapabilityHooks`][capability-hooks] 以获取 NeoForge 本身注册的提供者列表。 [block-cap-provider]: https://github.com/neoforged/NeoForge/blob/1.20.x/src/main/java/net/neoforged/neoforge/capabilities/IBlockCapabilityProvider.java [capability-hooks]: https://github.com/neoforged/NeoForge/blob/1.20.x/src/main/java/net/neoforged/neoforge/capabilities/CapabilityHooks.java From b0f05f0548894f8b8746b6560ab23d92fce3a5f4 Mon Sep 17 00:00:00 2001 From: DaZiDian <113085496+DaZiDian@users.noreply.github.com> Date: Thu, 25 Apr 2024 19:29:57 +0800 Subject: [PATCH 43/87] Update codecs.md --- docs/datastorage/codecs.md | 323 +++++++++++++++++++------------------ 1 file changed, 164 insertions(+), 159 deletions(-) diff --git a/docs/datastorage/codecs.md b/docs/datastorage/codecs.md index 0d08b3dd6..4009381bb 100644 --- a/docs/datastorage/codecs.md +++ b/docs/datastorage/codecs.md @@ -1,125 +1,126 @@ -# Codecs +# 编解码器 -Codecs are a serialization tool from Mojang's [DataFixerUpper] used to describe how objects can be transformed between different formats, such as `JsonElement`s for JSON and `Tag`s for NBT. +编解码器是 Mojang 的 [DataFixerUpper] 库中的一种序列化工具,用于描述对象在不同格式之间的转换方式,如将对象从 `JsonElement` 的 JSON 格式转换为 NBT 的 `Tag` 格式。 -## Using Codecs +## 使用编解码器 -Codecs are primarily used to encode, or serialize, Java objects to some data format type and decode, or deserialize, formatted data objects back to its associated Java type. This is typically accomplished using `Codec#encodeStart` and `Codec#parse`, respectively. +编解码器主要用于将 Java 对象编码(或序列化)成某种数据格式,并将格式化的数据对象解码(或反序列化)回其关联的 Java 类型。这通常通过 `Codec#encodeStart` 和 `Codec#parse` 来实现。 -### DynamicOps +### 动态操作 -To determine what intermediate file format to encode and decode to, both `#encodeStart` and `#parse` require a `DynamicOps` instance to define the data within that format. +为了确定将数据编码和解码至哪种中间文件格式,`#encodeStart` 和 `#parse` 都需要一个 `DynamicOps` 实例来定义该格式中的数据。 -The [DataFixerUpper] library contains `JsonOps` to codec JSON data stored in [`Gson`'s][gson] `JsonElement` instances. `JsonOps` supports two versions of `JsonElement` serialization: `JsonOps#INSTANCE` which defines a standard JSON file, and `JsonOps#COMPRESSED` which allows data to be compressed into a single string. +[DataFixerUpper] 库包含了 `JsonOps`,用于对存储在 [`Gson`][gson] 的 `JsonElement` 实例中的 JSON 数据进行编解码。`JsonOps` 支持两种 `JsonElement` 序列化版本:`JsonOps#INSTANCE` 定义了标准的 JSON 文件,而 `JsonOps#COMPRESSED` 允许将数据压缩成单一字符串。 ```java -// Let exampleCodec represent a Codec -// Let exampleObject be a ExampleJavaObject -// Let exampleJson be a JsonElement +// 假设 exampleCodec 代表一个 Codec +// 假设 exampleObject 为一个 ExampleJavaObject +// 假设 exampleJson 为一个 JsonElement -// Encode Java object to regular JsonElement +// 将 Java 对象编码为常规 JsonElement exampleCodec.encodeStart(JsonOps.INSTANCE, exampleObject); -// Encode Java object to compressed JsonElement +// 将 Java 对象编码为压缩的 JsonElement exampleCodec.encodeStart(JsonOps.COMPRESSED, exampleObject); -// Decode JsonElement into Java object -// Assume JsonElement was parsed normally +// 将 JsonElement 解码为 Java 对象 +// 假设 JsonElement 是正常解析的 exampleCodec.parse(JsonOps.INSTANCE, exampleJson); ``` -Minecraft also provides `NbtOps` to codec NBT data stored in `Tag` instances. This can be referenced using `NbtOps#INSTANCE`. +Minecraft 还提供了 `NbtOps` 用于对存储在 `Tag` 实例中的 NBT 数据进行编解码。可以通过 `NbtOps#INSTANCE` 来引用。 ```java -// Let exampleCodec represent a Codec -// Let exampleObject be a ExampleJavaObject -// Let exampleNbt be a Tag +// 假设 exampleCodec 代表一个 Codec +// 假设 exampleObject 为一个 ExampleJavaObject +// 假设 exampleNbt 为一个 Tag -// Encode Java object to Tag +// 将 Java 对象编码为 Tag exampleCodec.encodeStart(JsonOps.INSTANCE, exampleObject); -// Decode Tag into Java object +// 将 Tag 解码为 Java 对象 exampleCodec.parse(JsonOps.INSTANCE, exampleNbt); ``` -#### Format Conversion +#### 格式转换 -`DynamicOps` can also be used separately to convert between two different encoded formats. This can be done using `#convertTo` and supplying the `DynamicOps` format and the encoded object to convert. +`DynamicOps` 还可以单独用来在两种不同的编码格式之间转换。这可以通过使用 `#convertTo` 并提供 `DynamicOps` 格式和要转换的编码对象来完成。 ```java -// Convert Tag to JsonElement -// Let exampleTag be a Tag +// 将 Tag 转换为 JsonElement +// 假设 exampleTag 为一个 Tag JsonElement convertedJson = NbtOps.INSTANCE.convertTo(JsonOps.INSTANCE, exampleTag); ``` -### DataResult +### 数据结果 -Encoded or decoded data using codecs return a `DataResult` which holds the converted instance or some error data depending on whether the conversion was successful. When the conversion is successful, the `Optional` supplied by `#result` will contain the successfully converted object. If the conversion fails, the `Optional` supplied by `#error` will contain the `PartialResult`, which holds the error message and a partially converted object depending on the codec. +使用编解码器编码或解码数据时返回的 `DataResult` 将根据转换是否成功,持有转换后的实例或一些错误数据。当转换成功时,由 `#result` 提供的 `Optional` 将包含成功转换的对象。如果转换失败,由 `#error` 提供的 `Optional` 将包含 `PartialResult`,后者持有错误消息和根据编解码器部分转换的对象。 -Additionally, there are many methods on `DataResult` that can be used to transform the result or error into the desired format. For example, `#resultOrPartial` will return an `Optional` containing the result on success, and the partially converted object on failure. The method takes in a string consumer to determine how to report the error message if present. +此外,`DataResult` 上有许多方法可以用来将结果或错误转换为所需格式。例如,`#resultOrPartial` 将返回一个 `Optional`,在成功时包含结果,在失败时包含部分转换的对象。此方法接受一个字符串消费者以确定如何报告错误消息(如果存在)。 ```java -// Let exampleCodec represent a Codec -// Let exampleJson be a JsonElement +// 假设 exampleCodec 代表一个 Codec +// 假设 exampleJson 为一个 JsonElement -// Decode JsonElement into Java object +// 将 JsonElement 解码为 Java 对象 DataResult result = exampleCodec.parse(JsonOps.INSTANCE, exampleJson); result - // Get result or partial on error, report error message - .resultOrPartial(errorMessage -> /* Do something with error message */) - // If result or partial is present, do something - .ifPresent(decodedObject -> /* Do something with decoded object */); -``` + // 获取结果或部分错误时的错误消息 + .resultOrPartial(errorMessage -> /* 处理错误消息 */) + // 如果结果或部分存在,则进行某些操作 + .ifPresent(decodedObject -## Existing Codecs + -> /* 处理解码对象 */); +``` -### Primitives +## 现有编解码器 -The `Codec` class contains static instances of codecs for certain defined primitives. +### 原始类型 -Codec | Java Type -:---: | :--- -`BOOL` | `Boolean` -`BYTE` | `Byte` -`SHORT` | `Short` -`INT` | `Integer` -`LONG` | `Long` -`FLOAT` | `Float` -`DOUBLE` | `Double` -`STRING` | `String` -`BYTE_BUFFER` | `ByteBuffer` -`INT_STREAM` | `IntStream` -`LONG_STREAM` | `LongStream` -`PASSTHROUGH` | `Dynamic`\* -`EMPTY` | `Unit`\*\* +`Codec` 类包含了一些定义的原始类型的静态编解码器实例。 -\* `Dynamic` is an object which holds a value encoded in a supported `DynamicOps` format. These are typically used to convert encoded object formats into other encoded object formats. +| 编解码器 | Java 类型 | +|------------|--------| +| `BOOL` | `Boolean` | +| `BYTE` | `Byte` | +| `SHORT` | `Short` | +| `INT` | `Integer` | +| `LONG` | `Long` | +| `FLOAT` | `Float` | +| `DOUBLE` | `Double` | +| `STRING` | `String` | +| `BYTE_BUFFER` | `ByteBuffer` | +| `INT_STREAM` | `IntStream` | +| `LONG_STREAM` | `LongStream` | +| `PASSTHROUGH` | `Dynamic` | +| `EMPTY` | `Unit` | -\*\* `Unit` is an object used to represent `null` objects. +* `Dynamic` 是一个在支持的 `DynamicOps` 格式中编码值的对象。这些通常用于将编码对象格式转换为其他编码对象格式。 +* `Unit` 是用于表示 `null` 对象的对象。 -### Vanilla and Forge +### 原版和 Forge -Minecraft and Forge define many codecs for objects that are frequently encoded and decoded. Some examples include `ResourceLocation#CODEC` for `ResourceLocation`s, `ExtraCodecs#INSTANT_ISO8601` for `Instant`s in the `DateTimeFormatter#ISO_INSTANT` format, and `CompoundTag#CODEC` for `CompoundTag`s. +Minecraft 和 Forge 定义了许多常见对象的编解码器。一些示例包括用于 `ResourceLocation` 的 `ResourceLocation#CODEC`,用于 `DateTimeFormatter#ISO_INSTANT` 格式的 `Instant` 的 `ExtraCodecs#INSTANT_ISO8601`,以及用于 `CompoundTag` 的 `CompoundTag#CODEC`。 -:::caution -`CompoundTag`s cannot decode lists of numbers from JSON using `JsonOps`. `JsonOps`, when converting, sets a number to its most narrow type. `ListTag`s force a specific type for its data, so numbers with different types (e.g. `64` would be `byte`, `384` would be `short`) will throw an error on conversion. +:::警告 +使用 `JsonOps` 的 `CompoundTag` 不能从 JSON 解码数字列表。`JsonOps` 在转换时会将数字设置为其最窄类型。`ListTag` 强制其数据使用特定类型,因此不同类型的数字(例如 `64` 会是 `byte`,`384` 会是 `short`)在转换时会引发错误。 ::: -Vanilla and Forge registries also have codecs for the type of object the registry contains (e.g. `Registry#BLOCK` or `ForgeRegistries#BLOCKS` have a `Codec`). `Registry#byNameCodec` and `IForgeRegistry#getCodec` will encode the registry object to their registry name, or an integer identifier if compressed. Vanilla registries also have a `Registry#holderByNameCodec` which encodes to a registry name and decodes to the registry object wrapped in a `Holder`. +原版和 Forge 注册也有针对注册表所包含对象类型的编解码器(例如 `Registry#BLOCK` 或 `ForgeRegistries#BLOCKS` 有一个 `Codec`)。`Registry#byNameCodec` 和 `IForgeRegistry#getCodec` 会将注册表对象编码为其注册名,或者如果压缩,则为整数标识符。原版注册表还有一个 `Registry#holderByNameCodec`,它将编码为注册名并解码为被 `Holder` 包装的注册表对象。 -## Creating Codecs +## 创建编解码器 -Codecs can be created for encoding and decoding any object. For understanding purposes, the equivalent encoded JSON will be shown. +可以为任何对象创建编解码器。为了便于理解,将显示等效的编码 JSON。 -### Records +### 记录 -Codecs can define objects through the use of records. Each record codec defines any object with explicit named fields. There are many ways to create a record codec, but the simplest is via `RecordCodecBuilder#create`. +编解码器可以通过使用记录来定义对象。每个记录编解码器定义了具有明确命名字段的任何对象。创建记录编解码器的方法有很多,但最简单的是通过 `RecordCodecBuilder#create`。 -`RecordCodecBuilder#create` takes in a function which defines an `Instance` and returns an application (`App`) of the object. A correlation can be drawn to creating a class *instance* and the constructors used to *apply* the class to the constructed object. +`RecordCodecBuilder#create` 接受一个函数,该函数定义了一个 `Instance` 并返回一个应用(`App`)到构建对象的对象。这可以与创建类*实例*和用于*应用*类的构造函数联系起来。 ```java -// Some object to create a codec for +// 一个需要创建编解码器的对象 public class SomeObject { public SomeObject(String s, int i, boolean b) { /* ... */ } @@ -132,168 +133,172 @@ public class SomeObject { } ``` -#### Fields +#### 字段 + +`Instance` 可以使用 `#group` 定义多达 16 个字段。每个字段必须是一个定义了对象被制造的实例及对象类型的应用。满足此要求的最简单方式是使用 `Codec`,设置字段的解码名称,并设置用于编码字段的 getter。 -An `Instance` can define up to 16 fields using `#group`. Each field must be an application defining the instance the object is being made for and the type of the object. The simplest way to meet this requirement is by taking a `Codec`, setting the name of the field to decode from, and setting the getter used to encode the field. +字段可以使用 `#fieldOf` 从 `Codec` 创建,如果字段是必需的,或使用 `#optionalFieldOf` 创建,如果字段被包装在 `Optional` 中或默认存在。任一方法都需要包含编码对象中字段名称的字符串。然后可以使用 `#forGetter` 设置用于编码字段的 getter,它接受一个函数,该函数给定对象,返回字段数据。 -A field can be created from a `Codec` using `#fieldOf`, if the field is required, or `#optionalFieldOf`, if the field is wrapped in an `Optional` or defaulted. Either method requires a string containing the name of the field in the encoded object. The getter used to encode the field can then be set using `#forGetter`, taking in a function which given the object, returns the field data. +从那里 -From there, the resulting product can be applied via `#apply` to define how the instance should construct the object for the application. For ease of convenience, the grouped fields should be listed in the same order they appear in the constructor such that the function can simply be a constructor method reference. +,生成的产品可以通过 `#apply` 应用,以定义如何为应用构建对象。为了方便起见,分组字段应按照它们在构造函数中出现的顺序列出,以便函数可以简单地是一个构造函数方法引用。 ```java -public static final Codec RECORD_CODEC = RecordCodecBuilder.create(instance -> // Given an instance - instance.group( // Define the fields within the instance - Codec.STRING.fieldOf("s").forGetter(SomeObject::s), // String - Codec.INT.optionalFieldOf("i", 0).forGetter(SomeObject::i), // Integer, defaults to 0 if field not present - Codec.BOOL.fieldOf("b").forGetter(SomeObject::b) // Boolean - ).apply(instance, SomeObject::new) // Define how to create the object +public static final Codec RECORD_CODEC = RecordCodecBuilder.create(instance -> // 给定一个实例 + instance.group( // 在实例中定义字段 + Codec.STRING.fieldOf("s").forGetter(SomeObject::s), // 字符串 + Codec.INT.optionalFieldOf("i", 0).forGetter(SomeObject::i), // 整数,默认为 0(如果字段不存在) + Codec.BOOL.fieldOf("b").forGetter(SomeObject::b) // 布尔 + ).apply(instance, SomeObject::new) // 定义如何创建对象 ); ``` ```js -// Encoded SomeObject +// 编码后的 SomeObject { "s": "value", "i": 5, "b": false } -// Another encoded SomeObject +// 另一个编码后的 SomeObject { "s": "value2", - // i is omitted, defaults to 0 + // i 被省略,默认为 0 "b": true } ``` -### Transformers +### 转换器 -Codecs can be transformed into equivalent, or partially equivalent, representations through mapping methods. Each mapping method takes in two functions: one to transform the current type into the new type, and one to transform the new type back to the current type. This is done through the `#xmap` function. +编解码器可以通过映射方法转换成等效或部分等效的表现形式。每个映射方法接收两个函数:一个用于将当前类型转换为新类型,另一个用于将新类型转换回当前类型。这是通过 `#xmap` 函数完成的。 ```java -// A class +// 一个类 public class ClassA { public ClassB toB() { /* ... */ } } -// Another equivalent class +// 另一个等效的类 public class ClassB { public ClassA toA() { /* ... */ } } -// Assume there is some codec A_CODEC +// 假设存在某个编解码器 A_CODEC public static final Codec B_CODEC = A_CODEC.xmap(ClassA::toB, ClassB::toA); ``` -If a type is partially equivalent, meaning that there are some restrictions during conversion, there are mapping functions which return a `DataResult` which can be used to return an error state whenever an exception or invalid state is reached. +如果类型部分等效,即转换过程中存在某些限制,则存在返回 `DataResult` 的映射函数,可用于在遇到异常或无效状态时返回错误状态。 -Is A Fully Equivalent to B | Is B Fully Equivalent to A | Transform Method -:---: | :---: | :--- -Yes | Yes | `#xmap` -Yes | No | `#flatComapMap` -No | Yes | `#comapFlatMap` -No | No | `#flatXMap` +是否 A 完全等同于 B | 是否 B 完全等同于 A | 转换方法 +:---: | :---: | :--- +是 | 是 | `#xmap` +是 | 否 | `#flatComapMap` +否 | 是 | `#comapFlatMap` +否 | 否 | `#flatXMap` ```java -// Given an string codec to convert to a integer -// Not all strings can become integers (A is not fully equivalent to B) -// All integers can become strings (B is fully equivalent to A) +// 给定一个字符串编解码器转换为整数 +// 并非所有字符串都可以变成整数(A 与 B 非完全等效) +// 所有整数都可以变成字符串(B 与 A 完全等效) public static final Codec INT_CODEC = Codec.STRING.comapFlatMap( - s -> { // Return data result containing error on failure + s -> { // 返回失败时包含错误的数据结果 try { return DataResult.success(Integer.valueOf(s)); } catch (NumberFormatException e) { - return DataResult.error(s + " is not an integer."); + return DataResult.error(s + " 不是一个整数。"); } }, - Integer::toString // Regular function + Integer::toString // 常规函数 ); ``` ```js -// Will return 5 +// 将返回 5 "5" -// Will error, not an integer +// 将错误,不是整数 "value" ``` -#### Range Codecs +#### 范围编解码器 -Range codecs are an implementation of `#flatXMap` which returns an error `DataResult` if the value is not inclusively between the set minimum and maximum. The value is still provided as a partial result if outside the bounds. There are implementations for integers, floats, and doubles via `#intRange`, `#floatRange`, and `#doubleRange` respectively. +范围编解码器是 `#flatXMap` 的实现,如果值不在设定的最小值和最大值之间,则返回错误的 `DataResult`。如果值超出范围,仍会提供部分结果。分别有整数、浮点和双精度通过 `#intRange`、`#floatRange` 和 `#doubleRange` 实现。 ```java -public static final Codec RANGE_CODEC = Codec.intRange(0, 4); +public static final Codec RANGE_CODEC = Codec.intRange(0, 4); ``` ```js -// Will be valid, inside [0, 4] +// 将有效,在 [0, 4] 内 4 -// Will error, outside [0, 4] +// 将错误,在 [0, 4] 外 5 ``` -### Defaults +### 默认值 -If the result of encoding or decoding fails, a default value can be supplied instead via `Codec#orElse` or `Codec#orElseGet`. +如果编码或解码的结果失败,可以通过 `Codec#orElse` 或 `Codec#orElseGet` 提供默认值。 ```java -public static final Codec DEFAULT_CODEC = Codec.INT.orElse(0); // Can also be a supplied value via #orElseGet +public static final Codec DEFAULT_CODEC = Codec.INT.orElse(0); // 也可以通过 #orElseGet 提供值 ``` ```js -// Not an integer, defaults to 0 +// 不是整数,默认为 0 "value" ``` -### Unit +### 单位 -A codec which supplies an in-code value and encodes to nothing can be represented using `Codec#unit`. This is useful if a codec uses a non-encodable entry within the data object. +一个编解码器,提供代码中的值并不编码任何东西,可以使用 `Codec#unit` 表示。如果编解码器在数据对象中使用了一个不可编码的条目,这非常有用。 ```java public static final Codec> UNIT_CODEC = Codec.unit( - () -> ForgeRegistries.BLOCKS // Can also be a raw value + () -> ForgeRegistries.BLOCKS // 也可以是原始值 ); ``` ```js -// Nothing here, will return block registry codec +// 这里没有任何内容,将返回方块注册表编解码器 ``` -### List +### 列表 -A codec for a list of objects can be generated from an object codec via `Codec#listOf`. +可以从对象编解码器生成一个对象列表的编解码器,通过 `Codec#listOf` 实现。 ```java -// BlockPos#CODEC is a Codec +// BlockPos#CODEC 是一个 Codec public static final Codec> LIST_CODEC = BlockPos.CODEC.listOf(); ``` ```js -// Encoded List +// 编码的 List [ [1, 2, 3], // BlockPos(1, 2, 3) [4, 5, 6], // BlockPos(4, 5, 6) [7, 8, 9] // BlockPos(7, 8, 9) ] -``` +`` + +` -List objects decoded using a list codec are stored in an **immutable** list. If a mutable list is needed, a [transformer] should be applied to the list codec. +使用列表编解码器解码的列表对象存储在一个**不可变**列表中。如果需要可变列表,则应该对列表编解码器应用[变换器]。 -### Map +### 映射 -A codec for a map of keys and value objects can be generated from two codecs via `Codec#unboundedMap`. Unbounded maps can specify any string-based or string-transformed value to be a key. +可以通过两个编解码器生成键和值对象映射的编解码器,通过 `Codec#unboundedMap` 实现。无界映射可以指定任何基于字符串的或转换为字符串的值作为键。 ```java -// BlockPos#CODEC is a Codec +// BlockPos#CODEC 是一个 Codec public static final Codec> MAP_CODEC = Codec.unboundedMap(Codec.STRING, BlockPos.CODEC); ``` ```js -// Encoded Map +// 编码的 Map { "key1": [1, 2, 3], // key1 -> BlockPos(1, 2, 3) "key2": [4, 5, 6], // key2 -> BlockPos(4, 5, 6) @@ -301,17 +306,17 @@ public static final Codec> MAP_CODEC = Codec.unboundedMap( } ``` -Map objects decoded using a unbounded map codec are stored in an **immutable** map. If a mutable map is needed, a [transformer] should be applied to the map codec. +使用无界映射编解码器解码的映射对象存储在一个**不可变**映射中。如果需要可变映射,则应对映射编解码器应用[变换器]。 -:::caution -Unbounded maps only support keys that encode/decode to/from strings. A key-value [pair] list codec can be used to get around this restriction. +:::警告 +无界映射只支持可以编码/解码为字符串的键。可以使用键值[对]列表编解码器来绕过此限制。 ::: -### Pair +### 对 -A codec for pairs of objects can be generated from two codecs via `Codec#pair`. +可以通过两个编解码器生成对象对的编解码器,通过 `Codec#pair` 实现。 -A pair codec decodes objects by first decoding the left object in the pair, then taking the remaining part of the encoded object and decodes the right object from that. As such, the codecs must either express something about the encoded object after decoding (such as [records]), or they have to be augmented into a `MapCodec` and transformed into a regular codec via `#codec`. This can typically done by making the codec a [field] of some object. +对编解码器通过首先解码对中的左对象,然后取剩下的编码对象部分并从中解码右对象来解码对象。因此,编解码器必须在解码后表达关于编码对象的某些信息(如[记录]),或者必须被增强为 `MapCodec` 并通过 `#codec` 转换为常规编解码器。这通常可以通过将编解码器作为某个对象的[字段]来实现。 ```java public static final Codec> PAIR_CODEC = Codec.pair( @@ -321,22 +326,22 @@ public static final Codec> PAIR_CODEC = Codec.pair( ``` ```js -// Encoded Pair +// 编码的 Pair { - "left": 5, // fieldOf looks up 'left' key for left object - "right": "value" // fieldOf looks up 'right' key for right object + "left": 5, // fieldOf 查找左对象的 'left' 键 + "right": "value" // fieldOf 查找右对象的 'right' 键 } ``` -:::tip -A map codec with a non-string key can be encoded/decoded using a list of key-value pairs applied with a [transformer]. +:::tips +可以使用非字符串键的映射编解码器通过应用带有[变换器]的键值对列表来编码/解码。 ::: -### Either +### Either 编解码器 -A codec for two different methods of encoding/decoding some object data can be generated from two codecs via `Codec#either`. +可以通过两个编解码器生成一个针对某个对象数据的两种不同编解码方法的编解码器,使用 `Codec#either` 实现。 -An either codec attempts to decode the object using the first codec. If it fails, it attempts to decode using the second codec. If that also fails, then the `DataResult` will only contain the error from the second codec failure. +Either 编解码器首先尝试使用第一个编解码器解码对象。如果失败,它将尝试使用第二个编解码器。如果第二次也失败,那么 `DataResult` 将只包含第二次编解码器失败的错误。 ```java public static final Codec> EITHER_CODEC = Codec.either( @@ -346,32 +351,32 @@ public static final Codec> EITHER_CODEC = Codec.either( ``` ```js -// Encoded Either$Left +// 编码 Either$Left 5 -// Encoded Either$Right +// 编码 Either$Right "value" ``` -:::tip -This can be used in conjunction with a [transformer] to get a specific object from two different methods of encoding. +:::tips +这可以与[变换器]结合使用,从两种不同的编码方法中获取特定对象。 ::: -### Dispatch +### 分派编解码器 -Codecs can have subcodecs which can decode a particular object based upon some specified type via `Codec#dispatch`. This is typically used in registries which contain codecs, such as rule tests or block placers. +编解码器可以拥有可以根据某些指定类型解码特定对象的子编解码器,通过 `Codec#dispatch` 实现。这通常用于包含编解码器的注册表,如规则测试或方块放置器。 -A dispatch codec first attempts to get the encoded type from some string key (usually `type`). From there, the type is decoded, calling a getter for the specific codec used to decode the actual object. If the `DynamicOps` used to decode the object compresses its maps, or the object codec itself is not augmented into a `MapCodec` (such as records or fielded primitives), then the object needs to be stored within a `value` key. Otherwise, the object is decoded at the same level as the rest of the data. +分派编解码器首先尝试从某个字符串键(通常是 `type`)获取编码类型。从那里开始,解码类型,调用用于解码实际对象的特定编解码器的获取器。如果用于解码对象的 `DynamicOps` 压缩其映射,或者对象编解码器本身没有增强成 `MapCodec`(如记录或字段化原语),则对象需要存储在 `value` 键中。否则,对象将在与其余数据相同的级别上解码。 ```java -// Define our object +// 定义我们的对象 public abstract class ExampleObject { - // Define the method used to specify the object type for encoding + // 定义用于指定编码对象类型的方法 public abstract Codec type(); } -// Create simple object which stores a string +// 创建存储字符串的简单对象 public class StringObject extends ExampleObject { public StringObject(String s) { /* ... */ } @@ -379,14 +384,14 @@ public class StringObject extends ExampleObject { public String s() { /* ... */ } public Codec type() { - // A registered registry object + // 一个注册的注册表对象 // "string": // Codec.STRING.xmap(StringObject::new, StringObject::s) return STRING_OBJECT_CODEC.get(); } } -// Create complex object which stores a string and integer +// 创建存储字符串和整数的复杂对象 public class ComplexObject extends ExampleObject { public ComplexObject(String s, int i) { /* ... */ } @@ -396,7 +401,7 @@ public class ComplexObject extends ExampleObject { public int i() { /* ... */ } public Codec type() { - // A registered registry object + // 一个注册的注册表对象 // "complex": // RecordCodecBuilder.create(instance -> // instance.group( @@ -408,26 +413,26 @@ public class ComplexObject extends ExampleObject { } } -// Assume there is an IForgeRegistry> DISPATCH -public static final Codec = DISPATCH.getCodec() // Gets Codec> +// 假设存在一个 IForgeRegistry> DISPATCH +public static final Codec = DISPATCH.getCodec() // 获取 Codec> .dispatch( - ExampleObject::type, // Get the codec from the specific object - Function.identity() // Get the codec from the registry + ExampleObject::type, // 从特定对象获取编解码器 + Function.identity() // 从注册表获取编解码器 ); ``` ```js -// Simple object +// 简单对象 { - "type": "string", // For StringObject - "value": "value" // Codec type is not augmented from MapCodec, needs field + "type": "string", // 对于 StringObject + "value": "value" // 编解码器类型未从 MapCodec 增强,需要字段 } -// Complex object +// 复杂对象 { - "type": "complex", // For ComplexObject + "type": "complex", // 对于 ComplexObject - // Codec type is augmented from MapCodec, can be inlined + // 编解码器类型从 MapCodec 增强,可以内联 "s": "value", "i": 0 } From 5807ad8dd3a33f0fba20910c8f8d0545c71ae802 Mon Sep 17 00:00:00 2001 From: DaZiDian <113085496+DaZiDian@users.noreply.github.com> Date: Thu, 25 Apr 2024 19:32:32 +0800 Subject: [PATCH 44/87] Update nbt.md --- docs/datastorage/nbt.md | 74 +++++++++++++++++++++-------------------- 1 file changed, 38 insertions(+), 36 deletions(-) diff --git a/docs/datastorage/nbt.md b/docs/datastorage/nbt.md index c2f583180..f46adb011 100644 --- a/docs/datastorage/nbt.md +++ b/docs/datastorage/nbt.md @@ -1,42 +1,42 @@ # Named Binary Tag (NBT) -NBT is a format introduced in the earliest days of Minecraft, written by Notch himself. It is widely used throughout the Minecraft codebase for data storage. +NBT 是 Minecraft 最初时期由 Notch 本人引入的一种格式,它在整个 Minecraft 代码库中广泛用于数据存储。 -## Specification +## 规范 -The NBT spec is similar to the JSON spec, with a few differences: +NBT 规范与 JSON 规范类似,但有一些区别: -- Distinct types for bytes, shorts, longs and floats exist, suffixed by `b`, `s`, `l` and `f`, respectively, similar to how they would be represented in Java code. - - Doubles may also be suffixed with `d`, but this is not required, similar to Java code. The optional `i` suffix available in Java for integers is not permitted. - - The suffixes are not case-sensitive. So for example, `64b` is the same as `64B`, and `0.5F` is the same as `0.5f`. -- Booleans do not exist, they are instead represented by bytes. `true` becomes `1b`, `false` becomes `0b`. - - The current implementation treats all non-zero values as `true`, so `2b` would be treated as `true` as well. -- There is no `null` equivalent in NBT. -- Quotes around keys are optional. So a JSON property `"duration": 20` can become both `duration: 20` and `"duration": 20` in NBT. -- What is known in JSON as a sub-object is known in NBT as a **compound tag** (or just compound). -- NBT lists cannot mix and match types, unlike in JSON. The list type is determined by the first element, or defined in code. - - However, lists of lists can mix and match different list types. So a list of two lists, where the first one is a list of strings and the second one is a list of bytes, is allowed. -- There are special **array** types that are different from lists, but follow their scheme of containing elements in square brackets. There are three array types: - - Byte arrays, denoted by a `B;` at the beginning of the array. Example: `[B;0b,30b]` - - Integer arrays, denoted by a `I;` at the beginning of the array. Example: `[I;0,-300]` - - Long arrays, denoted by an `L;` at the beginning of the array. Example: `[L;0l,240l]` -- Trailing commas in lists, arrays and compound tags are allowed. +- 存在字节、短整型、长整型和浮点型的明确类型,分别以 `b`、`s`、`l` 和 `f` 为后缀,类似于在 Java 代码中的表示方式。 + - 双精度浮点型也可以用 `d` 后缀,但这不是必需的,类似于 Java 代码中的可选 `i` 后缀不被允许。 + - 后缀不区分大小写。例如,`64b` 与 `64B` 相同,`0.5F` 与 `0.5f` 相同。 +- 布尔类型不存在,而是用字节表示。`true` 变为 `1b`,`false` 变为 `0b`。 + - 当前实现将所有非零值视为 `true`,因此 `2b` 也会被视为 `true`。 +- NBT 中不存在 `null` 的等效物。 +- 键周围的引号是可选的。所以 JSON 属性 `"duration": 20` 在 NBT 中可以表示为 `duration: 20` 或 `"duration": 20`。 +- 在 JSON 中被称为子对象的东西,在 NBT 中被称为**复合标签**(或简称复合)。 +- NBT 列表不能混合匹配类型,不同于 JSON。列表类型由第一个元素确定,或在代码中定义。 + - 然而,列表的列表可以混合匹配不同的列表类型。因此,一个列表包含两个列表,其中第一个是字符串列表,第二个是字节列表,是允许的。 +- 存在特殊的**数组**类型,它们不同于列表,但遵循包含元素在方括号中的模式。有三种数组类型: + - 字节数组,以 `B;` 开头。例如:`[B;0b,30b]` + - 整数数组,以 `I;` 开头。例如:`[I;0,-300]` + - 长整型数组,以 `L;` 开头。例如:`[L;0l,240l]` +- 列表、数组和复合标签中允许有尾随逗号。 -## NBT Files +## NBT 文件 -Minecraft uses `.nbt` files extensively, for example for structure files in [datapacks][datapack]. Region files (`.mca`) that contain the contents of a region (i.e. a collection of chunks), as well as the various `.dat` files used in different places by the game, are NBT files as well. +Minecraft 广泛使用 `.nbt` 文件,例如 [datapacks][datapack] 中的结构文件。包含区域内容(即一系列区块)的区域文件(`.mca`),以及游戏中不同位置使用的各种 `.dat` 文件,也是 NBT 文件。 -NBT files are typically compressed with GZip. As such, they are binary files and cannot be edited directly. +NBT 文件通常使用 GZip 压缩。因此,它们是二进制文件,不能直接编辑。 -## NBT in Code +## NBT 在代码中的使用 -Like in JSON, all NBT objects are children of an enclosing object. So let's create one: +与 JSON 类似,所有 NBT 对象都是封闭对象的子对象。让我们创建一个: ```java CompoundTag tag = new CompoundTag(); ``` -We can now put our data into that tag: +现在我们可以将数据放入该标签: ```java tag.putInt("Color", 0xffffff); @@ -44,9 +44,9 @@ tag.putString("Level", "minecraft:overworld"); tag.putDouble("IAmRunningOutOfIdeasForNamesHere", 1d); ``` -Several helpers exist here, for example, `putIntArray` also has a convenience method that takes a `List` in addition to the standard variant that takes an `int[]`. +这里存在几个辅助方法,例如,`putIntArray` 也有一个便利方法,除了标准变体接受 `int[]` 外,还接受 `List`。 -Of course, we can also get values from that tag: +当然,我们也可以从该标签中获取值: ```java int color = tag.getInt("Color"); @@ -54,43 +54,45 @@ String level = tag.getString("Level"); double d = tag.getDouble("IAmRunningOutOfIdeasForNamesHere"); ``` -Number types will return 0 if absent. Strings will return `""` if absent. More complex types (lists, arrays, compounds) will throw an exception if absent. +如果不存在,数字类型将返回 0。字符串将返回 `""` 如果不存在。更复杂的类型(列表、数组、复合标签)如果不存在会抛出异常。 -As such, we want to safeguard by checking if a tag element exists: +因此,我们 + +希望通过检查标签元素是否存在来进行防护: ```java boolean hasColor = tag.contains("Color"); boolean hasColorMoreExplicitly = tag.contains("Color", Tag.TAG_INT); ``` -The `TAG_INT` constant is defined in `Tag`, which is the super interface for all tag types. Most tag types besides `CompoundTag` are mostly internal, for example `ByteTag` or `StringTag`, though the direct `CompoundTag#get` and `#put` methods can work with them if you ever stumble across some. +`TAG_INT` 常量在 `Tag` 中定义,这是所有标签类型的超接口。大多数标签类型除了 `CompoundTag` 外大多是内部的,例如 `ByteTag` 或 `StringTag`,尽管如果你偶然遇到一些,直接的 `CompoundTag#get` 和 `#put` 方法可以与它们一起工作。 -There is one obvious exception, though: `ListTag`s. Working with these is special because when getting a list tag through `CompoundTag#getList`, you must also specify the list type. So getting a list of strings, for example, would work like this: +不过,有一个明显的例外:`ListTag`。处理这些是特别的,因为当通过 `CompoundTag#getList` 获取列表标签时,你还必须指定列表类型。例如,获取字符串列表会像这样工作: ```java ListTag list = tag.getList("SomeListHere", Tag.TAG_STRING); ``` -Similarly, when creating a `ListTag`, you must also specify the list type during creation: +类似地,创建 `ListTag` 时,也必须在创建过程中指定列表类型: ```java ListTag list = new ListTag(List.of("Value1", "Value2"), Tag.TAG_STRING); ``` -Finally, working with `CompoundTag`s inside other `CompoundTag`s directly utilizes `CompoundTag#get` and `#put`: +最后,直接在其他 `CompoundTag` 中操作 `CompoundTag` 利用 `CompoundTag#get` 和 `#put`: ```java tag.put("Tag", new CompoundTag()); tag.get("Tag"); ``` -## Usages of NBT +## NBT 的用途 -NBT is used in a lot of places in Minecraft. Some of the most common examples include [`ItemStack`][itemstack]s, [`BlockEntity`][blockentity]s and `Entity`s. +NBT 在 Minecraft 中有很多用途。一些最常见的例子包括 [`ItemStack`][itemstack]、[`BlockEntity`][blockentity] 和 `Entity`。 -## See Also +## 另见 -- [NBT Format on the Minecraft Wiki][nbtwiki] +- [Minecraft Wiki 上的 NBT 格式][nbtwiki] [blockentity]: ../blockentities/index.md [datapack]: ../resources/server/index.md From f594161bca68d14fcba96e6ff43b0403cc653a88 Mon Sep 17 00:00:00 2001 From: DaZiDian <113085496+DaZiDian@users.noreply.github.com> Date: Thu, 25 Apr 2024 19:33:49 +0800 Subject: [PATCH 45/87] Update saveddata.md --- docs/datastorage/saveddata.md | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/docs/datastorage/saveddata.md b/docs/datastorage/saveddata.md index 614d1d171..1e2247519 100644 --- a/docs/datastorage/saveddata.md +++ b/docs/datastorage/saveddata.md @@ -1,40 +1,40 @@ -# Saved Data +# 保存的数据系统 -The Saved Data (SD) system can be used to save additional data on levels. +保存的数据(SD)系统可用于在各级别上保存额外数据。 -_If the data is specific to some block entities, chunks, or entities, consider using a [data attachment](attachments) instead._ +_如果数据特定于某些方块实体、区块或实体,请考虑使用[数据附件](attachments)。_ -## Declaration +## 声明 -Each SD implementation must subtype the `SavedData` class. There are two important methods to be aware of: +每个 SD 实现必须是 `SavedData` 类的子类型。有两个重要方法需要注意: -* `save`: Allows the implementation to write NBT data to the level. -* `setDirty`: A method that must be called after changing the data, to notify the game that there are changes that need to be written. If not called, `#save` will not get called and the original data will remain unchanged. +* `save`:允许实现将 NBT 数据写入级别。 +* `setDirty`:在更改数据后必须调用的方法,以通知游戏需要写入的更改。如果不调用,`#save` 将不会被调用,原始数据将保持不变。 -## Attaching to a Level +## 附加到级别 -Any `SavedData` is loaded and/or attached to a level dynamically. As such, if one is never created on a level, then it will not exist. +任何 `SavedData` 都是动态加载和/或附加到级别的。因此,如果一个级别上从未创建过,那么它将不存在。 -`SavedData`s are created and loaded from the `DimensionDataStorage`, which can be accessed by calling either `ServerChunkCache#getDataStorage` or `ServerLevel#getDataStorage`. From there, you can get or create an instance of your SD by calling `DimensionDataStorage#computeIfAbsent`. This will attempt to get the current instance of the SD if present or create a new one and load all available data. +`SavedData` 是从 `DimensionDataStorage` 创建和加载的,可以通过调用 `ServerChunkCache#getDataStorage` 或 `ServerLevel#getDataStorage` 访问。从那里,您可以通过调用 `DimensionDataStorage#computeIfAbsent` 来获取或创建您的 SD 实例。这将尝试获取当前存在的 SD 实例,或创建一个新实例并加载所有可用数据。 -`DimensionDataStorage#computeIfAbsent` takes in two arguments. The first is an instance of `SavedData.Factory`, which consists of a supplier to construct a new instance of the SD and a function to load NBT data into a SD and return it. The second argument is the name of the `.dat` file stored within the `data` folder for the implemented level. The name must be a valid filename and can not contain `/` or `\`. +`DimensionDataStorage#computeIfAbsent` 接受两个参数。第一个是 `SavedData.Factory` 的实例,它包括一个供应商来构建一个新的 SD 实例和一个函数,以将 NBT 数据加载到 SD 并返回它。第二个参数是实施级别的 `data` 文件夹中存储的 `.dat` 文件的名称。名称必须是有效的文件名,不能包含 `/` 或 `\`。 -For example, if a SD was named "example" within the Nether, then a file would be created at `.//DIM-1/data/example.dat` and would be implemented like so: +例如,如果一个 SD 在下界被命名为 "example",则会在 `.//DIM-1/data/example.dat` 创建一个文件,并且会像这样实现: ```java -// In some class +// 在某个类中 public ExampleSavedData create() { return new ExampleSavedData(); } public ExampleSavedData load(CompoundTag tag) { ExampleSavedData data = this.create(); - // Load saved data + // 加载保存的数据 return data; } -// In some method within the class +// 在类中的某个方法内 netherDataStorage.computeIfAbsent(new Factory<>(this::create, this::load), "example"); ``` -If a SD is not specific to a level, the SD should be attached to the Overworld, which can be obtained from `MinecraftServer#overworld`. The Overworld is the only dimension that is never fully unloaded and as such makes it perfect to store multi-level data on. +如果一个 SD 不特定于一个级别,那么 SD 应该附加到主世界,可以从 `MinecraftServer#overworld` 获取。主世界是唯一从不完全卸载的维度,因此非常适合在其上存储多级别数据。 From c3965a509e0744ffe7603c5b2433250ec1c83cf4 Mon Sep 17 00:00:00 2001 From: DaZiDian <113085496+DaZiDian@users.noreply.github.com> Date: Thu, 25 Apr 2024 19:41:33 +0800 Subject: [PATCH 46/87] Update index.md --- docs/gettingstarted/index.md | 60 +++++++++++++++++++----------------- 1 file changed, 31 insertions(+), 29 deletions(-) diff --git a/docs/gettingstarted/index.md b/docs/gettingstarted/index.md index 340e18813..f9995afec 100644 --- a/docs/gettingstarted/index.md +++ b/docs/gettingstarted/index.md @@ -1,52 +1,54 @@ -# Getting Started with NeoForge +# 开始使用 NeoForge -This section includes information about how to set up a NeoForge workspace, and how to run and test your mod. +本节包含有关如何设置 NeoForge 工作区以及如何运行和测试您的模组的信息。 -## Prerequisites +## 先决条件 -- Familiarity with the Java programming language, specifically its object-oriented, polymorphic, generic, and functional features. -- An installation of the Java 17 Development Kit (JDK) and 64-bit Java Virtual Machine (JVM). NeoForge recommends and officially supports the [Microsoft builds of OpenJDK][jdk], but any other JDK should work as well. +- 熟悉 Java 编程语言,特别是其面向对象、多态、泛型和功能性特征。 +- 安装 Java 17 开发工具包(JDK)和 64 位 Java 虚拟机(JVM)。NeoForge 推荐并官方支持 [Microsoft 的 OpenJDK 构建][jdk],但其他 JDK 也应该可以工作。 -:::caution -Make sure you are using a 64-bit JVM. One way of checking is to run `java -version` in a terminal. Issues may arise when using a 32-bit JVM, due to 32-bit JVMs running out of support for a lot of things. +:::warning +确保您正在使用 64 位 JVM。检查的一种方式是在终端运行 `java -version`。使用 32 位 JVM 可能会出现问题,因为很多东西已经不再支持 32 位 JVM 了。 ::: -- Familiarity with an Integrated Development Environment (IDE) of your choice. - - NeoForge officially supports [IntelliJ IDEA][intellij] and [Eclipse][eclipse], both of which have integrated Gradle support. However, any IDE can be used, ranging from Netbeans or Visual Studio Code to Vim or Emacs. -- Familiarity with [Git][git] and [GitHub][github]. This is technically not required, but it will make your life a lot easier. +- 熟悉您选择的集成开发环境(IDE)。 + - NeoForge 官方支持 [IntelliJ IDEA][intellij] 和 [Eclipse][eclipse],这两者都集成了 Gradle 支持。但是,可以使用任何 IDE,从 Netbeans 或 Visual Studio Code 到 Vim 或 Emacs 都可以。 +- 熟悉 [Git][git] 和 [GitHub][github]。技术上这不是必需的,但它会让您的生活变得更加轻松。 -## Setting Up the Workspace +## 设置工作区 -- Open the [Mod Developer Kit (MDK)][mdk] GitHub repository, click "Use this template" and clone the newly-created repository to your local machine. - - If you do not want to use GitHub, or if you want to get the template for an older commit or a non-default branch (which would be the case e.g. for older versions), you can also download the ZIP of the repository (under Code -> Download ZIP) and extract it. -- Open your IDE and import the Gradle project. Eclipse and IntelliJ IDEA will do this automatically for you. If you have an IDE that does not do this, you can also do it via the `gradlew` terminal command. - - When doing this for the first time, Gradle will download all dependencies of NeoForge, including Minecraft itself, and decompile them. This can take a fair amount of time (up to an hour, depending on your hardware and network strength). - - Whenever you make a change to the Gradle files, the Gradle changes will need to be reloaded, either through the "Reload Gradle" button in your IDE, or again through the `gradlew` terminal command. +- 打开 [Mod Developer Kit (MDK)][mdk] GitHub 仓库,点击“使用此模板”并将新创建的仓库克隆到您的本地机器。 + - 如果您不想使用 GitHub,或者想获取旧提交或非默认分支的模板(例如,对于旧版本),您也可以下载仓库的 ZIP 文件(在代码 -> 下载 ZIP 下)并解压。 +- 打开您的 IDE 并导入 Gradle 项目。Eclipse 和 IntelliJ IDEA 会为您自动完成此操作。如果您使用的 IDE 不支持此操作,您也可以通过 `gradlew` 终端命令来完成。 + - 首次进行此操作时,Gradle 将下载 NeoForge 的所有依赖项,包括 Minecraft 本身,并对其进行反编译。这可能需要相当长的时间(取决于您的硬件和网络强度,最多可达一个小时)。 + - 每当您对 Gradle 文件进行更改时,需要重新加载 Gradle 更改,可以通过您的 IDE 中的“重新加载 Gradle”按钮或再次通过 `gradlew` 终端命令来完成。 -## Customizing Your Mod Information +## 自定义您的模组信息 -Many of the basic properties of your mod can be changed in the `gradle.properties` file. This includes basic things like the mod name or the mod version. For more information, see the comments in the `gradle.properties` file, or see [the documentation of the `gradle.properties` file][properties]. +您的模组的许多基本属性都可以在 `gradle.properties` 文件中更改。这包括模组名称或模组版本等基本事项。有关更多信息,请参阅 `gradle.properties` 文件中的注释,或查看 [关于 `gradle.properties` 文件的文档][properties]。 -If you want to modify the build process beyond that, you can edit the `build.gradle` file. NeoGradle, the Gradle plugin for NeoForge, provides several configuration options, a few of which are explained by comments in the `build.gradle` files. For full documentation, see the [NeoGradle documentation][neogradle]. +如果您想进一步修改构建过程,可以编辑 `build.gradle` 文件。NeoGradle,NeoForge 的 Gradle 插件,提供了几个配置选项,其中一些选项通过 `build.gradle` 文件中的注释进行了解释。有关完整文档,请参阅 [NeoGradle 文档][neogradle]。 -:::caution -Only edit the `build.gradle` and `settings.gradle` files if you know what you are doing. All basic properties can be set via `gradle.properties`. +:::warning +只有在您知道自己在做什么时才编辑 `build.gradle` 和 `settings.gradle` 文件。所有基本属性都可以通过 `gradle.properties` 设置。 ::: -## Building and Testing Your Mod +## 构建和测试您的模组 -To build your mod, run `gradlew build`. This will output a file in `build/libs` with the name `-.jar`. `` and `` are properties set by the `build.gradle` and default to the `mod_id` and `mod_version` values in the `gradle.properties` file, respectively; this can be changed in the `build.gradle` if desired. The resulting JAR file can then be placed in the `mods` folder of a NeoForge-enabled Minecraft setup, or uploaded to a mod distribution platform. +要构建您的模组,请运行 `gradlew build`。这将在 `build/libs` 中输出一个名为 `-.jar` 的文件。`` 和 `` 是通过 `build.gradle` 设置的属性,默认为 `gradle.properties` 文件中的 `mod_id` 和 `mod_version` 值;如果需要,这可以在 `build.gradle` 中更改。然后可以将生成的 JAR 文件放置在启用 NeoForge 的 Minecraft 设置的 `mods` 文件夹中,或 -To run your mod in a test environment, you can either use the generated run configurations or use the associated tasks (e.g. `gradlew runClient`). This will launch Minecraft from the corresponding runs directory (e.g. `runs/client` or `runs/server`), along with any source sets specified. The default MDK includes the `main` source set, so any code written in `src/main/java` will be applied. +上传到模组分发平台。 -### Server Testing +要在测试环境中运行您的模组,您可以使用生成的运行配置或使用相关任务(例如 `gradlew runClient`)。这将从相应的运行目录(例如 `runs/client` 或 `runs/server`)启动 Minecraft,以及任何指定的源集。默认 MDK 包括 `main` 源集,因此在 `src/main/java` 中编写的任何代码都将被应用。 -If you are running a dedicated server, whether through the run configuration or `gradlew runServer`, the server will shut down immediately. You will need to accept the Minecraft EULA by editing the `eula.txt` file in the run directory. +### 服务器测试 -Once accepted, the server will load and become available under `localhost` (or `127.0.0.1` by default). However, you will still not able to join, because the server will be put into online mode by default, which requires authentication (that the Dev player does not have). To fix this, stop your server again and set the `online-mode` property in the `server.properties` file to `false`. Now, start your server, and you should be able to connect. +如果您正在运行一个专用服务器,无论是通过运行配置还是 `gradlew runServer`,服务器将立即关闭。您需要通过编辑运行目录中的 `eula.txt` 文件来接受 Minecraft EULA。 -:::tip -You should always test your mod in a dedicated server environment. This includes [client-only mods][client], as these should not do anything when loaded on the server. +一旦接受,服务器将加载并在 `localhost`(或默认的 `127.0.0.1`)下可用。然而,您仍然无法加入,因为服务器默认会进入在线模式,这需要认证(开发玩家没有)。要解决此问题,请再次停止您的服务器并将 `server.properties` 文件中的 `online-mode` 属性设置为 `false`。现在,启动您的服务器,您应该能够连接。 + +:::tips +您应该始终在专用服务器环境中测试您的模组。这包括[仅客户端模组][client],因为这些在服务器上加载时不应做任何事情。 ::: [client]: ../concepts/sides.md From fe17696147b8a698fd8160413c5b67d38b77f688 Mon Sep 17 00:00:00 2001 From: DaZiDian <113085496+DaZiDian@users.noreply.github.com> Date: Thu, 25 Apr 2024 19:44:46 +0800 Subject: [PATCH 47/87] Update modfiles.md --- docs/gettingstarted/modfiles.md | 156 +++++--------------------------- 1 file changed, 21 insertions(+), 135 deletions(-) diff --git a/docs/gettingstarted/modfiles.md b/docs/gettingstarted/modfiles.md index a22d2a059..733cda92c 100644 --- a/docs/gettingstarted/modfiles.md +++ b/docs/gettingstarted/modfiles.md @@ -1,169 +1,55 @@ # Mod Files -The mod files are responsible for determining what mods are packaged into your JAR, what information to display within the 'Mods' menu, and how your mod should be loaded in the game. +Mod文件负责确定哪些模组被打包到您的JAR文件中,显示在“Mods”菜单中的信息,以及您的模组在游戏中应如何加载。 ## gradle.properties -The `gradle.properties` file holds various common properties of your mod, such as the mod id or mod version. During building, Gradle reads the values in these files and inlines them in various places, such as the [mods.toml][modstoml] file. This way, you only need to change values in one place, and they are then applied everywhere for you. - -Most values are also explained as comments in [the MDK's `gradle.properties` file]. - -| Property | Description | Example | -|---------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-------------------------------------------| -| `org.gradle.jvmargs` | Allows you to pass extra JVM arguments to Gradle. Most commonly, this is used to assign more/less memory to Gradle. Note that this is for Gradle itself, not Minecraft. | `org.gradle.jvmargs=-Xmx3G` | -| `org.gradle.daemon` | Whether Gradle should use the daemon when building. | `org.gradle.daemon=false` | -| `org.gradle.debug` | Whether Gradle is set to debug mode. Debug mode mainly means more Gradle log output. Note that this is for Gradle itself, not Minecraft. | `org.gradle.debug=false` | -| `minecraft_version` | The Minecraft version you are modding on. Must match with `neo_version`. | `minecraft_version=1.20.2` | -| `minecraft_version_range` | The Minecraft version range this mod can use, as a [Maven Version Range][mvr]. Note that [snapshots, pre-releases and release candidates][mcversioning] are not guaranteed to sort properly, as they do not follow maven versioning. | `minecraft_version_range=[1.20.2,1.20.3)` | -| `neo_version` | The NeoForge version you are modding on. Must match with `minecraft_version`. See [NeoForge Versioning][neoversioning] for more information on how NeoForge versioning works. | `minecraft_version=1.20.2` | -| `neo_version_range` | The NeoForge version range this mod can use, as a [Maven Version Range][mvr]. | `minecraft_version_range=[1.20.2,1.20.3)` | -| `loader_version_range` | The version range of the mod loader this mod can use, as a [Maven Version Range][mvr]. Note that the loader versioning is decoupled from NeoForge versioning. | `loader_version_range=[1,)` | -| `mod_id` | The id of your mod. This should be something unique and memorable, as having two mods with the same id will prevent the game from loading. The mod id shows up in a lot of places, for example as the namespace for all your registered things, or as the namespace for your resource and data packs. | `mod_id=examplemod` | -| `mod_name` | The human-readable display name of your mod. By default, this can only be seen in the mod list, however, mods such as [JEI][jei] prominently display mod names in item tooltips as well. | `mod_name=Example Mod` | -| `mod_license` | The license your mod is provided under. It is suggested that this is set to the [SPDX identifier][spdx] you are using and/or a link to the license. You can visit https://choosealicense.com/ to help pick the license you want to use. | `mod_license=MIT` | -| `mod_version` | The version of your mod, shown in the mod list. See [the page on Versioning][versioning] for more information. | `mod_version=1.0` | -| `mod_group_id` | See [The Group ID][group]. | `mod_group_id=com.example.examplemod` | -| `mod_authors` | The authors of the mod, shown in the mod list. | `mod_authors=ExampleModder` | -| `mod_description` | The description of the mod, as a multiline string, shown in the mod list. Newline characters (`\n`) can be used and will be replaced properly. | `mod_authors=Example mod description.` | -| `pack_format_number` | The version number of your mod's data and resource pack. Mojang bumps this without something that could be considered a consistent scheme, so it's best to just look up what the current number is. As of Minecraft 1.20.2, the pack version is `18`. | `pack_version_number=18` | - -### The Group ID - -While the `group` property in the `build.gradle` is only necessary if you plan to publish your mod to a maven, it is considered good practice to always properly set this. This is done for you through the `gradle.properties`'s `mod_group_id` property. - -The group id should be set to your top-level package. See [Packaging][packaging] for more information. - -```text -// In your gradle.properties file -mod_group_id=com.example -``` - -The packages within your java source (`src/main/java`) should also now conform to this structure, with an inner package representing the mod id: +`gradle.properties`文件保存了您的模组的各种常见属性,例如模组ID或模组版本。在构建过程中,Gradle会读取这些文件中的值,并将它们内联到各种位置,如[mods.toml][modstoml]文件中。这样,您只需要在一个地方更改值,然后它们就会为您在所有地方应用。 -```text -com -- example (top-level package specified in group property) - - mymod (the mod id) - - MyMod.java (renamed ExampleMod.java) -``` +大多数值也在[MDK的`gradle.properties`文件]中以注释形式解释。 ## mods.toml -The `mods.toml` file, located at `src/main/resources/META-INF/mods.toml`, is a file in [TOML][toml] format that defines the metadata of your mod(s). It also contains additional information on how your mod(s) should be loaded into the game, as well as display information that is displayed within the 'Mods' menu. The [`mods.toml` file provided by the MDK][mdkmodstoml] contains comments explaining every entry, they will be explained here in more detail. - -The `mods.toml` can be separated into three parts: the non-mod-specific properties, which are linked to the mod file; the mod properties, with a section for each mod; and the dependency configurations, with a section for each mod's or mods' dependencies. Some of the properties associated with the `mods.toml` file are mandatory; mandatory properties require a value to be specified, otherwise an exception will be thrown. - -:::note -In the default MDK, Gradle replaces various properties in this file with the values specified in the `gradle.properties` file. For example, the line `license="${mod_license}"` means that the `license` field is replaced by the `mod_license` property from `gradle.properties`. Values that are replaced like this should be changed in the `gradle.properties` instead of changing them here. -::: - -### Non-Mod-Specific Properties - -Non-mod-specific properties are properties associated with the JAR itself, indicating how to load the mod(s) and any additional global metadata. - -| Property | Type | Default | Description | Example | -|:---------------------|:-------:|:-------------:|:--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------:|:-------------------------------------------------------------------------------| -| `modLoader` | string | **mandatory** | The language loader used by the mod(s). Can be used to support alternative language structures, such as Kotlin objects for the main file, or different methods of determining the entrypoint, such as an interface or method. NeoForge provides the Java loader [`"javafml"`][javafml] and the lowcode/nocode loader [`"lowcodefml"`][lowcodefml]. | `modLoader="javafml"` | -| `loaderVersion` | string | **mandatory** | The acceptable version range of the language loader, expressed as a [Maven Version Range][mvr]. For `javafml` and `lowcodefml`, this is currently version `1`. | `loaderVersion="[1,)"` | -| `license` | string | **mandatory** | The license the mod(s) in this JAR are provided under. It is suggested that this is set to the [SPDX identifier][spdx] you are using and/or a link to the license. You can visit https://choosealicense.com/ to help pick the license you want to use. | `license="MIT"` | -| `showAsResourcePack` | boolean | `false` | When `true`, the mod(s)'s resources will be displayed as a separate resource pack on the 'Resource Packs' menu, rather than being combined with the 'Mod resources' pack. | `showAsResourcePack=true` | -| `services` | array | `[]` | An array of services your mod uses. This is consumed as part of the created module for the mod from NeoForge's implementation of the Java Platform Module System. | `services=["net.neoforged.neoforgespi.language.IModLanguageProvider"]` | -| `properties` | table | `{}` | A table of substitution properties. This is used by `StringSubstitutor` to replace `${file.}` with its corresponding value. | `properties={"example"="1.2.3"}` (can then be referenced by `${file.example}`) | -| `issueTrackerURL` | string | *nothing* | A URL representing the place to report and track issues with the mod(s). | `"https://github.com/neoforged/NeoForge/issues"` | - -:::note -The `services` property is functionally equivalent to specifying the [`uses` directive in a module][uses], which allows [loading a service of a given type][serviceload]. +位于`src/main/resources/META-INF/mods.toml`的`mods.toml`文件是一个[TOML][toml]格式的文件,定义了您的模组的元数据。它还包含了有关如何将您的模组加载到游戏中的附加信息,以及显示在“Mods”菜单中的显示信息。[MDK提供的`mods.toml`文件][mdkmodstoml]包含解释每个条目的注释,这里将更详细地解释。 -Alternatively, it can be defined in a service file inside the `src/main/resources/META-INF/services` folder, where the file name is the fully-qualified name of the service, and the file content is the name of the service to load (see also [this example from the AtlasViewer mod][atlasviewer]). -::: - -### Mod-Specific Properties - -Mod-specific properties are tied to the specified mod using the `[[mods]]` header. This is an [array of tables][array]; all key/value properties will be attached to that mod until the next header. - -```toml -# Properties for examplemod1 -[[mods]] -modId = "examplemod1" - -# Properties for examplemod2 -[[mods]] -modId = "examplemod2" -``` - -| Property | Type | Default | Description | Example | -|:----------------|:-------:|:---------------------------:|:------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------:|:----------------------------------------------------------------| -| `modId` | string | **mandatory** | The unique identifier representing this mod. The id must match `^[a-z][a-z0-9_]{1,63}$` (a string 2-64 characters; starts with a lowercase letter; made up of lowercase letters, numbers, or underscores). | `modId="examplemod"` | -| `namespace` | string | value of `modId` | An override namespace for the mod. The namespace much match `^[a-z][a-z0-9_.-]{1,63}$` (a string 2-64 characters; starts with a lowercase letter; made up of lowercase letters, numbers, underscores, dots, or dashes). Currently unused. | `namespace="example"` | -| `version` | string | `"1"` | The version of the mod, preferably in a [variation of Maven versioning][versioning]. When set to `${file.jarVersion}`, it will be replaced with the value of the `Implementation-Version` property in the JAR's manifest (displays as `0.0NONE` in a development environment). | `version="1.20.2-1.0.0"` | -| `displayName` | string | value of `modId` | The display name of the mod. Used when representing the mod on a screen (e.g., mod list, mod mismatch). | `displayName="Example Mod"` | -| `description` | string | `'''MISSING DESCRIPTION'''` | The description of the mod shown in the mod list screen. It is recommended to use a [multiline literal string][multiline]. | `description='''This is an example.'''` | -| `logoFile` | string | *nothing* | The name and extension of an image file used on the mods list screen. The logo must be in the root of the JAR or directly in the root of the source set (e.g. `src/main/resources` for the main source set). | `logoFile="example_logo.png"` | -| `logoBlur` | boolean | `true` | Whether to use `GL_LINEAR*` (true) or `GL_NEAREST*` (false) to render the `logoFile`. In simpler terms, this means whether the logo should be blurred or not when trying to scale the logo. | `logoBlur=false` | -| `updateJSONURL` | string | *nothing* | A URL to a JSON used by the [update checker][update] to make sure the mod you are playing is the latest version. | `updateJSONURL="https://example.github.io/update_checker.json"` | -| `features` | table | `{}` | See [features]. | `features={java_version="[17,)"}` | -| `modproperties` | table | `{}` | A table of key/values associated with this mod. Unused by NeoForge, but is mainly for use by mods. | `modproperties={example="value"}` | -| `modUrl` | string | *nothing* | A URL to the download page of the mod. Currently unused. | `modUrl="https://neoforged.net/"` | -| `credits` | string | *nothing* | Credits and acknowledges for the mod shown on the mod list screen. | `credits="The person over here and there."` | -| `authors` | string | *nothing* | The authors of the mod shown on the mod list screen. | `authors="Example Person"` | -| `displayURL` | string | *nothing* | A URL to the display page of the mod shown on the mod list screen. | `displayURL="https://neoforged.net/"` | -| `displayTest` | string | `"MATCH_VERSION"` | See [sides]. | `displayTest="NONE"` | - -:::note -Some properties (`displayName` and `description`) can also be localized using language files. See [Translating Mod Metadata][i18n] for more detail. -::: +`mods.toml`可以分为三部分:非模组特定属性,这些属性与模组文件相关联;模组属性,每个模组有一个部分;依赖配置,每个模组的依赖项有一个部分。与`mods.toml`文件关联的某些属性是强制性的;强制性属性需要指定一个值,否则会抛出异常。 -#### Features +### 非模组特定属性 -The features system allows mods to demand that certain settings, software, or hardware are available when loading the system. When a feature is not satisfied, mod loading will fail, informing the user about the requirement. Currently, NeoForge provides the following features: +非模组特定属性与JAR本身相关联,指示如何加载模组以及任何额外的全局元数据。 -| Feature | Description | Example | -|:---------------:|:---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------:|:------------------------------------| -| `java_version` | The acceptable version range of the Java version, expressed as a [Maven Version Range][mvr]. This should be the supported version used by Minecraft. | `features={java_version="[17,)"}` | -| `openGLVersion` | The acceptable version range of the OpenGL version, expressed as a [Maven Version Range][mvr]. Minecraft requires OpenGL 3.2 or newer. If you want to require a newer OpenGL version, you can do so here. | `features={openGLVersion="[4.6,)"}` | +### 模组特定属性 -### Dependency Configurations +模组特定属性通过`[[mods]]`头部与指定的模组关联。这是一个[表的数组][array];所有键/值属性都将附加到该模组,直到下一个头部。 -Mods can specify their dependencies, which are checked by NeoForge before loading the mods. These configurations are created using the [array of tables][array] `[[dependencies.]]`, where `modid` is the identifier of the mod that consumes the dependency. +### 依赖配置 -| Property | Type | Default | Description | Example | -|:---------------|:------:|:-------------:|:---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:---------------------------------------------| -| `modId` | string | **mandatory** | The identifier of the mod added as a dependency. | `modId="jei"` | -| `type` | string | `"required"` | Specifies the nature of this dependency: `"required"` is the default and prevents the mod from loading if this dependency is missing; `"optional"` will not prevent the mod from loading if the dependency is missing, but still validates that the dependency is compatible; `"incompatible"` prevents the mod from loading if this dependency is present; `"discouraged"` still allows the mod to load if the dependency is present, but presents a warning to the user. | `type="incompatible"` | -| `reason` | string | *nothing* | An optional user-facing message to describe why this dependency is required, or why it is incompatible. | -| `versionRange` | string | `""` | The acceptable version range of the language loader, expressed as a [Maven Version Range][mvr]. An empty string matches any version. | `versionRange="[1, 2)"` | -| `ordering` | string | `"NONE"` | Defines if the mod must load before (`"BEFORE"`) or after (`"AFTER"`) this dependency. If the ordering does not matter, return `"NONE"` | `ordering="AFTER"` | -| `side` | string | `"BOTH"` | The [physical side][sides] the dependency must be present on: `"CLIENT"`, `"SERVER"`, or `"BOTH"`. | `side="CLIENT"` | -| `referralUrl` | string | *nothing* | A URL to the download page of the dependency. Currently unused. | `referralUrl="https://library.example.com/"` | - -:::danger -The `ordering` of two mods may cause a crash due to a cyclic dependency, for example if mod A must load `"BEFORE"` mod B and at the same time, mod B must load `"BEFORE"` mod A. -::: +模组可以指定它们的依赖关系,NeoForge在加载模组之前会检查这些配置。这些配置是使用`[[dependencies.]]`创建的,其中`modid`是消耗依赖项的模组的标识符。 -## Mod Entrypoints +## Mod 入口点 -Now that the `mods.toml` is filled out, we need to provide an entrypoint for the mod. Entrypoints are essentially the starting point for executing the mod. The entrypoint itself is determined by the language loader used in the `mods.toml`. +现在`mods.toml`已经填写完毕,我们需要为模组提供一个入口点。入口点本质上是执行模组的起点。入口点本身由`mods.toml`中使用的语言加载器确定。 -### `javafml` and `@Mod` +### `javafml` 和 `@Mod` -`javafml` is a language loader provided by NeoForge for the Java programming language. The entrypoint is defined using a public class with the `@Mod` annotation. The value of `@Mod` must contain one of the mod ids specified within the `mods.toml`. From there, all initialization logic (e.g. [registering events][events] or [adding `DeferredRegister`s][registration]) can be specified within the constructor of the class. +`javafml`是NeoForge为Java编程语言提供的语言加载器。入口点是使用带有`@Mod`注解的公共类定义的。`@Mod`的值必须包含`mods.toml`中指定的模组ID之一。从那里开始,所有初始化逻辑(例如[注册事件][events]或[添加`DeferredRegister`][registration])可以在类的构造函数中指定。 ```java -@Mod("examplemod") // Must match a mod id in the mods.toml +@Mod("examplemod") // 必须与mods.toml中的模组ID匹配 public class Example { - public Example(IEventBus modBus) { // The parameter is the mod-specific event bus, needed e.g. for registration and events - // Initialize logic here + public Example(IEventBus modBus) { // 参数是模组特定的事件总线,例如用于注册和事件 + // 在这里初始化逻辑 } } ``` -:::note -There must be a 1-to-1 matching of mods in the `mods.toml` file and `@Mod` entrypoints. This means that for every mod defined, there must be exactly one `@Mod` annotation with that mod's id. +:::tips +`mods.toml`文件中的模组和`@Mod`入口点必须有1对1的匹配。这意味着对于每个定义的模组,必须有一个带有该模组ID的`@Mod`注解。 ::: ### `lowcodefml` -`lowcodefml` is a language loader used as a way to distribute datapacks and resource packs as mods without the need of an in-code entrypoint. It is specified as `lowcodefml` rather than `nocodefml` for minor additions in the future that might require minimal coding. +`lowcodefml`是一种语言加载器,用作以模组形式分发数据包和资源包,而无需代码内入口点。它被指定为`lowcodefml`而不是`nocodefml`,因为未来可能需要最小的代码添加。 [array]: https://toml.io/en/v1.0.0#array-of-tables [atlasviewer]: https://github.com/XFactHD/AtlasViewer/blob/1.20.2/neoforge/src/main/resources/META-INF/services/xfacthd.atlasviewer.platform.services.IPlatformHelper From f37c7253f0d0068cae8551c16a59a26a6e42739e Mon Sep 17 00:00:00 2001 From: DaZiDian <113085496+DaZiDian@users.noreply.github.com> Date: Thu, 25 Apr 2024 19:48:34 +0800 Subject: [PATCH 48/87] Update structuring.md --- docs/gettingstarted/structuring.md | 82 +++++++++++++++--------------- 1 file changed, 42 insertions(+), 40 deletions(-) diff --git a/docs/gettingstarted/structuring.md b/docs/gettingstarted/structuring.md index c91ba153b..113e5ee52 100644 --- a/docs/gettingstarted/structuring.md +++ b/docs/gettingstarted/structuring.md @@ -1,78 +1,80 @@ -# Structuring Your Mod +# 结构化您的模组 -Structured mods are beneficial for maintenance, making contributions, and providing a clearer understanding of the underlying codebase. Some of the recommendations from Java, Minecraft, and NeoForge are listed below. +结构化模组有利于维护、贡献并提供对底层代码库的更清晰理解。以下是一些建议,源自 Java、Minecraft 和 NeoForge。 :::note -You do not have to follow the advice below; you can structure your mod any way you see fit. However, it is still highly recommended to do so. +您不必遵循下面的建议;您可以按照自己认为合适的方式结构化您的模组。然而,还是强烈建议这样做。 ::: -## Packaging +## 包结构 -When structuring your mod, pick a unique, top-level package structure. Many programmers will use the same name for different classes, interfaces, etc. Java allows classes to have the same name as long as they are in different packages. As such, if two classes have the same package with the same name, only one would be loaded, most likely causing the game to crash. +在构建您的模组时,选择一个独特的顶级包结构。许多程序员会为不同的类、接口等使用相同的名称。Java允许在不同的包中有相同名称的类。因此,如果两个类有相同的包和相同的名称,只有一个类会被加载,很可能会导致游戏崩溃。 ``` a.jar - com.example.ExampleClass b.jar - - com.example.ExampleClass // This class will not normally be loaded + - com.example.ExampleClass // 这个类通常不会被加载 ``` -This is even more relevant when it comes to loading modules. If there are class files in two packages under the same name in separate modules, this will cause the mod loader to crash on startup since mod modules are exported to the game and other mods. +当涉及到加载模块时,这一点尤其相关。如果两个包在不同的模块下有同名的类文件,这将导致模组加载器在启动时崩溃,因为模组模块被导出到游戏和其他模组。 ``` -module A - - package X - - class I - - class J -module B - - package X // This package will cause the mod loader to crash, as there already is a module with package X being exported - - class R - - class S - - class T +模块 A + - 包 X + - 类 I + - 类 J +模块 B + - 包 X // 这个包将导致模组加载器崩溃,因为已经有一个模块导出了包 X + - 类 R + - 类 S + - 类 T ``` -As such, your top level package should be something that you own: a domain, email address, a (subdomain of a) website, etc. It can even be your name or username as long as you can guarantee that it will be uniquely identifiable within the expected target. Furthermore, the top-level package should also match your [group id][group]. +因此,您的顶级包应该是您拥有的东西:域名、电子邮件地址、网站(或子域)等。它甚至可以是您的名字或用户名,只要您能保证它在预期目标中具有唯一可识别性。此外,顶级包还应与您的[group id][group]匹配。 -| Type | Value | Top-Level Package | -|:---------:|:-----------------:|:--------------------| -| Domain | example.com | `com.example` | -| Subdomain | example.github.io | `io.github.example` | -| Email | example@gmail.com | `com.gmail.example` | +| 类型 | 值 | 顶级包 | +|:---------:|:--------------:|:----------------------| +| 域名 | example.com | `com.example` | +| 子域名 | example.github.io | `io.github.example` | +| 电邮 | example@gmail.com | `com.gmail.example` | -The next level package should then be your mod's id (e.g. `com.example.examplemod` where `examplemod` is the mod id). This will guarantee that, unless you have two mods with the same id (which should never be the case), your packages should not have any issues loading. +下一级包应该是您的模组的ID(例如,`com.example.examplemod`,其中`examplemod`是模组ID)。这将保证,除非您有两个模组ID相同的模组(这种情况永远不应该发生),否则您的包不应该有任何加载问题。 -You can find some additional naming conventions on [Oracle's tutorial page][naming]. +您可以在[Oracle的教程页面][naming]上找到一些额外的命名约定。 -### Sub-package Organization +### 子包组织 -In addition to the top-level package, it is highly recommend to break your mod's classes between subpackages. There are two major methods on how to do so: +除了顶级包,强烈建议将模组的类分配到子包中。有两种主要的方法来做到这一点: -* **Group By Function**: Make subpackages for classes with a common purpose. For example, blocks can be under `block`, items under `item`, entities under `entity`, etc. Minecraft itself uses a similar structure (with some exceptions). -* **Group By Logic**: Make subpackages for classes with a common logic. For example, if you were creating a new type of crafting table, you would put its block, menu, item, and more under `feature.crafting_table`. +* **按功能分组**:为具有共同目的的类制作子包。例如,可以将方块放在`block`下,物品放在`item`下,实体放在`entity`下等。Minecraft本身使用类似的结构(有一些例外)。 +* **按逻辑分组**:为具有共同逻辑的类制作子包。例如,如果您正在创建一个新类型的工作台,您可以将其方块、菜单、物品等放在`feature.crafting_table`下。 -#### Client, Server, and Data Packages +#### 客户端、服务器和数据包 -In general, code only for a given side or runtime should be isolated from the other classes in a separate subpackage. For example, code related to [data generation][datagen] should go in a `data` package, and code only on the dedicated server should go in a `server` package. +通常,只针对给定侧或运行时的代码应该与其他类隔离在一个单独的子包中。例如,与[数据生成][datagen]相关的代码应该放在`data`包中,只在专用服务器上的代码应该放在`server`包中。 -It is highly recommended that [client-only code][sides] should be isolated in a `client` subpackage. This is because dedicated servers have no access to any of the client-only packages in Minecraft and will crash if your mod tries to access them anyway. As such, having a dedicated package provides a decent sanity check to verify you are not reaching across sides within your mod. +强烈建议将[仅客户端代码][sides] -## Class Naming Schemes +隔离在`client`子包中。这是因为专用服务器无法访问Minecraft中的任何客户端专用包,并且如果您的模组试图访问它们,将会崩溃。因此,拥有一个专用包提供了一个很好的检查方法,以验证您没有在模组内跨侧访问。 -A common class naming scheme makes it easier to decipher the purpose of the class or to easily locate specific classes. +## 类命名方案 -Classes are commonly suffixed with its type, for example: +常见的类命名方案使得更容易理解类的用途或轻松找到特定类。 -* An `Item` called `PowerRing` -> `PowerRingItem`. -* A `Block` called `NotDirt` -> `NotDirtBlock`. -* A menu for an `Oven` -> `OvenMenu`. +类通常以其类型为后缀,例如: + +* 一个名为 `PowerRing` 的 `Item` -> `PowerRingItem`。 +* 一个名为 `NotDirt` 的 `Block` -> `NotDirtBlock`。 +* 一个 `Oven` 的菜单 -> `OvenMenu`。 :::tip -Mojang typically follows a similar structure for all classes except entities. Those are represented by just their names (e.g. `Pig`, `Zombie`, etc.). +Mojang 通常遵循类似的结构来命名所有类,除了实体。这些实体只用它们的名字表示(例如,`Pig`,`Zombie`等)。 ::: -## Choose One Method from Many +## 从多种方法中选择一种 -There are many methods for performing a certain task: registering an object, listening for events, etc. It's generally recommended to be consistent by using a single method to accomplish a given task. This improves readability and avoids weird interactions or redundancies that may occur (e.g. your event listener running twice). +执行特定任务有许多方法:注册对象、监听事件等。通常建议使用单一方法来完成给定任务。这可以提高可读性,并避免可能发生的奇怪交互或冗余(例如,您的事件监听器运行两次)。 [group]: index.md#the-group-id [naming]: https://docs.oracle.com/javase/tutorial/java/package/namingpkgs.html From 69b93efb74ac01478bb107f32037726b650975a4 Mon Sep 17 00:00:00 2001 From: DaZiDian <113085496+DaZiDian@users.noreply.github.com> Date: Thu, 25 Apr 2024 20:10:34 +0800 Subject: [PATCH 49/87] Update versioning.md --- docs/gettingstarted/versioning.md | 78 ++++++++++++++++--------------- 1 file changed, 41 insertions(+), 37 deletions(-) diff --git a/docs/gettingstarted/versioning.md b/docs/gettingstarted/versioning.md index b6203418d..c17930719 100644 --- a/docs/gettingstarted/versioning.md +++ b/docs/gettingstarted/versioning.md @@ -1,84 +1,88 @@ -# Versioning +# 版本管理 -This article will break down how versioning works in Minecraft and NeoForge, and will give some recommendations for mod versioning as well. +本文将详细解释 Minecraft 和 NeoForge 的版本管理方式,并为模组版本提供一些建议。 ## Minecraft -Minecraft uses [semantic versioning][semver]. Semantic versioning, or "semver" for short, has the format `major.minor.patch`. So for example, Minecraft 1.20.2 has the major version 1, the minor version 20 and the patch version 2. +Minecraft 使用[语义化版本控制][semver](semantic versioning,简称 "semver"),格式为 `major.minor.patch`。例如,Minecraft 1.20.2 的主版本号(major)为 1,次版本号(minor)为 20,修订号(patch)为 2。 -Minecraft has used `1` as the major version since 2011, when Minecraft 1.0 was introduced. Before that, the versioning scheme changed often, and there were versions like `a1.1` (Alpha 1.1), `b1.7.3` (Beta 1.7.3) or even the `infdev` versions, which didn't follow a clear versioning scheme at all. Due to the `1` major version holding up for over a decade now, and due to the in-joke that is Minecraft 2, it is generally considered unlikely that this is ever going to change. +自从 2011 年 Minecraft 1.0 发布以来,Minecraft 的主版本号一直使用 `1`。在此之前,版本号方案经常变化,有诸如 `a1.1`(Alpha 1.1)、`b1.7.3`(Beta 1.7.3)甚至 `infdev` 版本,这些版本根本没有遵循明确的版本控制方案。由于 `1` 作为主版本号已经持续了十多年,并且鉴于 Minecraft 2 的内部笑话,通常认为这一情况不太可能改变。 -### Snapshots +### 快照版 -Snapshots deviate from the standard semver scheme. They are labeled as `YYwWWa`, where `YY` represents the last two digits of the year (e.g. `23`) and `WW` represents the week of that year (e.g. `01`). So for example, snapshot `23w01a` is the snapshot released in the first week of 2023. +快照版偏离了标准的 semver 方案。它们被标记为 `YYwWWa`,其中 `YY` 代表年份的最后两位数字(例如 `23`),`WW` 代表那年的周数(例如 `01`)。例如,快照 `23w01a` 是 2023 年第一周发布的快照。 -The `a` suffix exists for occasions where two snapshots get released in the same week (where the second snapshot would then be named something like `23w01b`). Mojang has occasionally used this in the past. The alternative suffix has also been used for snapshots like `20w14infinite`, which was the [2020 infinite dimensions April Fool's joke][infinite]. +`a` 后缀用于同一周发布两个快照的情况(第二个快照则被命名为 `23w01b`)。Mojang 过去偶尔使用过这种方式。此外,还有像 `20w14infinite` 这样的快照,它是[2020 年无限维度愚人节玩笑][infinite]。 -### Pre-releases and Release Candidates +### 预发布和候选发布版本 -When a snapshot cycle is coming completion, Mojang starts releasing so-called pre-releases. Pre-releases are deemed feature-complete for a version and focus solely on bugfixes. They use the semver notation for the version it is for, suffixed by `-preX`. So for example, the first pre-release for 1.20.2 was named `1.20.2-pre1`. There can be and usually are multiple pre-releases, which are accordingly suffixed with `-pre2`, `-pre3`, etc. +当快照周期接近完成时,Mojang 开始发布所谓的预发布版本。预发布版本被视为功能完整的版本,专注于修复bug。它们使用 semver 格式并附加 `-preX` 后缀。例如,1.20.2 的第一个预发布版本被命名为 `1.20.2-pre1`。通常会有多个预发布版本,相应地使用 `-pre2`、`-pre3` 等后缀。 -Similarly, when the pre-release cycle completes, Mojang releases Release Candidate 1 (suffixing the version with `-rc1`, for example `1.20.2-rc1`). Mojang aims to have one release candidate that they can release if no further bugs occur. However, if an unexpected bug occurs, then there can also be an `-rc2`, `-rc3`, etc. version, similar to pre-releases. +类似地,当预发布周期完成时,Mojang 发布第一个候选发布版本(后缀为 `-rc1`,例如 `1.20.2-rc1`)。Mojang 的目标是发布一个候选版本,如果没有进一步的 bug 出现,则可以发布该版本。然而,如果出现意外的 bug,则可能会有 `-rc2`、`-rc3` 等版本,类似于预发布版本。 ## NeoForge -NeoForge uses an adapted semver system: The major version is Minecraft's minor version, the minor version is Minecraft's patch version, and the patch version is the "actual" NeoForge version. So for example, NeoForge 20.2.59 is the 60th version (we start at 0) for Minecraft 1.20.2. The `1` at the beginning is omitted because it is very unlikely that it will ever change, see [above][minecraft] for why that is the case. +NeoForge 使用一种调整过的 semver 系统:主版本号是 Minecraft 的次版本号,次版本号是 Minecraft 的修订号,而修订号则是“实际”的 NeoForge 版本。例如,NeoForge 20.2.59 是 Minecraft 1.20.2 的第 60 版(从 0 开始计数)。由于 `1` 作为开头的数字不太可能改变,请参见[上文][minecraft]了解原因。 -A few places in NeoForge also use [Maven version ranges][mvr], for example the Minecraft and NeoForge version ranges in the [`mods.toml`][modstoml] file. These are mostly, but not fully compatible with semver (the `pre`-tag is not considered by it, for example). +NeoForge 的一些位置还使用了[Maven 版本范围][mvr],例如 [`mods.toml`][modstoml] 文件中的 Minecraft 和 NeoForge 版本范围。这些主要与 semver 兼容,但有些例外(例如,它不考虑 `pre` 标签)。 -## Mods +## 模组 -There is no definitive best versioning system. Different styles of development, scopes of projects, etc. all influence the decision of what versioning system to use. Sometimes, versioning system can also be combined. This section attempts to give an overview over some commonly used versioning systems, with real-life examples. +没有最佳的版本控制系统。不同的开发风格、项目范围等都会影响选择哪种版本控制系统的决定。有时,也可以组合使用多种版本控制系统。本节试图概述一些 -Usually, a mod's file name looks like `modid-.jar`. So if our mod id is `examplemod` and our version is `1.2.3`, our mod file would be named `examplemod-1.2.3.jar`. +常用的版本控制系统,并提供现实生活中的示例。 + +通常,模组的文件名看起来像 `modid-.jar`。所以如果我们的模组ID是 `examplemod`,版本是 `1.2.3`,我们的模组文件将被命名为 `examplemod-1.2.3.jar`。 :::note -Versioning systems are suggestions, rather than strictly enforced rules. This is especially true with regard to when the version is changed ("bumped"), and in what way. If you want to use a different versioning system, nobody is going to stop you. +版本控制系统是建议,而不是严格执行的规则。特别是关于何时更改("bump")版本,以及以何种方式更改。如果您想使用不同的版本控制系统,没有人会阻止您。 ::: -### Semantic Versioning +### 语义化版本控制 -Semantic versioning ("semver") consists of three parts: `major.minor.patch`. The major version is bumped when major changes are made to the codebase, which usually correlates with major new features and bugfixes. The minor version is bumped when minor features are introduced, and patch bumps happen when an update only includes bugs. +语义化版本控制("semver")包括三个部分:`major.minor.patch`。当对代码库进行重大更改时,主版本号会提升,这通常与重大新功能和错误修复相关。次版本号在引入次要功能时提升,修订号只包括错误修复时提升。 -It is generally agreed upon that any version `0.x.x` is a development version, and with the first (full) release, the version should be bumped to `1.0.0`. +通常认为任何 `0.x.x` 版本都是开发版本,而第一个(完整)发布版本应该提升到 `1.0.0`。 -The "minor for features, patch for bugfixes" rule is often disregarded in practice. A popular example for this is Minecraft itself, which does major features through the minor version number, minor features through the patch number, and bugfixes in snapshots (see above). +"次要功能提升次版本号,错误修复提升修订号" 的规则在实践中经常被忽视。一个流行的例子是 Minecraft 本身,它通过次版本号进行重大功能更新,通过修订号进行次要功能更新,并在快照中修复错误(见上文)。 -Depending on how often a mod is updated, these numbers can be smaller or larger. For example, [Supplementaries][supplementaries] is on version `2.6.31` (at the time of writing). Triple- or even quadruple-digit numbers, especially in the `patch`, are absolutely possible. +根据模组更新的频率,版本号可能会有所增减。例如,[Supplementaries][supplementaries]目前的版本为`2.6.31`(撰写本文时)。在`patch`版本中,出现三位或四位数字的情况完全有可能。 -### "Reduced" and "Expanded" Semver +### “简化”与“扩展”语义化版本控制 -Sometimes, semver can be seen with only two numbers. This is a sort of "reduced" semver, or "2-part" semver. Their version numbers only have a `major.minor` scheme. This is commonly used by small mods that only add a few simple objects and thus rarely need updates (except Minecraft version updates), often staying at version `1.0` forever. +有时候,我们可以看到只有两个数字的语义化版本控制,这种是一种“简化”语义化版本控制,或称为“2部分”语义化版本控制。这种版本号只包含`major.minor`模式。它通常用于只添加几个简单物体的小型模组,这类模组很少需要更新(除了Minecraft版本更新),常常永远停留在`1.0`版本。 -"Expanded" semver, or "4-part" semver, has four numbers (so something like `1.0.0.0`). Depending on the mod, the format can be `major.api.minor.patch`, or `major.minor.patch.hotfix`, or something different entirely - there is no standard way to do it. +而“扩展”语义化版本控制,或称为“4部分”语义化版本控制,包括四个数字(比如`1.0.0.0`)。根据模组的不同,其格式可能是`major.api.minor.patch`,或`major.minor.patch.hotfix`,或是完全不同的格式——没有统一的标准方式。 -For `major.api.minor.patch`, the `major` version is decoupled from the `api` version. This means that the `major` (feature) bit and the `api` bit can be bumped independently. This is commonly used by mods that expose an API for other modders to use. For example, [Mekanism][mekanism] is currently on version 10.4.5.19 (at the time of writing). +对于`major.api.minor.patch`,`major`版本与`api`版本是分离的。这意味着`major`(功能)位和`api`位可以独立提升。这种方式通常用于那些提供API供其他模组开发者使用的模组。例如,[Mekanism][mekanism]当前的版本是10.4.5.19(撰写本文时)。 -For `major.minor.patch.hotfix`, the patch level is split into two. This is the approach used by the [Create][create] mod, which is currently on version 0.5.1f (at the time of writing). Note that Create denotes the hotfix as a letter instead of a fourth number, in order to stay compatible with regular semver. +对于`major.minor.patch.hotfix`,则是将修订级别分为两部分。这是[Create][create]模组使用的方法,目前版本为0.5.1f(撰写本文时)。注意,Create模组将hotfix表示为一个字母,而不是第四个数字,以保持与常规语义化版本控制的兼容。 :::info -Reduced semver, expanded semver, 2-part semver and 4-part semver are not official terms or standardized formats in any way. +简化语义化版本控制、扩展语义化版本控制、2部分语义化版本控制和4部分语义化版本控制并非官方术语或标准化格式。 ::: -### Alpha, Beta, Release +### Alpha、Beta、Release阶段 + +像Minecraft本身一样,模组开发通常也会经历软件工程中熟知的`alpha`/`beta`/`release`阶段,其中`alpha`代表不稳定/实验版本(有时也称为`experimental`或`snapshot`),`beta`代表半稳定版本,而`release`则代表稳定版本(有时用`stable`代替`release`)。 -Like Minecraft itself, modding is often done in the classical `alpha`/`beta`/`release` stages known from software engineering, where `alpha` denotes an unstable/experimental version (sometimes also called `experimental` or `snapshot`), `beta` denotes a semi-stable version, and `release` denotes a stable version (sometimes called `stable` instead of `release`). +一些模组利用它们的主要版本号来表示Minecraft版本的更新。例如,[JEI][jei]使用`13.x.x.x`表示Minecraft 1.19.2,`14.x.x.x`表示1.19.4,以及`15.x.x.x`表示1.20.1(不存在1.19.3和1.20.0的版本)。其他一些模组则在模组名称后附加标签,例如[Minecolonies][minecolonies]模组,当前版本为`1.1.328-BETA`(撰写本文时)。 -Some mods use their major version to denote a Minecraft version bump. An example of this is [JEI][jei], which uses `13.x.x.x` for Minecraft 1.19.2, `14.x.x.x` for 1.19.4, and `15.x.x.x` for 1.20.1 (there are no versions for 1.19.3 and 1.20.0). Others append the tag to the mod name, for example the [Minecolonies][minecolonies] mod, which is on `1.1.328-BETA` at the time of writing. +### 包含Minecraft版本 -### Including the Minecraft Version +在模组文件名中包含其适用的Minecraft版本是常见做法。这使得最终用户更容易确定模组适用于哪个版本的Minecraft。这通常发生在模组版本之前或之后,前者比后者更常见。例如,JEI的最新版本`16.0.0.28`(撰写本文时) -It is common to include the Minecraft version a mod is for in the filename. This makes it easier for end users to easily find out what Minecraft version a mod is for. A common place for this is either before or after the mod version, with the former being more widespread than the latter. For example, JEI version `16.0.0.28` (latest at the time of writing) for 1.20.2 would become `jei-1.20.2-16.0.0.28` or `jei-16.0.0.28-1.20.2`. +适用于1.20.2,可能表示为`jei-1.20.2-16.0.0.28`或`jei-16.0.0.28-1.20.2`。 -### Including the Mod Loader +### 包含模组加载器 -As you probably know, NeoForge is not the only mod loader out there, and many mod developers develop on multiple platforms. As a result, a way to distinguish between two files of the same mod of the same version, but for different mod loaders is needed. +正如您可能知道的那样,NeoForge并非唯一的模组加载器,许多模组开发者在多个平台上开发。因此,需要一种方法来区分同一个模组、相同版本但适用于不同模组加载器的两个文件。 -Usually, this is done by including the mod loader somewhere in the name. `jei-neoforge-1.20.2-16.0.0.28`, `jei-1.20.2-neoforge-16.0.0.28` or `jei-1.20.2-16.0.0.28-neoforge` are all valid ways to do it. For other mod loaders, the `neoforge` bit would be replaced with `forge`, `fabric`, `quilt` or whatever different mod loader you might be developing on alongside NeoForge. +通常,这是通过在名称中包含模组加载器来实现的。例如,`jei-neoforge-1.20.2-16.0.0.28`、`jei-1.20.2-neoforge-16.0.0.28`或`jei-1.20.2-16.0.0.28-neoforge`都是有效的命名方式。对于其他模组加载器,`neoforge`部分会被替换为`forge`、`fabric`、`quilt`或您可能正在使用的其他模组加载器。 -### A Note on Maven +### Maven备注 -Maven, the system used for dependency hosting, uses a versioning system that differs from semver in some details (though the general `major.minor.patch` pattern remains the same). The related [Maven Versioning Range (MVR)][mvr] system is used in some places in NeoForge (see [above][neoforge]). When choosing your versioning scheme, you should make sure it is compatible with MVR, as otherwise, mods will not be able to depend on specific versions of your mod! +Maven——用于依赖托管的系统,其版本控制系统在一些细节上与语义化版本控制不同(尽管基本的`major.minor.patch`模式仍然相同)。NeoForge的某些部分使用了相关的[Maven版本范围(MVR)][mvr]系统。在选择您的版本控制方案时,您应确保它与MVR兼容,否则模组将无法依赖您模组的特定版本! [create]: https://www.curseforge.com/minecraft/mc-mods/create [infinite]: https://minecraft.wiki/w/Java_Edition_20w14∞ From cc509745f858f2f578b2f74fe5c22daa854fbf30 Mon Sep 17 00:00:00 2001 From: DaZiDian <113085496+DaZiDian@users.noreply.github.com> Date: Thu, 25 Apr 2024 20:35:31 +0800 Subject: [PATCH 50/87] Update menus.md --- docs/gui/menus.md | 212 +++++++++++++++++++++++----------------------- 1 file changed, 108 insertions(+), 104 deletions(-) diff --git a/docs/gui/menus.md b/docs/gui/menus.md index 3744046cc..3d3c0513b 100644 --- a/docs/gui/menus.md +++ b/docs/gui/menus.md @@ -1,22 +1,26 @@ -# Menus +根据您的要求,这里是对上述Markdown文档进行的中文翻译,尽量使语言流畅且专业: -Menus are one type of backend for Graphical User Interfaces, or GUIs; they handle the logic involved in interacting with some represented data holder. Menus themselves are not data holders. They are views which allow to user to indirectly modify the internal data holder state. As such, a data holder should not be directly coupled to any menu, instead passing in the data references to invoke and modify. +--- + +# 菜单 + +菜单是图形用户界面(GUI)后端的一种类型,它们处理与某些数据持有者交互的逻辑。菜单本身并不持有数据。它们是视图,允许用户间接修改内部数据持有者的状态。因此,数据持有者不应直接与任何菜单耦合,而是传递数据引用以调用和修改。 ## `MenuType` -Menus are created and removed dynamically and as such are not registry objects. As such, another factory object is registered instead to easily create and refer to the *type* of the menu. For a menu, these are `MenuType`s. +菜单是动态创建和删除的,因此它们不是注册对象。因此,另一个工厂对象被注册以便轻松创建和引用菜单的*类型*。对于一个菜单,这些就是`MenuType`。 -`MenuType`s must be [registered]. +`MenuType`必须[注册]。 ### `MenuSupplier` -A `MenuType` is created by passing in a `MenuSupplier` and a `FeatureFlagSet` to its constructor. A `MenuSupplier` represents a function which takes in the id of the container and the inventory of the player viewing the menu, and returns a newly created [`AbstractContainerMenu`][acm]. +通过将`MenuSupplier`和`FeatureFlagSet`传递给其构造函数来创建`MenuType`。`MenuSupplier`代表一个函数,它接受容器的id和查看菜单的玩家的库存,返回一个新创建的[`AbstractContainerMenu`][acm]。 ```java -// For some DeferredRegister> REGISTER +// 对于某个DeferredRegister> REGISTER public static final RegistryObject> MY_MENU = REGISTER.register("my_menu", () -> new MenuType(MyMenu::new, FeatureFlags.DEFAULT_FLAGS)); -// In MyMenu, an AbstractContainerMenu subclass +// 在MyMenu中,一个AbstractContainerMenu子类 public MyMenu(int containerId, Inventory playerInv) { super(MY_MENU.get(), containerId); // ... @@ -24,116 +28,118 @@ public MyMenu(int containerId, Inventory playerInv) { ``` :::note -The container identifier is unique for an individual player. This means that the same container id on two different players will represent two different menus, even if they are viewing the same data holder. +容器标识符对每个玩家是唯一的。这意味着在两个不同的玩家上相同的容器id将代表两个不同的菜单,即使他们正在查看同一个数据持有者。 ::: -The `MenuSupplier` is usually responsible for creating a menu on the client with dummy data references used to store and interact with the synced information from the server data holder. +`MenuSupplier`通常负责在客户端创建菜单,使用虚拟数据引用来存储和交互服务器数据持有者同步的信息。 ### `IContainerFactory` -If additional information is needed on the client (e.g. the position of the data holder in the world), then the subclass `IContainerFactory` can be used instead. In addition to the container id and the player inventory, this also provides a `FriendlyByteBuf` which can store additional information that was sent from the server. A `MenuType` can be created using an `IContainerFactory` via `IForgeMenuType#create`. +如果客户端需要额外的信息(例如,数据持有者在世界中的位置),则可以使用子类`IContainerFactory`。除了容器id和玩家库存外,这还提供了一个`FriendlyByteBuf`,可以存储从服务器发送的额外信息。可以通过`IForgeMenuType#create`使用`IContainerFactory`创建`MenuType`。 ```java -// For some DeferredRegister> REGISTER +// 对于某个DeferredRegister> REGISTER public static final RegistryObject> MY_MENU_EXTRA = REGISTER.register("my_menu_extra", () -> IForgeMenuType.create(MyMenu::new)); -// In MyMenuExtra, an AbstractContainerMenu subclass +// 在MyMenuExtra中,一个AbstractContainerMenu子类 public MyMenuExtra(int containerId, Inventory playerInv, FriendlyByteBuf extraData) { super(MY_MENU_EXTRA.get(), containerId); - // Store extra data from buffer + // 存储缓冲区的额外数据 // ... } ``` ## `AbstractContainerMenu` -All menus are extended from `AbstractContainerMenu`. A menu takes in two parameters, the [`MenuType`][mt], which represents the type of the menu itself, and the container id, which represents the unique identifier of the menu for the current accessor. +所有菜单都继承自`AbstractContainerMenu`。一个菜单需要两个参数,[`MenuType`][mt],代表菜单本身的类型,以及容器id,代表当前访问者的菜单的唯一标识符。 :::caution -The player can only have 100 unique menus open at once. +玩家一次最多只能打开100个唯一的菜单。 ::: -Each menu should contain two constructors: one used to initialize the menu on the server and one used to initialize the menu on the client. The constructor used to initialize the menu on the client is the one supplied to the `MenuType`. Any fields that the server menu constructor contains should have some default for the client menu constructor. +每个菜单应包含两个构造函数:一个用于在服务器上初始化菜单,另一个用于在客户端初始化菜单。用于初始化客户端菜单的构造函数是提供给`MenuType`的。 ```java -// Client menu constructor +// 客户端菜单构造函数 public MyMenu(int containerId, Inventory playerInventory) { this(containerId, playerInventory); } -// Server menu constructor +// 服务器菜单构造函数 public MyMenu(int containerId, Inventory playerInventory) { // ... } ``` -Each menu implementation must implement two methods: `#stillValid` and [`#quickMoveStack`][qms]. +每个菜单实现必须实现两个方法:`#stillValid`和[`#quickMoveStack`][qms]。 + +### `#stillValid`和`ContainerLevelAccess` -### `#stillValid` and `ContainerLevelAccess` +`#stillValid` -`#stillValid` determines whether the menu should remain open for a given player. This is typically directed to the static `#stillValid` which takes in a `ContainerLevelAccess`, the player, and the `Block` this menu is attached to. The client menu must always return `true` for this method, which the static `#stillValid` does default to. This implementation checks whether the player is within eight blocks of where the data storage object is located. +确定是否应该为给定玩家保持菜单打开。这通常指向静态的`#stillValid`,它需要一个`ContainerLevelAccess`、玩家和菜单所附属的`Block`。客户端菜单必须始终为此方法返回`true`,这是静态`#stillValid`的默认设置。此实现检查玩家是否在数据存储对象所在位置的八个方块范围内。 -A `ContainerLevelAccess` supplies the current level and location of the block within an enclosed scope. When constructing the menu on the server, a new access can be created by calling `ContainerLevelAccess#create`. The client menu constructor can pass in `ContainerLevelAccess#NULL`, which will do nothing. +`ContainerLevelAccess`提供了当前级别和块所在位置的封闭范围。在服务器上构造菜单时,可以通过调用`ContainerLevelAccess#create`创建新的访问权限。客户端菜单构造函数可以传递`ContainerLevelAccess#NULL`,这将不做任何事。 ```java -// Client menu constructor +// 客户端菜单构造函数 public MyMenuAccess(int containerId, Inventory playerInventory) { this(containerId, playerInventory, ContainerLevelAccess.NULL); } -// Server menu constructor +// 服务器菜单构造函数 public MyMenuAccess(int containerId, Inventory playerInventory, ContainerLevelAccess access) { // ... } -// Assume this menu is attached to RegistryObject MY_BLOCK +// 假设此菜单附属于RegistryObject MY_BLOCK @Override public boolean stillValid(Player player) { return AbstractContainerMenu.stillValid(this.access, player, MY_BLOCK.get()); } ``` -### Data Synchronization +### 数据同步 -Some data needs to be present on both the server and the client to display to the player. To do this, the menu implements a basic layer of data synchronization such that whenever the current data does not match the data last synced to the client. For players, this is checked every tick. +需要在服务器和客户端上存在某些数据才能显示给玩家。为此,菜单实现了基本的数据同步层,以便每当当前数据与上次同步到客户端的数据不匹配时进行检查。对于玩家来说,这每个游戏刻都会检查。 -Minecraft supports two forms of data synchronization by default: `ItemStack`s via `Slot`s and integers via `DataSlot`s. `Slot`s and `DataSlot`s are views which hold references to data storages that can be be modified by the player in a screen, assuming the action is valid. These can be added to a menu within the constructor through `#addSlot` and `#addDataSlot`. +Minecraft默认支持两种数据同步形式:通过`Slot`的`ItemStack`和通过`DataSlot`的整数。`Slot`和`DataSlot`是持有可以在屏幕上被玩家修改的数据存储引用的视图,前提是动作有效。这些可以通过在构造函数中的`#addSlot`和`#addDataSlot`添加到菜单中。 :::note -Since `Container`s used by `Slot`s are deprecated by NeoForge in favor of using the [`IItemHandler` capability][cap], the rest of the explanation will revolve around using the capability variant: `SlotItemHandler`. +由于NeoForge不推荐使用`Container`,转而使用[`IItemHandler`能力][cap],下文将围绕使用这种能力的变体:`SlotItemHandler`进行解释。 ::: -A `SlotItemHandler` contains four parameters: the `IItemHandler` representing the inventory the stacks are within, the index of the stack this slot is specifically representing, and the x and y position of where the top-left position of the slot will render on the screen relative to `AbstractContainerScreen#leftPos` and `#topPos`. The client menu constructor should always supply an empty instance of an inventory of the same size. +`SlotItemHandler`包含四个参数:代表堆栈所在库存的`IItemHandler`,此槽特别代表的堆栈的索引,以及槽在屏幕上相对于`AbstractContainerScreen#leftPos`和`#topPos`的左上位置的x和y位置。客户端菜单构造函数应始终提供一个相同大小的空库存实例。 -In most cases, any slots the menu contains is first added, followed by the player's inventory, and finally concluded with the player's hotbar. To access any individual `Slot` from the menu, the index must be calculated based upon the order of which slots were added. +在大多数情况下,首先添加菜单包含的任何槽,然后是玩家的库存,最后是玩家的快捷栏。要从菜单访问任何单独的`Slot`,必须根据添加槽的顺序计算索引。 -A `DataSlot` is an abstract class which should implement a getter and setter to reference the data stored in the data storage object. The client menu constructor should always supply a new instance via `DataSlot#standalone`. +`DataSlot`是一个抽象类,应实现getter和setter以引用数据存储对象中存储的数据。客户端菜单构造函数应始终通过`DataSlot#standalone`提供一个新实例。 -These, along with slots, should be recreated every time a new menu is initialized. +这些,连同槽一起,应在每次初始化新菜单时重新创建。 :::caution -Although a `DataSlot` stores an integer, it is effectively limited to a **short** (-32768 to 32767) because of how it sends the value across the network. The 16 high-order bits of the integer are ignored. +尽管`DataSlot`存储一个整数,但由于其通过网络发送值的方式,它实际上限制为一个**短整型**(-32768至32767)。整数的16个高阶位被忽略。 ::: ```java -// Assume we have an inventory from a data object of size 5 -// Assume we have a DataSlot constructed on each initialization of the server menu +// 假设我们有一个大小为5的数据对象库存 +// 假设每次服务器菜单初始化时都构建了一个DataSlot -// Client menu constructor +// 客户端菜单构造函数 public MyMenuAccess(int containerId, Inventory playerInventory) { this(containerId, playerInventory, new ItemStackHandler(5), DataSlot.standalone()); } -// Server menu constructor +// 服务器菜单构造函数 public MyMenuAccess(int containerId, Inventory playerInventory, IItemHandler dataInventory, DataSlot dataSingle) { - // Check if the data inventory size is some fixed value - // Then, add slots for data inventory + // 检查数据库存大小是否为某个固定值 + // 然后,为数据库存添加槽位 this.addSlot(new SlotItemHandler(dataInventory, /*...*/)); - // Add slots for player inventory + // 为玩家库存添加槽位 this.addSlot(new Slot(playerInventory, /*...*/)); - // Add data slots for handled integers + // 为处理的整数添加数据槽位 this.addDataSlot(dataSingle); // ... @@ -142,22 +148,22 @@ public MyMenuAccess(int containerId, Inventory playerInventory, IItemHandler dat #### `ContainerData` -If multiple integers need to be synced to the client, a `ContainerData` can be used to reference the integers instead. This interface functions as an index lookup such that each index represents a different integer. `ContainerData`s can also be constructed in the data object itself if the `ContainerData` is added to the menu through `#addDataSlots`. The method creates a new `DataSlot` for the amount of data specified by the interface. The client menu constructor should always supply a new instance via `SimpleContainerData`. +如果需要将多个整数同步到客户端,可以使用`ContainerData`来引用这些整数。这个接口功能类似于索引查找,其中每个索引代表一个不同的整数。如果将`ContainerData`通过`#addDataSlots`方法添加到菜单中,则可以直接在数据对象本身中构建`ContainerData`。该方法为接口指定的数据量创建新的`DataSlot`。客户端菜单构造函数应始终通过`SimpleContainerData`提供新实例。 ```java -// Assume we have a ContainerData of size 3 +// 假设我们有一个大小为3的ContainerData -// Client menu constructor +// 客户端菜单构造函数 public MyMenuAccess(int containerId, Inventory playerInventory) { this(containerId, playerInventory, new SimpleContainerData(3)); } -// Server menu constructor +// 服务器菜单构造函数 public MyMenuAccess(int containerId, Inventory playerInventory, ContainerData dataMultiple) { - // Check if the ContainerData size is some fixed value + // 检查ContainerData大小是否为某个固定值 checkContainerDataCount(dataMultiple, 3); - // Add data slots for handled integers + // 为处理的整数添加数据槽位 this.addDataSlots(dataMultiple); // ... @@ -165,139 +171,137 @@ public MyMenuAccess(int containerId, Inventory playerInventory, ContainerData da ``` :::caution -As `ContainerData` delegates to `DataSlot`s, these are also limited to a **short** (-32768 to 32767). +由于`ContainerData`委托给`DataSlot`,这些也被限制为一个**短整型**(-32768至32767)。 ::: #### `#quickMoveStack` -`#quickMoveStack` is the second method that must be implemented by any menu. This method is called whenever a stack has been shift-clicked, or quick moved, out of its current slot until the stack has been fully moved out of its previous slot or there is no other place for the stack to go. The method returns a copy of the stack in the slot being quick moved. +`#quickMoveStack`是任何菜单必须实现的第二个方法。每当一个堆栈被快速移动或通过Shift点击从其当前槽中移出时,就会调用此方法,直到堆栈完全移出其前一个槽或没有其他地方可以放置堆栈为止。该方法返回被快速移动的槽中的堆栈副本。 + +通常使用 `#moveItemStackTo` 在插槽之间移动堆叠物品,该方法将堆叠物品移动到第一个可用的插槽。它接受要移动的堆叠物品、要尝试将堆叠物品移动到的第一个插槽索引(包括)、最后一个插槽索引(不包括),以及是否从第一个到最后一个插槽进行检查(当为 `false` 时)或从最后一个到第一个插槽进行检查(当为 `true` 时)。 -Stacks are typically moved between slots using `#moveItemStackTo`, which moves the stack into the first available slot. It takes in the stack to be moved, the first slot index (inclusive) to try and move the stack to, the last slot index (exclusive), and whether to check the slots from first to last (when `false`) or from last to first (when `true`). - -Across Minecraft implementations, this method is fairly consistent in its logic: +在 Minecraft 的各种实现中,该方法在逻辑上相当一致: ```java -// Assume we have a data inventory of size 5 -// The inventory has 4 inputs (index 1 - 4) which outputs to a result slot (index 0) -// We also have the 27 player inventory slots and the 9 hotbar slots -// As such, the actual slots are indexed like so: -// - Data Inventory: Result (0), Inputs (1 - 4) -// - Player Inventory (5 - 31) -// - Player Hotbar (32 - 40) +// 假设我们有一个大小为 5 的数据存储库 +// 存储库有 4 个输入插槽(索引 1 - 4),输出到一个结果插槽(索引 0) +// 我们还有 27 个玩家存储库插槽和 9 个快捷栏插槽 +// 因此,实际插槽的索引如下: +// - 数据存储库:结果(0)、输入(1 - 4) +// - 玩家存储库(5 - 31) +// - 玩家快捷栏(32 - 40) @Override public ItemStack quickMoveStack(Player player, int quickMovedSlotIndex) { - // The quick moved slot stack + // 快速移动的插槽堆叠物品 ItemStack quickMovedStack = ItemStack.EMPTY; - // The quick moved slot + // 快速移动的插槽 Slot quickMovedSlot = this.slots.get(quickMovedSlotIndex) - // If the slot is in the valid range and the slot is not empty + // 如果插槽在有效范围内且插槽不为空 if (quickMovedSlot != null && quickMovedSlot.hasItem()) { - // Get the raw stack to move + // 获取要移动的原始堆叠物品 ItemStack rawStack = quickMovedSlot.getItem(); - // Set the slot stack to a copy of the raw stack + // 将插槽堆叠设置为原始堆叠的副本 quickMovedStack = rawStack.copy(); /* - The following quick move logic can be simplified to if in data inventory, - try to move to player inventory/hotbar and vice versa for containers - that cannot transform data (e.g. chests). + 以下快速移动逻辑可以简化为在数据存储库中,尝试移动到玩家存储库/快捷栏,反之亦然,对于无法转换数据的容器(例如箱子)。 */ - // If the quick move was performed on the data inventory result slot + // 如果快速移动在数据存储库结果插槽上执行 if (quickMovedSlotIndex == 0) { - // Try to move the result slot into the player inventory/hotbar + // 尝试将结果插槽移动到玩家存储库/快捷栏 if (!this.moveItemStackTo(rawStack, 5, 41, true)) { - // If cannot move, no longer quick move + // 如果无法移动,不再快速移动 return ItemStack.EMPTY; } - // Perform logic on result slot quick move + // 对结果插槽快速移动执行逻辑 slot.onQuickCraft(rawStack, quickMovedStack); } - // Else if the quick move was performed on the player inventory or hotbar slot + // 否则如果快速移动在玩家存储库或快捷栏插槽上执行 else if (quickMovedSlotIndex >= 5 && quickMovedSlotIndex < 41) { - // Try to move the inventory/hotbar slot into the data inventory input slots + // 尝试将玩家存储库/快捷栏插槽移动到数据存储库输入插槽 if (!this.moveItemStackTo(rawStack, 1, 5, false)) { - // If cannot move and in player inventory slot, try to move to hotbar + // 如果无法移动且在玩家存储库插槽中,尝试移动到快捷栏 if (quickMovedSlotIndex < 32) { if (!this.moveItemStackTo(rawStack, 32, 41, false)) { - // If cannot move, no longer quick move + // 如果无法移动,不再快速移动 return ItemStack.EMPTY; } } - // Else try to move hotbar into player inventory slot + // 否则尝试将快捷栏移动到玩家存储库插槽 else if (!this.moveItemStackTo(rawStack, 5, 32, false)) { - // If cannot move, no longer quick move + // 如果无法移动,不再快速移动 return ItemStack.EMPTY; } } } - // Else if the quick move was performed on the data inventory input slots, try to move to player inventory/hotbar + // 否则如果快速移动在数据存储库输入插槽上,则尝试移动到玩家存储库/快捷栏 else if (!this.moveItemStackTo(rawStack, 5, 41, false)) { - // If cannot move, no longer quick move + // 如果无法移动,不再快速移动 return ItemStack.EMPTY; } if (rawStack.isEmpty()) { - // If the raw stack has completely moved out of the slot, set the slot to the empty stack + // 如果原始堆叠完全移出插槽,则将插槽设置为空堆叠 quickMovedSlot.set(ItemStack.EMPTY); } else { - // Otherwise, notify the slot that that the stack count has changed + // 否则,通知插槽堆叠数量已更改 quickMovedSlot.setChanged(); } /* - The following if statement and Slot#onTake call can be removed if the - menu does not represent a container that can transform stacks (e.g. - chests). + 如果菜单不表示可以转换堆叠的容器(例如箱子),则可以删除以下 if 语句和 Slot#onTake 调用。 */ if (rawStack.getCount() == quickMovedStack.getCount()) { - // If the raw stack was not able to be moved to another slot, no longer quick move + // 如果原始堆叠无法移动到另一个插槽,则不再快速移动 return ItemStack.EMPTY; } - // Execute logic on what to do post move with the remaining stack + // 执行堆叠剩余部分后移动的逻辑 quickMovedSlot.onTake(player, rawStack); } - return quickMovedStack; // Return the slot stack + return quickMovedStack; // 返回插槽堆叠 } ``` -## Opening a Menu +## 打开菜单 -Once a menu type has been registered, the menu itself has been finished, and a [screen] has been attached, a menu can then be opened by the player. Menus can be opened by calling `NetworkHooks#openScreen` on the logical server. The method takes in the player opening the menu, the `MenuProvider` of the server side menu, and optionally a `FriendlyByteBuf` if extra data needs to be synced to the client. +一旦菜单类型已注册,菜单本身已完成,并且 [screen] 已附加,玩家就可以打开菜单。可以通过在逻辑服务器上调用 `NetworkHooks#openScreen` 来打开菜单。该方法接受打开菜单的玩家、服务器端菜单的 `MenuProvider`,以及可选的 `FriendlyByteBuf`,如果需要向客户端同步额外数据。 :::note -`NetworkHooks#openScreen` with the `FriendlyByteBuf` parameter should only be used if a menu type was created using an [`IContainerFactory`][icf]. +只有在使用 [`IContainerFactory`][icf] 创建菜单类型时,才应使用带有 `FriendlyByteBuf` 参数的 `NetworkHooks + +#openScreen`。 ::: #### `MenuProvider` -A `MenuProvider` is an interface that contains two methods: `#createMenu`, which creates the server instance of the menu, and `#getDisplayName`, which returns a component containing the title of the menu to pass to the [screen]. The `#createMenu` method contains three parameter: the container id of the menu, the inventory of the player who opened the menu, and the player who opened the menu. +`MenuProvider` 是一个包含两个方法的接口:`#createMenu`,用于创建菜单的服务器实例,以及 `#getDisplayName`,返回包含菜单标题的组件以传递给 [screen]。`#createMenu` 方法包含三个参数:菜单的容器 ID、打开菜单的玩家的库存,以及打开菜单的玩家。 -A `MenuProvider` can easily be created using `SimpleMenuProvider`, which takes in a method reference to create the server menu and the title of the menu. +可以使用 `SimpleMenuProvider` 轻松创建 `MenuProvider`,它接受一个方法引用来创建服务器菜单和菜单的标题。 ```java -// In some implementation +// 在某些实现中 NetworkHooks.openScreen(serverPlayer, new SimpleMenuProvider( (containerId, playerInventory, player) -> new MyMenu(containerId, playerInventory), Component.translatable("menu.title.examplemod.mymenu") )); ``` -### Common Implementations +### 常见实现 -Menus are typically opened on a player interaction of some kind (e.g. when a block or entity is right-clicked). +通常通过某种玩家交互方式(例如右键单击方块或实体)打开菜单。 -#### Block Implementation +#### 方块实现 -Blocks typically implement a menu by overriding `BlockBehaviour#use`. If on the logical client, the interaction returns `InteractionResult#SUCCESS`. Otherwise, it opens the menu and returns `InteractionResult#CONSUME`. +方块通常通过覆盖 `BlockBehaviour#use` 来实现菜单。如果在逻辑客户端上,交互返回 `InteractionResult#SUCCESS`。否则,打开菜单并返回 `InteractionResult#CONSUME`。 -The `MenuProvider` should be implemented by overriding `BlockBehaviour#getMenuProvider`. Vanilla methods use this to view the menu in spectator mode. +`MenuProvider` 应该通过覆盖 `BlockBehaviour#getMenuProvider` 来实现。原版方法使用此方法在旁观模式下查看菜单。 ```java -// In some Block subclass +// 在某个 Block 子类中 @Override public MenuProvider getMenuProvider(BlockState state, Level level, BlockPos pos) { return new SimpleMenuProvider(/* ... */); @@ -313,12 +317,12 @@ public InteractionResult use(BlockState state, Level level, BlockPos pos, Player ``` :::note -This is the simplest way to implement the logic, not the only way. If you want the block to only open the menu under certain conditions, then some data will need to be synced to the client beforehand to return `InteractionResult#PASS` or `#FAIL` if the conditions are not met. +这是实现逻辑的最简单方法,不是唯一的方法。如果希望方块仅在满足某些条件时打开菜单,则需要事先将一些数据同步到客户端,以返回 `InteractionResult#PASS` 或 `#FAIL`,如果条件不满足。 ::: -#### Mob Implementation +#### 生物实现 -Mobs typically implement a menu by overriding `Mob#mobInteract`. This is done similarly to the block implementation with the only difference being that the `Mob` itself should implement `MenuProvider` to support spectator mode viewing. +生物通常通过覆盖 `Mob#mobInteract` 来实现菜单。这与方块实现类似,唯一的区别是生物本身应该实现 `MenuProvider` 以支持旁观模式查看。 ```java public class MyMob extends Mob implements MenuProvider { @@ -335,7 +339,7 @@ public class MyMob extends Mob implements MenuProvider { ``` :::note -Once again, this is the simplest way to implement the logic, not the only way. +再次强调,这是实现逻辑的最简单方法,不是唯一的方法。 ::: [registered]: ../concepts/registries.md#methods-for-registering From d54dd324d727d4c7c974707a800780236517d6f2 Mon Sep 17 00:00:00 2001 From: DaZiDian <113085496+DaZiDian@users.noreply.github.com> Date: Thu, 25 Apr 2024 20:41:48 +0800 Subject: [PATCH 51/87] Update screens.md --- docs/gui/screens.md | 252 ++++++++++++++++++++++---------------------- 1 file changed, 126 insertions(+), 126 deletions(-) diff --git a/docs/gui/screens.md b/docs/gui/screens.md index 4bde01e18..8824e99b5 100644 --- a/docs/gui/screens.md +++ b/docs/gui/screens.md @@ -1,233 +1,237 @@ -# Screens +## 屏幕 -Screens are typically the base of all Graphical User Interfaces (GUIs) in Minecraft: taking in user input, verifying it on the server, and syncing the resulting action back to the client. They can be combined with [menus] to create an communication network for inventory-like views, or they can be standalone which modders can handle through their own [network] implementations. +在 Minecraft 中,屏幕通常是所有图形用户界面(GUI)的基础,用于接收用户输入、在服务器上验证输入,并将结果同步回客户端。它们可以与[菜单](menus)结合使用,创建用于类似库存的视图的通信网络,或者它们可以是独立的,由模组开发者通过自己的[网络](../networking/index.md)实现来处理。 -Screens are made up of numerous parts, making it difficult to fully understand what a 'screen' actually is in Minecraft. As such, this document will go over each of the screen's components and how it is applied before discussing the screen itself. +屏幕由许多部分组成,这使得理解“屏幕”在 Minecraft 中实际上是什么变得困难。因此,本文档将介绍屏幕的每个组件以及它是如何应用的,然后讨论屏幕本身。 -## Relative Coordinates +## 相对坐标 -Whenever anything is rendered, there needs to be some identifier which specifies where it will appear. With numerous abstractions, most of Minecraft's rendering calls takes in an x, y, and z value in a coordinate plane. X values increase from left to right, y from top to bottom, and z from far to near. However, the coordinates are not fixed to a specified range. They can change depending on the size of the screen and the scale at which is specified within the options. As such, extra care must be taken to make sure the values of the coordinates while rendering scale properly to the changeable screen size. +无论何时渲染任何东西,都需要一些标识符来指定其出现的位置。使用许多抽象化,Minecraft 的大多数渲染调用在一个坐标平面上接收 x、y 和 z 值。X 值从左到右增加,y 值从上到下增加,z 值从远到近增加。但是,这些坐标不是固定的范围。它们可以根据屏幕的大小和在选项中指定的比例而变化。因此,必须特别注意确保在渲染时的坐标值相对于可变的屏幕大小适当缩放。 -Information on how to relativize your coordinates will be within the [screen] section. +有关如何使您的坐标相对的信息将在[屏幕](#屏幕)部分中提供。 :::caution -If you choose to use fixed coordinates or incorrectly scale the screen, the rendered objects may look strange or misplaced. An easy way to check if you relativized your coordinates correctly is to click the 'Gui Scale' button in your video settings. This value is used as the divisor to the width and height of your display when determining the scale at which a GUI should render. +如果选择使用固定坐标或不正确地缩放屏幕,则渲染的对象可能会看起来奇怪或错位。检查坐标是否正确相对化的简单方法是单击您的视频设置中的“GUI 比例”按钮。此值用作除数以确定 GUI 应以哪个比例渲染。 ::: -## Gui Graphics +## GUI 图形 -Any GUI rendered by Minecraft is typically done using `GuiGraphics`. `GuiGraphics` is the first parameter to almost all rendering methods; it contains basic methods to render commonly used objects. These fall into five categories: colored rectangles, strings, and textures, items, and tooltips. There is also an additional method for rendering a snippet of a component (`#enableScissor` / `#disableScissor`). `GuiGraphics` also exposes the `PoseStack` which applies the transformations necessary to properly render where the component should be rendered. Additionally, colors are in the [ARGB][argb] format. +Minecraft 中渲染的任何 GUI 通常都是使用 `GuiGraphics` 进行的。`GuiGraphics` 是几乎所有渲染方法的第一个参数;它包含用于渲染常用对象的基本方法。这些方法分为五类:彩色矩形、字符串和纹理、物品和工具提示。还有一个用于渲染组件的额外方法(`#enableScissor` / `#disableScissor`)。`GuiGraphics` 还公开了 `PoseStack`,它应用了必要的转换,以便将组件正确渲染到屏幕上。此外,颜色采用[ARGB](https://en.wikipedia.org/wiki/RGBA_color_model#ARGB32)格式。 -### Colored Rectangles +### 彩色矩形 -Colored rectangles are drawn through a position color shader. There are three types of colored rectangles that can be drawn. +通过位置颜色着色器绘制彩色矩形。可以绘制三种类型的彩色矩形。 -First, there is a colored horizontal and vertical one-pixel wide line, `#hLine` and `#vLine` respectively. `#hLine` takes in two x coordinates defining the left and right (inclusively), the top y coordinate, and the color. `#vLine` takes in the left x coordinate, two y coordinates defining the top and bottom (inclusively), and the color. +首先是水平和垂直的一像素宽线,分别是 `#hLine` 和 `#vLine`。`#hLine` 接受两个 x 坐标,定义了左侧和右侧(包括在内),顶部 y 坐标和颜色。`#vLine` 接受左侧 x 坐标、两个 y 坐标,定义了顶部和底部(包括在内),以及颜色。 -Second, there is the `#fill` method, which draws a rectangle to the screen. The line methods internally call this method. This takes in the left x coordinate, the top y coordinate, the right x coordinate, the bottom y coordinate, and the color. +其次是 `#fill` 方法,它绘制一个矩形到屏幕上。线方法内部调用此方法。它接受左侧 x 坐标、顶部 y 坐标、右侧 x 坐标、底部 y 坐标和颜色。 -Finally, there is the `#fillGradient` method, which draws a rectangle with a vertical gradient. This takes in the right x coordinate, the bottom y coordinate, the left x coordinate, the top y coordinate, the z coordinate, and the bottom and top colors. +最后是 `#fillGradient` 方法,它绘制一个具有垂直渐变的矩形。它接受右侧 x 坐标、底部 y 坐标、左侧 x 坐标、顶部 y 坐标、z 坐标、底部和顶部颜色。 -### Strings +### 字符串 -Strings are drawn through its `Font`, typically consisting of their own shaders for normal, see through, and offset mode. There are two alignment of strings that can be rendered, each with a back shadow: a left-aligned string (`#drawString`) and a center-aligned string (`#drawCenteredString`). These both take in the font the string will be rendered in, the string to draw, the x coordinate representing the left or center of the string respectively, the top y coordinate, and the color. +字符串通过其 `Font` 绘制,通常包含正常、透明和偏移模式的自己的着色器。可以渲染两种对齐的字符串,每种字符串都带有背景阴影:左对齐字符串 (`#drawString`) 和居中对齐字符串 (`#drawCenteredString`)。这两种方法都接受要绘制字符串的字体、字符串本身、x 坐标(分别是字符串的左侧或中心)、顶部 y 坐标和颜色。 :::note -Strings should typically be passed in as [`Component`s][component] as they handle a variety of usecases, including the two other overloads of the method. +字符串通常应该作为 [`Component`](../resources/client/i18n.md#components) 传递,因为它们处理各种用例,包括该方法的其他两个重载。 ::: -### Textures +### 纹理 -Textures are drawn through blitting, hence the method name `#blit`, which, for this purpose, copies the bits of an image and draws them directly to the screen. These are drawn through a position texture shader. While there are many different `#blit` overloads, we will only discuss two static `#blit`s. +纹理通过贴图绘制,因此方法名为 `#blit`,在这种情况下,它复制图像的 -The first static `#blit` takes in six integers and assumes the texture being rendered is on a 256 x 256 PNG file. It takes in the left x and top y screen coordinate, the left x and top y coordinate within the PNG, and the width and height of the image to render. +位并直接将其绘制到屏幕上。这些是通过位置纹理着色器绘制的。虽然有许多不同的 `#blit` 重载,但我们只讨论两种静态 `#blit`。 + +第一个静态 `#blit` 接受六个整数,假定正在渲染的纹理位于一个 256 x 256 的 PNG 文件中。它接受左侧 x 和顶部 y 屏幕坐标,PNG 内部的左侧 x 和顶部 y 坐标,以及要渲染的图像的宽度和高度。 :::tip -The size of the PNG file must be specified so that the coordinates can be normalized to obtain the associated UV values. +必须指定 PNG 文件的大小,以便将坐标归一化为获取关联的 UV 值。 ::: -The static `#blit` which the first calls expands this to nine integers, only assuming the image is on a PNG file. It takes in the left x and top y screen coordinate, the z coordinate (referred to as the blit offset), the left x and top y coordinate within the PNG, the width and height of the image to render, and the width and height of the PNG file. +第一个调用的静态 `#blit` 将此扩展为九个整数,仅假定图像位于 PNG 文件中。它接受左侧 x 和顶部 y 屏幕坐标,z 坐标(称为 blit 偏移量),PNG 内部的左侧 x 和顶部 y 坐标,要渲染的图像的宽度和高度,以及 PNG 文件的宽度和高度。 -#### Blit Offset +#### Blit 偏移量 -The z coordinate when rendering a texture is typically set to the blit offset. The offset is responsible for properly layering renders when viewing a screen. Renders with a smaller z coordinate are rendered in the background and vice versa where renders with a larger z coordinate are rendered in the foreground. The z offset can be set directly on the `PoseStack` itself via `#translate`. Some basic offset logic is applied internally in some methods of `GuiGraphics` (e.g. item rendering). +在渲染纹理时,z 坐标通常设置为 blit 偏移量。偏移量负责在查看屏幕时正确分层渲染。具有较小 z 坐标的渲染在背景中渲染,反之,具有较大 z 坐标的渲染在前景中渲染。可以通过 `#translate` 方法直接在 `PoseStack` 本身上设置 z 偏移量。一些 `GuiGraphics` 方法内部应用了一些基本的偏移逻辑(例如物品渲染)。 :::caution -When setting the blit offset, you must reset it after rendering your object. Otherwise, other objects within the screen may be rendered in an incorrect layer causing graphical issues. It is recommended to push the current pose before translating and then popping after all rendering at the offset is completed. +设置 blit 偏移量时,必须在渲染对象完成后重置它。否则,屏幕中的其他对象可能会以不正确的层次渲染,导致图形问题。建议在平移前推送当前姿势,然后在所有以偏移量渲染的对象完成后弹出。 ::: -## Renderable +## 可渲染对象 -`Renderable`s are essentially objects that are rendered. These include screens, buttons, chat boxes, lists, etc. `Renderable`s only have one method: `#render`. This takes in the `GuiGraphics` used to render things to the screen, the x and y positions of the mouse scaled to the relative screen size, and the tick delta (how many ticks have passed since the last frame). +`Renderable` 实际上是指可以被渲染的对象。这些包括屏幕、按钮、聊天框、列表等。`Renderable` 只有一个方法:`#render`。此方法接受用于将事物渲染到屏幕上的 `GuiGraphics`、鼠标的 x 和 y 位置(按相对屏幕大小缩放)、以及帧之间的时间差(自上一帧以来经过了多少个 tick)。 -Some common renderables are screens and 'widgets': interactable elements which typically render on the screen such as `Button`, its subtype `ImageButton`, and `EditBox` which is used to input text on the screen. +一些常见的可渲染对象是屏幕和“小部件”:通常渲染在屏幕上的可交互元素,如 `Button`、它的子类型 `ImageButton`,以及 `EditBox`,用于在屏幕上输入文本。 -## GuiEventListener +## GUI 事件侦听器 -Any screen rendered in Minecraft implements `GuiEventListener`. `GuiEventListener`s are responsible for handling user interaction with the screen. These include inputs from the mouse (movement, clicked, released, dragged, scrolled, mouseover) and keyboard (pressed, released, typed). Each method returns whether the associated action affected the screen successfully. Widgets like buttons, chat boxes, lists, etc. also implement this interface. +Minecraft 中呈现的任何屏幕都实现了 `GuiEventListener`。`GuiEventListener` 负责处理用户与屏幕的交互。这些包括鼠标(移动、点击、释放、拖动、滚动、悬停)和键盘(按下、释放、键入)的输入。每个方法返回关联动作是否成功地影响了屏幕。按钮、聊天框、列表等小部件也实现了此接口。 -### ContainerEventHandler +### 容器事件处理程序 -Almost synonymous with `GuiEventListener`s are their subtype: `ContainerEventHandler`s. These are responsible for handling user interaction on screens which contain widgets, managing which is currently focused and how the associated interactions are applied. `ContainerEventHandler`s add three additional features: interactable children, dragging, and focusing. +与 `GuiEventListener` 几乎同义的是它们的子类型:`ContainerEventHandler`。这些负责处理在包含小部件的屏幕上的用户交互,管理当前聚焦的是哪个小部件以及如何应用相关交互。`ContainerEventHandler` 添加了三个额外功能:可交互的子元素、拖动和聚焦。 -Event handlers hold children which are used to determine the interaction order of elements. During the mouse event handlers (excluding dragging), the first child in the list that the mouse hovers over has their logic executed. +事件处理程序保存了子元素,用于确定元素的交互顺序。在鼠标事件处理程序中(不包括拖动),鼠标悬停在上面的第一个子元素将执行其逻辑。 -Dragging an element with the mouse, implemented via `#mouseClicked` and `#mouseReleased`, provides more precisely executed logic. +通过鼠标拖动元素,通过 `#mouseClicked` 和 `#mouseReleased` 实现更精确的逻辑。 -Focusing allows for a specific child to be checked first and handled during an event's execution, such as during keyboard events or dragging the mouse. Focus is typically set through `#setFocused`. In addition, interactable children can be cycled using `#nextFocusPath`, selecting the child based upon the `FocusNavigationEvent` passed in. +聚焦允许首先检查特定的子元素,并在事件执行期间处理它,例如在键盘事件或拖动鼠标期间。焦点通常通过 `#setFocused` 设置。此外,可以使用 `#nextFocusPath` 循环可交互的子元素,选择基于传递的 `FocusNavigationEvent` 的子元素。 :::note -Screens implement `ContainerEventHandler` through `AbstractContainerEventHandler`, which adds in the setter and getter logic for dragging and focusing children. +屏幕通过 `AbstractContainerEventHandler` 实现了 `ContainerEventHandler`,它添加了拖动和聚焦子元素的设置和获取逻辑。 ::: ## NarratableEntry -`NarratableEntry`s are elements which can be spoken about through Minecraft's accessibility narration feature. Each element can provide different narration depending on what is hovered or selected, prioritized typically by focus, hovering, and then all other cases. +`NarratableEntry` 是可以通过 Minecraft 的辅助功能叙述功能讲述的元素。每个元素可以根据悬停或选择的内容提供不同的叙述,通常由焦点、悬停,然后是所有其他情况优先。 -`NarratableEntry`s have three methods: one which determines the priority of the element (`#narrationPriority`), one which determines whether to speak the narration (`#isActive`), and finally one which supplies the narration to its associated output, spoken or read (`#updateNarration`). +`NarratableEntry` 有三个方法:确定元素的优先级的一个方法 (`#narrationPriority`),确定是否说出叙述的一个方法 (`#isActive`),最后是向其关联输出提供叙述的一个方法 (`#updateNarration`)。 :::note -All widgets from Minecraft are `NarratableEntry`s, so it typically does not need to be manually implemented if using an available subtype. +Minecraft 中的所有小部件都是 `NarratableEntry`,因此通常不需要手动实现它,如果使用可用的子类型。 ::: -## The Screen Subtype +## 屏幕子类型 -With all of the above knowledge, a basic screen can be constructed. To make it easier to understand, the components of a screen will be mentioned in the order they are typically encountered. +有了上述所有知识,就可以构建一个基本的屏幕了。为了更方便理解,屏幕的组件将按照它们通常出现的顺序而被提及。 -First, all screens take in a `Component` which represents the title of the screen. This component is typically drawn to the screen by one of its subtypes. It is only used in the base screen for the narration message. +首先,所有的屏幕都需要一个组件 `Component` 来表示屏幕的标题。这个组件通常由其子类型绘制到屏幕上。它仅在基本屏幕中用于叙述消息。 ```java -// In some Screen subclass +// 在某个屏幕的子类中 public MyScreen(Component title) { super(title); } ``` -### Initialization +### 初始化 -Once a screen has been initialized, the `#init` method is called. The `#init` method sets the initial settings inside the screen from the `ItemRenderer` and `Minecraft` instance to the relative width and height as scaled by the game. Any setup such as adding widgets or precomputing relative coordinates should be done in this method. If the game window is resized, the screen will be reinitialized by calling the `#init` method. +屏幕初始化后,将调用 `#init` 方法。`#init` 方法设置屏幕的初始设置,从 `ItemRenderer` 和 `Minecraft` 实例到由游戏缩放的相对宽度和高度。在这个方法中进行任何设置,比如添加小部件或预计算相对坐标。如果游戏窗口大小改变,将通过调用 `#init` 方法来重新初始化屏幕。 -There are three ways to add a widget to a screen, each serving a separate purpose: +有三种方法可以向屏幕添加小部件,每种方法都有不同的作用: -Method | Description -:---: | :--- -`#addWidget` | Adds a widget that is interactable and narrated, but not rendered. -`#addRenderableOnly` | Adds a widget that will only be rendered; it is not interactable or narrated. -`#addRenderableWidget` | Adds a widget that is interactable, narrated, and rendered. +方法 | 描述 +:---: | :--- +`#addWidget` | 添加一个可交互且叙述的小部件,但不渲染。 +`#addRenderableOnly` | 添加一个仅渲染的小部件;不可交互也不叙述。 +`#addRenderableWidget` | 添加一个既可交互、叙述又可渲染的小部件。 -Typically, `#addRenderableWidget` will be used most often. +通常情况下,最常用的是 `#addRenderableWidget`。 ```java -// In some Screen subclass +// 在某个屏幕的子类中 @Override protected void init() { super.init(); - // Add widgets and precomputed values + // 添加小部件和预计算的值 this.addRenderableWidget(new EditBox(/* ... */)); } ``` -### Ticking Screens +### 屏幕更新 -Screens also tick using the `#tick` method to perform some level of client side logic for rendering purposes. The most common example is the `EditBox` for the blinking cursor. +屏幕也会使用 `#tick` 方法进行更新,以执行一些用于渲染目的的客户端逻辑。最常见的示例是用于闪烁光标的 `EditBox`。 ```java -// In some Screen subclass +// 在某个屏幕的子类中 @Override public void tick() { super.tick(); - // Add ticking logic for EditBox in editBox + // 添加 EditBox 中的更新逻辑 this.editBox.tick(); } ``` -### Input Handling +### 输入处理 -Since screens are subtypes of `GuiEventListener`s, the input handlers can also be overridden, such as for handling logic on a specific [key press][keymapping]. +由于屏幕是 `GuiEventListener` 的子类型,输入处理程序也可以被覆盖,例如处理特定的[按键][keymapping]逻辑。 -### Rendering the Screen +### 屏幕渲染 -Finally, screens are rendered through the `#render` method provided by being a `Renderable` subtype. As mentioned, the `#render` method draws the everything the screen has to render every frame, such as the background, widgets, tooltips, etc. By default, the `#render` method only renders the widgets to the screen. +最后,屏幕通过作为 `Renderable` 子类型而提供的 `#render` 方法进行渲染。正如前面提到的,`#render` 方法在每一帧绘制屏幕上的所有内容,比如背景、小部件、工具提示等。默认情况下,`#render` 方法只会将小部件渲染到屏幕上。 -The two most common things rendered within a screen that is typically not handled by a subtype is the background and the tooltips. +屏幕中通常不由子类型处理的两个最常见的渲染内容是背景和工具提示。 -The background can be rendered using `#renderBackground`, with one method taking in a v Offset for the options background whenever a screen is rendered when the level behind it cannot be. +背景可以使用 `#renderBackground` 进行渲染,其中一个方法接受一个 v 偏移量,用于在渲染屏幕时绘制选项背景,当背后的级别无法显示时使用。 -Tooltips are rendered through `GuiGraphics#renderTooltip` or `GuiGraphics#renderComponentTooltip` which can take in the text components being rendered, an optional custom tooltip component, and the x / y relative coordinates on where the tooltip should be rendered on the screen. +工具提示通过 `GuiGraphics#renderTooltip` 或 `GuiGraphics#renderComponentTooltip` 进行渲染,它们可以接受正在渲染的文本组件、可选的自定义工具提示组件以及工具提示应该在屏幕上渲染的 x / y 相对坐标。 ```java -// In some Screen subclass +// 在某个屏幕的子类中 -// mouseX and mouseY indicate the scaled coordinates of where the cursor is in on the screen +// mouseX 和 mouseY 表示光标在屏幕上的缩放坐标 @Override public void render(GuiGraphics graphics, int mouseX, int mouseY, float partialTick) { - // Background is typically rendered first + // 通常首先渲染背景 this.renderBackground(graphics); - // Render things here before widgets (background textures) + // 在小部件之前渲染东西(背景纹理) - // Then the widgets if this is a direct child of the Screen + // 如果这是屏幕的直接子类,则渲染小部件 super.render(graphics, mouseX, mouseY, partialTick); - // Render things after widgets (tooltips) + // 在小部件之后渲染东西(工具提示) } ``` -### Closing the Screen +### 关闭屏幕 -When a screen is closed, two methods handle the teardown: `#onClose` and `#removed`. +当屏幕关闭时,两个方法处理清理工作:`#onClose` 和 `#removed`。 -`#onClose` is called whenever the user makes an input to close the current screen. This method is typically used as a callback to destroy and save any internal processes in the screen itself. This includes sending packets to the server. +`#onClose` 在用户输入关闭当前屏幕时调用。通常用作销毁和保存屏幕内部流程的回调。这包括向服务器发送数据包。 -`#removed` is called just before the screen changes and is released to the garbage collector. This handles anything that hasn't been reset back to its initial state before the screen was opened. +`#removed` 在屏幕改变之前调用,并释放给垃圾收集器。这处理任何在屏幕打开之前未重置回初始状态的内容。 ```java -// In some Screen subclass +// 在某个屏幕的子类中 @Override public void onClose() { - // Stop any handlers here + // 在这里停止任何处理程序 - // Call last in case it interferes with the override + // 最后调用,以防干扰覆盖 super.onClose(); } @Override public void removed() { - // Reset initial states here + // 在这里重置初始状态 - // Call last in case it interferes with the override + // 最后调用,以防干扰覆盖 super.removed() ;} ``` ## `AbstractContainerScreen` -If a screen is directly attached to a [menu][menus], then an `AbstractContainerScreen` should be subclassed instead. An `AbstractContainerScreen` acts as the renderer and input handler of a menu and contains logic for syncing and interacting with slots. As such, only two methods typically need to be overridden or implemented to have a working container screen. Once again, to make it easier to understand, the components of a container screen will be mentioned in the order they are typically encountered. +如果一个屏幕直接附加到一个[菜单][menus]上,则应该使用 `AbstractContainerScreen` 的子类。`AbstractContainerScreen` 作为菜单的渲染器和输入处理程序,并包含与插槽同步和交互的逻辑。因此,通常只需要覆盖或实现两个方法,就可以有一个可工作的容器屏幕。再次强调,为了更易于理解,容器屏幕的组件将按照它们通常出现的顺序进行说明。 + +通常,`AbstractContainerScreen` 需要三个参数:被打开的容器菜单(由泛型 `T` 表示)、玩家库存(仅用于显示名称 -An `AbstractContainerScreen` typically requires three parameters: the container menu being opened (represented by the generic `T`), the player inventory (only for the display name), and the title of the screen itself. Within here, a number of positioning fields can be set: +)和屏幕本身的标题。在这里,可以设置一些定位字段: -Field | Description +字段 | 描述 :---: | :--- -`imageWidth` | The width of the texture used for the background. This is typically inside a PNG of 256 x 256 and defaults to 176. -`imageHeight` | The width of the texture used for the background. This is typically inside a PNG of 256 x 256 and defaults to 166. -`titleLabelX` | The relative x coordinate of where the screen title will be rendered. -`titleLabelY` | The relative y coordinate of where the screen title will be rendered. -`inventoryLabelX` | The relative x coordinate of where the player inventory name will be rendered. -`inventoryLabelY` | The relative y coordinate of where the player inventory name will be rendered. +`imageWidth` | 背景使用的纹理的宽度。这通常位于一个 256 x 256 的 PNG 内,默认为 176。 +`imageHeight` | 背景使用的纹理的高度。这通常位于一个 256 x 256 的 PNG 内,默认为 166。 +`titleLabelX` | 屏幕标题将被渲染的相对 x 坐标。 +`titleLabelY` | 屏幕标题将被渲染的相对 y 坐标。 +`inventoryLabelX` | 玩家库存名称将被渲染的相对 x 坐标。 +`inventoryLabelY` | 玩家库存名称将被渲染的相对 y 坐标。 :::caution -In a previous section, it mentioned that precomputed relative coordinates should be set in the `#init` method. This still remains true, as the values mentioned here are not precomputed coordinates but static values and relativized coordinates. +在前面的部分中,提到应该在 `#init` 方法中设置预计算的相对坐标。这仍然适用,因为这里提到的值不是预计算的坐标,而是静态值和相对化的坐标。 -The image values are static and non changing as they represent the background texture size. To make things easier when rendering, two additional values (`leftPos` and `topPos`) are precomputed in the `#init` method which marks the top left corner of where the background will be rendered. The label coordinates are relative to these values. +图像值是静态且不变的,因为它们代表背景纹理的大小。为了在渲染时更方便,`#init` 方法中预计算了两个附加值 (`leftPos` 和 `topPos`),标记了背景将被渲染的左上角位置。标签坐标是相对于这些值的。 -The `leftPos` and `topPos` is also used as a convenient way to render the background as they already represent the position to pass into the `#blit` method. +`leftPos` 和 `topPos` 也被用作渲染背景的便捷方式,因为它们已经表示传递到 `#blit` 方法的位置。 :::caution ```java -// In some AbstractContainerScreen subclass +// 在某个 AbstractContainerScreen 子类中 public MyContainerScreen(MyMenu menu, Inventory playerInventory, Component title) { super(menu, playerInventory, title); @@ -235,103 +239,99 @@ public MyContainerScreen(MyMenu menu, Inventory playerInventory, Component title this.inventoryLabelX = 10; /* - * If the 'imageHeight' is changed, 'inventoryLabelY' must also be - * changed as the value depends on the 'imageHeight' value. + * 如果 'imageHeight' 被改变,'inventoryLabelY' 也必须被 + * 改变,因为该值取决于 'imageHeight' 的值。 */ } ``` -### Menu Access +### 菜单访问 -As the menu is passed into the screen, any values that were within the menu and synced (either through slots, data slots, or a custom system) can now be accessed through the `menu` field. +由于菜单被传递到屏幕中,现在可以通过 `menu` 字段访问任何在菜单中并通过插槽、数据插槽或自定义系统同步的值。 -### Container Tick +### 容器更新 -Container screens tick within the `#tick` method when the player is alive and looking at the screen via `#containerTick`. This essentially takes the place of `#tick` within container screens, with its most common usage being to tick the recipe book. +当玩家活着并且正在查看屏幕时,容器屏幕在 `#tick` 方法中进行更新,通过 `#containerTick` 进行。这基本上取代了容器屏幕内部的 `#tick` 方法,最常见的用法是进行食谱书的更新。 ```java -// In some AbstractContainerScreen subclass +// 在某个 AbstractContainerScreen 子类中 @Override protected void containerTick() { super.containerTick(); - // Tick things here + // 在这里更新内容 } ``` -### Rendering the Container Screen +### 渲染容器屏幕 -The container screen is rendered across three methods: `#renderBg`, which renders the background textures, `#renderLabels`, which renders any text on top of the background, and `#render` which encompass the previous two methods in addition to providing a grayed out background and tooltips. +容器屏幕的渲染涉及三种方法:`#renderBg`,用于渲染背景纹理,`#renderLabels`,用于在背景上方渲染任何文本,以及 `#render`,它包含前两种方法,并提供了一个灰色背景和工具提示。 -Starting with `#render`, the most common override (and typically the only case) adds the background, calls the super to render the container screen, and finally renders the tooltips on top of it. +从 `#render` 开始,最常见的覆盖(通常是唯一的情况)添加了背景,调用 super 来渲染容器屏幕,并最后在其上渲染工具提示。 ```java -// In some AbstractContainerScreen subclass +// 在某个 AbstractContainerScreen 子类中 @Override public void render(GuiGraphics graphics, int mouseX, int mouseY, float partialTick) { this.renderBackground(graphics); super.render(graphics, mouseX, mouseY, partialTick); /* - * This method is added by the container screen to render - * the tooltip of the hovered slot. + * 这个方法由容器屏幕添加,用于渲染鼠标悬停插槽的工具提示。 */ this.renderTooltip(graphics, mouseX, mouseY); } ``` -Within the super, `#renderBg` is called to render the background of the screen. The most standard representation uses three method calls: two for setup and one to draw the background texture. +在 super 中,调用了 `#renderBg` 来渲染屏幕的背景。最标准的表示使用了三个方法调用:两个用于设置,一个用于绘制背景纹理。 ```java -// In some AbstractContainerScreen subclass +// 在某个 AbstractContainerScreen 子类中 -// The location of the background texture (assets//) +// 背景纹理的位置(assets//) private static final ResourceLocation BACKGROUND_LOCATION = new ResourceLocation(MOD_ID, "textures/gui/container/my_container_screen.png"); @Override protected void renderBg(GuiGraphics graphics, float partialTick, int mouseX, int mouseY) { /* - * Sets the texture location for the shader to use. While up to - * 12 textures can be set, the shader used within 'blit' only - * looks at the first texture index. + * 设置着色器使用的纹理位置。虽然最多可以设置12个纹理,但 'blit' 中使用的着色器 + * 只查看第一个纹理索引。 */ RenderSystem.setShaderTexture(0, BACKGROUND_LOCATION); /* - * Renders the background texture to the screen. 'leftPos' and - * 'topPos' should already represent the top left corner of where - * the texture should be rendered as it was precomputed from the - * 'imageWidth' and 'imageHeight'. The two zeros represent the - * integer u/v coordinates inside the 256 x 256 PNG file. + * 将背景纹理渲染到屏幕上。'leftPos' 和 'topPos' 应该已经表示纹理应该渲染的 + * 左上角位置,因为它们是从 'imageWidth' 和 'imageHeight' 预计算出来的。两个 + * 零表示在 256 x 256 PNG 文件内的整数 u/v 坐标。 */ graphics.blit(BACKGROUND_LOCATION, this.leftPos, this.topPos, 0, 0, this.imageWidth, this.imageHeight); } ``` -Finally, `#renderLabels` is called to render any text above the background, but below the tooltips. This simply calls uses the font to draw the associated components. +最后,调用 `#renderLabels` 来在背景上方但工具提示下方渲染任何文本。这简单地使用字体来绘制关联的组件。 ```java -// In some AbstractContainerScreen subclass +// 在某个 AbstractContainerScreen 子类中 @Override protected void renderLabels(GuiGraphics graphics, int mouseX, int mouseY) { super.renderLabels(graphics, mouseX, mouseY); - // Assume we have some Component 'label' - // 'label' is drawn at 'labelX' and 'labelY' + // 假设我们有一些 Component 'label' + // 'label' 将被绘制在 'labelX' 和 'labelY' 处 graphics.drawString(this.font, this.label, this.labelX, this.labelY, 0x404040); } ``` :::note -When rendering the label, you do **not** need to specify the `leftPos` and `topPos` offset. Those have already been translated within the `PoseStack` so everything within this method is drawn relative to those coordinates. +在渲染标签时,**不需要**指定 `leftPos` 和 `topPos` 偏移量。这些已经在 `PoseStack` 中被转换,因此该方法内的所有内容都相对于这些坐标进行绘制。 ::: -## Registering an AbstractContainerScreen +## 注册 AbstractContainerScreen -To use an `AbstractContainerScreen` with a menu, it needs to be registered. This can be done by calling `register` within the `RegisterMenuScreensEvent` on the [**mod event bus**][modbus]. +要将 `AbstractContainerScreen` 与菜单一起使用,需要对其进行注册。可以在 [**mod 事件总线**][modbus] 的 `RegisterMenuScreensEvent` 中调用 `register` 方法来完成。 ```java -// Event is listened to on the mod event bus +// 事件在 mod 事件总线上监听 private void registerScreens(RegisterMenuScreensEvent event) { event.register(MY_MENU.get(), MyContainerScreen::new); } From 0e36882f4f5c47e836ade84af3c1a3660fd5c6c4 Mon Sep 17 00:00:00 2001 From: DaZiDian <113085496+DaZiDian@users.noreply.github.com> Date: Thu, 25 Apr 2024 20:42:33 +0800 Subject: [PATCH 52/87] Update bewlr.md --- docs/items/bewlr.md | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/docs/items/bewlr.md b/docs/items/bewlr.md index fcabedad8..44f3d6d32 100644 --- a/docs/items/bewlr.md +++ b/docs/items/bewlr.md @@ -1,22 +1,22 @@ -BlockEntityWithoutLevelRenderer -======================= -`BlockEntityWithoutLevelRenderer` is a method to handle dynamic rendering on items. This system is much simpler than the old `ItemStack` system, which required a `BlockEntity`, and did not allow access to the `ItemStack`. +### BlockEntityWithoutLevelRenderer -Using BlockEntityWithoutLevelRenderer +`BlockEntityWithoutLevelRenderer` 是一种处理物品的动态渲染方法。这个系统比旧的 `ItemStack` 系统简单得多,因为旧系统需要一个 `BlockEntity`,并且无法访问 `ItemStack`。 + +使用 BlockEntityWithoutLevelRenderer -------------------------- -BlockEntityWithoutLevelRenderer allows you to render your item using `public void renderByItem(ItemStack itemStack, ItemDisplayContext ctx, PoseStack poseStack, MultiBufferSource bufferSource, int combinedLight, int combinedOverlay)`. +BlockEntityWithoutLevelRenderer 允许你使用 `public void renderByItem(ItemStack itemStack, ItemDisplayContext ctx, PoseStack poseStack, MultiBufferSource bufferSource, int combinedLight, int combinedOverlay)` 来渲染你的物品。 -In order to use an BEWLR, the `Item` must first satisfy the condition that its model returns true for `BakedModel#isCustomRenderer`. If it does not have one, it will use the default `ItemRenderer#getBlockEntityRenderer`. Once that returns true, the Item's BEWLR will be accessed for rendering. +为了使用 BEWLR,`Item` 必须首先满足一个条件:它的模型对于 `BakedModel#isCustomRenderer` 返回 true。如果没有自定义渲染器,它将使用默认的 `ItemRenderer#getBlockEntityRenderer`。一旦返回 true,物品的 BEWLR 将被访问以进行渲染。 :::note -`Block`s also render using a BEWLR if `Block#getRenderShape` is set to `RenderShape#ENTITYBLOCK_ANIMATED`. +如果 `Block#getRenderShape` 设置为 `RenderShape#ENTITYBLOCK_ANIMATED`,`Block` 也会使用 BEWLR 进行渲染。 ::: -To set the BEWLR for an Item, an anonymous instance of `IClientItemExtensions` must be consumed within `Item#initializeClient`. Within the anonymous instance, `IClientItemExtensions#getCustomRenderer` should be overridden to return the instance of your BEWLR: +要为物品设置 BEWLR,必须在 `Item#initializeClient` 中消费 `IClientItemExtensions` 的匿名实例。在匿名实例中,应该重写 `IClientItemExtensions#getCustomRenderer` 以返回你的 BEWLR 的实例: ```java -// In your item class +// 在你的物品类中 @Override public void initializeClient(Consumer consumer) { consumer.accept(new IClientItemExtensions() { @@ -30,7 +30,7 @@ public void initializeClient(Consumer consumer) { ``` :::caution -Each mod should only have one instance of a custom BEWLR. +每个模组应该只有一个自定义 BEWLR 实例。 ::: -That is it, no additional setup is necessary to use a BEWLR. +就是这样,使用 BEWLR 不需要额外的设置。 From 2c710547f8561aa8cf8a19c410e4082eb3135cf1 Mon Sep 17 00:00:00 2001 From: DaZiDian <113085496+DaZiDian@users.noreply.github.com> Date: Thu, 25 Apr 2024 20:45:55 +0800 Subject: [PATCH 53/87] Update index.md --- docs/items/index.md | 168 ++++++++++++++++++++++---------------------- 1 file changed, 85 insertions(+), 83 deletions(-) diff --git a/docs/items/index.md b/docs/items/index.md index 0e1221d47..5765e113e 100644 --- a/docs/items/index.md +++ b/docs/items/index.md @@ -1,87 +1,89 @@ -# Items +# 物品 -Along with blocks, items are a key component of Minecraft. While blocks make up the world around you, items exist within inventories. +除了方块外,物品是 Minecraft 的关键组成部分。方块构成了你周围的世界,而物品存在于物品栏中。 -## What Even Is an Item? +## 物品到底是什么? -Before we get further into creating items, it is important to understand what an item actually is, and what distinguishes it from, say, a [block][block]. Let's illustrate this using an example: +在我们进一步创建物品之前,了解物品究竟是什么,以及它与方块的区别是什么,是非常重要的。让我们通过一个例子来说明这一点: -- In the world, you encounter a dirt block and want to mine it. This is a **block**, because it is placed in the world. (Actually, it is not a block, but a blockstate. See the [Blockstates article][blockstates] for more detailed information.) - - Not all blocks drop themselves when breaking (e.g. leaves), see the article on [loot tables][loottables] for more information. -- Once you have [mined the block][breaking], it is removed (= replaced with an air block) and the dirt drops. The dropped dirt is an item **entity**. This means that like other entities (pigs, zombies, arrows, etc.), it can inherently be moved by things like water pushing on it, or burned by fire and lava. -- Once you pick up the dirt item entity, it becomes an **item stack** in your inventory. An item stack is, simply put, an instance of an item with some extra information, such as the stack size. -- Item stacks are backed by their corresponding **item** (which is what we're creating). Items hold information that is the same across all items (for example, every iron sword has a max durability of 250), while item stacks hold information that can be different between two similar items (for example, one iron sword has 100 uses left, while another iron sword has 200 uses left). For more information on what is done through items and what is done through item stacks, read on. - - The relationship between items and item stacks is roughly the same as between [blocks][block] and [blockstates][blockstates], in that a blockstate is always backed by a block. It's not a really accurate comparison (item stacks aren't singletons, for example), but it gives a good basic idea about what the concept is here. +- 在游戏世界中,你遇到了一个泥土方块并想要挖掘它。这是一个 **方块**,因为它被放置在世界中。(实际上,它不是一个方块,而是一个方块状态。请参阅 [方块状态文章][blockstates] 以获取更详细的信息。) + - 并非所有方块在破坏时都会掉落自己(例如树叶),有关更多信息,请参阅 [战利品表][loottables] 文章。 +- 一旦你 [挖掘了方块][breaking],它就会被移除(即被替换为空气方块),并且泥土掉落。掉落的泥土是一个 **物品实体**。这意味着像其他实体(猪、僵尸、箭等)一样,它可以被水推动,或者被火和岩浆燃烧。 +- 一旦你捡起泥土物品实体,它就会成为你物品栏中的一个 **物品堆叠**。物品堆叠简单地说就是一个物品的实例,带有一些额外的信息,比如堆叠大小。 +- 物品堆叠由它们对应的 **物品**(我们正在创建的东西)支持,物品持有所有物品之间相同的信息(例如,每把铁剑的最大耐久度为 250),而物品堆叠持有在两个类似物品之间可能不同的信息(例如,一把铁剑剩余 100 次使用,而另一把铁剑剩余 200 次使用)。有关通过物品和物品堆叠执行的操作和通过物品堆叠执行的操作的更多信息,请继续阅读。 + - 物品与物品堆叠之间的关系大致与 [方块][block] 和 [方块状态][blockstates] 之间的关系相同,即方块状态始终由方块支持。这不是一个非常准确的比较(物品堆叠不是单例,例如),但它可以给出一个关于这里概念的好基本理解。 -## Creating an Item +## 创建一个物品 -Now that we understand what an item is, let's create one! +现在我们了解了物品是什么,让我们创建一个吧! -Like with basic blocks, for basic items that need no special functionality (think sticks, sugar, etc.), the `Item` class can be used directly. To do so, during registration, instantiate `Item` with a `Item.Properties` parameter. This `Item.Properties` parameter can be created using `Item.Properties#of`, and it can be customized by calling its methods: +与基本方块一样,对于不需要特殊功能的基本物品(如棍子、糖等),可以直接使用 `Item` 类。为此,在注册期间,使用 `Item.Properties` 参数实例化 `Item`。可以使用 `Item.Properties#of` 创建此 `Item.Properties` 参数,并通过调用其方法来自定义它: -- `stacksTo` - Sets the max stack size of this item. Defaults to 64. Used e.g. by ender pearls or other items that only stack to 16. -- `durability` - Sets the durability of this item. Defaults to 0, which means "no durability". For example, iron tools use 250 here. Note that setting the durability automatically locks the stack size to 1. -- `craftRemainder` - Sets the crafting remainder of this item. Vanilla uses this for filled buckets that leave behind empty buckets after crafting. -- `fireResistant` - Makes item entities that use this item immune to fire and lava. Used by various netherite items. -- `setNoRepair` - Disables anvil and crafting grid repairing for this item. Unused in vanilla. -- `rarity` - Sets the rarity of this item. Currently, this simply changes the item's color. `Rarity` is an enum consisting of the four values `COMMON` (white, default), `UNCOMMON` (yellow), `RARE` (aqua) and `EPIC` (light purple). Be aware that mods may add more rarity types. -- `requiredFeatures` - Sets the required feature flags for this item. This is mainly used for vanilla's feature locking system in minor versions. It is discouraged to use this, unless you're integrating with a system locked behind feature flags by vanilla. -- `food` - Sets the [`FoodProperties`][food] of this item. +- `stacksTo` - 设置此物品的最大堆叠大小。默认为 64。例如末影珍珠或其他只能堆叠到 16 的物品使用了这个值。 +- `durability` - 设置此物品的耐久度。默认为 0,表示“无耐久度”。例如,铁制工具在此处使用了 250。请注意,设置耐久度会自动将堆叠大小锁定为 1。 +- `craftRemainder` - 设置此物品的制作剩余物品。Vanilla 在制作后留下空桶时使用了这个值。 +- `fireResistant` - 使使用此物品的物品实体对火和岩浆免疫。许多下界物品都使用了这个。 +- `setNoRepair` - 禁用此物品的铁砧和合成网格修复。Vanilla 中未使用。 +- `rarity` - 设置此物品的稀有度。当前,这只是改变物品的颜色。`Rarity` 是一个由四个值 `COMMON`(白色,默认)、`UNCOMMON`(黄色)、`RARE`(青色)和 `EPIC`(浅紫色) 组成的枚举。请注意,模组可能会添加更多的稀有度类型。 +- `requiredFeatures` - 设置此物品所需的功能标志。这主要用于小版本中 Vanilla 的功能锁定系统。除非你要集成 Vanilla 中由功能标志锁定的系统,否则不建议使用这个。 +- `food` - 设置此物品的 [`FoodProperties`][food]。 -For examples, or to look at the various values used by Minecraft, have a look at the `Items` class. +有关示例,或查看 Minecraft 中使用的各种值,请查看 `Items` 类。 -### Food +### 食物 -The `Item` class provides default functionality for food items, meaning you don't need a separate class for that. To make your item edible, all you need to do is set the `FoodProperties` on it through the `food` method in `Item.Properties`. +`Item` 类提供了食物物品的默认功能,这意味着你不需要单独的类来处理。要使你的物品可食用,你只需要通过 `Item.Properties` 的 `food` 方法设置其上的 `FoodProperties`。 -`FoodProperties` are created using a `FoodProperties.Builder`. You can then set various properties on it: +使用 `FoodProperties.Builder` 创建 `FoodProperties`。然后,你可以在其上设置各种属性: -- `nutrition` - Probably the most obvious part. Sets how many hunger points are restored. Counts in half hunger points, so for example, Minecraft's steak restores 8 hunger points. -- `saturationMod` - The saturation modifier used in calculating the [saturation value][hunger] restored when eating this food. The calculation is `min(2 * nutrition * saturationMod, playerNutrition)`, meaning that using `0.5` will make the effective saturation value the same as the nutrition value. -- `meat` - Whether this item should be considered meat or not. Used e.g. for determining if healing dogs with this food is possible. -- `alwaysEat` - Whether this item can always be eaten, even if the hunger bar is full. `false` by default, `true` for golden apples and other items that provide bonuses beyond just filling the hunger bar. -- `fast` - Whether fast eating should be enabled for this food. `false` by default, `true` for dried kelp in vanilla. -- `effect` - Adds a [`MobEffectInstance`][mobeffectinstance] to apply when eating this item. The second parameter denotes the probability of the effect being applied; for example, Rotten Flesh has an 80% chance (= 0.8) of applying the Hunger effect when eaten. This method comes in two variants; you should use the one that takes in a supplier (the other one directly takes a mob effect instance and is deprecated by NeoForge due to classloading issues). -- `build` - Once you've set everything you want to set, call `build` to get a `FoodProperties` object for further use. +- `nutrition` - 可能是最明显的部分。设置恢复多少饥饿点。以半个饥饿点为单位计数,所以例如,Minecraft 的牛排恢复了 8 个饥饿点。 +- `saturationMod` - 用于计算 [进食][hunger] 时恢复的饱和度值的饱和度修 -For examples, or to look at the various values used by Minecraft, have a look at the `Foods` class. +饰符。计算公式为 `min(2 * nutrition * saturationMod, playerNutrition)`,这意味着使用 `0.5` 将使有效的饱和度值与营养值相同。 +- `meat` - 是否应将此物品视为肉类。用于确定是否可以使用此食物治愈狗。 +- `alwaysEat` - 此物品是否始终可以食用,即使饥饿条已满。默认为 `false`,例如金苹果等提供了除填充饥饿条之外的奖励的物品为 `true`。 +- `fast` - 是否为此食物启用快速进食。默认为 `false`,例如 Vanilla 中的干海带为 `true`。 +- `effect` - 在吃这个物品时添加一个 [`MobEffectInstance`][mobeffectinstance]。第二个参数表示应用效果的概率;例如,腐肉在被吃时有 80% 的几率(= 0.8)应用饥饿效果。这个方法有两个变体;你应该使用一个带有提供者的(另一个直接使用了一个 mob 效果实例,并因为类加载问题而被 NeoForge 废弃)。 +- `build` - 一旦你设置了你想设置的所有内容,调用 `build` 获取一个用于进一步使用的 `FoodProperties` 对象。 -To get the `FoodProperties` for an item, call `Item#getFoodProperties(ItemStack, LivingEntity)`. This may return null, since not every item is edible. To determine whether an item is edible, call `Item#isEdible()` or null-check the result of the `getFoodProperties` call. +有关示例,或查看 Minecraft 中使用的各种值,请查看 `Foods` 类。 -### More Functionality +要获取物品的 `FoodProperties`,请调用 `Item#getFoodProperties(ItemStack, LivingEntity)`。这可能返回 null,因为并非每个物品都是可食用的。要确定物品是否可食用,请调用 `Item#isEdible()` 或对 `getFoodProperties` 调用的结果进行空检查。 -Directly using `Item` only allows for very basic items. If you want to add functionality, for example right-click interactions, a custom class that extends `Item` is required. The `Item` class has many methods that can be overridden to do different things; see the classes `Item` and `IItemExtension` for more information. +### 更多功能 -The two most common use cases for items are left-clicking and right-clicking. For left-clicking, see [Breaking a Block][breaking] and Attacking an Entity (WIP). For right-clicking, see [The Interaction Pipeline][interactionpipeline]. +直接使用 `Item` 仅允许非常基本的物品。如果你想添加功能,例如右键交互,需要一个扩展 `Item` 的自定义类。`Item` 类有许多可以重写以执行不同操作的方法;有关更多信息,请参阅类 `Item` 和 `IItemExtension`。 + +物品的两种最常见用途是左键单击和右键单击。对于左键单击,请参阅 [破坏方块][breaking] 和 攻击实体(工作中)。对于右键单击,请参阅 [交互管道][interactionpipeline]。 ### `DeferredRegister.Items` -All registries use `DeferredRegister` to register their contents, and items are no exceptions. However, due to the fact that adding new items is such an essential feature of an overwhelming amount of mods, NeoForge provides the `DeferredRegister.Items` helper class that extends `DeferredRegister` and provides some item-specific helpers: +所有注册表都使用 `DeferredRegister` 来注册它们的内容,物品也不例外。然而,由于添加新物品是大量模组中一个至关重要的功能,NeoForge 提供了 `DeferredRegister.Items` 帮助类,它扩展了 `DeferredRegister` 并提供了一些特定于物品的辅助程序: ```java public static final DeferredRegister.Items ITEMS = DeferredRegister.createItems(ExampleMod.MOD_ID); public static final Supplier EXAMPLE_ITEM = ITEMS.registerItem( "example_item", - Item::new, // The factory that the properties will be passed into. - new Item.Properties() // The properties to use. + Item::new, // 将属性传递给工厂。 + new Item.Properties() // 要使用的属性。 ); ``` -Internally, this will simply call `ITEMS.register("example_item", () -> new Item(new Item.Properties()))` by applying the properties parameter to the provided item factory (which is commonly the constructor). +在内部,这只是通过将属性参数应用于提供的物品工厂(通常是构造函数)来调用 `ITEMS.register("example_item", () -> new Item(new Item.Properties()))`。 -If you want to use `Item::new`, you can leave out the factory entirely and use the `simple` method variant: +如果你想使用 `Item::new`,可以完全省略工厂,并使用 `simple` 方法变体: ```java public static final Supplier EXAMPLE_ITEM = ITEMS.registerSimpleItem( "example_item", - new Item.Properties() // The properties to use. + new Item.Properties() // 要使用的属性。 ); ``` -This does the exact same as the previous example, but is slightly shorter. Of course, if you want to use a subclass of `Item` and not `Item` itself, you will have to use the previous method instead. +这和上一个示例的效果完全相同,但稍微更短一些。当然,如果你想使用 `Item` 的子类而不是 `Item` 本身,则必须使用前一种方法。 -Both of these methods also have overloads that omit the `new Item.Properties()` parameter: +这两种方法也有省略 `new Item.Properties()` 参数的重载: ```java public static final Supplier EXAMPLE_ITEM = ITEMS.registerItem("example_item", Item::new); @@ -89,7 +91,7 @@ public static final Supplier EXAMPLE_ITEM = ITEMS.registerItem("example_it public static final Supplier EXAMPLE_ITEM = ITEMS.registerSimpleItem("example_item"); ``` -Finally, there's also shortcuts for block items: +最后,还有块项目的快捷方式: ```java public static final Supplier EXAMPLE_BLOCK_ITEM = ITEMS.registerSimpleBlockItem("example_block", ExampleBlocksClass.EXAMPLE_BLOCK, new Item.Properties()); @@ -102,86 +104,88 @@ public static final Supplier EXAMPLE_BLOCK_ITEM = ITEMS.registerSimpl ``` :::note -If you keep your registered blocks in a separate class, you should classload your blocks class before your items class. +如果您将注册的方块保存在单独的类中,则应该在项目类之前对方块类进行类加载。 ::: -### Resources +### 资源 -If you register your item and get your item (via `/give` or through a [creative tab][creativetabs]), you will find it to be missing a proper model and texture. This is because textures and models are handled by Minecraft's resource system. +如果你注册了你的物品并获得了你的物品(通过 `/give` 命令或通过 [创造模式标签][creativetabs]),你会发现它缺少正确的模型和纹理。这是因为纹理和模型是由 Minecraft 的资源系统处理的。 -To apply a simple texture to an item, you must add an item model JSON and a texture PNG. See the section on [resources][resources] for more information. +要为物品应用一个简单的纹理,你必须添加一个物品模型 JSON 文件和一个纹理 PNG 文件。有关更多信息,请参阅 [资源][resources] 部分。 -## `ItemStack`s +## `ItemStack`(物品堆叠) -Like with blocks and blockstates, most places where you'd expect an `Item` actually use an `ItemStack` instead. `ItemStack`s represent a stack of one or multiple items in a container, e.g. an inventory. Again like with blocks and blockstates, methods should be overridden by the `Item` and called on the `ItemStack`, and many methods in `Item` get an `ItemStack` instance passed in. +与方块和方块状态一样,大多数情况下你期望使用一个 `Item` 实际上都是使用 `ItemStack`。`ItemStack` 表示容器中一个或多个物品的堆叠,例如一个物品栏。与方块和方块状态一样,方法应该被 `Item` 重写,并在 `ItemStack` 上调用,`Item` 中的许多方法都会传入一个 `ItemStack` 实例。 -An `ItemStack` consists of three major parts: +`ItemStack` 由三个主要部分组成: -- The `Item` it represents, obtainable through `itemstack.getItem()`. -- The stack size, typically between 1 and 64, obtainable through `itemstack.getCount()` and changeable through `itemstack.setCount(int)` or `itemstack.shrink(int)`. -- The extra [NBT][nbt] data, where stack-specific data is stored. Obtainable through `itemstack.getTag()`, or alternatively through `itemstack.getOrCreateTag()` which accounts for no tag existing yet. A variety of other NBT-related methods exist as well, the most important being `hasTag()` and `setTag()`. - - It is worth nothing that `ItemStack`s with empty NBT are not the same as `ItemStack`s with no NBT at all. This means that they will not stack, despite being functionally equivalent to one another. +- 它所表示的 `Item`,可通过 `itemstack.getItem()` 获取。 +- 堆叠大小,通常在 1 和 64 之间,通过 `itemstack.getCount()` 获取,通过 `itemstack.setCount(int)` 或 `itemstack.shrink(int)` 可更改。 +- 额外的 [NBT][nbt] 数据,其中存储了堆叠特定的数据。可通过 `itemstack.getTag()` 获取,或者通过 `itemstack.getOrCreateTag()` 获取,它考虑了尚不存在标签的情况。还存在许多其他与 NBT 相关的方法,最重要的是 `hasTag()` 和 `setTag()`。 + - 值得注意的是,带有空 NBT 的 `ItemStack` 与根本没有 NBT 的 `ItemStack` 不同。这意味着它们不会堆叠,尽管它们在功能上是等效的。 -To create a new `ItemStack`, call `new ItemStack(Item)`, passing in the backing item. By default, this uses a count of 1 and no NBT data; there are constructor overloads that accept a count and NBT data as well if needed. +要创建一个新的 `ItemStack`,请调用 `new ItemStack(Item)`,传入支持的物品。默认情况下,这使用数量为 1 和没有 NBT 数据;如果需要,有接受数量和 NBT 数据的构造函数重载。 -`ItemStack`s are mutable objects (see below), however it is sometimes required to treat them as immutables. If you need to modify an `ItemStack` that is to be treated immutable, you can clone the stack using `itemstack.copy()`. +`ItemStack` 是可变对象(见下文),但有时需要将它们视为不可变的。如果你需要修改一个被视为不可变的 `ItemStack`,可以使用 `itemstack.copy()` 克隆堆栈。 -If you want to represent that a stack has no item, use `ItemStack.EMPTY`. If you want to check whether an `ItemStack` is empty, call `itemstack.isEmpty()`. +如果要表示堆叠没有物品,可以使用 `ItemStack.EMPTY`。要检查一个 `ItemStack` 是否为空,请调用 `itemstack.isEmpty()`。 -### Mutability of `ItemStack`s +### `ItemStack` 的可变性 -`ItemStack`s are mutable objects. This means that if you call for example `setCount`, `setTag` or `getOrCreateTag`, the `ItemStack` itself will be modified. Vanilla uses the mutability of `ItemStack`s extensively, and several methods rely on it. For example, `itemstack.split(int)` splits the given amount off the stack it is called on, both modifying the caller and returning a new `ItemStack` in the process. +`ItemStack` 是可变对象。这意味着如果你调用例如 `setCount`、`setTag` 或 `getOrCreateTag`,`ItemStack` 本身将被修改。Vanilla 广泛使用了 `ItemStack` 的可变性,许多方法依赖于它。例如,`itemstack.split(int)` 从调用者堆栈中分离给定数量的堆栈,同时修改调用者并在过程中返回一个新的 `ItemStack`。 -However, this can sometimes lead to issues when dealing with multiple `ItemStack`s at once. The most common instance where this arises is when handling inventory slots, since you have to consider both the `ItemStack` currently selected by the cursor, as well as the `ItemStack` you are trying to insert to/extract from. +然而,当处理多个 `ItemStack` 时,有时可能会出现问题。最常见的情况是处理库存槽时,因为你必须考虑到当前由光标选择的 `ItemStack`,以及你正在尝试插入/提取的 `ItemStack`。 :::tip -When in doubt, better be safe than sorry and `#copy()` the stack. +如果不确定,最好安全起见并对堆栈进行 `#copy()`。 ::: -## Creative Tabs +## 创造模式标签 -By default, your item will only be available through `/give` and not appear in the creative inventory. Let's change that! +默认情况下,你的物品只能通过 `/give` 命令获得,并不会出现在创造模式的物品栏中。让我们来改变这一点吧! -The way you get your item into the creative menu depends on what tab you want to add it to. +将你的物品添加到创造模式菜单中的方式取决于你想要添加到哪个标签。 -### Existing Creative Tabs +### 现有的创造模式标签 :::note -This method is for adding your items to Minecraft's tabs, or to other mods' tabs. To add items to your own tabs, see below. +这种方法用于将你的物品添加到 Minecraft 的标签,或其他模组的标签中。要将物品添加到你自己的标签中,请参见下文。 ::: -An item can be added to an existing `CreativeModeTab` via the `BuildCreativeModeTabContentsEvent`, which is fired on the [mod event bus][modbus], only on the [logical client][sides]. Add items by calling `event#accept`. +可以通过 `BuildCreativeModeTabContentsEvent` 将物品添加到现有的 `CreativeModeTab` 中,该事件在 [模组事件总线][modbus] 上触发,仅在 [逻辑客户端][sides] 上触发。通过调用 `event#accept` 来添加物品。 ```java -//MyItemsClass.MY_ITEM is a Supplier, MyBlocksClass.MY_BLOCK is a Supplier +//MyItemsClass.MY_ITEM 是 Supplier,MyBlocksClass.MY_BLOCK 是 Supplier @SubscribeEvent public static void buildContents(BuildCreativeModeTabContentsEvent event) { - // Is this the tab we want to add to? + // 这是我们要添加到的标签吗? if (event.getTabKey() == CreativeModeTabs.INGREDIENTS) { event.accept(MyItemsClass.MY_ITEM); - // Accepts an ItemLike. This assumes that MY_BLOCK has a corresponding item. + // 接受一个 ItemLike。这假设 MY_BLOCK 有一个相应的物品。 event.accept(MyBlocksClass.MY_BLOCK); } } ``` -The event also provides some extra information, such as `getFlags()` to get the list of enabled feature flags, or `hasPermissions()` to check if the player has permissions to view the operator items tab. +事件还提供了一些额外信息,例如 `getFlags()` 来获取已启用的功能标志列表,或 `hasPermissions()` 来检查玩家是否有权限查看操作员物品标签。 + +### 自定义创造模式标签 -### Custom Creative Tabs +`CreativeModeTab` 是一个注册表,意味着自定义的 `CreativeModeTab` 必须被 [注册][registering]。创建创造模式标签使用一个构建器系统 -`CreativeModeTab`s are a registry, meaning custom `CreativeModeTab`s must be [registered][registering]. Creating a creative tab uses a builder system, the builder is obtainable through `CreativeModeTab#builder`. The builder provides options to set the title, icon, default items, and a number of other properties. In addition, NeoForge provides additional methods to customize the tab's image, label and slot colors, where the tab should be ordered, etc. +,该构建器通过 `CreativeModeTab#builder` 获得。构建器提供了设置标题、图标、默认物品以及许多其他属性的选项。此外,NeoForge 还提供了额外的方法来自定义标签的图像、标签和槽颜色,标签应该排序在哪里等等。 ```java -//CREATIVE_MODE_TABS is a DeferredRegister +//CREATIVE_MODE_TABS 是 DeferredRegister public static final Supplier EXAMPLE_TAB = CREATIVE_MODE_TABS.register("example", () -> CreativeModeTab.builder() - //Set the title of the tab. Don't forget to add a translation! + // 设置标签的标题。不要忘记添加一个翻译! .title(Component.translatable("itemGroup." + MOD_ID + ".example")) - //Set the icon of the tab. + // 设置标签的图标。 .icon(() -> new ItemStack(MyItemsClass.EXAMPLE_ITEM.get())) - //Add your items to the tab. + // 将你的物品添加到标签中。 .displayItems((params, output) -> { output.accept(MyItemsClass.MY_ITEM); - // Accepts an ItemLike. This assumes that MY_BLOCK has a corresponding item. + // 接受一个 ItemLike。这假设 MY_BLOCK 有一个相应的物品。 output.accept(MyBlocksClass.MY_BLOCK); }) .build() @@ -200,5 +204,3 @@ public static final Supplier EXAMPLE_TAB = CREATIVE_MODE_TABS.r [modbus]: ../concepts/events.md#event-buses [nbt]: ../datastorage/nbt.md [registering]: ../concepts/registries.md#methods-for-registering -[resources]: ../resources/index.md#assets -[sides]: ../concepts/sides.md From 09a2189fb7900c23b4aa3ad4fc15419217975cfd Mon Sep 17 00:00:00 2001 From: DaZiDian <113085496+DaZiDian@users.noreply.github.com> Date: Thu, 25 Apr 2024 20:47:27 +0800 Subject: [PATCH 54/87] Update interactionpipeline.md --- docs/items/interactionpipeline.md | 88 +++++++++++++++---------------- 1 file changed, 44 insertions(+), 44 deletions(-) diff --git a/docs/items/interactionpipeline.md b/docs/items/interactionpipeline.md index 8f05f0aeb..7643241f5 100644 --- a/docs/items/interactionpipeline.md +++ b/docs/items/interactionpipeline.md @@ -1,71 +1,71 @@ The Interaction Pipeline ======================== -This page aims to make the fairly complex and confusing process of things being right-clicked by the player more understandable, as well as clarifying what result to use where and why. +本页面旨在使玩家右键单击的相当复杂和令人困惑的过程更容易理解,并澄清应在何处以及为什么使用哪种结果。 -What Happens When I Right-Click? +右键单击时发生了什么? -------------------------------- -When you right-click anywhere in the world, a number of things happen, depending on what you are currently looking at and what `ItemStack`s are in your hands. A number of methods returning one of two result types (see below) are called. Most of these methods cancel the pipeline if an explicit success or an explicit failure is returned. For the sake of readability, this "explicit success or explicit failure" will be called a "definitive result" from now on. - -- `InputEvent.InteractionKeyMappingTriggered` is fired with the right mouse button and the main hand. If the event is canceled, the pipeline ends. -- Several circumstances are checked, for example that you are not in spectator mode or that all required feature flags for the `ItemStack` in your main hand are enabled. If at least one of these checks fails, the pipeline ends. -- Depending on what you are looking at, different things happen: - - If you are looking at an entity that is within your reach and not outside the world border: - - `PlayerInteractEvent.EntityInteractSpecific` is fired. If the event is canceled, the pipeline ends. - - `Entity#interactAt` will be called **on the entity you are looking at**. If it returns a definitive result, the pipeline ends. - - If you want to add behavior for your own entity, override this method. If you want to add behavior for a vanilla entity, use the event. - - If the entity opens an interface (for example a villager trading GUI or a chest minecart GUI), the pipeline ends. - - `PlayerInteractEvent.EntityInteract` is fired. If the event is canceled, the pipeline ends. - - `Entity#interact` is called **on the entity you are looking at**. If it returns a definitive result, the pipeline ends. - - If you want to add behavior for your own entity, override this method. If you want to add behavior for a vanilla entity, use the event. - - For `Mob`s, the override of `Entity#interact` handles things like leashing and spawning babies when the `ItemStack` in your main hand is a spawn egg, and then defers mob-specific handling to `Mob#mobInteract`. The rules for results for `Entity#interact` apply here as well. - - If the entity you are looking at is a `LivingEntity`, `Item#interactLivingEntity` is called on the `ItemStack` in your main hand. If it returns a definitive result, the pipeline ends. - - If you are looking at a block that is within your reach and not outside the world border: - - `PlayerInteractEvent.RightClickBlock` is fired. If the event is canceled, the pipeline ends. You may also specifically deny only block or item usage in this event. - - `IItemExtension#onItemUseFirst` is called. If it returns a definitive result, the pipeline ends. - - If the player is not sneaking and the event does not deny block usage, `Block#use` is called. If it returns a definitive result, the pipeline ends. - - If the event does not deny item usage, `Item#useOn` is called. If it returns a definitive result, the pipeline ends. -- `Item#use` is called. If it returns a definitive result, the pipeline ends. -- The above process runs a second time, this time with the off hand instead of the main hand. - -Result Types +当你在世界中的任何地方右键单击时,会发生一系列的事情,这取决于你当前正在查看的内容以及你手中的 `ItemStack`。会调用返回两种结果类型之一的一些方法。如果显式地返回了成功或失败,大多数情况下这些方法将取消管线。为了易读起见,这里将“显式成功或显式失败”称为“明确结果”。 + +- 用右鼠标按钮和主手触发 `InputEvent.InteractionKeyMappingTriggered`。如果事件被取消,管线结束。 +- 检查了几种情况,例如你不处于旁观模式,或者你主手中的 `ItemStack` 的所有必需特性标志都已启用。如果这些检查中至少有一个失败,管线结束。 +- 根据你的视线朝向的内容不同,会发生不同的事情: + - 如果你的视线朝向一个在你可触及范围内且不在世界边界之外的实体: + - 触发 `PlayerInteractEvent.EntityInteractSpecific`。如果事件被取消,管线结束。 + - 将在你所看的实体上调用 `Entity#interactAt`。如果它返回了明确结果,管线结束。 + - 如果你想为你自己的实体添加行为,请重写此方法。如果你想为一个原版实体添加行为,请使用事件。 + - 如果实体打开了一个界面(例如村民交易 GUI 或箱子矿车 GUI),管线结束。 + - 触发 `PlayerInteractEvent.EntityInteract`。如果事件被取消,管线结束。 + - 将在你所看的实体上调用 `Entity#interact`。如果它返回了明确结果,管线结束。 + - 如果你想为你自己的实体添加行为,请重写此方法。如果你想为一个原版实体添加行为,请使用事件。 + - 对于 `Mob`,`Entity#interact` 的重写处理了像使用生成蛋时拴绳和产生孩子这样的事情,然后将特定于 mob 的处理推迟到 `Mob#mobInteract`。`Entity#interact` 的结果规则也适用于这里。 + - 如果你所看的实体是一个 `LivingEntity`,将在你主手中的 `ItemStack` 上调用 `Item#interactLivingEntity`。如果它返回了明确结果,管线结束。 + - 如果你的视线朝向一个在你可触及范围内且不在世界边界之外的方块: + - 触发 `PlayerInteractEvent.RightClickBlock`。如果事件被取消,管线结束。你也可以在这个事件中具体地否定只有方块或物品的使用。 + - 调用 `IItemExtension#onItemUseFirst`。如果它返回了明确结果,管线结束。 + - 如果玩家没有潜行并且事件没有否定方块的使用,将调用 `Block#use`。如果它返回了明确结果,管线结束。 + - 如果事件没有否定物品的使用,将调用 `Item#useOn`。如果它返回了明确结果,管线结束。 +- 调用 `Item#use`。如果它返回了明确结果,管线结束。 +- 上述过程再次运行,这次是用副手而不是主手。 + +结果类型 ------------ -There are two different types of results: `InteractionResult`s and `InteractionResultHolder`s. `InteractionResult` is used most of the time, only `Item#use` uses `InteractionResultHolder`. +有两种不同的结果类型:`InteractionResult` 和 `InteractionResultHolder`。`InteractionResult` 大多数情况下使用,只有 `Item#use` 使用 `InteractionResultHolder`。 -`InteractionResult` is an enum consisting of five values: `SUCCESS`, `CONSUME`, `CONSUME_PARTIAL`, `PASS` and `FAIL`. Additionally, the method `InteractionResult#sidedSuccess` is available, which returns `SUCCESS` on the server and `CONSUME` on the client. +`InteractionResult` 是一个枚举,包含五个值:`SUCCESS`、`CONSUME`、`CONSUME_PARTIAL`、`PASS` 和 `FAIL`。此外,方法 `InteractionResult#sidedSuccess` 可用,它在服务器端返回 `SUCCESS`,在客户端返回 `CONSUME`。 -`InteractionResultHolder` is a wrapper around `InteractionResult` that adds additional context for `T`. `T` can be anything, but in 99.99 percent of cases, it is an `ItemStack`. `InteractionResultHolder` provides wrapper methods for the enum values (`#success`, `#consume`, `#pass` and `#fail`), as well as `#sidedSuccess`, which calls `#success` on the server and `#consume` on the client. +`InteractionResultHolder` 是 `InteractionResult` 的包装器,它为 `T` 添加了额外的上下文。`T` 可以是任何东西,但在 99.99% 的情况下,它是一个 `ItemStack`。`InteractionResultHolder` 为枚举值提供了包装方法(`#success`、`#consume`、`#pass` 和 `#fail`),以及 `#sidedSuccess` 方法,它在服务器上调用 `#success`,在客户端上调用 `#consume`。 -Generally, the different values mean the following: +一般来说,不同的值意味着以下内容: -- `InteractionResult#sidedSuccess` (or `InteractionResultHolder#sidedSuccess` where needed) should be used if the operation should be considered successful, and you want the arm to swing. The pipeline will end. -- `InteractionResult.SUCCESS` (or `InteractionResultHolder#success` where needed) should be used if the operation should be considered successful, and you want the arm to swing, but only on one side. Only use this if you want to return a different value on the other logical side for whatever reason. The pipeline will end. -- `InteractionResult.CONSUME` (or `InteractionResultHolder#consume` where needed) should be used if the operation should be considered successful, but you do not want the arm to swing. The pipeline will end. -- `InteractionResult.CONSUME_PARTIAL` is mostly identical to `InteractionResult.CONSUME`, the only difference is in its usage in [`Item#useOn`][itemuseon]. -- `InteractionResult.FAIL` (or `InteractionResultHolder#fail` where needed) should be used if the item functionality should be considered failed and no further interaction should be performed. The pipeline will end. This can be used everywhere, but it should be used with care outside of `Item#useOn` and `Item#use`. In many cases, using `InteractionResult.PASS` makes more sense. -- `InteractionResult.PASS` (or `InteractionResultHolder#pass` where needed) should be used if the operation should be considered neither successful nor failed. The pipeline will continue. This is the default behavior (unless otherwise specified). +- `InteractionResult#sidedSuccess`(或需要时 `InteractionResultHolder#sidedSuccess`)应该在操作应该被认为成功,并且你想要挥动手臂时使用。管线将结束。 +- `InteractionResult.SUCCESS`(或需要时 `InteractionResultHolder#success`)应该在操作应该被认为成功,并且你想要挥动手臂时使用,但只在一侧使用。只有在出于某种原因希望在另一逻辑侧返回不同值时才使用此选项。管线将结束。 +- `InteractionResult.CONSUME`(或需要时 `InteractionResultHolder#consume`)应该在操作应该被认为成功,但你不想要挥动手臂时使用。管线将结束。 +- `InteractionResult.CONSUME_PARTIAL` 在大多数情况下与 `InteractionResult.CONSUME` 相同,唯一的区别在于它在 [`Item#useOn`][itemuseon] 中的使用方式。 +- `InteractionResult.FAIL`(或需要时 `InteractionResult -Some methods have special behavior or requirements, which are explained in the below chapters. +Holder#fail`)应该在物品功能被认为失败并且不应再进行进一步交互时使用。管线将结束。这可以用在任何地方,但在 `Item#useOn` 和 `Item#use` 之外使用时需要小心。在许多情况下,使用 `InteractionResult.PASS` 更有意义。 +- `InteractionResult.PASS`(或需要时 `InteractionResultHolder#pass`)应该在操作既不应被认为成功也不应被认为失败时使用。管线将继续。这是默认行为(除非另有规定)。 + +一些方法具有特殊的行为或要求,这些将在下面的章节中解释。 `IItemExtension#onItemUseFirst` --------------------------- -`InteractionResult#sidedSuccess` and `InteractionResult.CONSUME` don't have an effect here. Only `InteractionResult.SUCCESS`, `InteractionResult.FAIL` or `InteractionResult.PASS` should be used here. +`InteractionResult#sidedSuccess` 和 `InteractionResult.CONSUME` 在这里没有效果。在这里只应该使用 `InteractionResult.SUCCESS`、`InteractionResult.FAIL` 或 `InteractionResult.PASS`。 `Item#useOn` ------------ -If you want the operation to be considered successful, but you do not want the arm to swing or an `ITEM_USED` stat point to be awarded, use `InteractionResult.CONSUME_PARTIAL`. +如果你希望操作被视为成功,但你不希望手臂摆动或奖励一个 `ITEM_USED` 统计点,请使用 `InteractionResult.CONSUME_PARTIAL`。 `Item#use` ---------- -This is the only instance where the return type is `InteractionResultHolder`. The resulting `ItemStack` in the `InteractionResultHolder` replaces the `ItemStack` the usage was initiated with, if it has changed. - -The default implementation of `Item#use` returns `InteractionResultHolder#consume` when the item is edible and the player can eat the item (because they are hungry, or because the item is always edible), `InteractionResultHolder#fail` when the item is edible but the player cannot eat the item, and `InteractionResultHolder#pass` if the item is not edible. +这是唯一一个返回类型为 `InteractionResultHolder` 的实例。`InteractionResultHolder` 中的结果 `ItemStack` 将替换发起使用的 `ItemStack`,如果它已更改。 -Returning `InteractionResultHolder#fail` here while considering the main hand will prevent offhand behavior from running. If you want offhand behavior to run (which you usually want), return `InteractionResultHolder#pass` instead. +当物品可食用并且玩家可以吃掉物品时(因为他们饥饿了,或者因为物品总是可食用时),`Item#use` 的默认实现返回 `InteractionResultHolder#consume`;当物品可食用但玩家无法吃掉物品时,返回 `InteractionResultHolder#fail`;如果物品不可食用,则返回 `InteractionResultHolder#pass`。 -[itemuseon]: #itemuseon +在考虑主手时返回 `InteractionResultHolder#fail` 将阻止运行副手行为。如果你希望运行副手行为(通常是这样),请改为返回 `InteractionResultHolder#pass`。 From b4f10e21c846b08fefea6fd72c131cd254bb9603 Mon Sep 17 00:00:00 2001 From: DaZiDian <113085496+DaZiDian@users.noreply.github.com> Date: Thu, 25 Apr 2024 20:49:09 +0800 Subject: [PATCH 55/87] Update mobeffects.md --- docs/items/mobeffects.md | 119 +++++++++++++++++++-------------------- 1 file changed, 59 insertions(+), 60 deletions(-) diff --git a/docs/items/mobeffects.md b/docs/items/mobeffects.md index a8c9e98ad..17ef1d09c 100644 --- a/docs/items/mobeffects.md +++ b/docs/items/mobeffects.md @@ -1,18 +1,18 @@ # Mob Effects & Potions -Status effects, sometimes known as potion effects and referred to in-code as `MobEffect`s, are effects that influence an entity every tick. This article explains how to use them, what the difference between an effect and a potion is, and how to add your own. +状态效果,有时称为药水效果,并在代码中称为 `MobEffect`,是每个游戏刻对实体产生影响的效果。本文解释了如何使用它们,效果与药水之间的区别,以及如何添加自定义效果。 -## Terminology +## 术语 -- A `MobEffect` affects an entity every tick. Like [blocks][block] or [items][item], `MobEffect`s are registry objects, meaning they must be [registered][registration] and are singletons. - - An **instant mob effect** is a special kind of mob effect that is designed to be applied for one tick. Vanilla has two instant effects, Instant Health and Instant Harming. -- A `MobEffectInstance` is an instance of a `MobEffect`, with a duration, amplifier and some other properties set (see below). `MobEffectInstance`s are to `MobEffect`s what [`ItemStack`s][itemstack] are to `Item`s. -- A `Potion` is a collection of `MobEffectInstance`s. Vanilla mainly uses potions for the four potion items (read on), however, they can be applied to any item at will. It is up to the item if and how the item then uses the potion set on it. -- A **potion item** is an item that is meant to have a potion set on it. This is an informal term, the vanilla `PotionItem` class has nothing to do with this (it refers to the "normal" potion item). Minecraft currently has four potion items: potions, splash potions, lingering potions, and tipped arrows; however more may be added by mods. +- `MobEffect` 每个游戏刻对实体产生影响。与[方块][block]或[物品][item]一样,`MobEffect` 是注册对象,这意味着它们必须[注册][registration]并且是单例的。 + - **即时效果** 是一种特殊类型的效果,设计用于应用一次游戏刻。原版有两种即时效果,即即时治疗和即时伤害。 +- `MobEffectInstance` 是 `MobEffect` 的实例,具有持续时间、增幅和一些其他设置(见下文)。`MobEffectInstance` 对于 `MobEffect` 就像 [`ItemStack`][itemstack] 对于 `Item` 一样。 +- `Potion` 是一组 `MobEffectInstance`。原版主要用于四种药水物品(后文),但可以随意应用于任何物品。物品如何使用所设置的药水取决于物品本身。 +- **药水物品** 是指应设置药水的物品。这是一个非正式的术语,原版中有四种药水物品:药水、溅射药水、挥发药水和毒箭;但是模组可能会添加更多。 ## `MobEffect`s -To create your own `MobEffect`, extend the `MobEffect` class: +要创建自己的 `MobEffect`,请扩展 `MobEffect` 类: ```java public class MyMobEffect extends MobEffect { @@ -22,38 +22,37 @@ public class MyMobEffect extends MobEffect { @Override public void applyEffectTick(LivingEntity entity, int amplifier) { - // Apply your effect logic here. + // 在这里应用你的效果逻辑。 } - // Whether the effect should apply this tick. Used e.g. by the Regeneration effect that only applies - // once every x ticks, depending on the tick count and amplifier. + // 决定是否在此游戏刻应用效果。例如,恢复效果每 x 个游戏刻应用一次,具体取决于游戏刻和增幅。 @Override public boolean shouldApplyEffectTickThisTick(int tickCount, int amplifier) { - return tickCount % 2 == 0; // replace this with whatever check you want + return tickCount % 2 == 0; // 用你想要的检查替换此处 } - // Utility method that is called when the effect is first added to the entity. + // 当效果首次添加到实体时调用的实用方法。 @Override public void onEffectStarted(LivingEntity entity, int amplifier) { } } ``` -Like all registry objects, `MobEffect`s must be registered, like so: +像所有注册对象一样,`MobEffect` 必须像下面这样注册: ```java -//MOB_EFFECTS is a DeferredRegister +// MOB_EFFECTS 是一个 DeferredRegister public static final Supplier MY_MOB_EFFECT = MOB_EFFECTS.register("my_mob_effect", () -> new MyMobEffect( - //Can be either BENEFICIAL, NEUTRAL or HARMFUL. Used to determine the potion tooltip color of this effect. + // 可以是 BENEFICIAL、NEUTRAL 或 HARMFUL。用于确定此效果的药水工具提示颜色。 MobEffectCategory.BENEFICIAL, - //The color of the effect particles. + // 效果粒子的颜色。 0xffffff )); ``` -If you want your effect to act solely as a marker, you can also directly use the `MobEffect` class, like you can with the `Block` or `Item` classes. +如果你的效果仅用作标记,你也可以直接使用 `MobEffect` 类,就像你可以使用 `Block` 或 `Item` 类一样。 -The `MobEffect` class also provides default functionality for adding attribute modifiers to affected entities. For example, the speed effect adds an attribute modifier for movement speed. Effect attribute modifiers are added like so: +`MobEffect` 类还为受影响实体添加属性修改器提供了默认功能。例如,速度效果会为移动速度添加属性修改器。效果属性修改器添加如下: ```java public static final String MY_MOB_EFFECT_UUID = "01234567-89ab-cdef-0123-456789abcdef"; @@ -63,12 +62,14 @@ public static final Supplier MY_MOB_EFFECT = MOB_EFFECTS.register(" ``` :::note -The UUID used must be a valid and unique UUIDv4, as for some reason, Mojang decided to use UUIDs here instead of some text-based identifier. A UUID is best obtained through an online generator, for example [uuidgenerator.net][uuidgen]. +使用的 UUID 必须是有效且唯一的 UUIDv4,因为出于某种原因,Mojang 决定在此处使用 UUID 而不是一些基于文本的标识符。最好 + +通过在线生成器获得 UUID,例如 [uuidgenerator.net][uuidgen]。 ::: ### `InstantenousMobEffect` -If you want to create an instant effect, you can use the helper class `InstantenousMobEffect` instead of the regular `MobEffect` class, like so: +如果要创建即时效果,可以使用助手类 `InstantenousMobEffect` 而不是常规的 `MobEffect` 类,如下所示: ```java public class MyMobEffect extends InstantenousMobEffect { @@ -78,105 +79,103 @@ public class MyMobEffect extends InstantenousMobEffect { @Override public void applyEffectTick(LivingEntity entity, int amplifier) { - // Apply your effect logic here. + // 在这里应用你的效果逻辑。 } } ``` -Then, register your effect like normal. +然后,像平常一样注册你的效果。 -### Events +### 事件 -Many effects have their logic applied in other places. For example, the levitation effect is applied in the living entity movement handler. For modded `MobEffect`s, it often makes sense to apply them in an [event handler][events]. NeoForge also provides a few events related to effects: +许多效果在其他地方应用它们的逻辑。例如,飘浮效果在生物移动处理程序中应用。对于模组 `MobEffect`,通常最好在[事件处理程序][events]中应用它们。NeoForge 还提供了一些与效果相关的事件: -- `MobEffectEvent.Applicable` is fired when the game checks whether a `MobEffectInstance` can be applied to an entity. This event can be used to deny or force adding the effect instance to the target. -- `MobEffectEvent.Added` is fired when the `MobEffectInstance` is added to the target. This event contains information about a previous `MobEffectInstance` that may have been present on the target. -- `MobEffectEvent.Expired` is fired when the `MobEffectInstance` expires, i.e. the timer goes to zero. -- `MobEffectEvent.Remove` is fired when the effect is removed from the entity through means other than expiring, e.g. through drinking milk or via commands. +- `MobEffectEvent.Applicable` 在游戏检查是否可以将 `MobEffectInstance` 应用于实体时触发。此事件可用于拒绝或强制向目标添加效果实例。 +- `MobEffectEvent.Added` 当 `MobEffectInstance` 添加到目标时触发。此事件包含有关可能存在于目标上的先前 `MobEffectInstance` 的信息。 +- `MobEffectEvent.Expired` 当 `MobEffectInstance` 到期时触发,即计时器归零时。 +- `MobEffectEvent.Remove` 当通过除到期之外的方式从实体中移除效果时触发,例如通过喝牛奶或通过命令。 ## `MobEffectInstance`s -A `MobEffectInstance` is, simply put, an effect applied to an entity. Creating a `MobEffectInstance` is done by calling the constructor: +简单来说,`MobEffectInstance` 是应用于实体的效果。通过调用构造函数创建 `MobEffectInstance`: ```java MobEffectInstance instance = new MobEffectInstance( - // The mob effect to use. + // 要使用的 mob 效果。 MobEffects.REGENERATION, - // The duration to use, in ticks. Defaults to 0 if not specified. + // 使用的持续时间,以游戏刻为单位。如果未指定,默认为 0。 500, - // The amplifier to use. This is the "strength" of the effect, i.e. Strength I, Strength II, etc; - // starting at 0. Defaults to 0 if not specified. + // 要使用的增幅。这是效果的 “强度”,例如,Strength I、Strength II 等;从 0 开始。如果未指定,默认为 0。 0, - // Whether the effect is an "ambient" effect, meaning it is being applied by an ambient source, - // of which Minecraft currently has the beacon and the conduit. Defaults to false if not specified. + // 是否为 “环境” 效果,表示它由环境源应用,Minecraft 目前有信标和导管。如果未指定,默认为 false。 false, - // Whether the effect is visible in the inventory. Defaults to true if not specified. + // 效果是否在库存中可见。如果未指定,默认为 true。 true, - // Whether an effect icon is visible in the top right corner. Defaults to true if not specified. + // 是否在右上角可见效果图标。如果未指定,默认为 true。 true ); ``` -Several constructor overloads are available, omitting the last 1-5 parameters, respectively. +有几种构造函数重载,分别省略最后 1-5 个参数。 :::info -`MobEffectInstance`s are mutable. If you need a copy, call `new MobEffectInstance(oldInstance)`. +`MobEffectInstance` 是可变的。如果需要副本,请调用 `new MobEffectInstance(oldInstance)`。 ::: -### Using `MobEffectInstance`s +### 使用 `MobEffectInstance` -A `MobEffectInstance` can be added to an entity like so: +可以将 `MobEffectInstance` 添加到实体,如下所示: ```java MobEffectInstance instance = new MobEffectInstance(...); entity.addEffect(instance); ``` -Similarly, `MobEffectInstance` can also be removed from an entity. Since a `MobEffectInstance` overwrites pre-existing `MobEffectInstance`s of the same `MobEffect` on the entity, there can only ever be one `MobEffectInstance` per `MobEffect` and entity. As such, specifying the `MobEffect` suffices when removing: +类似地,也可以从实体中移除 `MobEffectInstance`。由于 `MobEffectInstance` 覆盖了实体上的相同 `MobEffect` 的预先存在的 `MobEffectInstance`,因此每个 `MobEffect` 和实体只能有一个 `MobEffectInstance`。因此,在移除时只需指定 `MobEffect` 即可: ```java entity.removeEffect(MobEffects.REGENERATION); ``` :::info -`MobEffect`s can only be applied to `LivingEntity` or its subclasses, i.e. players and mobs. Things like items or thrown snowballs cannot be affected by `MobEffect`s. +`MobEffect` 只能应用于 `LivingEntity` 或其子类,例如玩家和生物。例如物品或投掷雪球无法受到 `MobEffect` 的影响。 ::: ## `Potion`s -`Potion`s are created by calling the constructor of `Potion` with the `MobEffectInstance`s you want the potion to have. For example: +`Potion`s 是通过调用 `Potion` 的构造函数并传递你想要的 `MobEffectInstance`s 来创建的。例如: ```java -//POTIONS is a DeferredRegister +// POTIONS 是一个 DeferredRegister public static final Supplier MY_POTION = POTIONS.register("my_potion", () -> new Potion(new MobEffectInstance(MY_MOB_EFFECT.get(), 3600))); ``` -Note that the parameter of `new Potion` is a vararg. This means that you can add as many effects as you want to the potion. This also means that it is possible to create empty potions, i.e. potions that don't have any effects. Simply call `new Potion()` and you're done! (This is how vanilla adds the `awkward` potion, by the way.) +请注意,`new Potion` 的参数是可变参数。这意味着你可以向药水添加任意数量的效果。这也意味着可以创建空药水,即不含任何效果的药水。只需调用 `new Potion()` 即可!(顺便说一句,这就是原版如何添加 `awkward` 药水的方式。) -The name of the potion can be passed as the first constructor argument. It is used for translating; for example, the long and strong potion variants in vanilla use this to have the same names as their base variant. The name is not required; if it is omitted, the name will be queried from the registry. +药水的名称可以作为第一个构造函数参数传递。它用于翻译;例如,原版中的长效和强效药水变种使用此参数,使其与基本变种具有相同的名称。名称不是必需的;如果省略了名称,将从注册表中查询名称。 -The `PotionUtils` class offers various helper methods related to potions, such as `getPotion` and `setPotion` for item stacks (this can be any kind of item and is not limited to potion items), or `getColor` for getting a potion's display color. +`PotionUtils` 类提供了与药水相关的各种辅助方法,例如 `getPotion` 和 `setPotion` 用于物品堆栈(这可以是任何类型的物品,不仅限于药水物品),或者 `getColor` 用于获取药水的显示颜色。 -### Brewing +### 酿造 -Now that your potion is added, potion items are available for your potion. However, there is no way to obtain your potion in survival, so let's change that! +现在,你的药水已经添加,药水物品可以使用你的药水了。但是,在生存模式下没有办法获得你的药水,所以让我们改变一下! -Potions are traditionally made in the Brewing Stand. Unfortunately, Mojang does not provide [datapack][datapack] support for brewing recipes, so we have to be a little old-fashioned and add our recipes through code. This is done like so: +传统上,药水是在酿造台上制作的。不幸的是,Mojang 没有为酿造配方提供 [数据包][datapack] 支持,因此我们必须有点老派,通过代码添加我们的配方。操作如下: ```java -// The brewing ingredient. This is the item at the top of the brewing stand. +// 酿造成分。这是在酿造台顶部的物品。 Ingredient brewingIngredient = Ingredient.of(Items.FEATHER); BrewingRecipeRegistry.addRecipe( - // The input potion ingredient, often an awkward potion. This is the item at the bottom of the brewing stand. - // Does not need to be a potion, but typically is. + // 输入药水成分,通常是一种混合药水。这是在酿造台底部的物品。 + // 不一定要是药水,但通常是。 PotionUtils.setPotion(new ItemStack(Items.POTION), Potions.AWKWARD), - // Our brewing ingredient. + // 我们的酿造成分。 brewingIngredient, - // The resulting item stack. Does not need to be a potion, but typically is. + // 结果物品堆栈。不一定要是药水,但通常是。 PotionUtils.setPotion(new ItemStack(Items.POTION), MY_POTION) ); -// We also need to do this separately for splash potions and lingering potions. -// The tipped arrow recipe is taken care of by Minecraft's tipped arrow special crafting handler. +// 对于溅射药水和挥发药水,我们还需要单独处理。 +// 原版的毒箭配方由 Minecraft 的毒箭特殊配方处理。 BrewingRecipeRegistry.addRecipe( PotionUtils.setPotion(new ItemStack(Items.SPLASH_POTION), Potions.AWKWARD), brewingIngredient, @@ -189,7 +188,7 @@ BrewingRecipeRegistry.addRecipe( ); ``` -This should be called some time during setup, for example during [`FMLCommonSetupEvent`][commonsetup]. Make sure to wrap this into an `event.enqueueWork()` call, as the brewing recipe registry is not thread-safe. +这应该在设置期间的某个时间调用,例如在 [`FMLCommonSetupEvent`][commonsetup] 中。确保将此代码包装到 `event.enqueueWork()` 调用中,因为酿造配方注册表不是线程安全的。 [block]: ../blocks/index.md [commonsetup]: ../concepts/events.md#event-buses From 6900322ebd3a240ba3b2411356e27a895b058757 Mon Sep 17 00:00:00 2001 From: DaZiDian <113085496+DaZiDian@users.noreply.github.com> Date: Thu, 25 Apr 2024 20:53:25 +0800 Subject: [PATCH 56/87] Update tools.md --- docs/items/tools.md | 237 ++++++++++++++++++++++++++------------------ 1 file changed, 143 insertions(+), 94 deletions(-) diff --git a/docs/items/tools.md b/docs/items/tools.md index d37fcfb70..dc840ab85 100644 --- a/docs/items/tools.md +++ b/docs/items/tools.md @@ -1,10 +1,10 @@ -# Tools & Armor +## 工具与护甲 -Tools are [items][item] whose primary use is to break [blocks][block]. Many mods add new tool sets (for example copper tools) or new tool types (for example hammers). +工具是其主要用途是破坏[方块][block]的[物品][item]。许多模组添加了新的工具套装(例如铜工具)或新的工具类型(例如锤子)。 -## Custom Tool Sets +## 自定义工具套装 -A tool set typically consists of five items: a pickaxe, an axe, a shovel, a hoe and a sword. (Swords aren't tools in the classical sense, but are included here for consistency as well.) All of those items have their corresponding class: `PickaxeItem`, `AxeItem`, `ShovelItem`, `HoeItem` and `SwordItem`, respectively. The class hierarchy of tools looks as follows: +工具套装通常包含五种物品:镐、斧、铲、锄和剑。(剑在传统意义上不是工具,但为了保持一致性也包括在内。)所有这些物品都有对应的类:`PickaxeItem`、`AxeItem`、`ShovelItem`、`HoeItem` 和 `SwordItem`。工具的类层次结构如下所示: ```text Item @@ -17,70 +17,71 @@ Item - SwordItem ``` -`TieredItem` is a class that contains helpers for items with a certain `Tier` (read on). `DiggerItem` contains helpers for items that are designed to break blocks. Note that other items usually considered tools, such as shears, are not included in this hierarchy. Instead, they directly extend `Item` and hold the breaking logic themselves. +`TieredItem` 是一个包含了特定 `Tier`(详见下文)的辅助类。`DiggerItem` 包含了设计用于破坏方块的物品的辅助功能。请注意,其他通常被认为是工具的物品,例如剪刀,不包含在此层次结构中。它们直接扩展了 `Item`,并自行处理破坏逻辑。 -To create a standard set of tools, you must first define a `Tier`. For reference values, see Minecraft's `Tiers` enum. This example uses copper tools, you can use your own material here and adjust the values as needed. +要创建标准工具套装,首先必须定义一个 `Tier`。有关参考值,请参阅 Minecraft 的 `Tiers` 枚举。以下示例使用铜工具,你可以在此处使用你自己的材料并根据需要调整值。 ```java -// We place copper somewhere between stone and iron. +// 我们将铜放在石头和铁之间。 public static final Tier COPPER_TIER = new SimpleTier( - // Determines the level of this tool. Since this is an int, there is no good way to place our tool between stone and iron. - // NeoForge introduces the TierSortingRegistry to solve this problem, see below for more information. Use a best-effort approximation here. - // Stone is 1, iron is 2. + // 确定此工具的等级。由于这是一个整数,没有很好的方法将我们的工具放在石头和铁之间。 + // 石头是 1,铁是 2。 1, - // Determines the durability of the tier. - // Stone is 131, iron is 250. + // 确定等级的耐久性。 + // 石头是 131,铁是 250。 200, - // Determines the mining speed of the tier. Unused by swords. - // Stone uses 4, iron uses 6. + // 确定等级的挖掘速度。剑不使用此值。 + // 石头使用 4,铁使用 6。 5f, - // Determines the attack damage bonus. Different tools use this differently. For example, swords do (getAttackDamageBonus() + 4) damage. - // Stone uses 1, iron uses 2, corresponding to 5 and 6 attack damage for swords, respectively; our sword does 5.5 damage now. + // 确定攻击伤害加成。不同的工具使用不同的方式。例如,剑会造成 (getAttackDamageBonus() + 4) 的伤害。 + // 石头使用 1,铁使用 2,对应于剑的 5 和 6 攻击伤害;现在我们的剑造成 5.5 伤害。 1.5f, - // Determines the enchantability of the tier. This represents how good the enchantments on this tool will be. - // Gold uses 22, we put copper slightly below that. + // 确定等级的附魔能力。这代表了此工具上附魔的好坏程度。 + // 金使用 22,我们将铜稍微低于这个值。 20, - // The tag that determines what blocks this tool can break. See below for more information. + // 确定此工具可以破坏的方块的标签。详见下文。 MyBlockTags.NEEDS_COPPER_TOOL, - // Determines the repair ingredient of the tier. Use a supplier for lazy initializing. + // 确定等级的修复原料。使用 Supplier 进行延迟初始化。 () -> Ingredient.of(Tags.Items.INGOTS_COPPER) ); ``` -Now that we have our `Tier`, we can use it for registering tools. All tool constructors have the same four parameters. +现在我们有了我们的 `Tier`,我们可以用它来注册工具。所有工具的构造函数都有相同的四个参数。 ```java -//ITEMS is a DeferredRegister +// ITEMS 是一个 DeferredRegister public static final Supplier COPPER_SWORD = ITEMS.register("copper_sword", () -> new SwordItem( - // The tier to use. + // 要使用的等级。 COPPER_TIER, - // The type-specific attack damage bonus. 3 for swords, 1.5 for shovels, 1 for pickaxes, varying for axes and hoes. + // 类型特定的攻击伤害加成。剑为 3,铲为 1.5,镐为 1,斧和锄有所不同。 3, - // The type-specific attack speed modifier. The player has a default attack speed of 4, so to get to the desired - // value of 1.6f, we use -2.4f. -2.4f for swords, -3f for shovels, -2.8f for pickaxes, varying for axes and hoes. + // 类型特定的攻击速度修正。玩家的默认攻击速度为 4,所以我们使用 -2.4f 来达到期望的值 1.6f。 + // 剑为 -2.4f,铲为 -3f,镐为 -2.8f,斧和锄有所不同。 -2.4f, - // The item properties. We don't need to set the durability here because TieredItem handles that for us. + // 物品属性。我们不需要在这里设置耐久性,因为 TieredItem 会为我们处理。 new Item.Properties() )); public static final Supplier COPPER_AXE = ITEMS.register("copper_axe", () -> new AxeItem(...)); public static final Supplier COPPER_PICKAXE = ITEMS.register("copper_pickaxe", () -> new PickaxeItem(...)); -public static final Supplier COPPER_SHOVEL = ITEMS.register("copper_shovel", () -> new ShovelItem(...)); +public static final + + Supplier COPPER_SHOVEL = ITEMS.register("copper_shovel", () -> new ShovelItem(...)); public static final Supplier COPPER_HOE = ITEMS.register("copper_hoe", () -> new HoeItem(...)); ``` -### Tags +### 标签 -When creating a `Tier`, it is assigned a block [tag][tags] containing blocks that need this tool (or a better one) to be broken. For example, the `minecraft:needs_iron_tool` tag contains Diamond Ores (among others), and the `minecraft:needs_diamond_tool` tag contains blocks like Obsidian and Ancient Debris. +创建 `Tier` 时,它被分配了一个包含需要此工具(或更好的工具)才能破坏的方块[标签][tags]。例如,`minecraft:needs_iron_tool` 标签包含了钻石矿石(以及其他方块),而 `minecraft:needs_diamond_tool` 标签包含了方块如黑曜石和远古残骸。 -You can reuse one of these tags for your tool if you're fine with that. For example, if we wanted our copper tools to just be more durable stone tools, we'd pass in `BlockTags.NEEDS_STONE_TOOL`. +如果你满意的话,你可以重用这些标签中的一个来制作你的工具。例如,如果我们想要我们的铜工具只是更耐用的石头工具,我们可以传入 `BlockTags.NEEDS_STONE_TOOL`。 -Alternatively, we can create our own tag, like so: +或者,我们可以创建自己的标签,操作如下: ```java public static final TagKey NEEDS_COPPER_TOOL = TagKey.create(BuiltInRegistries.BLOCK.key(), new ResourceLocation(MOD_ID, "needs_copper_tool")); ``` -And then, we populate our tag. For example, let's make copper able to mine gold ores, gold blocks and redstone ore, but not diamonds or emeralds. (Redstone blocks are already mineable by stone tools.) The tag file is located at `src/main/resources/data/mod_id/tags/blocks/needs_copper_tool.json` (where `mod_id` is your mod id): +然后,我们填充我们的标签。例如,让铜能够开采金矿石、金块和红石矿石,但不能开采钻石或绿宝石。 (红石块已经可以被石头工具开采了。)标签文件位于 `src/main/resources/data/mod_id/tags/blocks/needs_copper_tool.json`(其中 `mod_id` 是你的模组 ID): ```json { @@ -95,11 +96,11 @@ And then, we populate our tag. For example, let's make copper able to mine gold } ``` -Finally, we can pass our tag into our tier creation, as seen above. +最后,我们可以像上面看到的那样将我们的标签传递给我们的等级创建。 ### `TierSortingRegistry` -In order to make the game actually pick up your tier as between two others, you must register it to the `TierSortingRegistry`. This must happen before item registration, a `static` initializer in the same class as your tier definition is a good spot for that. If you do not add your tier to the registry, it will fall back to what vanilla would do. +为了使游戏真正选择你的等级位于另外两个等级之间,你必须将其注册到 `TierSortingRegistry`。这必须在物品注册之前发生,将 `static` 初始化器放在与你的等级定义相同的类中是一个不错的选择。如果你不将你的等级添加到注册表中,它将退回到原版所做的操作。 ```java public static final Tier COPPER_TIER = new SimpleTier(...); @@ -107,19 +108,19 @@ public static final Tier COPPER_TIER = new SimpleTier(...); static { TierSortingRegistry.registerTier( COPPER_TIER, - //The name to use for internal resolution. May use the Minecraft namespace if appropriate. + // 用于内部解析的名称。如果适用,可以使用 Minecraft 命名空间。 new ResourceLocation("minecraft", "copper"), - //A list of tiers that are considered lower than the type being added. For example, stone is lower than copper. - //We don't need to add wood and gold here because those are already lower than stone. + // 被认为低于正在添加的类型的一系列等级。例如,石头低于铜。 + // 我们不需要在这里添加木头和金,因为这些已经低于石头了。 List.of(Tiers.STONE), - //A list of tiers that are considered higher than the type being added. For example, iron is higher than copper. - //We don't need to add diamond and netherite here because those are already higher than iron. + // 被认为高于正在添加的类型的一系列等级。例如,铁高于铜。 + // 我们不需要在这里添加钻石和下界合金,因为这些已经高于铁了。 List.of(Tiers.IRON) ); } ``` -Instead of or in addition to a `Tier`, you can also pass in other tiers' ids into these lists. For example, say we want to make our material be considered weaker than both iron and the Osmium tools from [Mekanism Tools][mektools], we could do that like so: +可以将其他等级的 ID 传递到这些列表中,作为 `Tier` 的替代或补充。例如,假设我们想要使我们的材料被认为比铁和 [Mekanism 工具][mektools] 的钨更弱,我们可以这样做: ```java public static final Tier COPPER_TIER = new SimpleTier(...); @@ -129,67 +130,119 @@ static { COPPER_TIER, new ResourceLocation("minecraft", "copper"), List.of(Tiers.STONE), - //We can mix and match Tiers and ResourceLocations here. + // 我们可以在这里混合和匹配 Tiers 和 ResourceLocations。 List.of(Tiers.IRON, new ResourceLocation("mekanism", "osmium")) ); } ``` -This works for both the lower and higher tiers. If multiple options are available, the system will choose the strictest bounds available. +## 工具与护甲 -:::caution -Be aware that circular dependencies may occur if this is set up incorrectly, so make sure that your bounds actually make sense and don't all cross-reference one another. -::: +工具是主要用于破坏[方块][block]的[物品][item]。许多模组添加了新的工具套装(例如铜工具)或新的工具类型(例如锤子)。 -If you want to check if a tier is applicable for a block state, call `TierSortingRegistry#isCorrectTierForDrops`. +### 自定义工具套装 -## Custom Tool Types +工具套装通常由五种物品组成:镐、斧、铲、锄和剑。(剑在传统意义上不是工具,但为了保持一致性,也包括在内。)所有这些物品都有各自对应的类:`PickaxeItem`、`AxeItem`、`ShovelItem`、`HoeItem` 和 `SwordItem`。工具的类层次结构如下所示: -Custom tool types can be created by extending `DiggerItem` (or `TieredItem` if you are making custom weapon types). They don't need too big of a setup, it is an item class like any other, with all implications that has. +``` +Item +- TieredItem + - DiggerItem + - AxeItem + - HoeItem + - PickaxeItem + - ShovelItem + - SwordItem +``` -One thing worth noting is the parameters of `DiggerItem`. The first four parameters are the same as for its subclasses (see the explanation for `SwordItem` above), while the fifth parameter is the `mineable` tag for the tool type. Generally, the format here is `:mineable/`, though `forge` can be used as the namespace too if you expect other mods to add similar tools. For example, [Farmer's Delight][farmersdelight] uses a `forge:mineable/knives` tag. +`TieredItem` 是一个包含某个 `Tier` 的辅助类(详见下文)。`DiggerItem` 包含了用于破坏方块的物品的辅助方法。请注意,其他通常被视为工具的物品(如剪刀)不包括在这个层次结构中。相反,它们直接扩展 `Item` 并自行处理破坏逻辑。 -If you plan on making a multitool-like item (i.e. an item that combines two or more tools into one, e.g. an axe and a pickaxe as one item), it is best to extend `AxeItem` if applicable. This is because enchantment checks for things like Sharpness or Knockback are hardcoded to `instanceof AxeItem`. +要创建标准的工具套装,首先必须定义一个 `Tier`。参考 Minecraft 的 `Tiers` 枚举获取参考值。以下示例使用铜工具,你可以在此处使用你自己的材料并根据需要调整值。 -## `ToolAction`s +```java +// 我们将铜放在石头和铁之间。 +public static final Tier COPPER_TIER = new SimpleTier( + // 确定此工具的等级。由于这是一个整数,因此没有好的方法将我们的工具放置在石头和铁之间。 + // NeoForge 引入了 TierSortingRegistry 来解决这个问题,有关更多信息,请参见下文。在此处尽力估计。 + // 石头为 1,铁为 2。 + 1, + // 确定等级的耐久度。 + // 石头为 131,铁为 250。 + 200, + // 确定等级的挖掘速度。斧头不使用此项。 + // 石头使用 4,铁使用 6。 + 5f, + // 确定攻击伤害奖励。不同的工具使用方式不同。例如,剑会造成 (getAttackDamageBonus() + 4) 的伤害。 + // 石头使用 1,铁使用 2,对应于剑的伤害分别为 5 和 6;我们的剑现在造成 5.5 的伤害。 + 1.5f, + // 确定等级的附魔性。这代表了这个工具上附魔的好坏程度。 + // 金使用 22,我们稍微低于这个值。 + 20, + // 决定这个工具可以破坏哪些方块的标签。更多信息请参见下文。 + MyBlockTags.NEEDS_COPPER_TOOL, + // 确定等级的修复材料。使用供应商进行延迟初始化。 + () -> Ingredient.of(Tags.Items.INGOTS_COPPER) +); +``` + +现在我们有了我们的 `Tier`,我们可以在注册工具时使用它。所有工具构造函数都具有相同的四个参数。 + +```java +// ITEMS 是一个 DeferredRegister +public static final Supplier COPPER_SWORD = ITEMS.register("copper_sword", () -> new SwordItem( + // 要使用的等级。 + COPPER_TIER, + // 类型特定的攻击伤害奖励。剑为 3,铲子为 1.5,镐子为 1,斧头和锄头的值各不相同。 + 3, + // 类型特定的攻击速度修饰符。玩家的默认攻击速度为 4,所以要达到期望的值 1.6f,我们使用 -2.4f。对于剑,值为 -2.4f,铲子为 -3f,镐子为 -2.8f,斧头和锄头的值各不相同。 + -2.4f, + // 物品属性。我们不需要在此设置耐久度,因为 TieredItem 会为我们处理。 + new Item.Properties() +)); +public static final Supplier COPPER_AXE = ITEMS.register("copper_axe", () -> new AxeItem(...)); +public static final Supplier COPPER_PICKAXE = ITEMS.register("copper_pickaxe", () -> new PickaxeItem(...)); +public static final Supplier COPPER -`ToolAction`s are an abstraction over what a tool can and cannot do. This includes both left-click and right-click behavior. NeoForge provides default `ToolAction`s in the `ToolActions` class: +_SHOVEL = ITEMS.register("copper_shovel", () -> new ShovelItem(...)); +``` + +### 工具动作 -- Digging actions. These exist for all four `DiggerItem` types as mentioned above, as well as sword and shears digging. -- Axe right-click actions for stripping (logs), scraping (oxidized copper) and unwaxing (waxed copper). -- Shear actions for harvesting (honeycombs), carving (pumpkins) and disarming (tripwires). -- Actions for shovel flattening (dirt paths), sword sweeping, hoe tilling, shield blocking, and fishing rod casting. +工具动作是工具能够执行和不能执行的操作的抽象。这包括左键和右键行为。NeoForge 在 `ToolActions` 类中提供了默认的 `ToolAction`: -To create your own `ToolAction`s, use `ToolAction#get` - it will create a new `ToolAction` if needed. Then, in a custom tool type, override `IItemExtension#canPerformAction` as needed. +- 挖掘动作。这些适用于上文提到的所有四种 `DiggerItem` 类型,以及剑和剪刀挖掘。 +- 斧头右键动作用于去皮(原木)、刮(氧化铜)和去蜡(蜡质铜)。 +- 剪刀动作用于收获(蜜蜂巢)、雕刻(南瓜)和解除武装(绊线)。 +- 铲子平整(土径)、剑扫射、锄头耕作、盾牌阻挡和钓鱼竿抛出的动作。 -To query if an `ItemStack` can perform a certain `ToolAction`, call `IItemStackExtension#canPerformAction`. Note that this works on any `Item`, not just tools. +要创建自己的 `ToolAction`,请使用 `ToolAction#get` - 它会在需要时创建一个新的 `ToolAction`。然后,在自定义工具类型中根据需要覆盖 `IItemExtension#canPerformAction`。 -## Armor +要查询一个 `ItemStack` 是否可以执行某个 `ToolAction`,请调用 `IItemStackExtension#canPerformAction`。请注意,这适用于任何 `Item`,而不仅仅是工具。 -Similar to tools, armor uses a tier system (although a different one). What is called `Tier` for tools is called `ArmorMaterial` for armors. Like above, this example shows how to add copper armor; this can be adapted as needed. For the vanilla values, see the `ArmorMaterials` enum. +### 护甲 + +与工具类似,护甲也使用一个等级系统(尽管不同)。工具中称为 `Tier` 的东西在护甲中称为 `ArmorMaterial`。就像上面一样,这个例子展示了如何添加铜护甲;这可以根据需要进行调整。有关原始数值,请参见 `ArmorMaterials` 枚举。 ```java -// We place copper somewhere between chainmail and iron. +// 我们将铜放在锁链甲和铁之间。 public static final ArmorMaterial COPPER_ARMOR_MATERIAL = new ArmorMaterial() { - // The name of the armor material. Mainly determines where the armor texture is. Should contain - // a leading mod id to guarantee uniqueness, otherwise there may be issues when two mods - // try to add the same armor material. (If the mod id is omitted, the "minecraft" namespace will be used.) + // 护甲材料的名称。主要用于确定护甲纹理的位置。应包含一个前导的模组标识符以确保唯一性,否则当两个模组尝试添加相同的护甲材料时可能会出现问题。(如果省略模组标识符,则将使用 "minecraft" 命名空间。) @Override public String getName() { return "modid:copper"; } - // Override for StringRepresentable. Should generally return the same as getName(). + // StringRepresentable 的重写。通常应与 getName() 返回相同的值。 @Override public String getSerializedName() { return getName(); } - // Determines the durability of this armor material, depending on what armor piece it is. - // ArmorItem.Type is an enum of four values: HELMET, CHESTPLATE, LEGGINGS and BOOTS. - // Vanilla armor materials determine this by using a base value and multiplying it with a type-specific constant. - // The constants are 13 for BOOTS, 15 for LEGGINGS, 16 for CHESTPLATE and 11 for HELMET. - // Both chainmail and iron use 15 as the base value, so we'll use it as well. + // 确定此护甲材料的耐久度,具体取决于护甲部件是什么。 + // ArmorItem.Type 是四个值的枚举:HELMET、CHESTPLATE、LEGGINGS 和 BOOTS。 + // Vanilla 护甲材料通过使用一个基础值并将其与类型特定的常量相乘来确定这一点。 + // 这些常量是 13(BOOTS)、15(LEGGINGS)、16(CHESTPLATE)和 11(HELMET)。 + // 锁链甲和铁都使用 15 作为基础值,所以我们也使用它。 @Override public int getDurabilityForType(ArmorItem.Type type) { return switch (type) { @@ -200,7 +253,7 @@ public static final ArmorMaterial COPPER_ARMOR_MATERIAL = new ArmorMaterial() { }; } - // Determines the defense value of this armor material, depending on what armor piece it is. + // 确定此护甲材料的防御值,具体取决于护甲部件是什么。 @Override public int getDurabilityForType(ArmorItem.Type type) { return switch (type) { @@ -211,59 +264,55 @@ public static final ArmorMaterial COPPER_ARMOR_MATERIAL = new ArmorMaterial() { }; } - // Returns the toughness value of the armor. The toughness value is an additional value included in - // damage calculation, for more information, refer to the Minecraft Wiki's article on armor mechanics: - // https://minecraft.wiki/w/Armor#Armor_toughness - // Only diamond and netherite have values greater than 0 here, so we just return 0. + // 返回护甲的坚韧度值。坚韧度值是包含在伤害计算中的额外值,有关更多信息,请参见 Minecraft Wiki 上的护甲机制文章:https://minecraft.wiki/w/Armor#Armor_toughness + // 只有钻石和下界合金在这里的值大于 0,所以我们只返回 0。 @Override public float getToughness() { return 0; } - // Returns the knockback resistance value of the armor. While wearing this armor, the player is - // immune to knockback to some degree. If the player has a total knockback resistance value of 1 or greater - // from all armor pieces combined, they will not take any knockback at all. - // Only netherite has values greater than 0 here, so we just return 0. + // 返回护甲的抗击退值。穿戴这种护甲时,玩家对击退具有一定程度的免疫。如果玩家从所有护甲部件中获得的总击退抗性值大于或等于 1,则它们将根本不受到任何击退。 + // 只有下界合金在这里的值大于 0,所以我们只返回 0。 @Override public float getKnockbackResistance() { return 0; } - // Determines the enchantability of the tier. This represents how good the enchantments on this armor will be. - // Gold uses 25, we put copper slightly below that. + // 确定等级的附魔性。这代表了这个护甲上的附魔有多好。 + // 金使用 25,我们将铜放在稍低的位置。 @Override public int getEnchantmentValue(ArmorItem.Type type) { return 20; } - // Determines the sound played when equipping this armor. + // 确定装备这件护甲时播放的声音。 @Override public SoundEvent getEquipSound() { return SoundEvents.ARMOR_EQUIP_GENERIC; } - // Determines the repair item for this armor. + // 确定这件护甲的修复物品。 @Override public Ingredient getRepairIngredient() { return Ingredient.of(Tags.Items.INGOTS_COPPER); } - // Optionally, you can also override #getArmorTexture here. This method returns a ResourceLocation - // that determines where the armor location is stored, in case you want to store it in a non-default location. - // See the default implementation in Tier for an example. + // 可选地,您还可以在这里重写 #getArmorTexture。此方法返回一个 ResourceLocation,用于确定存储护甲位置的位置,以防您希望将其存储在非默认位置。 + +有关示例,请参见 Tier 中的默认实现。 } ``` -And then, we use that armor material in item registration. +然后,在物品注册中使用该护甲材料。 ```java -//ITEMS is a DeferredRegister +// ITEMS 是一个 DeferredRegister public static final Supplier COPPER_HELMET = ITEMS.register("copper_helmet", () -> new ArmorItem( - // The armor material to use. + // 要使用的护甲材料。 COPPER_ARMOR_MATERIAL, - // The armor type to use. + // 要使用的护甲类型。 ArmorItem.Type.HELMET, - // The item properties. We don't need to set the durability here because ArmorItem handles that for us. + // 物品属性。我们不需要在此设置耐久度,因为 ArmorItem 会为我们处理。 new Item.Properties() )); public static final Supplier COPPER_CHESTPLATE = ITEMS.register("copper_chestplate", () -> new ArmorItem(...)); @@ -271,9 +320,9 @@ public static final Supplier COPPER_LEGGINGS = ITEMS.register("copper public static final Supplier COPPER_BOOTS = ITEMS.register("copper_boots", () -> new ArmorItem(...)); ``` -Besides the usual resources, armors also need a worn armor texture that will be rendered over the player model when the armor is equipped. This texture must be located at `src/main/resources/assets//textures/models/armor/_layer_1.png` for the helmet, chestplate and boots textures, and in the same directory at `_layer_2.png` for the leggings. +除了通常的资源外,护甲还需要一个穿戴时的护甲纹理,它将在装备护甲时渲染在玩家模型上。该纹理必须位于 `src/main/resources/assets//textures/models/armor/_layer_1.png`(头盔、胸甲和靴子的纹理),以及相同目录中的 `_layer_2.png`(护腿的纹理)。 -When creating your armor texture, it is a good idea to work on top of the vanilla armor texture to see which part goes where. +创建护甲纹理时,最好在基于标准护甲纹理的基础上进行工作,以确定每个部分的位置。 [block]: ../blocks/index.md [farmersdelight]: https://www.curseforge.com/minecraft/mc-mods/farmers-delight From ab83b1595b2a7a7bf31b2f71927ce7765dcffcc1 Mon Sep 17 00:00:00 2001 From: DaZiDian <113085496+DaZiDian@users.noreply.github.com> Date: Thu, 25 Apr 2024 20:54:27 +0800 Subject: [PATCH 57/87] Update porting.md --- docs/legacy/porting.md | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/docs/legacy/porting.md b/docs/legacy/porting.md index cc7c0480b..9e50a0119 100644 --- a/docs/legacy/porting.md +++ b/docs/legacy/porting.md @@ -1,17 +1,17 @@ -Porting to Minecraft 1.20 +移植到 Minecraft 1.20 ========================= -Here you can find a list of primers on how to port from old versions to the current version. Some versions are lumped together since that particular version never saw much usage. +在这里,您可以找到有关如何从旧版本移植到当前版本的入门指南。某些版本被合并在一起,因为该特定版本从未被广泛使用。 -| From -> To | Primer | +| 从 -> 到 | 入门指南 | |:-----------------:|:----------------------------------------| -| 1.12 -> 1.13/1.14 | [Primer by williewillus][112to114] | -| 1.14 -> 1.15 | [Primer by williewillus][114to115] | -| 1.15 -> 1.16 | [Primer by 50ap5ud5][115to116] | -| 1.16 -> 1.17 | [Primer by 50ap5ud5][116to117] | -| 1.19.2 -> 1.19.3 | [Primer by ChampionAsh5357][1192to1193] | -| 1.19.3 -> 1.19.4 | [Primer by ChampionAsh5357][1193to1194] | -| 1.19.4 -> 1.20.0 | [Primer by ChampionAsh5357][1194to120] | +| 1.12 -> 1.13/1.14 | [williewillus 的入门指南][112to114] | +| 1.14 -> 1.15 | [williewillus 的入门指南][114to115] | +| 1.15 -> 1.16 | [50ap5ud5 的入门指南][115to116] | +| 1.16 -> 1.17 | [50ap5ud5 的入门指南][116to117] | +| 1.19.2 -> 1.19.3 | [ChampionAsh5357 的入门指南][1192to1193] | +| 1.19.3 -> 1.19.4 | [ChampionAsh5357 的入门指南][1193to1194] | +| 1.19.4 -> 1.20.0 | [ChampionAsh5357 的入门指南][1194to120] | [112to114]: https://gist.github.com/williewillus/353c872bcf1a6ace9921189f6100d09a [114to115]: https://gist.github.com/williewillus/30d7e3f775fe93c503bddf054ef3f93e From eb6d05b9d2ff100fdb151b5bc043f50f1631c439 Mon Sep 17 00:00:00 2001 From: DaZiDian <113085496+DaZiDian@users.noreply.github.com> Date: Thu, 25 Apr 2024 20:55:49 +0800 Subject: [PATCH 58/87] Update config.md --- docs/misc/config.md | 148 ++++++++++++++++++++++---------------------- 1 file changed, 74 insertions(+), 74 deletions(-) diff --git a/docs/misc/config.md b/docs/misc/config.md index 6ba9abdc6..e6f94f3eb 100644 --- a/docs/misc/config.md +++ b/docs/misc/config.md @@ -1,136 +1,136 @@ -Configuration +配置 ============= -Configurations define settings and consumer preferences that can be applied to a mod instance. NeoForge uses a configuration system using [TOML][toml] files and read with [NightConfig][nightconfig]. +配置定义了可应用于模组实例的设置和用户偏好。NeoForge 使用 [TOML][toml] 文件并使用 [NightConfig][nightconfig] 进行读取的配置系统。 -Creating a Configuration +创建配置 ------------------------ -A configuration can be created using a subtype of `IConfigSpec`. NeoForge implements the type via `ModConfigSpec` and enables its construction through `ModConfigSpec.Builder`. The builder can separate the config values into sections via `Builder#push` to create a section and `Builder#pop` to leave a section. Afterwards, the configuration can be built using one of two methods: +可以使用 `IConfigSpec` 的子类型来创建配置。NeoForge 通过 `ModConfigSpec` 实现了该类型,并通过 `ModConfigSpec.Builder` 启用其构建。该构建器可以通过 `Builder#push` 将配置值分成部分以创建一个部分,通过 `Builder#pop` 离开一个部分。然后,可以使用以下两种方法之一构建配置: - Method | Description + 方法 | 描述 :--- | :--- -`build` | Creates the `ModConfigSpec`. -`configure` | Creates a pair of the class holding the config values and the `ModConfigSpec`. +`build` | 创建 `ModConfigSpec`。 +`configure` | 创建持有配置值的类和 `ModConfigSpec` 的一对。 :::note -`ModConfigSpec.Builder#configure` is typically used with a `static` block and a class that takes in `ModConfigSpec.Builder` as part of its constructor to attach and hold the values: +`ModConfigSpec.Builder#configure` 通常与 `static` 块和一个类一起使用,该类作为其构造函数的一部分接受 `ModConfigSpec.Builder` 来附加和保存值: ```java -// In some config class +// 在某个配置类中 ExampleConfig(ModConfigSpec.Builder builder) { - // Define values here in final fields + // 在此定义值的最终字段 } -// Somewhere the constructor is accessible +// 某处可以访问构造函数 static { Pair pair = new ModConfigSpec.Builder() .configure(ExampleConfig::new); - // Store pair values in some constant field + // 将配对值存储在某个常量字段中 } ``` ::: -Each config value can be supplied with additional context to provide additional behavior. Contexts must be defined before the config value is fully built: +每个配置值可以提供额外的上下文以提供附加行为。必须在完全构建配置值之前定义上下文: -| Method | Description | +| 方法 | 描述 | |:---------------|:------------------------------------------------------------------------------------------------------------| -| `comment` | Provides a description of what the config value does. Can provide multiple strings for a multiline comment. | -| `translation` | Provides a translation key for the name of the config value. | -| `worldRestart` | The world must be restarted before the config value can be changed. | +| `comment` | 提供配置值功能的描述。可以为多行注释提供多个字符串。 | +| `translation` | 为配置值的名称提供翻译键。 | +| `worldRestart` | 必须在更改配置值之前重新启动世界。 | ### ConfigValue -Config values can be built with the provided contexts (if defined) using any of the `#define` methods. +可以使用提供的上下文(如果已定义)使用任何 `#define` 方法构建配置值。 -All config value methods take in at least two components: +所有配置值方法至少接受两个组件: -* A path representing the name of the variable: a `.` separated string representing the sections the config value is in -* The default value when no valid configuration is present +* 表示变量名称的路径:一个 `.` 分隔的字符串,表示配置值所在的部分 +* 当没有有效配置时的默认值 -The `ConfigValue` specific methods take in two additional components: +`ConfigValue` 特定的方法接受两个额外的组件: -* A validator to make sure the deserialized object is valid -* A class representing the data type of the config value +* 验证器,以确保反序列化的对象有效 +* 表示配置值的数据类型的类 ```java -// For some ModConfigSpec.Builder builder +// 对于某个 ModConfigSpec.Builder builder ConfigValue value = builder.comment("Comment") .define("config_value_name", defaultValue); ``` -The values themselves can be obtained using `ConfigValue#get`. The values are additionally cached to prevent multiple readings from files. +还可以使用 `ConfigValue#get` 获取值。值还被缓存以防止从文件中进行多次读取。 -#### Additional Config Value Types +#### 额外的配置值类型 -* **Range Values** - * Description: Value must be between the defined bounds - * Class Type: `Comparable` - * Method Name: `#defineInRange` - * Additional Components: - * The minimum and maximum the config value may be - * A class representing the data type of the config value +* **范围值** + * 描述:值必须在定义的边界之间 + * 类型:`Comparable` + * 方法名:`#defineInRange` + * 额外组件: + * 配置值可能的最小值和最大值 + * 表示配置值的数据类型的类 :::note -`DoubleValue`s, `IntValue`s, and `LongValue`s are range values which specify the class as `Double`, `Integer`, and `Long` respectively. +`DoubleValue`、`IntValue` 和 `LongValue` 是范围值,它们将类指定为 `Double`、`Integer` 和 `Long`,分别。 ::: -* **Whitelisted Values** - * Description: Value must be in supplied collection - * Class Type: `T` - * Method Name: `#defineInList` - * Additional Components: - * A collection of the allowed values the configuration can be - -* **List Values** - * Description: Value is a list of entries - * Class Type: `List` - * Method Name: `#defineList`, `#defineListAllowEmpty` if list can be empty - * Additional Components: - * A validator to make sure a deserialized element from the list is valid - -* **Enum Values** - * Description: An enum value in the supplied collection - * Class Type: `Enum` - * Method Name: `#defineEnum` - * Additional Components: - * A getter to convert a string or integer into an enum - * A collection of the allowed values the configuration can be - -* **Boolean Values** - * Description: A `boolean` value - * Class Type: `Boolean` - * Method Name: `#define` - -Registering a Configuration +* **白名单值** + * 描述:值必须在提供的集合中 + * 类型:`T` + * 方法名:`#defineInList` + * 额外组件: + * 配置可以是哪些值的集合 + +* **列表值** + * 描述:值是一系列条目 + * 类型:`List` + * 方法名:`#defineList`,如果列表可以为空,则为 `#defineListAllowEmpty` + * 额外组件: + * 验证器,以确保从列表中反序列化的元素有效 + +* **枚举值** + * 描述:在提供的集合中的枚举值 + * 类型:`Enum` + * 方法名:`#defineEnum` + * 额外组件: + * 一个 getter,将字符串或整数转换为枚举 + * 配置可以是哪些值的集合 + +* **布尔值** + * 描述:一个 `boolean` 值 + * 类型:`Boolean` + * 方法名:`#define` + +注册配置 --------------------------- -Once a `ModConfigSpec` has been built, it must be registered to allow NeoForge to load, track, and sync the configuration settings as required. Configurations should be registered in the mod constructor via `ModLoadingContext#registerConfig`. A configuration can be registered with a given type representing the side the config belongs to, the `ModConfigSpec`, and optionally a specific file name for the configuration. +一旦构建了 `ModConfigSpec`,就必须注册它以允许 NeoForge 加载、跟踪和根据需要同步配置设置。配置应该在模组构造函数中通过 `ModLoadingContext#registerConfig` 注册。可以使用给定的类型(表示配置所属的一侧)、`ModConfigSpec` 和可选的特定文件名为配置注册。 ```java -// In the mod constructor with a ModConfigSpec CONFIG +// 在具有 ModConfigSpec CONFIG 的模组构造函数中 ModLoadingContext.get().registerConfig(Type.COMMON, CONFIG); ``` -Here is a list of the available configuration types: +以下是可用的配置类型列表: -| Type | Loaded | Synced to Client | Client Location | Server Location | Default File Suffix | +| 类型 | 加载 | 同步到客户端 | 客户端位置 | 服务器位置 | 默认文件后缀 | |:------:|:----------------:|:----------------:|:--------------------------------------------:|:------------------------------------:|:--------------------| -| CLIENT | Client Side Only | No | `.minecraft/config` | N/A | `-client` | -| COMMON | On Both Sides | No | `.minecraft/config` | `/config` | `-common` | -| SERVER | Server Side Only | Yes | `.minecraft/saves//serverconfig` | `/world/serverconfig` | `-server` | +| CLIENT | 仅客户端 | 否 | `.minecraft/config` | N/A | `-client` | +| COMMON | 两边都有 | 否 | `.minecraft/config` | `/config` | `-common` | +| SERVER | 仅服务器 | 是 | `.minecraft/saves//serverconfig` | `/world/serverconfig` | `-server` | :::tip -NeoForge documents the [config types][type] within their codebase. +NeoForge 在其代码库中记录了[配置类型][type]。 ::: -Configuration Events +配置事件 -------------------- -Operations that occur whenever a config is loaded or reloaded can be done using the `ModConfigEvent$Loading` and `ModConfigEvent$Reloading` events. The events must be [registered][events] to the mod event bus. +可以使用 `ModConfigEvent$Loading` 和 `ModConfigEvent$Reloading` 事件在加载或重新加载配置时执行的操作。必须将这些事件[注册][events]到模组事件总线上。 :::caution -These events are called for all configurations for the mod; the `ModConfig` object provided should be used to denote which configuration is being loaded or reloaded. +这些事件适用于模组的所有配置;应使用提供的 `ModConfig` 对象来指示正在加载或重新加载的配置。 ::: [toml]: https://toml.io/ From 88c53693057047091fb1b7762e650a759227932c Mon Sep 17 00:00:00 2001 From: DaZiDian <113085496+DaZiDian@users.noreply.github.com> Date: Thu, 25 Apr 2024 20:56:38 +0800 Subject: [PATCH 59/87] Update debugprofiler.md --- docs/misc/debugprofiler.md | 32 +++++++++++++++----------------- 1 file changed, 15 insertions(+), 17 deletions(-) diff --git a/docs/misc/debugprofiler.md b/docs/misc/debugprofiler.md index f59e33b56..15e821aed 100644 --- a/docs/misc/debugprofiler.md +++ b/docs/misc/debugprofiler.md @@ -1,23 +1,22 @@ -# Debug Profiler +# 调试性能分析器 -Minecraft provides a Debug Profiler that provides system data, current game settings, JVM data, level data, and sided tick information to find time consuming code. Considering things like `TickEvent`s and ticking `BlockEntities`, this can be very useful for modders and server owners that want to find a lag source. +Minecraft 提供了一个调试性能分析器,它提供系统数据、当前游戏设置、JVM 数据、级别数据和边界刻信息,以找到耗时的代码。考虑到诸如 `TickEvent` 和刻动 `BlockEntities` 等因素,这对于想要找到卡顿来源的模组开发者和服务器所有者非常有用。 -## Using the Debug Profiler +## 使用调试性能分析器 -The Debug Profiler is very simple to use. It requires the debug keybind `F3 + L` to start the profiler. After 10 seconds, it will automatically stop; however, it can be stopped earlier by pressing the keybind again. +调试性能分析器非常简单易用。它需要使用调试按键组合 `F3 + L` 来启动分析器。10 秒后,它将自动停止;但是,您也可以通过再次按下该组合键来提前停止。 :::note -Naturally, you can only profile code paths that are actually being reached. `Entities` and `BlockEntities` that you want to profile must exist in the level to show up in the results. +自然而然,您只能分析实际被执行的代码路径。您想要分析的 `实体` 和 `BlockEntities` 必须存在于级别中才会出现在结果中。 ::: -After you have stopped the debugger, it will create a new zip within the `debug/profiling` subdirectory in your run directory. -The file name will be formatted with the date and time as `yyyy-mm-dd_hh_mi_ss-WorldName-VersionNumber.zip` +在停止调试器后,它将在运行目录的 `debug/profiling` 子目录中创建一个新的 zip 文件。文件名将以日期和时间格式化为 `yyyy-mm-dd_hh_mi_ss-WorldName-VersionNumber.zip` -## Reading a Profiling result +## 阅读性能分析结果 -Within each sided folder (`client` and `server`), you will find a `profiling.txt` file containing the result data. At the top, it first tells you how long in milliseconds it was running and how many ticks ran in that time. +在每个边界文件夹 (`client` 和 `server`) 中,您会找到一个包含结果数据的 `profiling.txt` 文件。在顶部,它首先告诉您在运行的毫秒数以及在此期间运行了多少个刻。 -Below that, you will find information similar to the snippet below: +在此之下,您会发现类似于以下片段的信息: ``` [00] levels - 96.70%/96.70% [01] | Level Name - 99.76%/96.47% @@ -29,19 +28,18 @@ Below that, you will find information similar to the snippet below: [05] | | | | | minecraft:furnace - 33.35%/0.14% [05] | | | | | minecraft:chest - 2.39%/0.01% ``` -Here is a small explanation of what each part means +这里是每个部分的简要解释 | [02] | tick | 99.31% | 95.81% | | :----------------------- | :---------------------- | :----------- | :----------- | -| The Depth of the section | The Name of the Section | The percentage of time it took in relation to it's parent. For Layer 0, it is the percentage of the time a tick takes. For Layer 1, it is the percentage of the time its parent takes. | The second percentage tells you how much time it took from the entire tick. +| 该部分的深度 | 该部分的名称 | 它花费的时间与其父部分的百分比。对于层级 0,它是一次刻所花费时间的百分比。对于层级 1,它是其父部分所花费时间的百分比。 | 第二个百分比告诉您它从整个刻中花费了多少时间。 -## Profiling your own code +## 对自己的代码进行性能分析 -The Debug Profiler has basic support for `Entity` and `BlockEntity`. If you would like to profile something else, you may need to manually create your sections like so: +调试性能分析器对 `Entity` 和 `BlockEntity` 有基本支持。如果您想分析其他内容,您可能需要手动创建您的部分,如下所示: ```java ProfilerFiller#push(yourSectionName : String); -//The code you want to profile +//您想要分析的代码 ProfilerFiller#pop(); ``` -You can obtain the `ProfilerFiller` instance from a `Level`, `MinecraftServer`, or `Minecraft` instance. -Now you just need to search the results file for your section name. +您可以从 `Level`、`MinecraftServer` 或 `Minecraft` 实例获取 `ProfilerFiller` 实例。现在,您只需要在结果文件中搜索您的部分名称即可。 From fd9931a31c8c4cae7756a5991fecc5ba627d3f45 Mon Sep 17 00:00:00 2001 From: DaZiDian <113085496+DaZiDian@users.noreply.github.com> Date: Thu, 25 Apr 2024 20:59:33 +0800 Subject: [PATCH 60/87] Update gametest.mdx --- docs/misc/gametest.mdx | 219 +++++++++++++++++++++-------------------- 1 file changed, 113 insertions(+), 106 deletions(-) diff --git a/docs/misc/gametest.mdx b/docs/misc/gametest.mdx index 7562122c9..15b44f8e6 100644 --- a/docs/misc/gametest.mdx +++ b/docs/misc/gametest.mdx @@ -1,22 +1,21 @@ -Game Tests -========== +# 游戏测试 -Game Tests are a way to run in-game unit tests. The system was designed to be scalable and in parallel to run large numbers of different tests efficiently. Testing object interactions and behaviors are simply a few of the many applications of this framework. +游戏测试是一种运行游戏内单元测试的方法。该系统被设计为可扩展并且并行运行,以有效地运行大量不同的测试。测试对象的交互和行为只是该框架的众多应用之一。 -Creating a Game Test --------------------- +创建游戏测试 +------------ -A standard Game Test follows three basic steps: +标准的游戏测试遵循三个基本步骤: -1. A structure, or template, is loaded holding the scene on which the interaction or behavior is tested. -1. A method conducts the logic to perform on the scene. -1. The method logic executes. If a successful state is reached, then the test succeeds. Otherwise, the test fails and the result is stored within a lectern adjacent to the scene. +1. 加载一个结构或模板,其中包含要测试的交互或行为所在的场景。 +2. 一个方法执行逻辑来执行场景上的操作。 +3. 方法逻辑执行。如果达到了成功的状态,则测试成功。否则,测试失败,并将结果存储在场景旁边的讲台上。 -As such, to create a Game Test, there must be an existing template holding the initial start state of the scene and a method which provides the logic of execution. +因此,要创建一个游戏测试,必须有一个存在的模板,其中包含场景的初始起始状态,以及一个提供执行逻辑的方法。 -### The Test Method +### 测试方法 -A Game Test method is a `Consumer` reference, meaning it takes in a `GameTestHelper` and returns nothing. For a Game Test method to be recognized, it must have a `@GameTest` annotation: +游戏测试方法是一个 `Consumer` 引用,意味着它接受一个 `GameTestHelper` 并且不返回任何内容。为了使游戏测试方法被识别,它必须有一个 `@GameTest` 注解: ```java public class ExampleGameTests { @@ -27,98 +26,100 @@ public class ExampleGameTests { } ``` -The `@GameTest` annotation also contains members which configure how the game test should run. +`@GameTest` 注解还包含配置游戏测试运行方式的成员。 ```java -// In some class +// 在某个类中 @GameTest( - setupTicks = 20L, // The test spends 20 ticks to set up for execution - required = false // The failure is logged but does not affect the execution of the batch + setupTicks = 20L, // 测试花费 20 刻来设置执行环境 + required = false // 失败会被记录,但不会影响批次的执行 ) public static void exampleConfiguredTest(GameTestHelper helper) { // Do stuff } ``` -#### Relative Positioning +#### 相对定位 -All `GameTestHelper` methods translate relative coordinates within the structure template scene to its absolute coordinates using the structure block's current location. To allow for easy conversion between relative and absolute positioning, `GameTestHelper#absolutePos` and `GameTestHelper#relativePos` can be used respectively. +所有 `GameTestHelper` 方法都将结构模板场景中的相对坐标转换为其绝对坐标,使用结构方块的当前位置。为了方便相对和绝对定位之间的转换,可以分别使用 `GameTestHelper#absolutePos` 和 `GameTestHelper#relativePos`。 -The relative position of a structure template can be obtained in-game by loading the structure via the [test command][test], placing the player at the wanted location, and finally running the `/test pos` command. This will grab the coordinates of the player relative to the closest structure within 200 blocks of the player. The command will export the relative position as a copyable text component in the chat to be used as a final local variable. +通过在游戏中加载结构并使用 [测试命令][test] 将玩家放置在所需位置,最后运行 `/test pos` 命令,可以获取结构模板的相对位置。该命令将以可复制的文本组件的形式在聊天中导出相对于玩家所在位置的最近结构的坐标。这个导出的文本可以作为最终的局部变量使用。 :::tip -The local variable generated by `/test pos` can specify its reference name by appending it to the end of the command: +`/test pos` 生成的本地变量可以通过将其附加到命令的末尾来指定其引用名称: ```bash -/test pos # Exports 'final BlockPos = new BlockPos(...);' +/test pos # 导出 'final BlockPos = new BlockPos(...);' ``` -:::tip +::: -#### Successful Completion +#### 成功完成 -A Game Test method is responsible for one thing: marking the test was successful on a valid completion. If no success state was achieved before the timeout is reached (as defined by `GameTest#timeoutTicks`), then the test automatically fails. +游戏测试方法负责一件事情:在有效完成时标记测试为成功。如果在达到超时之前(由 `GameTest#timeoutTicks` 定义)没有达到成功状态,则测试会自动失败。 -There are many abstracted methods within `GameTestHelper` which can be used to define a successful state; however, four are extremely important to be aware of. +在 `GameTestHelper` 中有许多抽象方法可以用来定义成功状态;然而,有四个是非常重要的需要注意的。 -Method | Description -:---: | :--- -`#succeed` | The test is marked as successful. -`#succeedIf` | The supplied `Runnable` is tested immediately and succeeds if no `GameTestAssertException` is thrown. If the test does not succeed on the immediate tick, then it is marked as a failure. -`#succeedWhen` | The supplied `Runnable` is tested every tick until timeout and succeeds if the check on one of the ticks does not throw a `GameTestAssertException`. -`#succeedOnTickWhen` | The supplied `Runnable` is tested on the specified tick and will succeed if no `GameTestAssertException` is thrown. If the `Runnable` succeeds on any other tick, then it is marked as a failure. +方法 | 描述 +:---: | :--- +`#succeed` | 将测试标记为成功。 +`#succeedIf` | 立即测试提供的 `Runnable`,如果没有抛出 `GameTestAssertException`,则成功。如果在当前刻没有成功,则标记为失败。 +`#succeedWhen` | 每刻测试提供的 `Runnable`,直到超时,如果在其中一刻检查不会抛出 `GameTestAssertException` 则成功。 +`#succeedOnTickWhen` | 在指定的刻上测试提供的 `Runnable`,如果没有抛出 `GameTestAssertException`,则成功。如果在任何其他刻上成功,则标记为失败。 :::caution -Game Tests are executed every tick until the test is marked as a success. As such, methods which schedule success on a given tick must be careful to always fail on any previous tick. +游戏测试会每刻执行一次,直到测试被标记为成功为止。因此,为了成功标记某一特定刻上的成功,必须在之前的任何刻上都失败。 ::: -#### Scheduling Actions +#### 安排操作 -Not all actions will occur when a test begins. Actions can be scheduled to occur at specific times or intervals: +并不是所有的操作在测试开始时都会发生。可以安排在特定时间或间隔发生的操作: -Method | Description +方法 | 描述 :---: | :--- -`#runAtTickTime` | The action is ran on the specified tick. -`#runAfterDelay` | The action is ran `x` ticks after the current tick. -`#onEachTick` | The action is ran every tick. +`#runAtTickTime` | 在指定的刻上运行操作。 +`#runAfterDelay` | 在当前刻之后的 `x` 刻运行操作。 +`#onEachTick` | 每一刻都运行操作。 -#### Assertions +#### 断言 -At any time during a Game Test, an assertion can be made to check if a given condition is true. There are numerous assertion methods within `GameTestHelper`; however, it simplifies to throwing a `GameTestAssertException` whenever the appropriate state is not met. +在游戏测试的任何时候,都可以进行断言来检查给定条件是否为真。在 `GameTestHelper` 中有许多断言方法;然而,简化为在适当状态未满足时抛出 `GameTestAssertException`。 -### Generated Test Methods +### 生成的测试方法 -If Game Test methods need to be generated dynamically, a test method generator can be created. These methods take in no parameters and return a collection of `TestFunction`s. For a test method generator to be recognized, it must have a `@GameTestGenerator` annotation: +如果游戏测试方法需要动态生成,可以创建一个测试方法生成器。这些方法不接受任何参数,并返回一个 `TestFunction` 集合。为了使测试方法生成器被识别,它必须有一个 `@GameTestGenerator` 注解: ```java public class ExampleGameTests { @GameTestGenerator public static Collection exampleTests() { - // Return a collection of TestFunctions + // 返回一个 TestFunction 集合 } } ``` #### TestFunction -A `TestFunction` is the boxed information held by the `@GameTest` annotation and the method running the test. +`TestFunction` 是由 `@GameTest` 注解和运行测试的方法所包装的信息。 :::tip -Any methods annotated using `@GameTest` are translated into a `TestFunction` using `GameTestRegistry#turnMethodIntoTestFunction`. That method can be used as a reference for creating `TestFunction`s without the use of the annotation. +使用 `@GameTest` 注解的任何方法都会使用 `GameTestRegistry#turnMethodIntoTestFunction` 将其转换为 `TestFunction`。该方法可用作创建 `TestFunction` 的参考,而不需要使用注解。 ::: -### Batching +### 批次处理 -Game Tests can be executed in batches instead of registration order. A test can be added to a batch by having the same supplied `GameTest#batch` string. +游戏测试可以以批次方式而不是按照注册顺序执行。可以通过具有相同的提供的 `GameTest#batch` 字符串将测试添加到 -On its own, batching does not provide anything useful. However, batching can be used to perform setup and teardown states on the current level the tests are running in. This is done by annotating a method with either `@BeforeBatch` for setup or `@AfterBatch` for takedown. The `#batch` methods must match the string supplied to the game test. +批次中。 -Batch methods are `Consumer` references, meaning they take in a `ServerLevel` and return nothing: +仅有批处理本身并不提供任何有用的功能。然而,批处理可以用于在测试运行的当前级别上执行设置和拆卸状态。通过将方法标记为 `@BeforeBatch` 进行设置,或标记为 `@AfterBatch` 进行拆卸。`#batch` 方法必须与游戏测试中提供的字符串匹配。 + +批处理方法是 `Consumer` 引用,意味着它们接受一个 `ServerLevel` 并且不返回任何内容: ```java public class ExampleGameTests { @BeforeBatch(batch = "firstBatch") public static void beforeTest(ServerLevel level) { - // Perform setup + // 执行设置 } @GameTest(batch = "firstBatch") @@ -128,14 +129,14 @@ public class ExampleGameTests { } ``` -Registering a Game Test ------------------------ +注册游戏测试 +---------- -A Game Test must be registered to be ran in-game. There are two methods of doing so: via the `@GameTestHolder` annotation or `RegisterGameTestsEvent`. Both registration methods still require the test methods to be annotated with either `@GameTest`, `@GameTestGenerator`, `@BeforeBatch`, or `@AfterBatch`. +游戏测试必须注册才能在游戏中运行。有两种方法可以实现:通过 `@GameTestHolder` 注解或 `RegisterGameTestsEvent`。这两种注册方法仍然要求测试方法被注解为 `@GameTest`、`@GameTestGenerator`、`@BeforeBatch` 或 `@AfterBatch` 中的一种。 ### GameTestHolder -The `@GameTestHolder` annotation registers any test methods within the type (class, interface, enum, or record). `@GameTestHolder` contains a single method which has multiple uses. In this instance, the supplied `#value` must be the mod id of the mod; otherwise, the test will not run under default configurations. +`@GameTestHolder` 注解注册类型(类、接口、枚举或记录)中的任何测试方法。`@GameTestHolder` 包含一个方法,具有多种用途。在此示例中,提供的 `#value` 必须是 mod 的 id;否则,测试将不会在默认配置下运行。 ```java @GameTestHolder(MODID) @@ -146,124 +147,124 @@ public class ExampleGameTests { ### RegisterGameTestsEvent -`RegisterGameTestsEvent` can also register either classes or methods using `#register`. The event listener must be [added][event] to the mod event bus. Test methods registered this way must supply their mod id to `GameTest#templateNamespace` on every method annotated with `@GameTest`. +`RegisterGameTestsEvent` 也可以通过 `#register` 注册类或方法。事件监听器必须 [添加][event] 到 mod 事件总线上。以这种方式注册的测试方法必须在每个标注了 `@GameTest` 的方法中提供它们的 mod id 给 `GameTest#templateNamespace`。 ```java -// In some class +// 在某个类中 public void registerTests(RegisterGameTestsEvent event) { event.register(ExampleGameTests.class); } -// In ExampleGameTests +// 在 ExampleGameTests 中 @GameTest(templateNamespace = MODID) public static void exampleTest3(GameTestHelper helper) { - // Perform setup + // 执行设置 } ``` :::note -The value supplied to `GameTestHolder#value` and `GameTest#templateNamespace` can be different from the current mod id. The configuration within the [buildscript][namespaces] would need to be changed. +提供给 `GameTestHolder#value` 和 `GameTest#templateNamespace` 的值可以与当前 mod id 不同。在 [buildscript][namespaces] 中的配置需要更改。 ::: -Structure Templates -------------------- +结构模板 +----------- -Game Tests are performed within scenes loaded by structures, or templates. All templates define the dimensions of the scene and the initial data (blocks and entities) that will be loaded. The template must be stored as an `.nbt` file within `data//structures`. +游戏测试在由结构或模板加载的场景中执行。所有模板都定义了场景的尺寸以及将要加载的初始数据(方块和实体)。模板必须以 `.nbt` 文件的形式存储在 `data//structures` 目录中。 :::tip -A structure template can be created and saved using a structure block. +可以使用结构方块创建并保存结构模板。 ::: -The location of the template is specified by a few factors: +模板的位置由以下几个因素确定: -* If the namespace of the template is specified. -* If the class should be prepended to the name of the template. -* If the name of the template is specified. +- 如果指定了模板的命名空间。 +- 如果类应该作为模板名称的前缀。 +- 如果指定了模板的名称。 -The namespace of the template is determined by `GameTest#templateNamespace`, then `GameTestHolder#value` if not specified, then `minecraft` if neither is specified. +模板的命名空间由 `GameTest#templateNamespace` 确定,如果未指定,则由 `GameTestHolder#value` 确定,如果两者都未指定,则由 `minecraft` 确定。 -The simple class name is not prepended to the name of the template if the `@PrefixGameTestTemplate` is applied to a class or method with the test annotations and set to `false`. Otherwise, the simple class name is made lowercase and prepended and followed by a dot before the template name. +如果将 `@PrefixGameTestTemplate` 应用于带有测试注解的类或方法,并且设置为 `false`,则不会将简单类名添加到模板名称的前面。否则,简单类名将被转换为小写并添加到模板名称的前后,中间用点分隔。 -The name of the template is determined by `GameTest#template`. If not specified, then the lowercase name of the method is used instead. +模板的名称由 `GameTest#template` 确定。如果未指定,则使用方法的小写名称。 ```java -// Modid for all structures will be MODID +// 所有结构的 modid 将为 MODID @GameTestHolder(MODID) public class ExampleGameTests { - // Class name is prepended, template name is not specified - // Template Location at 'modid:examplegametests.exampletest' + // 类名作为前缀,未指定模板名称 + // 模板位置为 'modid:examplegametests.exampletest' @GameTest public static void exampleTest(GameTestHelper helper) { /*...*/ } - // Class name is not prepended, template name is not specified - // Template Location at 'modid:exampletest2' + // 类名不作为前缀,未指定模板名称 + // 模板位置为 'modid:exampletest2' @PrefixGameTestTemplate(false) @GameTest public static void exampleTest2(GameTestHelper helper) { /*...*/ } - // Class name is prepended, template name is specified - // Template Location at 'modid:examplegametests.test_template' + // 类名作为前缀,指定了模板名称 + // 模板位置为 'modid:examplegametests.test_template' @GameTest(template = "test_template") public static void exampleTest3(GameTestHelper helper) { /*...*/ } - // Class name is not prepended, template name is specified - // Template Location at 'modid:test_template2' + // 类名不作为前缀,指定了模板名称 + // 模板位置为 'modid:test_template2' @PrefixGameTestTemplate(false) @GameTest(template = "test_template2") public static void exampleTest4(GameTestHelper helper) { /*...*/ } } ``` -Running Game Tests +运行游戏测试 ------------------ -Game Tests can be run using the `/test` command. The `test` command is highly configurable; however, only a few are of importance to running tests: +可以使用 `/test` 命令运行游戏测试。`test` 命令是高度可配置的;然而,只有几个对于运行测试至关重要: -Subcommand | Description +子命令 | 描述 :---: | :--- -`run` | Runs the specified test: `run `. -`runall` | Runs all available tests. -`runthis` | Runs the nearest test to the player within 15 blocks. -`runthese` | Runs tests within 200 blocks of the player. -`runfailed` | Runs all tests that failed in the previous run. +`run` | 运行指定的测试:`run `。 +`runall` | 运行所有可用的测试。 +`runthis` | 在玩家附近 15 格内运行最近的测试。 +`runthese` | 运行玩家 200 格内的测试。 +`runfailed` | 运行上次运行失败的所有测试。 :::note -Subcommands follow the test command: `/test `. +子命令遵循测试命令:`/test `。 ::: -Buildscript Configurations --------------------------- +构建脚本配置 +------------------ -Game Tests provide additional configuration settings within a buildscript (the `build.gradle` file) to run and integrate into different settings. +游戏测试提供了在构建脚本(`build.gradle` 文件)中运行和集成到不同设置中的额外配置设置。 -### Enabling Other Namespaces +### 启用其他命名空间 -If the buildscript was [setup as recommended][buildscript], then only Game Tests under the current mod id would be enabled. To enable other namespaces to load Game Tests from, a run configuration must set the property `forge.enabledGameTestNamespaces` to a string specifying each namespace separated by a comma. If the property is empty or not set, then all namespaces will be loaded. +如果构建脚本已按照[推荐方式设置][buildscript],那么只有当前 mod id 下的游戏测试将被启用。要启用其他命名空间加载游戏测试,运行配置必须将属性 `forge.enabledGameTestNamespaces` 设置为一个字符串,其中指定每个命名空间,用逗号分隔。如果属性为空或未设置,则将加载所有命名空间。 ```gradle -// Inside a run configuration +// 在运行配置中 property 'forge.enabledGameTestNamespaces', 'modid1,modid2,modid3' ``` :::caution -There must be no spaces in-between namespaces; otherwise, the namespace will not be loaded correctly. +命名空间之间不能有空格;否则,命名空间将无法正确加载。 ::: -### Game Test Server Run Configuration +### 游戏测试服务器运行配置 -The Game Test Server is a special configuration which runs a build server. The build server returns an exit code of the number of required, failed Game Tests. All failed tests, whether required or optional, are logged. This server can be run using `gradlew runGameTestServer`. +游戏测试服务器是一个特殊配置,运行一个构建服务器。构建服务器返回需要的失败游戏测试数的退出代码。所有失败的测试,无论是必需的还是可选的,都将被记录。可以使用 `gradlew runGameTestServer` 来运行此服务器。
- Important infromation on FG5 + FG5 的重要信息 :::caution -Due to a quirk in how Gradle works, by default, if a task forces a system exit, the Gradle daemon will be killed, causing the Gradle runner to report a build failure. ForgeGradle sets by default a force exit on run tasks such that any subprojects are not executed in sequence. However, as such, the Game Test Server will always fail. +由于 Gradle 的工作方式的一个怪异之处,如果一个任务强制系统退出,默认情况下 Gradle 守护进程将被终止,导致 Gradle 运行器报告构建失败。ForgeGradle 默认设置了对运行任务的强制退出,以便任何子项目都不会按顺序执行。然而,这样一来,游戏测试服务器将总是失败。 -This can be fixed by disabling the force exit on the run configuration using the `#setForceExit` method: +可以通过在运行配置中禁用强制退出来解决此问题,使用 `#setForceExit` 方法: ```gradle -// Game Test Server run configuration +// 游戏测试服务器运行配置 gameTestServer { // ... setForceExit false @@ -272,17 +273,23 @@ gameTestServer { :::
+### 在其他运行配置中启用游戏测试 -### Enabling Game Tests in Other Run Configurations - -By default, only the `client`, `server`, and `gameTestServer` run configurations have Game Tests enabled. If another run configuration should run Game Tests, then the `forge.enableGameTest` property must be set to `true`. +默认情况下,只有 `client`、`server` 和 `gameTestServer` 运行配置中启用了游戏测试。如果另一个运行配置应该运行游戏测试,那么必须将 `forge.enableGameTest` 属性设置为 `true`。 ```gradle -// Inside a run configuration -property 'forge.enableGameTest', 'true' +// 在运行配置中 +property + + 'forge.enableGameTest', 'true' ``` [test]: #running-game-tests [namespaces]: #enabling-other-namespaces [event]: ../concepts/events.md#registering-an-event-handler [buildscript]: ../gettingstarted/index.md#simple-buildgradle-customizations + +[test]: #running-game-tests +[namespaces]: #enabling-other-namespaces +[event]: ../concepts/events.md#registering-an-event-handler +[buildscript]: ../gettingstarted/index.md#simple-buildgradle-customizations From 00c99441de217da1fc4b0372c1704eda1064b344 Mon Sep 17 00:00:00 2001 From: DaZiDian <113085496+DaZiDian@users.noreply.github.com> Date: Thu, 25 Apr 2024 20:59:53 +0800 Subject: [PATCH 61/87] Update gametest.mdx --- docs/misc/gametest.mdx | 5 ----- 1 file changed, 5 deletions(-) diff --git a/docs/misc/gametest.mdx b/docs/misc/gametest.mdx index 15b44f8e6..981b1c40c 100644 --- a/docs/misc/gametest.mdx +++ b/docs/misc/gametest.mdx @@ -288,8 +288,3 @@ property [namespaces]: #enabling-other-namespaces [event]: ../concepts/events.md#registering-an-event-handler [buildscript]: ../gettingstarted/index.md#simple-buildgradle-customizations - -[test]: #running-game-tests -[namespaces]: #enabling-other-namespaces -[event]: ../concepts/events.md#registering-an-event-handler -[buildscript]: ../gettingstarted/index.md#simple-buildgradle-customizations From 32582b41f0108bcaae69069ecc7834df00798a6e Mon Sep 17 00:00:00 2001 From: DaZiDian <113085496+DaZiDian@users.noreply.github.com> Date: Thu, 25 Apr 2024 21:02:17 +0800 Subject: [PATCH 62/87] Update keymappings.md --- docs/misc/keymappings.md | 102 ++++++++++++++++++++------------------- 1 file changed, 52 insertions(+), 50 deletions(-) diff --git a/docs/misc/keymappings.md b/docs/misc/keymappings.md index ecbf21526..20d66d57f 100644 --- a/docs/misc/keymappings.md +++ b/docs/misc/keymappings.md @@ -1,127 +1,129 @@ # Key Mappings -A key mapping, or key binding, defines a particular action that should be tied to an input: mouse click, key press, etc. Each action defined by a key mapping can be checked whenever the client can take an input. Furthermore, each key mapping can be assigned to any input through the [Controls option menu][controls]. +一个按键映射或键绑定定义了应与输入相关联的特定操作:鼠标单击、按键等。每当客户端可以接受输入时,都可以检查键映射定义的每个操作。 此外,每个按键映射都可以通过[控制选项菜单][控制]分配给任何输入。 -## Registering a `KeyMapping` +## 注册一个`KeyMapping` -A `KeyMapping` can be registered by listening to the `RegisterKeyMappingsEvent` on the [**mod event bus**][modbus] only on the physical client and calling `#register`. +可以通过仅在物理客户端上监听 [**mod 事件总线**][modbus] 上的 `RegisterKeyMappingsEvent` 并调用 `#register` 来注册 `KeyMapping`。 ```java -// In some physical client only class +// 在某个仅限物理客户端的类中 -// Key mapping is lazily initialized so it doesn't exist until it is registered +// KeyMapping 是惰性初始化的,因此在注册之前它不存在 public static final Lazy EXAMPLE_MAPPING = Lazy.of(() -> /*...*/); -// Event is on the mod event bus only on the physical client +// 事件仅在物理客户端上的 mod 事件总线上 @SubscribeEvent public void registerBindings(RegisterKeyMappingsEvent event) { event.register(EXAMPLE_MAPPING.get()); } ``` -## Creating a `KeyMapping` +## 创建 `KeyMapping` -A `KeyMapping` can be created using it's constructor. The `KeyMapping` takes in a [translation key][tk] defining the name of the mapping, the default input of the mapping, and the [translation key][tk] defining the category the mapping will be put within in the [Controls option menu][controls]. +可以使用其构造函数创建 `KeyMapping`。`KeyMapping` 接受一个[翻译键][tk],用于定义映射的名称,映射的默认输入以及在[控制选项菜单][controls]中将映射放置在其中的类别的[翻译键][tk]。 :::tip -A `KeyMapping` can be added to a custom category by providing a category [translation key][tk] not provided by vanilla. Custom category translation keys should contain the mod id (e.g. `key.categories.examplemod.examplecategory`). +可以通过提供未由原版提供的自定义类别 [翻译键][tk] 来将 `KeyMapping` 添加到自定义类别中。自定义类别翻译键应包含 mod id(例如 `key.categories.examplemod.examplecategory`)。 ::: -### Default Inputs +### 默认输入 -Each key mapping has a default input associated with it. This is provided through `InputConstants$Key`. Each input consists of an `InputConstants$Type`, which defines what device is providing the input, and an integer, which defines the associated identifier of the input on the device. +每个键映射都与一个默认输入关联。这是通过 `InputConstants$Key` 提供的。每个输入由一个 `InputConstants$Type` 组成,用于定义提供输入的设备,以及一个整数,用于定义设备上关联的标识符。 -Vanilla provides three types of inputs: `KEYSYM`, which defines a keyboard through the provided `GLFW` key tokens, `SCANCODE`, which defines a keyboard through the platform-specific scancode, and `MOUSE`, which defines a mouse. +原版提供了三种输入类型:`KEYSYM`,它使用提供的 `GLFW` 键令定义键盘,`SCANCODE`,它使用平台特定的扫描码定义键盘,以及 `MOUSE`,它定义了鼠标。 :::note -It is highly recommended to use `KEYSYM` over `SCANCODE` for keyboards as `GLFW` key tokens are not tied to any particular system. You can read more on the [GLFW docs][keyinput]. +强烈建议使用 `KEYSYM` 而不是 `SCANCODE` 用于键盘,因为 `GLFW` 键令与任何特定的系统无关。您可以在 [GLFW 文档][keyinput] 上阅读更多信息。 ::: -The integer is dependent on the type provided. All input codes are defined in `GLFW`: `KEYSYM` tokens are prefixed with `GLFW_KEY_*` while `MOUSE` codes are prefixed with `GLFW_MOUSE_*`. +整数取决于所提供的类型。所有输入代码都在 `GLFW` 中定义:`KEYSYM` 令牌以 `GLFW_KEY_*` 为前缀,而 `MOUSE` 代码以 `GLFW_MOUSE_*` 为前缀。 ```java new KeyMapping( - "key.examplemod.example1", // Will be localized using this translation key - InputConstants.Type.KEYSYM, // Default mapping is on the keyboard - GLFW.GLFW_KEY_P, // Default key is P - "key.categories.misc" // Mapping will be in the misc category + "key.examplemod.example1", // 将使用此翻译键进行本地化 + InputConstants.Type.KEYSYM, // 默认映射在键盘上 + GLFW.GLFW_KEY_P, // 默认键为 P + "key.categories.misc" // 映射将位于杂项类别中 ) ``` :::note -If the key mapping should not be mapped to a default, the input should be set to `InputConstants#UNKNOWN`. The vanilla constructor will require you to extract the input code via `InputConstants$Key#getValue` while the Forge constructor can be supplied the raw input field. +如果键映射不应映射到默认键,则应将输入设置为 `InputConstants#UNKNOWN`。原版构造函数将要求您通过 `InputConstants$Key#getValue` 提取输入代码,而 Forge 构造函数可以提供原始输入字段。 ::: ### `IKeyConflictContext` -Not all mappings are used in every context. Some mappings are only used in a GUI, while others are only used purely in game. To avoid mappings of the same key used in different contexts conflicting with each other, an `IKeyConflictContext` can be assigned. +并非所有映射都在每个上下文中使用。某些映射仅在 GUI 中使用,而其他映射则仅在游戏中使用。为了避免在不同上下文中使用相同键的映射相互冲突,可以分配一个 `IKeyConflictContext`。 -Each conflict context contains two methods: `#isActive`, which defines if the mapping can be used in the current game state, and `#conflicts`, which defines whether the mapping conflicts with a key in the same or different conflict context. +每个冲突上下文都包含两种方法:`#isActive`,定义映射是否可以在当前游戏状态下使用,以及 `#conflicts`,定义映射是否与同一冲突上下文中的键或不同冲突上下文中的键冲突。 -Currently, Forge defines three basic contexts through `KeyConflictContext`: `UNIVERSAL`, which is the default meaning the key can be used in every context, `GUI`, which means the mapping can only be used when a `Screen` is open, and `IN_GAME`, which means the mapping can only be used if a `Screen` is not open. New conflict contexts can be created by implementing `IKeyConflictContext`. +目前,Forge 通过 `KeyConflictContext` 定义了三个基本上下文:`UNIVERSAL`,默认为意味着键可以在每个上下文中使用,`GUI`,意味着映射只能在打开 `Screen` 时使用,以及 `IN_GAME`,意味着映射只能在未打开 `Screen` 时使用。可以通过实现 `IKeyConflictContext` 来创建新的冲突上下文。 ```java new KeyMapping( "key.examplemod.example2", - KeyConflictContext.GUI, // Mapping can only be used when a screen is open - InputConstants.Type.MOUSE, // Default mapping is on the mouse - GLFW.GLFW_MOUSE_BUTTON_LEFT, // Default mouse input is the left mouse button - "key.categories.examplemod.examplecategory" // Mapping will be in the new example category + KeyConflictContext.GUI, // 只能在打开屏幕时使用映射 + InputConstants.Type.MOUSE, // 默认映射在鼠标上 + GLFW.GLFW_MOUSE_BUTTON_LEFT, // 默认鼠标输入为左键 + "key.categories.examplemod.examplecategory" // 映射将位于新示例类别中 ) ``` ### `KeyModifier` -Modders may not want mappings to have the same behavior if a modifier key is held at the same (e.g. `G` vs `CTRL + G`). To remedy this, Forge adds an additional parameter to the constructor to take in a `KeyModifier` which can apply control (`KeyModifier#CONTROL`), shift (`KeyModifier#SHIFT`), or alt (`KeyModifier#ALT`) to any input. `KeyModifier#NONE` is the default and will apply no modifier. +模组可能不希望映射在按下修饰键时具有相同的行为(例如 `G` vs `CTRL + G`)。为解决这个问题,Forge 在构造函数中添加了一个额外的参数,以接受一个 `KeyModifier`,该修饰符可以应用控制(`KeyModifier#CONTROL`)、shift(`KeyModifier#SHIFT`)或 alt(`KeyModifier#ALT`)到任何输入。`KeyModifier#NONE` 是默认值,不会应用任何修饰符。 -A modifier can be added in the [controls option menu][controls] by holding down the modifier key and the associated input. +可以通过按住修饰键和相关输入来将修饰符添加到[控制选项菜单][controls]中。 ```java new KeyMapping( "key.examplemod.example3", KeyConflictContext.UNIVERSAL, - KeyModifier.SHIFT, // Default mapping requires shift to be held down - InputConstants.Type.KEYSYM, // Default mapping is on the keyboard - GLFW.GLFW_KEY_G, // Default key is G + KeyModifier.SHIFT, // 默认映射需要按住 shift + InputConstants.Type.KEYSYM, // 默认映射在键盘上 + GLFW.GLFW_KEY_G, // 默认键为 G "key.categories.misc" ) ``` -## Checking a `KeyMapping` +## 检查 `KeyMapping` -A `KeyMapping` can be checked to see whether it has been clicked. Depending on when, the mapping can be used in a conditional to apply the associated logic. +可以检查 `KeyMapping` 来查看是否已单击它。根据何时,映射可以在条件中用于应用相关逻辑。 -### Within the Game +### 在游戏中 -Within the game, a mapping should be checked by listening to `ClientTickEvent` on the [**Forge event bus**][forgebus] and checking `KeyMapping#consumeClick` within a while loop. `#consumeClick` will return `true` only the number of times the input was performed and not already previously handled, so it won't infinitely stall the game. +在游戏中,应通过监听[**Forge 事件总线**][forgebus]上的 `ClientTickEvent` 并在 while 循环中检查 `KeyMapping#consumeClick` 来检查映射是否已被单击。`#consumeClick` 仅在执行输入的次数且尚未处理之前返回 `true`,因此不会 + +无限制地阻止游戏。 ```java -// Event is on the Forge event bus only on the physical client +// 事件仅在 Forge 事件总线上的物理客户端上 public void onClientTick(ClientTickEvent event) { - if (event.phase == TickEvent.Phase.END) { // Only call code once as the tick event is called twice every tick + if (event.phase == TickEvent.Phase.END) { // 由于每个刻度事件都调用两次,因此只调用一次代码 while (EXAMPLE_MAPPING.get().consumeClick()) { - // Execute logic to perform on click here + // 在此处执行单击时要执行的逻辑 } } } ``` :::caution -Do not use the `InputEvent`s as an alternative to `ClientTickEvent`. There are separate events for keyboard and mouse inputs only, so they wouldn't handle any additional inputs. +不要将 `InputEvent` 用作 `ClientTickEvent` 的替代方案。键盘和鼠标输入各有单独的事件,因此它们不会处理任何额外的输入。 ::: -### Inside a GUI +### 在 GUI 内 -Within a GUI, a mapping can be checked within one of the `GuiEventListener` methods using `IForgeKeyMapping#isActiveAndMatches`. The most common methods which can be checked are `#keyPressed` and `#mouseClicked`. +在 GUI 中,可以在 `GuiEventListener` 方法之一中使用 `IForgeKeyMapping#isActiveAndMatches` 检查映射。可以检查的最常见方法是 `#keyPressed` 和 `#mouseClicked`。 -`#keyPressed` takes in the `GLFW` key token, the platform-specific scan code, and a bitfield of the held down modifiers. A key can be checked against a mapping by creating the input using `InputConstants#getKey`. The modifiers are already checked within the mapping methods itself. +`#keyPressed` 接受 `GLFW` 键令、平台特定的扫描码以及按下的修饰键的位字段。可以通过使用 `InputConstants#getKey` 创建输入来将键与映射进行检查。修饰符已经在映射方法本身中进行了检查。 ```java -// In some Screen subclass +// 在某个 Screen 子类中 @Override public boolean keyPressed(int key, int scancode, int mods) { if (EXAMPLE_MAPPING.get().isActiveAndMatches(InputConstants.getKey(key, scancode))) { - // Execute logic to perform on key press here + // 在此处执行按键按下时要执行的逻辑 return true; } return super.keyPressed(x, y, button); @@ -129,17 +131,17 @@ public boolean keyPressed(int key, int scancode, int mods) { ``` :::note -If you do not own the screen which you are trying to check a **key** for, you can listen to the `Pre` or `Post` events of `ScreenEvent$KeyPressed` on the [**Forge event bus**][forgebus] instead. +如果您不拥有要检查 **键** 的屏幕,则可以监听[**Forge 事件总线**][forgebus]上的 `ScreenEvent$KeyPressed` 的 `Pre` 或 `Post` 事件。 ::: -`#mouseClicked` takes in the mouse's x position, y position, and the button clicked. A mouse button can be checked against a mapping by creating the input using `InputConstants$Type#getOrCreate` with the `MOUSE` input. +`#mouseClicked` 接受鼠标的 x 位置、y 位置和单击的按钮。鼠标按钮可以使用 `InputConstants$Type#getOrCreate` 与 `MOUSE` 输入创建输入进行与映射的检查。 ```java -// In some Screen subclass +// 在某个 Screen 子类中 @Override public boolean mouseClicked(double x, double y, int button) { if (EXAMPLE_MAPPING.get().isActiveAndMatches(InputConstants.TYPE.MOUSE.getOrCreate(button))) { - // Execute logic to perform on mouse click here + // 在此处执行鼠标单击时要执行的逻辑 return true; } return super.mouseClicked(x, y, button); @@ -147,11 +149,11 @@ public boolean mouseClicked(double x, double y, int button) { ``` :::note -If you do not own the screen which you are trying to check a **mouse** for, you can listen to the `Pre` or `Post` events of `ScreenEvent$MouseButtonPressed` on the [**Forge event bus**][forgebus] instead. +如果您不拥有要检查 **鼠标** 的屏幕,则可以监听[**Forge 事件总线**][forgebus]上的 `ScreenEvent$MouseButtonPressed` 的 `Pre` 或 `Post` 事件。 ::: [modbus]: ../concepts/events.md#event-buses -[controls]: https://minecraft.wiki/w/Options#Controls +[controls]: https://minecraft.fandom.com/wiki/Controls [tk]: ../resources/client/i18n.md#components [keyinput]: https://www.glfw.org/docs/3.3/input_guide.html#input_key [forgebus]: ../concepts/events.md#registering-an-event-handler From 5f2c65544ed00222dc37cec0147d38cf60c99df3 Mon Sep 17 00:00:00 2001 From: DaZiDian <113085496+DaZiDian@users.noreply.github.com> Date: Thu, 25 Apr 2024 21:04:51 +0800 Subject: [PATCH 63/87] Update resourcelocation.md --- docs/misc/resourcelocation.md | 46 +++++++++++++++++------------------ 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/docs/misc/resourcelocation.md b/docs/misc/resourcelocation.md index 89cc8e4fb..ee7682cd0 100644 --- a/docs/misc/resourcelocation.md +++ b/docs/misc/resourcelocation.md @@ -1,48 +1,48 @@ -# Resource Locations +# 资源位置 -`ResourceLocation`s are one of the most important things in Minecraft. They are used as keys in [registries][registries], as identifiers for data or resource files, as references to models in code, and in a lot of other places. A `ResourceLocation` consists of two parts: a namespace and a path, separated by a `:`. +`ResourceLocation` 是 Minecraft 中最重要的内容之一。它们用作[注册表][registries]中的键,作为数据或资源文件的标识符,作为代码中模型的引用,以及许多其他地方。`ResourceLocation` 由两部分组成:命名空间和路径,由 `:` 分隔。 -The namespace denotes what mod, resource pack or datapack the location refers to. For example, a mod with the mod id `examplemod` will use the `examplemod` namespace. Minecraft uses the `minecraft` namespace. Extra namespaces can be defined at will simply by creating a corresponding data folder, this is usually done by datapacks to keep their logic separate from the point where they integrate with vanilla. +命名空间表示资源位置所指的 mod、资源包或数据包。例如,具有模组 ID `examplemod` 的模组将使用 `examplemod` 命名空间。Minecraft 使用 `minecraft` 命名空间。可以根据需要定义额外的命名空间,只需创建相应的数据文件夹,这通常是由数据包执行的,以将其逻辑与 Vanilla 分开。 -The path is a reference to whatever object you want, inside your namespace. For example, `minecraft:cow` is a reference to something named `cow` in the `minecraft` namespace - usually this location would be used to get the cow entity from the entity registry. Another example would be `examplemod:example_item`, which would probably be used to get your mod's `example_item` from the item registry. +路径是指你的命名空间内的任何对象的引用。例如,`minecraft:cow` 是指 `minecraft` 命名空间中名为 `cow` 的对象 - 通常此位置将用于从实体注册表中获取 cow 实体。另一个示例是 `examplemod:example_item`,它可能用于从项注册表中获取模组的 `example_item`。 -`ResourceLocation`s may only contain lowercase letters, digits, underscores, dots and hyphens. Paths may additionally contain forward slashes. Note that due to Java module restrictions, mod ids may not contain hyphens, which by extension means that mod namespaces may not contain hyphens either (they are still permitted in paths). +`ResourceLocation` 只能包含小写字母、数字、下划线、点和连字符。路径可能还包含斜杠。请注意,由于 Java 模块的限制,模组 ID 不得包含连字符,这意味着模组命名空间也不得包含连字符(路径仍然允许包含)。 :::info -A `ResourceLocation` on its own says nothing about what kind of objects we are using it for. Objects named `minecraft:dirt` exist in multiple places, for example. It is up to whatever receives the `ResourceLocation` to associate an object with it. +`ResourceLocation` 本身并不表示我们要使用它的对象的类型。例如,名为 `minecraft:dirt` 的对象存在于多个位置。由接收 `ResourceLocation` 的对象决定将对象与其关联。 ::: -A new `ResourceLocation` can be created by calling `new ResourceLocation("examplemod", "example_item")` or `new ResourceLocation("examplemod:example_item")`. If the latter is used with a string that does not contain a `:`, the entire string will be used as the path, and `minecraft` will be used as the namespace. So for example, `new ResourceLocation("example_item")` will result in `minecraft:example_item`. +可以通过调用 `new ResourceLocation("examplemod", "example_item")` 或 `new ResourceLocation("examplemod:example_item")` 来创建新的 `ResourceLocation` 实例。如果使用后者,并且字符串不包含 `:`,则整个字符串将用作路径,而 `minecraft` 将用作命名空间。因此,例如 `new ResourceLocation("example_item")` 将导致 `minecraft:example_item`。 -The namespace and path of a `ResourceLocation` can be retrieved using `ResourceLocation#getNamespace()` and `#getPath()`, respectively, and the combined form can be retrieved through `ResourceLocation#toString`. +可以使用 `ResourceLocation#getNamespace()` 和 `#getPath()` 分别检索 `ResourceLocation` 的命名空间和路径,并通过 `ResourceLocation#toString()` 检索组合形式。 -`ResourceLocation`s are immutable. All utility methods on `ResourceLocation`, such as `withPrefix` or `withSuffix`, return a new `ResourceLocation`. +`ResourceLocation` 是不可变的。`ResourceLocation` 上的所有实用方法,例如 `withPrefix` 或 `withSuffix`,都返回一个新的 `ResourceLocation`。 -## Resolving `ResourceLocation`s +## 解析 `ResourceLocation` -Some places, for example registries, use `ResourceLocation`s directly. Some other places, however, will resolve the `ResourceLocation` as needed. For example: +某些位置,例如注册表,直接使用 `ResourceLocation`。然而,其他一些位置将根据需要解析 `ResourceLocation`。例如: -- `ResourceLocation`s are used as identifiers for GUI background. For example, the furnace GUI uses the resource location `minecraft:textures/gui/container/furnace.png`. This maps to the file `assets/minecraft/textures/gui/container/furnace.png` on disk. Note that the `.png` suffix is required in this resource location. -- `ResourceLocation`s are used as identifiers for block models. For example, the block model of dirt uses the resource location `minecraft:block/dirt`. This maps to the file `assets/minecraft/models/block/dirt.json` on disk. Note that the `.json` suffix is not required here. Note as well that this resource location automatically maps into the `models` subfolder. -- `ResourceLocation`s are used as identifiers for recipes. For example, the iron block crafting recipe uses the resource location `minecraft:iron_block`. This maps to the file `data/minecraft/recipes/iron_block.json` on disk. Note that the `.json` suffix is not required here. Note as well that this resource location automatically maps into the `recipes` subfolder. +- `ResourceLocation` 用作 GUI 背景的标识符。例如,熔炉 GUI 使用资源位置 `minecraft:textures/gui/container/furnace.png`。这映射到磁盘上的文件 `assets/minecraft/textures/gui/container/furnace.png`。请注意,在此资源位置中需要 `.png` 后缀。 +- `ResourceLocation` 用作方块模型的标识符。例如,泥土的方块模型使用资源位置 `minecraft:block/dirt`。这映射到磁盘上的文件 `assets/minecraft/models/block/dirt.json`。请注意,在此资源位置中不需要 `.json` 后缀。还请注意,此资源位置自动映射到 `models` 子文件夹。 +- `ResourceLocation` 用作配方的标识符。例如,铁块的合成配方使用资源位置 `minecraft:iron_block`。这映射到磁盘上的文件 `data/minecraft/recipes/iron_block.json`。请注意,在此资源位置中不需要 `.json` 后缀。还请注意,此资源位置自动映射到 `recipes` 子文件夹。 -Whether the `ResourceLocation` expects a file suffix, or what exactly the resource location resolves to, depends on the use case. +`ResourceLocation` 是否需要文件后缀,以及资源位置解析为什么内容,取决于使用情况。 -## `ModelResourceLocation`s +## `ModelResourceLocation` -`ModelResourceLocation`s are a special kind of resource location that includes a third part, called the variant. Minecraft uses these mainly to differentiate between different variants of models, where the different variants are used in different display contexts (for example with tridents, which have different models in first person, third person and inventories). The variant is always `inventory` for items, and the comma-delimited string of property-value pairs for blockstates (for example `facing=north,waterlogged=false`, empty for blocks with no blockstate properties). +`ModelResourceLocation` 是一种特殊类型的资源位置,包含第三部分,称为变体。Minecraft 主要用于区分模型的不同变体,在不同的显示上下文中使用不同的变体(例如三叉戟,在第一人称、第三人称和库存中有不同的模型)。对于项,变体始终为 `inventory`,对于块状态,变体是由属性-值对的逗号分隔字符串组成的(例如 `facing=north,waterlogged=false`),对于没有块状态属性的块为空。 -The variant is appended to the regular resource location, along with a `#`. For example, the full name of the diamond sword's item model is `minecraft:diamond_sword#inventory`. However, in most contexts, the `inventory` variant can be omitted. +变体附加到常规资源位置,以及 `#`。例如,钻石剑的项模型的完整名称是 `minecraft:diamond_sword#inventory`。然而,在大多数情况下,`inventory` 变体可以省略。 -`ModelResourceLocation` is a [client only][sides] class. This means that servers referencing this class will crash with a `NoClassDefFoundError`. +`ModelResourceLocation` 是一个[仅客户端][sides]的类。这意味着引用该类的服务器将因为 `NoClassDefFoundError` 而崩溃。 -## `ResourceKey`s +## `ResourceKey` -`ResourceKey`s combine a registry id with a registry name. An example would be a registry key with the registry id `minecraft:item` and the registry name `minecraft:diamond_sword`. Unlike a `ResourceLocation`, `ResourceKey`s actually refer to a unique element, thus being able to clearly identify an element. They are most commonly used in contexts where many different registries come in contact with one another. A common use case are datapacks, especially worldgen. +`ResourceKey` 将注册表 ID 与注册表名称结合在一起。一个示例是具有注册表 ID `minecraft:item` 和注册表名称 `minecraft:diamond_sword` 的注册表键。与 `ResourceLocation` 不同,`ResourceKey` 实际上指代一个唯一的元素,因此能够清楚地识别一个元素。它们通常用于许多不同的注册表相互接触的情况。一个常见的用例是数据包,特别是世界生成。 -A new `ResourceKey` can be created through the static method `ResourceKey#create(ResourceKey>, ResourceLocation)`. The second parameter here is the registry name, while the first parameter is what is known as a registry key. Registry keys are a special kind of `ResourceKey` whose registry is the root registry (i.e. the registry of all other registries). A registry key can be created via `ResourceKey#createRegistryKey(ResourceLocation)` with the desired registry's id. +可以通过静态方法 `ResourceKey#create(ResourceKey>, ResourceLocation)` 创建新的 `ResourceKey`。这里的第二个参数是注册表名称,而第一个参数是所谓的注册表键。注册表键是一种特殊的 `ResourceKey`,其注册表是根注册表(即所有其他注册表的注册表)。可以通过 `ResourceKey#createRegistryKey(ResourceLocation)` 创建所需注册表的注册表键。 -`ResourceKey`s are interned at creation. This means that comparing by reference equality (`==`) is possible and encouraged, but their creation is comparatively expensive. +`ResourceKey` 在创建时进行了内部化。这意味着可以并且鼓励通过引用相等性(`==`)进行比较,但它们的创建相对较昂贵。 [registries]: ../concepts/registries.md [sides]: ../concepts/sides.md From 85005c2c78b361b284ab9e614c87b9e596e189a2 Mon Sep 17 00:00:00 2001 From: DaZiDian <113085496+DaZiDian@users.noreply.github.com> Date: Thu, 25 Apr 2024 21:05:40 +0800 Subject: [PATCH 64/87] Update updatechecker.md --- docs/misc/updatechecker.md | 60 +++++++++++++++++++------------------- 1 file changed, 30 insertions(+), 30 deletions(-) diff --git a/docs/misc/updatechecker.md b/docs/misc/updatechecker.md index 85d7960ce..3f6194d43 100644 --- a/docs/misc/updatechecker.md +++ b/docs/misc/updatechecker.md @@ -1,61 +1,61 @@ -Forge Update Checker +Forge更新检查器 ==================== -Forge provides a very lightweight, opt-in, update-checking framework. If any mods have an available update, it will show a flashing icon on the 'Mods' button of the main menu and mod list along with the respective changelogs. It *does not* download updates automatically. +Forge提供了一个非常轻量级的、可选择的更新检查框架。如果任何mod有可用的更新,它将在主菜单的“Mods”按钮和mod列表上显示一个闪烁的图标,以及相应的更新日志。它*不会*自动下载更新。 -Getting Started +入门指南 --------------- -The first thing you want to do is specify the `updateJSONURL` parameter in your `mods.toml` file. The value of this parameter should be a valid URL pointing to an update JSON file. This file can be hosted on your own web server, GitHub, or wherever you want as long as it can be reliably reached by all users of your mod. +首先,您需要在您的 `mods.toml` 文件中指定 `updateJSONURL` 参数。该参数的值应该是一个指向更新JSON文件的有效URL。此文件可以托管在您自己的Web服务器、GitHub或任何您想要的地方,只要所有使用您的mod的用户都可以可靠地访问它。 -Update JSON format +更新JSON格式 ------------------ -The JSON itself has a relatively simple format as follows: +JSON本身具有相对简单的格式,如下所示: ```js { - "homepage": "", + "homepage": "<您的mod的主页/下载页面>", "": { - "": "", - // List all versions of your mod for the given Minecraft version, along with their changelogs + "": "<此版本的更新日志>", + // 列出给定Minecraft版本的所有版本的您的mod,以及它们的更新日志 // ... }, "promos": { "-latest": "", - // Declare the latest "bleeding-edge" version of your mod for the given Minecraft version + // 声明给定Minecraft版本的最新的“最新”版本的您的mod "-recommended": "", - // Declare the latest "stable" version of your mod for the given Minecraft version + // 声明给定Minecraft版本的最新的“稳定”版本的您的mod // ... } } ``` -This is fairly self-explanatory, but some notes: - -* The link under `homepage` is the link the user will be shown when the mod is outdated. -* Forge uses an internal algorithm to determine whether one version string of your mod is "newer" than another. Most versioning schemes should be compatible, but see the `ComparableVersion` class if you are concerned about whether your scheme is supported. Adherence to [Maven versioning][mvnver] is highly recommended. -* The changelog string can be separated into lines using `\n`. Some prefer to include a abbreviated changelog, then link to an external site that provides a full listing of changes. -* Manually inputting data can be chore. You can configure your `build.gradle` to automatically update this file when building a release as Groovy has native JSON parsing support. Doing this is left as an exercise to the reader. +这相当容易理解,但一些注意事项: -- Some examples can be found here for [nocubes][], [Corail Tombstone][corail] and [Chisels & Bits 2][chisel]. +* `homepage` 下的链接是当mod过期时将向用户显示的链接。 +* Forge使用内部算法来确定您的mod的一个版本字符串是否比另一个版本字符串“更新”。大多数版本方案都应该是兼容的,但如果您担心您的方案是否受支持,请参阅 `ComparableVersion` 类。强烈建议遵循[Maven版本规范][mvnver]。 +* 更新日志字符串可以使用 `\n` 分隔成行。一些人喜欢包含一个简短的更新日志,然后链接到一个提供完整更改列表的外部网站。 +* 手动输入数据可能会很烦琐。您可以将您的 `build.gradle` 配置为在构建发布时自动更新此文件,因为 Groovy 具有原生的JSON解析支持。将此操作留给读者作为练习。 -Retrieving Update Check Results +- 这里有一些示例,[nocubes][], [Corail Tombstone][corail] 和 [Chisels & Bits 2][chisel]。 + +检索更新检查结果 ------------------------------- -You can retrieve the results of the Forge Update Checker using `VersionChecker#getResult(IModInfo)`. You can obtain your `IModInfo` via `ModContainer#getModInfo`. You can get your `ModContainer` using `ModLoadingContext.get().getActiveContainer()` inside your constructor, `ModList.get().getModContainerById()`, or `ModList.get().getModContainerByObject()`. You can obtain any other mod's `ModContainer` using `ModList.get().getModContainerById()`. The returned object has a method `#status` which indicates the status of the version check. +您可以使用 `VersionChecker#getResult(IModInfo)` 检索 Forge 更新检查器的结果。您可以通过 `ModContainer#getModInfo` 获取您的 `IModInfo`。您可以在构造函数中使用 `ModLoadingContext.get().getActiveContainer()`,`ModList.get().getModContainerById()` 或 `ModList.get().getModContainerByObject()` 获取您的 `ModContainer`。您可以使用 `ModList.get().getModContainerById()` 获取任何其他 mod 的 `ModContainer`。返回的对象具有一个 `#status` 方法,该方法指示版本检查的状态。 -| Status | Description | +| 状态 | 描述 | |----------------:|:------------| -| `FAILED` | The version checker could not connect to the URL provided. | -| `UP_TO_DATE` | The current version is equal to the recommended version. | -| `AHEAD` | The current version is newer than the recommended version if there is not latest version. | -| `OUTDATED` | There is a new recommended or latest version. | -| `BETA_OUTDATED` | There is a new latest version. | -| `BETA` | The current version is equal to or newer than the latest version. | -| `PENDING` | The result requested has not finished yet, so you should try again in a little bit. | - -The returned object will also have the target version and any changelog lines as specified in `update.json`. +| `FAILED` | 版本检查器无法连接到提供的URL。 | +| `UP_TO_DATE` | 当前版本等于推荐版本。 | +| `AHEAD` | 如果没有最新版本,当前版本比推荐版本新。 | +| `OUTDATED` | 有新的推荐或最新版本。 | +| `BETA_OUTDATED` | 有新的最新版本。 | +| `BETA` | 当前版本等于或比最新版本更新。 | +| `PENDING` | 请求的结果尚未完成,因此您应该稍后重试。 | + +返回的对象还将具有目标版本和任何在 `update.json` 中指定的更新日志行。 [mvnver]: ../gettingstarted/versioning.md [nocubes]: https://cadiboo.github.io/projects/nocubes/update.json From e33adf6d88b8315d94091a2f72f32820fa8e853e Mon Sep 17 00:00:00 2001 From: DaZiDian <113085496+DaZiDian@users.noreply.github.com> Date: Thu, 25 Apr 2024 21:06:55 +0800 Subject: [PATCH 65/87] Update configuration-tasks.md --- docs/networking/configuration-tasks.md | 68 +++++++++++++------------- 1 file changed, 34 insertions(+), 34 deletions(-) diff --git a/docs/networking/configuration-tasks.md b/docs/networking/configuration-tasks.md index 1e94b3c5d..e8f40c032 100644 --- a/docs/networking/configuration-tasks.md +++ b/docs/networking/configuration-tasks.md @@ -1,27 +1,27 @@ -# Using Configuration Tasks +# 使用配置任务 -The networking protocol for the client and server has a specific phase where the server can configure the client before the player actually joins the game. -This phase is called the configuration phase, and is for example used by the vanilla server to send the resource pack information to the client. +客户端和服务器的网络协议有一个特定的阶段,服务器可以在玩家实际加入游戏之前配置客户端。 +这个阶段称为配置阶段,例如,原版服务器用它来向客户端发送资源包信息。 -This phase can also be used by mods to configure the client before the player joins the game. +这个阶段也可以被 mod 用来在玩家加入游戏之前配置客户端。 -## Registering a configuration task -The first step to using the configuration phase is to register a configuration task. -This can be done by registering a new configuration task in the `OnGameConfigurationEvent` event. +## 注册配置任务 +使用配置阶段的第一步是注册一个配置任务。 +这可以通过在 `OnGameConfigurationEvent` 事件中注册新的配置任务来完成。 ```java @SubscribeEvent public static void register(final OnGameConfigurationEvent event) { event.register(new MyConfigurationTask()); } ``` -The `OnGameConfigurationEvent` event is fired on the mod bus, and exposes the current listener used by the server to configure the relevant client. -A modder can use the exposed listener to figure out if the client is running the mod, and if so, register a configuration task. +`OnGameConfigurationEvent` 事件在 mod 总线上触发,并暴露了服务器用来配置相关客户端的当前监听器。 +Modder 可以使用暴露的监听器来判断客户端是否运行了 mod,并在是这样的情况下注册一个配置任务。 -## Implementing a configuration task -A configuration task is a simple interface: `ICustomConfigurationTask`. -This interface has two methods: `void run(Consumer sender);`, and `ConfigurationTask.Type type();` which returns the type of the configuration task. -The type is used to identify the configuration task. -An example of a configuration task is shown below: +## 实现配置任务 +配置任务是一个简单的接口:`ICustomConfigurationTask`。 +这个接口有两个方法:`void run(Consumer sender);`,和 `ConfigurationTask.Type type();` 返回配置任务的类型。 +类型用于标识配置任务。 +下面是一个配置任务的示例: ```java public record MyConfigurationTask implements ICustomConfigurationTask { public static final ConfigurationTask.Type TYPE = new ConfigurationTask.Type(new ResourceLocation("mymod:my_task")); @@ -39,14 +39,14 @@ public record MyConfigurationTask implements ICustomConfigurationTask { } ``` -## Acknowledging a configuration task -Your configuration is executed on the server, and the server needs to know when the next configuration task can be executed. -This is done by acknowledging the execution of said configuration task. +## 确认配置任务 +您的配置在服务器上执行,服务器需要知道何时可以执行下一个配置任务。 +这可以通过确认所述配置任务的执行来完成。 -There are two primary ways of achieving this: +有两种主要方式可以实现这一点: -### Capturing the listener -When the client does not need to acknowledge the configuration task, then the listener can be captured, and the configuration task can be acknowledged directly on the server side. +### 捕获监听器 +当客户端不需要确认配置任务时,可以捕获监听器,并可以直接在服务器端确认配置任务。 ```java public record MyConfigurationTask(ServerConfigurationListener listener) implements ICustomConfigurationTask { public static final ConfigurationTask.Type TYPE = new ConfigurationTask.Type(new ResourceLocation("mymod:my_task")); @@ -64,25 +64,25 @@ public record MyConfigurationTask(ServerConfigurationListener listener) implemen } } ``` -To use such a configuration task, the listener needs to be captured in the `OnGameConfigurationEvent` event. +要使用这样的配置任务,需要在 `OnGameConfigurationEvent` 事件中捕获监听器。 ```java @SubscribeEvent public static void register(final OnGameConfigurationEvent event) { event.register(new MyConfigurationTask(event.listener())); } ``` -Then the next configuration task will be executed immediately after the current configuration task has completed, and the client does not need to acknowledge the configuration task. -Additionally, the server will not wait for the client to properly process the send payloads. +然后,在当前配置任务完成后,下一个配置任务将立即执行,客户端不需要确认配置任务。 +此外,服务器将不会等待客户端正确处理发送的载荷。 -### Acknowledging the configuration task -When the client needs to acknowledge the configuration task, then you will need to send your own payload to the client: +### 确认配置任务 +当客户端需要确认配置任务时,您将需要向客户端发送自己的载荷: ```java public record AckPayload() implements CustomPacketPayload { public static final ResourceLocation ID = new ResourceLocation("mymod:ack"); @Override public void write(final FriendlyByteBuf buffer) { - // No data to write + // 无需写入数据 } @Override @@ -91,14 +91,14 @@ public record AckPayload() implements CustomPacketPayload { } } ``` -When a payload from a server side configuration task is properly processed you can send this payload to the server to acknowledge the configuration task. +当服务器端配置任务发送的有效载荷被正确处理时,您可以向服务器发送此载荷以确认配置任务。 ```java public void onMyData(MyData data, ConfigurationPayloadContext context) { context.submitAsync(() -> { blah(data.name()); }) .exceptionally(e -> { - // Handle exception + // 处理异常 context.packetHandler().disconnect(Component.translatable("my_mod.configuration.failed", e.getMessage())); return null; }) @@ -107,16 +107,16 @@ public void onMyData(MyData data, ConfigurationPayloadContext context) { }); } ``` -Where `onMyData` is the handler for the payload that was sent by the server side configuration task. +其中 `onMyData` 是处理由服务器端配置任务发送的载荷的处理程序。 -When the server receives this payload it will acknowledge the configuration task, and the next configuration task will be executed: +当服务器接收到此载荷时,将确认配置任务,并将执行下一个配置任务: ```java public void onAck(AckPayload payload, ConfigurationPayloadContext context) { context.taskCompletedHandler().onTaskCompleted(MyConfigurationTask.TYPE); } ``` -Where `onAck` is the handler for the payload that was sent by the client. +其中 `onAck` 是处理由客户端发送的载荷的处理程序。 -## Stalling the login process -When the configuration is not acknowledged, then the server will wait forever, and the client will never join the game. -So it is important to always acknowledge the configuration task, unless the configuration task failed, then you can disconnect the client. +## 阻塞登录过程 +当配置未被确认时,服务器将永远等待,客户端将永远无法加入游戏。 +因此,始终确认配置任务非常重要,除非配置任务失败,然后您可以断开客户端的连接。 From 365ae476ed3bc1f98e39ec183f166450a2f0dae2 Mon Sep 17 00:00:00 2001 From: DaZiDian <113085496+DaZiDian@users.noreply.github.com> Date: Thu, 25 Apr 2024 21:07:26 +0800 Subject: [PATCH 66/87] Update entities.md --- docs/networking/entities.md | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/docs/networking/entities.md b/docs/networking/entities.md index a63e958d1..07af05890 100644 --- a/docs/networking/entities.md +++ b/docs/networking/entities.md @@ -1,29 +1,29 @@ -# Entities +# 实体 -In addition to regular network messages, there are various other systems provided to handle synchronizing entity data. +除了常规的网络消息外,还提供了各种其他系统来处理实体数据的同步。 -## Spawn Data -Since 1.20.2 Mojang introduced the concept of Bundle packets, which are used to send entity spawn packets together. -This allows for more data to be sent with the spawn packet, and for that data to be sent more efficiently. +## 生成数据 +自 1.20.2 版本以来,Mojang 引入了 Bundle 数据包的概念,用于将实体生成数据包一起发送。 +这允许更多的数据与生成数据包一起发送,并且使得数据的发送更有效率。 -You can add extra data to the spawn packet Forge sends by implementing the following interface. +您可以通过实现以下接口向 Forge 发送的生成数据包添加额外数据。 ### IEntityWithComplexSpawn -If your entity has data that is needed on the client, but does not change over time, then it can be added to the entity spawn packet using this interface. `#writeSpawnData` and `#readSpawnData` control how the data should be encoded to/decoded from the network buffer. -Alternatively you can override the method `sendPairingData(...)` which is called when the entity is paired with a client. This method is called on the server, and can be used to send additional payloads to the client within the same bundle as the spawn packet. +如果您的实体具有在客户端上需要但随时间不变的数据,则可以使用此接口将其添加到实体生成数据包中。`#writeSpawnData` 和 `#readSpawnData` 控制如何将数据编码到/从网络缓冲区中解码。 +或者,您可以重写 `sendPairingData(...)` 方法,该方法在实体与客户端配对时调用。此方法在服务器上调用,可用于在生成数据包的同一捆绑包中向客户端发送附加负载。 -## Dynamic Data -### Data Parameters +## 动态数据 +### 数据参数 -This is the main vanilla system for synchronizing entity data from the server to the client. As such, a number of vanilla examples are available to refer to. +这是将实体数据从服务器同步到客户端的主要原始系统。因此,有许多可用于参考的原始示例。 -Firstly, you need a `EntityDataAccessor` for the data you wish to keep synchronized. This should be stored as a `static final` field in your entity class, obtained by calling `SynchedEntityData#defineId` and passing the entity class and a serializer for that type of data. The available serializer implementations can be found as static constants within the `EntityDataSerializers` class. +首先,您需要为要保持同步的数据获取一个 `EntityDataAccessor`。这应该作为您的实体类中的 `static final` 字段存储,通过调用 `SynchedEntityData#defineId` 并传递实体类和该类型数据的序列化程序来获得。可用的序列化程序实现可以在 `EntityDataSerializers` 类的静态常量中找到。 :::caution -You should __only__ create data parameters for your own entities, _within that entity's class_. -Adding parameters to entities you do not control can cause the IDs used to send that data over the network to become desynchronized, causing difficult to debug crashes. +您应该 __仅__ 为您自己的实体创建数据参数,在该实体类内部。 +为您无法控制的实体添加参数可能会导致用于通过网络发送该数据的 ID 不同步,导致难以调试的崩溃。 ::: -Then, override `Entity#defineSynchedData` and call `this.entityData.define(...)` for each of your data parameters, passing the parameter and an initial value to use. Remember to always call the `super` method first! +然后,重写 `Entity#defineSynchedData` 并为每个数据参数调用 `this.entityData.define(...)`,传递参数和要使用的初始值。记得始终先调用 `super` 方法! -You can then get and set these values via your entity's `entityData` instance. Changes made will be synchronized to the client automatically. +然后,您可以通过实体的 `entityData` 实例获取和设置这些值。所做的更改将自动同步到客户端。 From 65a1fb0f7318507c3c0152c7cfd29f66bbd406b6 Mon Sep 17 00:00:00 2001 From: DaZiDian <113085496+DaZiDian@users.noreply.github.com> Date: Thu, 25 Apr 2024 21:07:51 +0800 Subject: [PATCH 67/87] Update index.md --- docs/networking/index.md | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/docs/networking/index.md b/docs/networking/index.md index d11abe4bd..6a0cb4b8c 100644 --- a/docs/networking/index.md +++ b/docs/networking/index.md @@ -1,18 +1,18 @@ -# Networking +# 网络通信 -Communication between servers and clients is the backbone of a successful mod implementation. +服务器和客户端之间的通信是成功实现模组的基础。 -There are two primary goals in network communication: +网络通信有两个主要目标: -1. Making sure the client view is "in sync" with the server view - - The flower at coordinates (X, Y, Z) just grew -2. Giving the client a way to tell the server that something has changed about the player - - the player pressed a key +1. 确保客户端视图与服务器视图“同步” + - 在坐标 (X, Y, Z) 处的花刚刚生长了 +2. 让客户端告诉服务器有关玩家状态变化的信息 + - 玩家按下了一个键 -The most common way to accomplish these goals is to pass messages between the client and the server. These messages will usually be structured, containing data in a particular arrangement, for easy sending and receiving. +实现这些目标最常见的方式是在客户端和服务器之间传递消息。这些消息通常会被结构化,按特定的排列方式包含数据,以便于发送和接收。 -There is a technique provided by NeoForge to facilitate communication mostly built on top of [netty][]. -This technique can be used by listening for the `RegisterPayloadHandlerEvent` event, and then registering a specific type of [payloads][], its reader, and its handler function to the registrar. +NeoForge 提供了一种技术来促进通信,主要建立在 [netty][] 之上。 +通过监听 `RegisterPayloadHandlerEvent` 事件,可以注册特定类型的 [负载][payloads]、其读取器和处理函数到注册器中。 [netty]: https://netty.io "Netty Website" [payloads]: ./payload.md "Registering custom Payloads" From e85fdd2e9b540e12da790c24e833c61907fcecae Mon Sep 17 00:00:00 2001 From: DaZiDian <113085496+DaZiDian@users.noreply.github.com> Date: Thu, 25 Apr 2024 21:08:38 +0800 Subject: [PATCH 68/87] Update payload.md --- docs/networking/payload.md | 60 +++++++++++++++++++------------------- 1 file changed, 30 insertions(+), 30 deletions(-) diff --git a/docs/networking/payload.md b/docs/networking/payload.md index c36ea99ec..b549ec2e4 100644 --- a/docs/networking/payload.md +++ b/docs/networking/payload.md @@ -1,6 +1,6 @@ -# Registering Payloads +# 注册负载 -Payloads are a way to send arbitrary data between the client and the server. They are registered using the `IPayloadRegistrar` that can be retrieved for a given namespace from the `RegisterPayloadHandlerEvent` event. +负载是在客户端和服务器之间发送任意数据的一种方法。它们使用从 `RegisterPayloadHandlerEvent` 事件中获取的 `IPayloadRegistrar` 进行注册,该事件可以为给定的命名空间检索到。 ```java @SubscribeEvent public static void register(final RegisterPayloadHandlerEvent event) { @@ -8,12 +8,12 @@ public static void register(final RegisterPayloadHandlerEvent event) { } ``` -Assuming we want to send the following data: +假设我们想要发送以下数据: ```java public record MyData(String name, int age) {} ``` -Then we can implement the `CustomPacketPayload` interface to create a payload that can be used to send and receive this data. +然后,我们可以实现 `CustomPacketPayload` 接口来创建一个可用于发送和接收此数据的负载。 ```java public record MyData(String name, int age) implements CustomPacketPayload { @@ -35,10 +35,10 @@ public record MyData(String name, int age) implements CustomPacketPayload { } } ``` -As you can see from the example above the `CustomPacketPayload` interface requires us to implement the `write` and `id` methods. The `write` method is responsible for writing the data to the buffer, and the `id` method is responsible for returning a unique identifier for this payload. -We then also need a reader to register this later on, here we can use a custom constructor to read the data from the buffer. +从上面的示例中可以看出,`CustomPacketPayload` 接口要求我们实现 `write` 和 `id` 方法。`write` 方法负责将数据写入缓冲区,而 `id` 方法负责返回此负载的唯一标识符。 +然后,我们还需要一个读取器来稍后进行注册,在这里我们可以使用自定义构造函数从缓冲区中读取数据。 -Finally, we can register this payload with the registrar: +最后,我们可以使用注册器注册此负载: ```java @SubscribeEvent public static void register(final RegisterPayloadHandlerEvent event) { @@ -48,17 +48,17 @@ public static void register(final RegisterPayloadHandlerEvent event) { .server(ServerPayloadHandler.getInstance()::handleData)); } ``` -Dissecting the code above we can notice a couple of things: -- The registrar has a `play` method, that can be used for registering payloads which are send during the play phase of the game. - - Not visible in this code are the methods `configuration` and `common`, however they can also be used to register payloads for the configuration phase. The `common` method can be used to register payloads for both the configuration and play phase simultaneously. -- The constructor of `MyData` is used as a method reference to create a reader for the payload. -- The third argument for the registration method is a callback that can be used to register the handlers for when the payload arrives at either the client or server side. - - The `client` method is used to register a handler for when the payload arrives at the client side. - - The `server` method is used to register a handler for when the payload arrives at the server side. - - There is additionally a secondary registration method `play` on the registrar itself that accepts a handler for both the client and server side, this can be used to register a handler for both sides at once. +分解上面的代码,我们可以注意到几件事情: +- 注册器有一个 `play` 方法,可用于注册在游戏播放阶段发送的负载。 + - 此代码中未显示的方法还有 `configuration` 和 `common`,但它们也可以用于为配置阶段注册负载。`common` 方法可用于同时为配置和游戏播放阶段注册负载。 +- `MyData` 的构造函数被用作方法引用,以创建负载的读取器。 +- 注册方法的第三个参数是一个回调,用于注册负载到达客户端或服务器端时的处理程序。 + - `client` 方法用于在负载到达客户端时注册处理程序。 + - `server` 方法用于在负载到达服务器端时注册处理程序。 + - 在注册器本身上还有一个次要的注册方法 `play`,它接受客户端和服务器端的处理程序,可以用于同时为两端注册处理程序。 -Now that we have registered the payload we need to implement a handler. -For this example we will specifically take a look at the client side handler, however the server side handler is very similar. +现在我们已经注册了负载,我们需要实现一个处理程序。 +在此示例中,我们将特别关注客户端端处理程序,但服务器端处理程序非常相似。 ```java public class ClientPayloadHandler { @@ -69,30 +69,30 @@ public class ClientPayloadHandler { } public void handleData(final MyData data, final PlayPayloadContext context) { - // Do something with the data, on the network thread + // 处理数据,在网络线程上 blah(data.name()); - // Do something with the data, on the main thread + // 在主游戏线程上处理数据 context.workHandler().submitAsync(() -> { blah(data.age()); }) .exceptionally(e -> { - // Handle exception + // 处理异常 context.packetHandler().disconnect(Component.translatable("my_mod.networking.failed", e.getMessage())); return null; }); } } ``` -Here a couple of things are of note: -- The handling method here gets the payload, and a contextual object. The contextual object is different for the play and configuration phase, and if you register a common packet, then it will need to accept the super type of both contexts. -- The handler of the payload method is invoked on the networking thread, so it is important to do all the heavy work here, instead of blocking the main game thread. -- If you want to run code on the main game thread you can use the `workHandler` of the context to submit a task to the main thread. - - The `workHandler` will return a `CompletableFuture` that will be completed on the main thread, and can be used to submit tasks to the main thread. - - Notice: A `CompletableFuture` is returned, this means that you can chain multiple tasks together, and handle exceptions in a single place. - - If you do not handle the exception in the `CompletableFuture` then it will be swallowed, **and you will not be notified of it**. +这里需要注意几件事情: +- 此处处理方法获取负载和上下文对象。上下文对象对于播放和配置阶段是不同的,如果注册了一个通用负载,则需要接受两个上下文的超类型。 +- 负载方法的处理程序在网络线程上调用,因此重要的是在此处进行所有繁重的工作,而不是阻塞主游戏线程。 +- 如果要在主游戏线程上运行代码,可以使用上下文的 `workHandler` 提交任务到主线程。 + - `workHandler` 将返回一个在主线程上完成的 `CompletableFuture`,可以用于提交任务到主线程。 + - 注意:返回的是 `CompletableFuture`,这意味着您可以将多个任务链接在一起,并在单个位置处理异常。 + - 如果不在 `CompletableFuture` 中处理异常,则它将被忽略,**您将不会收到任何通知**。 -Now that you know how you can facilitate the communication between the client and the server for your mod, you can start implementing your own payloads. -With your own payloads you can then use those to configure the client and server using [Configuration Tasks][] +现在您知道了如何为您的模组促进客户端和服务器之间的通信,您可以开始实现自己的负载。 +有了自己的负载,您就可以使用它们来配置客户端和服务器,使用[配置任务][]。 -[Configuration Tasks]: ./configuration-tasks.md +[配置任务]: ./configuration-tasks.md From 5d7006bce88db242cf4bc1c375eaea10ce35a17e Mon Sep 17 00:00:00 2001 From: DaZiDian <113085496+DaZiDian@users.noreply.github.com> Date: Thu, 25 Apr 2024 21:15:22 +0800 Subject: [PATCH 69/87] Update index.md --- docs/resources/index.md | 184 +++++++++++++++++++++------------------- 1 file changed, 95 insertions(+), 89 deletions(-) diff --git a/docs/resources/index.md b/docs/resources/index.md index e6f610974..57215a819 100644 --- a/docs/resources/index.md +++ b/docs/resources/index.md @@ -1,87 +1,91 @@ -# Resources +# 资源 -Resources are external files that are used by the game, but are not code. The most prominent kinds of resources are textures, however, many other types of resources exist in the Minecraft ecosystem. Of course, all these resources require a consumer on the code side, so the consuming systems are grouped in this section as well. +资源是游戏使用的外部文件,但不包括代码。在 Minecraft 生态系统中,最常见的资源类型是纹理,但还有许多其他类型的资源。当然,所有这些资源都需要代码端的消费系统,因此本节也将对这些系统进行分组。 -Minecraft generally has two kinds of resources: client-side resources, known as assets, and server-side resources, known as data. Assets are mostly display-only information, for example textures, display models, translations, or sounds, while data includes various things that affect gameplay, such as loot tables, recipes, or worldgen information. They are loaded from resource packs and data packs, respectively. NeoForge generates a built-in resource and data pack for every mod. +Minecraft 通常有两种资源:客户端资源,称为资产,以及服务器端资源,称为数据。资产主要是显示信息,例如纹理、显示模型、翻译或声音,而数据包括影响游戏玩法的各种内容,如战利品表、配方或世界生成信息。它们分别从资源包和数据包中加载。NeoForge 为每个模组生成内置的资源包和数据包。 -Both resource and data packs normally require a [`pack.mcmeta` file][packmcmeta], this was also the case in past Forge versions. However, NeoForge generates these at runtime for you, so you don't need to worry about it anymore. +无论资源包还是数据包,通常都需要一个 [`pack.mcmeta` 文件][packmcmeta],在过去的 Forge 版本中也是如此。然而,NeoForge 为您在运行时生成了这些文件,因此您无需再担心。 -If you are confused about the format of something, have a look at the vanilla resources. Your NeoForge development environment not only contains vanilla code, but also vanilla resources. They can be found in the External Resources section (IntelliJ)/Project Libraries section (Eclipse), under the name `ng_dummy_ng.net.minecraft:client:client-extra:` (for Minecraft resources) or `ng_dummy_ng.net.neoforged:neoforge:` (for NeoForge resources). +如果您对某个格式感到困惑,请查看原版资源。您的 NeoForge 开发环境不仅包含原版代码,还包含原版资源。它们可以在 External Resources 部分(IntelliJ)/Project Libraries 部分(Eclipse)中找到,名称为 `ng_dummy_ng.net.minecraft:client:client-extra:`(对于 Minecraft 资源)或 `ng_dummy_ng.net.neoforged:neoforge:`(对于 NeoForge 资源)。 -## Assets +## 资产 -_See also: [Resource Packs][mcwikiresourcepacks] on the [Minecraft Wiki][mcwiki]_ +_另见:[Minecraft Wiki 上的资源包][mcwikiresourcepacks]_ -Assets, or client-side resources, are all resources that are only relevant on the [client][sides]. They are loaded from resource packs, sometimes also known by the old term texture packs (stemming from old versions when they could only affect textures). A resource pack is basically an `assets` folder. The `assets` folder contains subfolders for the various namespaces the resource pack includes; every namespace is one subfolder. For example, a resource pack for a mod with the id `coolmod` will probably contain a `coolmod` namespace, but may additionally include other namespaces, such as `minecraft`. +资产,或客户端资源,是所有仅在[客户端][sides]上相关的资源。它们从资源包中加载,有时也被称为旧术语纹理包(源自旧版本,当时它们只能影响纹理)。资源包基本上是一个 `assets` 文件夹。`assets` 文件夹包含资源包包含的各种命名空间的子文件夹;每个命名空间是一个子文件夹。例如,一个模组的资源包可能包含 `coolmod` 命名空间,但可能还包括其他命名空间,例如 `minecraft`。 -NeoForge automatically collects all mod resource packs into the `Mod resources` pack, which sits at the bottom of the Selected Packs side in the resource packs menu. It is currently not possible to disable the `Mod resources` pack. However, resource packs that sit above the `Mod resources` pack override resources defined in a resource pack below them. This mechanic allows resource pack makers to override your mod's resources, and also allows mod developers to override Minecraft resources if needed. +NeoForge 自动将所有模组资源包收集到 `Mod resources` 包中,该包位于资源包菜单中的 Selected Packs 边的底部。当前无法禁用 `Mod resources` 包。但是,位于 `Mod resources` 包上方的资源包可以覆盖位于其下的资源包中定义的资源。这种机制允许资源包制作者覆盖您的模组资源,并允许模组开发者覆盖 Minecraft 资源。 -Resource packs can contain [models][models], [blockstate files][bsfile], [textures][textures], [sounds][sounds], [particle definitions][particles] and [translation files][translations]. +资源包可以包含 [模型][models]、[方块状态文件][bsfile]、[纹理][textures]、[声音][sounds]、[粒子定义][particles] 和 [翻译文件][translations]。 -## Data +## 数据 -_See also: [Data Packs][mcwikidatapacks] on the [Minecraft Wiki][mcwiki]_ +_另见:[Minecraft Wiki 上的数据包][mcwikidatapacks]_ -In contrast to assets, data is the term for all [server][sides] resources. Similar to resource packs, data is loaded through data packs (or datapacks). Like a resource pack, a data pack consists of a [`pack.mcmeta` file][packmcmeta] and a root folder, named `data`. Then, again like with resource packs, that `data` folder contains subfolders for the various namespaces the resource pack includes; every namespace is one subfolder. For example, a data pack for a mod with the id `coolmod` will probably contain a `coolmod` namespace, but may additionally include other namespaces, such as `minecraft`. +与资产不同,数据是所有[服务器][sides]资源的术语。与资源包类似,数据通过数据包加载。像资源包一样,数据包由 [`pack.mcmeta` 文件][packmcmeta] 和一个名为 `data` 的根文件夹组成。然后,同样像资源包一样,`data` 文件夹包含数据包包含的各种命名空间的子文件夹;每个命名空间是一个子文件夹。例如,一个模组的数据包可能包含 `coolmod` 命名空间,但可能还包括其他命名空间,例如 `minecraft`。 -NeoForge automatically applies all mod data packs to a new world upon creation. It is currently not possible to disable mod data packs. However, most data files can be overridden (and thus be removed by replacing them with an empty file) by a data pack with a higher priority. Additional data packs can be enabled or disabled by placing them in a world's `datapacks` subfolder and then enabling or disabling them through the [`/datapack`][datapackcmd] command. +NeoForge 在创建新世界时自动应用所有模组数据包。当前无法禁用模组数据包。但是,大多数数据文件可以通过具有更高优先级的数据包覆 + +盖(因此可以通过替换为空文件来删除)。通过将数据包放置在世界的 `datapacks` 子文件夹中,然后通过 [`/datapack`][datapackcmd] 命令启用或禁用它们,可以启用或禁用额外的数据包。 :::info -There is currently no built-in way to apply a set of custom data packs to every world. However, there are a number of mods that achieve this. +目前没有内置的方法将一组自定义数据包应用到每个世界。然而,有许多模组可以实现这一点。 ::: -Data packs may contain folders with files affecting the following things: +数据包可能包含影响以下事物的文件夹: -| Folder name | Contents | -|-----------------------------------------------------------------------|------------------------------| -| `advancements` | [Advancements][advancements] | -| `damage_type` | Damage types | -| `loot_tables` | [Loot tables][loottables] | -| `recipes` | [Recipes][recipes] | -| `structures` | Structures | -| `tags` | [Tags][tags] | -| `dimension`, `dimension_type`, `worldgen`, `neoforge/biome_modifiers` | Worldgen files | -| `neoforge/global_loot_modifiers` | [Global loot modifiers][glm] | +| 文件夹名称 | 内容 | +|---------|------| +| `advancements` | [进度][advancements] | +| `damage_type` | 伤害类型 | +| `loot_tables` | [战利品表][loottables] | +| `recipes` | [配方][recipes] | +| `structures` | 结构 | +| `tags` | [标签][tags] | +| `dimension`, `dimension_type`, `worldgen`, `neoforge/biome_modifiers` | 世界生成文件 | +| `neoforge/global_loot_modifiers` | [全局战利品修饰器][glm] | -Additionally, they may also contain subfolders for some systems that integrate with commands. These systems are rarely used in conjunction with mods, but worth mentioning regardless: +此外,它们还可能包含一些与命令集成的系统的子文件夹。这些系统很少与模组一起使用,但无论如何都值得一提: -| Folder name | Contents | -|------------------|--------------------------------| -| `chat_type` | [Chat types][chattype] | -| `functions` | [Functions][function] | -| `item_modifiers` | [Item modifiers][itemmodifier] | -| `predicates` | [Predicates][predicate] | +| 文件夹名称 | 内容 | +|---------|------| +| `chat_type` | [聊天类型][chattype] | +| `functions` | [功能][function] | +| `item_modifiers` | [物品修饰器][itemmodifier] | +| `predicates` | [条件判断][predicate] | ## `pack.mcmeta` -_See also: [`pack.mcmeta` (Resource Pack)][packmcmetaresourcepack] and [`pack.mcmeta` (Data Pack)][packmcmetadatapack] on the [Minecraft Wiki][mcwiki]_ +_另见:[Minecraft Wiki 上的 `pack.mcmeta` (资源包)][packmcmetaresourcepack] 和 `pack.mcmeta` (数据包)][packmcmetadatapack]_ + +`pack.mcmeta` 文件保存资源包或数据包的元数据。对于模组来说,NeoForge 让这个文件变得多余,因为 `pack.mcmeta` 是合成生成的。如果您仍需要一个 `pack.mcmeta` 文件,完整的规范可以在链接的 Minecraft Wiki 文章中找到。 -`pack.mcmeta` files hold the metadata of a resource or data pack. For mods, NeoForge makes this file obsolete, as the `pack.mcmeta` is generated synthetically. In case you still need a `pack.mcmeta` file, the full specification can be found in the linked Minecraft Wiki articles. +## 数据生成 -## Data Generation +数据生成,俗称 datagen,是一种以编程方式生成 JSON 资源文件的方式,以避免手动编写它们时的繁琐和容易出错的过程。这个名字有点误导,因为它适用于资产和数据。 -Data generation, colloquially known as datagen, is a way to programmatically generate JSON resource files, in order to avoid the tedious and error-prone process of writing them by hand. The name is a bit misleading, as it works for assets as well as data. +Datagen 通过为您生成的客户端和服务器运行配置旁的数据运行配置来运行。数据运行配置遵循[模组生命周期][lifecycle],直到注册事件触发之后。然后触发 [`GatherDataEvent`][event],在该事件中,您可以注册您要生成的对象,以数据提供者的形式,将所述对象写入磁盘,并结束过程。 -Datagen is run through the Data run configuration, which is generated for you alongside the Client and Server run configurations. The data run configuration follows the [mod lifecycle][lifecycle] until after the registry events are fired. It then fires the [`GatherDataEvent`][event], in which you can register your to-be-generated objects in the form of data providers, writes said objects to disk, and ends the process. +所有数据提供者都扩展了 `DataProvider` 接口,通常需要重写一个方法。以下是 Minecraft 和 NeoForge 提供的一些值得注意的数据生成器(链接文章提供了更多信息,如辅助方法): -All data providers extend the `DataProvider` interface and usually require one method to be overridden. The following is a list of noteworthy data generators Minecraft and NeoForge offer (the linked articles add further information, such as helper methods): +| 类 | 方法 | 生成 | 方面 | 备注 | +|---|----|-----|----|----| +| [`BlockStateProvider`][blockstateprovider] | `registerStatesAndModels()` | 方块状态文件,方块模型 | 客户端 | | +| [`ItemModelProvider`][itemmodelprovider] | `registerModels()` | 物品模型 | 客户端 | | +| [`LanguageProvider`][langprovider] | `addTranslations()` | 翻译 | 客户端 | 构造函数还需要传递语言。 | +| [`ParticleDescriptionProvider`][particleprovider] | `addDescriptions()` | 粒子定义 | 客户端 | | +| [`SoundDefinitionsProvider`][soundprovider] | `registerSounds()` | 声音定义 | 客户端 | | +| [`AdvancementProvider`][advancementprovider] | `generate()` | 进度 | 服务器 | 确保使用 NeoForge 变体,而不是 Minecraft 本身 -| Class | Method | Generates | Side | Notes | -|------------------------------------------------------|----------------------------------|-----------------------------------------|--------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| [`BlockStateProvider`][blockstateprovider] | `registerStatesAndModels()` | Blockstate files, block models | Client | | -| [`ItemModelProvider`][itemmodelprovider] | `registerModels()` | Item models | Client | | -| [`LanguageProvider`][langprovider] | `addTranslations()` | Translations | Client | Also requires passing the language in the constructor. | -| [`ParticleDescriptionProvider`][particleprovider] | `addDescriptions()` | Particle definitions | Client | | -| [`SoundDefinitionsProvider`][soundprovider] | `registerSounds()` | Sound definitions | Client | | -| [`AdvancementProvider`][advancementprovider] | `generate()` | Advancements | Server | Make sure to use the NeoForge variant, not the Minecraft one. | -| [`LootTableProvider`][loottableprovider] | `generate()` | Loot tables | Server | Requires extra methods and classes to work properly, see linked article for details. | -| [`RecipeProvider`][recipeprovider] | `buildRecipes(RecipeOutput)` | Recipes | Server | | -| [Various subclasses of `TagsProvider`][tagsprovider] | `addTags(HolderLookup.Provider)` | Tags | Server | Several specialized subclasses exist, for example `BlockTagsProvider`. If the one you need doesn't exist, extend `TagsProvider` (or `IntrinsicHolderTagsProvider` if applicable) with your tag type as the generic parameter. | -| [`DatapackBuiltinEntriesProvider`][datapackprovider] | N/A | Datapack builtin entries, e.g. worldgen | Server | See linked article for details. | -| [`DataMapProvider`][datamapprovider] | `gather()` | Data map entries | Server | | -| [`GlobalLootModifierProvider`][glmprovider] | `start()` | Global loot modifiers | Server | | +。 | +| [`LootTableProvider`][loottableprovider] | `generate()` | 战利品表 | 服务器 | 需要额外的方法和类才能正常工作,详情请参阅链接文章。 | +| [`RecipeProvider`][recipeprovider] | `buildRecipes(RecipeOutput)` | 配方 | 服务器 | | +| [多个 `TagsProvider` 的子类][tagsprovider] | `addTags(HolderLookup.Provider)` | 标签 | 服务器 | 存在几个专门的子类,例如 `BlockTagsProvider`。如果您需要的不存在,请扩展 `TagsProvider`(或适用时扩展 `IntrinsicHolderTagsProvider`),将您的标签类型作为泛型参数。 | +| [`DatapackBuiltinEntriesProvider`][datapackprovider] | N/A | 数据包内置条目,例如世界生成 | 服务器 | 详情请参阅链接文章。 | +| [`DataMapProvider`][datamapprovider] | `gather()` | 数据映射条目 | 服务器 | | +| [`GlobalLootModifierProvider`][glmprovider] | `start()` | 全局战利品修饰器 | 服务器 | | -All of these providers follow the same pattern. First, you create a subclass and add your own resources to be generated. Then, you add the provider to the event in an [event handler][eventhandler]. An example using a `RecipeProvider`: +所有这些提供者都遵循相同的模式。首先,创建一个子类并添加您自己要生成的资源。然后,在[事件处理器][eventhandler]中添加提供者。使用 `RecipeProvider` 的一个示例: ```java public class MyRecipeProvider extends RecipeProvider { @@ -91,7 +95,7 @@ public class MyRecipeProvider extends RecipeProvider { @Override protected void buildRecipes(RecipeOutput output) { - // Register your recipes here. + // 在这里注册您的配方。 } } @@ -99,72 +103,74 @@ public class MyRecipeProvider extends RecipeProvider { public class MyDatagenHandler { @SubscribeEvent public static void gatherData(GatherDataEvent event) { - // Data generators may require some of these as constructor parameters. - // See below for more details on each of these. + // 数据生成器可能需要这些作为构造函数参数。 + // 详情请参阅下文每个部分。 DataGenerator generator = event.getGenerator(); PackOutput output = generator.getPackOutput(); ExistingFileHelper existingFileHelper = event.getExistingFileHelper(); CompletableFuture lookupProvider = event.getLookupProvider(); - // Register the provider. + // 注册提供者。 generator.addProvider( - // A boolean that determines whether the data should actually be generated. - // The event provides methods that determine this: + // 一个布尔值,确定是否实际生成数据。 + // 事件提供了确定这一点的方法: // event.includeClient(), event.includeServer(), - // event.includeDev() and event.includeReports(). - // Since recipes are server data, we only run them in a server datagen. + // event.includeDev() 和 event.includeReports()。 + // 由于配方是服务器数据,我们只在服务器数据生成中运行它们。 event.includeServer(), - // Our provider. + // 我们的提供者。 new MyRecipeProvider(output) ); - // Other data providers here. + // 在这里注册其他数据提供者。 } } ``` -The event offers some context for you to use: +事件提供了一些上下文供您使用: + +- `event.getGenerator()` 返回您注册提供者的 `DataGenerator`。 +- `event.getPackOutput()` 返回 `PackOutput`,一些提供者用它来确定文件输出位置。 +- `event.getExistingFileHelper()` 返回 `ExistingFileHelper`,用于提供者需要引用其他文件的事物(例如,可以指定父文件的方块模型)。 +- `event.getLookupProvider()` 返回 `CompletableFuture`,主要用于标签和数据生成注册表引用其他尚未存在的元素。 +- `event.includeClient()`、`event.includeServer()`、`event.includeDev()` 和 `event.includeReports()` 是 `boolean` 方法,允许您检查是否启用了特定的命令行参数。 -- `event.getGenerator()` returns the `DataGenerator` that you register the providers to. -- `event.getPackOutput()` returns a `PackOutput` that is used by some providers to determine their file output location. -- `event.getExistingFileHelper()` returns an `ExistingFileHelper` that is used by providers for things that can reference other files (for example block models, which can specify a parent file). -- `event.getLookupProvider()` returns a `CompletableFuture` that is mainly used by tags and datagen registries to reference other, potentially not yet existing elements. -- `event.includeClient()`, `event.includeServer()`, `event.includeDev()` and `event.includeReports()` are `boolean` methods that allow you to check whether specific command line arguments (see below) are enabled. +### 命令行参数 -### Command Line Arguments +数据生成器可以接受几个命令行参数: -The data generator can accept several command line arguments: +- `--mod examplemod`: 告诉数据生成器为此模组运行数据生成。NeoGradle 为所属模组 ID 自动添加此项,如果您有多个模组在一个项目中,请添加此项。 +- `--output path/to/folder`: 告诉数据生成器输出到给定文件夹。建议使用 Gradle 的 `file(...).getAbsolutePath()` 为您生成绝对路径(相对于项目根目录的路径)。默认为 `file('src/generated/resources').getAbsolutePath()`。 +- `--existing path/to/folder`: 告诉数据生成器 -- `--mod examplemod`: Tells the data generator to run datagen for this mod. Automatically added by NeoGradle for the owning mod id, add this if you e.g. have multiple mods in one project. -- `--output path/to/folder`: Tells the data generator to output into the given folder. It is recommended to use Gradle's `file(...).getAbsolutePath()` to generate an absolute path for you (with a path relative to the project root directory). Defaults to `file('src/generated/resources').getAbsolutePath()`. -- `--existing path/to/folder`: Tells the data generator to consider the given folder when checking for existing files. Like with the output, it is recommended to use Gradle's `file(...).getAbsolutePath()`. -- `--existing-mod examplemod`: Tells the data generator to consider the resources in the given mod's JAR file when checking for existing files. -- Generator modes (all of these are boolean arguments and do not need any additional arguments): - - `--includeClient`: Whether to generate client resources (assets). Check at runtime with `GatherDataEvent#includeClient()`. - - `--includeServer`: Whether to generate server resources (data). Check at runtime with `GatherDataEvent#includeServer()`. - - `--includeDev`: Whether to run dev tools. Generally shouldn't be used by mods. Check at runtime with `GatherDataEvent#includeDev()`. - - `--includeReports`: Whether to dump a list of registered objects. Check at runtime with `GatherDataEvent#includeReports()`. - - `--all`: Enable all generator modes. +在检查现有文件时考虑给定文件夹。与输出一样,建议使用 Gradle 的 `file(...).getAbsolutePath()`。 +- `--existing-mod examplemod`: 告诉数据生成器在检查现有文件时考虑给定模组的 JAR 文件中的资源。 +- 生成器模式(所有这些都是布尔参数,不需要任何额外的参数): + - `--includeClient`: 是否生成客户端资源(资产)。在运行时检查 `GatherDataEvent#includeClient()`。 + - `--includeServer`: 是否生成服务器资源(数据)。在运行时检查 `GatherDataEvent#includeServer()`。 + - `--includeDev`: 是否运行开发工具。通常不应由模组使用。在运行时检查 `GatherDataEvent#includeDev()`。 + - `--includeReports`: 是否转储注册对象列表。在运行时检查 `GatherDataEvent#includeReports()`。 + - `--all`: 启用所有生成器模式。 -All arguments can be added to the run configurations by adding the following to your `build.gradle`: +所有参数可以通过在 `build.gradle` 中添加以下内容来添加到运行配置中: ```groovy runs { - // other run configurations here + // 这里有其他运行配置 data { - programArguments.addAll '--arg1', 'value1', '--arg2', 'value2', '--all' // boolean args have no value + programArguments.addAll '--arg1', 'value1', '--arg2', 'value2', '--all' // 布尔参数没有值 } } ``` -For example, to replicate the default arguments, you could specify the following: +例如,要复制默认参数,您可以指定以下内容: ```groovy runs { - // other run configurations here + // 这里有其他运行配置 data { - programArguments.addAll '--mod', 'examplemod', // insert your own mod id + programArguments.addAll '--mod', 'examplemod', // 插入您自己的模组 ID '--output', file('src/generated/resources').getAbsolutePath(), '--includeClient', '--includeServer' @@ -196,7 +202,7 @@ runs { [mcwikidatapacks]: https://minecraft.wiki/w/Data_pack [mcwikiresourcepacks]: https://minecraft.wiki/w/Resource_pack [models]: client/models/index.md -[packmcmeta]: #packmcmeta +[packmcmeta]: #pack.mcmeta [packmcmetadatapack]: https://minecraft.wiki/w/Data_pack#pack.mcmeta [packmcmetaresourcepack]: https://minecraft.wiki/w/Resource_pack#Contents [particleprovider]: client/particles.md#datagen From f1079ba49bb1b506e497244badf9bcaa5095e8c3 Mon Sep 17 00:00:00 2001 From: DaZiDian <113085496+DaZiDian@users.noreply.github.com> Date: Thu, 25 Apr 2024 21:21:38 +0800 Subject: [PATCH 70/87] Update bakedmodel.md --- docs/resources/client/models/bakedmodel.md | 146 +++++++++++---------- 1 file changed, 76 insertions(+), 70 deletions(-) diff --git a/docs/resources/client/models/bakedmodel.md b/docs/resources/client/models/bakedmodel.md index 58ffd0735..214e08d5f 100644 --- a/docs/resources/client/models/bakedmodel.md +++ b/docs/resources/client/models/bakedmodel.md @@ -1,124 +1,130 @@ -# Baked Models +# 烘焙模型 -`BakedModel`s are the in-code representation of a shape with textures. They can originate from multiple sources, for example from a call to `UnbakedModel#bake` (default model loader) or `IUnbakedGeometry#bake` ([custom model loaders][modelloader]). Some [block entity renderers][ber] also make use of baked models. There is no limit to how complex a model may be. +`BakedModel` 是带有纹理的形状在代码中的表示形式。它们可以来自多个来源,例如通过 `UnbakedModel#bake`(默认模型加载器)或 `IUnbakedGeometry#bake`([自定义模型加载器][modelloader])的调用生成。一些[方块实体渲染器][ber]也使用烘焙模型。模型的复杂度没有限制。 -Models are stored in the `ModelManager`, which can be accessed through `Minecraft.getInstance().modelManager`. Then, you can call `ModelManager#getModel` to get a certain model by its [`ResourceLocation`][rl] or [`ModelResourceLocation`][mrl]. Mods will basically always reuse a model that was previously automatically loaded and baked. +模型存储在 `ModelManager` 中,可以通过 `Minecraft.getInstance().modelManager` 访问。然后,您可以调用 `ModelManager#getModel` 通过其 [`ResourceLocation`][rl] 或 [`ModelResourceLocation`][mrl] 获取特定模型。模组基本上总是重用之前自动加载和烘焙的模型。 -## Methods of `BakedModel` +## `BakedModel` 的方法 ### `getQuads` -The most important method of a baked model is `getQuads`. This method is responsible for returning a list of `BakedQuad`s, which can then be sent to the GPU. A quad compares to a triangle in a modeling program (and in most other games), however due to Minecraft's general focus on squares, the developers elected to use quads (4 vertices) instead of triangles (3 vertices) for rendering in Minecraft. `getQuads` has five parameters that can be used: +烘焙模型最重要的方法是 `getQuads`。此方法负责返回 `BakedQuad` 的列表,这些列表随后可以发送到 GPU。在建模程序中(以及大多数其他游戏中),四边形类似于三角形,然而由于 Minecraft 通常关注于方形,开发者选择使用四边形(4个顶点)而非三角形(3个顶点)进行渲染。`getQuads` 有五个参数可用: -- A `BlockState`: The [blockstate] being rendered. May be null, indicating that an item is being rendered. -- A `Direction`: The direction of the face being culled against. May be null, which means quads that cannot be occluded should be returned. -- A `RandomSource`: A client-bound random source you can use for randomization. -- A `ModelData`: The extra model data to use. This may contain additional data from the block entity needed for rendering. Supplied by `BakedModel#getModelData`. -- A `RenderType`: The [render type][rendertype] to use for rendering the block. May be null, indicating that the quads for all render types used by this model should be returned. Otherwise, it is one of the render types returned by `BakedModel#getRenderTypes` (see below). +- `BlockState`:正在渲染的[方块状态][blockstate]。可能为空,表示正在渲染物品。 +- `Direction`:正在剔除的面的方向。可能为空,这意味着应该返回不可遮挡的四边形。 +- `RandomSource`:客户端绑定的随机源,您可以用于随机化。 +- `ModelData`:使用的额外模型数据。这可能包含方块实体渲染所需的额外数据。由 `BakedModel#getModelData` 提供。 +- `RenderType`:用于渲染方块的[渲染类型][rendertype]。可能为空,表示应返回此模型使用的所有渲染类型的四边形。否则,它是 `BakedModel#getRenderTypes` 返回的渲染类型之一(见下文)。 -Models should heavily cache. This is because even though chunks are only rebuilt when a block in them changes, the computations done in this method still need to be as fast as possible and should ideally be cached heavily due to the amount of times this method will be called per chunk section (up to seven times per RenderType used by a given model * amount of RenderTypes used by the respective model * 4096 blocks per chunk section). In addition, [BERs][ber] or entity renderers may actually call this method several times per frame. +模型应该大量缓存。这是因为即使区块只有在其中一个方块更改时才重建,但此方法中的计算仍需尽可能快速,并且理想情况下由于该方法将被每个区块部分调用多次(每个给定模型使用的渲染类型的次数 * 每个模型使用的渲染类型的数量 * 每个区块部分 4096 个方块),因此应该进行大量缓存。此外,[方块实体渲染器][ber]或实体渲染器实际上可能每帧调用此方法几次。 -### `applyTransform` and `getTransforms` +### `applyTransform` 和 `getTransforms` -`applyTransform` allows for applying custom logic when applying perspective transformations to the model, including returning a completely separate model. This method is added by NeoForge as a replacement for the vanilla `getTransforms()` method, which only allows you to customize the transforms themselves, but not the way they are applied. However, `applyTransform`'s default implementation defers to `getTransforms`, so if you only need custom transforms, you can also override `getTransforms` and be done with it. `applyTransforms` offers three parameters: +`applyTransform` 允许在应用模型的透视变换时应用自定义逻辑,包括返回完全不同的模型。此方法由 NeoForge 添加,替代了 vanilla 的 `getTransforms()` 方法,后者只允许您自定义变换本身,而不能自定义应用方式。然而,`applyTransform` 的默认实现遵循 `getTransforms`,所以如果您只需要自定义变换,也可以重写 `getTransforms` 并完成它。`applyTransforms` 提供三个参数: -- An `ItemDisplayContext`: The [perspective] the model is being transformed to. -- A `PoseStack`: The pose stack used for rendering. -- A `boolean`: Whether to use modified values for left-hand rendering instead of the default right hand rendering; `true` if the rendered hand is the left hand (off hand, or main hand if left hand mode is enabled in the options) +- `ItemDisplayContext`:模型正在转换到的[透视][perspective]。 +- `PoseStack`:用于渲染的姿势堆栈。 +- `boolean`:是否使用修改后的值进行左手渲染,而不是默认的右手渲染;如果渲染的手是左手(副手,或在选项中启用 + +左手模式的主手),则为 `true`。 :::note -`applyTransform` and `getTransforms` only apply to item models. +`applyTransform` 和 `getTransforms` 仅适用于物品模型。 ::: -### Others - -Other methods in `BakedModel` that you may override and/or query include: - -| Signature | Effect | -|-------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| `TriState useAmbientOcclusion()` | Whether to use [ambient occlusion][ao] or not. Accepts a `BlockState`, `RenderType` and `ModelData` parameter and returns a `TriState` which allows not only force-disabling AO but also force-enabling AO. Has two overloads that each return a `boolean` parameter and accept either only a `BlockState` or no parameters at all; both of these are deprecated for removal in favor of the first variant. | -| `boolean isGui3d()` | Whether this model renders as 3d or flat in GUI slots. | -| `boolean usesBlockLight()` | Whether to use 3D lighting (`true`) or flat lighting from the front (`false`) when lighting the model. | -| `boolean isCustomRenderer()` | If true, skips normal rendering and calls an associated [`BlockEntityWithoutLevelRenderer`][bewlr]'s `renderByItem` method instead. If false, renders through the default renderer. | -| `ItemOverrides getOverrides()` | Returns the [`ItemOverrides`][itemoverrides] associated with this model. This is only relevant on item models. | -| `ModelData getModelData(BlockAndTintGetter, BlockPos, BlockState, ModelData)` | Returns the model data to use for the model. This method is passed an existing `ModelData` that is either the result of `BlockEntity#getModelData()` if the block has an associated block entity, or `ModelData.EMPTY` if that is not the case. This method can be used for blocks that need model data, but do not have a block entity, for example for blocks with connected textures. | -| `TextureAtlasSprite getParticleIcon(ModelData)` | Returns the particle sprite to use for the model. May use the model data to use different particle sprites for different model data values. NeoForge-added, replacing the vanilla `getParticleIcon()` overload with no parameters. | -| `ChunkRenderTypeSet getRenderTypes(BlockState, RandomSource, ModelData)` | Returns a `ChunkRenderTypeSet` containing the render type(s) to use for rendering the block model. A `ChunkRenderTypeSet` is a set-backed ordered `Iterable`. By default falls back to [getting the render type from the model JSON][rendertype]. Only used for block models, item models use the overload below. | -| `List getRenderTypes(ItemStack, boolean)` | Returns a `List` containing the render type(s) to use for rendering the item model. By default falls back to the normal model-bound render type lookup, which always yields a list with one element. Only used for item models, block models use the overload above. | - -## Perspectives - -Minecraft's render engine recognizes a total of 8 perspective types (9 if you include the in-code fallback) for item rendering. These are used in a model JSON's `display` block, and represented in code through the `ItemDisplayContext` enum. - -| Enum value | JSON key | Usage | -|---------------------------|---------------------------|------------------------------------------------------------------------------------------------------------------| -| `THIRD_PERSON_RIGHT_HAND` | `"thirdperson_righthand"` | Right hand in third person (F5 view, or on other players) | -| `THIRD_PERSON_LEFT_HAND` | `"thirdperson_lefthand"` | Left hand in third person (F5 view, or on other players) | -| `FIRST_PERSON_RIGHT_HAND` | `"firstperson_righthand"` | Right hand in first person | -| `FIRST_PERSON_LEFT_HAND` | `"firstperson_lefthand"` | Left hand in first person | -| `HEAD` | `"head"` | When in a player's head armor slot (often only achievable via commands) | -| `GUI` | `"gui"` | Inventories, player hotbar | -| `GROUND` | `"ground"` | Dropped items; note that the rotation of the dropped item is handled by the dropped item renderer, not the model | -| `FIXED` | `"fixed"` | Item frames | -| `NONE` | `"none"` | Fallback purposes in code, should not be used in JSON | +### 其他 + +您可能会重写和/或查询的其他 `BakedModel` 方法包括: + +| 签名 | 效果 | +|---|---| +| `TriState useAmbientOcclusion()` | 是否使用[环境光遮蔽][ao]。接受 `BlockState`、`RenderType` 和 `ModelData` 参数,并返回 `TriState`,这不仅允许强制禁用 AO,还允许强制启用 AO。有两个重载,每个都返回一个 `boolean` 参数,并只接受 `BlockState` 或无参数;这两个都已弃用,将被第一个变体取代。 | +| `boolean isGui3d()` | 此模型在 GUI 插槽中是否渲染为 3D 或平面。 | +| `boolean usesBlockLight()` | 在照亮模型时是否使用 3D 照明(`true`)或正面的平面照明(`false`)。 | +| `boolean isCustomRenderer()` | 如果为真,跳过正常渲染并调用关联的 [`BlockEntityWithoutLevelRenderer`][bewlr] 的 `renderByItem` 方法。如果为假,则通过默认渲染器渲染。 | +| `ItemOverrides getOverrides()` | 返回与此模型相关的 [`ItemOverrides`][itemoverrides]。这仅在物品模型中相关。 | +| `ModelData getModelData(BlockAndTintGetter, BlockPos, BlockState, ModelData)` | 返回用于模型的模型数据。此方法传递一个现有的 `ModelData`,如果方块有关联的方块实体,则为 `BlockEntity#getModelData()` 的结果,如果不是这种情况,则为 `ModelData.EMPTY`。此方法可用于需要模型数据但没有方块实体的方块,例如具有连接纹理的方块。 | +| `TextureAtlasSprite getParticleIcon(ModelData)` | 返回用于模型的粒子精灵。可以使用模型数据为不同的模型数据值使用不同的粒子精灵。NeoForge 添加,替换了没有参数的 vanilla `getParticleIcon()` 重载。 | +| `ChunkRenderTypeSet getRenderTypes(BlockState, RandomSource, ModelData)` | 返回包含用于渲染方块模型的渲染类型的 `ChunkRenderTypeSet`。`ChunkRenderTypeSet` 是一个支持集合的有序 `Iterable`。默认回退到从模型 JSON [获取渲染类型][rendertype]。仅用于方块模型,物品模型使用下面的重载。 | +| `List getRenderTypes(ItemStack, boolean)` | 返回包含用于渲染物品模型的渲染类型的 `List`。默认回退到正常的模型绑定渲染类型查找,这总是产生一个元素的列表。仅用于物品模型,方块模型使用上面的重载。 | + +## 透视 + +Minecraft 的渲染引擎总共识别 8 种透视类型(如果包括代码中的回退,则为 9 种)用于物品渲染。这些在模型 JSON 的 `display` 块中使用,并在代码中通过 `ItemDisplayContext` 枚举表示。 + +| 枚举值 | JSON 键 | 用途 | +|---|---|---| +| `THIRD_PERSON_RIGHT_HAND` | `"thirdperson_righthand"` | 第三人称右手(F5 视图,或其他玩家上) | +| `THIRD_PERSON_LEFT_HAND` | `"thirdperson_lefthand"` | 第三人称左手(F5 视图,或其他玩家上) | +| `FIRST_PERSON_RIGHT_HAND` | `"firstperson_righthand"` | 第一人称右手 | +| `FIRST_PERSON_LEFT_HAND` | `"firstperson_lefthand"` | 第一人 + +称左手 | +| `HEAD` | `"head"` | 玩家头部装备槽中(通常只能通过命令实现) | +| `GUI` | `"gui"` | 背包,玩家快捷栏 | +| `GROUND` | `"ground"` | 掉落物;注意,掉落物的旋转是由掉落物渲染器处理的,而非模型 | +| `FIXED` | `"fixed"` | 物品框 | +| `NONE` | `"none"` | 代码中的回退用途,不应在 JSON 中使用 | ## `ItemOverrides` -`ItemOverrides` is a class that provides a way for baked models to process the state of an [`ItemStack`][itemstack] and return a new baked model through the `#resolve` method. `#resolve` has five parameters: +`ItemOverrides` 是一个类,提供了一种方式让烘焙模型处理 [`ItemStack`][itemstack] 的状态并通过 `#resolve` 方法返回新的烘焙模型。`#resolve` 有五个参数: -- A `BakedModel`: The original model. -- An `ItemStack`: The item stack being rendered. -- A `ClientLevel`: The level the model is being rendered in. This should only be used for querying the level, not mutating it in any way. May be null. -- A `LivingEntity`: The entity the model is rendered on. May be null, e.g. when rendering from a [block entity renderer][ber]. -- An `int`: A seed for randomizing. +- `BakedModel`:原始模型。 +- `ItemStack`:正在渲染的物品堆栈。 +- `ClientLevel`:模型正在其中渲染的级别。这应该只用于查询级别,不得以任何方式修改。可能为空。 +- `LivingEntity`:模型渲染在其上的实体。可能为空,例如在[方块实体渲染器][ber]中渲染时。 +- `int`:用于随机化的种子。 -`ItemOverrides` also hold the model's override options as `BakedOverride`s. An object of `BakedOverride` is an in-code representation of a model's [`overrides`][overrides] block. It can be used by baked models to return different models depending on its contents. A list of all `BakedOverride`s of an `ItemOverrides` instance can be retrieved through `ItemOverrides#getOverrides()`. +`ItemOverrides` 还持有模型的覆盖选项作为 `BakedOverride`。`BakedOverride` 的对象是模型的 [`overrides`][overrides] 块在代码中的表示形式。它可以由烘焙模型使用,根据其内容返回不同的模型。可以通过 `ItemOverrides#getOverrides()` 检索 `ItemOverrides` 实例的所有 `BakedOverride` 列表。 ## `BakedModelWrapper` -A `BakedModelWrapper` can be used to modify an already existing `BakedModel`. `BakedModelWrapper` is a subclass of `BakedModel` that accepts another `BakedModel` (the "original" model) in the constructor and by default redirects all methods to the original model. Your implementation can then override only select methods, like so: +`BakedModelWrapper` 可用于修改已存在的 `BakedModel`。`BakedModelWrapper` 是 `BakedModel` 的一个子类,它在构造函数中接受另一个 `BakedModel`(“原始”模型),并默认将所有方法重定向到原始模型。然后,您的实现可以选择只覆盖某些方法,如下所示: ```java -// The generic parameter may optionally be a more specific subclass of BakedModel. -// If it is, the constructor parameter must match that type. +// 泛型参数可以是 BakedModel 的更具体的子类。 +// 如果是这样,构造参数必须匹配该类型。 public class MyBakedModelWrapper extends BakedModelWrapper { - // Pass the original model to super. + // 将原始模型传递给 super。 public MyBakedModelWrapper(BakedModel originalModel) { super(originalModel); } - // Override whatever methods you want here. You may also access originalModel if needed. + // 在这里覆盖您想要的方法。如果需要,也可以访问 originalModel。 } ``` -After writing your model wrapper class, you must apply the wrappers to the models it should affect. Do so in a [client-side][sides] [event handler][event] for `ModelEvent.ModifyBakingResult`: +编写您的模型包装类后,必须将包装器应用于它应影响的模型。在[客户端][sides]的[事件处理器][event]中为 `ModelEvent.ModifyBakingResult` 这样做: ```java @SubscribeEvent public static void modifyBakingResult(ModelEvent.ModifyBakingResult event) { - // For block models + // 对于方块模型 event.getModels().computeIfPresent( - // The model resource location of the model to modify. Get it from - // BlockModelShaper#stateToModelLocation with the blockstate to be affected as a parameter. + // 要修改的模型的模型资源位置。从 + // BlockModelShaper#stateToModelLocation 获取,参数为受影响的方块状态。 BlockModelShaper.stateToModelLocation(MyBlocksClass.EXAMPLE_BLOCK.defaultBlockState()), - // A BiFunction with the location and the original models as parameters, returning the new model. + // 一个带有位置和原始模型为参数的 BiFunction,返回新模型。 (location, model) -> new MyBakedModelWrapper(model); ); - // For item models + // 对于物品模型 event.getModels().computeIfPresent( - // The model resource location of the model to modify. + // 要修改的模型的模型资源位置。 new ModelResourceLocation("examplemod", "example_item", "inventory"), - // A BiFunction with the location and the original models as parameters, returning the new model. + // 一个带有位置和原始模型为参数的 BiFunction,返回新模型。 (location, model) -> new MyBakedModelWrapper(model); ); } ``` :::warning -It is generally encouraged to use a [custom model loader][modelloader] over wrapping baked models in `ModelEvent.ModifyBakingResult` when possible. Custom model loaders can also use `BakedModelWrapper`s if needed. +通常建议在可能的情况下使用[自定义模型加载器][modelloader + +]而不是在 `ModelEvent.ModifyBakingResult` 中包装烘焙模型。如果需要,自定义模型加载器也可以使用 `BakedModelWrapper`。 ::: -[ao]: https://en.wikipedia.org/wiki/Ambient_occlusion +[ao]: https://zh.wikipedia.org/wiki/环境光遮蔽 [ber]: ../../../blockentities/ber.md [bewlr]: ../../../items/bewlr.md [blockstate]: ../../../blocks/states.md From eb7fa0c5cb12a75dd8f8312d2dced11743a474a2 Mon Sep 17 00:00:00 2001 From: DaZiDian <113085496+DaZiDian@users.noreply.github.com> Date: Thu, 25 Apr 2024 21:34:00 +0800 Subject: [PATCH 71/87] Update datagen.md --- docs/resources/client/models/datagen.md | 278 ++++++++++++------------ 1 file changed, 143 insertions(+), 135 deletions(-) diff --git a/docs/resources/client/models/datagen.md b/docs/resources/client/models/datagen.md index a80d59719..7d084a943 100644 --- a/docs/resources/client/models/datagen.md +++ b/docs/resources/client/models/datagen.md @@ -1,53 +1,55 @@ -# Model Datagen +# 模型数据生成 -Like most JSON data, block and item models can be [datagenned][datagen]. Since some things are common between item and block models, so is some of the datagen code. +与大多数JSON数据一样,方块和物品模型可以通过[数据生成][datagen]创建。由于物品和方块模型之间有一些共同之处,因此部分数据生成代码也是相同的。 -## Model Datagen Classes +## 模型数据生成类 ### `ModelBuilder` -Every model starts out as a `ModelBuilder` of some sort - usually a `BlockModelBuilder` or an `ItemModelBuilder`, depending on what you are generating. It contains all the properties of the model: its parent, its textures, its elements, its transforms, its loader, etc. Each of the properties can be set by a method: +每个模型都以某种形式的`ModelBuilder`开始 - 通常是`BlockModelBuilder`或`ItemModelBuilder`,具体取决于您要生成的内容。它包含模型的所有属性:其父级、纹理、元素、变换、加载器等。每个属性都可以通过一个方法设置: -| Method | Effect | -|--------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| `#texture(String key, ResourceLocation texture)` | Adds a texture variable with the given key and the given texture location. Has an overload where the second parameter is a `String`. | -| `#renderType(ResourceLocation renderType)` | Sets the render type. Has an overload where the parameter is a `String`. For a list of valid values, see the `RenderType` class. | -| `#ao(boolean ao)` | Sets whether to use [ambient occlusion][ao] or not. | -| `#guiLight(GuiLight light)` | Sets the GUI light. May be `GuiLight.FRONT` or `GuiLight.SIDE`. | -| `#element()` | Adds a new `ElementBuilder` (equivalent to adding a new [element][elements] to the model). Returns said `ElementBuilder` for further modification. | -| `#transforms()` | Returns the builder's `TransformVecBuilder`, used for setting the `display` on a model. | -| `#customLoader(BiFunction customLoaderFactory)` | Using the given factory, makes this model use a [custom loader][custommodelloader], and thus, a custom loader builder. This changes the builder type, and as such may use different methods, depending on the loader's implementation. NeoForge provides a few custom loaders out of the box, see the linked article for more info (including datagen). | +| 方法 | 效果 | +|----------------------------------------------------|----------------------------------------------------------------------------------------------------------| +| `#texture(String key, ResourceLocation texture)` | 添加具有给定键和给定纹理位置的纹理变量。有一个重载,其中第二个参数是`String`。 | +| `#renderType(ResourceLocation renderType)` | 设置渲染类型。有一个参数为`String`的重载。有效值的列表见`RenderType`类。 | +| `#ao(boolean ao)` | 设置是否使用[环境光遮蔽][ao]。 | +| `#guiLight(GuiLight light)` | 设置GUI光源。可以是`GuiLight.FRONT`或`GuiLight.SIDE`。 | +| `#element()` | 添加一个新的`ElementBuilder`(相当于向模型添加一个新[element][elements])。返回该`ElementBuilder`以便进一步修改。| +| `#transforms()` | 返回构建器的`TransformVecBuilder`,用于设置模型的`display`。 | +| `#customLoader(BiFunction customLoaderFactory)` | 使用给定的工厂使该模型使用[自定义加载器][custommodelloader],因此使用自定义加载器构建器。这会改变构建器类型,因此可能会使用不同的方法,这取决于加载器的实现。NeoForge提供了一些开箱即用的自定义加载器,详见链接文章(包括数据生成)。| :::tip -While elaborate and complex models can be created through datagen, it is recommended to instead use modeling software such as [Blockbench][blockbench] to create more complex models and then have the exported models be used, either directly or as parents for other models. +虽然可以通过数据生成创建复杂和详细的模型,但建议使用如[Blockbench][blockbench]之类的建模软件创建更复杂的模型,然后直接使用导出的模型或作为其他模型的父级。 ::: ### `ModelProvider` -Both block and item model datagen utilize subclasses of `ModelProvider`, named `BlockModelProvider` and `ItemModelProvider`, respectively. While item model datagen directly extends `ItemModelProvider`, block model datagen uses the `BlockStateProvider` base class, which has an internal `BlockModelProvider` that can be accessed via `BlockStateProvider#models()`. Additionally, `BlockStateProvider` also has its own internal `ItemModelProvider`, accessible via `BlockStateProvider#itemModels()`. The most important part of `ModelProvider` is the `getBuilder(String path)` method, which returns a `BlockModelBuilder` (or `ItemModelBuilder`) at the given location. +方块和物品模型数据生成都利用了`ModelProvider`的子类,分别命名为`BlockModelProvider`和`ItemModelProvider`。虽然物品模型数据生成直接扩展`ItemModelProvider`,但方块模型数据生成使用`BlockStateProvider`基类,它内部有一个可以通过`BlockStateProvider#models()`访问的`BlockModelProvider`。此外,`BlockStateProvider`还有自己的内部`ItemModelProvider`,可通过`BlockStateProvider#itemModels()`访问。`ModelProvider`最重要的部分是`getBuilder(String path)`方法,它返回给定位置的`BlockModelBuilder`(或`ItemModelBuilder`)。 -However, `ModelProvider` also contains various helper methods. The most important helper method is probably `withExistingParent(String name, ResourceLocation parent)`, which returns a new builder (via `getBuilder(name)`) and sets the given `ResourceLocation` as model parent. Two other very common helpers are `mcLoc(String name)`, which returns a `ResourceLocation` with the namespace `minecraft` and the given name as path, and `modLoc(String name)`, which does the same but with the provider's mod id (so usually your mod id) instead of `minecraft`. Furthermore, it provides various helper methods that are shortcuts for `#withExistingParent` for common things such as slabs, stairs, fences, doors, etc. +然而,`ModelProvider`还包含各种辅助方法。可能最重要的辅助方法是`withExistingParent(String name, ResourceLocation parent)`,它返回一个新的构建器(通过`getBuilder(name)`)并将给定的`ResourceLocation`设置为模型父级。另外两个非常常见的辅助器是`mcLoc(String name)`,返回带有命名空间`minecraft`和给定路径的`ResourceLocation`,以及`modLoc(String name)`,做同样的事情但使用提供者的mod id(通常是您的mod id)而不是`minecraft`。此外,它还提供了各种辅助方法,这些方法是`#withExistingParent`的快捷方式,用于常见事物如板条、楼梯、栅栏、门等。 ### `ModelFile` -Finally, the last important class is `ModelFile`. A `ModelFile` is an in-code representation of a model JSON on disk. `ModelFile` is an abstract class and has two inner subclasses `ExistingModelFile` and `UncheckedModelFile`. An `ExistingModelFile`'s existence is verified using an `ExistingFileHelper`, while an `UncheckedModelFile` is assumed to be existent without further checking. In addition, a `ModelBuilder` is considered to be a `ModelFile` as well. +最后一个重要的类是`ModelFile`。`ModelFile`是 -## Block Model Datagen +磁盘上模型JSON的代码表示形式。`ModelFile`是一个抽象类,有两个内部子类`ExistingModelFile`和`UncheckedModelFile`。使用`ExistingFileHelper`验证`ExistingModelFile`的存在,而`UncheckedModelFile`被假定为存在而无需进一步检查。此外,`ModelBuilder`也被视为`ModelFile`。 -Now, to actually generate blockstate and block model files, extend `BlockStateProvider` and override the `registerStatesAndModels()` method. Note that block models will always be placed in the `models/block` subfolder, but references are relative to `models` (i.e. they must always be prefixed with `block/`). In most cases, it makes sense to choose from one of the many predefined helper methods: +## 方块模型数据生成 + +现在,要实际生成方块状态和方块模型文件,请扩展`BlockStateProvider`并重写`registerStatesAndModels()`方法。请注意,方块模型总是放置在`models/block`子文件夹中,但引用相对于`models`(即它们必须总是以`block/`为前缀)。在大多数情况下,选择众多预定义辅助方法之一是有意义的: ```java public class MyBlockStateProvider extends BlockStateProvider { - // Parameter values are provided by GatherDataEvent. + // 参数值由GatherDataEvent提供。 public MyBlockStateProvider(PackOutput output, ExistingFileHelper existingFileHelper) { - // Replace "examplemod" with your own mod id. + // 用您自己的mod id替换"examplemod"。 super(output, "examplemod", existingFileHelper); } @Override protected void registerStatesAndModels() { - // Placeholders, their usages should be replaced with real values. See above for how to use the model builder, - // and below for the helpers the model builder offers. + // 占位符,其用法应替换为实际值。请参阅上文了解如何使用模型构建器, + // 以及下文了解模型构建器提供的辅助方法。 ModelFile exampleModel = models().withExistingParent("minecraft:block/cobblestone"); Block block = MyBlocksClass.EXAMPLE_BLOCK.get(); ResourceLocation exampleTexture = modLoc("block/example_texture"); @@ -56,91 +58,95 @@ public class MyBlockStateProvider extends BlockStateProvider { ResourceLocation sideTexture = modLoc("block/example_texture_front"); ResourceLocation frontTexture = modLoc("block/example_texture_front"); - // Create a simple block model with the same texture on each side. - // The texture must be located at assets//textures/block/.png, where - // and are the block's registry name's namespace and path, respectively. - // Used by the majority of (full) blocks, such as planks, cobblestone or bricks. + // 创建一个简单的方块模型,每个面都使用相同的纹理。 + // 纹理必须位于assets//textures/block/.png, + // 其中分别是方块的注册名的命名空间和路径。 + // 用于大多数(完整)方块,如木板、圆石或砖块。 simpleBlock(block); - // Overload that accepts a model file to use. + // 接受要使用的模型文件的重载。 simpleBlock(block, exampleModel); - // Overload that accepts one or multiple (vararg) ConfiguredModel objects. - // See below for more info about ConfiguredModel. + // 接受一个或多个(变量参数)ConfiguredModel对象的重载。 + // 有关ConfiguredModel的更多信息,请参见下文。 simpleBlock(block, ConfiguredModel.builder().build()); - // Adds an item model file with the block's name, parenting the given model file, for a block item to pick up. + // 添加一个带有方块名称的物品模型文件,以给定的模型文件为父级,供方块物品使用。 simpleBlockItem(block, exampleModel); - // Shorthand for calling #simpleBlock() (model file overload) and #simpleBlockItem. + // 调用#simpleBlock()(模型文件重载)和#simpleBlockItem的简写。 simpleBlockWithItem(block, exampleModel); - // Adds a log block model. Requires two textures at assets//textures/block/.png and - // assets//textures/block/_top.png, referencing the side and top texture, respectively. - // Note that the block input here is limited to RotatedPillarBlock, which is the class vanilla logs use. + // 添加一个木材方块模型。需要两个纹理,位于assets//textures/block/.png和 + // assets//textures/block/_top.png,分别引用侧面和顶部纹理。 + // 请注意,这里的方块输入仅限于RotatedPillarBlock,这是原版木材使用的类。 logBlock(block); - // Like #logBlock, but the textures are named _side.png and _end.png instead of - // .png and _top.png, respectively. Used by quartz pillars and similar blocks. - // Has an overload that allow you to specify a different texture base name, that is then suffixed - // with _side and _end as needed, an overload that allows you to specify two resource locations - // for the side and end textures, and an overload that allows specifying side and end model files. + // 类似于#logBlock,但纹理命名为_side.png和_end.png而不是 + // .png和_top.png。由石英柱和类似方块使用。 + // 有一个重载允许您指定不同的纹理基本名称,然后根据需要后缀为_side和_end, + // 一个重载允许您指定两个资源位置 + // 为侧面和端部纹理,以及一个重载允许指定侧面和端部模型文件。 axisBlock(block); - // Variants of #logBlock and #axisBlock that additionally allow for render types to be specified. - // Comes in string and resource location variants for the render type, - // in all combinations with all variants of #logBlock and #axisBlock. + // #logBlock和#axisBlock的变体,另外允 + +许指定渲染类型。 + // 有字符串和资源位置变体用于渲染类型, + // 与#logBlock和#axisBlock的所有变体结合使用。 logBlockWithRenderType(block, "minecraft:cutout"); axisBlockWithRenderType(block, mcLoc("cutout_mipped")); - // Specifies a horizontally-rotatable block model with a side texture, a front texture, and a top texture. - // The bottom will use the side texture as well. If you don't need the front or top texture, - // just pass in the side texture twice. Used by e.g. furnaces and similar blocks. + // 指定一个具有侧面纹理、前面纹理和顶部纹理的水平可旋转方块模型。 + // 底部将使用侧面纹理。如果不需要前面或顶部纹理, + // 只需传入侧面纹理两次。例如,用于熔炉和类似方块。 horizontalBlock(block, sideTexture, frontTexture, topTexture); - // Specifies a horizontally-rotatable block model with a model file that will be rotated as needed. - // Has an overload that instead of a model file accepts a Function, - // allowing for different rotations to use different models. Used e.g. by the stonecutter. + // 指定一个将根据需要旋转的模型文件的水平可旋转方块模型。 + // 有一个重载,而不是模型文件接受一个Function, + // 允许不同的旋转使用不同的模型。例如,用于石切机。 horizontalBlock(block, exampleModel); - // Specifies a horizontally-rotatable block model that is attached to a face, e.g. for buttons or levers. - // Accounts for placing the block on the ground and on the ceiling, and rotates them accordingly. - // Like #horizontalBlock, has an overload that accepts a Function instead. + // 指定一个附着在面上的水平可旋转方块模型,例如按钮或拉杆。 + // 考虑到在地面和天花板上放置方块,并相应旋转它们。 + // 像#horizontalBlock一样,有一个重载接受一个Function。 horizontalFaceBlock(block, exampleModel); - // Similar to #horizontalBlock, but for blocks that are rotatable in all directions, including up and down. - // Again, has an overload that accepts a Function instead. + // 类似于#horizontalBlock,但用于可向上和向下旋转的方块。 + // 同样,有一个重载接受一个Function。 directionalBlock(block, exampleModel); } } ``` -Additionally, helpers for the following common block models exist in `BlockStateProvider`: - -- Stairs -- Slabs -- Buttons -- Pressure Plates -- Signs -- Fences -- Fence Gates -- Walls -- Panes -- Doors -- Trapdoors - -In some cases, the blockstates don't need special casing, but the models do. For this case, the `BlockModelProvider`, accessible via `BlockStateProvider#models()`, provides a few additional helpers, all of which accept a name as the first parameter and most of which are in some way related to full cubes. They will typically be used as model file parameters for e.g. `simpleBlock`. The helpers include supporting methods for the ones in `BlockStateProvider`, as well as: - -- `withExistingParent`: Already mentioned before, this method returns a new model builder with the given parent. The parent must either already exist or be created before the model. -- `getExistingFile`: Performs a lookup in the model provider's `ExistingFileHelper`, returning the corresponding `ModelFile` if present and throwing an `IllegalStateException` otherwise. -- `singleTexture`: Accepts a parent and a single texture location, returning a model with the given parent, and with the texture variable `texture` set to the given texture location. -- `sideBottomTop`: Accepts a parent and three texture locations, returning a model with the given parent and the side, bottom and top textures set to the three texture locations. -- `cube`: Accepts six texture resource locations for the six sides, returning a full cube model with the six sides set to the six textures. -- `cubeAll`: Accepts a texture location, returning a full cube model with the given texture applied to all six sides. A mix between `singleTexture` and `cube`, if you will. -- `cubeTop`: Accepts two texture locations, returning a full cube model with the first texture applied to the sides and the bottom, and the second texture applied to the top. -- `cubeBottomTop`: Accepts three texture locations, returning a full cube model with the side, bottom and top textures set to the three texture locations. A mix between `cube` and `sideBottomTop`, if you will. -- `cubeColumn` and `cubeColumnHorizontal`: Accepts two texture locations, returning a "standing" or "laying" pillar cube model with the side and end textures set to the two texture locations. Used by `BlockStateProvider#logBlock`, `BlockStateProvider#axisBlock` and their variants. -- `orientable`: Accepts three texture locations, returning a cube with a "front" texture. The three texture locations are the side, front and top texture, respectively. -- `orientableVertical`: Variant of `orientable` that omits the top parameter, instead using the side parameter as well. -- `orientableWithBottom`: Variant of `orientable` that has a fourth parameter for a bottom texture between the front and top parameter. -- `crop`: Accepts a texture location, returning a crop-like model with the given texture, as used by the four vanilla crops. -- `cross`: Accepts a texture location, returning a cross model with the given texture, as used by flowers, saplings and many other foliage blocks. -- `torch`: Accepts a texture location, returning a torch model with the given texture. -- `wall_torch`: Accepts a texture location, returning a wall torch model with the given texture (wall torches are separate blocks from standing torches). -- `carpet`: Accepts a texture location, returning a carpet model with the given texture. - -Finally, don't forget to register your block state provider to the event: +另外,`BlockStateProvider`中存在以下常见方块模型的辅助方法: + +- 楼梯 +- 板条 +- 按钮 +- 压力板 +- 标志 +- 栅栏 +- 栅栏门 +- 墙 +- 窗格 +- 门 +- 活板门 + +在某些情况下,方块状态不需要特殊处理,但模型需要。在这种情况下,可通过`BlockStateProvider#models()`访问的`BlockModelProvider`提供了一些额外的辅助方法,所有这些方法都接受第一个参数为名称,并且大多数与完整立方体有关。它们通常用作例如`simpleBlock`的模型文件参数。辅助方法包括支持`BlockStateProvider`中的方法,以及: + +- `withExistingParent`: 前面已经提到,此方法返回一个带有给定父级的新模型构建器。父级必须已经存在或在模型之前创建。 +- `getExistingFile`: 在模型提供者的`ExistingFileHelper`中执行查找,如果存在则返回相应的`ModelFile`,否则抛出`IllegalStateException`。 +- `singleTexture`: 接受一个父级和一个纹理位置,返回一个带有给定父级的模型,并将纹理变量`texture`设置为给定的纹理位置。 +- `sideBottomTop`: 接受一个父级和三个纹理位置,返回一个模型,其侧面、底部和顶部纹理设置为三个纹理位置。 +- `cube`: 接受六个纹理资源位置,分别用于六个面,返回一个完整立方体模型,其六个面设置为六个纹理。 +- `cubeAll`: 接受一个纹理位置,返回一个完整立方体模型,将给定纹理应用于所有六个面。如果愿意,可以将其视为`singleTexture`和`cube`的混合体。 +- `cubeTop`: 接受两个纹理位置,返回一个完整立方体模型,第一个纹理 + +应用于侧面和底部,第二个纹理应用于顶部。 +- `cubeBottomTop`: 接受三个纹理位置,返回一个完整立方体模型,其侧面、底部和顶部纹理设置为三个纹理位置。如果愿意,可以将其视为`cube`和`sideBottomTop`的混合体。 +- `cubeColumn`和`cubeColumnHorizontal`: 接受两个纹理位置,返回一个“立立”或“横卧”的柱状立方体模型,其侧面和端部纹理设置为两个纹理位置。由`BlockStateProvider#logBlock`、`BlockStateProvider#axisBlock`及其变体使用。 +- `orientable`: 接受三个纹理位置,返回一个带有“前面”纹理的立方体。这三个纹理位置分别是侧面、前面和顶部纹理。 +- `orientableVertical`: `orientable`的变体,省略了顶部参数,改为使用侧面参数。 +- `orientableWithBottom`: `orientable`的变体,其具有一个在前面和顶部参数之间的底部纹理的第四参数。 +- `crop`: 接受一个纹理位置,返回一个带有给定纹理的类似作物的模型,如四种原版作物所使用的。 +- `cross`: 接受一个纹理位置,返回一个带有给定纹理的十字模型,如花、树苗和许多其他植被方块所使用的。 +- `torch`: 接受一个纹理位置,返回一个带有给定纹理的火把模型。 +- `wall_torch`: 接受一个纹理位置,返回一个带有给定纹理的壁挂火把模型(壁挂火把是与立火把不同的方块)。 +- `carpet`: 接受一个纹理位置,返回一个带有给定纹理的地毯模型。 + +最后,别忘了在事件中注册您的方块状态提供者: ```java @SubscribeEvent @@ -149,7 +155,7 @@ public static void gatherData(GatherDataEvent event) { PackOutput output = generator.getPackOutput(); ExistingFileHelper existingFileHelper = event.getExistingFileHelper(); - // other providers here + // 其他提供者在这里 generator.addProvider( event.includeClient(), new MyBlockStateProvider(output, existingFileHelper) @@ -159,84 +165,86 @@ public static void gatherData(GatherDataEvent event) { ### `ConfiguredModel.Builder` -If the default helpers won't do it for you, you can also directly build model objects using a `ConfiguredModel.Builder` and then use them in a `VariantBlockStateBuilder` to build a `variants` blockstate file, or in a `MultiPartBlockStateBuilder` to build a `multipart` blockstate file: +如果默认辅助工具不能满足您的需求,您也可以使用`ConfiguredModel.Builder`直接构建模型对象,然后在`VariantBlockStateBuilder`中使用它们构建`variants`块状态文件,或在`MultiPartBlockStateBuilder`中构建`multipart`块状态文件: ```java -// Create a ConfiguredModel.Builder. Alternatively, you can use one of the ways demonstrated below -// (VariantBlockStateBuilder.PartialBlockstate#modelForState or MultiPartBlockStateBuilder#part) where applicable. +// 创建一个ConfiguredModel.Builder。或者,您可以使用下面演示的方式之一 +// (VariantBlockStateBuilder.PartialBlockstate#modelForState或MultiPartBlockStateBuilder#part)在适用的情况下。 ConfiguredModel.Builder builder = ConfiguredModel.builder() -// Use a model file. As mentioned previously, can either be an ExistingModelFile, an UncheckedModelFile, -// or some sort of ModelBuilder. See above for how to use ModelBuilder. +// 使用一个模型文件。如前所述,可以是ExistingModelFile、UncheckedModelFile, +// 或某种类型的ModelBuilder。参见上文了解如何使用ModelBuilder。 .modelFile(models().withExistingParent("minecraft:block/cobblestone")) - // Set rotations around the x and y axes. + // 设置绕x轴和y轴的旋转。 .rotationX(90) .rotationY(180) - // Set a uvlock. + // 设置uv锁定。 .uvlock(true) - // Set a weight. + // 设置权重。 .weight(5); -// Build the configured model. The return type is an array -// to account for multiple possible models in the same blockstate. +// 构建配置模型。返回类型是一个数组 +// 以考虑同一块状态中可能有多个模型。 ConfiguredModel[] model = builder.build(); -// Get a variant block state builder. +// 获取一个变体块状态构建器。 VariantBlockStateBuilder variantBuilder = getVariantBuilder(MyBlocksClass.EXAMPLE_BLOCK.get()); -// Create a partial state and set properties on it. -VariantBlockStateBuilder.PartialBlockstate partialState = variantBuilder.partialState(); -// Add one or multiple models for a partial blockstate. The models are a vararg parameter. +// 创建一个部分状态并设置属性。 +VariantBlockStateBuilder.PartialBlockstate partialState = variantBuilder.partialState + +(); +// 为部分块状态添加一个或多个模型。模型是变量参数。 variantBuilder.addModels(partialState, - // Specify at least one ConfiguredModel.Builder, as seen above. Create through #modelForState(). + // 至少指定一个ConfiguredModel.Builder,如上所见。通过#modelForState创建。 partialState.modelForState() .modelFile(models().withExistingParent("minecraft:block/cobblestone")) .uvlock(true) ); -// Alternatively, forAllStates(Function) creates a model for every state. -// The passed function will be called once for each possible state. +// 或者,forAllStates(Function)为每个可能的状态创建一个模型。 +// 传递的函数将为每个可能的状态调用一次。 variantBuilder.forAllStates(state -> { - // Return a ConfiguredModel depending on the state's properties. - // For example, the following code will rotate the model depending on the horizontal rotation of the block. + // 根据状态的属性返回一个ConfiguredModel。 + // 例如,以下代码将根据方块的水平旋转旋转模型。 return ConfiguredModel.builder() .modelFile(models().withExistingParent("minecraft:block/cobblestone")) .rotationY((int) state.getValue(BlockStateProperties.HORIZONTAL_FACING).toYRot()) .build(); }); -// Get a multipart block state builder. +// 获取一个多部分块状态构建器。 MultiPartBlockStateBuilder multipartBuilder = getMultipartBuilder(MyBlocksClass.EXAMPLE_BLOCK.get()); -// Add a new part. Starts with .part() and ends with .end(). +// 添加一个新部分。从.part()开始,以.end()结束。 multipartBuilder.addPart(multipartBuilder.part() - // Step one: Build the model. multipartBuilder.part() returns a ConfiguredModel.Builder, - // meaning that all methods seen above can be used here as well. + // 步骤一:构建模型。multipartBuilder.part()返回一个ConfiguredModel.Builder, + // 意味着上面看到的所有方法都可以在这里使用。 .modelFile("minecraft:block/cobblestone") - // Call .addModel(). Now that the model is built, we can proceed to step two: add the part data. + // 调用.addModel()。现在模型已构建,我们可以进入步骤二:添加部分数据。 .addModel() - // Add a condition for the part. Requires a property - // and at least one property value; property values are a vararg. + // 为部分添加条件。需要一个属性 + // 和至少一个属性值;属性值是变量参数。 .condition(BlockStateProperties.FACING, Direction.NORTH, Direction.SOUTH) - // Set the multipart conditions to be ORed instead of the default ANDing. + // 将多部分条件设置为或运算而不是默认的与运算。 .useOr() - // Creates a nested condition group. + // 创建一个嵌套条件组。 .nestedGroup() - // Adds a condition to the nested group. + // 向嵌套组添加一个条件。 .condition(BlockStateProperties.FACING, Direction.NORTH) - // Sets only this condition group to be ORed instead of ANDed. + // 仅将这个条件组设置为或运算而不是与运算。 .useOr() - // Creates yet another nested condition group. There is no limit on how many groups can be nested. + // 创建另一个嵌套条件组。嵌套组的数量没有限制。 .nestedGroup() - // Ends the nested condition group, returning to the owning part builder or condition group level. - // Called twice here since we currently have two nested groups. + // 结束嵌套条件组,返回到拥有的部分构建器或条件组级别。 + // 这里调用两次,因为我们当前有两个嵌套组。 .endNestedGroup() .endNestedGroup() - // End the part builder and add the resulting part to the multipart builder. + // 结束部分构建器并将生成的部分添加到多部分构建器中。 .end() ); ``` -## Item Model Datagen +## 物品模型数据生成 -Generating item models is considerably simpler, which is mainly due to the fact that we operate directly on an `ItemModelProvider` instead of using an intermediate class like `BlockStateProvider`, which is of course because item models don't have an equivalent to blockstate files and are instead used directly. +生成物品模型相对简单得多,这主要是因为我们直接在`ItemModelProvider`上操作,而不是使用像`BlockStateProvider`这样的中间类,这当然是因为物品模型没有与方块状态文件等价的文件,而是直接使用。 -Similar to above, we create a class and have it extend the base provider, in this case `ItemModelProvider`. Since we are directly in a subclass of `ModelProvider`, all `models()` calls become `this` (or are omitted). +与上面类似,我们创建一个类并让它扩展基础提供者,在这种情况下是`ItemModelProvider`。由于我们直接在`ModelProvider`的子类中,所有的`models()`调用都变成了`this`(或被省略)。 ```java public class MyItemModelProvider extends ItemModelProvider { @@ -246,20 +254,20 @@ public class MyItemModelProvider extends ItemModelProvider { @Override protected void registerModels() { - // Block items generally use their corresponding block models as parent. + // 方块物品通常使用其相应的方块模型作为父级。 withExistingParent(MyItemsClass.EXAMPLE_BLOCK_ITEM.get(), modLoc("block/example_block")); - // Items generally use a simple parent and one texture. The most common parents are item/generated and item/handheld. - // In this example, the item texture would be located at assets/examplemod/textures/item/example_item.png. - // If you want a more complex model, you can use getBuilder() and then work from that, like you would with block models. - withExistingParent(MyItemsClass.EXAMPLE_ITEM.get(), mcLoc("item/generated")).texture("layer0", "item/example_item"); - // The above line is so common that there is a shortcut for it. Note that the item registry name and the - // texture path, relative to textures/item, must match. + // 物品通常使用一个简单的父级和一个纹理。最常见的父级是item/generated和item/handheld。 + // 在这个例子中,物品纹理位于assets/examplemod/textures/item/example_item.png。 + // 如果您想要一个更复杂的模型,您可以使用 getBuilder(),然后从中进行工作,就像使用块模型一样。 + withExistingParent(MyItemsClass.EXAMPLE_ITEM.get(), mcLoc("item/ generated")).texture("layer0", "item/example_item"); + // 上面的行很常见,因此有一个快捷方式。 请注意项目注册表名称和 + // 相对于纹理/项目的纹理路径必须匹配。 basicItem(MyItemsClass.EXAMPLE_ITEM.get()); } } ``` -And like all data providers, don't forget to register your provider to the event: +与所有数据提供者一样,不要忘记将您的提供者注册到该事件: ```java @SubscribeEvent From c2b148f3f84ca824a76d71126109e354dcd5d26e Mon Sep 17 00:00:00 2001 From: DaZiDian <113085496+DaZiDian@users.noreply.github.com> Date: Thu, 25 Apr 2024 21:35:45 +0800 Subject: [PATCH 72/87] Update datagen.md --- docs/resources/client/models/datagen.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/docs/resources/client/models/datagen.md b/docs/resources/client/models/datagen.md index 7d084a943..b84b30b07 100644 --- a/docs/resources/client/models/datagen.md +++ b/docs/resources/client/models/datagen.md @@ -83,9 +83,7 @@ public class MyBlockStateProvider extends BlockStateProvider { // 一个重载允许您指定两个资源位置 // 为侧面和端部纹理,以及一个重载允许指定侧面和端部模型文件。 axisBlock(block); - // #logBlock和#axisBlock的变体,另外允 - -许指定渲染类型。 + // #logBlock和#axisBlock的变体,另外允许指定渲染类型。 // 有字符串和资源位置变体用于渲染类型, // 与#logBlock和#axisBlock的所有变体结合使用。 logBlockWithRenderType(block, "minecraft:cutout"); From 290ccb9b533323c673b7d9986a84e96efac38912 Mon Sep 17 00:00:00 2001 From: DaZiDian <113085496+DaZiDian@users.noreply.github.com> Date: Wed, 8 May 2024 15:06:01 +0800 Subject: [PATCH 73/87] Update index.md --- docs/resources/client/models/index.md | 237 +++++++++++++------------- 1 file changed, 120 insertions(+), 117 deletions(-) diff --git a/docs/resources/client/models/index.md b/docs/resources/client/models/index.md index 968b8e4d1..04f3b3324 100644 --- a/docs/resources/client/models/index.md +++ b/docs/resources/client/models/index.md @@ -1,104 +1,106 @@ -# Models - -Models are JSON files that determine the visual shape and texture(s) of a block or item. A model consists of cuboid elements, each with their own size, that then get assigned a texture to each face. - -Each item gets an item model assigned to it by its registry name. For example, an item with the registry name `examplemod:example_item` would get the model at `assets/examplemod/models/item/example_item.json` assigned to it. For blocks, this is a bit more complicated, as they get assigned a blockstate file first. See [below][bsfile] for more information. - -## Specification - -_See also: [Model][mcwikimodel] on the [Minecraft Wiki][mcwiki]_ - -A model is a JSON file with the following optional properties in the root tag: - -- `loader`: NeoForge-added. Sets a custom model loader. See [Custom Model Loaders][custommodelloader] for more information. -- `parent`: Sets a parent model, in the form of a [resource location][rl] relative to the `models` folder. All parent properties will be applied and then overridden by the properties set in the declaring model. Common parents include: - - `minecraft:block/block`: The common parent of all block models. - - `minecraft:block/cube`: Parent of all models that use a 1x1x1 cube model. - - `minecraft:block/cube_all`: Variant of the cube model that uses the same texture on all six sides, for example cobblestone or planks. - - `minecraft:block/cube_bottom_top`: Variant of the cube model that uses the same texture on all four horizontal sides, and separate textures on the top and the bottom. Common examples include sandstone or chiseled quartz. - - `minecraft:block/cube_column`: Variant of the cube model that has a side texture and a bottom and top texture. Examples include wooden logs, as well as quartz and purpur pillars. - - `minecraft:block/cross`: Model that uses two planes with the same texture, one rotated 45° clockwise and the other rotated 45° counter-clockwise, forming an X when viewed from above (hence the name). Examples include most plants, e.g. grass, saplings and flowers. - - `minecraft:item/generated`: Parent for classic 2D flat item models. Used by most items in the game. Ignores an `elements` block since its quads are generated from the textures. - - `minecraft:item/handheld`: Parent for 2D flat item models that appear to be actually held by the player. Used predominantly by tools. Submodel of `item/generated`, which causes it to ignore the `elements` block as well. - - `minecraft:builtin/entity`: Specifies no textures other than `particle`. If this is the parent, [`BakedModel#isCustomRenderer()`][iscustomrenderer] returns `true` to allow use of a [`BlockEntityWithoutLevelRenderer`][bewlr]. - - Block items commonly (but not always) use their corresponding block models as parent. For example, the cobblestone item model uses the parent `minecraft:block/cobblestone`. -- `ambientocclusion`: Whether to enable [ambient occlusion][ao] or not. Only effective on block models. Defaults to `true`. If your custom block model has weird shading, try setting this to `false`. -- `render_type`: See [Render Types][rendertype]. -- `gui_light`: Can be `"front"` or `"side"`. If `"front"`, light will come from the front, useful for flat 2D models. If `"side"`, light will come from the side, useful for 3D models (especially block models). Defaults to `"side"`. Only effective on item models. -- `textures`: A sub-object that maps names (known as texture variables) to [texture locations][textures]. Texture variables can then be used in [elements]. They can also be specified in elements, but left unspecified in order for child models to specify them. - - Block models should additionally specify a `particle` texture. This texture is used when falling on, running across, or breaking the block. - - Item models can also use layer textures, named `layer0`, `layer1`, etc., where layers with a higher index are rendered above those with a lower index (e.g. `layer1` would be rendered above `layer0`). Only works if the parent is `item/generated`, and only works for up to 5 layers (`layer0` through `layer4`). -- `elements`: A list of cuboid [elements]. -- `overrides`: A list of [override models][overrides]. Only effective on item models. -- `display`: A sub-object that holds the different display options for different [perspectives], see linked article for possible keys. Only effective on item models, but often specified in block models so that item models can inherit the display options. Every perspective is an optional sub-object that may contain the following options, which are applied in that order: - - `translation`: The translation of the model, specified as `[x, y, z]`. - - `rotation`: The rotation of the model, specified as `[x, y, z]`. - - `scale`: The scale of the model, specified as `[x, y, z]`. - - `right_rotation`: NeoForge-added. A second rotation that is applied after scaling, specified as `[x, y, z]`. -- `transform`: See [Root Transforms][roottransforms]. +模型是JSON文件,确定方块或物品的视觉形状和纹理。模型由立方体元素组成,每个元素都有自己的大小,然后每个面都被分配一个纹理。 + +每个物品通过其注册名称被分配一个物品模型。例如,注册名称为 `examplemod:example_item` 的物品将被分配到 `assets/examplemod/models/item/example_item.json` 中的模型。对于方块来说,情况稍微复杂一些,因为它们首先被分配一个方块状态文件。更多信息请参见[下文][bsfile]。 + +## 规范 + +_另请参阅:[Minecraft Wiki][mcwiki]上的[模型][mcwikimodel]_ + +模型是一个具有以下可选属性的JSON文件: + +- `loader`:NeoForge添加的。设置自定义模型加载器。有关更多信息,请参阅[自定义模型加载器][custommodelloader]。 +- `parent`:设置父模型,格式为相对于 `models` 文件夹的[资源位置][rl]。所有父属性将首先应用,然后被声明模型中设置的属性覆盖。常见的父模型包括: + - `minecraft:block/block`:所有方块模型的通用父模型。 + - `minecraft:block/cube`:所有使用1x1x1立方体模型的模型的父模型。 + - `minecraft:block/cube_all`:使用相同纹理在所有六个面上的立方体模型变种,例如圆石或木板。 + - `minecraft:block/cube_bottom_top`:使用相同纹理在所有四个水平面上,并在顶部和底部使用单独的纹理的立方体模型变种。常见示例包括砂岩或镶嵌石英。 + - `minecraft:block/cube_column`:具有侧面纹理和底部和顶部纹理的立方体模型变种。示例包括木头原木,以及石英和紫珀柱。 + - `minecraft:block/cross`:使用两个具有相同纹理的平面,一个顺时针旋转45°,另一个逆时针旋转45°,从上方看形成X形(因此得名)。示例包括大多数植物,例如草、树苗和花朵。 + - `minecraft:item/generated`:经典的2D平面物品模型的父模型。大多数物品都使用此模型。由于其四边形是从纹理生成的,因此会忽略 `elements` 块。 + - `minecraft:item/handheld`:用于看起来实际由玩家持有的2D平面物品模型的父模型。主要由工具使用。作为 `item/generated` 的子模型,因此它也会忽略 `elements` 块。 + - `minecraft:builtin/entity`:指定除 `particle` 外没有其他纹理。如果这是父模型,则[`BakedModel#isCustomRenderer()`][iscustomrenderer]将返回 `true`,以允许使用 [`BlockEntityWithoutLevelRenderer`][bewlr]。 + - 方块物品通常(但不总是)使用其对应的方块模型作为父模型。例如,圆石物品模型使用父模型 `minecraft:block/cobblestone`。 +- `ambientocclusion`:是否启用[环境光遮蔽][ao]。仅在方块模型上有效。默认为 `true`。如果您的自定义方块模型具有奇怪的阴影,请尝试将其设置为 `false`。 +- `render_type`:参见[渲染类型][rendertype]。 +- `gui_light`:可以是 `"front"` 或 `"side"`。如果是 `"front"`,光将来自前方,对于平面2D模型很有用。如果是 `"side"`,光将来自侧面,对于3D模型(尤其是方块模型)很有用。默认为 `"side"`。仅在物品模型上有效。 +- `textures`:一个子对象,将名称(称为纹理变量)映射到[纹理位置][textures]。然后可以在[elements]中使用纹理变量。它们也可以在元素中指定,但在子模型中保留未指定。 + - 方块模型还应指定一个 `particle` 纹理。当坠落在、穿越或破坏方块时,将使用此纹理。 + - 物品模型还可以使用层纹理,命名为 `layer0`、`layer1` 等,其中具有较高索引的层会呈现在具有较低索引的层上方(例如 `layer1` 将呈现在 `layer0` 上方)。仅在父模型为 `item/generated` 时有效,最多支持5层(`layer0` 到 `layer4`)。 +- `elements`:立方体[元素]的列表。 +- `overrides`:[覆盖模型][overrides]的列表。仅在物品模型上有效。 +- `display`:包含不同[视角]的不同显示选项的子对象,请参见链接的文章以获取可能的键。仅在物品模型上有效,但通常在方块模型中指定,以便物品模型可以继承显示选项。每个视角都是一个可选的子对象,可能包含以下选项,按顺序应用: + - `translation`:模型的平移,指定为 `[x, y, z]`。 + - `rotation`:模型的旋转,指定为 `[x, y, z]`。 + - `scale`:模型的 + +缩放,指定为 `[x, y, z]`。 + - `right_rotation`:NeoForge添加的。在缩放后应用的第二个旋转,指定为 `[x, y, z]`。 +- `transform`:参见[根变换][roottransforms]。 :::tip -If you're having trouble finding out how exactly to specify something, have a look at a vanilla model that does something similar. +如果您在确定如何精确指定某些内容方面遇到困难,请查看执行类似操作的原版模型。 ::: -### Render Types +### 渲染类型 -Using the optional NeoForge-added `render_type` field, you can set a render type for your model. If this is not set (as is the case in all vanilla models), the game will fall back to the render types hardcoded in `ItemBlockRenderTypes`. If `ItemBlockRenderTypes` doesn't contain the render type for that block either, it will fall back to `minecraft:solid`. Vanilla provides the following default render types: +使用可选的 NeoForge 添加的 `render_type` 字段,您可以为模型设置渲染类型。如果未设置(如所有原版模型),游戏将退回到 `ItemBlockRenderTypes` 中硬编码的渲染类型。如果 `ItemBlockRenderTypes` 中也不存在该方块的渲染类型,它将退回到 `minecraft:solid`。原版提供以下默认渲染类型: -- `minecraft:solid`: Used for fully solid blocks, such as stone. -- `minecraft:cutout`: Used for blocks where any pixel is either fully solid or fully transparent, i.e. with either full or no transparency, for example glass. -- `minecraft:cutout_mipped`: Variant of `minecraft:cutout` that will scale down textures at large distances to avoid visual artifacts ([mipmapping]). Does not apply the mipmapping to item rendering, as it is usually undesired on items and may cause artifacts. Used for example by leaves. -- `minecraft:cutout_mipped_all`: Variant of `minecraft:cutout_mipped` which applies mipmapping to item models as well. -- `minecraft:translucent`: Used for blocks where any pixel may be partially transparent, for example stained glass. -- `minecraft:tripwire`: Used by blocks with the special requirement of being rendered to the weather target, i.e. tripwire. +- `minecraft:solid`:用于完全实心的方块,例如石头。 +- `minecraft:cutout`:用于任何像素完全实心或完全透明的方块,即具有完全不透明或完全透明的像素,例如玻璃。 +- `minecraft:cutout_mipped`:`minecraft:cutout` 的变体,将在较大距离上缩小纹理以避免视觉伪影([mipmapping])。由于通常不希望物品上使用mipmapping并且可能会导致伪影,因此不会对物品渲染应用mipmapping。例如,用于树叶。 +- `minecraft:cutout_mipped_all`:`minecraft:cutout_mipped` 的变体,将mipmapping应用于物品模型。 +- `minecraft:translucent`:用于任何像素可能部分透明的方块,例如有色玻璃。 +- `minecraft:tripwire`:用于具有被渲染到天气目标的特殊要求的方块,即绊线。 -Selecting the correct render type is a question of performance to some degree. Solid rendering is faster than cutout rendering, and cutout rendering is faster than translucent rendering. Because of this, you should specify the "strictest" render type applicable for your use case, as it will also be the fastest. +选择正确的渲染类型在某种程度上是一个性能问题。实心渲染比切割渲染快,切割渲染比半透明渲染快。因此,您应该为您的用例指定最严格的适用渲染类型,因为它也将是最快的。 -If you want, you can also add your own render types. To do so, subscribe to the [mod bus][modbus] [event] `RegisterNamedRenderTypesEvent` and `#register` your render types. `#register` has three or four parameters: +如果愿意,您也可以添加自己的渲染类型。要这样做,请订阅 [mod 总线][modbus] [事件] `RegisterNamedRenderTypesEvent` 并 `#register` 您的渲染类型。`#register` 具有三个或四个参数: -- The name of the render type. Will be prefixed with your mod id. For example, using `"my_cutout"` here will provide `examplemod:my_cutout` as a new render type for you to use (provided that your mod id is `examplemod`, of course). -- The chunk render type. Any of the types in the list returned by `RenderType.chunkBufferLayers()` can be used. -- The entity render type. Must be a render type with the `DefaultVertexFormat.NEW_ENTITY` vertex format. -- Optional: The fabulous render type. Must be a render type with the `DefaultVertexFormat.NEW_ENTITY` vertex format. Will be used instead of the regular entity render type if the graphics mode is set to _Fabulous!_. If omitted, falls back to the regular render type. Generally recommended to set if the render type uses transparency in some way. +- 渲染类型的名称。将以您的mod id作为前缀。例如,在此处使用 `"my_cutout"` 将为您提供 `examplemod:my_cutout` 作为新的可供您使用的渲染类型(前提是您的mod id为 `examplemod`)。 +- 分块渲染类型。可以使用 `RenderType.chunkBufferLayers()` 返回的列表中的任何类型。 +- 实体渲染类型。必须是具有 `DefaultVertexFormat.NEW_ENTITY` 顶点格式的渲染类型。 +- 可选项:神奇的渲染类型。必须是具有 `DefaultVertexFormat.NEW_ENTITY` 顶点格式的渲染类型。如果将图形模式设置为 _Fabulous!_,则将使用此渲染类型而不是常规实体渲染类型。如果省略,将回退到常规渲染类型。通常建议在渲染类型在某种程度上使用透明度时设置。 -### Elements +### 元素 -An element is a JSON representation of a cuboid object. It has the following properties: +元素是立方体对象的JSON表示。它具有以下属性: -- `from`: The coordinate of the start corner of the cuboid, specified as `[x, y, z]`. Specified in 1/16 block units. For example, `[0, 0, 0]` would be the "bottom left" corner, `[8, 8, 8]` would be the center, and `[16, 16, 16]` would be the "top right" corner of the block. -- `to`: The coordinate of the end corner of the cuboid, specified as `[x, y, z]`. Like `from`, this is specified in 1/16 block units. +- `from`:立方体起始角的坐标,指定为 `[x, y, z]`。以1/16方块单位指定。例如,`[0, 0, 0]` 将是“左下”角,`[8, 8, 8]` 将是中心,`[16, 16, 16]` 将是“右上”角。 +- `to`:立方体结束角的坐标,指定为 `[x, y, z]`。与 `from` 一样,这是以1/16方块单位指定的。 :::tip -Values in `from` and `to` are limited by Minecraft to the range `[-16, 32]`. However, it is highly discouraged to exceed `[0, 16]`, as that will lead to lighting and/or culling issues. +Minecraft中的值在范围 `[-16, 32]` 内。但是,强烈不建议超过 `[0, 16]`,因为这将导致光照和/或剔除问题。 ::: -- `neoforge_data`: See [Extra Face Data][extrafacedata]. -- `faces`: An object containing data for of up to 6 faces, named `north`, `south`, `east`, `west`, `up` and `down`, respectively. Every face has the following data: - - `uv`: The uv of the face, specified as `[u1, v1, u2, v2]`, where `u1, v1` is the top left uv coordinates and `u2, v2` is the bottom right uv coordinates. - - `texture`: The texture to use for the face. Must be a texture variable prefixed with a `#`. For example, if your model had a texture named `wood`, you would use `#wood` to reference that texture. Technically optional, will use the missing texture if absent. - - `rotation`: Optional. Rotates the texture clockwise by 90, 180 or 270 degrees. - - `cullface`: Optional. Tells the render engine to skip rendering the face when there is a full block touching it in the specified direction. The direction can be `north`, `south`, `east`, `west`, `up` or `down`. - - `tintindex`: Optional. Specifies a tint index that may be used by a color handler, see [Tinting][tinting] for more information. Defaults to -1, which means no tinting. - - `neoforge_data`: See [Extra Face Data][extrafacedata]. +- `neoforge_data`:请参见[额外的面数据][extrafacedata]。 +- `faces`:包含最多6个面的数据的对象,分别命名为 `north`、`south`、`east`、`west`、`up` 和 `down`。每个面都具有以下数据: + - `uv`:面的uv,指定为 `[u1, v1, u2, v2]`,其中 `u1, v1` 是左上角uv坐标,`u2, v2` 是右下角uv坐标。 + - `texture`:面使用的纹理。必须是以 `#` 为前缀的纹理变量。例如,如果您的模型有一个名为 `wood` 的纹理,则可以使用 `#wood` 引用该纹理。在技术上是可选的,如果缺少将使用缺失的纹理。 + - `rotation`:可选。以顺时针90、180或270度旋转纹理。 + - `cullface`:可选。告诉渲染引擎在指定方向上有一个完整方块触碰时跳过渲染面。方向可以是 `north`、`south`、`east`、`west`、`up` 或 `down`。 + - `tint -Additionally, it can specify the following optional properties: +index`:可选。指定颜色处理程序可能使用的染色索引,有关更多信息,请参见[着色][tinting]。默认为-1,表示不染色。 + - `neoforge_data`:请参见[额外的面数据][extrafacedata]。 -- `shade`: Only for block models. Optional. Whether the faces of this element should have direction-dependent shading on it or not. Defaults to true. -- `rotation`: A rotation of the object, specified as a sub object containing the following data: - - `angle`: The rotation angle, in degrees. Can be -45 through 45 in steps of 22.5 degrees. - - `axis`: The axis to rotate around. It is currently not possible to rotate an object around more than one axis. - - `origin`: Optional. The origin point to rotate around, specified as `[x, y, z]`. Note that these are absolute values, i.e. they are not relative to the cube's position. If unspecified, will use `[0, 0, 0]`. +此外,它还可以指定以下可选属性: -#### Extra Face Data +- `shade`:仅适用于方块模型。可选。此元素的面是否应该有方向相关的阴影。默认为 true。 +- `rotation`:对象的旋转,指定为包含以下数据的子对象: + - `angle`:旋转角度,以度为单位。可以是 -45 到 45,步长为22.5度。 + - `axis`:围绕旋转的轴。目前无法围绕多个轴旋转对象。 + - `origin`:可选。旋转的原点,指定为 `[x, y, z]`。请注意,这些是绝对值,即它们不是相对于立方体位置的。如果未指定,将使用 `[0, 0, 0]`。 -Extra face data (`neoforge_data`) can be applied to both an element and a single face of an element. It is optional in all contexts where it is available. If both element-level and face-level extra face data is specified, the face-level data will override the element-level data. Extra data can specify the following data: +#### 额外的面数据 -- `color`: Tints the face with the given color. Must be an ARGB value. Can be specified as a string or as a decimal integer (JSON does not support hex literals). Defaults to `0xFFFFFFFF`. This can be used as a replacement for tinting if the color values are constant. -- `block_light`: Overrides the block light value used for this face. Defaults to 0. -- `sky_light`: Overrides the sky light value used for this face. Defaults to 0. -- `ambient_occlusion`: Disables or enables ambient occlusion for this face. Defaults to the value set in the model. +额外的面数据(`neoforge_data`)可以应用于元素和元素的单个面。在所有可用的上下文中,它都是可选的。如果同时指定了元素级和面级额外面数据,则面级数据将覆盖元素级数据。额外的数据可以指定以下数据: -Using the custom `neoforge:item_layers` loader, you can also specify extra face data to apply to all the geometry in an `item/generated` model. In the following example, layer 1 will be tinted red and glow at full brightness: +- `color`:使用给定颜色对面进行染色。必须是ARGB值。可以指定为字符串或十进制整数(JSON不支持十六进制文字)。默认为 `0xFFFFFFFF`。如果颜色值是恒定的,可以用作对染色的替代。 +- `block_light`:覆盖用于此面的块光照值。默认为0。 +- `sky_light`:覆盖用于此面的天空光照值。默认为0。 +- `ambient_occlusion`:为此面禁用或启用环境光遮蔽。默认为模型中设置的值。 + +使用自定义的 `neoforge:item_layers` 加载器,还可以指定要应用于 `item/generated` 模型中所有几何图形的额外面数据。在以下示例中,第1层将以红色染色并以完全亮度发光: ```json5 { @@ -119,12 +121,11 @@ Using the custom `neoforge:item_layers` loader, you can also specify extra face } ``` -### Overrides - -Item overrides can assign a different model to an item based on a float value, called the override value. For example, bows and crossbows use this to change the texture depending on how long they have been drawn. Overrides have both a model and a code side to them. +### 覆盖模型 -The model can specify one or multiple override models that should be used when the override value is equal to or greater than the given threshold value. For example, the bow uses two different properties `pulling` and `pull`. `pulling` is treated as a boolean value, with 1 being interpreted as pulling and 0 as not pulling, while `pull` represents how much the bow is currently pulled. It then uses these properties to specify usage of three alternative models when charged to below 65% (`pulling` 1, no `pull` value), 65% (`pulling` 1, `pull` 0.65) and 90% (`pulling` 1, `pull` 0.9). If multiple models apply (because the value keeps on becoming bigger), the last element of the list matches, so make sure your order is correct. The overrides look as follows: +物品覆盖可以根据浮点值(称为覆盖值)为物品分配不同的模型。例如,弓和十字弓使用此功能根据它们已经拉开的时间来更改纹理。覆盖模型有模型和代码两个方面。 +模型可以指定一个或多个覆盖模型,当覆盖值等于或大于给定的阈值时应使用。例如,弓使用两个不同的属性 `pulling` 和 `pull`。 `pulling` 被视为布尔值,其中1被解释为正在拉动,0被解释为未拉动,而 `pull` 表示弓当前拉伸的程度。然后,它使用这些属性来指定在充能至低于65%时(`pulling` 1,没有 `pull` 值),65%时(`pulling` 1,`pull` 0.65)和90%时(`pulling` 1,`pull` 0.9)使用三种不同的替代模型。如果多个模型适用(因为值不断变大),则匹配列表的最后一个元素,因此请确保您的顺序是正确的。覆盖模型如下所示: ```json5 { // other stuff here @@ -156,7 +157,7 @@ The model can specify one or multiple override models that should be used when t } ``` -The code side of things is pretty simple. Assuming that we want to add a property named `examplemod:property` to our item, we would use the following code in a [client-side][side] [event handler][eventhandler]: +事情的代码方面相当简单。假设我们想要向我们的物品添加一个名为 `examplemod:property` 的属性,我们会在[客户端][side]的[event handler][eventhandler]中使用以下代码: ```java @SubscribeEvent @@ -177,14 +178,14 @@ public static void onClientSetup(FMLClientSetupEvent event) { ``` :::info -Vanilla Minecraft only allows for float values between 0 and 1. NeoForge patches this to allow arbitrary float values. +原版 Minecraft 仅允许 0 到 1 之间的浮点值。NeoForge 对此进行了补充,以允许任意的浮点值。 ::: -### Root Transforms +### 根变换 -Adding the `transform` property at the top level of a model tells the loader that a transformation to all geometry should be applied right before the rotations in a [blockstate file][bsfile] (for block models) or the transformations in a `display` block (for item models) are applied. This is added by NeoForge. +在模型的顶层添加 `transform` 属性会告诉加载器在应用 [blockstate 文件][bsfile](用于方块模型)中的旋转或 `display` 块中的变换(用于物品模型)之前,应该对所有几何图形应用一个变换。这是由 NeoForge 添加的。 -The root transforms can be specified in two ways. The first way would be as a single property named `matrix` containing a transformation 3x4 matrix (row major order, last row is omitted) in the form of a nested JSON array. The matrix is the composition of the translation, left rotation, scale, right rotation and the transformation origin in that order. An example would look like this: +根变换可以通过两种方式指定。第一种方式是作为一个名为 `matrix` 的单个属性,其中包含一个 3x4 的变换矩阵(行主序,最后一行被省略),以嵌套的 JSON 数组形式表示。矩阵是按照平移、左旋转、缩放、右旋转和变换原点的顺序组合而成。示例如下: ```json5 { @@ -199,40 +200,42 @@ The root transforms can be specified in two ways. The first way would be as a si } ``` -The second way is to specify a JSON object containing any combination of the following entries, applied in that order: +根据Minecraft译名标准化,以下是翻译后的文档: + +第二种方式是指定一个包含以下条目的JSON对象,按以下顺序应用: -- `translation`: The relative translation. Specified as a three-dimensional vector (`[x, y, z]`) and defaults to `[0, 0, 0]` if absent. -- `rotation` or `left_rotation`: Rotation around the translated origin to be applied before scaling. Defaults to no rotation. Specified in one of the following ways: - - A JSON object with a single axis to rotation mapping, e.g. `{"x": 90}` - - An array of JSON objects with a single axis to rotation mapping each, applied in the order they are specified in, e.g. `[{"x": 90}, {"y": 45}, {"x": -22.5}]` - - An array with three values that each specify the rotation around each axis, e.g. `[90, 45, -22.5]` - - An array with four values directly specifying a quaternion, e.g. `[0.38268346, 0, 0, 0.9238795]` (= 45 degrees around the X axis) -- `scale`: The scale relative to the translated origin. Specified as a three-dimensional vector (`[x, y, z]`) and defaults to `[1, 1, 1]` if absent. -- `post_rotation` or `right_rotation`: Rotation around the translated origin to be applied after scaling. Defaults to no rotation. Specified the same as `rotation`. -- `origin`: Origin point used for rotation and scaling. The transformation is also moved here as a final step. Specified either as a three-dimensional vector (`[x, y, z]`) or using one of the three builtin values `"corner"` (= `[0, 0, 0]`), `"center"` (= `[0.5, 0.5, 0.5]`) or `"opposing-corner"` (= `[1, 1, 1]`, default). +- `translation`:相对位移。指定为一个三维向量(`[x, y, z]`),如果缺失默认为`[0, 0, 0]`。 +- `rotation` 或 `left_rotation`:在缩放之前应用于平移原点的旋转。默认不旋转。可以用以下方式指定: + - 一个带有单一轴到旋转映射的JSON对象,例如 `{"x": 90}` + - 一个包含单一轴到旋转映射的JSON对象的数组,按照指定的顺序应用,例如 `[{"x": 90}, {"y": 45}, {"x": -22.5}]` + - 一个包含三个值的数组,每个值分别指定每个轴的旋转,例如 `[90, 45, -22.5]` + - 一个包含四个值的数组,直接指定一个四元数,例如 `[0.38268346, 0, 0, 0.9238795]`(= X轴45度旋转) +- `scale`:相对于平移原点的缩放。指定为一个三维向量(`[x, y, z]`),如果缺失默认为`[1, 1, 1]`。 +- `post_rotation` 或 `right_rotation`:在缩放之后应用于平移原点的旋转。默认不旋转。指定方式与`rotation`相同。 +- `origin`:用于旋转和缩放的原点。转换也作为最后一步移到这里。指定为一个三维向量(`[x, y, z]`)或使用三个内置值之一 `"corner"`(=`[0, 0, 0]`),`"center"`(=`[0.5, 0.5, 0.5]`)或 `"opposing-corner"`(=`[1, 1, 1]`,默认值)。 -## Blockstate Files +## 方块状态文件 -_See also: [Blockstate files][mcwikiblockstate] on the [Minecraft Wiki][mcwiki]_ +参见:[Minecraft Wiki][mcwiki]上的[方块状态文件][mcwikiblockstate] -Blockstate files are used by the game to assign different models to different [blockstates]. There must be exactly one blockstate file per block registered to the game. Specifying block models for blockstates works in two mutually exclusive ways: via variants or via multipart. +方块状态文件由游戏用于为不同的[方块状态]分配不同的模型。每个注册到游戏的方块必须有一个确切的方块状态文件。指定方块模型到方块状态有两种相互排斥的方式:通过变体或者多部件。 -Inside a `variants` block, there is an element for each blockstate. This is the predominant way of associating blockstates with models, used by the vast majority of blocks. -- The key is the string representation of the blockstate without the block name, so for example `"type=top,waterlogged=false"` for a non-waterlogged top slab, or `""` for a block with no properties. It is worth noting that unused properties may be omitted. For example, if the `waterlogged` property has no influence on the model chosen, two objects `type=top,waterlogged=false` and `type=top,waterlogged=true` may be collapsed into one `type=top` object. This also means that an empty string is valid for every block. -- The value is either a single model object or an array of model objects. If an array of model objects is used, a model will be randomly chosen from it. A model object consists of the following data: - - `model`: A path to a model file location, relative to the namespace's `models` folder, for example `minecraft:block/cobblestone`. - - `x` and `y`: Rotation of the model on the x-axis/y-axis. Limited to steps of 90 degrees. Optional each, defaults to 0. - - `uvlock`: Whether to lock the UVs of the model when rotating or not. Optional, defaults to false. - - `weight`: Only useful with arrays of model objects. Gives the object a weight, used when choosing a random model object. Optional, defaults to 1. +在`variants`块内,每个方块状态都有一个元素。这是将方块状态与模型相关联的主要方式,被绝大多数方块使用。 +- 键是没有方块名的方块状态的字符串表示,例如对于非含水的台阶是`"type=top,waterlogged=false"`,或者对于没有属性的方块是`""`。值得注意的是,未使用的属性可以省略。例如,如果`waterlogged`属性对所选模型无影响,则两个对象`type=top,waterlogged=false`和`type=top,waterlogged=true`可以被合并为一个`type=top`对象。这也意味着对于每个方块,空字符串都是有效的。 +- 值要么是单一的模型对象,要么是模型对象的数组。如果使用了模型对象的数组,将从中随机选择一个模型。一个模型对象包含以下数据: + - `model`:模型文件位置的路径,相对于命名空间的`models`文件夹,例如`minecraft:block/cobblestone`。 + - `x`和`y`:模型在x轴/y轴的旋转。限制为90度的步进。每个都是可选的,默认为0。 + - `uvlock`:旋转模型时是否锁定UV。可选的,默认为false。 + - `weight`:仅在模型对象数组中有用。给对象一个权重,用于选择随机模型对象。可选的,默认为1。 -In contrast, inside a `multipart` block, elements are combined depending on the properties of the blockstate. This method is mainly used by fences and walls, who enable the four directional parts based on boolean properties. A multipart element consists of two parts: a `when` block and an `apply` block. +相反,在`multipart`块内,元素根据方块状态的属性组合。这种方法主要被栅栏和围墙使用,它们根据布尔属性启用四个方向的部分。一个多部分元素由两个部分组成:`when`块和`apply`块。 -- The `when` block specifies either a string representation of a blockstate or a list of properties that must be met for the element to apply. The lists can either be named `"OR"` or `"AND"`, performing the respective logical operation on its contents. Both single blockstate and list values can additionally specify multiple actual values by separating them with `|` (for example `facing=east|facing=west`). -- The `apply` block specifies the model object or an array of model objects to use. This works exactly like with a `variants` block. +- `when`块指定了一个方块状态的字符串表示,或者一个必须满足元素应用的属性列表。这些列表可以被命名为`"OR"`或`"AND"`,对其内容执行相应的逻辑操作。单个方块状态和列表值都可以通过用`|`分隔它们来指定多个实际值(例如 `facing=east|facing=west`)。 +- `apply`块指定了要使用的模型对象或模型对象数组。这与`variants`块的工作方式完全相同。 -## Tinting +## 着色 -Some blocks, such as grass or leaves, change their texture color based on their location and/or properties. [Model elements][elements] can specify a tint index on their faces, which will allow a color handler to handle the respective faces. The code side of things works through two events, one for block color handlers and one for item color handlers. They both work pretty similar, so let's have a look at a block handler first: +有些方块,如草或树叶,会根据它们的位置和/或属性改变它们的纹理。[模型元素][elements]可以在它们的面上指定一个染色指数,这将允许颜色处理器处理相应的面。代码方面通过两个事件来处理,一个是方块颜色处理器,另一个是物品颜色处理器。它们的工作方式非常相似,让我们先看一下方块处理器: ```java @SubscribeEvent @@ -249,7 +252,7 @@ public static void registerBlockColorHandlers(RegisterColorHandlersEvent.Block e } ``` -Item handlers work pretty much the same, except for some naming and the lambda parameters: +物品处理器的工作方式几乎相同,只是命名和lambda参数有所不同: ```java @SubscribeEvent @@ -263,11 +266,11 @@ public static void registerItemColorHandlers(RegisterColorHandlersEvent.Item eve } ``` -Be aware that the `item/generated` model specifies tint indices for its various layers - `layer0` has tint index 0, `layer1` has tint index 1, etc. Also, remember that block items are items, not blocks, and require an item color handler to be colored. +请注意,`item/generated`模型为其各个层指定了染色指数 - `layer0`有染色指数0,`layer1`有染色指数1,等等。另外,记住方块物品是物品,而不是方块,需要物品颜色处理器来着色。 -## Registering Additional Models +## 注册额外的模型 -Models that are not associated with a block or item in some way, but are still required in other contexts (e.g. [block entity renderers][ber]), can be registered through `ModelEvent.RegisterAdditional`: +一些并未与某个方块或物品有所关联,但在其他上下文(例如[方块实体渲染器][ber])中仍然需要的模型,可以通过`ModelEvent.RegisterAdditional`来注册: ```java // Client-side mod bus event handler From 9c3478d30f2f4d2ee077044f636d3bb9b9702f2b Mon Sep 17 00:00:00 2001 From: DaZiDian <113085496+DaZiDian@users.noreply.github.com> Date: Wed, 8 May 2024 15:17:50 +0800 Subject: [PATCH 74/87] Update modelloaders.md --- docs/resources/client/models/modelloaders.md | 96 ++++++++++---------- 1 file changed, 48 insertions(+), 48 deletions(-) diff --git a/docs/resources/client/models/modelloaders.md b/docs/resources/client/models/modelloaders.md index dcb86d901..3852f1fb8 100644 --- a/docs/resources/client/models/modelloaders.md +++ b/docs/resources/client/models/modelloaders.md @@ -1,16 +1,16 @@ -# Custom Model Loaders +# 自定义模型加载器 -A model is simply a shape. It can be a cube, a collection of cubes, a collection of triangles, or any other geometrical shape (or collection of geometrical shape). For most contexts, it is not relevant how a model is defined, as everything will end up as a `BakedModel` in memory anyway. As such, NeoForge adds the ability to register custom model loaders that can transform any model you want into a `BakedModel` for the game to use. +一个模型就是一个形状。它可以是一个立方体、一组立方体、一组三角形,或者任何其他的几何形状(或几何形状的组合)。在大多数上下文中,模型是如何定义的并不重要,因为最终一切都会在内存中变成`BakedModel`。因此,NeoForge增加了能够注册自定义模型加载器的功能,这些加载器可以将任何你想要的模型转换成游戏使用的`BakedModel`。 -The entry point for a block model remains the model JSON file. However, you can specify a `loader` field in the root of the JSON that will swap out the default loader for your own loader. A custom model loader may ignore all fields the default loader requires. +一个方块模型的入口点仍然是模型JSON文件。然而,你可以在JSON的根部指定一个`loader`字段,它会将默认加载器替换为你自己的加载器。一个自定义模型加载器可能会忽略默认加载器所需的所有字段。 -## Builtin Model Loaders +## 内建模型加载器 -Besides the default model loader, NeoForge offers several builtin loaders, each serving a different purpose. +除了默认模型加载器,NeoForge还提供了几个内建的加载器,每个加载器都有不同的用途。 -### Composite Model +### 组合模型 -A composite model can be used to specify different model parts in the parent and only apply some of them in a child. This is best illustrated by an example. Consider the following parent model at `examplemod:example_composite_model`: +组合模型可以用来在父模型中指定不同的模型部分,并且只在子模型中应用其中的一些。以下面的例子最能说明这一点。考虑以下位于`examplemod:example_composite_model`的父模型: ```json5 { @@ -31,7 +31,7 @@ A composite model can be used to specify different model parts in the parent and } ``` -Then, we can disable and enable individual parts in a child model of `examplemod:example_composite_model`: +然后,我们可以在`examplemod:example_composite_model`的子模型中禁用和启用单独的部分: ```json5 { @@ -44,11 +44,11 @@ Then, we can disable and enable individual parts in a child model of `examplemod } ``` -To [datagen][modeldatagen] this model, use the custom loader class `CompositeModelBuilder`. +要[datagen][modeldatagen]这个模型,使用自定义加载器类`CompositeModelBuilder`。 -### Dynamic Fluid Container Model +### 动态流体容器模型 -The dynamic fluid container model, also called dynamic bucket model after its most common use case, is used for items that represent a fluid container (such as a bucket or a tank) and want to show the fluid within the model. This only works if there is a fixed amount of fluids (e.g. only lava and powder snow) that can be used, use a [`BlockEntityWithoutLevelRenderer`][bewlr] instead if the fluid is arbitrary. +动态流体容器模型,也称为动态桶模型,它最常见的使用场景是用于表示流体容器的物品(如桶或罐)并想在模型中显示流体。这只有在流体量是固定的(例如只有岩浆和细雪)的情况下才可行,如果流体是任意的,那么请使用[`BlockEntityWithoutLevelRenderer`][bewlr]。 ```json5 { @@ -71,7 +71,7 @@ The dynamic fluid container model, also called dynamic bucket model after its mo } ``` -Very often, dynamic fluid container models will directly use the bucket model. This is done by specifying the `neoforge:item_bucket` parent model, like so: +很多时候,动态流体容器模型会直接使用桶模型。这是通过指定`neoforge:item_bucket`父模型来实现的,如下所示: ```json5 { @@ -83,11 +83,11 @@ Very often, dynamic fluid container models will directly use the bucket model. T } ``` -To [datagen][modeldatagen] this model, use the custom loader class `DynamicFluidContainerModelBuilder`. Be aware that for legacy support reasons, this class also provides a method to set the `apply_tint` property, which is no longer used. +要[datagen][modeldatagen]这个模型,使用自定义加载器类`DynamicFluidContainerModelBuilder`。请注意,出于对旧版本支持的考虑,这个类还提供了一个设置`apply_tint`属性的方法,这个属性现在已不再使用。 -### Elements Model +### 元素模型 -An elements model consists of block model [elements][elements] and an optional [root transform][transform]. Intended mainly for usage outside regular model rendering, for example within a [BER][ber]. +一个元素模型由方块模型[elements][elements]和一个可选的[根变换][transform]组成。主要用于常规模型渲染之外的场景,例如在[BER][ber]中。 ```json5 { @@ -97,9 +97,9 @@ An elements model consists of block model [elements][elements] and an optional [ } ``` -### Empty Model +### 空模型 -An empty model just renders nothing at all. +一个空模型什么都不渲染。 ```json5 { @@ -107,12 +107,12 @@ An empty model just renders nothing at all. } ``` -### Item Layer Model +### 物品层模型 -Item layer models are a variant of the standard `item/generated` model that offer the following additional features: +物品层模型是标准`item/generated`模型的一个变种,提供了以下额外的功能: -- Unlimited amount of layers (instead of the default 5) -- Per-layer [render types][rendertype] +- 无限数量的层(而不是默认的5层) +- 每一层的[渲染类型][rendertype] ```json5 { @@ -135,11 +135,11 @@ Item layer models are a variant of the standard `item/generated` model that offe } ``` -To [datagen][modeldatagen] this model, use the custom loader class `ItemLayerModelBuilder`. +要[datagen][modeldatagen]这个模型,使用自定义加载器类`ItemLayerModelBuilder`。 -### OBJ Model +### OBJ模型 -The OBJ model loader allows you to use Wavefront `.obj` 3D models in the game, allowing for arbitrary shapes (including triangles, circles, etc.) to be included in a model. The `.obj` model must be placed in the `models` folder (or a subfolder thereof), and a `.mtl` file with the same name must be provided (or set manually), so for example, an OBJ model at `models/block/example.obj` must have a corresponding MTL file at `models/block/example.mtl`. +OBJ模型加载器允许您在游戏中使用Wavefront `.obj` 3D模型,允许在模型中包含任意形状(包括三角形、圆形等)。`.obj`模型必须放在`models`文件夹(或其子文件夹)中,并且必须提供一个同名的`.mtl`文件(或手动设置),所以例如,位于`models/block/example.obj`的OBJ模型必须有一个对应的MTL文件位于`models/block/example.mtl`。 ```json5 { @@ -168,11 +168,11 @@ The OBJ model loader allows you to use Wavefront `.obj` 3D models in the game, a } ``` -To [datagen][modeldatagen] this model, use the custom loader class `ObjModelBuilder`. +要[datagen][modeldatagen]这个模型,使用自定义加载器类`ObjModelBuilder`。 -### Separate Transforms Model +### 独立变换模型 -A separate transforms model can be used to switch between different models based on the perspective. The perspectives are the same as for the `display` block in a [normal model][model]. This works by specifying a base model (as a fallback) and then specifying per-perspective override models. Note that each of these can be fully-fledged models if you so desire, but it is usually easiest to just refer to another model by using a child model of that model, like so: +独立变换模型可用于根据视角切换不同的模型。视角与[normal model][model]中的`display`块相同。这通过指定一个基础模型(作为后备)然后为每个视角指定覆盖模型来实现。注意,如果您愿意,每个这样的模型都可以是完整的模型,但通常最简单的方法是使用那个模型的子模型来引用另一个模型,如下所示: ```json5 { @@ -190,24 +190,24 @@ A separate transforms model can be used to switch between different models based } ``` -To [datagen][modeldatagen] this model, use the custom loader class `SeparateTransformsModelBuilder`. +要[datagen][modeldatagen]这个模型,使用自定义加载器类`SeparateTransformsModelBuilder`。 -## Creating Custom Model Loaders +## 创建自定义模型加载器 -To create your own model loader, you need three classes, plus an event handler: +要创建自己的模型加载器,您需要三个类加上一个事件处理程序: -- A geometry loader class -- A geometry class -- A dynamic [baked model][bakedmodel] class -- A [client-side][sides] [event handler][event] for `ModelEvent.RegisterGeometryLoaders` that registers the geometry loader +- 一个几何体加载器类 +- 一个几何体类 +- 一个动态的[baked model][bakedmodel]类 +- 一个用于`ModelEvent.RegisterGeometryLoaders`的[客户端][sides] [事件处理程序][event],用于注册几何体加载器 -To illustrate how these classes are connected, we will follow a model being loaded: +为了说明这些类是如何连接的,我们将跟随一个模型的加载过程: -- During model loading, a model JSON with the `loader` property set to your loader is passed to your geometry loader. The geometry loader then reads the model JSON and returns a geometry object using the model JSON's properties. -- During model baking, the geometry is baked, returning a dynamic baked model. -- During model rendering, the dynamic baked model is used for rendering. +- 在模型加载期间,带有`loader`属性设置为您的加载器的模型JSON被传递给您的几何体加载器。然后,几何体加载器读取模型JSON并使用模型JSON的属性返回一个几何体对象。 +- 在模型烘焙期间,几何体被烘焙,返回一个动态烘焙模型。 +- 在模型渲染期间,动态烘焙模型用于渲染。 -Let's illustrate this further through a basic class setup. The geometry loader class is named `MyGeometryLoader`, the geometry class is named `MyGeometry`, and the dynamic baked model class is named `MyDynamicModel`: +让我们通过一个基本的类设置进一步说明。几何体加载器类命名为`MyGeometryLoader`,几何体类命名为`MyGeometry`,动态烘焙模型类命名为`MyDynamicModel`: ```java public class MyGeometryLoader implements IGeometryLoader { @@ -328,7 +328,7 @@ public class MyDynamicModel implements IDynamicBakedModel { } ``` -When all is done, don't forget to actually register your loader, otherwise all the work will have been for nothing: +在所有操作完成后,不要忘记实际注册您的加载器,否则所有的工作都将白费: ```java // Client-side mod bus event handler @@ -338,9 +338,9 @@ public static void registerGeometryLoaders(ModelEvent.RegisterGeometryLoaders ev } ``` -### Datagen +### 数据生成 -Of course, we can also [datagen] our models. To do so, we need a class that extends `CustomLoaderBuilder`: +当然,我们也可以对我们的模型进行[数据生成]。为此,我们需要一个扩展`CustomLoaderBuilder`的类: ```java // This assumes a block model. Use ItemModelBuilder as the generic parameter instead @@ -371,7 +371,7 @@ public class MyLoaderBuilder extends CustomLoaderBuilder { } ``` -To use this loader builder, do the following during block (or item) [model datagen][modeldatagen]: +要使用这个加载器构建器,在块(或物品)[模型数据生成][modeldatagen]期间执行以下操作: ```java // This assumes a BlockStateProvider. Use getBuilder("my_cool_block") directly in an ItemModelProvider. @@ -380,15 +380,15 @@ To use this loader builder, do the following during block (or item) [model datag MyLoaderBuilder loaderBuilder = models().getBuilder("my_cool_block").customLoader(MyLoaderBuilder::new); ``` -Then, call your field setters on the `loaderBuilder`. +然后,在`loaderBuilder`上调用你的字段设置器。 -#### Visibility +#### 可见性 -The default implementation of `CustomLoaderBuilder` holds methods for applying visibility. You may choose to use or ignore the `visibility` property in your model loader. Currently, only the [composite model loader][composite] makes use of this property. +`CustomLoaderBuilder`的默认实现有应用可见性的方法。你可以选择在你的模型加载器中使用或忽视`visibility`属性。目前,只有[复合模型加载器][composite]使用了这个属性。 -### Reusing the Default Model Loader +### 重用默认模型加载器 -In some contexts, it makes sense to reuse the vanilla model loader and just building your model logic on top of that instead of outright replacing it. We can do so using a neat trick: In the model loader, we simply remove the `loader` property and send it back to the model deserializer, tricking it into thinking that it is a regular model now. We then pass it to the geometry, bake the model geometry there (like the default geometry handler would) and pass it along to the dynamic model, where we can then use the model's quads in whatever way we want: +在某些情况下,重用 Vanilla 模型加载器并在其基础上构建你的模型逻辑,而不是直接替换它,是有意义的。我们可以使用一个巧妙的技巧来实现这个目标:在模型加载器中,我们只需移除`loader`属性,然后将其发送回模型解析器,让其误以为现在是一个常规模型。然后我们将它传给几何体,在那里烘焙模型几何体(就像默认的几何体处理器那样),并将其传递给动态模型,在那里我们可以以我们想要的方式使用模型的quads: ```java public class MyGeometryLoader implements IGeometryLoader { From a394e58e4463c1130e712e7a97f03df0614afed8 Mon Sep 17 00:00:00 2001 From: DaZiDian <113085496+DaZiDian@users.noreply.github.com> Date: Wed, 8 May 2024 15:26:58 +0800 Subject: [PATCH 75/87] Update i18n.md --- docs/resources/client/i18n.md | 76 +++++++++++++++++------------------ 1 file changed, 38 insertions(+), 38 deletions(-) diff --git a/docs/resources/client/i18n.md b/docs/resources/client/i18n.md index e5be489e4..54bb13b3c 100644 --- a/docs/resources/client/i18n.md +++ b/docs/resources/client/i18n.md @@ -1,35 +1,35 @@ -# I18n and L10n +# I18n 与 L10n -I18n (short for internationalization) is the way of designing a program to work with multiple languages. L10n (short for localization) is the process of translating text into the user's language. Minecraft implements these using `Component`s. +I18n(国际化的简称)是一种设计程序以适应多种语言的方法。L10n(本地化的简称)是将文本翻译成用户语言的过程。Minecraft使用`Component`来实现这些功能。 -## `Component`s +## `Component`组件 -A `Component` is a piece of text with metadata, with the metadata including things such as text formatting. It can be created in one of the following ways (all of the following are static methods in the `Component` interface): +`Component`是有元数据的文本片段,元数据包括如文本格式化等内容。它可以通过以下方式之一创建(以下都是`Component`接口中的静态方法): -| Method | Description | -|----------------|-------------------------------------------------------------------------------------------------------| -| `empty` | Creates an empty component. | -| `literal` | Creates a component with the given text and directly displays that text without translating. | -| `nullToEmpty` | Creates an empty component when given null, and a literal component otherwise. | -| `translatable` | Creates a translatable component. The given string is then resolved as a translation key (see below). | -| `keybind` | Creates a component containing the (translated) display name of the given keybind. | -| `nbt` | Creates a component representing the [NBT][nbt] at the given path. | -| `score` | Creates a component containing a scoreboard objective value. | -| `selector` | Creates a component containing a list of entity names for a given [entity selector][selector]. | +| 方法 | 描述 | +|-----------------|--------------------------------------------------------------------------------------------------| +| `empty` | 创建一个空组件。 | +| `literal` | 创建一个具有给定文本的组件,并直接显示该文本,而不进行翻译。 | +| `nullToEmpty` | 对于给定的null创建一个空组件,否则创建一个文字组件。 | +| `translatable` | 创建一个可翻译的组件。给定的字符串随后会被解析为翻译键(见下文)。 | +| `keybind` | 创建一个包含给定按键绑定的(翻译后的)显示名称的组件。 | +| `nbt` | 创建一个表示给定路径上的[NBT][nbt]的组件。 | +| `score` | 创建一个包含记分板目标值的组件。 | +| `selector` | 创建一个组件,包含给定[实体选择器][selector]的实体名称列表。 | -`Component.translatable()` additionally has a vararg parameter that accepts string interpolation elements. This works similar to Java's `String#format`, but always uses `%s` instead of `%i`, `%d`, `%f` and any other format specifier, calling `#toString()` where needed. +`Component.translatable()`还有一个可变参数,接受字符串插值元素。这与Java的`String#format`类似,但总是使用`%s`代替`%i`、`%d`、`%f`和任何其他格式说明符,在需要时调用`#toString()`。 -Every `Component` can be resolved using `#getString()`. Resolving is generally lazy, meaning that the server can specify a `Component`, send it to the clients, and the clients will each resolve the `Component`s on their own (where different languages may result in different text). Many places in Minecraft will also directly accept `Component`s and take care of resolving for you. +每个`Component`都可以使用`#getString()`来解析。解析通常是惰性的,这意味着服务器可以指定一个`Component`,将其发送给客户端,然后客户端会各自解析`Component`(不同语言可能导致不同的文本)。Minecraft中的许多地方也会直接接受`Component`并为你解决解析问题。 :::caution -Never let a server translate a `Component`. Always send `Component`s to the client and resolve them there. +永远不要让服务器翻译`Component`。总是将`Component`发送到客户端并在那里解析它们。 ::: -### Text Formatting +### 文本格式化 -`Component`s can be formatted using `Style`s. `Style`s are immutable, creating a new `Style` object when modified, and thus allowing them to be created once and then be reused as needed. +`Component`可以使用`Style`进行格式化。`Style`是不可变的,修改时会创建一个新的`Style`对象,因此允许一次创建,然后根据需要重复使用。 -`Style.EMPTY` can generally be used as a base to work off. Multiple `Style`s can be merged with `Style#applyTo(Style other)`, which returns a new `Style` that takes settings from the `Style` the `applyTo()` method was called on unless the respective setting is absent, in which case the setting from the `Style` passed in as a parameter is used. `Style`s can then be applied to components like so: +`Style.EMPTY`通常可以用作工作的基础。可以通过`Style#applyTo(Style other)`合并多个`Style`,该方法返回一个新的`Style`,它从被`applyTo()`方法调用的`Style`中取得设置,除非相应的设置不存在,在这种情况下,则使用作为参数传入的`Style`中的设置。然后可以这样将`Style`应用到组件上: ```java Component text = Component.literal("Hello World!"); @@ -52,7 +52,7 @@ text.setStyle(merged); text.withStyle(Style.EMPTY.withColor(0xFF0000)); ``` -Another, more elaborate option of formatting is to use click and hover events: +另一个更复杂的格式化选项是使用点击和悬停事件: ```java // We have a total of 6 options for a click event, and a total of 3 options for a hover event. @@ -87,11 +87,11 @@ Style clickable = Style.EMPTY.withClickEvent(clickEvent); Style hoverable = Style.EMPTY.withHoverEvent(hoverEvent); ``` -## Language Files +## 语言文件 -Language files are JSON files that contain mappings from translation keys (see below) to actual names. They are located at `assets//lang/language_name.json`. For example, US English translations for a mod with id `examplemod` would be located at `assets/examplemod/lang/en_us.json`. A full list of languages supported by Minecraft can be found [here][mcwikilang]. +语言文件是包含从翻译键(见下文)到实际名称的映射的JSON文件。它们位于`assets//lang/language_name.json`。例如,对于一个id为`examplemod`的mod,US English的翻译将位于`assets/examplemod/lang/en_us.json`。可以在[这里][mcwikilang]找到Minecraft支持的所有语言的完整列表。 -A language file generally looks like this: +一个语言文件通常看起来是这样的: ```json { @@ -100,30 +100,30 @@ A language file generally looks like this: } ``` -### Translation Keys +### 翻译键 -Translation keys are the keys used in translations. In many cases, they follow the format `registry.modid.name`. For example, a mod with the id `examplemod` that provides a block named `example_block` will probably want to provide translations for the key `block.examplemod.example_block`. However, you can use basically any string as a translation key. +翻译键是在翻译中使用的键。在许多情况下,它们遵循格式`registry.modid.name`。例如,一个id为`examplemod`的mod提供了一个名为`example_block`的方块,可能会希望为键`block.examplemod.example_block`提供翻译。然而,你基本上可以使用任何字符串作为翻译键。 -If a translation key does not have an associated translation in the selected language, the game will fall back to US English (`en_us`), if that is not already the selected language. If US English does not have a translation either, the translation will fail silently, and the raw translation key will be displayed instead. +如果选定语言中没有与翻译键相关联的翻译,游戏将回退到US English(`en_us`),除非已经选择了US English。如果US English也没有翻译,翻译将静默失败,并代之以显示原始翻译键。 -Some places in Minecraft offer you helper methods to get a translation keys. For example, both blocks and items provide `#getDescriptionId` methods. These can not only be queried, but also overridden if needed. A common use case are items that have different names depending on their [NBT][nbt] value. These will usually override the variant of `#getDescriptionId` that has an [`ItemStack`][itemstack] parameter, and return different values based on the stack's NBT. Another common use case are `BlockItem`s, which override the method to use the associated block's translation key instead. +Minecraft的一些地方为你提供了获取翻译键的辅助方法。例如,方块和物品都提供了`#getDescriptionId`方法。这些不仅可以被查询,而且在需要时也可以被覆盖。一个常见的用例是,根据它们的[NBT][nbt]值有不同名称的物品。这些通常会覆盖带有[`ItemStack`][itemstack]参数的`#getDescriptionId`变体,并根据堆栈的NBT返回不同的值。另一个常见的用例是`BlockItem`,它覆盖该方法以使用关联方块的翻译键。 :::tip -The only purpose of translation keys is for localization. Do not use them for game logic, that's what [registry names][regname] are for. +翻译键的唯一目的是用于本地化。不要用它们来处理游戏逻辑,游戏逻辑应该使用[注册名称][regname]。 ::: -### Translating Mod Metadata +### 翻译Mod元数据 -Starting with NeoForge 20.4.179, translation files can override certain parts of [mod info][modstoml] using the following keys (where `modid` is to be replaced with the actual mod id): +从NeoForge 20.4.179版本开始,翻译文件可以使用以下键(其中`modid`需替换为实际的mod id)覆盖[mod信息][modstoml]的某些部分: -| | Translation Key | Overriding | -|--------------|----------------------------------------|------------------------------------------------------------------------------| -| Display Name | `fml.menu.mods.info.displayname.modid` | A field named `displayName` may be placed in the `[[mods]]` section instead. | -| Description | `fml.menu.mods.info.description.modid` | A field named `description` may be placed in the `[[mods]]` section instead. | +| | 翻译键 | 覆盖内容 | +|--------------|--------------------------------------------|-------------------------------------------------------------------------------| +| 显示名称 | `fml.menu.mods.info.displayname.modid` | 可在`[[mods]]`部分中放置一个名为`displayName`的字段代替。 | +| 描述 | `fml.menu.mods.info.description.modid` | 可在`[[mods]]`部分中放置一个名为`description`的字段代替。 | -### Datagen +### 数据生成 -Language files can be [datagenned][datagen]. To do so, extend the `LanguageProvider` class and add your translations in the `addTranslations()` method: +语言文件可以[通过数据生成][datagen]来创建。要这样做,请扩展`LanguageProvider`类并在`addTranslations()`方法中添加你的翻译: ```java public class MyLanguageProvider extends LanguageProvider { @@ -170,7 +170,7 @@ public class MyLanguageProvider extends LanguageProvider { } ``` -Then, register the provider like any other provider in the `GatherDataEvent`. +然后,在`GatherDataEvent`中像注册其他提供者一样注册这个提供者。 [datagen]: ../index.md#data-generation [itemstack]: ../../items/index.md#itemstacks From c5ee38c118e80c2076910d9d60f650e8b4b6ca43 Mon Sep 17 00:00:00 2001 From: DaZiDian <113085496+DaZiDian@users.noreply.github.com> Date: Wed, 8 May 2024 15:34:27 +0800 Subject: [PATCH 76/87] Update particles.md --- docs/resources/client/particles.md | 50 +++++++++++++++--------------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/docs/resources/client/particles.md b/docs/resources/client/particles.md index 1ed0126e0..e7f3e9bf6 100644 --- a/docs/resources/client/particles.md +++ b/docs/resources/client/particles.md @@ -1,12 +1,12 @@ -# Particles +# 粒子效果 -Particles are 2D effects that polish the game and add immersion. They can be spawned both client and server [side], but being mostly visual in nature, critical parts exist only on the physical (and logical) client side. +粒子效果是能够美化游戏并增加沉浸感的2D效果。它们可以在客户端和服务器端[side]产生,但由于它们主要是视觉效果,关键部分只存在于物理(和逻辑)客户端。 -## Registering Particles +## 注册粒子效果 ### `ParticleType` -Particles are registered using `ParticleType`s. These work similar to `EntityType`s or `BlockEntityType`s, in that there's a `Particle` class - every spawned particle is an instance of that class -, and then there's the `ParticleType` class, holding some common information, that is used for registration. `ParticleType`s are a [registry], which means that we want to register them using a `DeferredRegister` like all other registered objects: +粒子效果是使用`ParticleType`注册的。这与`EntityType`或`BlockEntityType`类似,有一个`Particle`类 - 每个产生的粒子都是该类的一个实例 -,然后有`ParticleType`类,保存一些共有信息,用于注册。`ParticleType`是一个[注册表],这意味着我们想要使用`DeferredRegister`来注册它们,就像所有其他注册的对象一样: ```java public class MyParticleTypes { @@ -28,14 +28,14 @@ public class MyParticleTypes { ``` :::info -A `ParticleType` is only necessary if you need to work with particles on the server side. The client can also use `Particle`s directly. +如果您需要在服务器端处理粒子效果,那么`ParticleType`是必需的。客户端也可以直接使用`Particle`。 ::: ### `Particle` -A `Particle` is what is later spawned into the world and displayed to the player. While you may extend `Particle` and implement things yourself, in many cases it will be better to extend `TextureSheetParticle` instead, as this class provides helpers for things such as animating and scaling, and also does the actual rendering for you (all of which you'd need to implement yourself if extending `Particle` directly). +`Particle`是稍后被生成到世界中并显示给玩家的实体。虽然你可以扩展`Particle`并自己实现一些功能,但在许多情况下,扩展`TextureSheetParticle`可能会更好,因为这个类为你提供了如动画和缩放等功能的助手,而且还为你实现了实际的渲染(如果直接扩展`Particle`,你需要自己实现这些功能)。 -Most properties of `Particle`s are controlled by fields such as `gravity`, `lifetime`, `hasPhysics`, `friction`, etc. The only two methods that make sense to implement yourself are `tick` and `move`, both of which do exactly what you'd expect. As such, custom particle classes are often short, consisting e.g. only of a constructor that sets some fields and lets the superclass handle the rest. A basic implementation would look somewhat like this: +`Particle`的大多数属性是由如`gravity`,`lifetime`,`hasPhysics`,`friction`等字段控制的。唯一有意义的自我实现方法是`tick`和`move`,这两个方法都正如你所期望的那样进行操作。因此,自定义的粒子类通常很简短,例如,只包括一个构造函数,设置一些字段并让超类处理剩下的事情。一个基本的实现可能看起来像这样: ```java public class MyParticle extends TextureSheetParticle { @@ -62,7 +62,7 @@ public class MyParticle extends TextureSheetParticle { ### `ParticleProvider` -Next, particle types must register a `ParticleProvider`. `ParticleProvider` is a client-only class responsible for actually creating our `Particle`s through the `createParticle` method. While more elaborate code can be included here, many particle providers are as simple as this: +接下来,粒子类型必须注册一个`ParticleProvider`。`ParticleProvider`是一个仅在客户端的类,负责通过`createParticle`方法实际创建我们的`Particle`。虽然这里可以包含更复杂的代码,但许多粒子提供器的实现可能非常简单,如下所示: ```java // The generic type of ParticleProvider must match the type of the particle type this provider is for. @@ -86,7 +86,7 @@ public class MyParticleProvider implements ParticleProvider } ``` -Your particle provider must then be associated with the particle type in the [client-side][side] [mod bus][modbus] [event] `RegisterParticleProvidersEvent`: +然后,您的粒子提供器必须在[客户端][side] [mod bus][modbus] [event] `RegisterParticleProvidersEvent`中与粒子类型关联: ```java @SubscribeEvent @@ -99,9 +99,9 @@ public static void registerParticleProviders(RegisterParticleProvidersEvent even } ``` -### Particle Definitions +### 粒子定义 -Finally, we must associate our particle type with a texture. Similar to how items are associated with an item model, we associate our particle type with what is known as a particle definition (or particle description). A particle definition is a JSON file in the `assets//particles` directory and has the same name as the particle type (so for example `my_particle.json` for the above example). The particle definition JSON has the following format: +最后,我们必须将我们的粒子类型与一个纹理关联起来。与物品被关联到一个物品模型相似,我们将我们的粒子类型与所谓的粒子定义(或粒子描述)关联起来。粒子定义是`assets//particles`目录中的一个JSON文件,它的名称与粒子类型相同(例如,对于上述示例是`my_particle.json`)。粒子定义JSON的格式如下: ```json5 { @@ -116,15 +116,15 @@ Finally, we must associate our particle type with a texture. Similar to how item } ``` -Note that a particle definition file is only necessary when using a sprite set particle. Single sprite particles directly map to the texture file at `assets//textures/particle/.png`, and special particle providers can do whatever you want anyway. +请注意,仅当使用精灵集粒子时才需要粒子定义文件。单精灵粒子直接映射到`assets//textures/particle/.png`的纹理文件,特殊粒子提供器可以做任何你想做的事情。 :::danger -A mismatched list of sprite set particle factories and particle definition files, i.e. a particle description without a corresponding particle factory, or vice versa, will throw an exception! +不匹配的精灵集粒子工厂列表和粒子定义文件,即没有相应粒子工厂的粒子描述,或者反之亦然,将会抛出异常! ::: -### Datagen +### 数据生成 -Particle definition files can also be [datagenned][datagen] by extending `ParticleDescriptionProvider` and overriding the `#addDescriptions()` method: +粒子定义文件也可以通过扩展`ParticleDescriptionProvider`并覆写`#addDescriptions()`方法来进行[数据生成][datagen]: ```java public class MyParticleDescriptionProvider extends ParticleDescriptionProvider { @@ -158,7 +158,7 @@ public class MyParticleDescriptionProvider extends ParticleDescriptionProvider { } ``` -Don't forget to add the provider to the `GatherDataEvent`: +不要忘了向 `GatherDataEvent` 添加提供器: ```java @SubscribeEvent @@ -175,9 +175,9 @@ public static void gatherData(GatherDataEvent event) { } ``` -### Custom `ParticleType`s +### 自定义 `ParticleType` -While for most cases `SimpleParticleType` suffices, it is sometimes necessary to attach additional data to the particle on the server side. This is where a custom `ParticleType` and an associated custom `ParticleOptions` are required. Let's start with the `ParticleOptions`, as that is where the information is actually stored: +虽然在大多数情况下,`SimpleParticleType`就足够了,但有时需要在服务器端为粒子附加额外的数据。这就需要一个自定义的`ParticleType`和一个关联的自定义`ParticleOptions`。让我们从`ParticleOptions`开始,因为这是实际存储信息的地方: ```java public class MyParticleOptions implements ParticleOptions { @@ -214,7 +214,7 @@ public class MyParticleOptions implements ParticleOptions { } ``` -We then use this `ParticleOptions` implementation in our custom `ParticleType`... +然后我们在我们的自定义`ParticleType`中使用这个`ParticleOptions`实现... ```java public class MyParticleType extends ParticleType { @@ -237,7 +237,7 @@ public class MyParticleType extends ParticleType { } ``` -... and reference it during registration: +... 并在注册过程中引用它: ```java public static final Supplier MY_CUSTOM_PARTICLE = PARTICLE_TYPES.register( @@ -245,13 +245,13 @@ public static final Supplier MY_CUSTOM_PARTICLE = PARTICLE_TYPES () -> new MyParticleType(false)); ``` -## Spawning Particles +## 生成粒子 -As a reminder from before, the server only knows `ParticleType`s and `ParticleOption`s, while the client works directly with `Particle`s provided by `ParticleProvider`s that are associated with a `ParticleType`. Consequently, the ways in which particles are spawned are vastly different depending on the side you are on. +作为之前的提醒,服务器只知道`ParticleType`和`ParticleOption`,而客户端直接使用与`ParticleType`关联的`ParticleProvider`提供的`Particle`。因此,生成粒子的方式根据你所在的方面有很大的不同。 -- **Common code**: Call `Level#addParticle` or `Level#addAlwaysVisibleParticle`. This is the preferred way of creating particles that are visible to everyone. -- **Client code**: Use the common code way. Alternatively, create a `new Particle()` with the particle class of your choice and call `Minecraft.getInstance().particleEngine#add(Particle)` with that particle. Note that particles added this way will only display for the client and thus not be visible to other players. -- **Server code**: Call `ServerLevel#sendParticles`. Used in vanilla by the `/particle` command. +- **通用代码**:调用`Level#addParticle`或`Level#addAlwaysVisibleParticle`。这是创建对所有人都可见的粒子的首选方式。 +- **客户端代码**:使用通用代码方式。或者,选择你喜欢的粒子类创建一个`new Particle()`,并用那个粒子调用`Minecraft.getInstance().particleEngine#add(Particle)`。注意,这种方式添加的粒子只会显示给客户端,因此其他玩家看不到。 +- **服务器代码**:调用`ServerLevel#sendParticles`。在原版中被`/particle`命令使用。 [datagen]: ../index.md#data-generation [event]: ../../concepts/events.md From 0ec036f9e3b293cbee42d47bb4fc926618c70ef1 Mon Sep 17 00:00:00 2001 From: DaZiDian <113085496+DaZiDian@users.noreply.github.com> Date: Wed, 8 May 2024 15:43:53 +0800 Subject: [PATCH 77/87] Update sounds.md --- docs/resources/client/sounds.md | 78 ++++++++++++++++----------------- 1 file changed, 39 insertions(+), 39 deletions(-) diff --git a/docs/resources/client/sounds.md b/docs/resources/client/sounds.md index c3e7d69f7..48c46f082 100644 --- a/docs/resources/client/sounds.md +++ b/docs/resources/client/sounds.md @@ -1,24 +1,24 @@ -# Sounds +# 声音 -Sounds, while not required for anything, can make a mod feel much more nuanced and alive. Minecraft offers you various ways to register and play sounds, which will be laid out in this article. +虽然声音对于任何事情都不是必需的,但它们可以使模组感觉更加细腻和生动。Minecraft为你提供了各种注册和播放声音的方式,本文将对此进行说明。 -## Terminology +## 术语 -The Minecraft sound engine uses a variety of terms to refer to different things: +Minecraft的声音引擎使用多种术语来指代不同的事物: -- **Sound event**: A sound event is an in-code trigger that tells the sound engine to play a certain sound. `SoundEvent`s are also the things you register to the game. -- **Sound category** or **sound source**: Sound categories are rough groupings of sounds that can be individually toggled. The sliders in the sound options GUI represent these categories: `master`, `block`, `player` etc. In code, they can be found in the `SoundSource` enum. -- **Sound definition**: A mapping of a sound event to one or multiple sound objects, plus some optional metadata. Sound definitions are located in a namespace's [`sounds.json` file][soundsjson]. -- **Sound object**: A JSON object consisting of a sound file location, plus some optional metadata. -- **Sound file**: An on-disk sound file. Minecraft only supports `.ogg` sound files. +- **声音事件**:声音事件是一个在代码中的触发器,它告诉声音引擎播放某个特定的声音。`SoundEvent`也是你需要注册到游戏中的东西。 +- **声音类别**或**声音源**:声音类别是声音的粗略分组,可以单独切换。声音选项GUI中的滑块代表这些类别:`master`(主音量)、`block`(方块)、`player`(玩家)等等。在代码中,它们可以在`SoundSource`枚举中找到。 +- **声音定义**:将声音事件映射到一个或多个声音对象的映射,加上一些可选的元数据。声音定义位于命名空间的[`sounds.json`文件][soundsjson]中。 +- **声音对象**:由声音文件位置加上一些可选的元数据组成的JSON对象。 +- **声音文件**:磁盘上的声音文件。Minecraft仅支持`.ogg`格式的声音文件。 :::danger -Due to the implementation of OpenAL (Minecraft's audio library), for your sound to have attenuation - that is, for it to get quieter and louder depending on the player's distance to it -, your sound file must be mono (single channel). Stereo (multichannel) sound files will not be subject to attenuation and always play at the player's location, making them ideal for ambient sounds and background music. See also [MC-146721][bug]. +由于OpenAL(Minecraft的音频库)的实现方式,为了让你的声音具有衰减效果——即根据玩家与声源的距离声音变小或变大——你的声音文件必须是单声道(单通道)。立体声(多通道)声音文件不会受到衰减的影响,并且总是在玩家的位置播放,这使它们成为环境声音和背景音乐的理想选择。也看看[MC-146721][bug]。 ::: -## Creating `SoundEvent`s +## 创建`SoundEvent` -`SoundEvent`s are [registered objects][registration], meaning that they must be registered to the game through a `DeferredRegister` and be singletons: +`SoundEvent`是[注册对象][registration],意味着它们必须通过`DeferredRegister`注册到游戏中,并且是单例的: ```java public class MySoundsClass { @@ -41,7 +41,7 @@ public class MySoundsClass { } ``` -Of course, don't forget to add your registry to the [mod event bus][modbus] in the [mod constructor][modctor]: +当然,不要忘记在[模组构造器][modctor]中将你的注册表添加到[模组事件总线][modbus]中: ```java public ExampleMod(IEventBus modBus) { @@ -50,13 +50,13 @@ public ExampleMod(IEventBus modBus) { } ``` -And voilà, you have a sound event! +然后,你就有了一个声音事件! ## `sounds.json` -_See also: [sounds.json][mcwikisounds] on the [Minecraft Wiki][mcwiki]_ +_另见:[Minecraft Wiki][mcwiki]上的[sounds.json][mcwikisounds]_ -Now, to connect your sound event to actual sound files, we need to create sound definitions. All sound definitions for a namespace are stored in a single file named `sounds.json`, also known as the sound definitions file, directly in the namespace's root. Every sound definition is a mapping of sound event id (e.g. `my_sound`) to a JSON sound object. Note that the sound event ids do not specify a namespace, as that is already determined by the namespace the sound definitions file is in. An example `sounds.json` would look something like this: +现在,为了将你的声音事件连接到实际的声音文件,我们需要创建声音定义。一个命名空间的所有声音定义都存储在一个名为`sounds.json`的文件中,也就是声音定义文件,直接放在命名空间的根目录下。每个声音定义都是声音事件id(如`my_sound`)到JSON声音对象的映射。注意,声音事件id不指定命名空间,因为这已经由声音定义文件所在的命名空间确定。一个示例的`sounds.json`看起来像这样: ```json5 { @@ -106,11 +106,11 @@ Now, to connect your sound event to actual sound files, we need to create sound } ``` -### Merging +### 合并 -Unlike most other resource files, `sounds.json` do not overwrite values in packs below them. Instead, they are merged together and then interpreted as one combined `sounds.json` file. Consider sounds `sound_1`, `sound_2`, `sound_3` and `sound_4` being defined in two `sounds.json` files from two different resource packs RP1 and RP2, where RP2 is placed below RP1: +与大多数其他资源文件不同,`sounds.json`文件不会覆盖它们下面的包中的值。相反,它们被合并在一起,然后解释为一个组合的`sounds.json`文件。考虑在两个不同资源包RP1和RP2中的两个`sounds.json`文件里定义了声音`sound_1`、`sound_2`、`sound_3`和`sound_4`,其中RP2位于RP1下面: -`sounds.json` in RP1: +RP1中的`sounds.json`: ```json5 { @@ -139,7 +139,7 @@ Unlike most other resource files, `sounds.json` do not overwrite values in packs } ``` -`sounds.json` in RP2: +RP2中的`sounds.json`: ```json5 { @@ -168,7 +168,7 @@ Unlike most other resource files, `sounds.json` do not overwrite values in packs } ``` -The combined (merged) `sounds.json` file the game would then go on and use to load sounds would look something look this (only in memory, this file is never written anywhere): +游戏最终会使用的组合(合并)的`sounds.json`文件,在内存中看起来会像这样(这个文件从不会被写在任何地方): ```json5 { @@ -202,43 +202,43 @@ The combined (merged) `sounds.json` file the game would then go on and use to lo } ``` -## Playing Sounds +## 播放声音 -Minecraft offers various methods to play sounds, and it is sometimes unclear which one should be used. All methods accept a `SoundEvent`, which can either be your own or a vanilla one (vanilla sound events are found in the `SoundEvents` class). For the following method descriptions, client and server refer to the [logical client and logical server][sides], respectively. +Minecraft提供了各种播放声音的方法,有时不清楚应该使用哪一个。所有的方法都接受一个`SoundEvent`,可以是你自己的,也可以是原版的(原版声音事件可以在`SoundEvents`类中找到)。以下的方法描述,客户端和服务器分别指的是[逻辑客户端和逻辑服务器][sides]。 ### `Level` - `playSound(Player player, double x, double y, double z, SoundEvent soundEvent, SoundSource soundSource, float volume, float pitch)` - - Client behavior: If the player passed in is the local player, play the sound event to the player at the given location, otherwise no-op. - - Server behavior: A packet instructing the client to play the sound event to the player at the given location is sent to all players except the one passed in. - - Usage: Call from client-initiated code that will run on both sides. The server not playing it to the initiating player prevents playing the sound event twice to them. Alternatively, call from server-initiated code (e.g. a [block entity][be]) with a `null` player to play the sound to everyone. + - 客户端行为:如果传入的玩家是本地玩家,则在给定位置为玩家播放声音事件,否则无操作。 + - 服务器行为:向所有除传入的玩家以外的玩家发送一个数据包,指示客户端在给定位置为玩家播放声音事件。 + - 用法:从将在两侧运行的客户端启动的代码中调用。服务器不会对发起播放的玩家播放声音,以防止对他们播放两次声音事件。或者,从服务器启动的代码(如[block entity][be])中调用,并使用`null`作为玩家,对所有人播放声音。 - `playSound(Player player, BlockPos pos, SoundEvent soundEvent, SoundSource soundSource, float volume, float pitch)` - - Forwards to the first method with `x`, `y` and `z` taking the values of `pos.getX() + 0.5`, `pos.getY() + 0.5` and `pos.getZ() + 0.5`, respectively. + - 转发到第一个方法,其中`x`、`y`和`z`分别取`pos.getX() + 0.5`、`pos.getY() + 0.5`和`pos.getZ() + 0.5`的值。 - `playLocalSound(double x, double y, double z, SoundEvent soundEvent, SoundSource soundSource, float volume, float pitch, boolean distanceDelay)` - - Client behavior: Plays the sound to the player at the given location. Does not send anything to the server. If `distanceDelay` is `true`, delays the sound based on the distance to the player. - - Server behavior: No-op. - - Usage: Called from custom packets sent from the server. Vanilla uses this for thunder sounds. + - 客户端行为:在给定位置为玩家播放声音。不向服务器发送任何内容。如果`distanceDelay`是`true`,则根据距离玩家的距离延迟声音。 + - 服务器行为:无操作。 + - 用法:从服务器发送的自定义数据包中调用。原版用这个方法播放雷声。 ### `ClientLevel` - `playLocalSound(BlockPos pos, SoundEvent soundEvent, SoundSource soundSource, float volume, float pitch, boolean distanceDelay)` - - Forwards to `Level#playLocalSound` with `x`, `y` and `z` taking the values of `pos.getX() + 0.5`, `pos.getY() + 0.5` and `pos.getZ() + 0.5`, respectively. + - 转发到`Level#playLocalSound`,其中`x`、`y`和`z`分别取`pos.getX() + 0.5`、`pos.getY() + 0.5`和`pos.getZ() + 0.5`的值。 ### `Entity` - `playSound(SoundEvent soundEvent, float volume, float pitch)` - - Forwards to `Level#playSound` with `null` as the player, `SoundSource.ENTITY` as the sound source, the entity's position for x/y/z, and the other parameters passed in. + - 转发到`Level#playSound`,其中玩家为`null`,声音源为`SoundSource.ENTITY`,实体的位置为x/y/z,其他参数为传入的参数。 ### `Player` -- `playSound(SoundEvent soundEvent, float volume, float pitch)` (overrides the method in `Entity`) - - Forwards to `Level#playSound` with `this` as the player, `SoundSource.PLAYER` as the sound source, the player's position for x/y/z, and the other parameters passed in. As such, the client/server behavior mimics the one from `Level#playSound`: - - Client behavior: Play the sound event to the client player at the given location. - - Server behavior: Play the sound event to everyone near the given location except the player this method was called on. +- `playSound(SoundEvent soundEvent, float volume, float pitch)` (覆盖`Entity`中的方法) + - 转发到`Level#playSound`,其中玩家为`this`,声音源为`SoundSource.PLAYER`,玩家的位置为x/y/z,其他参数为传入的参数。因此,客户端/服务器的行为模仿`Level#playSound`: + - 客户端行为:在给定位置为客户端玩家播放声音事件。 + - 服务器行为:除了调用此方法的玩家,对给定位置附近的所有人播放声音事件。 -## Datagen +## 数据生成 -Sound files themselves can of course not be [datagenned][datagen], but `sounds.json` files can. To do so, we extend `SoundDefinitionsProvider` and override the `registerSounds()` method: +声音文件本身当然不能被[数据生成][datagen],但是`sounds.json`文件可以。为了做到这一点,我们扩展`SoundDefinitionsProvider`并覆盖`registerSounds()`方法: ```java public class MySoundDefinitionsProvider extends SoundDefinitionsProvider { @@ -283,7 +283,7 @@ public class MySoundDefinitionsProvider extends SoundDefinitionsProvider { } ``` -As with every data provider, don't forget to register the provider to the event: +与所有数据提供器一样,不要忘记将提供者注册到事件中: ```java @SubscribeEvent From c5ec577a74cb489a91c1fd26c4a9f01b6d5c3152 Mon Sep 17 00:00:00 2001 From: DaZiDian <113085496+DaZiDian@users.noreply.github.com> Date: Wed, 8 May 2024 15:46:24 +0800 Subject: [PATCH 78/87] Update textures.md --- docs/resources/client/textures.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/docs/resources/client/textures.md b/docs/resources/client/textures.md index f9579a2c6..896eb020c 100644 --- a/docs/resources/client/textures.md +++ b/docs/resources/client/textures.md @@ -1,12 +1,12 @@ -# Textures +# 纹理 -All textures in Minecraft are PNG files located within a namespace's `textures` folder. JPG, GIF and other image formats are not supported. The path of [resource locations][rl] referring to textures is generally relative to the `textures` folder, so for example, the resource location `examplemod:block/example_block` refers to the texture file at `assets/examplemod/textures/block/example_block.png`. +Minecraft中的所有纹理都是PNG文件,位于命名空间的`textures`文件夹内。不支持JPG、GIF和其他图像格式。指向纹理的[资源位置][rl]的路径通常相对于`textures`文件夹,所以例如,资源位置`examplemod:block/example_block`指的是`assets/examplemod/textures/block/example_block.png`路径的纹理文件。 -Textures should generally be in sizes that are powers of two, for example 16x16 or 32x32. Unlike older versions, modern Minecraft natively supports block and item texture sizes greater than 16x16. For textures that are not in powers of two that you render yourself anyway (for example GUI backgrounds), create an empty file in the next available power-of-two size (often 256x256), and add your texture in the top left corner of that file, leaving the rest of the file empty. The actual size of the drawn texture can then be set in the code that uses the texture. +纹理的大小通常应该是2的幂,例如16x16或32x32。与旧版本不同,现代Minecraft本身就支持大于16x16的方块和物品纹理大小。对于那些你自己渲染出来的不是2的幂的纹理(例如GUI背景),可以在下一可用的2的幂大小(通常是256x256)创建一个空文件,然后在该文件的左上角添加你的纹理,让文件的其余部分保持空白。然后,可以在使用该纹理的代码中设置实际的纹理大小。 -## Texture Metadata +## 纹理元数据 -Texture metadata can be specified in a file named exactly the same as the texture, with an additional `.mcmeta` suffix. For example, an animated texture at `textures/block/example.png` would need an accompanying `textures/block/example.png.mcmeta` file. The `.mcmeta` file has the following format (all optional): +纹理元数据可以在一个与纹理完全同名的文件中指定,但需要添加一个`.mcmeta`后缀。例如,位于`textures/block/example.png`的动画纹理需要一个伴随的`textures/block/example.png.mcmeta`文件。`.mcmeta`文件有以下格式(所有的都是可选的): ```json5 { @@ -45,11 +45,11 @@ Texture metadata can be specified in a file named exactly the same as the textur } ``` -## Animated Textures +## 动画纹理 -Minecraft natively supports animated textures for blocks and items. Animated textures consist of a texture file where the different animation stages are located below each other (for example, an animated 16x16 texture with 8 phases would be represented through a 16x128 PNG file). +Minecraft本身支持方块和物品的动画纹理。动画纹理由一个纹理文件组成,不同的动画阶段位于彼此的下方(例如,一个带有8个阶段的动画16x16纹理将通过一个16x128的PNG文件表示)。 -To actually be animated and not just be displayed as a distorted texture, there must be an `animation` object in the texture metadata. The sub-object can be empty, but may contain the following optional entries: +为了确实被动画化而不仅仅是显示为扭曲的纹理,纹理元数据中必须有一个`animation`对象。子对象可以是空的,但可以包含以下可选条目: ```json5 { From e1577ccf6c1387a01c245db48e9a9d181ceb4362 Mon Sep 17 00:00:00 2001 From: DaZiDian <113085496+DaZiDian@users.noreply.github.com> Date: Wed, 8 May 2024 15:52:54 +0800 Subject: [PATCH 79/87] Update custom.md --- docs/resources/server/recipes/custom.md | 61 ++++++++++++------------- 1 file changed, 30 insertions(+), 31 deletions(-) diff --git a/docs/resources/server/recipes/custom.md b/docs/resources/server/recipes/custom.md index 1c77d2109..fdf8a7fdd 100644 --- a/docs/resources/server/recipes/custom.md +++ b/docs/resources/server/recipes/custom.md @@ -1,26 +1,26 @@ -Custom Recipes +自定义配方 ============== -Every recipe definition is made up of three components: the `Recipe` implementation which holds the data and handles the execution logic with the provided inputs, the `RecipeType` which represents the category or context the recipe will be used in, and the `RecipeSerializer` which handles decoding and network communication of the recipe data. How one chooses to use the recipe is up to the implementor. +每个配方定义都由三个组件组成:持有数据和处理提供的输入的执行逻辑的`Recipe`实现,表示配方将在其中使用的类别或上下文的`RecipeType`,以及处理配方数据的解码和网络通信的`RecipeSerializer`。如何选择使用配方取决于实现者。 -Recipe +配方 ------ -The `Recipe` interface describes the recipe data and the execution logic. This includes matching the inputs and providing the associated result. As the recipe subsystem performs item transformations by default, the inputs are supplied through a `Container` subtype. +`Recipe`接口描述了配方数据和执行逻辑。这包括匹配输入和提供相关的结果。由于配方子系统默认执行物品转换,因此通过`Container`子类型提供输入。 -:::caution -The `Container`s passed into the recipe should be treated as if its contents were immutable. Any mutable operations should be performed on a copy of the input through `ItemStack#copy`. +:::note +应该将传入配方的`Container`视为其内容是不可变的。任何可变操作都应该在输入的副本上执行,通过`ItemStack#copy`。 ::: -To be able to obtain a recipe instance from the manager, `#matches` must return true. This method checks against the provided container to see whether the associated inputs are valid. `Ingredient`s can be used for validation by calling `Ingredient#test`. +要能够从管理器获取配方实例,`#matches`必须返回true。此方法检查提供的容器,看看相关的输入是否有效。可以通过调用`Ingredient#test`来使用`Ingredient`进行验证。 -If the recipe has been chosen, it is then built using `#assemble` which may use data from the inputs to create the result. +如果选择了配方,那么就使用`#assemble`进行构建,这可能使用来自输入的数据来创建结果。 -:::tip -`#assemble` should always produce a unique `ItemStack`. If unsure whether `#assemble` does so, call `ItemStack#copy` on the result before returning. +:::note +`#assemble`应该总是产生一个唯一的`ItemStack`。如果不确定`#assemble`是否这样做,那么在返回之前在结果上调用`ItemStack#copy`。 ::: -Most of the other methods are purely for integration with the recipe book. +大多数其他的方法纯粹是为了与配方书的集成。 ```java public record ExampleRecipe(Ingredient input, int data, ItemStack output) implements Recipe { @@ -29,17 +29,17 @@ public record ExampleRecipe(Ingredient input, int data, ItemStack output) implem ``` :::note -While a record is used in the above example, it is not required to do so in your own implementation. +虽然在上面的例子中使用了记录(record),但在您自己的实现中不要求这样做。 ::: RecipeType ---------- -`RecipeType` is responsible for defining the category or context the recipe will be used within. For example, if a recipe was going to be smelted in a furnace, it would have a type of `RecipeType#SMELTING`. Being blasted in a blast furnace would have a type of `RecipeType#BLASTING`. +`RecipeType`负责定义配方将在其中使用的类别或上下文。例如,如果一个配方要在熔炉中熔炼,它会有一个`RecipeType#SMELTING`的类型。在高炉中爆炸熔炼将会有一个`RecipeType#BLASTING`的类型。 -If none of the existing types match what context the recipe will be used within, then a new `RecipeType` must be [registered][forge]. +如果没有现有的类型与配方将要使用的上下文匹配,那么必须[注册][forge]一个新的`RecipeType`。 -The `RecipeType` instance must then be returned by `Recipe#getType` in the new recipe subtype. +然后,新的配方子类型必须通过`Recipe#getType`返回`RecipeType`实例。 ```java // For some RegistryObject EXAMPLE_TYPE @@ -53,17 +53,17 @@ public RecipeType getType() { RecipeSerializer ---------------- -A `RecipeSerializer` is responsible for decoding JSONs and communicating across the network for an associated `Recipe` subtype. Each recipe decoded by the serializer is saved as a unique instance within the `RecipeManager`. A `RecipeSerializer` must be [registered][forge]. +`RecipeSerializer`负责解码JSON文件,并且负责与网络上相关的`Recipe`子类型进行通信。每个由序列化器解码的配方被保存为`RecipeManager`内的一个唯一实例。必须[注册][forge]一个`RecipeSerializer`。 -Only three methods need to be implemented for a `RecipeSerializer`: +对于`RecipeSerializer`,只需要实现三个方法: - Method | Description - :---: | :--- -fromJson | Decodes a JSON into the `Recipe` subtype. -toNetwork | Encodes a `Recipe` to the buffer to send to the client. The recipe identifier does not need to be encoded. -fromNetwork | Decodes a `Recipe` from the buffer sent from the server. The recipe identifier does not need to be decoded. + 方法 | 描述 + :---: | :--- +fromJson | 将JSON解码为`Recipe`子类型。 +toNetwork | 将`Recipe`编码到缓冲区以发送给客户端。配方标识符不需要被编码。 +fromNetwork | 从服务器发送的缓冲区解码`Recipe`。配方标识符不需要被解码。 -The `RecipeSerializer` instance must then be returned by `Recipe#getSerializer` in the new recipe subtype. +然后,`RecipeSerializer`实例必须通过新配方子类型的`Recipe#getSerializer`返回。 ```java // For some RegistryObject EXAMPLE_SERIALIZER @@ -75,14 +75,13 @@ public RecipeSerializer getSerializer() { ``` :::tip -There are some useful methods to make reading and writing data for recipes easier. `Ingredient`s can use `#fromJson`, `#toNetwork`, and `#fromNetwork` while `ItemStack`s can use `CraftingHelper#getItemStack`, `FriendlyByteBuf#writeItem`, and `FriendlyByteBuf#readItem`. +有一些有用的方法可以使阅读和写入配方数据更加容易。`Ingredient`可以使用`#fromJson`、`#toNetwork`和`#fromNetwork`,而`ItemStack`可以使用`CraftingHelper#getItemStack`、`FriendlyByteBuf#writeItem`和`FriendlyByteBuf#readItem`。 ::: -Building the JSON +构建JSON ----------------- -Custom Recipe JSONs are stored in the same place as other [recipes][json]. The specified `type` should represent the registry name of the **recipe serializer**. Any additional data is specified by the serializer during decoding. - +自定义配方JSON存储在与其他[配方][json]相同的地方。指定的`type`应表示**配方序列化器**的注册名。在解码过程中,序列化器指定任何额外的数据。 ```js { // The custom serializer registry name @@ -97,10 +96,10 @@ Custom Recipe JSONs are stored in the same place as other [recipes][json]. The s } ``` -Non-Item Logic +非物品逻辑 -------------- -If items are not used as part of the input or result of a recipe, then the normal methods provided in [`RecipeManager`][manager] will not be useful. Instead, an additional method for testing a recipe's validity and/or supplying the result should be added to the custom `Recipe` instance. From there, all the recipes for that specific `RecipeType` can be obtained via `RecipeManager#getAllRecipesFor` and then checked and/or supplied the result using the newly implemented methods. +如果物品不被用作配方的输入或结果的一部分,那么[`RecipeManager`][manager]提供的正常方法将不会有用。相反,应该向自定义`Recipe`实例添加一个额外的方法,用于测试配方的有效性和/或提供结果。从那里,可以通过`RecipeManager#getAllRecipesFor`获取特定`RecipeType`的所有配方,然后使用新实现的方法检查和/或提供结果。 ```java // In some Recipe subimplementation ExampleRecipe @@ -121,10 +120,10 @@ public Optional getRecipeFor(Level level, BlockPos pos) { } ``` -Data Generation +数据生成 --------------- -All custom recipes, regardless of input or output data, can be created into a `FinishedRecipe` for [data generation][datagen] using the `RecipeProvider`. +所有自定义配方,无论输入或输出数据如何,都可以使用`RecipeProvider`将其创建为用于[数据生成][datagen]的`FinishedRecipe`。 [forge]: ../../../concepts/registries.md#methods-for-registering [json]: https://minecraft.wiki/w/Recipe#JSON_format From 7a917f1bceaddc6d2cf194db05163464b943ab84 Mon Sep 17 00:00:00 2001 From: DaZiDian <113085496+DaZiDian@users.noreply.github.com> Date: Wed, 8 May 2024 15:57:54 +0800 Subject: [PATCH 80/87] Update incode.md --- docs/resources/server/recipes/incode.md | 30 ++++++++++++------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/docs/resources/server/recipes/incode.md b/docs/resources/server/recipes/incode.md index 00497a10a..907a03d39 100644 --- a/docs/resources/server/recipes/incode.md +++ b/docs/resources/server/recipes/incode.md @@ -1,33 +1,33 @@ -Non-Datapack Recipes +非数据包配方 ==================== -Not all recipes are simplistic enough or migrated to using data-driven recipes. Some subsystems still need to be patched within the codebase to provide support for adding new recipes. +并非所有配方都足够简单或已迁移到使用数据驱动的配方。一些子系统仍需要在代码库中进行修补,以支持添加新的配方。 -Brewing Recipes +酿造配方 --------------- -Brewing is one of the few recipes that still exist in code. Brewing recipes are added as part of a bootstrap within `PotionBrewing` for their containers, container recipes, and potion mixes. To expand upon the existing system, Forge allows brewing recipes to be added by calling `BrewingRecipeRegistry#addRecipe` in `FMLCommonSetupEvent`. +酿造是仍然存在于代码中的少数配方之一。酿造配方作为`PotionBrewing`内的引导程序的一部分添加,用于其容器、容器配方和药水混合。为了扩展现有系统,Forge允许通过在`FMLCommonSetupEvent`中调用`BrewingRecipeRegistry#addRecipe`来添加酿造配方。 :::caution -`BrewingRecipeRegistry#addRecipe` must be called within the synchronous work queue via `#enqueueWork` as the method is not thread-safe. +`BrewingRecipeRegistry#addRecipe`必须在同步工作队列中通过`#enqueueWork`调用,因为该方法不是线程安全的。 ::: -The default implementation takes in an input ingredient, a catalyst ingredient, and a stack output for a standard implementation. Additionally, an `IBrewingRecipe` instance can be supplied instead to do the transformations. +默认实现接受一个输入成分,一个催化剂成分,和一个堆叠输出以进行标准实现。此外,也可以提供一个`IBrewingRecipe`实例来执行转换。 ### IBrewingRecipe -`IBrewingRecipe` is a pseudo-[`Recipe`][recipe] interface that checks whether the input and catalyst is valid and provides the associated output if so. This is provided through `#isInput`, `#isIngredient`, and `#getOutput` respectively. The output method has access to the input and catalyst stacks to construct the result. +`IBrewingRecipe`是一种伪[`Recipe`][recipe]接口,它检查输入和催化剂是否有效,并在满足条件时提供相关的输出。这通过`#isInput`、`#isIngredient`和`#getOutput`实现。输出方法可以访问输入和催化剂堆叠以构造结果。 :::caution -When copying data between `ItemStack`s or `CompoundTag`s, make sure to use their respective `#copy` methods to create unique instances. +在`ItemStack`或`CompoundTag`之间复制数据时,确保使用它们各自的`#copy`方法创建唯一的实例。 ::: -There is no wrapper for adding additional potion containers or potion mixes similar to vanilla. A new `IBrewingRecipe` implementation will need to be added to replicate this behavior. +没有类似于原版的添加额外药水容器或药水混合的包装器。需要添加一个新的`IBrewingRecipe`实现来复制这种行为。 -Anvil Recipes +铁砧配方 ------------- -Anvils are responsible for taking a damaged input and given some material or a similar input, remove some of the damage on the input result. As such, its system is not easily data-driven. However, as anvil recipes are an input with some number of materials equals some output when the user has the required experience levels, it can be modified to create a pseudo-recipe system via `AnvilUpdateEvent`. This takes in the input and materials and allows the modder to specify the output, experience level cost, and number of materials to use for the output. The event can also prevent any output by [canceling][cancel] it. +铁砧负责接受一个受损的输入,给出一些材料或类似的输入,减少输入结果上的一些损伤。因此,其系统不容易被数据驱动。然而,因为铁砧配方是一个输入物品加上一些数量的材料等于一些输出物品,当用户有足够的经验等级时,它可以通过`AnvilUpdateEvent`修改为创建一个伪配方系统。这取决于输入和材料,并允许开发者指定输出、经验等级成本,以及用于输出的材料数量。事件还可以通过[取消][cancel]来阻止任何输出。 ```java // Checks whether the left and right items are correct @@ -41,15 +41,15 @@ public void updateAnvil(AnvilUpdateEvent event) { } ``` -The update event must be [attached] to the Forge event bus. +更新事件必须[附加]到Forge事件总线上。 -Loom Recipes +织布机配方 ------------ -Looms are responsible for applying a dye and pattern (either from the loom or from an item) to a banner. While the banner and the dye must be a `BannerItem` or `DyeItem` respectively, custom patterns can be created and applied in the loom. Banner Patterns can be created by [registering] a `BannerPattern`. +织布机负责将染料和图案(来自织布机或来自物品)应用于旗帜。虽然旗帜和染料必须分别是`BannerItem`或`DyeItem`,但可以在织布机中创建和应用自定义图案。可以通过[注册]一个`BannerPattern`来创建旗帜图案。 :::caution -`BannerPattern`s which are in the `minecraft:no_item_required` tag appear as an option in the loom. Patterns not in this tag must have an accompanying `BannerPatternItem` to be used along with an associated tag. +在`minecraft:no_item_required`标签中的`BannerPattern`会作为一个选项出现在织布机中。不在此标签中的图案必须有一个相应的`BannerPatternItem`,并且有一个相关联的标签,才能使用。 ::: ```java From 28cdab852fcd9d7deb1661d92044906d6d734ceb Mon Sep 17 00:00:00 2001 From: DaZiDian <113085496+DaZiDian@users.noreply.github.com> Date: Wed, 8 May 2024 16:04:31 +0800 Subject: [PATCH 81/87] Update ingredients.md --- docs/resources/server/recipes/ingredients.md | 60 ++++++++++---------- 1 file changed, 30 insertions(+), 30 deletions(-) diff --git a/docs/resources/server/recipes/ingredients.md b/docs/resources/server/recipes/ingredients.md index 7d5d3629d..7c5e661e6 100644 --- a/docs/resources/server/recipes/ingredients.md +++ b/docs/resources/server/recipes/ingredients.md @@ -1,20 +1,20 @@ -Ingredients +配料 =========== -`Ingredient`s are predicate handlers for item-based inputs which check whether a certain `ItemStack` meets the condition to be a valid input in a recipe. All [vanilla recipes][recipes] that take inputs use an `Ingredient` or a list of `Ingredient`s, which is then merged into a single `Ingredient`. +`Ingredient`是针对基于物品的输入的谓词处理器,它检查某个`ItemStack`是否满足作为配方有效输入的条件。所有采用输入的[原版配方][recipes]都使用一个`Ingredient`或一系列`Ingredient`,然后合并为一个单一的`Ingredient`。 -Custom Ingredients +自定义配料 ------------------ -Custom ingredients can be specified by setting `type` to the name of the [ingredient's serializer][serializer], with the exception of [compound ingredients][compound]. When no type is specified, `type` defaults to the vanilla ingredient `minecraft:item`. Custom ingredients can also easily be used in [data generation][datagen]. +可以通过将`type`设置为[配料序列化器][serializer]的名称来指定自定义配料,[复合配料][compound]除外。当未指定类型时,`type`默认为原版配料`minecraft:item`。自定义配料也可以轻松地用于[数据生成][datagen]。 -### Forge Types +### Forge 类型 -Forge provides a few additional `Ingredient` types for programmers to implement. +Forge为程序员提供了一些额外的`Ingredient`类型。 #### CompoundIngredient -Though they are functionally identical, Compound ingredients replaces the way one would implement a list of ingredients would in a recipe. They work as a set OR where the passed in stack must be within at least one of the supplied ingredients. This change was made to allow custom ingredients to work correctly within lists. As such, **no type** needs to be specified. +虽然它们在功能上是相同的,但复合配料替换了在配方中实现配料列表的方式。它们作为一个集合OR操作,传入的堆叠必须至少在提供的配料中的一个中。这个更改是为了让自定义配料能在列表中正常工作。因此,**无需**指定类型。 ```js // For some input @@ -32,7 +32,7 @@ Though they are functionally identical, Compound ingredients replaces the way on #### StrictNBTIngredient -`StrictNBTIngredient`s compare the item, damage, and the share tags (as defined by `IForgeItem#getShareTag`) on an `ItemStack` for exact equivalency. This can be used by specifying the `type` as `forge:nbt`. +`StrictNBTIngredient`会比较`ItemStack`上的物品、损坏和共享标签(由`IForgeItem#getShareTag`定义)以确保完全等价。这可以通过将`type`指定为`forge:nbt`来使用。 ```js // For some input @@ -47,7 +47,7 @@ Though they are functionally identical, Compound ingredients replaces the way on ### PartialNBTIngredient -`PartialNBTIngredient`s are a looser version of [`StrictNBTIngredient`][nbt] as they compare against a single or set of items and only keys specified within the share tag (as defined by `IForgeItem#getShareTag`). This can be used by specifying the `type` as `forge:partial_nbt`. +`PartialNBTIngredient`是[`StrictNBTIngredient`][nbt]的一个更宽松的版本,因为它们只比较一个或一组物品,并且只比较在共享标签(由`IForgeItem#getShareTag`定义)中指定的键。这可以通过将`type`指定为`forge:partial_nbt`来使用。 ```js // For some input @@ -76,7 +76,7 @@ Though they are functionally identical, Compound ingredients replaces the way on ### IntersectionIngredient -`IntersectionIngredient`s work as a set AND where the passed in stack must match all supplied ingredients. There must be at least two ingredients supplied to this. This can be used by specifying the `type` as `forge:intersection`. +`IntersectionIngredient`作为一个集合AND操作,传入的堆叠必须匹配所有提供的配料。至少必须向此提供两种配料。这可以通过将`type`指定为`forge:intersection`来使用。 ```js // For some input @@ -98,7 +98,7 @@ Though they are functionally identical, Compound ingredients replaces the way on ### DifferenceIngredient -`DifferenceIngredient`s work as a set subtraction (SUB) where the passed in stack must match the first ingredient but must not match the second ingredient. This can be used by specifying the `type` as `forge:difference`. +`DifferenceIngredient`作为一个集合减法(SUB)操作,传入的堆叠必须匹配第一个配料,但不得匹配第二个配料。这可以通过将`type`指定为`forge:difference`来使用。 ```js // For some input @@ -113,40 +113,40 @@ Though they are functionally identical, Compound ingredients replaces the way on } ``` -Creating Custom Ingredients +创建自定义配料 --------------------------- -Custom ingredients can be created by implementing `IIngredientSerializer` for the created `Ingredient` subclass. +可以通过为创建的`Ingredient`子类实现`IIngredientSerializer`来创建自定义配料。 -:::tip -Custom ingredients should subclass `AbstractIngredient` as it provides some useful abstractions for ease of implementation. +:::提示 +自定义配料应该子类化`AbstractIngredient`,因为它为实施提供了一些有用的抽象。 ::: -### Ingredient Subclass +### Ingredient子类 -There are three important methods to implement for each ingredient subclass: +每个配料子类要实施三个重要的方法: - Method | Description + 方法 | 描述 :---: | :--- -getSerializer | Returns the [serializer] used to read and write the ingredient. -test | Returns true if the input is valid for this ingredient. -isSimple | Returns false if the ingredient matches on the stack's tag. `AbstractIngredient` subclasses will need to define this behavior, while `Ingredient` subclasses return `true` by default. +getSerializer | 返回用于读写配料的[序列化器]。 +test | 如果输入对于这个配料是有效的,返回true。 +isSimple | 如果配料匹配堆栈的标签,则返回false。`AbstractIngredient`子类需要定义这种行为,而`Ingredient`子类默认返回`true`。 -All other defined methods are left as an exercise to the reader to use as required for the ingredient subclass. +所有其他定义的方法留给读者根据需要使用配料子类。 ### IIngredientSerializer -`IIngredientSerializer` subtypes must implement three methods: +`IIngredientSerializer`子类型必须实现三个方法: - Method | Description + 方法 | 描述 :---: | :--- -parse (JSON) | Converts a `JsonObject` to an `Ingredient`. -parse (Network) | Reads the network buffer to decode an `Ingredient`. -write | Writes an `Ingredient` to the network buffer. +parse (JSON) | 将`JsonObject`转化为`Ingredient`。 +parse (Network) | 读取网络缓冲区以解码一个`Ingredient`。 +write | 将一个`Ingredient`写入网络缓冲区。 -Additionally, `Ingredient` subclasses should implement `Ingredient#toJson` for use with [data generation][datagen]. `AbstractIngredient` subclasses make `#toJson` an abstract method requiring the method to be implemented. +此外,`Ingredient`子类应该实现`Ingredient#toJson`以用于[data generation][datagen]。`AbstractIngredient`子类将`#toJson`设置为抽象方法,要求实现该方法。 -Afterwards, a static instance should be declared to hold the initialized serializer and then registered using `CraftingHelper#register` either during the `RegisterEvent` for `RecipeSerializer`s or during `FMLCommonSetupEvent`. The `Ingredient` subclass return the static instance of the serializer in `Ingredient#getSerializer`. +然后,应声明一个静态实例来保存初始化的序列化器,然后使用`CraftingHelper#register`在`RecipeSerializer`的`RegisterEvent`或`FMLCommonSetupEvent`期间注册。`Ingredient`子类在`Ingredient#getSerializer`中返回序列化器的静态实例。 ```java // In some serializer class @@ -167,7 +167,7 @@ public IIngredientSerializer getSerializer() { ``` :::tip -If using `FMLCommonSetupEvent` to register an ingredient serializer, it must be enqueued to the synchronous work queue via `FMLCommonSetupEvent#enqueueWork` as `CraftingHelper#register` is not thread-safe. +如果使用`FMLCommonSetupEvent`来注册配料序列化器,必须通过`FMLCommonSetupEvent#enqueueWork`将其加入到同步工作队列,因为`CraftingHelper#register`不是线程安全的。 ::: [recipes]: https://minecraft.wiki/w/Recipe#List_of_recipe_types From 88cfd24e20a848a9d62c8e37caaba5883ca54db1 Mon Sep 17 00:00:00 2001 From: DaZiDian <113085496+DaZiDian@users.noreply.github.com> Date: Wed, 8 May 2024 16:17:45 +0800 Subject: [PATCH 82/87] Update advancements.md --- docs/resources/server/advancements.md | 49 +++++++++++++-------------- 1 file changed, 24 insertions(+), 25 deletions(-) diff --git a/docs/resources/server/advancements.md b/docs/resources/server/advancements.md index 5cb920bf5..b5e4d59e5 100644 --- a/docs/resources/server/advancements.md +++ b/docs/resources/server/advancements.md @@ -1,14 +1,14 @@ -# Advancements +# 成就 -Advancements are tasks that can be achieved by the player which may advance the progress of the game. Advancements can trigger based on any action the player may be directly involved in. +成就是玩家可以完成的任务,可能会推进游戏的进程。玩家可能直接参与的任何动作都可以触发成就。 -All advancement implementations within vanilla are data driven via JSON. This means that a mod is not necessary to create a new advancement, only a [data pack][datapack]. A full list on how to create and put these advancements within the mod's `resources` can be found on the [Minecraft Wiki][wiki]. Additionally, advancements can be [loaded conditionally and defaulted][conditional] depending on what information is present (mod loaded, item exists, etc.). As with other data driven features, advancements can be generated via [data generators][datagen]. +所有原版内的成就实现都通过JSON进行数据驱动。这意味着创建新的成就不需要mod,只需要一个[data pack][datapack]。关于如何创建并将这些成就放入mod的`resources`的完整列表,可以在[Minecraft Wiki][wiki]上找到。此外,根据存在的信息(加载的模组,存在的项目等),成就可以有条件地[加载和默认][conditional]。和其他数据驱动的功能一样,成就可以通过[data generators][datagen]来生成。 -## Advancement Criteria +## 成就条件 -To unlock an advancement, the specified criteria must be met. Criteria are tracked through triggers which execute when a certain action is performed: killing an entity, changing an inventory, breading animals, etc. Any time an advancement is loaded into the game, the criteria defined are read and added as listeners to the trigger. Afterwards a trigger function is called (usually named `#trigger`) which checks all listeners as to whether the current state meets the conditions of the advancement criteria. The criteria listeners for the advancement are only removed once the advancement has been obtained by completing all requirements. +为了解锁一个成就,必须满足指定的条件。条件通过触发器进行跟踪,当执行某个动作时触发:杀死实体,改变库存,繁殖动物等。每当一个成就被加载到游戏中,定义的条件就会被读取并添加为触发器的监听器。然后调用一个触发器函数(通常命名为`#trigger`),检查所有监听器是否当前状态满足成就条件。只有在完成所有要求并获得成就后,成就的条件监听器才会被移除。 -Requirements are defined as an array of string arrays representing the name of the criteria specified on the advancement. An advancement is completed once one string array of criteria has been met: +要求被定义为一个字符串数组的数组,表示在成就上指定的条件的名称。一旦满足一个条件的字符串数组,成就就完成了: ```js // In some advancement JSON @@ -37,17 +37,17 @@ Requirements are defined as an array of string arrays representing the name of t ] ``` -A list of criteria triggers defined by vanilla can be found in `CriteriaTriggers`. Additionally, the JSON formats are defined on the [Minecraft Wiki][triggers]. +原版定义的条件触发器列表可以在`CriteriaTriggers`中找到。此外,JSON格式在[Minecraft Wiki][triggers]上有定义。 -### Custom Criteria Triggers +### 自定义条件触发器 -Custom criteria triggers are made up of two parts: the trigger, which is activated in code at some point you specify by calling `#trigger`, and the instance which defines the conditions under which the trigger should award the criterion. The trigger extends `SimpleCriterionTrigger` while the instance implements `SimpleCriterionTrigger.SimpleInstance`. The generic value `T` represents the trigger instance type. +自定义条件触发器由两部分组成:触发器,它在你指定的某个时候在代码中被激活,通过调用`#trigger`,和实例,它定义了触发器应在何种条件下授予条件。触发器扩展了`SimpleCriterionTrigger`,而实例实现了`SimpleCriterionTrigger.SimpleInstance`。泛型值`T`代表触发器实例类型。 -### The SimpleCriterionTrigger.SimpleInstance Implementation +### The SimpleCriterionTrigger.SimpleInstance实现 -The `SimpleCriterionTrigger.SimpleInstance` represents a single criteria defined in the `criteria` object. Trigger instances are responsible for holding the defined conditions, and returning whether the inputs match the condition. +`SimpleCriterionTrigger.SimpleInstance`代表在`criteria`对象中定义的单个条件。触发器实例负责保存定义的条件,并返回输入是否匹配条件。 -Conditions are usually passed in through the constructor. The `SimpleCriterionTrigger.SimpleInstance` interface requires only one function, called `#player`, which returns the conditions the player must meet as an `Optional`. If the subclass is a Java record with a `player` parameter of this type (as below), the automatically generated `#player` method will suffice. +条件通常通过构造函数传入。`SimpleCriterionTrigger.SimpleInstance`接口只需要一个函数,名为`#player`,它返回玩家必须满足的条件,作为一个`Optional`。如果子类是一个Java记录,有一个这种类型的`player`参数(如下所示),那么自动生成的`#player`方法就足够了。 ```java public record ExampleTriggerInstance(Optional player, ItemPredicate item) implements SimpleCriterionTrigger.SimpleInstance { @@ -56,7 +56,7 @@ public record ExampleTriggerInstance(Optional player, Ite ``` :::note -Typically, trigger instances have static helper methods which construct the full `Criterion` object from the arguments to the instance. This allows these instances to be easily created during data generation, but are optional. +通常,触发器实例具有静态辅助方法,这些方法可以根据实例的参数构造完整的`Criterion`对象。这使得这些实例可以在数据生成期间轻松创建,但这是可选的。 ```java // In this example, EXAMPLE_TRIGGER is a DeferredHolder> @@ -66,7 +66,7 @@ public static Criterion instance(ContextAwarePredicate p ``` ::: -Finally, a method should be added which takes in the current data state and returns whether the user has met the necessary conditions. The conditions of the player are already checked through `SimpleCriterionTrigger#trigger(ServerPlayer, Predicate)`. Most trigger instances call this method `#matches`. +最后,应该添加一个方法,该方法输入当前的数据状态,并返回用户是否满足了必要的条件。玩家的条件已经通过`SimpleCriterionTrigger#trigger(ServerPlayer, Predicate)`进行了检查。大多数触发器实例将这个方法称为`#matches`。 ```java // This method is unique for each instance and is as such not overridden @@ -78,9 +78,9 @@ public boolean matches(ItemStack stack) { ### SimpleCriterionTrigger -The `SimpleCriterionTrigger` subclass is responsible for specifying a codec to [serialize] the trigger instance `T` and supplying a method to check trigger instances and run attached listeners on success. +`SimpleCriterionTrigger`子类负责指定一个编解码器来[序列化]触发器实例`T`,并提供一个方法来检查触发器实例并在成功时运行已附加的监听器。 -The latter is done by defining a method to check all trigger instances and run the listeners if their condition is met. This method takes in the `ServerPlayer` and whatever other data defined by the matching method in the `SimpleCriterionTrigger.SimpleInstance` subclass. This method should internally call `SimpleCriterionTrigger#trigger` to properly handle checking all listeners. Most trigger instances call this method `#trigger`. +后者是通过定义一个方法来检查所有的触发器实例,并在满足条件时运行监听器来完成的。该方法接收`ServerPlayer`和`SimpleCriterionTrigger.SimpleInstance`子类中匹配方法定义的其他数据。该方法应内部调用`SimpleCriterionTrigger#trigger`以正确处理检查所有监听器。大多数触发器实例将这个方法称为`#trigger`。 ```java // This method is unique for each trigger and is as such not a method to override @@ -92,11 +92,11 @@ public void trigger(ServerPlayer player, ItemStack stack) { } ``` -Finally, instances must be registered on the `Registries.TRIGGER_TYPE` registry. Techniques for doing so can be found under [Registries][registration]. +最后,实例必须在`Registries.TRIGGER_TYPE`注册表上注册。关于如何进行注册的技巧可以在[Registries][registration]下找到。 -### Serialization +### 序列化 -A [codec] must be defined to serialize and deserialize the trigger instance. Vanilla typically creates this codec as a constant within the instance implementation that is then returned by the trigger's `#codec` method. +必须定义一个[编解码器]来序列化和反序列化触发器实例。原版通常在实例实现中创建这个编解码器作为一个常量,然后通过触发器的`#codec`方法返回。 ```java @@ -113,8 +113,7 @@ class ExampleTrigger extends SimpleCriterionTrigger instance.group( ExtraCodecs.strictOptionalField(EntityPredicate.ADVANCEMENT_CODEC, "player").forGetter(ExampleTriggerInstance::player), @@ -122,9 +121,9 @@ RecordCodecBuilder.create(instance -> instance.group( ).apply(instance, ExampleTriggerInstance::new)); `````` -### Calling the Trigger +### 调用触发器 -Whenever the action being checked is performed, the `#trigger` method defined by the `SimpleCriterionTrigger` subclass should be called. +每当执行正在检查的动作时,应该调用由`SimpleCriterionTrigger`子类定义的`#trigger`方法。 ```java // In some piece of code where the action is being performed @@ -135,9 +134,9 @@ public void performExampleAction(ServerPlayer player, ItemStack stack) { } ``` -## Advancement Rewards +## 成就奖励 -When an advancement is completed, rewards may be given out. These can be a combination of experience points, loot tables, recipes for the recipe book, or a [function] executed as a creative player. +当一个成就完成时,可能会给予奖励。这些奖励可以是经验点数、战利品表、食谱书中的食谱,或者作为创造者玩家执行的[函数]的组合。 ```js // In some advancement JSON From 6d738d2694d440dea5340f66c31a1aa59f4138cb Mon Sep 17 00:00:00 2001 From: DaZiDian <113085496+DaZiDian@users.noreply.github.com> Date: Wed, 8 May 2024 16:24:23 +0800 Subject: [PATCH 83/87] Update glm.md --- docs/resources/server/glm.md | 72 ++++++++++++++++++------------------ 1 file changed, 36 insertions(+), 36 deletions(-) diff --git a/docs/resources/server/glm.md b/docs/resources/server/glm.md index b935aac05..91058726f 100644 --- a/docs/resources/server/glm.md +++ b/docs/resources/server/glm.md @@ -1,34 +1,34 @@ -Global Loot Modifiers +全局战利品修改器 (Global Loot Modifiers) =========== -Global Loot Modifiers are a data-driven method of handling modification of harvested drops without the need to overwrite dozens to hundreds of vanilla loot tables or to handle effects that would require interactions with another mod's loot tables without knowing what mods may be loaded. Global Loot Modifiers are also stacking, rather than last-load-wins, similar to tags. +全局战利品修改器是一种数据驱动的方法,用于处理收获物品的修改,无需覆盖数十到数百个原版战利品表,或处理可能需要与其他模组的战利品表互动而不知道哪些模组可能已加载的效果。全局战利品修改器也是叠加的,而不是最后加载胜出的方式,类似于标签。 -Registering a Global Loot Modifier +注册全局战利品修改器 ------------------------------- -You will need 4 things: +您将需要做4件事: -1. Create a `global_loot_modifiers.json`. - * This will tell Forge about your modifiers and works similar to [tags]. -2. A serialized json representing your modifier. - * This will contain all of the data about your modification and allows data packs to tweak your effect. -3. A class that extends `IGlobalLootModifier`. - * The operational code that makes your modifier work. Most modders can extend `LootModifier` as it supplies base functionality. -4. Finally, a codec to encode and decode your operational class. - * This is [registered] as any other `IForgeRegistryEntry`. +1. 创建一个`global_loot_modifiers.json`。 + * 这将告诉Forge有关您的修改器,并且其工作方式类似于[标签]。 +2. 表示您的修改器的序列化json。 + * 这将包含有关您的修改的所有数据,并允许数据包调整您的效果。 +3. 一个扩展了`IGlobalLootModifier`的类。 + * 使您的修改器工作的操作代码。大多数模组开发者可以扩展`LootModifier`,因为它提供了基本功能。 +4. 最后,一个编解码器来编码和解码您的操作类。 + * 这被[注册]为任何其他`IForgeRegistryEntry`。 -The `global_loot_modifiers.json` +`global_loot_modifiers.json` ------------------------------- -The `global_loot_modifiers.json` represents all loot modifiers to be loaded into the game. This file **MUST** be placed within `data/forge/loot_modifiers/global_loot_modifiers.json`. +`global_loot_modifiers.json`代表要加载到游戏中的所有战利品修改器。这个文件**必须**被放置在`data/forge/loot_modifiers/global_loot_modifiers.json`内。 :::danger -`global_loot_modifiers.json` will only be read in the `forge` namespace. The file will be neglected if it is under the mod's namespace. +`global_loot_modifiers.json`将只在`forge`命名空间下读取。如果文件位于模组的命名空间下,则会被忽略。 ::: -`entries` is an *ordered list* of the modifiers that will be loaded. The [ResourceLocation][resloc]s specified points to their associated entry within `data//loot_modifiers/.json`. This is primarily relevant to data pack makers for resolving conflicts between modifiers from separate mods. +`entries`是将要加载的修改器的*有序列表*。指定的[ResourceLocation][resloc]指向`data//loot_modifiers/.json`内的相关条目。这主要与数据包制作者相关,用于解决来自不同模组的修改器之间的冲突。 -`replace`, when `true`, changes the behavior from appending loot modifiers to the global list to replacing the global list entries entirely. Modders will want to use `false` for compatibility with other mod implementations. Datapack makers may want to specify their overrides with `true`. +`replace`为`true`时,会将从在全局列表中追加战利品修改器的行为改为完全替换全局列表条目。模组开发者会想使用`false`来与其他模组的实现兼容。数据包制作者可能会想使用`true`来指定他们的覆盖项。 ```js { @@ -42,20 +42,20 @@ The `global_loot_modifiers.json` represents all loot modifiers to be loaded into } ``` -The Serialized JSON +序列化的JSON ------------------------------- -This file contains all of the potential variables related to your modifier, including the conditions that must be met prior to modifying any loot. Avoid hard-coded values wherever possible so that data pack makers can adjust balance if they wish to. +此文件包含与您的修改器相关的所有可能变量,包括在修改任何战利品之前必须满足的条件。应尽可能避免硬编码的值,以便数据包制作者可以在需要时调整平衡。 -`type` represents the registry name of the [codec] used to read the associated JSON file. This must always be present. +`type`代表用来读取关联的JSON文件的[编解码器]的注册名称。这必须始终存在。 -`conditions` should represent the loot table conditions for this modifier to activate. Conditions should avoid being hardcoded to allow datapack creators as much flexibility to adjust the criteria. This must also be always present. +`conditions`应该代表此修改器激活的战利品表条件。条件应尽量避免硬编码,以便数据包创建者可以尽可能灵活地调整条件。这也必须始终存在。 :::caution -Although `conditions` should represent what is needed for the modifier to activate, this is only the case if using the bundled Forge classes. If using `LootModifier` as a subclass, all conditions will be **ANDed** together and checked to see if the modifier should be applied. +虽然`conditions`应该代表激活修改器所需的条件,但只有在使用捆绑的Forge类时才是这种情况。如果使用`LootModifier`作为子类,所有的条件将被**并在一起**,并检查是否应该应用修改器。 ::: -Any additional properties read by the serializer and defined by the modifier can also be specified. +也可以指定由修改器定义并由序列化器读取的任何其他属性。 ```js // Within data/examplemod/loot_modifiers/example_glm.json @@ -74,25 +74,25 @@ Any additional properties read by the serializer and defined by the modifier can `IGlobalLootModifier` --------------------- -To supply the functionality a global loot modifier specifies, a `IGlobalLootModifier` implementation must be specified. These are instances generated each time a serializer decodes the information from JSON and supplies it into this object. +为了提供全局战利品修改器指定的功能,必须指定一个`IGlobalLootModifier`的实现。这些是每次序列化器从JSON解码信息并将其提供到此对象时生成的实例。 -There are two methods that needs to be defined in order to create a new modifier: `#apply` and `#codec`. `#apply` takes in the current loot that will be generated along with the context information such as the currently level or additional defined parameters. It returns the list of drops to generate. +为了创建一个新的修改器,需要定义两个方法:`#apply`和`#codec`。`#apply`接收将要生成的当前战利品以及上下文信息,如当前级别或额外定义的参数。它返回要生成的掉落物列表。 :::info -The returned list of drops from any one modifier is fed into other modifiers in the order they are registered. As such, modified loot can be modified by another loot modifier. +从任一修改器返回的掉落物列表将按照它们注册的顺序提供给其他修改器。因此,修改过的战利品可以被其他战利品修改器修改。 ::: -`#codec` returns the registered [codec] used to encode and decode the modifier to/from JSON. +`#codec`返回已注册的[编解码器],用于将修改器编码并解码为JSON。 -### The `LootModifier` Subclass +### `LootModifier`子类 -`LootModifier` is an abstract implementation of `IGlobalLootModifier` to provide the base functionality which most modders can easily extend and implement. This expands upon the existing interface by defining the `#apply` method to check the conditions to determine whether or not to modify the generated loot. +`LootModifier`是`IGlobalLootModifier`的一个抽象实现,它提供了大多数模组开发者可以轻松扩展和实现的基本功能。这在现有的接口上进行了扩展,通过定义`#apply`方法来检查条件,以确定是否要修改生成的战利品。 -There are two things of note within the subclass implementation: the constructor which must take in an array of `LootItemCondition`s and the `#doApply` method. +在子类实现中有两件事需要注意:一个是必须接受一个`LootItemCondition`数组的构造器,另一个是`#doApply`方法。 -The array of `LootItemCondition`s define the list of conditions that must be true before the loot can be modified. The supplied conditions are **ANDed** together, meaning that all conditions must be true. +`LootItemCondition`数组定义了在战利品可以被修改之前必须为真的条件列表。提供的条件将被**并在一起**,这意味着所有的条件都必须为真。 -The `#doApply` method works the same as the `#apply` method except that it only executes once all conditions return true. +`#doApply`方法的工作方式与`#apply`方法相同,只是它只在所有条件返回真时才执行。 ```java public class ExampleModifier extends LootModifier { @@ -115,12 +115,12 @@ public class ExampleModifier extends LootModifier { } ``` -The Loot Modifier Codec +战利品修改器编解码器 ----------------------- -The connector between the JSON and the `IGlobalLootModifier` instance is a [`Codec`][codecdef], where `T` represents the type of the `IGlobalLootModifier` to use. +将JSON与`IGlobalLootModifier`实例连接起来的是一个[`Codec`][codecdef],其中`T`代表要使用的`IGlobalLootModifier`的类型。 -For ease of convenience, a loot conditions codec has been provided for an easy addition to a record-like codec via `LootModifier#codecStart`. This is utilized for [data generation][datagen] of the associated loot modifier. +为了方便起见,已经提供了一个战利品条件编解码器,可以通过`LootModifier#codecStart`轻松地添加到类似记录的编解码器中。这用于关联战利品修改器的[数据生成][datagen]。 ```java // For some DeferredRegister> REGISTRAR @@ -137,7 +137,7 @@ public static final RegistryObject> = REGISTRAR.register( ); ``` -[Examples][examples] can be found on the Forge Git repository, including silk touch and smelting effects. +[示例][examples]可以在Forge Git仓库中找到,包括精准采集和烧炼效果的示例。 [tags]: ./tags.md [resloc]: ../../misc/resourcelocation.md From c47a548e11da7e505a7da78dcf168544b938e9cb Mon Sep 17 00:00:00 2001 From: DaZiDian <113085496+DaZiDian@users.noreply.github.com> Date: Wed, 8 May 2024 16:26:14 +0800 Subject: [PATCH 84/87] Update index.md --- docs/resources/server/index.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/docs/resources/server/index.md b/docs/resources/server/index.md index 3c097338c..6c50b1c47 100644 --- a/docs/resources/server/index.md +++ b/docs/resources/server/index.md @@ -1,13 +1,13 @@ -Datapacks +数据包 (Datapacks) ========= -In 1.13, Mojang added [datapacks][datapack] to the base game. They allow for the modification of the files for logical servers through the `data` directory. This includes advancements, loot_tables, structures, recipes, tags, etc. Forge, and your mod, can also have datapacks. Any user can therefore modify all the recipes, loot tables, and other data defined within this directory. +在1.13版本中,Mojang向基础游戏中添加了[数据包][datapack]。它们允许通过`data`目录修改逻辑服务器的文件。这包括进度、战利品表、结构、配方、标签等。Forge和您的模组也可以有数据包。因此,任何用户都可以修改在此目录内定义的所有配方、战利品表和其他数据。 -### Creating a Datapack -Datapacks are stored within the `data` directory within your project's resources. -Your mod can have multiple data domains, since you can add or modify already existing datapacks, like vanilla's, forge's, or another mod's. -You can then follow the steps found [here][createdatapack] to create any datapack. +### 创建一个数据包 +数据包存储在项目资源中的`data`目录内。 +您的模组可以有多个数据域,因为您可以添加或修改已存在的数据包,比如原版的、Forge的或其他模组的。 +然后,您可以按照[这里][createdatapack]找到的步骤创建任何数据包。 -Additional reading: [Resource Locations][resourcelocation] +额外阅读资料:[资源位置][resourcelocation] [datapack]: https://minecraft.wiki/w/Data_pack [createdatapack]: https://minecraft.wiki/w/Tutorials/Creating_a_data_pack From 7a756bbc934ca1e37f1b4d6755d881fb4716a919 Mon Sep 17 00:00:00 2001 From: DaZiDian <113085496+DaZiDian@users.noreply.github.com> Date: Wed, 8 May 2024 16:34:19 +0800 Subject: [PATCH 85/87] Update loottables.md --- docs/resources/server/loottables.md | 63 ++++++++++++++--------------- 1 file changed, 31 insertions(+), 32 deletions(-) diff --git a/docs/resources/server/loottables.md b/docs/resources/server/loottables.md index 2e80c2f17..99c785315 100644 --- a/docs/resources/server/loottables.md +++ b/docs/resources/server/loottables.md @@ -1,50 +1,50 @@ -Loot Tables +战利品表 (Loot Tables) =========== -Loot tables are logic files which dictate what should happen when various actions or scenarios occur. Although the vanilla system deals purely with item generation, the system can be expanded to perform any number of defined actions. +战利品表是控制在不同动作或场景发生时应该发生什么的逻辑文件。虽然原版系统纯粹与物品生成有关,但这个系统可以扩展以执行任意数量的定义动作。 -Data-Driven Tables +数据驱动的表 ------------------ -Most loot tables within vanilla are data driven via JSON. This means that a mod is not necessary to create a new loot table, only a [Data pack][datapack]. A full list on how to create and put these loot tables within the mod's `resources` folder can be found on the [Minecraft Wiki][wiki]. +原版中的大多数战利品表都是通过JSON数据驱动的。这意味着创建一个新的战利品表不需要模组,只需要一个[数据包][datapack]。有关如何创建这些战利品表并将它们放入模组的`resources`文件夹的完整列表可以在[Minecraft Wiki][wiki]上找到。 -Using a Loot Table +使用战利品表 ------------------ -A loot table is referenced by its `ResourceLocation` which points to `data//loot_tables/.json`. The `LootTable` associated with the reference can be obtained using `LootDataResolver#getLootTable`, where `LootDataResolver` can be obtained via `MinecraftServer#getLootData`. +战利品表通过其`ResourceLocation`引用,它指向`data//loot_tables/.json`。可以使用`LootDataResolver#getLootTable`获取与引用相关联的`LootTable`,其中`LootDataResolver`可以通过`MinecraftServer#getLootData`获得。 -A loot table is always generated with given parameters. The `LootParams` contains the level the table is generated in, luck for better generation, the `LootContextParam`s which define scenario context, and any dynamic information that should occur on activation. The `LootParams` can be created using the constructor of the `LootParams$Builder` builder, and built via `LootParams$Builder#create` by passing in the `LootContextParamSet`. +战利品表总是带有给定的参数生成的。`LootParams`包含生成表的等级、用于更好生成的幸运值、定义场景上下文的`LootContextParam`,以及在激活时应进行的任何动态信息。`LootParams`可以使用`LootParams$Builder`构造器的构造函数创建,并通过向`LootParams$Builder#create`传入`LootContextParamSet`构建。 -A loot table may also have some context. The `LootContext` takes in the built `LootParams` and can set some random seeded instance. The context is created via the builder `LootContext$Builder` and built using `LootContext$Builder#create` by passing in a nullable `ResourceLocation` representing the random instance to use. +战利品表也可能有一些上下文。`LootContext`接受构建的`LootParams`,并可以设置一些随机种子实例。上下文是通过构建器`LootContext$Builder`创建的,并通过向`LootContext$Builder#create`传入一个可为空的`ResourceLocation`来构建,代表要使用的随机实例。 -A `LootTable` can be used to generate `ItemStack`s using one of the available methods which may take in a `LootParams` or a `LootContext`: +`LootTable`可以使用可用的方法之一生成`ItemStack`,这些方法可能接受`LootParams`或`LootContext`: -Method | Description -:---: | :--- -`getRandomItemsRaw` | Consumes the items generated by the loot table. -`getRandomItems` | Returns the items generated by the loot table. -`fill` | Fills a container with the generated loot table. +方法 | 描述 +:---: | :--- +`getRandomItemsRaw` | 消费战利品表生成的物品。 +`getRandomItems` | 返回战利品表生成的物品。 +`fill` | 用生成的战利品填充一个容器。 :::note -Loot tables were built for generating items, so the methods expect some handling for the `ItemStack`s. +战利品表是为生成物品而构建的,所以这些方法期望对`ItemStack`进行一些处理。 ::: -Additional Features +额外功能 ------------------- -Forge provides some additional behavior to loot tables for greater control of the system. +Forge为战利品表提供了一些额外的行为,以更好地控制系统。 ### `LootTableLoadEvent` -`LootTableLoadEvent` is an [event] fired on the Forge event bus which is fired whenever a loot table is loaded. If the event is canceled, then an empty loot table will be loaded instead. +`LootTableLoadEvent`是在Forge事件总线上触发的一个[事件],每当一个战利品表被加载时就会触发。如果事件被取消,则将加载一个空的战利品表。 :::info -Do **not** modify a loot table's drops through this event. Those modifications should be done using [global loot modifiers][glm]. +不要通过这个事件修改战利品表的掉落。这些修改应该使用[全局战利品修改器][glm]来完成。 ::: -### Loot Pool Names +### 战利品池名称 -Loot pools can be named using the `name` key. Any non-named loot pool will be the hash code of the pool prefixed by `custom#`. +可以使用`name`键命名战利品池。任何未命名的战利品池将以`custom#`为前缀,后面跟着池的哈希码。 ```js // For some loot pool @@ -59,22 +59,21 @@ Loot pools can be named using the `name` key. Any non-named loot pool will be th } ``` -### Looting Modifiers +### 掠夺修饰符 -Loot tables are now affected by the `LootingLevelEvent`, on the Forge event bus, in addition to the looting enchantment. +现在,战利品表除了掠夺附魔以外,还受到Forge事件总线上的`LootingLevelEvent`的影响。 -### Additional Context Parameters +### 额外的上下文参数 -Forge extends certain parameter sets to account for missing contexts which may be applicable. `LootContextParamSets#CHEST` now allows for a `LootContextParams#KILLER_ENTITY` as chest minecarts are entities which can be broken (or 'killed'). `LootContextParamSets#FISHING` also allows for a `LootContextParams#KILLER_ENTITY` since the fishing hook is also an entity which is retracted (or 'killed') when the player retrieves it. +Forge扩展了某些参数集来考虑可能适用的缺失上下文。`LootContextParamSets#CHEST`现在允许使用`LootContextParams#KILLER_ENTITY`,因为宝藏矿车是可以被破坏(或“杀死”)的实体。`LootContextParamSets#FISHING`也允许使用`LootContextParams#KILLER_ENTITY`,因为钓鱼钩也是一个实体,当玩家收回它时,它被收回(或“杀死”)。 -### Multiple Items on Smelting +### 多项物品熔炼 -When using the `SmeltItemFunction`, a smelted recipe will now return the actual number of items from the result instead of a single smelted item (e.g. if a smelting recipe returns 3 items and there are 3 drops, then the result would be 9 smelted items instead of 3). +使用`SmeltItemFunction`时,熔炼的配方现在会返回结果中的实际物品数量,而不是单个熔炼物品(例如,如果熔炼配方返回3个物品,而且有3个掉落,那么结果将是9个熔炼物品,而不是3个)。 -### Loot Table Id Condition - -Forge adds an additional `LootItemCondition` which allows certain items to generate for a specific table. This is typically used within [global loot modifiers][glm]. +### 战利品表ID条件 +Forge添加了一个额外的`LootItemCondition`,它允许某些物品为特定表生成。这通常在[全局战利品修改器][glm]中使用。 ```js // In some loot pool or pool entry { @@ -88,9 +87,9 @@ Forge adds an additional `LootItemCondition` which allows certain items to gener } ``` -### Can Tool Perform Action Condition +### 工具是否能执行动作条件 -Forge adds an additional `LootItemCondition` which checks whether the given `LootContextParams#TOOL` can perform the specified `ToolAction`. +Forge添加了一个额外的`LootItemCondition`,用于检查给定的`LootContextParams#TOOL`是否能执行指定的`ToolAction`。这允许战利品表更精确地根据玩家使用的工具来调整掉落物,不仅限于工具的类型,还包括其能够执行的动作,例如挖掘、砍伐等。 ```js // In some loot pool or pool entry From 9eace16e1e6a1eea7e2e6a146ab17dfc2e3c2b88 Mon Sep 17 00:00:00 2001 From: DaZiDian <113085496+DaZiDian@users.noreply.github.com> Date: Wed, 8 May 2024 16:44:48 +0800 Subject: [PATCH 86/87] Update tags.md --- docs/resources/server/tags.md | 94 +++++++++++++++++------------------ 1 file changed, 46 insertions(+), 48 deletions(-) diff --git a/docs/resources/server/tags.md b/docs/resources/server/tags.md index e5a7855de..b5f88eeb3 100644 --- a/docs/resources/server/tags.md +++ b/docs/resources/server/tags.md @@ -1,15 +1,15 @@ -Tags +标签 (Tags) ==== -Tags are generalized sets of objects in the game used for grouping related things together and providing fast membership checks. +标签是游戏中对象的广义集合,用于将相关事物分组在一起并提供快速的成员检查。 -Declaring Your Own Groupings +声明您自己的分组 ---------------------------- -Tags are declared in your mod's [datapack][datapack]. For example, a `TagKey` with a given identifier of `modid:foo/tagname` will reference a tag at `/data//tags/blocks/foo/tagname.json`. Tags for `Block`s, `Item`s, `EntityType`s, `Fluid`s, and `GameEvent`s use the plural forms for their folder location while all other registries use the singular version (`EntityType` uses the folder `entity_types` while `Potion` would use the folder `potion`). -Similarly, you may append to or override tags declared in other domains, such as Vanilla, by declaring your own JSONs. -For example, to add your own mod's saplings to the Vanilla sapling tag, you would specify it in `/data/minecraft/tags/blocks/saplings.json`, and Vanilla will merge everything into one tag at reload, if the `replace` option is false. -If `replace` is true, then all entries before the json specifying `replace` will be removed. -Values listed that are not present will cause the tag to error unless the value is listed using an `id` string and `required` boolean set to false, as in the following example: +标签在您的模组的[数据包][datapack]中声明。例如,一个给定标识符为`modid:foo/tagname`的`TagKey`将引用在`/data//tags/blocks/foo/tagname.json`的标签。`Block`、`Item`、`EntityType`、`Fluid`和`GameEvent`的标签使用它们的文件夹位置的复数形式,而所有其他注册表使用单数版本(`EntityType`使用文件夹`entity_types`,而`Potion`则使用文件夹`potion`)。 +同样,您可以通过声明自己的JSON来附加或覆盖在其他域中声明的标签,比如Vanilla。 +例如,要将您自己模组的树苗添加到Vanilla的树苗标签,您需要在`/data/minecraft/tags/blocks/saplings.json`中指定,如果`replace`选项为false,那么Vanilla将在重新加载时将所有内容合并到一个标签中。 +如果`replace`为true,则指定`replace`的json之前的所有条目将被删除。 +列出的不存在的值将导致标签出错,除非该值使用`id`字符串和`required`布尔值列出且设置为false,如下例: ```js { @@ -25,48 +25,48 @@ Values listed that are not present will cause the tag to error unless the value } ``` -See the [Vanilla wiki][tags] for a description of the base syntax. +请参阅[Vanilla wiki][tags]了解基本语法的描述。 -There is also a Forge extension on the Vanilla syntax. -You may declare a `remove` array of the same format as the `values` array. Any values listed here will be removed from the tag. This acts as a finer grained version of the Vanilla `replace` option. +此外,Forge在Vanilla语法上进行了扩展。 +您可以声明一个与`values`数组格式相同的`remove`数组。列在这里的任何值都将从标签中删除。这作为Vanilla `replace`选项的更细粒度版本。 - -Using Tags In Code +在代码中使用标签 ------------------ -Tags for all registries are automatically sent from the server to any remote clients on login and reload. `Block`s, `Item`s, `EntityType`s, `Fluid`s, and `GameEvent`s are special cased as they have `Holder`s allowing for available tags to be accessible through the object itself. +所有注册表的标签都会在登录和重新加载时自动从服务器发送到任何远程客户端。`Block`、`Item`、`EntityType`、`Fluid`和`GameEvent`是特殊情况,因为它们有`Holder`,允许通过对象本身访问可用的标签。 :::note -Intrusive `Holder`s may be removed in a future version of Minecraft. If they are, the below methods can be used instead to query the associated `Holder`s. +未来版本的Minecraft中可能会删除侵入式的`Holder`。如果它们被删除,下面的方法可以用来查询相关的`Holder`。 ::: ### ITagManager -Forge wrapped registries provide an additional helper for creating and managing tags through `ITagManager` which can be obtained via `IForgeRegistry#tags`. Tags can be created using using `#createTagKey` or `#createOptionalTagKey`. Tags or registry objects can also be checked for either or using `#getTag` or `#getReverseTag` respectively. +Forge包装的注册表提供了一个额外的帮助器,通过`ITagManager`来创建和管理标签,可以通过`IForgeRegistry#tags`获得。标签可以使用`#createTagKey`或`#createOptionalTagKey`创建。也可以分别使用`#getTag`或`#getReverseTag`检查标签或注册对象。 + +#### 自定义注册表 -#### Custom Registries +自定义注册表可以在构建它们的`DeferredRegister`时通过`#createTagKey`或`#createOptionalTagKey`创建标签。然后可以通过调用`DeferredRegister#makeRegistry`获得的`IForgeRegistry`来检查它们的标签或注册对象。 -Custom registries can create tags when constructing their `DeferredRegister` via `#createTagKey` or `#createOptionalTagKey` respectively. Their tags or registry objects can then checked for either using the `IForgeRegistry` obtained by calling `DeferredRegister#makeRegistry`. +### 引用标签 -### Referencing Tags +有四种创建标签包装的方法: -There are four methods of creating a tag wrapper: +方法 | 适用于 +:---: | :--- +`*Tags#create` | `BannerPattern`、`Biome`、`Block`、`CatVariant`、`DamageType`、`EntityType`、`FlatLevelGeneratorPreset`、`Fluid`、`GameEvent`、`Instrument`、`Item`、`PaintingVariant`、`PoiType`、`Structure`和`WorldPreset`,其中`*`代表这些类型之一。 +`ITagManager#createTagKey` | Forge包装的vanilla注册表,注册表可以从`ForgeRegistries`获得。 +`DeferredRegister#createTagKey` | 自定义forge注册表。 +`TagKey#create` | 没有forge包装的vanilla注册表,注册表可以从`Registry`获得。 -Method | For -:---: | :--- -`*Tags#create` | `BannerPattern`, `Biome`, `Block`, `CatVariant`, `DamageType`, `EntityType`, `FlatLevelGeneratorPreset`, `Fluid`, `GameEvent`, `Instrument`, `Item`, `PaintingVariant`, `PoiType`, `Structure`, and `WorldPreset` where `*` represents one of these types. -`ITagManager#createTagKey` | Forge wrapped vanilla registries, registries can be obtained from `ForgeRegistries`. -`DeferredRegister#createTagKey` | Custom forge registries. -`TagKey#create` | Vanilla registries without forge wrappers, registries can be obtained from `Registry`. +注册对象可以通过它们的`Holder`或对于vanilla或forge注册表对象分别通过`ITag`/`IReverseTag`检查它们的标签或注册对象。 -Registry objects can check their tags or registry objects either through their `Holder` or through `ITag`/`IReverseTag` for vanilla or forge registry objects respectively. +Vanilla注册表对象可以使用`Registry#getHolder`或`Registry#getHolderOrThrow`抓取它们关联的holder,然后使用`Holder#is`比较注册表对象是否有标签。 -Vanilla registry objects can grab their associated holder using either `Registry#getHolder` or `Registry#getHolderOrThrow` and then compare if the registry object has a tag using `Holder#is`. +Forge注册表对象可以使用`ITagManager#getTag`或`ITagManager#getReverseTag`抓取它们的标签定义,然后分别使用`ITag#contains`或`IReverseTag#containsTag`比较注册表对象是否有标签。 -Forge registry objects can grab their tag definition using either `ITagManager#getTag` or `ITagManager#getReverseTag` and then compare if a registry object has a tag using `ITag#contains` or `IReverseTag#containsTag` respectively. +包含标签的注册表对象包含一个称为`#is`的方法,在它们的注册表对象或状态感知类中,用以检查对象是否属于某个标签。 -Tag-holding registry objects contain a method called `#is` in either their registry object or state-aware class to check whether the object belongs to a certain tag. +作为一个示例: -As an example: ```java public static final TagKey myItemTag = ItemTags.create(new ResourceLocation("mymod", "myitemgroup")); @@ -86,32 +86,30 @@ ResourceKey villagerTypeKey = /*...*/; boolean isInVillagerTypeGroup = BuiltInRegistries.VILLAGER_TYPE.getHolder(villagerTypeKey).map(holder -> holder.is(myVillagerTypeTag)).orElse(false); ``` -Conventions +约定 ----------- -There are several conventions that will help facilitate compatibility in the ecosystem: +有几个约定将有助于在生态系统中促进兼容性: -* If there is a Vanilla tag that fits your block or item, add it to that tag. See the [list of Vanilla tags][taglist]. -* If there is a Forge tag that fits your block or item, add it to that tag. The list of tags declared by Forge can be seen on [GitHub][forgetags]. -* If there is a group of something you feel should be shared by the community, use the `forge` namespace instead of your mod id. -* Tag naming conventions should follow Vanilla conventions. In particular, item and block groupings are plural instead of singular (e.g. `minecraft:logs`, `minecraft:saplings`). -* Item tags should be sorted into subdirectories according to their type (e.g. `forge:ingots/iron`, `forge:nuggets/brass`, etc.). +* 如果有Vanilla标签适合您的方块或物品,请将其添加到该标签中。参见[Vanilla标签列表][taglist]。 +* 如果有Forge标签适合您的方块或物品,请将其添加到该标签中。可以在[GitHub][forgetags]上查看Forge声明的标签列表。 +* 如果您觉得有一组东西应该被社区共享,请使用`forge`命名空间而不是您的mod id。 +* 标签命名约定应遵循Vanilla约定。特别是,物品和方块分组应使用复数而不是单数(例如,`minecraft:logs`,`minecraft:saplings`)。 +* 物品标签应按照它们的类型排序到子目录中(例如,`forge:ingots/iron`,`forge:nuggets/brass`等)。 - -Migration from OreDictionary +从OreDictionary迁移 ---------------------------- -* For recipes, tags can be used directly in the vanilla recipe format (see below). -* For matching items in code, see the section above. -* If you are declaring a new type of item grouping, follow a couple naming conventions: - * Use `domain:type/material`. When the name is a common one that all modders should adopt, use the `forge` domain. - * For example, brass ingots should be registered under the `forge:ingots/brass` tag and cobalt nuggets under the `forge:nuggets/cobalt` tag. - +* 对于配方,可以直接在vanilla配方格式中使用标签(见下文)。 +* 要在代码中匹配物品,请参阅上述部分。 +* 如果您正在声明一种新类型的物品分组,请遵循一些命名约定: + * 使用`domain:type/material`。当名称是所有modders都应采用的常见名称时,使用`forge`域。 + * 例如,铜锭应在`forge:ingots/brass`标签下注册,钴粒则应在`forge:nuggets/cobalt`标签下注册。 -Using Tags in Recipes and Advancements +在配方和成就中使用标签 -------------------------------------- -Tags are directly supported by Vanilla. See the respective Vanilla wiki pages for [recipes] and [advancements] for usage details. +Vanilla直接支持标签。有关使用详细信息,请参阅相应的Vanilla wiki页面,包括[配方]和[成就]。 [datapack]: ./index.md [tags]: https://minecraft.wiki/w/Tag#JSON_format From 6da839d55082957c399d53a7c2579f09d389c4fb Mon Sep 17 00:00:00 2001 From: DaZiDian Date: Sat, 11 May 2024 16:33:49 +0800 Subject: [PATCH 87/87] =?UTF-8?q?=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/advanced/accesstransformers.mdx | 133 +++-- docs/blockentities/ber.md | 28 +- docs/blockentities/index.md | 101 ++-- docs/blocks/index.md | 216 ++++---- docs/blocks/states.md | 124 ++--- docs/concepts/events.md | 106 ++-- docs/concepts/registries.md | 180 ++++--- docs/concepts/sides.md | 52 +- docs/datagen/advancements.md | 44 +- docs/datagen/glm.md | 12 +- docs/datagen/loottables.md | 84 ++-- docs/datagen/recipes.md | 140 +++--- docs/datagen/tags.md | 100 ++-- docs/datamaps/index.md | 148 +++--- docs/datamaps/neo_maps.md | 47 +- docs/datamaps/structure.md | 74 +-- docs/datastorage/attachments.md | 90 ++-- docs/datastorage/capabilities.md | 307 ++++++------ docs/datastorage/codecs.md | 323 ++++++------ docs/datastorage/nbt.md | 74 ++- docs/datastorage/saveddata.md | 32 +- docs/gettingstarted/index.md | 60 ++- docs/gettingstarted/modfiles.md | 156 +++++- docs/gettingstarted/structuring.md | 82 ++- docs/gettingstarted/versioning.md | 78 ++- docs/gui/menus.md | 212 ++++---- docs/gui/screens.md | 252 +++++----- docs/items/bewlr.md | 22 +- docs/items/index.md | 168 ++++--- docs/items/interactionpipeline.md | 88 ++-- docs/items/mobeffects.md | 119 ++--- docs/items/tools.md | 237 ++++----- docs/legacy/porting.md | 20 +- docs/misc/config.md | 148 +++--- docs/misc/debugprofiler.md | 32 +- docs/misc/gametest.mdx | 214 ++++---- docs/misc/keymappings.md | 102 ++-- docs/misc/resourcelocation.md | 46 +- docs/misc/updatechecker.md | 60 +-- docs/networking/configuration-tasks.md | 68 +-- docs/networking/entities.md | 32 +- docs/networking/index.md | 20 +- docs/networking/payload.md | 60 +-- docs/resources/client/i18n.md | 76 +-- docs/resources/client/models/bakedmodel.md | 146 +++--- docs/resources/client/models/datagen.md | 276 +++++------ docs/resources/client/models/index.md | 237 +++++---- docs/resources/client/models/modelloaders.md | 96 ++-- docs/resources/client/particles.md | 50 +- docs/resources/client/sounds.md | 78 +-- docs/resources/client/textures.md | 16 +- docs/resources/index.md | 184 ++++--- docs/resources/server/advancements.md | 49 +- docs/resources/server/glm.md | 72 +-- docs/resources/server/index.md | 14 +- docs/resources/server/loottables.md | 63 +-- docs/resources/server/recipes/custom.md | 61 +-- docs/resources/server/recipes/incode.md | 30 +- docs/resources/server/recipes/ingredients.md | 60 +-- docs/resources/server/tags.md | 94 ++-- docusaurus.config.js | 4 +- i18n/zh-Hans/code.json | 424 ++++++++++++++++ .../options.json | 14 + .../current.json | 14 + .../version-5.x.json | 18 + .../current.json | 78 +++ .../current/advanced/_category_.json | 3 + .../current/advanced/accesstransformers.mdx | 167 +++++++ .../current/blockentities/ber.md | 28 ++ .../current/blockentities/index.md | 125 +++++ .../current/blocks/index.md | 235 +++++++++ .../current/blocks/states.md | 151 ++++++ .../current/concepts/_category_.json | 3 + .../current/concepts/events.md | 137 +++++ .../current/concepts/registries.md | 320 ++++++++++++ .../current/concepts/sides.md | 77 +++ .../current/datagen/_category_.json | 3 + .../current/datagen/advancements.md | 68 +++ .../current/datagen/glm.md | 30 ++ .../current/datagen/loottables.md | 148 ++++++ .../current/datagen/recipes.md | 197 ++++++++ .../current/datagen/tags.md | 123 +++++ .../current/datamaps/_category_.json | 3 + .../current/datamaps/index.md | 192 +++++++ .../current/datamaps/neo_maps.md | 128 +++++ .../current/datamaps/structure.md | 103 ++++ .../current/datastorage/_category_.json | 3 + .../current/datastorage/attachments.md | 109 ++++ .../current/datastorage/capabilities.md | 300 +++++++++++ .../current/datastorage/codecs.md | 446 +++++++++++++++++ .../current/datastorage/nbt.md | 100 ++++ .../current/datastorage/saveddata.md | 40 ++ .../current/gettingstarted/_category_.json | 4 + .../current/gettingstarted/index.md | 62 +++ .../current/gettingstarted/modfiles.md | 79 +++ .../current/gettingstarted/structuring.md | 82 +++ .../current/gettingstarted/versioning.md | 100 ++++ .../current/gui/_category_.json | 3 + .../current/gui/menus.md | 351 +++++++++++++ .../current/gui/screens.md | 346 +++++++++++++ .../current/items/bewlr.md | 36 ++ .../current/items/index.md | 206 ++++++++ .../current/items/interactionpipeline.md | 71 +++ .../current/items/mobeffects.md | 200 ++++++++ .../current/items/tools.md | 331 ++++++++++++ .../current/legacy/_category_.json | 3 + .../current/legacy/porting.md | 22 + .../current/misc/_category_.json | 3 + .../current/misc/config.md | 139 ++++++ .../current/misc/debugprofiler.md | 45 ++ .../current/misc/gametest.mdx | 290 +++++++++++ .../current/misc/keymappings.md | 159 ++++++ .../current/misc/resourcelocation.md | 48 ++ .../current/misc/updatechecker.md | 63 +++ .../current/networking/configuration-tasks.md | 122 +++++ .../current/networking/entities.md | 29 ++ .../current/networking/index.md | 18 + .../current/networking/payload.md | 98 ++++ .../current/resources/client/_category_.json | 3 + .../current/resources/client/i18n.md | 181 +++++++ .../resources/client/models/bakedmodel.md | 138 ++++++ .../resources/client/models/datagen.md | 289 +++++++++++ .../current/resources/client/models/index.md | 305 ++++++++++++ .../resources/client/models/modelloaders.md | 469 ++++++++++++++++++ .../current/resources/client/particles.md | 260 ++++++++++ .../current/resources/client/sounds.md | 311 ++++++++++++ .../current/resources/client/textures.md | 70 +++ .../current/resources/index.md | 219 ++++++++ .../current/resources/server/advancements.md | 167 +++++++ .../current/resources/server/conditional.md | 176 +++++++ .../current/resources/server/glm.md | 148 ++++++ .../current/resources/server/index.md | 14 + .../current/resources/server/loottables.md | 110 ++++ .../resources/server/recipes/custom.md | 131 +++++ .../resources/server/recipes/incode.md | 65 +++ .../current/resources/server/recipes/index.md | 104 ++++ .../resources/server/recipes/ingredients.md | 177 +++++++ .../current/resources/server/tags.md | 119 +++++ .../docusaurus-theme-classic/footer.json | 38 ++ .../docusaurus-theme-classic/navbar.json | 26 + neogradle/docs/5.x_to_6.0.md | 62 +-- neogradle/docs/configuration/advanced.md | 10 +- neogradle/docs/configuration/index.mdx | 76 +-- neogradle/docs/configuration/runs.md | 112 ++--- neogradle/docs/dependencies/index.md | 65 ++- neogradle/docs/dependencies/jarinjar.md | 68 +-- neogradle/docs/gettingstarted/index.md | 58 +-- neogradle/docs/index.md | 32 +- .../version-5.x/docs/configuration/index.md | 40 +- .../version-5.x/docs/configuration/runs.md | 110 ++-- .../version-5.x/docs/dependencies/index.md | 57 ++- .../version-5.x/docs/dependencies/jarinjar.md | 85 +++- .../version-5.x/docs/gettingstarted/index.md | 54 +- .../version-5.x/docs/index.md | 27 +- package-lock.json | 324 +++++++----- src/pages/contributing.mdx | 157 +++--- src/pages/index.mdx | 24 +- 157 files changed, 14150 insertions(+), 3725 deletions(-) create mode 100644 i18n/zh-Hans/code.json create mode 100644 i18n/zh-Hans/docusaurus-plugin-content-blog/options.json create mode 100644 i18n/zh-Hans/docusaurus-plugin-content-docs-neogradle/current.json create mode 100644 i18n/zh-Hans/docusaurus-plugin-content-docs-neogradle/version-5.x.json create mode 100644 i18n/zh-Hans/docusaurus-plugin-content-docs/current.json create mode 100644 i18n/zh-Hans/docusaurus-plugin-content-docs/current/advanced/_category_.json create mode 100644 i18n/zh-Hans/docusaurus-plugin-content-docs/current/advanced/accesstransformers.mdx create mode 100644 i18n/zh-Hans/docusaurus-plugin-content-docs/current/blockentities/ber.md create mode 100644 i18n/zh-Hans/docusaurus-plugin-content-docs/current/blockentities/index.md create mode 100644 i18n/zh-Hans/docusaurus-plugin-content-docs/current/blocks/index.md create mode 100644 i18n/zh-Hans/docusaurus-plugin-content-docs/current/blocks/states.md create mode 100644 i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/_category_.json create mode 100644 i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/events.md create mode 100644 i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/registries.md create mode 100644 i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/sides.md create mode 100644 i18n/zh-Hans/docusaurus-plugin-content-docs/current/datagen/_category_.json create mode 100644 i18n/zh-Hans/docusaurus-plugin-content-docs/current/datagen/advancements.md create mode 100644 i18n/zh-Hans/docusaurus-plugin-content-docs/current/datagen/glm.md create mode 100644 i18n/zh-Hans/docusaurus-plugin-content-docs/current/datagen/loottables.md create mode 100644 i18n/zh-Hans/docusaurus-plugin-content-docs/current/datagen/recipes.md create mode 100644 i18n/zh-Hans/docusaurus-plugin-content-docs/current/datagen/tags.md create mode 100644 i18n/zh-Hans/docusaurus-plugin-content-docs/current/datamaps/_category_.json create mode 100644 i18n/zh-Hans/docusaurus-plugin-content-docs/current/datamaps/index.md create mode 100644 i18n/zh-Hans/docusaurus-plugin-content-docs/current/datamaps/neo_maps.md create mode 100644 i18n/zh-Hans/docusaurus-plugin-content-docs/current/datamaps/structure.md create mode 100644 i18n/zh-Hans/docusaurus-plugin-content-docs/current/datastorage/_category_.json create mode 100644 i18n/zh-Hans/docusaurus-plugin-content-docs/current/datastorage/attachments.md create mode 100644 i18n/zh-Hans/docusaurus-plugin-content-docs/current/datastorage/capabilities.md create mode 100644 i18n/zh-Hans/docusaurus-plugin-content-docs/current/datastorage/codecs.md create mode 100644 i18n/zh-Hans/docusaurus-plugin-content-docs/current/datastorage/nbt.md create mode 100644 i18n/zh-Hans/docusaurus-plugin-content-docs/current/datastorage/saveddata.md create mode 100644 i18n/zh-Hans/docusaurus-plugin-content-docs/current/gettingstarted/_category_.json create mode 100644 i18n/zh-Hans/docusaurus-plugin-content-docs/current/gettingstarted/index.md create mode 100644 i18n/zh-Hans/docusaurus-plugin-content-docs/current/gettingstarted/modfiles.md create mode 100644 i18n/zh-Hans/docusaurus-plugin-content-docs/current/gettingstarted/structuring.md create mode 100644 i18n/zh-Hans/docusaurus-plugin-content-docs/current/gettingstarted/versioning.md create mode 100644 i18n/zh-Hans/docusaurus-plugin-content-docs/current/gui/_category_.json create mode 100644 i18n/zh-Hans/docusaurus-plugin-content-docs/current/gui/menus.md create mode 100644 i18n/zh-Hans/docusaurus-plugin-content-docs/current/gui/screens.md create mode 100644 i18n/zh-Hans/docusaurus-plugin-content-docs/current/items/bewlr.md create mode 100644 i18n/zh-Hans/docusaurus-plugin-content-docs/current/items/index.md create mode 100644 i18n/zh-Hans/docusaurus-plugin-content-docs/current/items/interactionpipeline.md create mode 100644 i18n/zh-Hans/docusaurus-plugin-content-docs/current/items/mobeffects.md create mode 100644 i18n/zh-Hans/docusaurus-plugin-content-docs/current/items/tools.md create mode 100644 i18n/zh-Hans/docusaurus-plugin-content-docs/current/legacy/_category_.json create mode 100644 i18n/zh-Hans/docusaurus-plugin-content-docs/current/legacy/porting.md create mode 100644 i18n/zh-Hans/docusaurus-plugin-content-docs/current/misc/_category_.json create mode 100644 i18n/zh-Hans/docusaurus-plugin-content-docs/current/misc/config.md create mode 100644 i18n/zh-Hans/docusaurus-plugin-content-docs/current/misc/debugprofiler.md create mode 100644 i18n/zh-Hans/docusaurus-plugin-content-docs/current/misc/gametest.mdx create mode 100644 i18n/zh-Hans/docusaurus-plugin-content-docs/current/misc/keymappings.md create mode 100644 i18n/zh-Hans/docusaurus-plugin-content-docs/current/misc/resourcelocation.md create mode 100644 i18n/zh-Hans/docusaurus-plugin-content-docs/current/misc/updatechecker.md create mode 100644 i18n/zh-Hans/docusaurus-plugin-content-docs/current/networking/configuration-tasks.md create mode 100644 i18n/zh-Hans/docusaurus-plugin-content-docs/current/networking/entities.md create mode 100644 i18n/zh-Hans/docusaurus-plugin-content-docs/current/networking/index.md create mode 100644 i18n/zh-Hans/docusaurus-plugin-content-docs/current/networking/payload.md create mode 100644 i18n/zh-Hans/docusaurus-plugin-content-docs/current/resources/client/_category_.json create mode 100644 i18n/zh-Hans/docusaurus-plugin-content-docs/current/resources/client/i18n.md create mode 100644 i18n/zh-Hans/docusaurus-plugin-content-docs/current/resources/client/models/bakedmodel.md create mode 100644 i18n/zh-Hans/docusaurus-plugin-content-docs/current/resources/client/models/datagen.md create mode 100644 i18n/zh-Hans/docusaurus-plugin-content-docs/current/resources/client/models/index.md create mode 100644 i18n/zh-Hans/docusaurus-plugin-content-docs/current/resources/client/models/modelloaders.md create mode 100644 i18n/zh-Hans/docusaurus-plugin-content-docs/current/resources/client/particles.md create mode 100644 i18n/zh-Hans/docusaurus-plugin-content-docs/current/resources/client/sounds.md create mode 100644 i18n/zh-Hans/docusaurus-plugin-content-docs/current/resources/client/textures.md create mode 100644 i18n/zh-Hans/docusaurus-plugin-content-docs/current/resources/index.md create mode 100644 i18n/zh-Hans/docusaurus-plugin-content-docs/current/resources/server/advancements.md create mode 100644 i18n/zh-Hans/docusaurus-plugin-content-docs/current/resources/server/conditional.md create mode 100644 i18n/zh-Hans/docusaurus-plugin-content-docs/current/resources/server/glm.md create mode 100644 i18n/zh-Hans/docusaurus-plugin-content-docs/current/resources/server/index.md create mode 100644 i18n/zh-Hans/docusaurus-plugin-content-docs/current/resources/server/loottables.md create mode 100644 i18n/zh-Hans/docusaurus-plugin-content-docs/current/resources/server/recipes/custom.md create mode 100644 i18n/zh-Hans/docusaurus-plugin-content-docs/current/resources/server/recipes/incode.md create mode 100644 i18n/zh-Hans/docusaurus-plugin-content-docs/current/resources/server/recipes/index.md create mode 100644 i18n/zh-Hans/docusaurus-plugin-content-docs/current/resources/server/recipes/ingredients.md create mode 100644 i18n/zh-Hans/docusaurus-plugin-content-docs/current/resources/server/tags.md create mode 100644 i18n/zh-Hans/docusaurus-theme-classic/footer.json create mode 100644 i18n/zh-Hans/docusaurus-theme-classic/navbar.json diff --git a/docs/advanced/accesstransformers.mdx b/docs/advanced/accesstransformers.mdx index 829efaf09..b8a625ff0 100644 --- a/docs/advanced/accesstransformers.mdx +++ b/docs/advanced/accesstransformers.mdx @@ -3,21 +3,21 @@ import TabItem from '@theme/TabItem'; # Access Transformers -Access Transformers (简称 ATs) 允许开发者扩大类、方法和字段的可见性,以及修改其 `final` 属性。这些工具使得模组开发者能够访问和修改通常无法访问的类成员。 +Access Transformers (ATs for short) allow for widening the visibility and modifying the `final` flags of classes, methods, and fields. They allow modders to access and modify otherwise inaccessible members in classes outside their control. -[规范文档][specs]可以在 NeoForged 的 GitHub 上查看。 +The [specification document][specs] can be viewed on the NeoForged GitHub. -## 添加 ATs +## Adding ATs -将 Access Transformer 添加到您的 mod 项目与在 `build.gradle` 中添加一行代码一样简单: +Adding an Access Transformer to your mod project is as simple as adding a single line into your `build.gradle`: -Access Transformers 需要在 `build.gradle` 和 `mods.toml` 中声明: +Access Transformers need to be declared in both `build.gradle` and `mods.toml`: ```groovy -// 在 build.gradle 中: -// 此部分也是指定映射版本的地方 +// In build.gradle: +// This block is where your mappings version is also specified minecraft { accessTransformers { file('src/main/resources/META-INF/accesstransformer.cfg') @@ -26,14 +26,14 @@ minecraft { ``` ```toml -# 在 mods.toml 中: +# In mods.toml: [[accessTransformers]] file="META-INF/accesstransformer.cfg" ``` -AT 文件可以位于上面几行指定的任何位置,但如果没有指定其他文件,NeoForge 将默认搜索 `META-INF/accesstransformer.cfg`。 +AT files can be anywhere specified by the lines above, though NeoForge will default to searching for `META-INF/accesstransformer.cfg` if no other file is specified. -此外,可以指定多个 AT 文件并按顺序应用。 这对于具有多个软件包的大型模组非常有用。 +Additionally, multiple AT files can be specified and will be applied in order. This can be useful for larger mods with multiple packages. ```groovy // In build.gradle: @@ -54,112 +54,103 @@ file="accesstransformer_main.cfg" file="accesstransformer_additions.cfg" ``` -添加或修改任何访问转换器后,必须刷新 Gradle 项目,以便使更改生效。 +After adding or modifying any Access Transformer, the Gradle project must be refreshed for the transformations to take effect. -在你的模组项目中添加一个 AT 也很简单,只需在 `build.gradle` 中添加一行代码: +Adding an Access Transformer to your mod project is as simple as adding a single line into your `build.gradle`: ```groovy -// 这一部分也是你指定映射版本的地方 +// This block is where your mappings version is also specified minecraft { accessTransformer = file('src/main/resources/META-INF/accesstransformer.cfg') } ``` -在开发环境中,AT 文件可以放在上述指定的任何位置。然而,在非开发环境中加载时,NeoForge 只会搜索你的 JAR 文件中 `META-INF/accesstransformer.cfg` 的确切路径。 +During development, the AT file can be anywhere specified by the line above. However, when loading in a non-development environment, NeoForge will only search for the exact path of `META-INF/accesstransformer.cfg` in your JAR file. -添加或修改访问转换器后,必须刷新 Gradle 项目,以便使更改生效。 +After adding or modifying the Access Transformer, the Gradle project must be refreshed for the transformations to take effect. -## 注释 +## Comments -`#` 之后直到行尾的所有文本都将被视为注释并且不会被解析。 +All text after a `#` until the end of the line will be treated as a comment and will not be parsed. -## 访问控制修饰符 +## Access Modifiers -访问控制修饰符指明目标将被转换到的新的成员的可见性。按可见性递减的顺序排列如下: +Access modifiers specify to what new member visibility the given target will be transformed to. In decreasing order of visibility: -* `public` - 对包内外的所有类可见 -* `protected` - 只对包内的类和子类可见 -* `default` - 只对包内的类可见 -* `private` - 只对类的内部可见 +* `public` - visible to all classes inside and outside its package +* `protected` - visible only to classes inside the package and subclasses +* `default` - visible only to classes inside the package +* `private` - visible only to inside the class -特殊修饰符 `+f` 和 `-f` 可以分别添加到上述的修饰符之后,用以添加或移除 `final` 修饰符,当应用时可防止子类化、方法覆盖或字段修改。 +A special modifier `+f` and `-f` can be appended to the aforementioned modifiers to either add or remove respectively the `final` modifier, which prevents subclassing, method overriding, or field modification when applied. :::danger -指令只修改直接引用的方法;任何覆盖的方法都不会被转换。建议确保转换的方法没有未转换覆盖的方法,否则这会限制其可见性,并可能导致 JVM 报错。 +Directives only modify the method they directly reference; any overriding methods will not be access-transformed. It is advised to ensure transformed methods do not have non-transformed overrides that restrict the visibility, which will result in the JVM throwing an error. -可以安全转换的方法示例包括 `private` 方法、`final` 方法(或 `final` 类中的方法)以及 `static` 方法。 +Examples of methods that can be safely transformed are `private` methods, `final` methods (or methods in `final` classes), and `static` methods. ::: -## 目标和指令 - -### 类 - -定位类的格式如下: +## Targets and Directives +### Classes +To target classes: ``` ``` +Inner classes are denoted by combining the fully qualified name of the outer class and the name of the inner class with a `$` as separator. -内部类通过组合外部类的完全限定名和内部类名,使用 `$` 作为分隔符来表示。 - -### 字段 - -定位字段的格式如下: - +### Fields +To target fields: ``` ``` -### 方法 - -定位方法需要特殊的语法来表示方法参数和返回类型: - +### Methods +Targeting methods require a special syntax to denote the method parameters and return type: ``` () ``` -#### 指定类型 - -也称为"描述符":有关更多技术细节,请参见 [Java 虚拟机规范,SE 8,第 4.3.2 节和 4.3.3 节][jvmdescriptors]。 - -* `B` - `byte`,有符号字节 -* `C` - `char`,Unicode 字符代码点,使用 UTF-16 编码 -* `D` - `double`,双精度浮点值 -* `F` - `float`,单精度浮点值 -* `I` - `integer`,32 位整数 -* `J` - `long`,64 位整数 -* `S` - `short`,有符号短整数 -* `Z` - `boolean`,`true` 或 `false` 值 -* `[` - 表示数组的一个维度 - * 示例: `[[S` 表示 `short[][]` -* `L;` - 表示引用类型 - * 示例: `Ljava/lang/String;` 表示 `java.lang.String` 引 - -用类型 _(注意使用斜线而非点)_ -* `(` - 表示方法描述符,参数应在此处提供,如果没有参数则不提供 - * 示例: `(I)Z` 表示一个需要整型参数并返回布尔值的方法 -* `V` - 表示方法不返回值,只能在方法描述符的末尾使用 - * 示例: `()V` 表示一个没有参数并且不返回任何结果的方法 - -## 示例 +#### Specifying Types + +Also called "descriptors": see the [Java Virtual Machine Specification, SE 8, sections 4.3.2 and 4.3.3][jvmdescriptors] for more technical details. + +* `B` - `byte`, a signed byte +* `C` - `char`, a Unicode character code point in UTF-16 +* `D` - `double`, a double-precision floating-point value +* `F` - `float`, a single-precision floating-point value +* `I` - `integer`, a 32-bit integer +* `J` - `long`, a 64-bit integer +* `S` - `short`, a signed short +* `Z` - `boolean`, a `true` or `false` value +* `[` - references one dimension of an array + * Example: `[[S` refers to `short[][]` +* `L;` - references a reference type + * Example: `Ljava/lang/String;` refers to `java.lang.String` reference type _(note the use of slashes instead of periods)_ +* `(` - references a method descriptor, parameters should be supplied here or nothing if no parameters are present + * Example: `(I)Z` refers to a method that requires an integer argument and returns a boolean +* `V` - indicates a method returns no value, can only be used at the end of a method descriptor + * Example: `()V` refers to a method that has no arguments and returns nothing + +## Examples ``` -# 将 Crypt 中的 ByteArrayToKeyFunction 接口公开 +# Makes public the ByteArrayToKeyFunction interface in Crypt public net.minecraft.util.Crypt$ByteArrayToKeyFunction -# 将 MinecraftServer 中的 'random' 字段设为 protected 并移除 final 修饰 +# Makes protected and removes the final modifier from 'random' in MinecraftServer protected-f net.minecraft.server.MinecraftServer random -# 将 Util 中的 'makeExecutor' 方法公开, -# 接受一个 String 并返回一个 ExecutorService +# Makes public the 'makeExecutor' method in Util, +# accepting a String and returns an ExecutorService public net.minecraft.Util makeExecutor(Ljava/lang/String;)Ljava/util/concurrent/ExecutorService; -# 将 UUIDUtil 中的 'leastMostToIntArray' 方法公开, -# 接受两个 long 并返回一个 int 数组 +# Makes public the 'leastMostToIntArray' method in UUIDUtil, +# accepting two longs and returning an int[] public net.minecraft.core.UUIDUtil leastMostToIntArray(JJ)[I ``` diff --git a/docs/blockentities/ber.md b/docs/blockentities/ber.md index a83d801aa..0196f965d 100644 --- a/docs/blockentities/ber.md +++ b/docs/blockentities/ber.md @@ -1,28 +1,28 @@ BlockEntityRenderer ================== -`BlockEntityRenderer` 或 `BER` 用于以无法用静态烘焙模型(JSON,OBJ,B3D,其他)表示的方式渲染块。块实体渲染器要求块有一个 `BlockEntity`。 +A `BlockEntityRenderer` or `BER` is used to render blocks in a way that cannot be represented with a static baked model (JSON, OBJ, B3D, others). A block entity renderer requires the block to have a `BlockEntity`. -创建 BER +Creating a BER -------------- -要创建 BER,创建一个继承自 `BlockEntityRenderer` 的类。它需要一个泛型参数,指定块的 `BlockEntity` 类。泛型参数在 BER 的 `render` 方法中使用。 +To create a BER, create a class that inherits from `BlockEntityRenderer`. It takes a generic argument specifying the block's `BlockEntity` class. The generic argument is used in the BER's `render` method. -对于给定的 `BlockEntityType`,只存在一个 BER。因此,应将特定于等级中的单个实例的值存储在传递给渲染器的块实体中,而不是在 BER 本身中。例如,每帧递增的整数,如果存储在 BER 中,将会在该类型的等级中的每一个块实体中每帧递增。 +Only one BER exists for a given `BlockEntityType`. Therefore, values that are specific to a single instance in the level should be stored in the block entity being passed to the renderer rather than in the BER itself. For example, an integer that increments every frame, if stored in the BER, will increment every frame for every block entity of this type in the level. ### `render` -每一帧都会调用这个方法来渲染块实体。 +This method is called every frame in order to render the block entity. -#### 参数 -* `blockEntity`:这是正在渲染的块实体的实例。 -* `partialTick`:自上一完整 tick以来已经过去的以 tick 的分数表示的时间。 -* `poseStack`:这是一个堆栈,可以持有四维矩阵条目,这些条目可以偏移到块实体的当前位置。 -* `bufferSource`:一个渲染缓冲区,能够访问顶点消费者。 -* `combinedLight`:块实体上当前光值的整数。 -* `combinedOverlay`:一个设置为块实体当前覆盖层的整数,通常是 `OverlayTexture#NO_OVERLAY` 或 655,360。 +#### Parameters +* `blockEntity`: This is the instance of the block entity being rendered. +* `partialTick`: The amount of time, in fractions of a tick, that has passed since the last full tick. +* `poseStack`: A stack holding four-dimensional matrix entries offset to the current position of the block entity. +* `bufferSource`: A rendering buffer able to access a vertex consumer. +* `combinedLight`: An integer of the current light value on the block entity. +* `combinedOverlay`: An integer set to the current overlay of the block entity, usually `OverlayTexture#NO_OVERLAY` or 655,360. -注册 BER +Registering a BER ----------------- -要注册 BER,你必须订阅模组事件总线上的 `EntityRenderersEvent$RegisterRenderers` 事件,并调用 `#registerBlockEntityRenderer`。 +In order to register a BER, you must subscribe to the `EntityRenderersEvent$RegisterRenderers` event on the mod event bus and call `#registerBlockEntityRenderer`. diff --git a/docs/blockentities/index.md b/docs/blockentities/index.md index 84a2627e0..89ef2b872 100644 --- a/docs/blockentities/index.md +++ b/docs/blockentities/index.md @@ -1,121 +1,142 @@ -## 注册方块实体 +Block Entities +====== -方块实体的创建和移除是动态的,因此它们本身不是注册对象。要创建一个`BlockEntity`,你需要扩展`BlockEntity`类。相应地,另一个对象被注册以方便创建和引用动态对象的*类型*。对于`BlockEntity`,这些类型被称为`BlockEntityType`。 +`BlockEntities` are like simplified `Entities` that are bound to a Block. +They are used to store dynamic data, execute tick based tasks, and dynamic rendering. +Some examples from vanilla Minecraft would be handling of inventories on chests, smelting logic on furnaces, or area effects on beacons. +More advanced examples exist in mods, such as quarries, sorting machines, pipes, and displays. -`BlockEntityType`可以像其他注册对象一样被[注册][registration]。使用`BlockEntityType.Builder#of`来构建`BlockEntityType`,它接受两个参数:一个`BlockEntityType.BlockEntitySupplier`,它接受一个`BlockPos`和`BlockState`来创建新的`BlockEntity`实例,以及一个可变数量的`Block`,这些方块可以附加到此`BlockEntity`。 +:::note +`BlockEntities` aren't a solution for everything and they can cause lag when used wrongly. +When possible, try to avoid them. +::: + +## Registering + +Block Entities are created and removed dynamically and as such are not registry objects on their own. + +In order to create a `BlockEntity`, you need to extend the `BlockEntity` class. As such, another object is registered instead to easily create and refer to the *type* of the dynamic object. For a `BlockEntity`, these are known as `BlockEntityType`s. + +A `BlockEntityType` can be [registered][registration] like any other registry object. To construct a `BlockEntityType`, its builder form can be used via `BlockEntityType$Builder#of`. This takes in two arguments: a `BlockEntityType$BlockEntitySupplier` which takes in a `BlockPos` and `BlockState` to create a new instance of the associated `BlockEntity`, and a varargs of `Block`s which this `BlockEntity` can be attached to. Building the `BlockEntityType` is done by calling `BlockEntityType$Builder#build`. This takes in a `Type` which represents the type-safe reference used to refer to this registry object in a `DataFixer`. Since `DataFixer`s are an optional system to use for mods, this can be passed as `null`. ```java -// 对于某个DeferredRegister> REGISTER +// For some DeferredRegister> REGISTER public static final RegistryObject> MY_BE = REGISTER.register("mybe", () -> BlockEntityType.Builder.of(MyBE::new, validBlocks).build(null)); -// 在MyBE中,一个BlockEntity子类 +// In MyBE, a BlockEntity subclass public MyBE(BlockPos pos, BlockState state) { super(MY_BE.get(), pos, state); } ``` -## 创建方块实体 +## Creating a `BlockEntity` -要创建一个`BlockEntity`并将其附加到一个`Block`,你的`Block`子类必须实现`EntityBlock`接口。必须实现方法`EntityBlock#newBlockEntity(BlockPos, BlockState)`并返回你的`BlockEntity`的新实例。 +To create a `BlockEntity` and attach it to a `Block`, the `EntityBlock` interface must be implemented on your `Block` subclass. The method `EntityBlock#newBlockEntity(BlockPos, BlockState)` must be implemented and return a new instance of your `BlockEntity`. -## 存储你的方块实体内的数据 +## Storing Data within your `BlockEntity` -为了保存数据,覆盖以下两个方法: +In order to save data, override the following two methods: ```java BlockEntity#saveAdditional(CompoundTag tag) BlockEntity#load(CompoundTag tag) ``` -这些方法在包含`BlockEntity`的`LevelChunk`从标签加载/保存时调用。使用这些方法读写你的方块实体类中的字段。 +These methods are called whenever the `LevelChunk` containing the `BlockEntity` gets loaded from/saved to a tag. +Use them to read and write to the fields in your block entity class. :::note -每当你的数据发生变化时,你需要调用`BlockEntity#setChanged`;否则,在级别保存时可能会跳过包含你的`BlockEntity`的`LevelChunk`。 +Whenever your data changes, you need to call `BlockEntity#setChanged`; otherwise, the `LevelChunk` containing your `BlockEntity` might be skipped while the level is saved. ::: :::danger -调用`super`方法非常重要! +It is important that you call the `super` methods! -标签名`id`、`x`、`y`、`z`、`ForgeData`和`ForgeCaps`由`super`方法保留。 +The tag names `id`, `x`, `y`, `z`, `ForgeData` and `ForgeCaps` are reserved by the `super` methods. ::: -## `BlockEntities` 的定时器 +## Ticking `BlockEntities` -如果你需要一个定时的方块实体,例如跟踪熔炼过程中的进度,那么必须在`EntityBlock`内实现并覆盖另一个方法:`EntityBlock#getTicker(Level, BlockState, BlockEntityType)`。这可以实现不同的定时器,取决于用户所在的逻辑侧,或者只实现一个通用定时器。无论哪种情况,都必须返回一个`BlockEntityTicker`。由于这是一个功能接口,它可以仅采用表示定时器的方法: +If you need a ticking `BlockEntity`, for example to keep track of the progress during a smelting process, another method must be implemented and overridden within `EntityBlock`: `EntityBlock#getTicker(Level, BlockState, BlockEntityType)`. This can implement different tickers depending on which logical side the user is on, or just implement one general ticker. In either case, a `BlockEntityTicker` must be returned. Since this is a functional interface, it can just take in a method representing the ticker instead: ```java -// 在某个Block子类内 +// Inside some Block subclass @Nullable @Override public BlockEntityTicker getTicker(Level level, BlockState state, BlockEntityType type) { return type == MyBlockEntityTypes.MYBE.get() ? MyBlockEntity::tick : null; } -// 在MyBlockEntity内 +// Inside MyBlockEntity public static void tick(Level level, BlockPos pos, BlockState state, MyBlockEntity blockEntity) { - // 执行任务 + // Do stuff } ``` :::note -这个方法每个tick都会被调用;因此,你应该避免在这里进行复杂的计算。如果可能,你应该每X个ticks进行更复杂的计算。(一秒钟内的ticks数量可能低于20,但不会更高) +This method is called each tick; therefore, you should avoid having complicated calculations in here. If possible, you should make more complex calculations every X ticks. (The amount of ticks in a second may be lower then 20 (twenty) but won't be higher) ::: -## 将数据同步到客户端 +## Synchronizing the Data to the Client -有三种方法可以将数据同步到客户端:在LevelChunk加载时同步,在方块更新时同步,以及使用自定义网络消息同步。 +There are three ways of syncing data to the client: synchronizing on chunk load, on block updates, and with a custom network message. -### 在LevelChunk加载时同步 +### Synchronizing on LevelChunk Load -为此,你需要覆盖 +For this you need to override ```java BlockEntity#getUpdateTag() IForgeBlockEntity#handleUpdateTag(CompoundTag tag) ``` -第一个方法收集应该发送到客户端的数据,而第二个方法处理这些数据。如果你的`BlockEntity`不包含太多数据,你可能可以使用[存储你的方块实体内的数据][storing-data]部分中的方法。 +Again, this is pretty simple, the first method collects the data that should be sent to the client, +while the second one processes that data. If your `BlockEntity` doesn't contain much data, you might be able to use the methods out of the [Storing Data within your `BlockEntity`][storing-data] section. :::caution -同步过多/无用的方块实体数据可能导致网络拥塞。你应该优化你的网络使用,只在客户端需要时发送客户端需要的信息。例如,通常没有必要在更新标签中发送方块实体的库存,因为这可以通过其[`AbstractContainerMenu`][menu]同步。 +Synchronizing excessive/useless data for block entities can lead to network congestion. You should optimize your network usage by sending only the information the client needs when the client needs it. For instance, it is more often than not unnecessary to send the inventory of a block entity in the update tag, as this can be synchronized via its [`AbstractContainerMenu`][menu]. ::: -### 在方块更新时同步 +### Synchronizing on Block Update -这种方法有点复杂,但你只需覆盖两个或三个方法。这里是它的一个小示例实现: +This method is a bit more complicated, but again you just need to override two or three methods. +Here is a tiny example implementation of it: ```java @Override public CompoundTag getUpdateTag() { CompoundTag tag = new CompoundTag(); - // 将你的数据写入标签 + //Write your data into the tag return tag; } @Override public Packet getUpdatePacket() { - // 从#getUpdateTag获取标签 + // Will get tag from #getUpdateTag return ClientboundBlockEntityDataPacket.create(this); } -// 可以覆盖IForgeBlockEntity#onDataPacket。默认情况下,这将推迟到#load。 +// Can override IForgeBlockEntity#onDataPacket. By default, this will defer to the #load. ``` -静态构造函数`ClientboundBlockEntityDataPacket#create`接受: +The static constructors `ClientboundBlockEntityDataPacket#create` takes: -* `BlockEntity`。 -* 一个可选的函数,从`BlockEntity`获取`CompoundTag`。默认情况下,这使用`BlockEntity#getUpdateTag`。 +* The `BlockEntity`. +* An optional function to get the `CompoundTag` from the `BlockEntity`. By default, this uses `BlockEntity#getUpdateTag`. -现在,要发送数据包,服务器上必须给出更新通知。 +Now, to send the packet, an update notification must be given on the server. ```java Level#sendBlockUpdated(BlockPos pos, BlockState oldState, BlockState newState, int flags) ``` -`pos`应该是你的`BlockEntity`的位置。 -对于`oldState`和`newState`,你可以传递该位置当前的`BlockState`。 -`flags`是一个位掩码,应包含`2`,这将同步更改到客户端。有关更多信息以及其他标志,请参阅`Block`。标志`2`等同于`Block#UPDATE_CLIENTS`。 +The `pos` should be your `BlockEntity`'s position. +For `oldState` and `newState`, you can pass the current `BlockState` at that position. +`flags` is a bitmask that should contain `2`, which will sync the changes to the client. See `Block` for more info as well as the rest of the flags. The flag `2` is equivalent to `Block#UPDATE_CLIENTS`. -### 使用自定义网络消息同步 +### Synchronizing Using a Custom Network Message -这种同步方式可能是最复杂的,但通常是最优化的,因为你可以确保只有你需要同步的数据实际上被同步。你应该首先查看[`Networking`][networking]部分,特别是[`SimpleImpl`][simple_impl],然后再尝试这种方式。一旦你创建了自定义网络消息,你可以使用`SimpleChannel#send(PacketDistributor$PacketTarget, MSG)`将其发送给加载了`BlockEntity`的所有用户。 +This way of synchronizing is probably the most complicated but is usually the most optimized, +as you can make sure that only the data you need to be synchronized is actually synchronized. +You should first check out the [`Networking`][networking] section and especially [`SimpleImpl`][simple_impl] before attempting this. +Once you've created your custom network message, you can send it to all users that have the `BlockEntity` loaded with `SimpleChannel#send(PacketDistributor$PacketTarget, MSG)`. :::caution -进行安全检查非常重要,当消息到达玩家时,`BlockEntity`可能已经被销毁/替换!你还应该检查块是否已加载(`Level#hasChunkAt(BlockPos)`)。 +It is important that you do safety checks, the `BlockEntity` might already be destroyed/replaced when the message arrives at the player! You should also check if the chunk is loaded (`Level#hasChunkAt(BlockPos)`). ::: [registration]: ../concepts/registries.md#methods-for-registering diff --git a/docs/blocks/index.md b/docs/blocks/index.md index 95f1376da..95ed54dd3 100644 --- a/docs/blocks/index.md +++ b/docs/blocks/index.md @@ -1,67 +1,71 @@ -## 统一方块的规则 +# Blocks -在开始之前,你需要明白在游戏中每个方块都只有一个。一个世界由成千上万个在不同位置引用该方块的实例组成。换句话说,同一个方块多次被显示。 +Blocks are essential to the Minecraft world. They make up all the terrain, structures, and machines. Chances are if you are interested in making a mod, then you will want to add some blocks. This page will guide you through the creation of blocks, and some of the things you can do with them. -因此,一个方块只应该在[注册]期间实例化一次。一旦注册了方块,你可以根据需要使用已注册的引用。 +## One Block to Rule Them All -与大多数其他注册表不同,方块可以使用`DeferredRegister`的特殊版本,称为`DeferredRegister.Blocks`。 `DeferredRegister.Blocks`基本上就像`DeferredRegister`,但有一些细微的差别: +Before we get started, it is important to understand that there is only ever one of each block in the game. A world consists of thousands of references to that one block in different locations. In other words, the same block is just displayed a lot of times. -- 它们是通过`DeferredRegister.createBlocks("yourmodid")`创建的,而不是通常的`DeferredRegister.create(...)`方法。 -- `#register`返回一个`DeferredBlock`,它扩展了`DeferredHolder`。 `T`是我们正在注册的方块类的类型。 -- 有一些帮助注册方块的方法。 更多详情请参见[下方]。 +Due to this, a block should only ever be instantiated once, and that is during [registration]. Once the block is registered, you can then use the registered reference as needed. -现在,让我们注册我们的方块: +Unlike most other registries, blocks can use a specialized version of `DeferredRegister`, called `DeferredRegister.Blocks`. `DeferredRegister.Blocks` acts basically like a `DeferredRegister`, but with some minor differences: + +- They are created via `DeferredRegister.createBlocks("yourmodid")` instead of the regular `DeferredRegister.create(...)` method. +- `#register` returns a `DeferredBlock`, which extends `DeferredHolder`. `T` is the type of the class of the block we are registering. +- There are a few helper methods for registering block. See [below] for more details. + +So now, let's register our blocks: ```java //BLOCKS is a DeferredRegister.Blocks public static final DeferredBlock MY_BLOCK = BLOCKS.register("my_block", () -> new Block(...)); ``` -注册了方块后,所有对新的`my_block`的引用应使用此常量。例如,如果你想检查给定位置的方块是否是`my_block`,那么相应的代码看起来像这样: +After registering the block, all references to the new `my_block` should use this constant. For example, if you want to check if the block at a given position is `my_block`, the code for that would look something like this: ```java -level.getBlockState(position) //返回在给定位置放置的方块状态 +level.getBlockState(position) // returns the blockstate placed in the given level (world) at the given position //highlight-next-line .is(MyBlockRegistrationClass.MY_BLOCK.get()); ``` -这种方法也有一个方便的效果,即`block1 == block2`有效,并且可以代替Java的`equals`方法使用(当然,使用`equals`仍然有效,但是因为它还是通过引用进行比较,所以没有意义)。 +This approach also has the convenient effect that `block1 == block2` works and can be used instead of Java's `equals` method (using `equals` still works, of course, but is pointless since it compares by reference anyway). :::danger -不要在注册外部调用`new Block()`!一旦你那样做了,会出现问题: +Do not call `new Block()` outside registration! As soon as you do that, things can and will break: -- 方块必须在注册表解锁时创建。NeoForge为您解锁注册表,并稍后再冻结它们,所以注册是您创建方块的时机窗口。 -- 如果你在注册表再次冻结时尝试创建和/或注册方块,游戏将崩溃并报告一个“null”方块,这可能会非常混乱。 -- 如果你仍然设法拥有一个悬空的方块实例,游戏在同步和保存时将不能识别它,并将其替换为空气。 +- Blocks must be created while registries are unfrozen. NeoForge unfreezes registries for you and freezes them later, so registration is your time window to create blocks. +- If you try to create and/or register a block when registries are frozen again, the game will crash and report a `null` block, which can be very confusing. +- If you still manage to have a dangling block instance, the game will not recognize it while syncing and saving, and replace it with air. ::: -## 创建方块 +## Creating Blocks -如上所述,我们首先创建我们的`DeferredRegister.Blocks`: +As discussed before, we start by creating our `DeferredRegister.Blocks`: ```java public static final DeferredRegister.Blocks BLOCKS = DeferredRegister.createBlocks("yourmodid"); ``` -### 基础方块 +### Basic Blocks -对于不需要特殊功能的简单方块(如圆石,木板等),可以直接使用`Block`类。要做到这一点,在注册期间,用`BlockBehaviour.Properties`参数实例化`Block`。可以使用`BlockBehaviour.Properties#of`创建此`BlockBehaviour.Properties`参数,并可以通过调用其方法进行定制。这其中最重要的方法是: +For simple blocks which need no special functionality (think cobblestone, wooden planks, etc.), the `Block` class can be used directly. To do so, during registration, instantiate `Block` with a `BlockBehaviour.Properties` parameter. This `BlockBehaviour.Properties` parameter can be created using `BlockBehaviour.Properties#of`, and it can be customized by calling its methods. The most important methods for this are: -- `destroyTime`-决定破坏方块所需的时间。 - - 石头的破坏时间为1.5,泥土为0.5,黑曜石为50,基岩为-1(不可破坏)。 -- `explosionResistance`-决定方块的抗爆性。 - - 石头的抗爆性为6.0,泥土为0.5,黑曜石为1,200,基岩为3,600,000。 -- `sound`-设置方块在被击中,打破或放置时的声音。 - - 默认值是`SoundType.STONE`。更多详细信息请参见[声音页面][sounds]。 -- `lightLevel`-设置方块的光线发射。接收一个带有`BlockState`参数的函数,返回0到15之间的值。 - 例如,萤石使用`state -> 15`,火炬使用`state -> 14`。 -- `摩擦` - 设置方块的摩擦(滑滑的程度)。 - - 默认值是0.6。冰使用0.98。 +- `destroyTime` - Determines the time the block needs to be destroyed. + - Stone has a destroy time of 1.5, dirt has 0.5, obsidian has 50, and bedrock has -1 (unbreakable). +- `explosionResistance` - Determines the explosion resistance of the block. + - Stone has an explosion resistance of 6.0, dirt has 0.5, obsidian has 1,200, and bedrock has 3,600,000. +- `sound` - Sets the sound the block makes when it is punched, broken, or placed. + - The default value is `SoundType.STONE`. See the [Sounds page][sounds] for more details. +- `lightLevel` - Sets the light emission of the block. Accepts a function with a `BlockState` parameter that returns a value between 0 and 15. + - For example, glowstone uses `state -> 15`, and torches use `state -> 14`. +- `friction` - Sets the friction (slipperiness) of the block. + - Default value is 0.6. Ice uses 0.98. -例如,一个简单的实现可能看起来像这样: +So for example, a simple implementation would look something like this: ```java -// BLOCKS is a DeferredRegister.Blocks +//BLOCKS is a DeferredRegister.Blocks public static final DeferredBlock MY_BETTER_BLOCK = BLOCKS.register( "my_better_block", () -> new Block(BlockBehaviour.Properties.of() @@ -74,76 +78,78 @@ public static final DeferredBlock MY_BETTER_BLOCK = BLOCKS.register( )); ``` -有关更多文档,请参阅`BlockBehaviour.Properties`的源代码。有关更多示例,或查看Minecraft使用的值,请查看`Blocks`类。 +For further documentation, see the source code of `BlockBehaviour.Properties`. For more examples, or to look at the values used by Minecraft, have a look at the `Blocks` class. :::note -重要的是要理解,世界中的一个方块并不同于库存中的东西。库存中看起来像方块的其实是`BlockItem`,它是一种特殊类型的[物品],在使用时会放置一个方块。这也就意味着,创造标签页或最大堆叠大小等内容都由相应的`BlockItem`处理。 +It is important to understand that a block in the world is not the same thing as in an inventory. What looks like a block in an inventory is actually a `BlockItem`, a special type of [item] that places a block when used. This also means that things like the creative tab or the max stack size are handled by the corresponding `BlockItem`. + +A `BlockItem` must be registered separately from the block. This is because a block does not necessarily need an item, for example if it is not meant to be collected (as is the case with fire, for example). +::: -`BlockItem`必须与方块单独注册。这是因为方块不一定需要一个物品,例如,如果它不能被收集 (例如火)。 -### 更多功能 +### More Functionality -直接使用`Block`只能创造非常基本的方块。如果你想添加功能,像是玩家交互或不同的碰撞箱,就需要一个扩展了`Block`的自定义类。`Block`类有许多可以被重写以实现不同功能的方法;更多信息请参见`Block`、`BlockBehaviour`和`IBlockExtension`类。另外,请查看下方的[使用方块][usingblocks]部分,了解一些方块最常见的用例。 +Directly using `Block` only allows for very basic blocks. If you want to add functionality, like player interaction or a different hitbox, a custom class that extends `Block` is required. The `Block` class has many methods that can be overridden to do different things; see the classes `Block`, `BlockBehaviour` and `IBlockExtension` for more information. See also the [Using blocks][usingblocks] section below for some of the most common use cases for blocks. -如果你想制作一个有不同变体的方块(想想一个有底部、顶部和双层变体的台阶),你应该使用[blockstates]。最后,如果你想要一个可以存储额外数据的方块(比如一个可以存储其库存的箱子),那么应该使用[block entity][blockentities]。这里的经验法则是,如果你有有限而且相当小的状态量(=最多几百个状态),使用blockstates;如果你有无限或近乎无限的状态量,使用方块实体。 +If you want to make a block that has different variants (think a slab that has a bottom, top, and double variant), you should use [blockstates]. And finally, if you want a block that stores additional data (think a chest that stores its inventory), a [block entity][blockentities] should be used. The rule of thumb here is that if you have a finite and reasonably small amount of states (= a few hundred states at most), use blockstates, and if you have an infinite or near-infinite amount of states, use a block entity. -### `DeferredRegister.Blocks` 辅助器 +### `DeferredRegister.Blocks` helpers -我们已经讨论了如何创建`DeferredRegister.Blocks`[上面],以及它返回`DeferredBlock`的内容。现在,让我们看看这个专门的`DeferredRegister`还有哪些辅助工具。我们先从`#registerBlock`开始: +We already discussed how to create a `DeferredRegister.Blocks` [above], as well as that it returns `DeferredBlock`s. Now, let's have a look at what other utilities the specialized `DeferredRegister` has to offer. Let's start with `#registerBlock`: ```java public static final DeferredRegister.Blocks BLOCKS = DeferredRegister.createBlocks("yourmodid"); public static final DeferredBlock EXAMPLE_BLOCK = BLOCKS.registerBlock( "example_block", - Block::new, // 将使用的属性传递到哪个工厂。 - BlockBehaviour.Properties.of() // 要使用的属性。 + Block::new, // The factory that the properties will be passed into. + BlockBehaviour.Properties.of() // The properties to use. ); ``` -在内部,这将简单地通过应用属性参数到所提供的方块工厂(通常是构造函数)来调用`BLOCKS.register("example_block", () -> new Block(BlockBehaviour.Properties.of()))`。 +Internally, this will simply call `BLOCKS.register("example_block", () -> new Block(BlockBehaviour.Properties.of()))` by applying the properties parameter to the provided block factory (which is commonly the constructor). -如果你想使用`Block::new`,可以完全不使用工厂: +If you want to use `Block::new`, you can leave out the factory entirely: ```java public static final DeferredBlock EXAMPLE_BLOCK = BLOCKS.registerSimpleBlock( "example_block", - BlockBehaviour.Properties.of() // 要使用的属性。 + BlockBehaviour.Properties.of() // The properties to use. ); ``` -这和之前的例子做的完全一样,只是稍微简洁了一些。当然,如果你想使用`Block`的子类而不是`Block`本身,你将不得不使用前面的方法。 +This does the exact same as the previous example, but is slightly shorter. Of course, if you want to use a subclass of `Block` and not `Block` itself, you will have to use the previous method instead. -### 资源 +### Resources -当你注册你的方块并将其放置在世界中时,你会发现它缺少如纹理等内容。这是因为[纹理]等内容是由Minecraft的资源系统处理的。要将纹理应用到方块上,你必须提供一个[模型]和一个与纹理和形状关联的[方块状态文件][bsfile]。阅读链接文章以获取更多信息。 +If you register your block and place it in the world, you will find it to be missing things like a texture. This is because [textures], among others, are handled by Minecraft's resource system. To apply the texture to the block, you must provide a [model] and a [blockstate file][bsfile] that associates the block with the texture and a shape. Give the linked articles a read for more information. -## 使用方块 +## Using Blocks -方块很少直接用来做事。实际上,可能在整个Minecraft中最常见的两个操作 - 获取位置上的方块,和设置位置上的方块 - 使用的是方块状态,而不是方块。一般的设计方法是让方块定义行为,但实际上通过方块状态来运行行为。因此,`BlockState`经常作为参数传递给`Block`的方法。有关如何使用方块状态的更多信息,以及如何从方块获取方块状态,请参见[使用方块状态][usingblockstates]。 +Blocks are very rarely directly used to do things. In fact, probably two of the most common operations in all of Minecraft - getting the block at a position, and setting a block at a position - use blockstates, not blocks. The general design approach is to have the block define behavior, but have the behavior actually run through blockstates. Due to this, `BlockState`s are often passed to methods of `Block` as a parameter. For more information on how blockstates are used, and on how to get one from a block, see [Using Blockstates][usingblockstates]. -在几种情况下,`Block`的多个方法在不同的时间被使用。以下小节列出了最常见的与方块相关的流程。除非另有说明,否则所有方法都在逻辑两侧调用,并应在两侧返回相同的结果。 +In several situations, multiple methods of `Block` are used at different times. The following subsections list the most common block-related pipelines. Unless specified otherwise, all methods are called on both logical sides and should return the same result on both sides. -### 放置方块 +### Placing a Block -方块放置逻辑是从`BlockItem#useOn`(或其某些子类的实现,例如用于睡莲的`PlaceOnWaterBlockItem`)调用的。有关游戏如何到达这一点的更多信息,请参见[交互流程][interactionpipeline]。实际上,这意味着一旦`BlockItem`被右键点击(例如圆石物品),这个行为就被调用。 +Block placement logic is called from `BlockItem#useOn` (or some subclass's implementation thereof, such as in `PlaceOnWaterBlockItem`, which is used for lily pads). For more information on how the game gets there, see the [Interaction Pipeline][interactionpipeline]. In practice, this means that as soon as a `BlockItem` is right-clicked (for example a cobblestone item), this behavior is called. -- 检查几个先决条件,例如你不是在旁观者模式下,所有要求的方块功能标志都已启用,或目标位置不在世界边界之外。如果至少有一个检查失败,流程结束。 -- 对当前位于被尝试放置方块的位置的方块调用`Block#canBeReplaced`。如果它返回`false`,流程结束。在这里返回`true`的显著案例是高草或雪层。 -- 调用`Block#getStateForPlacement`。这是根据上下文(包括位置,旋转和放置方块的侧面等信息)返回不同方块状态的地方。这对于例如可以以不同方向放置的方块很有用。 -- 用前一步获得的方块状态调用`Block#canSurvive`。如果返回`false`,流程结束。 -- 通过`Level#setBlock`调用将方块状态设置到游戏世界中。 - - 在那个`Level#setBlock`调用中,调用`Block#onPlace`。 -- 调用`Block#setPlacedBy`。 +- Several prerequisites are checked, for example that you are not in spectator mode, that all required feature flags for the block are enabled or that the target position is not outside the world border. If at least one of these checks fails, the pipeline ends. +- `Block#canBeReplaced` is called for the block currently at the position where the block is attempted to be placed. If it returns `false`, the pipeline ends. Prominent cases that return `true` here are tall grass or snow layers. +- `Block#getStateForPlacement` is called. This is where, depending on the context (which includes information like the position, the rotation and the side the block is placed on), different block states can be returned. This is useful for example for blocks that can be placed in different directions. +- `Block#canSurvive` is called with the blockstate obtained in the previous step. If it returns `false`, the pipeline ends. +- The blockstate is set into the level via a `Level#setBlock` call. + - In that `Level#setBlock` call, `Block#onPlace` is called. +- `Block#setPlacedBy` is called. -### 破坏方块 +### Breaking a Block -破坏方块稍微复杂一些,因为它需要时间。这个过程可以大致分为三个阶段:“启动”,“挖掘”和“实际破坏”。 +Breaking a block is a bit more complex, as it requires time. The process can be roughly divided into three stages: "initiating", "mining" and "actually breaking". -- 当左键被点击时,进入“启动”阶段。 -- 现在,需要持续按住左键,进入“挖掘”阶段。**这个阶段的方法每个刻都会被调用。** -- 如果“继续”阶段没有被中断(通过释放左键)并且方块被打破,那么进入“实际破坏”阶段。 +- When the left mouse button is clicked, the "initiating" stage is entered. +- Now, the left mouse button needs to be held down, entering the "mining" stage. **This stage's methods are called every tick.** +- If the "continuing" stage is not interrupted (by releasing the left mouse button) and the block is broken, the "actually breaking" stage is entered. -或者对于那些更喜欢伪代码的人: +Or for those who prefer pseudocode: ```java leftClick(); @@ -157,65 +163,65 @@ while (leftClickIsBeingHeld()) { } ``` -以下小节进一步将这些阶段分解为实际的方法调用。 +The following subsections further break down these stages into actual method calls. -#### “启动”阶段 +#### The "Initiating" Stage -- 仅客户端:当左键和主手被触发时,会触发`InputEvent.InteractionKeyMappingTriggered`事件。如果事件被取消,流程结束。 -- 检查几个先决条件,例如你不是在旁观者模式下,主手中的`ItemStack`的所有必需功能标志都已启用,或被询问的方块不在世界边界之外。如果至少有一个检查失败,流程结束。 -- 触发`PlayerInteractEvent.LeftClickBlock`事件。如果事件被取消,流程结束。 - - 注意当事件在客户端被取消时,不会向服务器发送数据包,因此服务器上不会运行任何逻辑。 - - 然而,在服务器上取消此事件仍然会导致客户端代码运行,这可能会导致不同步! -- 调用`Block#attack`。 +- Client-only: `InputEvent.InteractionKeyMappingTriggered` is fired with the left mouse button and the main hand. If the event is canceled, the pipeline ends. +- Several prerequisites are checked, for example that you are not in spectator mode, that all required feature flags for the `ItemStack` in your main hand are enabled or that the block in question is not outside the world border. If at least one of these checks fails, the pipeline ends. +- `PlayerInteractEvent.LeftClickBlock` is fired. If the event is canceled, the pipeline ends. + - Note that when the event is canceled on the client, no packets are sent to the server and thus no logic runs on the server. + - However, canceling this event on the server will still cause client code to run, which can lead to desyncs! +- `Block#attack` is called. -#### “挖掘”阶段 +#### The "Mining" Stage -- 触发`PlayerInteractEvent.LeftClickBlock`事件。如果事件被取消,流程移动到“结束”阶段。 - - 注意当事件在客户端被取消时,不会向服务器发送数据包,因此服务器上不会运行任何逻辑。 - - 然而,在服务器上取消此事件仍然会导致客户端代码运行,这可能会导致不同步! -- 调用`Block#getDestroyProgress`并将其加到内部的破坏进度计数器上。 - - `Block#getDestroyProgress`返回一个介于0和1之间的浮点值,表示破坏进度计数器每个刻应该增加多少。 -- 相应地更新进度覆盖(破裂纹理)。 -- 如果破坏进度大于1.0(即完成,即方块应该被破坏),则退出“挖掘”阶段并进入“实际破坏”阶段。 +- `PlayerInteractEvent.LeftClickBlock` is fired. If the event is canceled, the pipeline moves to the "finishing" stage. + - Note that when the event is canceled on the client, no packets are sent to the server and thus no logic runs on the server. + - However, canceling this event on the server will still cause client code to run, which can lead to desyncs! +- `Block#getDestroyProgress` is called and added to the internal destroy progress counter. + - `Block#getDestroyProgress` returns a float value between 0 and 1, representing how much the destroy progress counter should be increased every tick. +- The progress overlay (cracking texture) is updated accordingly. +- If the destroy progress is greater than 1.0 (i.e. completed, i.e. the block should be broken), the "mining" stage is exited and the "actually breaking" stage is entered. -#### “实际破坏”阶段 +#### The "Actually Breaking" Stage -- 调用`Item#onBlockStartBreak`。如果它返回`true`(决定方块不应被破坏),流程移动到“结束”阶段。 -- 仅服务器:调用`IBlockExtension#canHarvestBlock`。这决定了方块是否可以被收获,即是否可以带着掉落物被破坏。 -- 调用`Block#onDestroyedByPlayer`。如果它返回`false`,流程移动到“结束”阶段。在`Block#onDestroyedByPlayer`调用中: - - 调用`Block#playerWillDestroy`。 - - 通过用`Blocks.AIR.defaultBlockState()`作为方块状态参数的`Level#setBlock`调用,从游戏世界中移除方块状态。 - - 在那个`Level#setBlock`调用中,调用`Block#onRemove`。 -- 调用`Block#destroy`。 -- 仅服务器:如果之前对`IBlockExtension#canHarvestBlock`的调用返回了`true`,则调用`Block#playerDestroy`。 -- 仅服务器:调用`IBlockExtension#getExpDrop`。 -- 仅服务器:如果之前`IBlockExtension#getExpDrop`调用的结果大于0,就调用`Block#popExperience`。 +- `Item#onBlockStartBreak` is called. If it returns `true` (determining that the block should not be broken), the pipeline moves to the "finishing" stage. +- Server-only: `IBlockExtension#canHarvestBlock` is called. This determines whether the block can be harvested, i.e. broken with drops. +- `Block#onDestroyedByPlayer` is called. If it returns `false`, the pipeline moves to the "finishing" stage. In that `Block#onDestroyedByPlayer` call: + - `Block#playerWillDestroy` is called. + - The blockstate is removed from the level via a `Level#setBlock` call with `Blocks.AIR.defaultBlockState()` as the blockstate parameter. + - In that `Level#setBlock` call, `Block#onRemove` is called. +- `Block#destroy` is called. +- Server-only: If the previous call to `IBlockExtension#canHarvestBlock` returned `true`, `Block#playerDestroy` is called. +- Server-only: `IBlockExtension#getExpDrop` is called. +- Server-only: `Block#popExperience` is called with the result of the previous `IBlockExtension#getExpDrop` call, if that call returned a value greater than 0. -### 游戏刻 +### Ticking -游戏刻是一种机制,它在每1/20秒或50毫秒(“一个游戏刻”)更新(游戏刻)游戏的某些部分。方块提供了不同的游戏刻方法,这些方法以不同的方式被调用。 +Ticking is a mechanism that updates (ticks) parts of the game every 1 / 20 seconds, or 50 milliseconds ("one tick"). Blocks provide different ticking methods that are called in different ways. -#### 服务器游戏刻和游戏刻调度 +#### Server Ticking and Tick Scheduling -`Block#tick`在两种情况下被调用:通过默认的[随机刻][randomtick](见下文),或通过调度的游戏刻。可以通过`Level#scheduleTick(BlockPos, Block, int)`创建调度的游戏刻,其中`int`表示延迟。这在vanilla的多个地方被使用,例如,大型滴叶的倾斜机制就严重依赖于这个系统。其他显著的使用者包括各种红石组件。 +`Block#tick` is called in two occasions: either through default [random ticking][randomtick] (see below), or through scheduled ticks. Scheduled ticks can be created through `Level#scheduleTick(BlockPos, Block, int)`, where the `int` denotes a delay. This is used in various places by vanilla, for example, the tilting mechanism of big dripleaves heavily relies on this system. Other prominent users are various redstone components. -#### 客户端游戏刻 +#### Client Ticking -`Block#animateTick`仅在客户端,每帧被调用。这是发生客户端仅行为的地方,例如火炬粒子的生成。 +`Block#animateTick` is called exclusively on the client, every frame. This is where client-only behavior, for example the torch particle spawning, happens. -#### 天气游戏刻 +#### Weather Ticking -天气游戏刻由`Block#handlePrecipitation`处理,并独立于常规游戏刻运行。它仅在服务器上被调用,仅当以某种形式下雨时,有1/16的机会被调用。这被用于例如收集雨水或雪水的炼药锅。 +Weather ticking is handled by `Block#handlePrecipitation` and runs independent of regular ticking. It is called only on the server, only when it is raining in some form, with a 1 in 16 chance. This is used for example by cauldrons that fill during rain or snowfall. -#### 随机刻 +#### Random Ticking -随机刻系统独立于常规游戏刻运行。随机刻必须通过调用`BlockBehaviour.Properties`的`BlockBehaviour.Properties#randomTicks()`方法来启用。这使得方块可以是随机刻机制的一部分。 +The random tick system runs independent of regular ticking. Random ticks must be enabled through the `BlockBehaviour.Properties` of the block by calling the `BlockBehaviour.Properties#randomTicks()` method. This enables the block to be part of the random ticking mechanic. -每个刻为一个区块中设定数量的方块执行随机刻。这个设定数量是通过`randomTickSpeed`游戏规则定义的。其默认值为3,每个刻,从区块中随机选择3个方块。如果这些方块启用了随机刻,则分别调用它们的`Block#randomTick`方法。 +Random ticks occur every tick for a set amount of blocks in a chunk. That set amount is defined through the `randomTickSpeed` gamerule. With its default value of 3, every tick, 3 random blocks from the chunk are chosen. If these blocks have random ticking enabled, then their respective `Block#randomTick` methods are called. -`Block#randomTick`默认调用`Block#tick`,这是通常应该被覆盖的。仅当你特别希望随机刻和常规(调度)游戏刻有不同行为时,才应覆盖`Block#randomTick`。 +`Block#randomTick` by default calls `Block#tick`, which is what should normally be overridden. `Block#randomTick` should only be overridden if you specifically want different behavior for random ticking and regular (scheduled) ticking. -随机刻被Minecraft中的许多机制使用,例如植物生长、冰雪融化或铜氧化。 +Random ticking is used by a wide range of mechanics in Minecraft, such as plant growth, ice and snow melting, or copper oxidizing. [above]: #one-block-to-rule-them-all [below]: #deferredregisterblocks-helpers diff --git a/docs/blocks/states.md b/docs/blocks/states.md index 9551668f3..c0829286e 100644 --- a/docs/blocks/states.md +++ b/docs/blocks/states.md @@ -1,14 +1,14 @@ Blockstates =========== -经常会遇到想要拥有不同状态的方块的情况。例如,小麦作物有八个生长阶段,为每个阶段制作一个单独的方块感觉并不合适。或者你有一个台阶或类台阶方块 - 一个底部状态、一个顶部状态,以及一个状态同时具有两者。 +Often, you will find yourself in a situation where you want different states of a block. For example, a wheat crop has eight growth stages, and making a separate block for each stage feels wrong. Or you have a slab or slab-like block - one bottom state, one top state, and one state that has both. -这就是Blockstates发挥作用的地方。Blockstates是一种表示方块可以具有的不同状态的简便方法,如生长阶段或台阶放置类型。 +This is where blockstates come into play. Blockstates are an easy way to represent the different states a block can have, like a growth stage or a slab placement type. -Blockstate 属性 +Blockstate Properties --------------------- -Blockstates使用一套属性系统。一个方块可以具有多种类型的多个属性。例如,一个末地传送门框架有两个属性:它是否有一个眼睛(`eye`,2个选项)和放置的方向(`facing`,4个选项)。所以总的来说,末地传送门框架有8(2 * 4)个不同的Blockstates: +Blockstates use a system of properties. A block can have multiple properties of multiple types. For example, an end portal frame has two properties: whether it has an eye (`eye`, 2 options) and which direction it is placed in (`facing`, 4 options). So in total, the end portal frame has 8 (2 * 4) different blockstates: ``` minecraft:end_portal_frame[facing=north,eye=false] @@ -21,69 +21,68 @@ minecraft:end_portal_frame[facing=south,eye=true] minecraft:end_portal_frame[facing=west,eye=true] ``` -表示法 `blockid[property1=value1,property2=value,...]` 是表示文本形式Blockstate的标准方式,并且在游戏的某些地方使用,例如在命令中。 +The notation `blockid[property1=value1,property2=value,...]` is the standardized way of representing a blockstate in text form, and is used in some locations in vanilla, for example in commands. -如果您的方块没有定义任何Blockstate属性,它仍然有一个Blockstate - 那就是没有任何属性的那个,因为没有属性需要指定。这可以表示为 `minecraft:oak_planks[]` 或者简单的 `minecraft:oak_planks`。 +If your block does not have any blockstate properties defined, it still has exactly one blockstate - that is the one without any properties, since there are no properties to specify. This can be denoted as `minecraft:oak_planks[]` or simply `minecraft:oak_planks`. -与方块一样,每个 `BlockState` 在内存中仅存在一个。这意味着可以并且应该使用 `==` 来比较 `BlockState`。`BlockState` 也是一个终极类,意味着它不能被扩展。**任何功能都在相应的[Block][block]类中!** +As with blocks, every `BlockState` exists exactly once in memory. This means that `==` can and should be used to compare `BlockState`s. `BlockState` is also a final class, meaning it cannot be extended. **Any functionality goes in the corresponding [Block][block] class!** -何时使用Blockstates +When to Use Blockstates ----------------------- -### Blockstates vs. 独立方块 +### Blockstates vs. Separate Blocks -一个好的经验法则是:**如果它有一个不同的名称,它应该是一个独立的方块**。一个例子是制作椅子方块:椅子的方向应该是一个属性,而不同类型的木头应该分成不同的方块。所以你会有一个椅子方块适用于每种木头类型,每个椅子方块有四个Blockstates(每个方向一个)。 +A good rule of thumb is: **if it has a different name, it should be a separate block**. An example is making chair blocks: the direction of the chair should be a property, while the different types of wood should be separated into different blocks. So you'd have one chair block for each wood type, and each chair block has four blockstates (one for each direction). -### Blockstates vs. [方块实体][blockentity] +### Blockstates vs. [Block Entities][blockentity] -这里的经验法则是:**如果你有一个有限的状态量,使用blockstate,如果你有一个无限或几乎无限的状态量,使用方块实体。** 方块实体可以存储任意量的数据,但比blockstates慢。 +Here, the rule of thumb is: **if you have a finite amount of states, use a blockstate, if you have an infinite or near-infinite amount of states, use a block entity.** Block entities can store arbitrary amounts of data, but are slower than blockstates. -Blockstates和方块实体可以联合使用。例如,箱子使用Blockstate属性来表示诸如方向、是否被水淹没或成为双箱子等事物,同时通过方块实体存储库存、是否当前打开或与漏斗的互动等。 +Blockstates and block entities can be used in conjunction with one another. For example, the chest uses blockstate properties for things like the direction, whether it is waterlogged or not, or becoming a double chest, while storing the inventory, whether it is currently open or not, or interacting with hoppers is handled by a block entity. -没有一个明确的答案来回答“对于Blockstate来说,多少状态太多了?”的问题,但我们建议,如果您需要超过8-9比特的数据(即超过几百种状态),您应该使用方块实体代替。 +There is no definitive answer to the question "How many states are too much for a blockstate?", but we recommend that if you need more than 8-9 bits of data (i.e. more than a few hundred states), you should use a block entity instead. -实现Blockstates +Implementing Blockstates ------------------------ -要实现Blockstate属性,在您的方块类中创建或引用一个 `public static final Property` 常量。虽然您可以自由制作自己的 `Property` 实现,但游戏代码提供了几种便利实现,应该涵盖大多数用例: +To implement a blockstate property, in your block class, create or reference a `public static final Property` constant. While you are free to make your own `Property` implementations, the vanilla code provides several convenience implementations that should cover most use cases: * `IntegerProperty` - * 实现 `Property`。定义一个持有整数值的属性。注意不支持负值。 - * 通过调用 `IntegerProperty#create(String propertyName, int minimum, int maximum)` 创建。 + * Implements `Property`. Defines a property that holds an integer value. Note that negative values are not supported. + * Created by calling `IntegerProperty#create(String propertyName, int minimum, int maximum)`. * `BooleanProperty` - * 实现 `Property`。定义一个持有 `true` 或 `false` 值的属性。 - * 通过调用 `BooleanProperty#create(String propertyName)` 创建。 + * Implements `Property`. Defines a property that holds a `true` or `false` value. + * Created by calling `BooleanProperty#create(String propertyName)`. * `EnumProperty>` - * 实现 `Property`。定义一个可以取枚举类值的属性。 - * 通过调用 `EnumProperty#create(String propertyName, Class enumClass)` 创建。 - * 还可以使用枚举值的子集(例如,16个`DyeColor`s中的4个),见 `EnumProperty#create` 的重载方法。 + * Implements `Property`. Defines a property that can take on the values of an Enum class. + * Created by calling `EnumProperty#create(String propertyName, Class enumClass)`. + * It is also possible to use only a subset of the Enum values (e.g. 4 out of 16 `DyeColor`s), see the overloads of `EnumProperty#create`. * `DirectionProperty` - * `DirectionProperty` - * 扩展自 `EnumProperty`。定义了一个可以承载 `Direction`(方向)的属性。 - * 通过调用 `DirectionProperty#create(String propertyName)` 来创建。 - * 提供了几个便利的谓词方法。例如,要获取代表基本方向的属性,调用 `DirectionProperty.create("", Direction.Plane.HORIZONTAL)`;要获取X轴方向,调用 `DirectionProperty.create("", Direction.Axis.X)`。 + * Extends `EnumProperty`. Defines a property that can take on a `Direction`. + * Created by calling `DirectionProperty#create(String propertyName)`. + * Several convenience predicates are provided. For example, to get a property that represents the cardinal directions, call `DirectionProperty.create("", Direction.Plane.HORIZONTAL)`; to get the X directions, `DirectionProperty.create("", Direction.Axis.X)`. -类 `BlockStateProperties` 包含了共享的原版属性,这些属性应尽可能使用或引用,而不是创建自己的属性。 +The class `BlockStateProperties` contains shared vanilla properties which should be used or referenced whenever possible, in place of creating your own properties. -一旦你有了你的属性常量,在你的方块类中重写 `Block#createBlockStateDefinition(StateDefinition$Builder)`。在该方法中,调用 `StateDefinition.Builder#add(YOUR_PROPERTY);`。`StateDefinition.Builder#add` 有一个变长参数,所以如果你有多个属性,你可以一次性添加它们所有。 +Once you have your property constant, override `Block#createBlockStateDefinition(StateDefinition$Builder)` in your block class. In that method, call `StateDefinition.Builder#add(YOUR_PROPERTY);`. `StateDefinition.Builder#add` has a vararg parameter, so if you have multiple properties, you can add them all in one go. -每个方块还有一个默认状态。如果没有指定其他内容,缺省状态使用每个属性的默认值。你可以通过从构造函数中调用 `Block#registerDefaultState(BlockState)` 方法来更改默认状态。 +Every block will also have a default state. If nothing else is specified, the default state uses the default value of every property. You can change the default state by calling the `Block#registerDefaultState(BlockState)` method from your constructor. -如果你希望改变放置方块时使用的 `BlockState`,请重写 `Block#getStateForPlacement(BlockPlaceContext)`。这可以用来设置方块的方向,比如基于玩家放置时的站立位置或方向。 +If you wish to change which `BlockState` is used when placing your block, override `Block#getStateForPlacement(BlockPlaceContext)`. This can be used to, for example, set the direction of your block depending on where the player is standing or looking when they place it. -为进一步说明,这是 `EndPortalFrameBlock` 类相关部分的样子: +To further illustrate this, this is what the relevant bits of the `EndPortalFrameBlock` class look like: ```java public class EndPortalFrameBlock extends Block { - // 注意:直接使用 BlockStateProperties 中的值而不是在这里再次引用它们是可能的。 - // 然而,为了简单和可读性考虑,推荐像这样添加常量。 + // Note: It is possible to directly use the values in BlockStateProperties instead of referencing them here again. + // However, for the sake of simplicity and readability, it is recommended to add constants like this. public static final DirectionProperty FACING = BlockStateProperties.FACING; public static final BooleanProperty EYE = BlockStateProperties.EYE; public EndPortalFrameBlock(BlockBehaviour.Properties pProperties) { super(pProperties); - // stateDefinition.any() 返回一个内部集合中的随机 BlockState, - // 我们不在意,因为我们 anyway 要自己设置所有值 + // stateDefinition.any() returns a random BlockState from an internal set, + // we don't care because we're setting all values ourselves anyway registerDefaultState(stateDefinition.any() .setValue(FACING, Direction.NORTH) .setValue(EYE, false) @@ -92,60 +91,63 @@ public class EndPortalFrameBlock extends Block { @Override protected void createBlockStateDefinition(StateDefinition.Builder pBuilder) { - // 这里是属性实际被添加到状态的地方 + // this is where the properties are actually added to the state pBuilder.add(FACING, EYE); } @Override @Nullable public BlockState getStateForPlacement(BlockPlaceContext pContext) { - // 根据 BlockPlaceContext 确定放置此方块时将使用的状态的 - // 代码 + // code that determines which state will be used when + // placing down this block, depending on the BlockPlaceContext } } ``` -使用 Blockstates ----------------- +Using Blockstates +----------------- -要从 `Block` 转换到 `BlockState`,调用 `Block#defaultBlockState()`。可以通过 `Block#registerDefaultState` 更改默认 blockstate,如上所述。 +To go from `Block` to `BlockState`, call `Block#defaultBlockState()`. The default blockstate can be changed through `Block#registerDefaultState`, as described above. -你可以通过调用 `BlockState#getValue(Property)` 来获取一个属性的值,传递你想获取值的属性。复用我们末地传送门框架的例子,这看起来像这样: +You can get the value of a property by calling `BlockState#getValue(Property)`, passing it the property you want to get the value of. Reusing our end portal frame example, this would look something like this: ```java -// EndPortalFrameBlock.FACING 是一个 DirectionProperty,因此可以用来从 BlockState 中获取一个 Direction +// EndPortalFrameBlock.FACING is a DirectionProperty and thus can be used to obtain a Direction from the BlockState Direction direction = endPortalFrameBlockState.getValue(EndPortalFrameBlock.FACING); ``` -如果你想获得一个具有不同值集的 `BlockState`,只需在现有的 block state 上调用 `BlockState#setValue(Property, T)` 并传入属性及其值。对于我们的杠杆来说,像这样: +If you want to get a `BlockState` with a different set of values, simply call `BlockState#setValue(Property, T)` on an existing block state with the property and its value. With our lever, this goes something like this: ```java endPortalFrameBlockState = endPortalFrameBlockState.setValue(EndPortalFrameBlock.FACING, Direction.SOUTH); ``` :::note -`BlockState` 是不可变的。这意味着当你调用 `#setValue(Property, T)` 时,你实际上不是在修改 blockstate。相反,内部进行了查找,你得到了你请求的 blockstate 对象,那是唯一存在的、具有这些确切属性值的对象。这也意味着,仅仅调用 `state#setValue` 而没有将其保存到一个变量中(例如回到 `state` 中)是无效的。 +`BlockState`s are immutable. This means that when you call `#setValue(Property, T)`, you are not actually modifying the blockstate. Instead, a lookup is performed internally, and you are given the blockstate object you requested, which is the one and only object that exists with these exact property values. This also means that just calling `state#setValue` without saving it into a variable (for example back into `state`) does nothing. ::: -要从场景中获取一个 `BlockState`,使用 `Level#getBlockState(BlockPos)`。 +To get a `BlockState` from the level, use `Level#getBlockState(BlockPos)`. ### `Level#setBlock` -要在场景中设置一个 `BlockState`,使用 `Level#setBlock(BlockPos, BlockState, int)`。 +To set a `BlockState` in the level, use `Level#setBlock(BlockPos, BlockState, int)`. -`int` 参数需要额外的解释,因为它的含义不立即明显。它表示更新标志。 +The `int` parameter deserves some extra explanation, as its meaning is not immediately obvious. It denotes what is known as update flags. -为了正确设置更新标志,`Block` 中有一些以 `UPDATE_` 开头的 `int` 常量。这些常量可以被按位或在一起(例如 `Block.UPDATE_NEIGHBORS | Block.UPDATE_CLIENTS`)如果你希望组合它们。 +To help setting the update flags correctly, there are a number of `int` constants in `Block`, prefixed with `UPDATE_`. These constants can be bitwise-ORed together (for example `Block.UPDATE_NEIGHBORS | Block.UPDATE_CLIENTS`) if you wish to combine them. -- `Block.UPDATE_NEIGHBORS` 向相邻方块发送更新。更具体地说,它调用了 `Block#neighborChanged`,它调用了许多方法,其中大部分以某种方式与红石相关。 -- `Block.UPDATE_CLIENTS` 将方块更新同步到客户端。 -- `Block.UPDATE_INVISIBLE` 显式不在客户端更新。这也覆盖了 `Block.UPDATE_CLIENTS`,导致更新不被同步。方块始终在服务器上更新。 -- `Block.UPDATE_IMMEDIATE` 强制在客户端的主线程上重新渲染。 -- `Block.UPDATE_KNOWN_SHAPE` 停止邻居更新递归。 -- `Block.UPDATE_SUPPRESS_DROPS` 禁止旧方块在该位置的掉落物。 -- `Block.UPDATE_MOVE_BY_PISTON` 仅被活塞代码用于表示方块被活塞移动。这主要是为了延迟光照引擎的更新。 -- `Block.UPDATE_ALL` 是 `Block.UPDATE_NEIGHBORS | Block.UPDATE_CLIENTS` 的别名。 -- `Block.UPDATE_ALL_IMMEDIATE` 是 `Block.UPDATE_NEIGHBORS | Block.UPDATE_CLIENTS | Block.UPDATE_IMMEDIATE` 的别名。 -- `Block.NONE` 是 `Block.UPDATE_INVISIBLE` 的别名。 +- `Block.UPDATE_NEIGHBORS` sends an update to the neighboring blocks. More specifically, it calls `Block#neighborChanged`, which calls a number of methods, most of which are redstone-related in some way. +- `Block.UPDATE_CLIENTS` syncs the block update to the client. +- `Block.UPDATE_INVISIBLE` explicitly does not update on the client. This also overrules `Block.UPDATE_CLIENTS`, causing the update to not be synced. The block is always updated on the server. +- `Block.UPDATE_IMMEDIATE` forces a re-render on the client's main thread. +- `Block.UPDATE_KNOWN_SHAPE` stops neighbor update recursion. +- `Block.UPDATE_SUPPRESS_DROPS` disables block drops for the old block at that position. +- `Block.UPDATE_MOVE_BY_PISTON` is only used by piston code to signal that the block was moved by a piston. This is mainly responsible for delaying light engine updates. +- `Block.UPDATE_ALL` is an alias for `Block.UPDATE_NEIGHBORS | Block.UPDATE_CLIENTS`. +- `Block.UPDATE_ALL_IMMEDIATE` is an alias for `Block.UPDATE_NEIGHBORS | Block.UPDATE_CLIENTS | Block.UPDATE_IMMEDIATE`. +- `Block.NONE` is an alias for `Block.UPDATE_INVISIBLE`. -还有一个便利方法 `Level#setBlockAndUpdate(BlockPos pos, BlockState state)`,它在内部调用 `setBlock(pos, state, Block.UPDATE_ALL)`。 +There is also a convenience method `Level#setBlockAndUpdate(BlockPos pos, BlockState state)` that calls `setBlock(pos, state, Block.UPDATE_ALL)` internally. + +[block]: index.md +[blockentity]: ../blockentities/index.md diff --git a/docs/concepts/events.md b/docs/concepts/events.md index 83debfc15..64b0b6f21 100644 --- a/docs/concepts/events.md +++ b/docs/concepts/events.md @@ -1,16 +1,16 @@ -# 事件系统 +# Events -NeoForge 的核心特性之一是其事件系统。在游戏中,各种事件根据游戏内的不同动作而触发。比如玩家右键点击、玩家或其他实体跳跃、方块渲染、游戏加载时等,都会触发相应的事件。模组开发者可以为这些事件编写处理函数,并在函数中实现他们期望的行为。 +One of NeoForge's main features is the event system. Events are fired for various things that happen in the game. For example, there are events for when the player right clicks, when a player or another entity jumps, when blocks are rendered, when the game is loaded, etc. A modder can subscribe event handlers to each of these events, and then perform their desired behavior inside these event handlers. -这些事件会在相应的事件总线上触发。其中最重要的是 `NeoForge.EVENT_BUS`。此外,在游戏启动期间,系统会为每个加载的模组生成一个独立的模组总线,并传递给模组的构造函数。许多模组总线事件是并行触发的,这与总在同一个线程上运行的主总线事件不同,这种设计显著提高了启动速度。更多细节,请参考[下文][modbus]。 +Events are fired on their respective event bus. The most important bus is `NeoForge.EVENT_BUS`. Besides that, during startup, a mod bus is spawned for each loaded mod and passed into the mod's constructor. Many mod bus events are fired in parallel (as opposed to main bus events that always run on the same thread), dramatically increasing startup speed. See [below][modbus] for more information. -## 注册事件处理函数 +## Registering an Event Handler -注册事件处理函数有多种方式。所有这些方式的共同点是,每个事件处理函数都是一个只接收单一事件参数并且不返回结果(即返回类型为 `void`)的方法。 +There are multiple ways to register event handlers. Common for all of those ways is that every event handler is a method with a single event parameter and no result (i.e. return type `void`). ### `IEventBus#addListener` -最简单的注册方法是直接引用方法,如下所示: +The simplest way to register method handlers is by registering their method reference, like so: ```java @Mod("yourmodid") @@ -19,10 +19,10 @@ public class YourMod { NeoForge.EVENT_BUS.addListener(YourMod::onLivingJump); } - // 每次实体跳跃时为其恢复半颗心的生命值。 + // Heals an entity by half a heart every time they jump. private static void onLivingJump(LivingJumpEvent event) { Entity entity = event.getEntity(); - // 仅在服务器端进行治疗 + // Only heal on the server side if (!entity.level().isClientSide()) { entity.heal(1); } @@ -32,7 +32,7 @@ public class YourMod { ### `@SubscribeEvent` -另一种方式是使用注解来驱动事件处理,为处理函数添加 `@SubscribeEvent` 注解。然后将包含该处理函数的类的实例传递给事件总线,从而注册该实例中所有带有 `@SubscribeEvent` 注解的事件处理函数: +Alternatively, event handlers can be annotation-driven by creating an event handler method and annotating it with `@SubscribeEvent`. Then, you can pass an instance of the encompassing class to the event bus, registering all `@SubscribeEvent`-annotated event handlers of that instance: ```java public class EventHandler { @@ -53,7 +53,7 @@ public class YourMod { } ``` -你还可以通过将所有事件处理函数设置为静态,并直接传递类本身,而不是类的实例来实现: +You can also do it statically. Simply make all event handlers static, and instead of a class instance, pass in the class itself: ```java public class EventHandler { @@ -76,9 +76,9 @@ public class YourMod { ### `@Mod.EventBusSubscriber` -我们可以进一步优化,将事件处理类标注为 `@Mod.EventBusSubscriber`。这个注解会被 NeoForge 自动识别,允许你从模组构造函数中移除所有与事件相关的代码。实际上,这等同于在模组构造结束时调用 `NeoForge.EVENT_BUS.register(EventHandler.class)`。这也意味着所有的处理函数必须设置为静态。 +We can go one step further and also annotate the event handler class with `@Mod.EventBusSubscriber`. This annotation is discovered automatically by NeoForge, allowing you to remove all event-related code from the mod constructor. In essence, it is equivalent to calling `NeoForge.EVENT_BUS.register(EventHandler.class)` at the end of the mod constructor. This means that all handlers must be static, too. -虽然不是必须的,但强烈建议在注解中指定 `modid` 参数,以便在处理模组冲突时能够更容易进行调试。 +While not required, it is highly recommended to specify the `modid` parameter in the annotation, in order to make debugging easier (especially when it comes to mod conflicts). ```java @Mod.EventBusSubscriber(modid = "yourmodid") @@ -93,41 +93,87 @@ public class EventHandler { } ``` -### 生命周期事件 +## Event Options -大多数模组总 +### Fields and Methods -线事件被称为生命周期事件。生命周期事件在每个模组的生命周期中仅在启动时运行一次。很多这类事件是并行触发的,如果你想要在主线程上运行这些事件的代码,可以使用 `#enqueueWork(Runnable runnable)` 方法将它们加入队列。 +Fields and methods are probably the most obvious part of an event. Most events contain context for the event handler to use, such as an entity causing the event or a level the event occurs in. -生命周期事件通常按以下顺序进行: +### Hierarchy -- 调用模组构造函数。在这里或下一步注册你的事件处理函数。 -- 所有的 `@Mod.EventBusSubscriber` 被调用。 -- 触发 `FMLConstructModEvent` 事件。 -- 触发注册事件,包括 [`NewRegistryEvent`][newregistry]、[`DataPackRegistryEvent.NewRegistry`][newdatapackregistry] 以及每个注册表的 [`RegisterEvent`][registerevent]。 -- 触发 `FMLCommonSetupEvent` 事件。这是进行各种杂项设置的阶段。 -- 根据服务器类型触发侧边设置事件:如果在客户端,则为 `FMLClientSetupEvent`;如果在服务器,则为 `FMLDedicatedServerSetupEvent`。 -- 处理 `InterModComms`(详情见下文)。 -- 触发 `FMLLoadCompleteEvent` 事件。 +In order to use the advantages of inheritance, some events do not directly extend `Event`, but one of its subclasses, for example `BlockEvent` (which contains block context for block-related events) or `EntityEvent` (which similarly contains entity context) and its subclasses `LivingEvent` (for `LivingEntity`-specific context) and `PlayerEvent` (for `Player`-specific context). These context-providing super events are `abstract` and cannot be listened to. + +:::danger +If you listen to an `abstract` event, your game will crash, as this is never what you want. You always want to listen to one of the subevents instead. +::: + +### Cancellable Events + +Some events implement the `ICancellableEvent` interface. These events can be cancelled using `#setCanceled(boolean canceled)`, and the cancellation status can be checked using `#isCanceled()`. If an event is cancelled, other event handlers for this event will not run, and some kind of behavior that is associated with "cancelling" is enabled. For example, cancelling `LivingJumpEvent` will prevent the jump. + +Event handlers can opt to explicitly receive cancelled events. This is done by setting the `receiveCanceled` parameter in `IEventBus#addListener` (or `@SubscribeEvent`, depending on your way of attaching the event handlers) to true. + +### Results + +Some events have a `Result`. A `Result` can be one of three things: `DENY` which stops the event, `ALLOW` which force-runs the event, and `DEFAULT` which uses the Vanilla behavior. The result of an event can be set by calling `Event#setResult`. Not all events have results; an event with a result will be annotated with `@HasResult`. + +:::caution +Results are deprecated and will be replaced by more specific per-event results soon. +::: + +### Priority + +Event handlers can optionally get assigned a priority. The `EventPriority` enum contains five values: `HIGHEST`, `HIGH`, `NORMAL` (default), `LOW` and `LOWEST`. Event handlers are executed from highest to lowest priority. If they have the same priority, they fire in registration order on the main bus, which is roughly related to mod load order, and in exact mod load order on the mod bus (see below). + +Priorities can be defined by setting the `priority` parameter in `IEventBus#addListener` or `@SubscribeEvent`, depending on how you attach event handlers. Note that priorities are ignored for events that are fired in parallel. + +### Sided Events + +Some events are only fired on one [side][side]. Common examples include the various render events, which are only fired on the client. Since client-only events generally need to access other client-only parts of the Minecraft codebase, they need to be registered accordingly. + +Event handlers that use `IEventBus#addListener()` should use a `FMLEnvironment.dist` check and a separate client-only class, as outlined in the article on sides. + +Event handlers that use `@Mod.EventBusSubscriber` can specify the side as the `value` parameter of the annotation, for example `@Mod.EventBusSubscriber(value = Dist.CLIENT, modid = "yourmodid")`. + +## Event Buses + +While most events are posted on the `NeoForge.EVENT_BUS`, some events are posted on the mod event bus instead. These are generally called mod bus events. Mod bus events can be distinguished from regular events by their superinterface `IModBusEvent`. + +The mod event bus is passed to you as a parameter in the mod constructor, and you can then subscribe mod bus events to it. If you use `@Mod.EventBusSubscriber`, you can also set the bus as an annotation parameter, like so: `@Mod.EventBusSubscriber(bus = Bus.MOD, modid = "yourmodid")`. The default bus is `Bus.FORGE`. + +### The Mod Lifecycle + +Most mod bus events are what is known as lifecycle events. Lifecycle events run once in every mod's lifecycle during startup. Many of them are fired in parallel; if you want to run code from one of these events on the main thread, enqueue them using `#enqueueWork(Runnable runnable)`. + +The lifecycle generally follows the following order: + +- The mod constructor is called. Register your event handlers here, or in the next step. +- All `@Mod.EventBusSubscriber`s are called. +- `FMLConstructModEvent` is fired. +- The registry events are fired, these include [`NewRegistryEvent`][newregistry], [`DataPackRegistryEvent.NewRegistry`][newdatapackregistry] and, for each registry, [`RegisterEvent`][registerevent]. +- `FMLCommonSetupEvent` is fired. This is where various miscellaneous setup happens. +- The [sided][side] setup is fired: `FMLClientSetupEvent` if on a physical client, and `FMLDedicatedServerSetupEvent` if on a physical server. +- `InterModComms` are handled (see below). +- `FMLLoadCompleteEvent` is fired. #### `InterModComms` -`InterModComms` 是一个系统,允许模组开发者向其他模组发送消息以实现功能兼容。这个系统保存了模组的消息,所有方法都是线程安全的。主要通过两个事件推动:`InterModEnqueueEvent` 和 `InterModProcessEvent`。 +`InterModComms` is a system that allows modders to send messages to other mods for compatibility features. The class holds the messages for mods, all methods are thread-safe to call. The system is mainly driven by two events: `InterModEnqueueEvent` and `InterModProcessEvent`. -在 `InterModEnqueueEvent` 期间,你可以使用 `InterModComms#sendTo` 向其他模组发送消息。这些方法接受要发送消息到的模组的 ID、与消息数据相关的键(以区分不同的消息),以及持有消息数据的 `Supplier`。发送者可以选择性指定。 +During `InterModEnqueueEvent`, you can use `InterModComms#sendTo` to send messages to other mods. These methods accept the id of the mod to send the message to, the key associated with the message data (to distinguish between different messages), and a `Supplier` holding the message data. The sender can be optionally specified as well. -接着,在 `InterModProcessEvent` 期间,你可以使用 `InterModComms#getMessages` 获取作为 `IMCMessage` 对象的所有接收到的消息的流。这些消息包含了数据的发送者、预期的接收者、数据键和实际数据的供应商。 +Then, during `InterModProcessEvent`, you can use `InterModComms#getMessages` to get a stream of all received messages as `IMCMessage` objects. These hold the sender of the data, the intended receiver of the data, the data key, and the supplier for the actual data. -### 其他模组总线事件 +### Other Mod Bus Events -除了生命周期事件外,还有一些其他在模组总线上触发的杂项事件,主要是出于历史原因。这些事件通常不是并行运行的,与生命周期事件相反。例如: +Next to the lifecycle events, there are a few miscellaneous events that are fired on the mod event bus, mostly for legacy reasons. These are generally events where you can register, set up, or initialize various things. Most of these events are not ran in parallel in contrast to the lifecycle events. A few examples: - `RegisterColorHandlersEvent` - `ModelEvent.BakingCompleted` - `TextureStitchEvent` :::warning -计划在未来版本中将大多数这些事件转移到主事件总线上。 +Most of these events are planned to be moved to the main event bus in a future version. ::: [modbus]: #event-buses diff --git a/docs/concepts/registries.md b/docs/concepts/registries.md index c959c41e3..5dea1fab0 100644 --- a/docs/concepts/registries.md +++ b/docs/concepts/registries.md @@ -1,92 +1,90 @@ -# 注册 +# Registries -注册是将模组中的对象(如[物品][item]、[方块][block]、实体等)加入游戏并使其被游戏识别的过程。注册这些对象非常重要,因为如果不注册,游戏将无法识别这些对象,这将导致无法解释的行为和崩溃。 +Registration is the process of taking the objects of a mod (such as [items][item], [blocks][block], entities, etc.) and making them known to the game. Registering things is important, as without registration the game will simply not know about these objects, which will cause unexplainable behaviors and crashes. -简而言之,注册表是围绕映射注册名称(下面将说明)到注册对象的映射的封装,这些注册对象通常称为注册表项。注册名在同一注册表中必须唯一,但同一注册名可以出现在多个注册表中。最常见的例子是方块(在`BLOCKS`注册表中)具有与其同名的物品形式(在`ITEMS`注册表中)。 +A registry is, simply put, a wrapper around a map that maps registry names (read on) to registered objects, often called registry entries. Registry names must be unique within the same registry, but the same registry name may be present in multiple registries. The most common example for this are blocks (in the `BLOCKS` registry) that have an item form with the same registry name (in the `ITEMS` registry). -每个注册对象都有一个唯一的名称,称为其注册名称。名称表示为[`ResourceLocation`][resloc]。例如,泥土方块的注册名称为`minecraft:dirt`,僵尸的注册名称为`minecraft:zombie`。当然,模组化对象不会使用`minecraft`命名空间;而是使用它们的模组ID。 +Every registered object has a unique name, called its registry name. The name is represented as a [`ResourceLocation`][resloc]. For example, the registry name of the dirt block is `minecraft:dirt`, and the registry name of the zombie is `minecraft:zombie`. Modded objects will of course not use the `minecraft` namespace; their mod id will be used instead. -## 原版与模组化 +## Vanilla vs. Modded -为了理解NeoForge的注册系统中所做的一些设计决策,我们首先看看Minecraft是如何处理这一问题的。我们将使用方块注册表作为例子,因为大多数其他注册表的工作方式相同。 +To understand some of the design decisions that were made in NeoForge's registry system, we will first look at how Minecraft does this. We will use the block registry as an example, as most other registries work the same way. -注册表通常注册[单例][singleton]。这意味着所有注册表项实际上只存在一次。例如,你在游戏中看到的所有石块实际上都是同一个石块,被多次显示。如果你需要石块,可以通过引用已注册的方块实例来获取它。 +Registries generally register [singletons][singleton]. This means that all registry entries exist exactly once. For example, all stone blocks you see throughout the game are actually the same stone block, displayed many times. If you need the stone block, you can get it by referencing the registered block instance. -Minecraft在`Blocks`类中注册所有方块。通过`register`方法,调用`Registry#register()`,其中第一个参数是方块注册表`BuiltInRegistries.BLOCK`。所有方块注册完成后,Minecraft会根据方块列表进行各种检查,例如验证所有方块是否已加载模型的自检。 +Minecraft registers all blocks in the `Blocks` class. Through the `register` method, `Registry#register()` is called, with the block registry at `BuiltInRegistries.BLOCK` being the first parameter. After all blocks are registered, Minecraft performs various checks based on the list of blocks, for example the self check that verifies that all blocks have a model loaded. -这一切之所以能够工作,是因为`Blocks`类在Minecraft中足够早地被类加载。模组并不会被Minecraft自动类加载,因此需要一些变通方法。 +The main reason all of this works is that `Blocks` is classloaded early enough by Minecraft. Mods are not automatically classloaded by Minecraft, and thus workarounds are needed. -## 注册方法 +## Methods for Registering -NeoForge提供了两种注册对象的方式:`DeferredRegister`类和`RegisterEvent`。请注意,前者是后者的封装,并且为了防止错误,推荐使用。 +NeoForge offers two ways to register objects: the `DeferredRegister` class, and the `RegisterEvent`. Note that the former is a wrapper around the latter, and is recommended in order to prevent mistakes. ### `DeferredRegister` -我们首先创建我们的`DeferredRegister`: +We begin by creating our `DeferredRegister`: ```java public static final DeferredRegister BLOCKS = DeferredRegister.create( - // 我们想要使用的注册表。 - // Minecraft的注册表可以在BuiltInRegistries中找到,NeoForge的注册表可以在NeoForgeRegistries中找到。 - // 模组也可以添加它们自己的注册表,具体请参考各个模组的文档或源代码。 + // The registry we want to use. + // Minecraft's registries can be found in BuiltInRegistries, NeoForge's registries can be found in NeoForgeRegistries. + // Mods may also add their own registries, refer to the individual mod's documentation or source code for where to find them. BuiltInRegistries.BLOCKS, - // 我们的模组ID。 + // Our mod id. ExampleMod.MOD_ID ); ``` -然后,我们可以将我们的注册表项添加为静态最终字段(有关在`new Block()`中添加什么参数,请参阅[关于方块的文章][block]): +We can then add our registry entries as static final fields (see [the article on Blocks][block] for what parameters to add in `new Block()`): ```java public static final DeferredHolder EXAMPLE_BLOCK = BLOCKS.register( - "example_block" // 我们的注册名称。 - () -> new Block(...) // 我们想要注册的对象的供应商。 + "example_block" // Our registry name. + () -> new Block(...) // A supplier of the object we want to register. ); ``` -`DeferredHolder`类持有我们的对象。类型参数`R`是我们正在注册到的注册表的类型(在这个例子中是`Block`)。类型参数`T`是我们供应商的类型。由于我们在这个例子中直接注册了一个`Block`,我们提供了`Block`作为第二个参数。如果我们 +The class `DeferredHolder` holds our object. The type parameter `R` is the type of the registry we are registering to (in our case `Block`). The type parameter `T` is the type of our supplier. Since we directly register a `Block` in this example, we provide `Block` as the second parameter. If we were to register an object of a subclass of `Block`, for example `SlabBlock`, we would provide `SlabBlock` here instead. -要注册一个`Block`的子类的对象,例如`SlabBlock`,我们将在此提供`SlabBlock`。 - -`DeferredHolder`是`Supplier`的子类。当我们需要时可以调用`DeferredHolder#get()`来获取我们注册的对象。`DeferredHolder`扩展`Supplier`的事实也允许我们使用`Supplier`作为我们字段的类型。这样,上面的代码块变为以下内容: +`DeferredHolder` is a subclass of `Supplier`. To get our registered object when we need it, we can call `DeferredHolder#get()`. The fact that `DeferredHolder` extends `Supplier` also allows us to use `Supplier` as the type of our field. That way, the above code block becomes the following: ```java public static final Supplier EXAMPLE_BLOCK = BLOCKS.register( - "example_block" // 我们的注册名称。 - () -> new Block(...) // 我们想要注册的对象的供应商。 + "example_block" // Our registry name. + () -> new Block(...) // A supplier of the object we want to register. ); ``` -请注意,有些地方明确要求使用`Holder`或`DeferredHolder`,而不仅仅接受任何`Supplier`。如果你需要其中的两者之一,最好将你的`Supplier`的类型更改回`Holder`或`DeferredHolder`。 +Be aware that a few places explicitly require a `Holder` or `DeferredHolder` and will not just accept any `Supplier`. If you need either of those two, it is best to change the type of your `Supplier` back to `Holder` or `DeferredHolder` as necessary. -最后,由于整个系统是围绕注册事件的封装,我们需要告诉`DeferredRegister`根据需要将自己附加到注册事件上: +Finally, since the entire system is a wrapper around registry events, we need to tell the `DeferredRegister` to attach itself to the registry events as needed: ```java -// 这是我们的模组构造函数 +//This is our mod constructor public ExampleMod(IModEventBus bus) { - // 高亮下一行 + //highlight-next-line ExampleBlocksClass.BLOCKS.register(bus); - // 这里还有其他内容 + //Other stuff here } ``` :::info -有针对方块和物品的`DeferredRegister`的特化变体,它们提供辅助方法,分别称为[`DeferredRegister.Blocks`][defregblocks]和[`DeferredRegister.Items`][defregitems]。 +There are specialized variants of `DeferredRegister`s for blocks and items that provide helper methods, called [`DeferredRegister.Blocks`][defregblocks] and [`DeferredRegister.Items`][defregitems], respectively. ::: ### `RegisterEvent` -`RegisterEvent`是注册对象的第二种方式。这个[事件][event]在模组构造函数之后(因为这是`DeferredRegister`注册它们内部事件处理器的地方)和加载配置之前为每个注册表触发。`RegisterEvent`在模组事件总线上触发。 +`RegisterEvent` is the second way to register objects. This [event][event] is fired for each registry, after the mod constructors (since those are where `DeferredRegister`s register their internal event handlers) and before the loading of configs. `RegisterEvent` is fired on the mod event bus. ```java @SubscribeEvent public void register(RegisterEvent event) { event.register( - // 这是注册表的注册键。 - // 从BuiltInRegistries获取vanilla注册表的, - // 或从NeoForgeRegistries.Keys获取NeoForge注册表的。 + // This is the registry key of the registry. + // Get these from BuiltInRegistries for vanilla registries, + // or from NeoForgeRegistries.Keys for NeoForge registries. BuiltInRegistries.BLOCKS, - // 在这里注册你的对象。 + // Register your objects here. registry -> { registry.register(new ResourceLocation(MODID, "example_block_1"), new Block(...)); registry.register(new ResourceLocation(MODID, "example_block_2"), new Block(...)); @@ -96,31 +94,29 @@ public void register(RegisterEvent event) { } ``` -## 查询注册表 +## Querying Registries -有时候,你可能会发现自己处于想要通过给定ID获取注册对象的情况,或者你想要获取某个注册对象的ID。由于注册表本质上是ID(`ResourceLocation`)到独立对象的映射,即可逆映射,这两种操作都是可行的: +Sometimes, you will find yourself in situations where you want to get a registered object by a given id. Or, you want to get the id of a certain registered object. Since registries are basically maps of ids (`ResourceLocation`s) to distinct objects, i.e. a reversible map, both of these operations work: ```java -BuiltInRegistries.BLOCKS.get(new ResourceLocation("minecraft", "dirt")); // 返回泥土方块 -BuiltInRegistries.BLOCKS.getKey(Blocks.DIRT); // 返回资源位置"minecraft:dirt" +BuiltInRegistries.BLOCKS.get(new ResourceLocation("minecraft", "dirt")); // returns the dirt block +BuiltInRegistries.BLOCKS.getKey(Blocks.DIRT); // returns the resource location "minecraft:dirt" -// 假设ExampleBlocksClass.EXAMPLE_BLOCK.get()是具有ID"yourmodid:example_block"的Supplier -BuiltInRegistries.BLOCKS.get(new ResourceLocation("yourmodid", "example_block")); // 返回示例方块 -BuiltInRegistries.BLOCKS.getKey(ExampleBlocksClass.EXAMPLE_BLOCK.get()); // 返回资源位置"yourmodid:example_block" +// Assume that ExampleBlocksClass.EXAMPLE_BLOCK.get() is a Supplier with the id "yourmodid:example_block" +BuiltInRegistries.BLOCKS.get(new ResourceLocation("yourmodid", "example_block")); // returns the example block +BuiltInRegistries.BLOCKS.getKey(ExampleBlocksClass.EXAMPLE_BLOCK.get()); // returns the resource location "yourmodid:example_block" ``` -如果你只是想检查是否存在某个对象,这也是可能的,尽管只能用键: +If you just want to check for the presence of an object, this is also possible, though only with keys: ```java BuiltInRegistries.BLOCKS.containsKey(new ResourceLocation("minecraft", "dirt")); // true -BuiltInRegistries.BLOCKS.containsKey(new ResourceLocation("create", "brass_ingot")); // 如果安装了Create则为true +BuiltInRegistries.BLOCKS.containsKey(new ResourceLocation("create", "brass_ingot")); // true only if Create is installed ``` -正如最后一个示例所示,这适用于任何模组ID,因此是检查另一个模组中是否存在某个物品的完美方式。 - -最后,我们还可以迭代注册表中的所有条目,无论是键还 +As the last example shows, this is possible with any mod id, and thus a perfect way to check if a certain item from another mod exists. -是条目(条目使用Java的`Map.Entry`类型): +Finally, we can also iterate over all entries in a registry, either over the keys or over the entries (entries use the Java `Map.Entry` type): ```java for (ResourceLocation id : BuiltInRegistries.BLOCKS.keySet()) { @@ -132,22 +128,22 @@ for (Map.Entry entry : BuiltInRegistries.BLOCKS.entrySe ``` :::note -查询操作始终使用vanilla `Registry`,而不是`DeferredRegister`。这是因为`DeferredRegister`只是注册工具。 +Query operations always use vanilla `Registry`s, not `DeferredRegister`s. This is because `DeferredRegister`s are merely registration utilities. ::: :::danger -查询操作只有在注册完成后才安全使用。**不要在注册仍在进行时查询注册表!** +Query operations are only safe to use after registration has finished. **DO NOT QUERY REGISTRIES WHILE REGISTRATION IS STILL ONGOING!** ::: -## 自定义注册表 +## Custom Registries -自定义注册表允许你指定其他模组可能想要接入的附加系统。例如,如果你的模组要添加效果,你可以使效果成为一个注册表,从而允许其他模组添加效果到你的模组中,而无需你做任何其他事情。它还允许你自动执行一些操作,如同步条目。 +Custom registries allow you to specify additional systems that addon mods for your mod may want to plug into. For example, if your mod were to add spells, you could make the spells a registry and thus allow other mods to add spells to your mod, without you having to do anything else. It also allows you to do some things, such as syncing the entries, automatically. -让我们从创建[注册表键][resourcekey]和注册表本身开始: +Let's start by creating the [registry key][resourcekey] and the registry itself: ```java -// 我们在这里使用效果作为注册表的例子,不涉及效果实际是什么(因为这不重要)。 -// 当然,所有提到的效果都可以并且应该替换为你的注册表实际是什么。 +// We use spells as an example for the registry here, without any details about what a spell actually is (as it doesn't matter). +// Of course, all mentions of spells can and should be replaced with whatever your registry actually is. public static final ResourceKey> SPELL_REGISTRY_KEY = ResourceKey.createRegistryKey(new ResourceLocation("yourmodid", "spells")); public static final Registry SPELL_REGISTRY = new RegistryBuilder<>(SPELL_REGISTRY_KEY) // If you want to enable integer id syncing, for networking. @@ -161,7 +157,7 @@ public static final Registry SPELL_REGISTRY = new Registry .create(); ``` -然后,通过将注册表注册到 `NewRegistryEvent` 中的根注册表来告诉游戏注册表存在: +Then, tell the game that the registry exists by registering them to the root registry in `NewRegistryEvent`: ```java @SubscribeEvent @@ -170,7 +166,8 @@ static void registerRegistries(NewRegistryEvent event) { } ``` -现在,您可以像使用任何其他注册表一样,通过`DeferredRegister`和`RegisterEvent`注册新的注册表内容: +You can now register new registry contents like with any other registry, through both `DeferredRegister` and `RegisterEvent`: + ```java public static final DeferredRegister SPELLS = DeferredRegister.create("yourmodid", SPELL_REGISTRY); public static final Supplier EXAMPLE_SPELL = SPELLS.register("example_spell", () -> new Spell(...)); @@ -184,20 +181,20 @@ public static void register(RegisterEvent event) { } ``` -# 数据包注册表 +## Datapack Registries -数据包注册表(也称为动态注册表或世界生成注册表)是一种特殊的注册表,它在世界加载时从数据包 JSON 文件中加载数据,而不是在游戏启动时加载。默认的数据包注册表主要包括大多数世界生成注册表和其他一些注册表。 +A datapack registry (also known as a dynamic registry or, after its main use case, worldgen registry) is a special kind of registry that loads data from [datapack][datapack] JSONs (hence the name) at world load, instead of loading them when the game starts. Default datapack registries most notably include most worldgen registries, among a few others. -数据包注册表允许通过 JSON 文件指定其内容。这意味着除了数据生成工具外,不需要任何代码(如果你不想自己编写 JSON 文件的话)。每个数据包注册表都有一个与之关联的编解码器(`Codec`),用于序列化,每个注册表的 ID 决定了其数据包路径: +Datapack registries allow their contents to be specified in JSON files. This means that no code (other than [datagen][datagen] if you don't want to write the JSON files yourself) is necessary. Every datapack registry has a [`Codec`][codec] associated with it, which is used for serialization, and each registry's id determines its datapack path: -- Minecraft 的数据包注册表使用格式 `data/yourmodid/registrypath`(例如 `data/yourmodid/worldgen/biomes`,其中 `worldgen/biomes` 是注册表路径)。 -- 所有其他数据包注册表(NeoForge 或模组化的)使用格式 `data/yourmodid/registrynamespace/registrypath`(例如 `data/yourmodid/neoforge/loot_modifiers`,其中 `neoforge` 是注册表命名空间,`loot_modifiers` 是注册表路径)。 +- Minecraft's datapack registries use the format `data/yourmodid/registrypath` (for example `data/yourmodid/worldgen/biomes`, where `worldgen/biomes` is the registry path). +- All other datapack registries (NeoForge or modded) use the format `data/yourmodid/registrynamespace/registrypath` (for example `data/yourmodid/neoforge/loot_modifiers`, where `neoforge` is the registry namespace and `loot_modifiers` is the registry path). -可以从 `RegistryAccess` 获取数据包注册表。如果在服务器上,可以通过调用 `ServerLevel#registryAccess()` 来检索此 `RegistryAccess`;如果在客户端,可以通过调用 `Minecraft.getInstance().connection#registryAccess()` 来检索(后者仅在实际连接到世界时有效,否则连接将为 null)。然后可以像使用任何其他注册表一样使用这些调用的结果来获取特定元素或遍历内容。 +Datapack registries can be obtained from a `RegistryAccess`. This `RegistryAccess` can be retrieved by calling `ServerLevel#registryAccess()` if on the server, or `Minecraft.getInstance().connection#registryAccess()` if on the client (the latter only works if you are actually connected to a world, as otherwise the connection will be null). The result of these calls can then be used like any other registry to get specific elements, or to iterate over the contents. -### 自定义数据包注册表 +### Custom Datapack Registries -自定义数据包注册表不需要构建 `Registry`。相反,它们只需要一个注册表键和至少一个编解码器(`Codec`)来序列化和反序列化其内容。根据之前的法术示例,将我们的法术注册表注册为数据包注册表的过程如下所示: +Custom datapack registries do not require a `Registry` to be constructed. Instead, they just need a registry key and at least one [`Codec`][codec] to (de-)serialize its contents. Reiterating on the spells example from before, registering our spell registry as a datapack registry looks something like this: ```java public static final ResourceKey> SPELL_REGISTRY_KEY = ResourceKey.createRegistryKey(new ResourceLocation("yourmodid", "spells")); @@ -205,52 +202,51 @@ public static final ResourceKey> SPELL_REGISTRY_KEY = ResourceKe @SubscribeEvent public static void registerDatapackRegistries(DataPackRegistryEvent.NewRegistry event) { event.dataPackRegistry( - // 注册表键。 + // The registry key. SPELL_REGISTRY_KEY, - // 注册表内容的编解码器。 + // The codec of the registry contents. Spell.CODEC, - // 网络编解码器。通常与普通编解码器相同。 - // 可能是普通编解码器的简化版本,省略了客户端不需要的数据。 - // 可能为 null。如果为 null,则注册表条目根本不会同步到客户端。 - // 可以省略,这在功能上与传递 null 相同(调用了一个带有两个参数的方法重载,该重载向普通的三参数方法传递 null)。 + // The network codec of the registry contents. Often identical to the normal codec. + // May be a reduced variant of the normal codec that omits data that is not needed on the client. + // May be null. If null, registry entries will not be synced to the client at all. + // May be omitted, which is functionally identical to passing null (a method overload + // with two parameters is called that passes null to the normal three parameter method). Spell.CODEC ); } ``` -### 数据包注册表的数据生成 +### Data Generation for Datapack Registries -由于手工编写所有 JSON 文件既繁琐又容易出错,NeoForge 提供了一个数据提供器来为你生成 JSON 文件。这适用于内置的和你自己的数据包注册表。 +Since writing all the JSON files by hand is both tedious and error-prone, NeoForge provides a [data provider][datagenindex] to generate the JSON files for you. This works for both built-in and your own datapack registries. -首先,我们创建一个 `RegistrySetBuilder` 并向其添加条目(一个 `RegistrySetBuilder` 可以包含多个注册表的条目): +First, we create a `RegistrySetBuilder` and add our entries to it (one `RegistrySetBuilder` can hold entries for multiple registries): ```java new RegistrySetBuilder() .add(Registries.CONFIGURED_FEATURE, bootstrap -> { - // 通过引导上下文注册配置特性。 + // Register configured features through the bootstrap context (see below) }) .add(Registries.PLACED_FEATURE, bootstrap -> { - // 通过引导上下文注册放置特性。 + // Register placed features through the bootstrap context (see below) }); ``` -`bootstrap` lambda 参数是我们实际用来注册对象的。要注册一个对象,我们这样调用 `#register`: +The `bootstrap` lambda parameter is what we actually use to register our objects. It has the type `BootstrapContext`. To register an object, we call `#register` on it, like so: ```java -// 我们对象的资源键。 +// The resource key of our object. public static final ResourceKey> EXAMPLE_CONFIGURED_FEATURE = ResourceKey.create( Registries.CONFIGURED_FEATURE, - new ResourceLocation(MOD_ID, "example_configured_feature - -") + new ResourceLocation(MOD_ID, "example_configured_feature") ); new RegistrySetBuilder() .add(Registries.CONFIGURED_FEATURE, bootstrap -> { bootstrap.register( - // 我们配置特性的资源键。 + // The resource key of our configured feature. EXAMPLE_CONFIGURED_FEATURE, - // 实际的配置特性。 + // The actual configured feature. new ConfiguredFeature<>(Feature.ORE, new OreConfiguration(...)) ); }) @@ -259,7 +255,7 @@ new RegistrySetBuilder() }); ``` -如果需要,`BootstrapContext`(在 1.20.4 及以下版本中名称误写为 `BootstapContext`)还可以用来从另一个注册表查找条目: +The `BootstrapContext` (name is typoed as `BootstapContext` in 1.20.4 and below) can also be used to lookup entries from another registry if needed: ```java public static final ResourceKey> EXAMPLE_CONFIGURED_FEATURE = ResourceKey.create( @@ -278,27 +274,27 @@ new RegistrySetBuilder() .add(Registries.PLACED_FEATURE, bootstrap -> { HolderGetter> otherRegistry = bootstrap.lookup(Registries.CONFIGURED_FEATURE); bootstrap.register(EXAMPLE_PLACED_FEATURE, new PlacedFeature( - otherRegistry.getOrThrow(EXAMPLE_CONFIGURED_FEATURE), // 获取配置特性 - List.of() // 放置发生时无操作 - 替换为你的放置参数 + otherRegistry.getOrThrow(EXAMPLE_CONFIGURED_FEATURE), // Get the configured feature + List.of() // No-op when placement happens - replace with whatever your placement parameters are )); }); ``` -最后,我们在实际的数据提供器中使用我们的 `RegistrySetBuilder` 并将该数据提供器注册到事件中: +Finally, we use our `RegistrySetBuilder` in an actual data provider, and register that data provider to the event: ```java @SubscribeEvent static void onGatherData(GatherDataEvent event) { event.getGenerator().addProvider( - // 仅在生成服务器数据时运行数据包生成 + // Only run datapack generation when server data is being generated event.includeServer(), - // 创建提供器 + // Create the provider output -> new DatapackBuiltinEntriesProvider( output, event.getLookupProvider(), - // 我们的注册表集生成器来生成数据。 + // Our registry set builder to generate the data from. new RegistrySetBuilder().add(...), - // 我们正在生成的模组 ID 集合。通常只有你自己的模组 ID。 + // A set of mod ids we are generating. Usually only your own mod id. Set.of("yourmodid") ) ); diff --git a/docs/concepts/sides.md b/docs/concepts/sides.md index e2154f25c..48712d430 100644 --- a/docs/concepts/sides.md +++ b/docs/concepts/sides.md @@ -1,62 +1,60 @@ -# 游戏两侧的区分 +# Sides -和许多其他程序一样,Minecraft 遵循客户端-服务器的概念,其中客户端负责显示数据,而服务器负责更新数据。当我们使用这些术语时,我们对其含义有一个相当直观的理解...对吗? +Like many other programs, Minecraft follows a client-server concept, where the client is responsible for displaying the data, while the server is responsible for updating them. When using these terms, we have a fairly intuitive understanding of what we mean... right? -事实证明,并非如此。很多混淆源于 Minecraft 有两种不同的“侧”的概念,这取决于上下文:物理侧和逻辑侧。 +Turns out, not so much. A lot of the confusion stems from Minecraft having two different concepts of sides, depending on the context: the physical and the logical side. -## 逻辑侧与物理侧 +## Logical vs. Physical Side -### 物理侧 +### The Physical Side -当你打开 Minecraft 启动器,选择一个 Minecraft 安装并按下播放时,你启动了一个**物理客户端**。这里使用“物理”一词是在“这是一个客户端程序”的意义上。这尤其意味着客户端功能,如所有渲染相关的功能,都可以在这里使用。相比之下,**物理服务器**,也称为专用服务器,是在你启动一个 Minecraft 服务器 JAR 时打开的。虽然 Minecraft 服务器带有一个基本的 GUI,但它缺少所有仅限客户端的功能。最值得注意的是,各种客户端类在服务器 JAR 中缺失。在物理服务器上调用这些类将导致缺少类错误,即崩溃,因此我们需要对此进行防护。 +When you open your Minecraft launcher, select a Minecraft installation and press play, you boot up a **physical client**. The word "physical" is used here in the sense of "this is a client program". This especially means that client-side functionality, such as all the rendering stuff, is available here and can be used as needed. In contrast, the **physical server**, also known as dedicated server, is what opens when you launch a Minecraft server JAR. While the Minecraft server comes with a rudimentary GUI, it is missing all client-only functionality. Most notably, this means that various client classes are missing from the server JAR. Calling these classes on the physical server will lead to missing class errors, i.e. crashes, so we need to safeguard against this. -### 逻辑侧 +### The Logical Side -逻辑侧主要关注 Minecraft 的内部程序结构。**逻辑服务器**是游戏逻辑运行的地方。如时间和天气变化、实体更新、实体生成等都在服务器上运行。所有种类的数据,如库存内容,也都是服务器的责任。另一方面,**逻辑客户端**负责显示所有需要显示的内容。Minecraft 在一个名为 `net.minecraft.client` 的独立包中保留了所有客户端代码,并在一个名为渲染线程的独立线程中运行它,而其他所有内容都被视为公共代码(即客户端和服务器代码)。 +The logical side is mainly focused on the internal program structure of Minecraft. The **logical server** is where the game logic runs. Things like time and weather changing, entity ticking, entity spawning, etc. all run on the server. All kinds of data, such as inventory contents, are the server's responsibility as well. The **logical client**, on the other hand, is responsible for displaying everything there is to display. Minecraft keeps all the client code in an isolated `net.minecraft.client` package, and runs it in a separate thread called the Render Thread, while everything else is considered common (i.e. client and server) code. -### 两者有何区别? +### What's the Difference? -物理侧和逻辑侧之间的区别最好通过两种情况来说明: +The difference between physical and logical sides is best exemplified by two scenarios: -- 玩家加入一个**多人游戏**世界。这相当直接:玩家的物理(和逻辑)客户端连接到别处的一个物理(和逻辑)服务器——玩家不关心在哪里;只要他们能连接,这就是所有客户端知道的,也是所有客户端需要知道的。 -- 玩家加入一个**单人游戏**世界。这里的情况变得有趣。玩家的物理客户端启动了一个逻辑服务器,然后现在作为逻辑客户端,连接到同一台机器上的那个逻辑服务器。如果你熟悉网络,你可以把它想象为连接到`localhost`(只是概念上的;没有实际的套接字或类似的东西涉及)。 +- The player joins a **multiplayer** world. This is fairly straightforward: The player's physical (and logical) client connects to a physical (and logical) server somewhere else - the player does not care where; so long as they can connect, that's all the client knows of, and all the client needs to know. +- The player joins a **singleplayer** world. This is where things get interesting. The player's physical client spins up a logical server and then, now in the role of the logical client, connects to that logical server on the same machine. If you are familiar with networking, you can think of it as a connection to `localhost` (only conceptually; there are no actual sockets or similar involved). -这两种情况也显示了主要问题:如果一个逻辑服务器可以使用你的代码,这并不保证物理服务器也能同样使用。这就是为什么你应该始终使用专用服务器进行测试,以检查意外行为。由于客户端和服务器分离不当导致的`NoClassDefFoundError`和`ClassNotFoundException`是模组制作中最常见的错误之一。另一个常见的错误是使用静态字段,并从两个逻辑侧访问它们;这特别棘手,因为通常没有迹象表明有什么问题。 +These two scenarios also show the main problem with this: If a logical server can work with your code, that alone doesn't guarantee that a physical server will be able to work with as well. This is why you should always test with dedicated servers to check for unexpected behavior. `NoClassDefFoundError`s and `ClassNotFoundException`s due to incorrect client and server separation are among the most common errors there are in modding. Another common mistake is working with static fields and accessing them from both logical sides; this is particularly tricky because there's usually no indication that something is wrong. :::tip -如果你需要将数据从一侧传输到另一侧,你必须[发送一个数据包 - -][networking]。 +If you need to transfer data from one side to another, you must [send a packet][networking]. ::: -在 NeoForge 代码库中,物理侧由一个名为 `Dist` 的枚举表示,而逻辑侧由一个名为 `LogicalSide` 的枚举表示。 +In the NeoForge codebase, the physical side is represented by an enum called `Dist`, while the logical side is represented by an enum called `LogicalSide`. :::info -从历史上看,服务器 JAR 拥有客户端没有的类。在现代版本中,这种情况已不复存在;如果愿意,可以认为物理服务器是物理客户端的一个子集。 +Historically, server JARs have had classes the client did not. This is not the case anymore in modern versions; physical servers are a subset of physical clients, if you will. ::: -## 执行侧特定操作 +## Performing Side-Specific Operations ### `Level#isClientSide()` -这个布尔检查将是你最常用的检查侧的方式。在 `Level` 对象上查询此字段可以确定级别所属的**逻辑**侧:如果此字段为 `true`,则级别在逻辑客户端上运行。如果字段为 `false`,则级别在逻辑服务器上运行。据此,物理服务器将始终在此字段中包含 `false`,但我们不能假设 `false` 暗示物理服务器,因为此字段也可能在物理客户端内的逻辑服务器(即单人游戏世界)中为 `false`。 +This boolean check will be your most used way to check sides. Querying this field on a `Level` object establishes the **logical** side the level belongs to: If this field is `true`, the level is running on the logical client. If the field is `false`, the level is running on the logical server. It follows that the physical server will always contain `false` in this field, but we cannot assume that `false` implies a physical server, since this field can also be `false` for the logical server inside a physical client (i.e. a singleplayer world). -只有在需要确定是否运行游戏逻辑和其他机制时才使用此检查。例如,如果你想在玩家每次点击你的方块时对玩家造成伤害,或者让你的机器将泥土处理成钻石,你应该确保 `#isClientSide` 为 `false` 后再进行。在逻辑客户端应用游戏逻辑可能导致最好的情况下出现数据不同步(幽灵实体、不同步的统计数据等),在最坏的情况下导致崩溃。 +Use this check whenever you need to determine if game logic and other mechanics should be run. For example, if you want to damage the player every time they click your block, or have your machine process dirt into diamonds, you should only do so after ensuring `#isClientSide` is `false`. Applying game logic to the logical client can cause desynchronization (ghost entities, desynchronized stats, etc.) in the best case, and crashes in the worst case. :::tip -这个检查应该作为你的默认选择。每当你有一个 `Level` 可用时,就使用这个检查。 +This check should be used as your go-to default. Whenever you have a `Level` available, use this check. ::: ### `FMLEnvironment.dist` -`FMLEnvironment.dist` 是 `Level#isClientSide()` 检查的**物理**对应项。如果此字段为 `Dist.CLIENT`,你就在物理客户端上。如果字段为 `Dist.SERVER`,你就在物理服务器上。 +`FMLEnvironment.dist` is the **physical** counterpart to a `Level#isClientSide()` check. If this field is `Dist.CLIENT`, you are on a physical client. If the field is `Dist.SERVER`, you are on a physical server. -检查物理环境在处理仅限客户端的类时非常重要。所有对客户端代码的调用都应始终包含在对 `Dist.CLIENT` 的检查中,并然后调用一个单独的类以防止意外的类加载: +Checking the physical environment is important when dealing with client-only classes. All calls to client-only code should always be encased in a check for `Dist.CLIENT`, and then call to a separate class to prevent accidental classloading: ```java public class SomeCommonClass { public void someCommonMethod() { - // 仅当你在物理客户端上时,SomeClientClass 才会被加载 + //SomeClientClass will be loaded if and only if you are on a physical client if (FMLEnvironment.dist == Dist.CLIENT) { SomeClientClass.someClientMethod(); } @@ -64,14 +62,14 @@ public class SomeCommonClass { } public class SomeClientClass { - public static void someClientMethod() { + public void someClientMethod() { Minecraft.getInstance().whatever(); } } ``` :::tip -通常期望模组在任一侧都能工作。这特别意味着,如果你正在开发一个仅限客户端的模组,你应该验证该模组实际上在物理客户端上运行,并且在不运行的情况下无操作。 +Mods are generally expected to work on either side. This especially means that if you are developing a client-only mod, you should verify that the mod actually runs on a physical client, and no-op in the event that it does not. ::: [networking]: ../networking/index.md diff --git a/docs/datagen/advancements.md b/docs/datagen/advancements.md index bca701e5c..61504fb03 100644 --- a/docs/datagen/advancements.md +++ b/docs/datagen/advancements.md @@ -1,24 +1,24 @@ -# 成就生成 +Advancement Generation ====================== -[成就] 可以通过构建一个新的 `AdvancementProvider` 并提供 `AdvancementSubProvider` 来为模组生成。成就可以手动创建和提供,或者为了方便,使用 `Advancement$Builder` 创建。提供者必须[添加][datagen]到 `DataGenerator` 中。 +[Advancements] can be generated for a mod by constructing a new `AdvancementProvider` and providing `AdvancementSubProvider`s. Advancements can either be created and supplied manually or, for convenience, created using `Advancement$Builder`. The provider must be [added][datagen] to the `DataGenerator`. :::note -Forge 提供了一个扩展的 `AdvancementProvider`,名为 `ForgeAdvancementProvider`,它更适合生成成就。因此,本文档将使用 `ForgeAdvancementProvider` 以及子提供者接口 `ForgeAdvancementProvider$AdvancementGenerator`。 +Forge provides an extension for the `AdvancementProvider` called `ForgeAdvancementProvider` which integrates better for generating advancements. So, this documentation will use `ForgeAdvancementProvider` along with the sub provider interface `ForgeAdvancementProvider$AdvancementGenerator`. ::: ```java -// 在 MOD 事件总线上 +// On the MOD event bus @SubscribeEvent public void gatherData(GatherDataEvent event) { event.getGenerator().addProvider( - // 仅在生成服务器数据时运行生成器 + // Tell generator to run only when server data are generating event.includeServer(), output -> new ForgeAdvancementProvider( output, event.getLookupProvider(), event.getExistingFileHelper(), - // 生成成就的子提供者 + // Sub providers which generate the advancements List.of(subProvider1, subProvider2, /*...*/) ) ); @@ -28,41 +28,41 @@ public void gatherData(GatherDataEvent event) { `ForgeAdvancementProvider$AdvancementGenerator` ----------------------------------------------- -`ForgeAdvancementProvider$AdvancementGenerator` 负责生成成就,包含一个方法,该方法接收注册表查找、写入者 (`Consumer`) 和现有文件助手。 +A `ForgeAdvancementProvider$AdvancementGenerator` is responsible for generating advancements, containing a method which takes in a registry lookup, the writer (`Consumer`), and the existing file helper.. ```java -// 在 ForgeAdvancementProvider$AdvancementGenerator 的某个子类中或作为 lambda 引用 +// In some subclass of ForgeAdvancementProvider$AdvancementGenerator or as a lambda reference @Override public void generate(HolderLookup.Provider registries, Consumer writer, ExistingFileHelper existingFileHelper) { - // 在这里构建成就 + // Build advancements here } ``` `Advancement$Builder` --------------------- -`Advancement$Builder` 是一个便利的实现,用于创建用于生成的 `Advancement`。它允许定义父成就、显示信息、完成成就时的奖励以及解锁成就的要求。只需指定要求即可创建一个 `Advancement`。 +`Advancement$Builder` is a convenience implementation for creating `Advancement`s to generate. It allows the definition of the parent advancement, the display information, the rewards when the advancement has been completed, and the requirements to unlock the advancement. Only the requirements need to be specified to create an `Advancement`. -虽然不是必需的,但有几个方法是重要的: +Although not required, there are a number of methods that are important to know of: -方法 | 描述 +Method | Description :---: | :--- -`parent` | 设置此成就直接链接到的成就。可以指定成就的名称或如果由模组制作者生成,则指定成就本身。 -`display` | 设置显示在聊天、弹窗和成就屏幕上的信息。 -`rewards` | 设置完成此成就时获得的奖励。 -`addCriterion` | 为成就添加条件。 -`requirements` | 指定条件是否必须全部为真,或者至少有一个为真。可以使用额外的重载来混合这些操作。 +`parent` | Sets the advancement which this advancement is directly linked to. Can either specify the name of the advancement or the advancement itself if its generated by the modder. +`display` | Sets the information to display to the chat, toast, and advancement screen. +`rewards` | Sets the rewards obtained when this advancement is completed. +`addCriterion` | Adds a condition to the advancement. +`requirements` | Specifies if the conditions must all return true or at least one must return true. An additional overload can be used to mix-and-match those operations. -一旦 `Advancement$Builder` 准备好建造,应调用 `#save` 方法,该方法需要写入者、成就的注册名和用于检查提供的父项是否存在的文件助手。 +Once an `Advancement$Builder` is ready to be built, the `#save` method should be called which takes in the writer, the registry name of the advancement, and the file helper used to check whether the supplied parent exists. ```java -// 在某个 ForgeAdvancementProvider$AdvancementGenerator#generate(registries, writer, existingFileHelper) 中 +// In some ForgeAdvancementProvider$AdvancementGenerator#generate(registries, writer, existingFileHelper) Advancement example = Advancement.Builder.advancement() - .addCriterion("example_criterion", triggerInstance) // 如何解锁成就 - .save(writer, name, existingFileHelper); // 将数据添加到构建器 + .addCriterion("example_criterion", triggerInstance) // How the advancement is unlocked + .save(writer, name, existingFileHelper); // Add data to builder ``` -[成就]: ../../resources/server/advancements.md +[advancements]: ../../resources/server/advancements.md [datagen]: ../index.md#data-providers [conditional]: ../../resources/server/conditional.md diff --git a/docs/datagen/glm.md b/docs/datagen/glm.md index e7aafcebe..1192b2377 100644 --- a/docs/datagen/glm.md +++ b/docs/datagen/glm.md @@ -1,23 +1,23 @@ -# 全局战利品修改器生成 +Global Loot Modifier Generation =============================== -通过继承 `GlobalLootModifierProvider` 并实现 `#start` 方法,可以为模组生成[全局战利品修改器 (GLMs)][glm]。通过调用 `#add` 并指定修改器的名称和将被序列化的[修改器实例][instance],可以添加生成每个 GLM。实现后,必须将提供者[添加][datagen]到 `DataGenerator`。 +[Global Loot Modifiers (GLMs)][glm] can be generated for a mod by subclassing `GlobalLootModifierProvider` and implementing `#start`. Each GLM can be added generated by calling `#add` and specifying the name of the modifier and the [modifier instance][instance] to be serialized. After implementation, the provider must be [added][datagen] to the `DataGenerator`. ```java -// 在 MOD 事件总线上 +// On the MOD event bus @SubscribeEvent public void gatherData(GatherDataEvent event) { event.getGenerator().addProvider( - // 通知生成器仅在生成服务器数据时运行 + // Tell generator to run only when server data are generating event.includeServer(), output -> new MyGlobalLootModifierProvider(output, MOD_ID) ); } -// 在某个 GlobalLootModifierProvider#start 中 +// In some GlobalLootModifierProvider#start this.add("example_modifier", new ExampleModifier( new LootItemCondition[] { - WeatherCheck.weather().setRaining(true).build() // 在下雨时执行 + WeatherCheck.weather().setRaining(true).build() // Executes when raining }, "val1", 10, diff --git a/docs/datagen/loottables.md b/docs/datagen/loottables.md index da652a3ed..24bd34a29 100644 --- a/docs/datagen/loottables.md +++ b/docs/datagen/loottables.md @@ -1,20 +1,20 @@ -# 战利品表生成 +Loot Table Generation ===================== -通过构建一个新的 `LootTableProvider` 并提供 `LootTableProvider$SubProviderEntry`,可以为模组生成[战利品表][loottable]。提供者必须被[添加][datagen]到 `DataGenerator`。 +[Loot tables][loottable] can be generated for a mod by constructing a new `LootTableProvider` and providing `LootTableProvider$SubProviderEntry`s. The provider must be [added][datagen] to the `DataGenerator`. ```java -// 在 MOD 事件总线上 +// On the MOD event bus @SubscribeEvent public void gatherData(GatherDataEvent event) { event.getGenerator().addProvider( - // 告诉生成器仅在生成服务器数据时运行 + // Tell generator to run only when server data are generating event.includeServer(), output -> new MyLootTableProvider( output, - // 指定需要生成的表的注册名称,或者可以留空 + // Specify registry names of tables that are required to generate, or can leave empty Collections.emptySet(), - // 生成战利品的子提供者 + // Sub providers which generate the loot List.of(subProvider1, subProvider2, /*...*/) ) ); @@ -24,124 +24,122 @@ public void gatherData(GatherDataEvent event) { `LootTableSubProvider` ---------------------- -每个 `LootTableProvider$SubProviderEntry` 都接收一个 `LootTableSubProvider`,它为给定的 `LootContextParamSet` 生成战利品表。`LootTableSubProvider` 包含一个方法,该方法接收写入者(`BiConsumer`)来生成表。 +Each `LootTableProvider$SubProviderEntry` takes in a supplied `LootTableSubProvider`, which generates the loot table, for a given `LootContextParamSet`. The `LootTableSubProvider` contains a method which takes in the writer (`BiConsumer`) to generate a table. ```java public class ExampleSubProvider implements LootTableSubProvider { - // 用于创建封装 Supplier 的工厂方法 + // Used to create a factory method for the wrapping Supplier public ExampleSubProvider() {} - // 用于生成战利品表的方法 + // The method used to generate the loot tables @Override public void generate(BiConsumer writer) { - // 在这里通过调用 writer#accept 生成战利品表 + // Generate loot tables here by calling writer#accept } } ``` -然后可以将表添加到 `LootTableProvider#getTables` 中,适用于任何可用的 `LootContextParamSet`: +The table can then be added to `LootTableProvider#getTables` for any available `LootContextParamSet`: ```java -// 传递到 LootTableProvider 构造函数的列表中 +// In the list passed into the LootTableProvider constructor new LootTableProvider.SubProviderEntry( ExampleSubProvider::new, - // 为 'empty' 参数集生成战利品表 + // Loot table generator for the 'empty' param set LootContextParamSets.EMPTY ) ``` -### `BlockLootSubProvider` 和 `EntityLootSubProvider` 子类 +### `BlockLootSubProvider` and `EntityLootSubProvider` Subclasses -对于 `LootContextParamSets#BLOCK` 和 `#ENTITY`,有特殊类型(分别是 `BlockLootSubProvider` 和 `EntityLootSubProvider`),它们提供额外的辅助方法来创建和验证是否存在战利品表。 +For `LootContextParamSets#BLOCK` and `#ENTITY`, there are special types (`BlockLootSubProvider` and `EntityLootSubProvider` respectively) which provide additional helper methods for creating and validating that there are loot tables. -`BlockLootSubProvider` 的构造函数接受一个物品列表,用于确定如果一个方块被爆炸时是否可以生成战利品表,以及一个 `FeatureFlagSet`,用于确定是否启用了方块以便为其生成战利品表。 +The `BlockLootSubProvider`'s constructor takes in a list of items, which are explosion resistant to determine whether the loot table can be generated if a block is exploded, and a `FeatureFlagSet`, which determines whether the block is enabled so that a loot table is generated for it. ```java -// 在某个 BlockLootSubProvider 子类中 +// In some BlockLootSubProvider subclass public MyBlockLootSubProvider() { super(Collections.emptySet(), FeatureFlags.REGISTRY.allFlags()); } ``` -`EntityLootSubProvider` 的构造函数接受一个 `FeatureFlagSet`,用于确定是否启用了实体类型以便为其生成战利品表。 +The `EntityLootSubProvider`'s constructor takes in a `FeatureFlagSet`, which determines whether the entity type is enabled so that a loot table is generated for it. ```java -// 在某个 EntityLootSubProvider 子类中 +// In some EntityLootSubProvider subclass public MyEntityLootSubProvider() { super(FeatureFlags.REGISTRY.allFlags()); } ``` -要使用它们,所有已注册的对象必须分别提供给 `BlockLootSubProvider#getKnownBlocks` 和 `EntityLootSubProvider#getKnownEntityTypes`。这些方法确保迭代中的所有对象都有一个战利品表。 +To use them, all registered objects must be supplied to either `BlockLootSubProvider#getKnownBlocks` and `EntityLootSubProvider#getKnownEntityTypes` respectively. These methods are to make sure all objects within the iterable has a loot table. :::tip -如果使用 `DeferredRegister` 来注册模组的对象,则 `#getKnown*` 方法可以通过 `DeferredRegister#getEntries` 提供条目: +If `DeferredRegister` is being used to register a mod's objects, then the `#getKnown*` methods can be supplied the entries via `DeferredRegister#getEntries`: ```java -// 在某个 BlockLootSubProvider 子类中,用于某个 DeferredRegister BLOCK_REGISTRAR +// In some BlockLootSubProvider subclass for some DeferredRegister BLOCK_REGISTRAR @Override protected Iterable getKnownBlocks() { - return BLOCK_REGISTRAR.getEntries() // 获取所有注册的条目 - .stream() // 流式处理封装的对象 - .flatMap(RegistryObject::stream) // 如果可用,则获取对象 - ::iterator; // 创建迭代器 + return BLOCK_REGISTRAR.getEntries() // Get all registered entries + .stream() // Stream the wrapped objects + .flatMap(RegistryObject::stream) // Get the object if available + ::iterator; // Create the iterable } ``` ::: -通过实现 `#generate` 方法可以添加战利品表。 +The loot tables themselves can be added by implementing the `#generate` method. ```java -// 在某个 BlockLootSubProvider 子类中 +// In some BlockLootSubProvider subclass @Override -public void - - generate() { - // 在这里添加战利品表 +public void generate() { + // Add loot tables here } ``` -战利品表生成器 +Loot Table Builders ------------------- -战利品表通过 `LootTableSubProvider` 接收为 `LootTable$Builder`。之后,指定的 `LootContextParamSet` 在 `LootTableProvider$SubProviderEntry` 中设置,然后通过 `#build` 构建。在构建之前,构建器可以指定条目、条件和修饰符,这些因素影响战利品表的功能。 +To generate loot tables, they are accepted by the `LootTableSubProvider` as a `LootTable$Builder`. Afterwards, the specified `LootContextParamSet` is set in the `LootTableProvider$SubProviderEntry` and then built via `#build`. Before being built, the builder can specify entries, conditions, and modifiers which affect how the loot table functions. :::note -战利品表的功能非常广泛,本文档无法全部涵盖。相反,每个组件将简要描述。每个组件的具体子类型可以使用 IDE 查找。它们的实现将留给读者作为练习。 +The functionality of loot tables is so expansive that it will not be covered by this documentation in its entirety. Instead, a brief description of each component will be mentioned. The specific subtypes of each component can be found using an IDE. Their implementations will be left as an exercise to the reader. ::: ### LootTable -战利品表是基本对象,可以使用 `LootTable#lootTable` 转换为所需的 `LootTable$Builder`。战利品表可以通过指定的方式构建,包括一系列池(通过 `#withPool` 应用)以及函数(通过 `#apply`),用于修改这些池的结果物品。 +Loot tables are the base object and can be transformed into the required `LootTable$Builder` using `LootTable#lootTable`. The loot table can be built with a list of pools (via `#withPool`) applied in the order they are specified along with functions (via `#apply`) to modify the resulting items of those pools. ### LootPool -战利品池代表一个执行操作的组,并可以使用 `LootPool#lootPool` 生成一个 `LootPool$Builder`。每个战利品池可以指定条目(通过 `#add`),这些条目定义池中的操作,条件(通过 `#when`)定义是否应执行池中的操作,以及函数(通过 `#apply`)修改条目的结果物品。每个池可以执行尽可能多的次数(通过 `#setRolls`)。此外,还可以指定额外的执行次数(通过 `#setBonusRolls`),这受到执行者幸运值的影响。 +Loot pools represents a group to perform operations and can generate a `LootPool$Builder` using `LootPool#lootPool`. Each loot pool can specify the entries (via `#add`) which define the operations in the pool, the conditions (via `#when`) which define if the operations in the pool should be performed, and functions (via `#apply`) to modify the resulting items of the entries. Each pool can be executed as many times as specified (via `#setRolls`). Additionally, bonus executions can be specified (via `#setBonusRolls`) which is modified by the luck of the executor. ### LootPoolEntryContainer -战利品条目定义了选中时要执行的操作,通常生成物品。每个条目都有一个相关的、[已注册][registered]的 `LootPoolEntryType`。它们还有自己的相关构建器,这些构建器是 `LootPoolEntryContainer$Builder` 的子类型。多个条目可以同时执行(通过 `#append`)或顺序执行,直到一个失败(通过 `#then`)。此外,条目可以在失败时默认为另一个条目(通过 `#otherwise`)。 +Loot entries define the operations to occur when selected, typically generating items. Each entry has an associated, [registered] `LootPoolEntryType`. They also have their own associated builders which subtype `LootPoolEntryContainer$Builder`. Multiple entries can execute at the same time (via `#append`) or sequentially until one fails (via `#then`). Additionally, entries can default to another entry on failure (via `#otherwise`). ### LootItemCondition -战利品条件定义了执行某些操作所需满足的要求。每个条件都有一个相关的、[已注册][registered]的 `LootItemConditionType`。它们还有自己的相关构建器,这些构建器是 `LootItemCondition$Builder` 的子类型。默认情况下,指定的所有战利品条件必须为真,才能执行操作。也可以指定战利品条件,使得只需一个条件为真即可(通过 `#or`)。此外,条件的结果输出可以被反转(通过 `#invert`)。 +Loot conditions define requirements which need to be met for some operation to execute. Each condition has an associated, [registered] `LootItemConditionType`. They also have their own associated builders which subtype `LootItemCondition$Builder`. By default, all loot conditions specified must return true for an operation to execute. Loot conditions can also be specified such that only one must return true instead (via `#or`). Additionally, the resulting output of a condition can be inverted (via `#invert`). ### LootItemFunction -战利品函数在将执行结果传递给输出之前修改结果。每个函数都有一个相关的、[已注册][registered]的 `LootItemFunctionType`。它们还有自己的相关构建器,这些构建器是 `LootItemFunction$Builder` 的子类型。 +Loot functions modify the result of an execution before passing it to the output. Each function has an associated, [registered] `LootItemFunctionType`. They also have their own associated builders which subtype `LootItemFunction$Builder`. #### NbtProvider -NBT 提供者是由 `CopyNbtFunction` 定义的特殊类型函数。它们定义了从哪里拉取标签信息。每个提供者都有一个相关的、[已注册][registered]的 `LootNbtProviderType`。 +NBT providers are a special type of functions defined by `CopyNbtFunction`. They define where to pull tag information from. Each provider has an associated, [registered] `LootNbtProviderType`. ### NumberProvider -数字提供者决定战利品池执行的次数。每个提供者都有一个相关的、[已注册][registered]的 `LootNumberProviderType`。 +Number providers determine how many times a loot pool executes. Each provider has an associated, [registered] `LootNumberProviderType`. #### ScoreboardNameProvider -记分板提供者是由 `ScoreboardValue` 定义的一种特殊类型的数字提供者。它们定义了从哪个记分板拉取执行次数的名字。每个提供者都有一个关联的[已注册][registered]`LootScoreProviderType`。 +Scoreboard providers are a special type of number providers defined by `ScoreboardValue`. They define the name of the scoreboard to pull the number of rolls to execute from. Each provider has an associated, [registered] `LootScoreProviderType`. [loottable]: ../../resources/server/loottables.md [datagen]: ../index.md#data-providers diff --git a/docs/datagen/recipes.md b/docs/datagen/recipes.md index 0abd31642..e4246eee6 100644 --- a/docs/datagen/recipes.md +++ b/docs/datagen/recipes.md @@ -1,16 +1,16 @@ -配方生成 +Recipe Generation ================= -可以通过继承 `RecipeProvider` 并实现 `#buildRecipes` 来为模组生成配方。一旦消费者接受了 `FinishedRecipe` 视图,就会为数据生成提供配方。 `FinishedRecipe` 可以手动创建和提供,或者为方便起见,使用 `RecipeBuilder` 创建。 +Recipes can be generated for a mod by subclassing `RecipeProvider` and implementing `#buildRecipes`. A recipe is supplied for data generation once a `FinishedRecipe` view is accepted by the consumer. `FinishedRecipe`s can either be created and supplied manually or, for convenience, created using a `RecipeBuilder`. -实现后,必须将提供者[添加][datagen]到 `DataGenerator`。 +After implementation, the provider must be [added][datagen] to the `DataGenerator`. ```java -// 在MOD事件总线上 +// On the MOD event bus @SubscribeEvent public void gatherData(GatherDataEvent event) { event.getGenerator().addProvider( - // 告诉生成器只在服务器数据生成时运行 + // Tell generator to run only when server data are generating event.includeServer(), MyRecipeProvider::new ); @@ -20,155 +20,157 @@ public void gatherData(GatherDataEvent event) { `RecipeBuilder` --------------- -`RecipeBuilder` 是创建 `FinishedRecipe` 以便于生成的便利实现。它提供了解锁、分组、保存和获取配方结果的基本定义。分别通过 `#unlockedBy`、 `#group`、 `#save` 和 `#getResult` 实现。 +`RecipeBuilder` is a convenience implementation for creating `FinishedRecipe`s to generate. It provides basic definitions for unlocking, grouping, saving, and getting the result of a recipe. This is done through `#unlockedBy`, `#group`, `#save`, and `#getResult` respectively. :::important -在原生配方构建器中不支持配方的 [`ItemStack` 输出][stack]。必须以不同的方式构建 `FinishedRecipe`,以便现有的原生配方序列化器可以生成这些数据。 +[`ItemStack` outputs][stack] in recipes are not supported within vanilla recipe builders. A `FinishedRecipe` must be built in a different manner for existing vanilla recipe serializers to generate this data. ::: :::warning -正在生成的物品结果必须有一个有效的 `RecipeCategory` 指定,否则会抛出 `NullPointerException`。 +The item results being generated must have a valid `RecipeCategory` specified; otherwise, a `NullPointerException` will be thrown. ::: -除了 [`SpecialRecipeBuilder`],所有的配方构建器都需要指定一个进步条件。所有的配方都会生成一个条件,如果玩家之前使用过这个配方,就会解锁这个配方。然而,必须指定一个额外的条件,允许玩家在没有任何先验知识的情况下获取到配方。如果指定的任何一个条件为真,那么玩家就会在配方书中获取到配方。 +All recipe builders except for [`SpecialRecipeBuilder`] require an advancement criteria to be specified. All recipes generate a criteria unlocking the recipe if the player has used the recipe previously. However, an additional criteria must be specified that allows the player to obtain the recipe without any prior knowledge. If any of the criteria specified is true, then the played will obtain the recipe for the recipe book. :::tip -配方条件通常使用 `InventoryChangeTrigger` 来在用户的库存中存在某些物品时解锁配方。 +Recipe criteria commonly use `InventoryChangeTrigger` to unlock their recipe when certain items are present in the user's inventory. ::: ### ShapedRecipeBuilder -`ShapedRecipeBuilder` 用于生成有形状的配方。构建器可以通过 `#shaped` 初始化。可以在保存之前指定配方组、输入符号模式、成分的符号定义和配方解锁条件。 +`ShapedRecipeBuilder` is used to generate shaped recipes. The builder can be initialized via `#shaped`. The recipe group, input symbol pattern, symbol definition of ingredients, and the recipe unlock criteria can be specified before saving. ```java -// 在 RecipeProvider#buildRecipes(writer) 中 +// In RecipeProvider#buildRecipes(writer) ShapedRecipeBuilder builder = ShapedRecipeBuilder.shaped(RecipeCategory.MISC, result) - .pattern("a a") // 创建配方模式 - .define('a', item) // 定义符号代表的物品 - .unlockedBy("criteria", criteria) // 配方的解锁方式 - .save(writer); // 将数据添加到构建器中 + .pattern("a a") // Create recipe pattern + .define('a', item) // Define what the symbol represents + .unlockedBy("criteria", criteria) // How the recipe is unlocked + .save(writer); // Add data to builder ``` -#### 额外的验证检查 +#### Additional Validation Checks -在构建之前,有形状的配方会进行一些额外的验证检查: +Shaped recipes have some additional validation checks performed before building: -* 必须定义一个模式,并且需要输入超过一个物品。 -* 所有模式行的宽度必须相同。 -* 一个符号不能被定义多次。 -* 空格字符 (`' '`) 是为了表示插槽中没有物品而保留的,因此不能被定义。 -* 模式必须使用用户定义的所有符号。 +* A pattern must be defined and take in more than one item. +* All pattern rows must be the same width. +* A symbol cannot be defined more than once. +* The space character (`' '`) is reserved for representing no item in a slot and, as such, cannot be defined. +* A pattern must use all symbols defined by the user. ### ShapelessRecipeBuilder -`ShapelessRecipeBuilder` 用于生成无形状的配方。可以通过 `#shapeless` 初始化构建器。在保存之前,可以指定配方组、输入成分和配方解锁条件。 +`ShapelessRecipeBuilder` is used to generate shapeless recipes. The builder can be initialized via `#shapeless`. The recipe group, input ingredients, and the recipe unlock criteria can be specified before saving. ```java -// 在 RecipeProvider#buildRecipes(writer) 中 +// In RecipeProvider#buildRecipes(writer) ShapelessRecipeBuilder builder = ShapelessRecipeBuilder.shapeless(RecipeCategory.MISC, result) - .requires(item) // 将物品添加到配方中 - .unlockedBy("criteria", criteria) // 配方的解锁方式 - .save(writer); // 将数据添加到构建器中 + .requires(item) // Add item to the recipe + .unlockedBy("criteria", criteria) // How the recipe is unlocked + .save(writer); // Add data to builder ``` ### SimpleCookingRecipeBuilder -`SimpleCookingRecipeBuilder` 用于生成熔炼、爆炸、烟熏和营火烹饪配方。此外,使用 `SimpleCookingSerializer` 的自定义烹饪配方也可以使用这个构建器进行数据生成。可以分别通过 `#smelting`、 `#blasting`、 `#smoking`、 `#campfireCooking` 或 `#cooking` 初始化构建器。在保存之前,可以指定配方组和配方解锁条件。 +`SimpleCookingRecipeBuilder` is used to generate smelting, blasting, smoking, and campfire cooking recipes. Additionally, custom cooking recipes using the `SimpleCookingSerializer` can also be data generated using this builder. The builder can be initialized via `#smelting`, `#blasting`, `#smoking`, `#campfireCooking`, or `#cooking` respectively. The recipe group and the recipe unlock criteria can be specified before saving. ```java -// 在 RecipeProvider#buildRecipes(writer) 中 +// In RecipeProvider#buildRecipes(writer) SimpleCookingRecipeBuilder builder = SimpleCookingRecipeBuilder.smelting(input, RecipeCategory.MISC, result, experience, cookingTime) - .unlockedBy("criteria", criteria) // 配方的解锁方式 - .save(writer); // 将数据添加到构建器中 + .unlockedBy("criteria", criteria) // How the recipe is unlocked + .save(writer); // Add data to builder ``` ### SingleItemRecipeBuilder -`SingleItemRecipeBuilder` 用于生成石切配方。此外,使用类似 `SingleItemRecipe$Serializer` 的序列化器的自定义单物品配方也可以使用这个构建器进行数据生成。可以通过 `#stonecutting` 或者构造函数分别初始化构建器。在保存之前,可以指定配方组和配方解锁条件。 +`SingleItemRecipeBuilder` is used to generate stonecutting recipes. Additionally, custom single item recipes using a serializer like `SingleItemRecipe$Serializer` can also be data generated using this builder. The builder can be initialized via `#stonecutting` or through the constructor respectively. The recipe group and the recipe unlock criteria can be specified before saving. ```java -// 在 RecipeProvider#buildRecipes(writer) 中 +// In RecipeProvider#buildRecipes(writer) SingleItemRecipeBuilder builder = SingleItemRecipeBuilder.stonecutting(input, RecipeCategory.MISC, result) - .unlockedBy("criteria", criteria) // 配方的解锁方式 - .save(writer); // 将数据添加到构建器中 + .unlockedBy("criteria", criteria) // How the recipe is unlocked + .save(writer); // Add data to builder ``` -非 `RecipeBuilder` 构建器 +Non-`RecipeBuilder` Builders ---------------------------- -一些配方构建器由于缺少所有先前提到的配方使用的功能而不实现 `RecipeBuilder`。 +Some recipe builders do not implement `RecipeBuilder` due to lacking features used by all previously mentioned recipes. ### SmithingTransformRecipeBuilder -`SmithingTransformRecipeBuilder` 用于生成将物品转化的铁匠配方。此外,使用类似 `SmithingTransformRecipe$Serializer` 的序列化器的自定义配方也可以使用这个构建器进行数据生成。可以通过 `#smithing` 或者构造函数分别初始化构建器。在保存之前,可以指定配方解锁条件。 +`SmithingTransformRecipeBuilder` is used to generate smithing recipes which transform an item. Additionally, custom recipes using a serializer like `SmithingTransformRecipe$Serializer` can also be data generated using this builder. The builder can be initialized via `#smithing` or through the constructor respectively. The recipe unlock criteria can be specified before saving. ```java -// 在 RecipeProvider#buildRecipes(writer) 中 +// In RecipeProvider#buildRecipes(writer) SmithingTransformRecipeBuilder builder = SmithingTransformRecipeBuilder.smithing(template, base, addition, RecipeCategory.MISC, result) - .unlocks("criteria", criteria) // 配方的解锁方式 - .save(writer, name); // 将数据添加到构建器中 + .unlocks("criteria", criteria) // How the recipe is unlocked + .save(writer, name); // Add data to builder ``` ### SmithingTrimRecipeBuilder -`SmithingTrimRecipeBuilder` 用于生成用于装甲修剪的铁匠配方。此外,使用类似 `SmithingTrimRecipe$Serializer` 的序列化器的自定义升级配方也可以使用这个构建器进行数据生成。可以通过 `#smithingTrim` 或者构造函数分别初始化构建器。在保存之前,可以指定配方解锁条件。 +`SmithingTrimRecipeBuilder` is used to generate smithing recipes for armor trims. Additionally, custom upgrade recipes using a serializer like `SmithingTrimRecipe$Serializer` can also be data generated using this builder. The builder can be initialized via `#smithingTrim` or through the constructor respectively. The recipe unlock criteria can be specified before saving. ```java -// 在 RecipeProvider#buildRecipes(writer) 中 +// In RecipeProvider#buildRecipes(writer) SmithingTrimRecipe builder = SmithingTrimRecipe.smithingTrim(template, base, addition, RecipeCategory.MISC) - .unlocks("criteria", criteria) // 配方的解锁方式 - .save(writer, name); // 将数据添加到构建器中 + .unlocks("criteria", criteria) // How the recipe is unlocked + .save(writer, name); // Add data to builder ``` ### SpecialRecipeBuilder -`SpecialRecipeBuilder` 用于为不能轻易约束到配方 JSON 格式(如染色护甲、焰火等)的动态配方生成空的 JSON。可以通过 `#special` 初始化构建器。 +`SpecialRecipeBuilder` is used to generate empty JSONs for dynamic recipes that cannot easily be constrained to the recipe JSON format (dying armor, firework, etc.). The builder can be initialized via `#special`. ```java -// 在 RecipeProvider#buildRecipes(writer) 中 +// In RecipeProvider#buildRecipes(writer) SpecialRecipeBuilder.special(dynamicRecipeSerializer) - .save(writer, name); // 将数据添加到构建器中 + .save(writer, name); // Add data to builder ``` -条件性配方 + +Conditional Recipes ------------------- -[条件性配方][conditional] 也可以通过 `ConditionalRecipe$Builder` 进行数据生成。可以使用 `#builder` 获取构建器。 +[Conditional recipes][conditional] can also be data generated via `ConditionalRecipe$Builder`. The builder can be obtained using `#builder`. -可以先调用 `#addCondition` 来指定每个配方的条件,然后在所有条件都指定后调用 `#addRecipe`。这个过程可以重复多次,由程序员自行决定。 +Conditions for each recipe can be specified by first calling `#addCondition` and then calling `#addRecipe` after all conditions have been specified. This process can be repeated as many times as the programmer would like. -在所有配方都指定之后,可以在最后使用 `#generateAdvancement` 为每个配方添加进步(advancements)。或者,可以使用 `#setAdvancement` 设置条件性进步。 +After all recipes have been specified, advancements can be added for each recipe at the end using `#generateAdvancement`. Alternatively, the conditional advancement can be set using `#setAdvancement`. ```java -// 在 RecipeProvider#buildRecipes(writer) 中 +// In RecipeProvider#buildRecipes(writer) ConditionalRecipe.builder() - // 添加配方的条件 + // Add the conditions for the recipe .addCondition(...) - // 当条件为真时返回配方 + // Add recipe to return when conditions are true .addRecipe(...) - // 添加下一个配方的条件 + // Add the next conditions for the next recipe .addCondition(...) - // 当下一个条件为真时返回下一个配方 + // Add next recipe to return when the next conditions are true .addRecipe(...) - // 创建使用上述配方中的条件和解锁进步的条件性进步 + // Create conditional advancement which uses the conditions + // and unlocking advancement in the recipes above .generateAdvancement() .build(writer, name); ``` ### IConditionBuilder -为了简化向条件性配方添加条件,而不必手动构造每个条件实例,扩展的 `RecipeProvider` 可以实现 `IConditionBuilder`。接口添加了轻松构造条件实例的方法。 +To simplify adding conditions to conditional recipes without having to construct the instances of each condition instance manually, the extended `RecipeProvider` can implement `IConditionBuilder`. The interface adds methods to easily construct condition instances. ```java -// 在 ConditionalRecipe$Builder#addCondition 中 +// In ConditionalRecipe$Builder#addCondition ( - // 如果 'examplemod:example_item' - // 或者 'examplemod:example_item2' 存在 - // 并且 - // 非FALSE + // If either 'examplemod:example_item' + // OR 'examplemod:example_item2' exists + // AND + // NOT FALSE - // 方法由 IConditionBuilder 定义 + // Methods are defined by IConditionBuilder and( or( itemExists("examplemod", "example_item"), @@ -181,13 +183,13 @@ ConditionalRecipe.builder() ) ``` -自定义配方序列化器 +Custom Recipe Serializers ------------------------- -通过创建一个可以构造 `FinishedRecipe` 的构建器,可以数据生成自定义配方序列化器。完成的配方将配方数据编码为JSON,并在存在时将其解锁进步也编码为JSON。此外,还需要指定配方的名称和序列化器,以知道写入位置以及加载时能解码对象的内容。一旦构造了 `FinishedRecipe`,只需将其传递给 `RecipeProvider#buildRecipes` 提供的 `Consumer`。 +Custom recipe serializers can be data generated by creating a builder that can construct a `FinishedRecipe`. The finished recipe encodes the recipe data and its unlocking advancement, when present, to JSON. Additionally, the name and serializer of the recipe is also specified to know where to write to and what can decode the object when loading. Once a `FinishedRecipe` is constructed, it simply needs to be passed to the `Consumer` supplied by `RecipeProvider#buildRecipes`. :::tip -`FinishedRecipe` 足够灵活,可以数据生成任何对象转换,不仅仅是物品。 +`FinishedRecipe`s are flexible enough that any object transformation can be data generated, not just items. ::: [datagen]: ../index.md#data-providers diff --git a/docs/datagen/tags.md b/docs/datagen/tags.md index d72c35a66..f07ca2c9a 100644 --- a/docs/datagen/tags.md +++ b/docs/datagen/tags.md @@ -1,16 +1,16 @@ -标签生成 -====== +Tag Generation +============== -通过继承`TagsProvider`并实现`#addTags`方法,可以为一个mod生成[标签]。实现后,必须将提供器[添加][datagen]到`DataGenerator`中。 +[Tags] can be generated for a mod by subclassing `TagsProvider` and implementing `#addTags`. After implementation, the provider must be [added][datagen] to the `DataGenerator`. ```java -// 在MOD事件总线上 +// On the MOD event bus @SubscribeEvent public void gatherData(GatherDataEvent event) { event.getGenerator().addProvider( - // 通知生成器仅在生成服务器数据时运行 + // Tell generator to run only when server data are generating event.includeServer(), - // 继承自net.neoforged.neoforge.common.data.BlockTagsProvider + // Extends net.neoforged.neoforge.common.data.BlockTagsProvider output -> new MyBlockTagsProvider( output, event.getLookupProvider(), @@ -24,75 +24,75 @@ public void gatherData(GatherDataEvent event) { `TagsProvider` -------------- -标签提供器有两种用于生成标签的方法:通过`#tag`使用对象和其他标签创建一个标签,或者通过`#getOrCreateRawBuilder`使用其他对象类型的标签来生成标签数据。 +The tags provider has two methods used for generating tags: creating a tag with objects and other tags via `#tag`, or using tags from other object types to generate the tag data via `#getOrCreateRawBuilder`. :::note -通常,除非一个注册表包含来自不同注册表的对象的表示(例如,方块有项目表示以在库存中获得方块),否则提供器不会直接调用`#getOrCreateRawBuilder`。 +Typically, a provider will not call `#getOrCreateRawBuilder` directly unless a registry contains a representation of objects from a different registry (blocks have item representations to obtain the blocks in the inventory). ::: -调用`#tag`时,会创建一个`TagAppender`,它作为链式消费者,用于向标签添加元素: +When `#tag` is called, a `TagAppender` is created which acts as a chainable consumer of elements to add to the tag: -方法 | 描述 -:---: | :--- -`add` | 通过其资源键将一个对象添加到标签中。 -`addOptional` | 通过其名称将一个对象添加到标签中。如果对象不存在,则加载时将跳过该对象。 -`addTag` | 通过其标签键将一个标签添加到标签中。内标签中的所有元素现在都是外标签的一部分。 -`addOptionalTag` | 通过其名称将一个标签添加到标签中。如果标签不存在,则加载时将跳过该标签。 -`replace` | 当为`true`时,此标签从其他数据包添加的所有先前加载的条目将被丢弃。如果在此之后加载了一个数据包,那么它仍然会将条目追加到标签中。 -`remove` | 通过其名称或键从标签中移除一个对象或标签。 +Method | Description +:---: | :--- +`add` | Adds an object to a tag through its resource key. +`addOptional` | Adds an object to a tag through its name. If the object is not present, then the object will be skipped when loading. +`addTag` | Adds a tag to a tag through its tag key. All elements within the inner tag are now a part of the outer tag. +`addOptionalTag` | Adds a tag to a tag through its name. If the tag is not present, then the tag will be skipped when loading. +`replace` | When `true`, all previously loaded entries added to this tag from other datapacks will be discarded. If a datapack is loaded after this one, then it will still append the entries to the tag. +`remove` | Removes an object or tag from a tag through its name or key. ```java -// 在某个TagProvider#addTags中 +// In some TagProvider#addTags this.tag(EXAMPLE_TAG) - .add(EXAMPLE_OBJECT) // 将一个对象添加到标签中 - .addOptional(new ResourceLocation("othermod", "other_object")) // 向标签添加来自其他mod的对象 + .add(EXAMPLE_OBJECT) // Adds an object to the tag + .addOptional(new ResourceLocation("othermod", "other_object")) // Adds an object from another mod to the tag this.tag(EXAMPLE_TAG_2) - .addTag(EXAMPLE_TAG) // 将一个标签添加到另一个标签 - .remove(EXAMPLE_OBJECT) // 从此标签中移除一个对象 + .addTag(EXAMPLE_TAG) // Adds a tag to the tag + .remove(EXAMPLE_OBJECT) // Removes an object from this tag ``` :::important -如果你的mod的标签对其他mod的标签有软依赖(也就是说,其他mod可能在运行时不存在),那么你应该使用可选的方法来引用其他mod的标签。 +If the mod's tags softly depends on another mod's tags (the other mod may or may not be present at runtime), the other mods' tags should be referenced using the optional methods. ::: -### 现有的供应商 +### Existing Providers -Minecraft中包含了一些可以被子类化的特定注册表的标签供应商。另外,一些供应商包含了额外的辅助方法,可以更容易地创建标签。 +Minecraft contains a few tag providers for certain registries that can be subclassed instead. Additionally, some providers contain additional helper methods to more easily create tags. -注册对象类型 | 标签供应商 +Registry Object Type | Tag Provider :---: | :--- -`方块` | `BlockTagsProvider`\* -`物品` | `ItemTagsProvider` -`实体类型` | `EntityTypeTagsProvider` -`流体` | `FluidTagsProvider` -`游戏事件` | `GameEventTagsProvider` -`生物群系` | `BiomeTagsProvider` -`平面世界生成预设` | `FlatLevelGeneratorPresetTagsProvider` -`世界预设` | `WorldPresetTagsProvider` -`结构` | `StructureTagsProvider` -`兴趣点类型` | `PoiTypeTagsProvider` -`旗帜图案` | `BannerPatternTagsProvider` -`猫的品种` | `CatVariantTagsProvider` -`画的种类` | `PaintingVariantTagsProvider` -`乐器` | `InstrumentTagsProvider` -`伤害类型` | `DamageTypeTagsProvider` - -\* `BlockTagsProvider` 是Forge所添加的 `TagsProvider`。 +`Block` | `BlockTagsProvider`\* +`Item` | `ItemTagsProvider` +`EntityType` | `EntityTypeTagsProvider` +`Fluid` | `FluidTagsProvider` +`GameEvent` | `GameEventTagsProvider` +`Biome` | `BiomeTagsProvider` +`FlatLevelGeneratorPreset` | `FlatLevelGeneratorPresetTagsProvider` +`WorldPreset` | `WorldPresetTagsProvider` +`Structure` | `StructureTagsProvider` +`PoiType` | `PoiTypeTagsProvider` +`BannerPattern` | `BannerPatternTagsProvider` +`CatVariant` | `CatVariantTagsProvider` +`PaintingVariant` | `PaintingVariantTagsProvider` +`Instrument` | `InstrumentTagsProvider` +`DamageType` | `DamageTypeTagsProvider` + +\* `BlockTagsProvider` is a Forge added `TagsProvider`. #### `ItemTagsProvider#copy` -方块在物品栏中有物品形式的表示。因此,许多的方块标签也可以是物品标签。为了方便地生成与方块标签具有相同条目的物品标签,可以使用 `#copy` 方法,此方法接收要复制的方块标签和要复制到的物品标签。 +Blocks have item representations to obtain them in the inventory. As such, many of the block tags can also be an item tag. To easily generate item tags to have the same entries as block tags, the `#copy` method can be used which takes in the block tag to copy from and the item tag to copy to. ```java -// 在 ItemTagsProvider#addTags 中 +//In ItemTagsProvider#addTags this.copy(EXAMPLE_BLOCK_TAG, EXAMPLE_ITEM_TAG); ``` -自定义标签提供者 +Custom Tag Providers -------------------- -通过 `TagsProvider` 的子类,可以创建自定义的标签提供者,这需要传入注册键以生成标签。 +A custom tag provider can be created via a `TagsProvider` subclass which takes in the registry key to generate tags for. ```java public RecipeTypeTagsProvider(PackOutput output, CompletableFuture registries, ExistingFileHelper fileHelper) { @@ -100,12 +100,12 @@ public RecipeTypeTagsProvider(PackOutput output, CompletableFuture registries, ExistingFileHelper fileHelper) { super( output, diff --git a/docs/datamaps/index.md b/docs/datamaps/index.md index d368ceb3f..3886d4ff6 100644 --- a/docs/datamaps/index.md +++ b/docs/datamaps/index.md @@ -1,24 +1,24 @@ -# 数据映射 +# Data Maps -注册表数据映射包含可附加到注册表对象的数据驱动、可重载的对象。 -这个系统允许更容易地数据驱动游戏行为,因为它们提供了如同步或冲突解决等功能,从而带来更好、更可配置的用户体验。 +A registry data map contains data-driven, reloadable objects that can be attached to a registry object. +This system allows more easily data-driving game behaviour, as they provide functionality such as syncing or conflict resolution, leading to a better and more configurable user experience. -你可以将标签看作注册表对象 ➜ 布尔映射,而数据映射则是更灵活的注册表对象 ➜ 对象映射。 +You can think of tags as registry object ➜ boolean maps, while data maps are more flexible registry object ➜ object maps. -数据映射可以附加到静态的内置注册表和动态的数据驱动的数据包注册表上。 +A data map can be attached to both static, built-in, registries and dynamic data-driven datapack registries. -数据映射支持通过使用 `/reload` 命令或任何其他重新加载服务器资源的方法来重新加载。 +Data maps support reloading through the use of the `/reload` command or any other means that reload server resources. -## 注册 -数据映射类型应该静态创建,然后注册到 `RegisterDataMapTypesEvent`(在 [mod事件总线](../concepts/events)上触发)。`DataMapType` 可以使用 `DataMapType$Builder` 通过 `DataMapType#builder` 创建。 +## Registration +A data map type should be statically created and then registered to the `RegisterDataMapTypesEvent` (which is fired on the [mod event bus](../concepts/events)). The `DataMapType` can be created using a `DataMapType$Builder`, through `DataMapType#builder`. -构建器提供了一个 `synced` 方法,可以用来标记数据映射为同步并将其发送给客户端。 +The builder provides a `synced` method which can be used to mark a data map as synced and have it sent to clients. -一个简单的 `DataMapType` 有两个泛型参数:`R`(数据映射所针对的注册表的类型)和 `T`(被附加的值)。因此,可以将附加到 `Item` 的 `SomeObject` 的数据映射表示为 `DataMapType`。 +A simple `DataMapType` has two generic arguments: `R` (the type of the registry the data map is for) and `T` (the values that are being attached). A data map of `SomeObject`s that are attached to `Item`s can, as such, be represented as `DataMapType`. -数据映射使用 [编解码器](../datastorage/codecs.md) 进行序列化和反序列化。 +Data maps are serialized and deserialized using [Codecs](../datastorage/codecs.md). -以以下表示数据映射值的记录为例: +Let's take the following record representing the data map value as an example: ```java public record DropHealing( float amount, float chance @@ -31,54 +31,54 @@ public record DropHealing( ``` :::warning -值 (`T`) 应为 *不可变* 对象,否则如果对象附加到标签内的所有条目,则可能导致奇怪的行为(因为不会创建副本)。 +The value (`T`) should be an *immutable* object, as otherwise weird behaviour can be caused if the object is attached to all entries within a tag (since no copy is created). ::: -为了本例的目的,我们将使用此数据映射在玩家丢弃物品时治疗玩家。 -`DataMapType` 可以这样创建: +For the purposes of this example, we will use this data map to heal players when they drop an item. +The `DataMapType` can be created as such: ```java public static final DataMapType DROP_HEALING = DataMapType.builder( new ResourceLocation("mymod:drop_healing"), Registries.ITEM, DropHealing.CODEC ).build(); ``` -然后使用 `RegisterDataMapTypesEvent#register` 注册到 `RegisterDataMapTypesEvent`。 +and then registered to the `RegisterDataMapTypesEvent` using `RegisterDataMapTypesEvent#register`. -## 同步 -同步的数据映射将会将其值同步到客户端。可以使用 `DataMapType$Builder#synced(Codec networkCodec, boolean mandatory)` 标记数据映射为同步。 -然后将使用 `networkCodec` 同步数据映射的值。 -如果 `mandatory` 标志设置为 `true`,则不支持数据映射的客户端(包括 Vanilla 客户端)将无法连接到服务器,反之亦然。另一方面,非强制性的数据映射是可选的,因此它不会阻止任何客户端加入。 +## Syncing +A synced data map will have its values synced to clients. A data map can be marked as synced using `DataMapType$Builder#synced(Codec networkCodec, boolean mandatory)`. +The values of the data map will then be synced using the `networkCodec`. +If the `mandatory` flag is set to `true`, clients that do not support the data map (including Vanilla clients) will not be able to connect to the server, nor vice-versa. A non-mandatory data map on the other hand is optional, so it will not prevent any clients from joining. :::tip -单独的网络编解码器允许包大小更小,因为你可以选择发送哪些数据以及以什么格式发送。否则可以使用默认编解码器。 +A separate network codec allows for packet sizes to be smaller, as you can choose what data to send, and in what format. Otherwise the default codec can be used. ::: -## JSON结构和位置 -数据映射从位于 `mapNamespace/data_maps/registryNamespace/registryPath/mapPath.json` 的JSON文件加载,其中: -- `mapNamespace` 是数据映射ID的命名空间 -- `mapPath` 是数据映射ID的路径 -- `registryNamespace` 是注册表ID的命名空间;如果命名空间是 `minecraft`,则此值将被省略 -- `registryPath` 是注册表ID的路径 +## JSON Structure and location +Data maps are loaded from a JSON file located at `mapNamespace/data_maps/registryNamespace/registryPath/mapPath.json`, where: +- `mapNamespace` is the namespace of the ID of the data map +- `mapPath` is the path of the ID of the data map +- `registryNamespace` is the namespace of the ID of the registry; if the namespace is `minecraft`, this value will be omitted +- `registryPath` is the path of the ID of the registry -更多信息,请[查看专用页面](./structure.md)。 +For more information, please [check out the dedicated page](./structure.md). -## 使用 -由于数据映射可以用于任何注册表,因此可以通过 `Holder` 查询它们,而不是通过实际的注册表对象。 -你可以使用 `Holder#getData(DataMapType)` 查询数据映射值。如果该对象没有附加值,方法将返回 `null`。 +## Usage +As data maps can be used on any registry, they can be queried through `Holder`s, and not through the actual registry objects. +You can query a data map value using `Holder#getData(DataMapType)`. If that object doesn't have a value attached, the method will return `null`. :::note -只有引用持有者会在该方法中返回值。`直接` 持有者 **不会**。通常,你只会遇到引用持有者(它们由 `Registry#wrapAsHolder`、`Registry#getHolder` 或不同的 `builtInRegistryHolder` 方法返回)。 +Only reference holders will return a value in that method. `Direct` holders will **not**. Generally, you will only encounter reference holders (which are returned by methods such as `Registry#wrapAsHolder`, `Registry#getHolder` or the different `builtInRegistryHolder` methods). ::: -为了继续上面的示例,我们可以如下实现我们预期的行为: +To continue the example above, we can implement our intended behaviour as follows: ```java public static void onItemDrop(final ItemTossEvent event) { final ItemStack stack = event.getEntity().getItem(); - // ItemStack 有一个 getItemHolder 方法,它将返回一个指向堆叠物的物品的 Holder - //高亮下一行 + // ItemStack has a getItemHolder method that will return a Holder which points to the item the stack is of + //highlight-next-line final DropHealing value = stack.getItemHolder().getData(DROP_HEALING); - // 由于 getData 如果物品没有附加 drop healing 值将返回 null,我们保护它不为 null + // Since getData returns null if the item will not have a drop healing value attached, we guard against it being null if (value != null) { - // 这里我们简单地使用值 + // And here we simply use the values if (event.getPlayer().level().getRandom().nextFloat() > value.chance()) { event.getPlayer().heal(value.amount()); } @@ -86,43 +86,43 @@ public static void onItemDrop(final ItemTossEvent event) { } ``` -## 高级数据映射 -高级数据映射是具有额外功能的数据映射。即,通过移除器合并值和选择性地移除它们的能力。对于值类似于集合(如 `Map` 或 `List`)的数据映射,强烈推荐实现某种形式的合并和移除器。 +## Advanced data maps +Advanced data maps are data maps which have additional functionality. Namely, the ability of merging values and selectively removing them, through a remover. Implementing some form of merging and removers is highly recommended for data maps whose values are collection-likes (like `Map`s or `List`s). -`AdvancedDataMapType` 除了 `T` 和 `R` 之外还有一个泛型:`VR extends DataMapValueRemover`。这个额外的泛型允许你通过提高类型安全性来生成移除对象。 +`AdvancedDataMapType` have one more generic besides `T` and `R`: `VR extends DataMapValueRemover`. This additional generic allows you to datagen remove objects with increased type safety. -### 创建 -你可以使用 `AdvancedDataMapType#builder` 创建 `AdvancedDataMapType`。与普通构建器不同,该方法返回的构建器将有两个额外的方法(`merger` 和 `remover`),并且它将返回一个 `AdvancedDataMapType`。注册方法保持不变。 +### Creation +You create an `AdvancedDataMapType` using `AdvancedDataMapType#builder`. Unlike the normal builder, the builder returned by that method will have two more methods (`merger` and `remover`), and it will return an `AdvancedDataMapType`. Registration methods remain the same. -### 合并器 -高级数据映射可以通过 `AdvancedDataMapType#merger` 提供一个 `DataMapValueMerger`。这个合并器将用于处理尝试将值附加到同一对象的数据包之间的冲突。 -合并器将给出两个冲突的值及其来源(作为 `Either, ResourceKey>`,因为值可以附加到标签内的所有条目,而不仅仅是个别条目),并期望返回实际附加的值。 -通常,合并器应简单地合并值,并且除非必要(即如果合并不可能),否则不应执行“硬”覆盖。如果一个包想要绕过合并器,它可以通过指定对象级别的 `replace` 字段来实现。 +### Mergers +An advanced data map can provide a `DataMapValueMerger` through `AdvancedDataMapType#merger`. This merger will be used to handle conflicts between data packs that attempt to attach a value to the same object. +The merger will be given the two conflicting values, and their sources (as an `Either, ResourceKey>` since values can be attached to all entries within a tag, not just individual entries), and is expected to return the value that will actually be attached. +Generally, mergers should simply merge the values, and should not perform "hard" overwrites unless necessary (i.e. if merging isn't possible). If a pack wants to bypass the merger, it can do so by specifying the object-level `replace` field. -假设我们有一个将整数附加到物品的数据映射的情况: +Let's imagine a scenario where we have a data map that attaches integers to items: ```java public class IntMerger implements DataMapValueMerger { @Override public Integer merge(Registry registry, Either, ResourceKey> first, Integer firstValue, Either, ResourceKey> second, Integer secondValue) { - //高亮下一行 + //highlight-next-line return firstValue + secondValue; } } ``` -如果两个数据包附加到同一对象,则上述合并器将合并值。所以如果第一个包将值 `12` 附加到 `minecraft:carrot`,第二个包将值 `15` 附加到 `minecraft:carrot`,最终的值将是 `27`。然而,如果第二个包指定对象级别的 `replace` 字段,最终值将是 `15`,因为不会调用合并器。 +The above merger will merge the values if two datapacks attach to the same object. So if the first pack attaches the value `12` to `minecraft:carrot`, and the second pack attaches the value `15` to `minecraft:carrot`, the final value will be `27`. However, if the second pack specifies the object-level `replace` field, the final value will be `15` as the merger won't be invoked. -NeoForge 为合并列表、集合和映射提供了一些默认的合并器,位于 `DataMapValueMerger` 中。 +NeoForge provides some default mergers for merging lists, sets and maps in `DataMapValueMerger`. -默认合并器(`DataMapValueMerger#defaultMerger`)具有你期望的普通数据包的典型行为,其中最新的值(来自最高的数据包)将覆盖之前的值。 +The default merger (`DataMapValueMerger#defaultMerger`) has the typical behaviour you'd expect from normal data packs, where the newest value (which comes from the highest datapack) overwrites the previous value. -### 移除器 -高级数据映射可以通过 `AdvancedDataMapType#remover` 提供一个 `DataMapValueRemover`。移除器将允许选择性地移除数据映射值,有效地进行分解。 -虽然默认情况下一个数据包只能移除附加到注册表条目的整个对象,但有了移除器,它可以只从附加对象中移除特定的值(即,在映射的情况下,只移除具有给定键的项,或在列表的情况下,只移除具有特定属性的项)。 +### Removers +An advanced data map can provide a `DataMapValueRemover` through `AdvancedDataMapType#remover`. The remover will allow selective removals of data map values, effectively decomposition. +While by default a datapack can only remove the whole object attached to a registry entry, with a remover it can remove just speciffic values from the attached object (i.e. just the entry with a given key in the case of a map, or the entry with a specific property in the case of a list). -传递给构建器的编解码器将解码移除器实例。然后这些移除器将给出当前附加的值及其来源,并期望创建一个新对象来替换旧值。 -或者,一个空的 `Optional` 将导致值被完全移除。 +The codec that is passed to the builder will decode remover instances. These removers will then be given the value currently attached and its source, and are expected to create a new object to replace the old value. +Alternatively, an empty `Optional` will lead to the value being completely removed. -一个从基于 `Map` 的数据映射中移除具有特定键的值的移除器示例: +An example of a remover that will remove a value with a specific key from a `Map`-based data map: ```java public record MapRemover(String key) implements DataMapValueRemover> { public static final Codec CODEC = Codec.STRING.xmap(MapRemover::new, MapRemover::key); @@ -136,36 +136,36 @@ public record MapRemover(String key) implements DataMapValueRemover> ATTACHMENT_TYPES = DeferredRegister.create(NeoForgeRegistries.ATTACHMENT_TYPES, MOD_ID); -// 通过 INBTSerializable 序列化 +// Serialization via INBTSerializable private static final Supplier> HANDLER = ATTACHMENT_TYPES.register( "handler", () -> AttachmentType.serializable(() -> new ItemStackHandler(1)).build()); -// 通过编解码器序列化 +// Serialization via codec private static final Supplier> MANA = ATTACHMENT_TYPES.register( "mana", () -> AttachmentType.builder(() -> 0).serialize(Codec.INT).build()); -// 无序列化 +// No serialization private static final Supplier> SOME_CACHE = ATTACHMENT_TYPES.register( "some_cache", () -> AttachmentType.builder(() -> new SomeCache()).build() ); -// 在您的 mod 构造函数中,不要忘记将 DeferredRegister 注册到您的 mod 总线: +// In your mod constructor, don't forget to register the DeferredRegister to your mod bus: ATTACHMENT_TYPES.register(modBus); ``` -## 使用附件类型 +## Using the attachment type -一旦附件类型注册后,它可以在任何持有对象上使用。 -如果没有数据,调用 `getData` 将附加一个新的默认实例。 +Once the attachment type is registered, it can be used on any holder object. +Calling `getData` if no data is present will attach a new default instance. ```java -// 如果已存在 ItemStackHandler,则获取它,否则附加一个新的: +// Get the ItemStackHandler if it already exists, else attach a new one: ItemStackHandler stackHandler = stack.getData(HANDLER); -// 获取当前玩家的法力值(如果有),否则附加 0: +// Get the current player mana if it is available, else attach 0: int playerMana = player.getData(MANA); -// 等等... +// And so on... ``` -如果不希望附加一个默认实例,可以添加一个 `hasData` 检查: +If attaching a default instance is not desired, a `hasData` check can be added: ```java -// 检查堆叠是否有 HANDLER 附件,然后再进行任何操作。 +// Check if the stack has the HANDLER attachment before doing anything. if (stack.hasData(HANDLER)) { ItemStackHandler stackHandler = stack.getData(HANDLER); - // 对 stack.getData(HANDLER) 做些什么。 + // Do something with stack.getData(HANDLER). } ``` -数据也可以用 `setData` 更新: +The data can also be updated with `setData`: ```java -// 将法力值增加 10。 +// Increment mana by 10. player.setData(MANA, player.getData(MANA) + 10); ``` :::important -通常,当修改方块实体和区块时需要将其标记为脏数据(使用 `setChanged` 和 `setUnsaved(true)`)。这对于 `setData` 的调用是自动完成的: +Usually, block entities and chunks need to be marked as dirty when they are modified (with `setChanged` and `setUnsaved(true)`). This is done automatically for calls to `setData`: ```java -chunk.setData(MANA, chunk.getData(MANA) + 10); // 将自动调用 setUnsaved +chunk.setData(MANA, chunk.getData(MANA) + 10); // will call setUnsaved automatically ``` -但如果您修改了从 `getData` 获取的数据(包括新创建的默认实例),则必须显式地将方块实体和区块标记为脏数据: +but if you modify some data that you obtained from `getData` (including a newly created default instance) then you must mark block entities and chunks as dirty explicitly: ```java var mana = chunk.getData(MUTABLE_MANA); mana.set(10); -chunk.setUnsaved(true); // 必须手动完成,因为我们没有使用 setData +chunk.setUnsaved(true); // must be done manually because we did not use setData ``` ::: -## 与客户端共享数据 -目前,只有可序列化的物品堆叠附件在客户端和服务器之间同步。 -这是自动完成的。 +## Sharing data with the client +Currently, only serializable item stack attachments are synced between the client and the server. +This is done automatically. -要将方块实体、区块或实体附件同步到客户端,你需要自己[向客户端发送数据包][network]。 -对于区块,您可以使用 `ChunkWatchEvent.Sent` 来知道何时向玩家发送区块数据。 +To sync block entity, chunk, or entity attachments to a client, you need to [send a packet to the client][network] yourself. +For chunks, you can use `ChunkWatchEvent.Sent` to know when to send chunk data to a player. -## 在玩家死亡时复制数据 -默认情况下,实体数据附件在玩家死亡时不会被复制。 -要在玩家死亡时自动复制附件,请在附件构建器中设置 `.copyOnDeath()`。 +## Copying data on player death +By default, entity data attachments are not copied on player death. +To automatically copy an attachment on player death, set `.copyOnDeath()` in the attachment builder. -更复杂的处理可以通过 `PlayerEvent.Clone` 实现,通过从原始实体中读取数据并将其分配给新实体。在此事件中,可以使用 `#isWasDeath` 方法区分死亡后重生和从末地返回。这很重要,因为从末地返回时数据已经存在,因此要注意在这种情况下不要重复值。 +More complex handling can be implemented via `PlayerEvent.Clone` by reading the data from the original entity and assigning it to the new entity. In this event, the `#isWasDeath` method can be used to distinguish between respawning after death and returning from the End. This is important because the data will already exist when returning from the End, so care has to be taken to not duplicate values in this case. -例如: +For example: ```java NeoForge.EVENT_BUS.register(PlayerEvent.Clone.class, event -> { if (event.isWasDeath() && event.getOriginal().hasData(MY_DATA)) { @@ -106,4 +106,4 @@ NeoForge.EVENT_BUS.register(PlayerEvent.Clone.class, event -> { }); ``` -[network]: ../networking/index.md +[network]: ../networking/index.md \ No newline at end of file diff --git a/docs/datastorage/capabilities.md b/docs/datastorage/capabilities.md index ecbd044a7..94fff8c71 100644 --- a/docs/datastorage/capabilities.md +++ b/docs/datastorage/capabilities.md @@ -1,281 +1,299 @@ -# 功能 +# Capabilities -功能(Capabilities)允许以动态和灵活的方式公开特性,无需直接实现许多接口。 +Capabilities allow exposing features in a dynamic and flexible way without having to resort to directly implementing many interfaces. -通常来说,每个功能都以接口的形式提供一个特性。 +In general terms, each capability provides a feature in the form of an interface. -NeoForge 为方块、实体和物品堆叠添加了功能支持。 -这将在以下部分中更详细地解释。 +NeoForge adds capability support to blocks, entities, and item stacks. +This will be explained in more detail in the following sections. -## 为什么使用功能 +## Why use capabilities -功能旨在将**能做什么**与**如何做**分离开来,适用于方块、实体或物品堆叠。 -如果您正在考虑功能是否适合某项工作,请问自己以下问题: -1. 我只关心方块、实体或物品堆叠能做什么,而不关心它如何做吗? -2. 这个 **能做什么**,也就是行为,是否只对某些方块、实体或物品堆叠有效,而不是所有? -3. 这个 **如何做**,即行为的实现,是否依赖于具体的方块、实体或物品堆叠? +Capabilities are designed to separate **what** a block, entity or item stack can do from **how** it does it. +If you are wondering whether capabilities are the right tool for a job, ask yourself the following questions: +1. Do I only care about **what** a block, entity or item stack can do, but not about **how** it does it? +2. Is the **what**, the behavior, only available for some blocks, entities, or item stacks, but not all of them? +3. Is the **how**, the implementation of that behavior, dependent on the specific block, entity or item stack? -以下是一些良好的功能使用示例: -- *“我希望我的流体容器能与其他模组的流体容器兼容,但我不知道每个流体容器的具体情况。”* - 是的,使用 `IFluidHandler` 功能。 -- *“我想计算某个实体中有多少物品,但我不知道实体可能如何存储它们。”* - 是的,使用 `IItemHandler` 功能。 -- *“我想给某个物品堆叠充能,但我不知道物品堆叠可能如何存储能量。”* - 是的,使用 `IEnergyStorage` 功能。 -- *“我想对玩家当前瞄准的任何方块应用颜色,但我不知道方块将如何变化。”* - 是的。NeoForge 没有提供给方块上色的功能,但你可以自己实现。 +Here are a few examples of good capability usage: +- *"I want my fluid container to be compatible with fluid containers from other mods, but I don't know the specifics of each fluid container."* - Yes, use the `IFluidHandler` capability. +- *"I want to count how many items are in some entity, but I do not know how the entity might store them."* - Yes, use the `IItemHandler` capability. +- *"I want to fill some item stack with power, but I do not know how the item stack might store it."* - Yes, use the `IEnergyStorage` capability. +- *"I want to apply some color to whatever block a player is currently targeting, but I do not know how the block will be transformed."* - Yes. NeoForge does not provide a capability to color blocks, but you can implement one yourself. -以下是不推荐使用功能的示例: -- *“我想检查某个实体是否在我的机器范围内。”* - 不,使用帮助方法代替。 +Here is an example of discouraged capability usage: +- *"I want to check if an entity is within the range of my machine."* - No, use a helper method instead. -## NeoForge 提供的功能 +## NeoForge-provided capabilities -NeoForge 为以下三个接口提供了功能:`IItemHandler`,`IFluidHandler` 和 `IEnergyStorage`。 +NeoForge provides capabilities for the following three interfaces: `IItemHandler`, `IFluidHandler` and `IEnergyStorage`. -`IItemHandler` 公开了处理库存槽的接口。`IItemHandler` 类型的功能有: -- `Capabilities.ItemHandler.BLOCK`:方块的自动化可访问库存(用于箱子、机器等)。 -- `Capabilities.ItemHandler.ENTITY`:实体的库存内容(额外的玩家槽位、怪物/生物的库存/包)。 -- `Capabilities.ItemHandler.ENTITY_AUTOMATION`:实体的自动化可访问库存(船、矿车等)。 -- `Capabilities.ItemHandler.ITEM`:物品堆叠的内容(便携背包等)。 +`IItemHandler` exposes an interface for handling inventory slots. The capabilities of type `IItemHandler` are: +- `Capabilities.ItemHandler.BLOCK`: automation-accessible inventory of a block (for chests, machines, etc). +- `Capabilities.ItemHandler.ENTITY`: inventory contents of an entity (extra player slots, mob/creature inventories/bags). +- `Capabilities.ItemHandler.ENTITY_AUTOMATION`: automation-accessible inventory of an entity (boats, minecarts, etc). +- `Capabilities.ItemHandler.ITEM`: contents of an item stack (portable backpacks and such). -`IFluidHandler` 公开了处理流体库存的接口。`IFluidHandler` 类型的功能有: -- `Capabilities.FluidHandler.BLOCK`:方块的自动化可访问流体库存。 -- `Capabilities.FluidHandler.ENTITY`:实体的流体库存。 -- `Capabilities.FluidHandler.ITEM`:物品堆叠的流体库存。 -这个功能是特殊的 `IFluidHandlerItem` 类型,因为桶装液体的方式有所不同。 +`IFluidHandler` exposes an interface for handling fluid inventories. The capabilities of type `IFluidHandler` are: +- `Capabilities.FluidHandler.BLOCK`: automation-accessible fluid inventory of a block. +- `Capabilities.FluidHandler.ENTITY`: fluid inventory of an entity. +- `Capabilities.FluidHandler.ITEM`: fluid inventory of an item stack. +This capability is of the special `IFluidHandlerItem` type due to the way buckets hold fluids. -`IEnergyStorage` 公开了处理能量容器的接口。它基于 TeamCoFH 的 RedstoneFlux API。`IEnergyStorage` 类型的功能有: -- `Capabilities.EnergyStorage.BLOCK`:方块内部的能量。 -- `Capabilities.EnergyStorage.ENTITY`:实体内部的能量。 -- `Capabilities.EnergyStorage.ITEM`:物品堆叠内部的能量。 +`IEnergyStorage` exposes an interface for handling energy containers. It is based on the RedstoneFlux API by TeamCoFH. The capabilities of type `IEnergyStorage` are: +- `Capabilities.EnergyStorage.BLOCK`: energy contained inside a block. +- `Capabilities.EnergyStorage.ENTITY`: energy containing inside an entity. +- `Capabilities.EnergyStorage.ITEM`: energy contained inside an item stack. -## 创建功能 +## Creating a capability -NeoForge为方块、实体和物品堆叠支持功能性。功能性允许在一定逻辑下查找某些API的实现。在NeoForge中实现了以下几种功能性: -- `BlockCapability`:适用于方块和方块实体的功能性;行为依赖于特定的`Block`。 -- `EntityCapability`:适用于实体的功能性;行为依赖于特定的`EntityType`。 -- `ItemCapability`:适用于物品堆叠的功能性;行为依赖于特定的`Item`。 +NeoForge supports capabilities for blocks, entities, and item stacks. + +Capabilities allow looking up implementations of some APIs with some dispatching logic. The following kinds of capabilities are implemented in NeoForge: +- `BlockCapability`: capabilities for blocks and block entities; behavior depends on the specific `Block`. +- `EntityCapability`: capabilities for entities: behavior dependends on the specific `EntityType`. +- `ItemCapability`: capabilities for item stacks: behavior depends on the specific `Item`. :::tip -为了与其他模组兼容,如果可能的话,我们建议使用NeoForge在`Capabilities`类中提供的功能性。否则,您可以按照本节所述创建自己的功能性。 +For compatibility with other mods, +we recommend using the capabilities provided by NeoForge in the `Capabilities` class if possible. +Otherwise, you can create your own as described in this section. ::: -创建功能性是单个函数调用,结果对象应该存储在一个`static final`字段中。必须提供以下参数: -- 功能性的名称。多次创建相同名称的功能性将始终返回相同的对象。不同名称的功能性是**完全独立的**,可以用于不同的目的。 -- 正在查询的行为类型。这是`T`类型参数。 -- 查询中的附加上下文类型。这是`C`类型参数。 +Creating a capability is a single function call, and the resulting object should be stored in a `static final` field. +The following parameters must be provided: +- The name of the capability. +Creating a capability with the same name multiple times will always return the same object. +Capabilities with different names are **completely independent**, and can be used for different purposes. +- The behavior type that is being queried. This is the `T` type parameter. +- The type for additional context in the query. This is the `C` type parameter. -例如,以下是如何声明侧向感知方块`IItemHandler`的功能性: +For example, here is how a capability for side-aware block `IItemHandler`s might be declared: ```java public static final BlockCapability ITEM_HANDLER_BLOCK = BlockCapability.create( - // 提供一个名称以唯一标识功能性。 + // Provide a name to uniquely identify the capability. new ResourceLocation("mymod", "item_handler"), - // 提供查询的类型。在这里,我们希望查找`IItemHandler`实例。 + // Provide the queried type. Here, we want to look up `IItemHandler` instances. IItemHandler.class, - // 提供上下文类型。我们将允许查询接收额外的`Direction side`参数。 + // Provide the context type. We will allow the query to receive an extra `Direction side` parameter. Direction.class); ``` -对于方块来说,`@Nullable Direction`是如此常见,以至于有一个专门的助手函数: - +A `@Nullable Direction` is so common for blocks that there is a dedicated helper: ```java public static final BlockCapability ITEM_HANDLER_BLOCK = BlockCapability.createSided( - // 提供一个名称以唯一标识功能性。 + // Provide a name to uniquely identify the capability. new ResourceLocation("mymod", "item_handler"), - // 提供查询的类型。在这里,我们希望查找`IItemHandler`实例。 + // Provide the queried type. Here, we want to look up `IItemHandler` instances. IItemHandler.class); ``` -如果不需要上下文,则应使用`Void`。对于无上下文的功能性也有专门的助手函数: - +If no context is required, `Void` should be used. +There is also a dedicated helper for context-less capabilities: ```java public static final BlockCapability ITEM_HANDLER_NO_CONTEXT = BlockCapability.createVoid( - // 提供一个名称以唯一标识功能性。 + // Provide a name to uniquely identify the capability. new ResourceLocation("mymod", "item_handler_no_context"), - // 提供查询的类型。在这里,我们希望查找`IItemHandler`实例。 + // Provide the queried type. Here, we want to look up `IItemHandler` instances. IItemHandler.class); ``` -对于实体和物品堆叠,`EntityCapability`和`ItemCapability`分别存在类似的方法。 - -## 查询功能性 -一旦我们在一个静态字段中拥有了`BlockCapability`、`EntityCapability`或`ItemCapability`对象,我们就可以查询一个功能性。 +For entities and item stacks, similar methods exist in `EntityCapability` and `ItemCapability` respectively. -对于实体和物品堆叠,我们可以尝试使用`getCapability`找到功能性的实现。如果结果是`null`,则没有可用的实现。 +## Querying capabilities +Once we have our `BlockCapability`, `EntityCapability`, or `ItemCapability` object in a static field, we can query a capability. -例如: +For entities and item stacks, we can try to find implementations of a capability with `getCapability`. +If the result is `null`, there no implementation is available. +For example: ```java var object = entity.getCapability(CAP, context); if (object != null) { - // 使用object + // Use object } ``` ```java var object = stack.getCapability(CAP, context); if (object != null) { - // 使用object + // Use object } ``` -方块功能性的使用略有不同,因为没有方块实体的方块也可以拥有功能性。现在,查询是在一个`level`上进行的,有一个我们正在寻找的`pos`位置作为附加参数: - +Block capabilities are used a bit differently because blocks without a block entity can have capabilities as well. +The query is now performed on a `level`, with the `pos`ition that we are looking for as an additional parameter: ```java var object = level.getCapability(CAP, pos, context); if (object != null) { - // 使用object + // Use object } ``` -如果已知方块实体和/或方块状态,可以传递它们以节省查询时间: - +If the block entity and/or the block state is known, they can be passed to save on query time: ```java var object = level.getCapability(CAP, pos, blockState, blockEntity, context); if (object != null) { - // 使用object + // Use object } ``` -为了给出一个更具体的示例,以下是如何从`Direction.NORTH`侧查询方块的`IItemHandler`功能性: - +To give a more concrete example, here is how one might query an `IItemHandler` capability for a block, from the `Direction.NORTH` side: ```java IItemHandler handler = level.getCapability(Capabilities.ItemHandler.BLOCK, pos, Direction.NORTH); if (handler != null) { - // 使用handler进行某些物品相关操作。 + // Use the handler for some item-related operation. } ``` -当查询某个功能性时,系统会在后台执行以下步骤: -1. 如果它们没有被提供的话,获取方块实体和方块状态。 -2. 获取注册的功能性提供者。(下文会有更多相关信息) -3. 遍历提供者并询问他们是否能提供该功能性。 -4. 提供者中的一个将返回功能性实例,可能会分配一个新对象。 +## Block capability caching +When a capability is looked up, the system will perform the following steps under the hood: +1. Fetch block entity and block state if they were not supplied. +2. Fetch registered capability providers. (More on this below). +3. Iterate the providers and ask them if they can provide the capability. +4. One of the providers will return a capability instance, potentially allocating a new object. -尽管实现相当高效,但对于频繁进行的查询,例如每个游戏刻,这些步骤可能会占用大量服务器时间。`BlockCapabilityCache` 系统为在特定位置频繁查询的能力提供了巨大的速度提升。 +The implementation is rather efficient, but for queries that are performed frequently, +for example every game tick, these steps can take a significant amount of server time. +The `BlockCapabilityCache` system provides a dramatic speedup for capabilities that are frequently queried at a given position. :::tip -通常,`BlockCapabilityCache` 会被创建一次,然后存储在执行频繁功能性查询的对象的字段中。何时何地存储缓存取决于您。 +Generally, a `BlockCapabilityCache` will be created once and then stored in a field of the object performing frequent capability queries. +When and where exactly you store the cache is up to you. ::: -要创建缓存,请使用要查询的功能性,级别,位置和查询上下文调用 `BlockCapabilityCache.create`。 +To create a cache, call `BlockCapabilityCache.create` with the capability to query, the level, the position, and the query context. ```java -// 声明字段: +// Declare the field: private BlockCapabilityCache capCache; -// 稍后,例如在方块实体的 `onLoad` 中: +// Later, for example in `onLoad` for a block entity: this.capCache = BlockCapabilityCache.create( - Capabilities.ItemHandler.BLOCK, // 要缓存的功能性 - level, // 世界级别 - pos, // 目标位置 - Direction.NORTH // 上下文 + Capabilities.ItemHandler.BLOCK, // capability to cache + level, // level + pos, // target position + Direction.NORTH // context ); ``` -然后通过 `getCapability()` 查询缓存: +Querying the cache is then done with `getCapability()`: ```java IItemHandler handler = this.capCache.getCapability(); if (handler != null) { - // 对某些与物品相关的操作使用 handler。 + // Use the handler for some item-related operation. } ``` -**缓存会被垃圾收集器自动清除,无需注销。** +**The cache is automatically cleared by the garbage collector, there is no need to unregister it.** -也可以接收到功能性对象变更的通知!这包括功能性变化(`oldHandler != newHandler`)、变得不可用(`null`)或再次变得可用(不再是 `null`)。 +It is also possible to receive notifications when the capability object changes! +This includes capabilities changing (`oldHandler != newHandler`), becoming unavailable (`null`) or becoming available again (not `null` anymore). -创建缓存时需要两个额外的参数: -- 一个有效性检查,用于确定缓存是否仍然有效。 -在作为方块实体字段的最简单用法中,`() -> !this.isRemoved()` 就可以了。 -- 一个失效监听器,当功能性改变时被调用。 -这是您可以对功能性变更、移除或出现做出反应的地方。 +The cache then needs to be created with two additional parameters: +- A validity check, that is used to determine if the cache is still valid. +In the simplest usage as a block entity field, `() -> !this.isRemoved()` will do. +- An invalidation listener, that is called when the capability changes. +This is where you can react to capability changes, removals, or appearances. ```java -// 带有可选的失效监听器: +// With optional invalidation listener: this.capCache = BlockCapabilityCache.create( - Capabilities.ItemHandler.BLOCK, // 要缓存的功能性 - level, // 世界级别 - pos, // 目标位置 - Direction.NORTH, // 上下文 - () -> !this.isRemoved(), // 有效性检查(因为缓存可能会比它所属的对象更久存在) - () -> onCapInvalidate() // 失效监听器 + Capabilities.ItemHandler.BLOCK, // capability to cache + level, // level + pos, // target position + Direction.NORTH, // context + () -> !this.isRemoved(), // validity check (because the cache might outlive the object it belongs to) + () -> onCapInvalidate() // invalidation listener ); ``` -## 方块功能性失效 +## Block capability invalidation :::info -失效功能是专门针对方块功能性的。实体和物品堆叠的功能性不能被缓存,因此不需要失效处理。 +Invalidation is exclusive to block capabilities. Entity and item stack capabilities cannot be cached and do not need to be invalidated. ::: -为了确保缓存可以正确更新它们存储的功能性,**模组开发者必须在功能性改变、出现或消失时调用 `level.invalidateCapabilities(pos)`**。 +To make sure that caches can correctly update their stored capability, **modders must call `level.invalidateCapabilities(pos)` whenever a capability changes, appears, or disappears**. ```java -// 每当一个功能性改变、出现或消失时: +// whenever a capability changes, appears, or disappears: level.invalidateCapabilities(pos); ``` -NeoForge已经处理了常见情况,例如区块的加载/卸载和方块实体的创建/移除,但其他情况需要模组开发者明确处理。例如,模组开发者必须在以下情况中使功能性失效: -- 如果先前返回的功能性不再有效。 -- 如果放置或状态变化的功能性提供方块(没有方块实体),通过覆写 `onPlace`。 -- 如果移除的功能性提供方块(没有方块实体),通过覆写 `onRemove`。 +NeoForge already handles common cases such as chunk load/unloads and block entity creation/removal, +but other cases need to be handled explicitly by modders. +For example, modders must invalidate capabilities in the following cases: +- If a previously returned capability is no longer valid. +- If a capability-providing block (without a block entity) is placed or changes state, by overriding `onPlace`. +- If a capability-providing block (without a block entity) is removed, by overriding `onRemove`. -对于一个简单的方块示例,参考 `ComposterBlock.java` 文件。 +For a plain block example, refer to the `ComposterBlock.java` file. -更多信息,请参考 [`IBlockCapabilityProvider`][block-cap-provider] 的 javadoc。 +For more information, refer to the javadoc of [`IBlockCapabilityProvider`][block-cap-provider]. -## 注册功能性 -功能性*提供者*是最终提供功能性的东西。功能性提供者是一个函数,可以返回一个功能性实例,或者如果不能提供功能性,就返回 `null`。提供者特定于: -- 它们为之提供服务的给定功能性,以及 -- 它们为之提供服务的方块实例、方块实体类型、实体类型或物品实例。 +## Registering capabilities +A capability _provider_ is what ultimately supplies a capability. +A capability provider is function that can either return a capability instance, or `null` if it cannot provide the capability. +Providers are specific to: +- the given capability that they are providing for, and +- the block instance, block entity type, entity type, or item instance that they are providing for. -它们需要在 `RegisterCapabilitiesEvent` 中注册。 +They need to be registered in the `RegisterCapabilitiesEvent`. -方块提供者使用 `registerBlock` 进行注册。例如: +Block providers are registered with `registerBlock`. For example: ```java private static void registerCapabilities(RegisterCapabilitiesEvent event) { event.registerBlock( - Capabilities.ItemHandler.BLOCK, // 注册的功能性 - (level, pos, state, be, side) -> <返回 IItemHandler>, - // 注册的方块 + Capabilities.ItemHandler.BLOCK, // capability to register for + (level, pos, state, be, side) -> , + // blocks to register for MY_ITEM_HANDLER_BLOCK, MY_OTHER_ITEM_HANDLER_BLOCK); } ``` -通常,注册将特定于一些方块实体类型,因此提供了 `registerBlockEntity` 辅助方法: +In general, registration will be specific to some block entity types, so the `registerBlockEntity` helper method is provided as well: ```java event.registerBlockEntity( - Capabilities.ItemHandler.BLOCK, // 注册的功能性 - MY_BLOCK_ENTITY_TYPE, // 注册的方块实体类型 - (myBlockEntity, side) -> <为 myBlockEntity 和 side 返回 IItemHandler>); + Capabilities.ItemHandler.BLOCK, // capability to register for + MY_BLOCK_ENTITY_TYPE, // block entity type to register for + (myBlockEntity, side) -> ); ``` -如果之前由方块或方块实体提供者返回的功能性不再有效,**您必须通过调用 `level.invalidateCapabilities(pos)` 来使缓存失效**。有关更多信息,请参考上文的[失效部分][invalidation]。 +:::danger +If the capability previously returned by a block or block entity provider is no longer valid, +**you must invalidate the caches** by calling `level.invalidateCapabilities(pos)`. +Refer to the [invalidation section][invalidation] above for more information. +::: -实体的注册类似,使用 `registerEntity`: +Entity registration is similar, using `registerEntity`: ```java event.registerEntity( - Capabilities.ItemHandler.ENTITY, // 要注册的功能性 - MY_ENTITY_TYPE, // 要注册的实体类型 - (myEntity, context) -> <返回 myEntity 的 IItemHandler>); + Capabilities.ItemHandler.ENTITY, // capability to register for + MY_ENTITY_TYPE, // entity type to register for + (myEntity, context) -> ); ``` -物品的注册也类似。注意,提供者接收堆叠: +Item registration is similar too. Note that the provider receives the stack: ```java event.registerItem( - Capabilities.ItemHandler.ITEM, // 要注册的功能性 - (itemStack, context) -> <返回 itemStack 的 IItemHandler>, - // 要注册的物品 + Capabilities.ItemHandler.ITEM, // capability to register for + (itemStack, context) -> , + // items to register for MY_ITEM, MY_OTHER_ITEM); ``` -## 为所有对象注册功能性 +## Registering capabilities for all objects -如果由于某种原因您需要为所有方块、实体或物品注册一个提供者,您将需要遍历相应的注册表并为每个对象注册提供者。 +If for some reason you need to register a provider for all blocks, entities, or items, +you will need to iterate the corresponding registry and register the provider for each object. -例如,NeoForge使用这个系统为所有的 `BucketItem`(不包括子类)注册一个流体处理器功能性: +For example, NeoForge uses this system to register a fluid handler capability for all `BucketItem`s (excluding subclasses): ```java -// 作为参考,您可以在 `CapabilityHooks` 类中找到这段代码。 +// For reference, you can find this code in the `CapabilityHooks` class. for (Item item : BuiltInRegistries.ITEM) { if (item.getClass() == BucketItem.class) { event.registerItem(Capabilities.FluidHandler.ITEM, (stack, ctx) -> new FluidBucketWrapper(stack), item); @@ -283,17 +301,20 @@ for (Item item : BuiltInRegistries.ITEM) { } ``` -按照注册的顺序请求提供者提供功能性。如果您想在NeoForge已经为您的某个对象注册的提供者之前运行,请使用更高优先级注册您的 `RegisterCapabilitiesEvent` 处理器。例如: +Providers are asked for a capability in the order that they are registered. +Should you want to run before a provider that NeoForge already registers for one of your objects, +register your `RegisterCapabilitiesEvent` handler with a higher priority. +For example: ```java modBus.addListener(RegisterCapabilitiesEvent.class, event -> { event.registerItem( Capabilities.FluidHandler.ITEM, (stack, ctx) -> new MyCustomFluidBucketWrapper(stack), - // 要注册的方块 + // blocks to register for MY_CUSTOM_BUCKET); -}, EventPriority.HIGH); // 使用 HIGH 优先级在 NeoForge 之前注册! +}, EventPriority.HIGH); // use HIGH priority to register before NeoForge! ``` -查看 [`CapabilityHooks`][capability-hooks] 以获取 NeoForge 本身注册的提供者列表。 +See [`CapabilityHooks`][capability-hooks] for a list of the providers registered by NeoForge itself. [block-cap-provider]: https://github.com/neoforged/NeoForge/blob/1.20.x/src/main/java/net/neoforged/neoforge/capabilities/IBlockCapabilityProvider.java [capability-hooks]: https://github.com/neoforged/NeoForge/blob/1.20.x/src/main/java/net/neoforged/neoforge/capabilities/CapabilityHooks.java diff --git a/docs/datastorage/codecs.md b/docs/datastorage/codecs.md index 4009381bb..0d08b3dd6 100644 --- a/docs/datastorage/codecs.md +++ b/docs/datastorage/codecs.md @@ -1,126 +1,125 @@ -# 编解码器 +# Codecs -编解码器是 Mojang 的 [DataFixerUpper] 库中的一种序列化工具,用于描述对象在不同格式之间的转换方式,如将对象从 `JsonElement` 的 JSON 格式转换为 NBT 的 `Tag` 格式。 +Codecs are a serialization tool from Mojang's [DataFixerUpper] used to describe how objects can be transformed between different formats, such as `JsonElement`s for JSON and `Tag`s for NBT. -## 使用编解码器 +## Using Codecs -编解码器主要用于将 Java 对象编码(或序列化)成某种数据格式,并将格式化的数据对象解码(或反序列化)回其关联的 Java 类型。这通常通过 `Codec#encodeStart` 和 `Codec#parse` 来实现。 +Codecs are primarily used to encode, or serialize, Java objects to some data format type and decode, or deserialize, formatted data objects back to its associated Java type. This is typically accomplished using `Codec#encodeStart` and `Codec#parse`, respectively. -### 动态操作 +### DynamicOps -为了确定将数据编码和解码至哪种中间文件格式,`#encodeStart` 和 `#parse` 都需要一个 `DynamicOps` 实例来定义该格式中的数据。 +To determine what intermediate file format to encode and decode to, both `#encodeStart` and `#parse` require a `DynamicOps` instance to define the data within that format. -[DataFixerUpper] 库包含了 `JsonOps`,用于对存储在 [`Gson`][gson] 的 `JsonElement` 实例中的 JSON 数据进行编解码。`JsonOps` 支持两种 `JsonElement` 序列化版本:`JsonOps#INSTANCE` 定义了标准的 JSON 文件,而 `JsonOps#COMPRESSED` 允许将数据压缩成单一字符串。 +The [DataFixerUpper] library contains `JsonOps` to codec JSON data stored in [`Gson`'s][gson] `JsonElement` instances. `JsonOps` supports two versions of `JsonElement` serialization: `JsonOps#INSTANCE` which defines a standard JSON file, and `JsonOps#COMPRESSED` which allows data to be compressed into a single string. ```java -// 假设 exampleCodec 代表一个 Codec -// 假设 exampleObject 为一个 ExampleJavaObject -// 假设 exampleJson 为一个 JsonElement +// Let exampleCodec represent a Codec +// Let exampleObject be a ExampleJavaObject +// Let exampleJson be a JsonElement -// 将 Java 对象编码为常规 JsonElement +// Encode Java object to regular JsonElement exampleCodec.encodeStart(JsonOps.INSTANCE, exampleObject); -// 将 Java 对象编码为压缩的 JsonElement +// Encode Java object to compressed JsonElement exampleCodec.encodeStart(JsonOps.COMPRESSED, exampleObject); -// 将 JsonElement 解码为 Java 对象 -// 假设 JsonElement 是正常解析的 +// Decode JsonElement into Java object +// Assume JsonElement was parsed normally exampleCodec.parse(JsonOps.INSTANCE, exampleJson); ``` -Minecraft 还提供了 `NbtOps` 用于对存储在 `Tag` 实例中的 NBT 数据进行编解码。可以通过 `NbtOps#INSTANCE` 来引用。 +Minecraft also provides `NbtOps` to codec NBT data stored in `Tag` instances. This can be referenced using `NbtOps#INSTANCE`. ```java -// 假设 exampleCodec 代表一个 Codec -// 假设 exampleObject 为一个 ExampleJavaObject -// 假设 exampleNbt 为一个 Tag +// Let exampleCodec represent a Codec +// Let exampleObject be a ExampleJavaObject +// Let exampleNbt be a Tag -// 将 Java 对象编码为 Tag +// Encode Java object to Tag exampleCodec.encodeStart(JsonOps.INSTANCE, exampleObject); -// 将 Tag 解码为 Java 对象 +// Decode Tag into Java object exampleCodec.parse(JsonOps.INSTANCE, exampleNbt); ``` -#### 格式转换 +#### Format Conversion -`DynamicOps` 还可以单独用来在两种不同的编码格式之间转换。这可以通过使用 `#convertTo` 并提供 `DynamicOps` 格式和要转换的编码对象来完成。 +`DynamicOps` can also be used separately to convert between two different encoded formats. This can be done using `#convertTo` and supplying the `DynamicOps` format and the encoded object to convert. ```java -// 将 Tag 转换为 JsonElement -// 假设 exampleTag 为一个 Tag +// Convert Tag to JsonElement +// Let exampleTag be a Tag JsonElement convertedJson = NbtOps.INSTANCE.convertTo(JsonOps.INSTANCE, exampleTag); ``` -### 数据结果 +### DataResult -使用编解码器编码或解码数据时返回的 `DataResult` 将根据转换是否成功,持有转换后的实例或一些错误数据。当转换成功时,由 `#result` 提供的 `Optional` 将包含成功转换的对象。如果转换失败,由 `#error` 提供的 `Optional` 将包含 `PartialResult`,后者持有错误消息和根据编解码器部分转换的对象。 +Encoded or decoded data using codecs return a `DataResult` which holds the converted instance or some error data depending on whether the conversion was successful. When the conversion is successful, the `Optional` supplied by `#result` will contain the successfully converted object. If the conversion fails, the `Optional` supplied by `#error` will contain the `PartialResult`, which holds the error message and a partially converted object depending on the codec. -此外,`DataResult` 上有许多方法可以用来将结果或错误转换为所需格式。例如,`#resultOrPartial` 将返回一个 `Optional`,在成功时包含结果,在失败时包含部分转换的对象。此方法接受一个字符串消费者以确定如何报告错误消息(如果存在)。 +Additionally, there are many methods on `DataResult` that can be used to transform the result or error into the desired format. For example, `#resultOrPartial` will return an `Optional` containing the result on success, and the partially converted object on failure. The method takes in a string consumer to determine how to report the error message if present. ```java -// 假设 exampleCodec 代表一个 Codec -// 假设 exampleJson 为一个 JsonElement +// Let exampleCodec represent a Codec +// Let exampleJson be a JsonElement -// 将 JsonElement 解码为 Java 对象 +// Decode JsonElement into Java object DataResult result = exampleCodec.parse(JsonOps.INSTANCE, exampleJson); result - // 获取结果或部分错误时的错误消息 - .resultOrPartial(errorMessage -> /* 处理错误消息 */) - // 如果结果或部分存在,则进行某些操作 - .ifPresent(decodedObject - - -> /* 处理解码对象 */); + // Get result or partial on error, report error message + .resultOrPartial(errorMessage -> /* Do something with error message */) + // If result or partial is present, do something + .ifPresent(decodedObject -> /* Do something with decoded object */); ``` -## 现有编解码器 +## Existing Codecs + +### Primitives -### 原始类型 +The `Codec` class contains static instances of codecs for certain defined primitives. -`Codec` 类包含了一些定义的原始类型的静态编解码器实例。 +Codec | Java Type +:---: | :--- +`BOOL` | `Boolean` +`BYTE` | `Byte` +`SHORT` | `Short` +`INT` | `Integer` +`LONG` | `Long` +`FLOAT` | `Float` +`DOUBLE` | `Double` +`STRING` | `String` +`BYTE_BUFFER` | `ByteBuffer` +`INT_STREAM` | `IntStream` +`LONG_STREAM` | `LongStream` +`PASSTHROUGH` | `Dynamic`\* +`EMPTY` | `Unit`\*\* -| 编解码器 | Java 类型 | -|------------|--------| -| `BOOL` | `Boolean` | -| `BYTE` | `Byte` | -| `SHORT` | `Short` | -| `INT` | `Integer` | -| `LONG` | `Long` | -| `FLOAT` | `Float` | -| `DOUBLE` | `Double` | -| `STRING` | `String` | -| `BYTE_BUFFER` | `ByteBuffer` | -| `INT_STREAM` | `IntStream` | -| `LONG_STREAM` | `LongStream` | -| `PASSTHROUGH` | `Dynamic` | -| `EMPTY` | `Unit` | +\* `Dynamic` is an object which holds a value encoded in a supported `DynamicOps` format. These are typically used to convert encoded object formats into other encoded object formats. -* `Dynamic` 是一个在支持的 `DynamicOps` 格式中编码值的对象。这些通常用于将编码对象格式转换为其他编码对象格式。 -* `Unit` 是用于表示 `null` 对象的对象。 +\*\* `Unit` is an object used to represent `null` objects. -### 原版和 Forge +### Vanilla and Forge -Minecraft 和 Forge 定义了许多常见对象的编解码器。一些示例包括用于 `ResourceLocation` 的 `ResourceLocation#CODEC`,用于 `DateTimeFormatter#ISO_INSTANT` 格式的 `Instant` 的 `ExtraCodecs#INSTANT_ISO8601`,以及用于 `CompoundTag` 的 `CompoundTag#CODEC`。 +Minecraft and Forge define many codecs for objects that are frequently encoded and decoded. Some examples include `ResourceLocation#CODEC` for `ResourceLocation`s, `ExtraCodecs#INSTANT_ISO8601` for `Instant`s in the `DateTimeFormatter#ISO_INSTANT` format, and `CompoundTag#CODEC` for `CompoundTag`s. -:::警告 -使用 `JsonOps` 的 `CompoundTag` 不能从 JSON 解码数字列表。`JsonOps` 在转换时会将数字设置为其最窄类型。`ListTag` 强制其数据使用特定类型,因此不同类型的数字(例如 `64` 会是 `byte`,`384` 会是 `short`)在转换时会引发错误。 +:::caution +`CompoundTag`s cannot decode lists of numbers from JSON using `JsonOps`. `JsonOps`, when converting, sets a number to its most narrow type. `ListTag`s force a specific type for its data, so numbers with different types (e.g. `64` would be `byte`, `384` would be `short`) will throw an error on conversion. ::: -原版和 Forge 注册也有针对注册表所包含对象类型的编解码器(例如 `Registry#BLOCK` 或 `ForgeRegistries#BLOCKS` 有一个 `Codec`)。`Registry#byNameCodec` 和 `IForgeRegistry#getCodec` 会将注册表对象编码为其注册名,或者如果压缩,则为整数标识符。原版注册表还有一个 `Registry#holderByNameCodec`,它将编码为注册名并解码为被 `Holder` 包装的注册表对象。 +Vanilla and Forge registries also have codecs for the type of object the registry contains (e.g. `Registry#BLOCK` or `ForgeRegistries#BLOCKS` have a `Codec`). `Registry#byNameCodec` and `IForgeRegistry#getCodec` will encode the registry object to their registry name, or an integer identifier if compressed. Vanilla registries also have a `Registry#holderByNameCodec` which encodes to a registry name and decodes to the registry object wrapped in a `Holder`. -## 创建编解码器 +## Creating Codecs -可以为任何对象创建编解码器。为了便于理解,将显示等效的编码 JSON。 +Codecs can be created for encoding and decoding any object. For understanding purposes, the equivalent encoded JSON will be shown. -### 记录 +### Records -编解码器可以通过使用记录来定义对象。每个记录编解码器定义了具有明确命名字段的任何对象。创建记录编解码器的方法有很多,但最简单的是通过 `RecordCodecBuilder#create`。 +Codecs can define objects through the use of records. Each record codec defines any object with explicit named fields. There are many ways to create a record codec, but the simplest is via `RecordCodecBuilder#create`. -`RecordCodecBuilder#create` 接受一个函数,该函数定义了一个 `Instance` 并返回一个应用(`App`)到构建对象的对象。这可以与创建类*实例*和用于*应用*类的构造函数联系起来。 +`RecordCodecBuilder#create` takes in a function which defines an `Instance` and returns an application (`App`) of the object. A correlation can be drawn to creating a class *instance* and the constructors used to *apply* the class to the constructed object. ```java -// 一个需要创建编解码器的对象 +// Some object to create a codec for public class SomeObject { public SomeObject(String s, int i, boolean b) { /* ... */ } @@ -133,172 +132,168 @@ public class SomeObject { } ``` -#### 字段 - -`Instance` 可以使用 `#group` 定义多达 16 个字段。每个字段必须是一个定义了对象被制造的实例及对象类型的应用。满足此要求的最简单方式是使用 `Codec`,设置字段的解码名称,并设置用于编码字段的 getter。 +#### Fields -字段可以使用 `#fieldOf` 从 `Codec` 创建,如果字段是必需的,或使用 `#optionalFieldOf` 创建,如果字段被包装在 `Optional` 中或默认存在。任一方法都需要包含编码对象中字段名称的字符串。然后可以使用 `#forGetter` 设置用于编码字段的 getter,它接受一个函数,该函数给定对象,返回字段数据。 +An `Instance` can define up to 16 fields using `#group`. Each field must be an application defining the instance the object is being made for and the type of the object. The simplest way to meet this requirement is by taking a `Codec`, setting the name of the field to decode from, and setting the getter used to encode the field. -从那里 +A field can be created from a `Codec` using `#fieldOf`, if the field is required, or `#optionalFieldOf`, if the field is wrapped in an `Optional` or defaulted. Either method requires a string containing the name of the field in the encoded object. The getter used to encode the field can then be set using `#forGetter`, taking in a function which given the object, returns the field data. -,生成的产品可以通过 `#apply` 应用,以定义如何为应用构建对象。为了方便起见,分组字段应按照它们在构造函数中出现的顺序列出,以便函数可以简单地是一个构造函数方法引用。 +From there, the resulting product can be applied via `#apply` to define how the instance should construct the object for the application. For ease of convenience, the grouped fields should be listed in the same order they appear in the constructor such that the function can simply be a constructor method reference. ```java -public static final Codec RECORD_CODEC = RecordCodecBuilder.create(instance -> // 给定一个实例 - instance.group( // 在实例中定义字段 - Codec.STRING.fieldOf("s").forGetter(SomeObject::s), // 字符串 - Codec.INT.optionalFieldOf("i", 0).forGetter(SomeObject::i), // 整数,默认为 0(如果字段不存在) - Codec.BOOL.fieldOf("b").forGetter(SomeObject::b) // 布尔 - ).apply(instance, SomeObject::new) // 定义如何创建对象 +public static final Codec RECORD_CODEC = RecordCodecBuilder.create(instance -> // Given an instance + instance.group( // Define the fields within the instance + Codec.STRING.fieldOf("s").forGetter(SomeObject::s), // String + Codec.INT.optionalFieldOf("i", 0).forGetter(SomeObject::i), // Integer, defaults to 0 if field not present + Codec.BOOL.fieldOf("b").forGetter(SomeObject::b) // Boolean + ).apply(instance, SomeObject::new) // Define how to create the object ); ``` ```js -// 编码后的 SomeObject +// Encoded SomeObject { "s": "value", "i": 5, "b": false } -// 另一个编码后的 SomeObject +// Another encoded SomeObject { "s": "value2", - // i 被省略,默认为 0 + // i is omitted, defaults to 0 "b": true } ``` -### 转换器 +### Transformers -编解码器可以通过映射方法转换成等效或部分等效的表现形式。每个映射方法接收两个函数:一个用于将当前类型转换为新类型,另一个用于将新类型转换回当前类型。这是通过 `#xmap` 函数完成的。 +Codecs can be transformed into equivalent, or partially equivalent, representations through mapping methods. Each mapping method takes in two functions: one to transform the current type into the new type, and one to transform the new type back to the current type. This is done through the `#xmap` function. ```java -// 一个类 +// A class public class ClassA { public ClassB toB() { /* ... */ } } -// 另一个等效的类 +// Another equivalent class public class ClassB { public ClassA toA() { /* ... */ } } -// 假设存在某个编解码器 A_CODEC +// Assume there is some codec A_CODEC public static final Codec B_CODEC = A_CODEC.xmap(ClassA::toB, ClassB::toA); ``` -如果类型部分等效,即转换过程中存在某些限制,则存在返回 `DataResult` 的映射函数,可用于在遇到异常或无效状态时返回错误状态。 +If a type is partially equivalent, meaning that there are some restrictions during conversion, there are mapping functions which return a `DataResult` which can be used to return an error state whenever an exception or invalid state is reached. -是否 A 完全等同于 B | 是否 B 完全等同于 A | 转换方法 -:---: | :---: | :--- -是 | 是 | `#xmap` -是 | 否 | `#flatComapMap` -否 | 是 | `#comapFlatMap` -否 | 否 | `#flatXMap` +Is A Fully Equivalent to B | Is B Fully Equivalent to A | Transform Method +:---: | :---: | :--- +Yes | Yes | `#xmap` +Yes | No | `#flatComapMap` +No | Yes | `#comapFlatMap` +No | No | `#flatXMap` ```java -// 给定一个字符串编解码器转换为整数 -// 并非所有字符串都可以变成整数(A 与 B 非完全等效) -// 所有整数都可以变成字符串(B 与 A 完全等效) +// Given an string codec to convert to a integer +// Not all strings can become integers (A is not fully equivalent to B) +// All integers can become strings (B is fully equivalent to A) public static final Codec INT_CODEC = Codec.STRING.comapFlatMap( - s -> { // 返回失败时包含错误的数据结果 + s -> { // Return data result containing error on failure try { return DataResult.success(Integer.valueOf(s)); } catch (NumberFormatException e) { - return DataResult.error(s + " 不是一个整数。"); + return DataResult.error(s + " is not an integer."); } }, - Integer::toString // 常规函数 + Integer::toString // Regular function ); ``` ```js -// 将返回 5 +// Will return 5 "5" -// 将错误,不是整数 +// Will error, not an integer "value" ``` -#### 范围编解码器 +#### Range Codecs -范围编解码器是 `#flatXMap` 的实现,如果值不在设定的最小值和最大值之间,则返回错误的 `DataResult`。如果值超出范围,仍会提供部分结果。分别有整数、浮点和双精度通过 `#intRange`、`#floatRange` 和 `#doubleRange` 实现。 +Range codecs are an implementation of `#flatXMap` which returns an error `DataResult` if the value is not inclusively between the set minimum and maximum. The value is still provided as a partial result if outside the bounds. There are implementations for integers, floats, and doubles via `#intRange`, `#floatRange`, and `#doubleRange` respectively. ```java -public static final Codec RANGE_CODEC = Codec.intRange(0, 4); +public static final Codec RANGE_CODEC = Codec.intRange(0, 4); ``` ```js -// 将有效,在 [0, 4] 内 +// Will be valid, inside [0, 4] 4 -// 将错误,在 [0, 4] 外 +// Will error, outside [0, 4] 5 ``` -### 默认值 +### Defaults -如果编码或解码的结果失败,可以通过 `Codec#orElse` 或 `Codec#orElseGet` 提供默认值。 +If the result of encoding or decoding fails, a default value can be supplied instead via `Codec#orElse` or `Codec#orElseGet`. ```java -public static final Codec DEFAULT_CODEC = Codec.INT.orElse(0); // 也可以通过 #orElseGet 提供值 +public static final Codec DEFAULT_CODEC = Codec.INT.orElse(0); // Can also be a supplied value via #orElseGet ``` ```js -// 不是整数,默认为 0 +// Not an integer, defaults to 0 "value" ``` -### 单位 +### Unit -一个编解码器,提供代码中的值并不编码任何东西,可以使用 `Codec#unit` 表示。如果编解码器在数据对象中使用了一个不可编码的条目,这非常有用。 +A codec which supplies an in-code value and encodes to nothing can be represented using `Codec#unit`. This is useful if a codec uses a non-encodable entry within the data object. ```java public static final Codec> UNIT_CODEC = Codec.unit( - () -> ForgeRegistries.BLOCKS // 也可以是原始值 + () -> ForgeRegistries.BLOCKS // Can also be a raw value ); ``` ```js -// 这里没有任何内容,将返回方块注册表编解码器 +// Nothing here, will return block registry codec ``` -### 列表 +### List -可以从对象编解码器生成一个对象列表的编解码器,通过 `Codec#listOf` 实现。 +A codec for a list of objects can be generated from an object codec via `Codec#listOf`. ```java -// BlockPos#CODEC 是一个 Codec +// BlockPos#CODEC is a Codec public static final Codec> LIST_CODEC = BlockPos.CODEC.listOf(); ``` ```js -// 编码的 List +// Encoded List [ [1, 2, 3], // BlockPos(1, 2, 3) [4, 5, 6], // BlockPos(4, 5, 6) [7, 8, 9] // BlockPos(7, 8, 9) ] -`` - -` +``` -使用列表编解码器解码的列表对象存储在一个**不可变**列表中。如果需要可变列表,则应该对列表编解码器应用[变换器]。 +List objects decoded using a list codec are stored in an **immutable** list. If a mutable list is needed, a [transformer] should be applied to the list codec. -### 映射 +### Map -可以通过两个编解码器生成键和值对象映射的编解码器,通过 `Codec#unboundedMap` 实现。无界映射可以指定任何基于字符串的或转换为字符串的值作为键。 +A codec for a map of keys and value objects can be generated from two codecs via `Codec#unboundedMap`. Unbounded maps can specify any string-based or string-transformed value to be a key. ```java -// BlockPos#CODEC 是一个 Codec +// BlockPos#CODEC is a Codec public static final Codec> MAP_CODEC = Codec.unboundedMap(Codec.STRING, BlockPos.CODEC); ``` ```js -// 编码的 Map +// Encoded Map { "key1": [1, 2, 3], // key1 -> BlockPos(1, 2, 3) "key2": [4, 5, 6], // key2 -> BlockPos(4, 5, 6) @@ -306,17 +301,17 @@ public static final Codec> MAP_CODEC = Codec.unboundedMap( } ``` -使用无界映射编解码器解码的映射对象存储在一个**不可变**映射中。如果需要可变映射,则应对映射编解码器应用[变换器]。 +Map objects decoded using a unbounded map codec are stored in an **immutable** map. If a mutable map is needed, a [transformer] should be applied to the map codec. -:::警告 -无界映射只支持可以编码/解码为字符串的键。可以使用键值[对]列表编解码器来绕过此限制。 +:::caution +Unbounded maps only support keys that encode/decode to/from strings. A key-value [pair] list codec can be used to get around this restriction. ::: -### 对 +### Pair -可以通过两个编解码器生成对象对的编解码器,通过 `Codec#pair` 实现。 +A codec for pairs of objects can be generated from two codecs via `Codec#pair`. -对编解码器通过首先解码对中的左对象,然后取剩下的编码对象部分并从中解码右对象来解码对象。因此,编解码器必须在解码后表达关于编码对象的某些信息(如[记录]),或者必须被增强为 `MapCodec` 并通过 `#codec` 转换为常规编解码器。这通常可以通过将编解码器作为某个对象的[字段]来实现。 +A pair codec decodes objects by first decoding the left object in the pair, then taking the remaining part of the encoded object and decodes the right object from that. As such, the codecs must either express something about the encoded object after decoding (such as [records]), or they have to be augmented into a `MapCodec` and transformed into a regular codec via `#codec`. This can typically done by making the codec a [field] of some object. ```java public static final Codec> PAIR_CODEC = Codec.pair( @@ -326,22 +321,22 @@ public static final Codec> PAIR_CODEC = Codec.pair( ``` ```js -// 编码的 Pair +// Encoded Pair { - "left": 5, // fieldOf 查找左对象的 'left' 键 - "right": "value" // fieldOf 查找右对象的 'right' 键 + "left": 5, // fieldOf looks up 'left' key for left object + "right": "value" // fieldOf looks up 'right' key for right object } ``` -:::tips -可以使用非字符串键的映射编解码器通过应用带有[变换器]的键值对列表来编码/解码。 +:::tip +A map codec with a non-string key can be encoded/decoded using a list of key-value pairs applied with a [transformer]. ::: -### Either 编解码器 +### Either -可以通过两个编解码器生成一个针对某个对象数据的两种不同编解码方法的编解码器,使用 `Codec#either` 实现。 +A codec for two different methods of encoding/decoding some object data can be generated from two codecs via `Codec#either`. -Either 编解码器首先尝试使用第一个编解码器解码对象。如果失败,它将尝试使用第二个编解码器。如果第二次也失败,那么 `DataResult` 将只包含第二次编解码器失败的错误。 +An either codec attempts to decode the object using the first codec. If it fails, it attempts to decode using the second codec. If that also fails, then the `DataResult` will only contain the error from the second codec failure. ```java public static final Codec> EITHER_CODEC = Codec.either( @@ -351,32 +346,32 @@ public static final Codec> EITHER_CODEC = Codec.either( ``` ```js -// 编码 Either$Left +// Encoded Either$Left 5 -// 编码 Either$Right +// Encoded Either$Right "value" ``` -:::tips -这可以与[变换器]结合使用,从两种不同的编码方法中获取特定对象。 +:::tip +This can be used in conjunction with a [transformer] to get a specific object from two different methods of encoding. ::: -### 分派编解码器 +### Dispatch -编解码器可以拥有可以根据某些指定类型解码特定对象的子编解码器,通过 `Codec#dispatch` 实现。这通常用于包含编解码器的注册表,如规则测试或方块放置器。 +Codecs can have subcodecs which can decode a particular object based upon some specified type via `Codec#dispatch`. This is typically used in registries which contain codecs, such as rule tests or block placers. -分派编解码器首先尝试从某个字符串键(通常是 `type`)获取编码类型。从那里开始,解码类型,调用用于解码实际对象的特定编解码器的获取器。如果用于解码对象的 `DynamicOps` 压缩其映射,或者对象编解码器本身没有增强成 `MapCodec`(如记录或字段化原语),则对象需要存储在 `value` 键中。否则,对象将在与其余数据相同的级别上解码。 +A dispatch codec first attempts to get the encoded type from some string key (usually `type`). From there, the type is decoded, calling a getter for the specific codec used to decode the actual object. If the `DynamicOps` used to decode the object compresses its maps, or the object codec itself is not augmented into a `MapCodec` (such as records or fielded primitives), then the object needs to be stored within a `value` key. Otherwise, the object is decoded at the same level as the rest of the data. ```java -// 定义我们的对象 +// Define our object public abstract class ExampleObject { - // 定义用于指定编码对象类型的方法 + // Define the method used to specify the object type for encoding public abstract Codec type(); } -// 创建存储字符串的简单对象 +// Create simple object which stores a string public class StringObject extends ExampleObject { public StringObject(String s) { /* ... */ } @@ -384,14 +379,14 @@ public class StringObject extends ExampleObject { public String s() { /* ... */ } public Codec type() { - // 一个注册的注册表对象 + // A registered registry object // "string": // Codec.STRING.xmap(StringObject::new, StringObject::s) return STRING_OBJECT_CODEC.get(); } } -// 创建存储字符串和整数的复杂对象 +// Create complex object which stores a string and integer public class ComplexObject extends ExampleObject { public ComplexObject(String s, int i) { /* ... */ } @@ -401,7 +396,7 @@ public class ComplexObject extends ExampleObject { public int i() { /* ... */ } public Codec type() { - // 一个注册的注册表对象 + // A registered registry object // "complex": // RecordCodecBuilder.create(instance -> // instance.group( @@ -413,26 +408,26 @@ public class ComplexObject extends ExampleObject { } } -// 假设存在一个 IForgeRegistry> DISPATCH -public static final Codec = DISPATCH.getCodec() // 获取 Codec> +// Assume there is an IForgeRegistry> DISPATCH +public static final Codec = DISPATCH.getCodec() // Gets Codec> .dispatch( - ExampleObject::type, // 从特定对象获取编解码器 - Function.identity() // 从注册表获取编解码器 + ExampleObject::type, // Get the codec from the specific object + Function.identity() // Get the codec from the registry ); ``` ```js -// 简单对象 +// Simple object { - "type": "string", // 对于 StringObject - "value": "value" // 编解码器类型未从 MapCodec 增强,需要字段 + "type": "string", // For StringObject + "value": "value" // Codec type is not augmented from MapCodec, needs field } -// 复杂对象 +// Complex object { - "type": "complex", // 对于 ComplexObject + "type": "complex", // For ComplexObject - // 编解码器类型从 MapCodec 增强,可以内联 + // Codec type is augmented from MapCodec, can be inlined "s": "value", "i": 0 } diff --git a/docs/datastorage/nbt.md b/docs/datastorage/nbt.md index f46adb011..c2f583180 100644 --- a/docs/datastorage/nbt.md +++ b/docs/datastorage/nbt.md @@ -1,42 +1,42 @@ # Named Binary Tag (NBT) -NBT 是 Minecraft 最初时期由 Notch 本人引入的一种格式,它在整个 Minecraft 代码库中广泛用于数据存储。 +NBT is a format introduced in the earliest days of Minecraft, written by Notch himself. It is widely used throughout the Minecraft codebase for data storage. -## 规范 +## Specification -NBT 规范与 JSON 规范类似,但有一些区别: +The NBT spec is similar to the JSON spec, with a few differences: -- 存在字节、短整型、长整型和浮点型的明确类型,分别以 `b`、`s`、`l` 和 `f` 为后缀,类似于在 Java 代码中的表示方式。 - - 双精度浮点型也可以用 `d` 后缀,但这不是必需的,类似于 Java 代码中的可选 `i` 后缀不被允许。 - - 后缀不区分大小写。例如,`64b` 与 `64B` 相同,`0.5F` 与 `0.5f` 相同。 -- 布尔类型不存在,而是用字节表示。`true` 变为 `1b`,`false` 变为 `0b`。 - - 当前实现将所有非零值视为 `true`,因此 `2b` 也会被视为 `true`。 -- NBT 中不存在 `null` 的等效物。 -- 键周围的引号是可选的。所以 JSON 属性 `"duration": 20` 在 NBT 中可以表示为 `duration: 20` 或 `"duration": 20`。 -- 在 JSON 中被称为子对象的东西,在 NBT 中被称为**复合标签**(或简称复合)。 -- NBT 列表不能混合匹配类型,不同于 JSON。列表类型由第一个元素确定,或在代码中定义。 - - 然而,列表的列表可以混合匹配不同的列表类型。因此,一个列表包含两个列表,其中第一个是字符串列表,第二个是字节列表,是允许的。 -- 存在特殊的**数组**类型,它们不同于列表,但遵循包含元素在方括号中的模式。有三种数组类型: - - 字节数组,以 `B;` 开头。例如:`[B;0b,30b]` - - 整数数组,以 `I;` 开头。例如:`[I;0,-300]` - - 长整型数组,以 `L;` 开头。例如:`[L;0l,240l]` -- 列表、数组和复合标签中允许有尾随逗号。 +- Distinct types for bytes, shorts, longs and floats exist, suffixed by `b`, `s`, `l` and `f`, respectively, similar to how they would be represented in Java code. + - Doubles may also be suffixed with `d`, but this is not required, similar to Java code. The optional `i` suffix available in Java for integers is not permitted. + - The suffixes are not case-sensitive. So for example, `64b` is the same as `64B`, and `0.5F` is the same as `0.5f`. +- Booleans do not exist, they are instead represented by bytes. `true` becomes `1b`, `false` becomes `0b`. + - The current implementation treats all non-zero values as `true`, so `2b` would be treated as `true` as well. +- There is no `null` equivalent in NBT. +- Quotes around keys are optional. So a JSON property `"duration": 20` can become both `duration: 20` and `"duration": 20` in NBT. +- What is known in JSON as a sub-object is known in NBT as a **compound tag** (or just compound). +- NBT lists cannot mix and match types, unlike in JSON. The list type is determined by the first element, or defined in code. + - However, lists of lists can mix and match different list types. So a list of two lists, where the first one is a list of strings and the second one is a list of bytes, is allowed. +- There are special **array** types that are different from lists, but follow their scheme of containing elements in square brackets. There are three array types: + - Byte arrays, denoted by a `B;` at the beginning of the array. Example: `[B;0b,30b]` + - Integer arrays, denoted by a `I;` at the beginning of the array. Example: `[I;0,-300]` + - Long arrays, denoted by an `L;` at the beginning of the array. Example: `[L;0l,240l]` +- Trailing commas in lists, arrays and compound tags are allowed. -## NBT 文件 +## NBT Files -Minecraft 广泛使用 `.nbt` 文件,例如 [datapacks][datapack] 中的结构文件。包含区域内容(即一系列区块)的区域文件(`.mca`),以及游戏中不同位置使用的各种 `.dat` 文件,也是 NBT 文件。 +Minecraft uses `.nbt` files extensively, for example for structure files in [datapacks][datapack]. Region files (`.mca`) that contain the contents of a region (i.e. a collection of chunks), as well as the various `.dat` files used in different places by the game, are NBT files as well. -NBT 文件通常使用 GZip 压缩。因此,它们是二进制文件,不能直接编辑。 +NBT files are typically compressed with GZip. As such, they are binary files and cannot be edited directly. -## NBT 在代码中的使用 +## NBT in Code -与 JSON 类似,所有 NBT 对象都是封闭对象的子对象。让我们创建一个: +Like in JSON, all NBT objects are children of an enclosing object. So let's create one: ```java CompoundTag tag = new CompoundTag(); ``` -现在我们可以将数据放入该标签: +We can now put our data into that tag: ```java tag.putInt("Color", 0xffffff); @@ -44,9 +44,9 @@ tag.putString("Level", "minecraft:overworld"); tag.putDouble("IAmRunningOutOfIdeasForNamesHere", 1d); ``` -这里存在几个辅助方法,例如,`putIntArray` 也有一个便利方法,除了标准变体接受 `int[]` 外,还接受 `List`。 +Several helpers exist here, for example, `putIntArray` also has a convenience method that takes a `List` in addition to the standard variant that takes an `int[]`. -当然,我们也可以从该标签中获取值: +Of course, we can also get values from that tag: ```java int color = tag.getInt("Color"); @@ -54,45 +54,43 @@ String level = tag.getString("Level"); double d = tag.getDouble("IAmRunningOutOfIdeasForNamesHere"); ``` -如果不存在,数字类型将返回 0。字符串将返回 `""` 如果不存在。更复杂的类型(列表、数组、复合标签)如果不存在会抛出异常。 +Number types will return 0 if absent. Strings will return `""` if absent. More complex types (lists, arrays, compounds) will throw an exception if absent. -因此,我们 - -希望通过检查标签元素是否存在来进行防护: +As such, we want to safeguard by checking if a tag element exists: ```java boolean hasColor = tag.contains("Color"); boolean hasColorMoreExplicitly = tag.contains("Color", Tag.TAG_INT); ``` -`TAG_INT` 常量在 `Tag` 中定义,这是所有标签类型的超接口。大多数标签类型除了 `CompoundTag` 外大多是内部的,例如 `ByteTag` 或 `StringTag`,尽管如果你偶然遇到一些,直接的 `CompoundTag#get` 和 `#put` 方法可以与它们一起工作。 +The `TAG_INT` constant is defined in `Tag`, which is the super interface for all tag types. Most tag types besides `CompoundTag` are mostly internal, for example `ByteTag` or `StringTag`, though the direct `CompoundTag#get` and `#put` methods can work with them if you ever stumble across some. -不过,有一个明显的例外:`ListTag`。处理这些是特别的,因为当通过 `CompoundTag#getList` 获取列表标签时,你还必须指定列表类型。例如,获取字符串列表会像这样工作: +There is one obvious exception, though: `ListTag`s. Working with these is special because when getting a list tag through `CompoundTag#getList`, you must also specify the list type. So getting a list of strings, for example, would work like this: ```java ListTag list = tag.getList("SomeListHere", Tag.TAG_STRING); ``` -类似地,创建 `ListTag` 时,也必须在创建过程中指定列表类型: +Similarly, when creating a `ListTag`, you must also specify the list type during creation: ```java ListTag list = new ListTag(List.of("Value1", "Value2"), Tag.TAG_STRING); ``` -最后,直接在其他 `CompoundTag` 中操作 `CompoundTag` 利用 `CompoundTag#get` 和 `#put`: +Finally, working with `CompoundTag`s inside other `CompoundTag`s directly utilizes `CompoundTag#get` and `#put`: ```java tag.put("Tag", new CompoundTag()); tag.get("Tag"); ``` -## NBT 的用途 +## Usages of NBT -NBT 在 Minecraft 中有很多用途。一些最常见的例子包括 [`ItemStack`][itemstack]、[`BlockEntity`][blockentity] 和 `Entity`。 +NBT is used in a lot of places in Minecraft. Some of the most common examples include [`ItemStack`][itemstack]s, [`BlockEntity`][blockentity]s and `Entity`s. -## 另见 +## See Also -- [Minecraft Wiki 上的 NBT 格式][nbtwiki] +- [NBT Format on the Minecraft Wiki][nbtwiki] [blockentity]: ../blockentities/index.md [datapack]: ../resources/server/index.md diff --git a/docs/datastorage/saveddata.md b/docs/datastorage/saveddata.md index 1e2247519..614d1d171 100644 --- a/docs/datastorage/saveddata.md +++ b/docs/datastorage/saveddata.md @@ -1,40 +1,40 @@ -# 保存的数据系统 +# Saved Data -保存的数据(SD)系统可用于在各级别上保存额外数据。 +The Saved Data (SD) system can be used to save additional data on levels. -_如果数据特定于某些方块实体、区块或实体,请考虑使用[数据附件](attachments)。_ +_If the data is specific to some block entities, chunks, or entities, consider using a [data attachment](attachments) instead._ -## 声明 +## Declaration -每个 SD 实现必须是 `SavedData` 类的子类型。有两个重要方法需要注意: +Each SD implementation must subtype the `SavedData` class. There are two important methods to be aware of: -* `save`:允许实现将 NBT 数据写入级别。 -* `setDirty`:在更改数据后必须调用的方法,以通知游戏需要写入的更改。如果不调用,`#save` 将不会被调用,原始数据将保持不变。 +* `save`: Allows the implementation to write NBT data to the level. +* `setDirty`: A method that must be called after changing the data, to notify the game that there are changes that need to be written. If not called, `#save` will not get called and the original data will remain unchanged. -## 附加到级别 +## Attaching to a Level -任何 `SavedData` 都是动态加载和/或附加到级别的。因此,如果一个级别上从未创建过,那么它将不存在。 +Any `SavedData` is loaded and/or attached to a level dynamically. As such, if one is never created on a level, then it will not exist. -`SavedData` 是从 `DimensionDataStorage` 创建和加载的,可以通过调用 `ServerChunkCache#getDataStorage` 或 `ServerLevel#getDataStorage` 访问。从那里,您可以通过调用 `DimensionDataStorage#computeIfAbsent` 来获取或创建您的 SD 实例。这将尝试获取当前存在的 SD 实例,或创建一个新实例并加载所有可用数据。 +`SavedData`s are created and loaded from the `DimensionDataStorage`, which can be accessed by calling either `ServerChunkCache#getDataStorage` or `ServerLevel#getDataStorage`. From there, you can get or create an instance of your SD by calling `DimensionDataStorage#computeIfAbsent`. This will attempt to get the current instance of the SD if present or create a new one and load all available data. -`DimensionDataStorage#computeIfAbsent` 接受两个参数。第一个是 `SavedData.Factory` 的实例,它包括一个供应商来构建一个新的 SD 实例和一个函数,以将 NBT 数据加载到 SD 并返回它。第二个参数是实施级别的 `data` 文件夹中存储的 `.dat` 文件的名称。名称必须是有效的文件名,不能包含 `/` 或 `\`。 +`DimensionDataStorage#computeIfAbsent` takes in two arguments. The first is an instance of `SavedData.Factory`, which consists of a supplier to construct a new instance of the SD and a function to load NBT data into a SD and return it. The second argument is the name of the `.dat` file stored within the `data` folder for the implemented level. The name must be a valid filename and can not contain `/` or `\`. -例如,如果一个 SD 在下界被命名为 "example",则会在 `.//DIM-1/data/example.dat` 创建一个文件,并且会像这样实现: +For example, if a SD was named "example" within the Nether, then a file would be created at `.//DIM-1/data/example.dat` and would be implemented like so: ```java -// 在某个类中 +// In some class public ExampleSavedData create() { return new ExampleSavedData(); } public ExampleSavedData load(CompoundTag tag) { ExampleSavedData data = this.create(); - // 加载保存的数据 + // Load saved data return data; } -// 在类中的某个方法内 +// In some method within the class netherDataStorage.computeIfAbsent(new Factory<>(this::create, this::load), "example"); ``` -如果一个 SD 不特定于一个级别,那么 SD 应该附加到主世界,可以从 `MinecraftServer#overworld` 获取。主世界是唯一从不完全卸载的维度,因此非常适合在其上存储多级别数据。 +If a SD is not specific to a level, the SD should be attached to the Overworld, which can be obtained from `MinecraftServer#overworld`. The Overworld is the only dimension that is never fully unloaded and as such makes it perfect to store multi-level data on. diff --git a/docs/gettingstarted/index.md b/docs/gettingstarted/index.md index f9995afec..340e18813 100644 --- a/docs/gettingstarted/index.md +++ b/docs/gettingstarted/index.md @@ -1,54 +1,52 @@ -# 开始使用 NeoForge +# Getting Started with NeoForge -本节包含有关如何设置 NeoForge 工作区以及如何运行和测试您的模组的信息。 +This section includes information about how to set up a NeoForge workspace, and how to run and test your mod. -## 先决条件 +## Prerequisites -- 熟悉 Java 编程语言,特别是其面向对象、多态、泛型和功能性特征。 -- 安装 Java 17 开发工具包(JDK)和 64 位 Java 虚拟机(JVM)。NeoForge 推荐并官方支持 [Microsoft 的 OpenJDK 构建][jdk],但其他 JDK 也应该可以工作。 +- Familiarity with the Java programming language, specifically its object-oriented, polymorphic, generic, and functional features. +- An installation of the Java 17 Development Kit (JDK) and 64-bit Java Virtual Machine (JVM). NeoForge recommends and officially supports the [Microsoft builds of OpenJDK][jdk], but any other JDK should work as well. -:::warning -确保您正在使用 64 位 JVM。检查的一种方式是在终端运行 `java -version`。使用 32 位 JVM 可能会出现问题,因为很多东西已经不再支持 32 位 JVM 了。 +:::caution +Make sure you are using a 64-bit JVM. One way of checking is to run `java -version` in a terminal. Issues may arise when using a 32-bit JVM, due to 32-bit JVMs running out of support for a lot of things. ::: -- 熟悉您选择的集成开发环境(IDE)。 - - NeoForge 官方支持 [IntelliJ IDEA][intellij] 和 [Eclipse][eclipse],这两者都集成了 Gradle 支持。但是,可以使用任何 IDE,从 Netbeans 或 Visual Studio Code 到 Vim 或 Emacs 都可以。 -- 熟悉 [Git][git] 和 [GitHub][github]。技术上这不是必需的,但它会让您的生活变得更加轻松。 +- Familiarity with an Integrated Development Environment (IDE) of your choice. + - NeoForge officially supports [IntelliJ IDEA][intellij] and [Eclipse][eclipse], both of which have integrated Gradle support. However, any IDE can be used, ranging from Netbeans or Visual Studio Code to Vim or Emacs. +- Familiarity with [Git][git] and [GitHub][github]. This is technically not required, but it will make your life a lot easier. -## 设置工作区 +## Setting Up the Workspace -- 打开 [Mod Developer Kit (MDK)][mdk] GitHub 仓库,点击“使用此模板”并将新创建的仓库克隆到您的本地机器。 - - 如果您不想使用 GitHub,或者想获取旧提交或非默认分支的模板(例如,对于旧版本),您也可以下载仓库的 ZIP 文件(在代码 -> 下载 ZIP 下)并解压。 -- 打开您的 IDE 并导入 Gradle 项目。Eclipse 和 IntelliJ IDEA 会为您自动完成此操作。如果您使用的 IDE 不支持此操作,您也可以通过 `gradlew` 终端命令来完成。 - - 首次进行此操作时,Gradle 将下载 NeoForge 的所有依赖项,包括 Minecraft 本身,并对其进行反编译。这可能需要相当长的时间(取决于您的硬件和网络强度,最多可达一个小时)。 - - 每当您对 Gradle 文件进行更改时,需要重新加载 Gradle 更改,可以通过您的 IDE 中的“重新加载 Gradle”按钮或再次通过 `gradlew` 终端命令来完成。 +- Open the [Mod Developer Kit (MDK)][mdk] GitHub repository, click "Use this template" and clone the newly-created repository to your local machine. + - If you do not want to use GitHub, or if you want to get the template for an older commit or a non-default branch (which would be the case e.g. for older versions), you can also download the ZIP of the repository (under Code -> Download ZIP) and extract it. +- Open your IDE and import the Gradle project. Eclipse and IntelliJ IDEA will do this automatically for you. If you have an IDE that does not do this, you can also do it via the `gradlew` terminal command. + - When doing this for the first time, Gradle will download all dependencies of NeoForge, including Minecraft itself, and decompile them. This can take a fair amount of time (up to an hour, depending on your hardware and network strength). + - Whenever you make a change to the Gradle files, the Gradle changes will need to be reloaded, either through the "Reload Gradle" button in your IDE, or again through the `gradlew` terminal command. -## 自定义您的模组信息 +## Customizing Your Mod Information -您的模组的许多基本属性都可以在 `gradle.properties` 文件中更改。这包括模组名称或模组版本等基本事项。有关更多信息,请参阅 `gradle.properties` 文件中的注释,或查看 [关于 `gradle.properties` 文件的文档][properties]。 +Many of the basic properties of your mod can be changed in the `gradle.properties` file. This includes basic things like the mod name or the mod version. For more information, see the comments in the `gradle.properties` file, or see [the documentation of the `gradle.properties` file][properties]. -如果您想进一步修改构建过程,可以编辑 `build.gradle` 文件。NeoGradle,NeoForge 的 Gradle 插件,提供了几个配置选项,其中一些选项通过 `build.gradle` 文件中的注释进行了解释。有关完整文档,请参阅 [NeoGradle 文档][neogradle]。 +If you want to modify the build process beyond that, you can edit the `build.gradle` file. NeoGradle, the Gradle plugin for NeoForge, provides several configuration options, a few of which are explained by comments in the `build.gradle` files. For full documentation, see the [NeoGradle documentation][neogradle]. -:::warning -只有在您知道自己在做什么时才编辑 `build.gradle` 和 `settings.gradle` 文件。所有基本属性都可以通过 `gradle.properties` 设置。 +:::caution +Only edit the `build.gradle` and `settings.gradle` files if you know what you are doing. All basic properties can be set via `gradle.properties`. ::: -## 构建和测试您的模组 +## Building and Testing Your Mod -要构建您的模组,请运行 `gradlew build`。这将在 `build/libs` 中输出一个名为 `-.jar` 的文件。`` 和 `` 是通过 `build.gradle` 设置的属性,默认为 `gradle.properties` 文件中的 `mod_id` 和 `mod_version` 值;如果需要,这可以在 `build.gradle` 中更改。然后可以将生成的 JAR 文件放置在启用 NeoForge 的 Minecraft 设置的 `mods` 文件夹中,或 +To build your mod, run `gradlew build`. This will output a file in `build/libs` with the name `-.jar`. `` and `` are properties set by the `build.gradle` and default to the `mod_id` and `mod_version` values in the `gradle.properties` file, respectively; this can be changed in the `build.gradle` if desired. The resulting JAR file can then be placed in the `mods` folder of a NeoForge-enabled Minecraft setup, or uploaded to a mod distribution platform. -上传到模组分发平台。 +To run your mod in a test environment, you can either use the generated run configurations or use the associated tasks (e.g. `gradlew runClient`). This will launch Minecraft from the corresponding runs directory (e.g. `runs/client` or `runs/server`), along with any source sets specified. The default MDK includes the `main` source set, so any code written in `src/main/java` will be applied. -要在测试环境中运行您的模组,您可以使用生成的运行配置或使用相关任务(例如 `gradlew runClient`)。这将从相应的运行目录(例如 `runs/client` 或 `runs/server`)启动 Minecraft,以及任何指定的源集。默认 MDK 包括 `main` 源集,因此在 `src/main/java` 中编写的任何代码都将被应用。 +### Server Testing -### 服务器测试 +If you are running a dedicated server, whether through the run configuration or `gradlew runServer`, the server will shut down immediately. You will need to accept the Minecraft EULA by editing the `eula.txt` file in the run directory. -如果您正在运行一个专用服务器,无论是通过运行配置还是 `gradlew runServer`,服务器将立即关闭。您需要通过编辑运行目录中的 `eula.txt` 文件来接受 Minecraft EULA。 +Once accepted, the server will load and become available under `localhost` (or `127.0.0.1` by default). However, you will still not able to join, because the server will be put into online mode by default, which requires authentication (that the Dev player does not have). To fix this, stop your server again and set the `online-mode` property in the `server.properties` file to `false`. Now, start your server, and you should be able to connect. -一旦接受,服务器将加载并在 `localhost`(或默认的 `127.0.0.1`)下可用。然而,您仍然无法加入,因为服务器默认会进入在线模式,这需要认证(开发玩家没有)。要解决此问题,请再次停止您的服务器并将 `server.properties` 文件中的 `online-mode` 属性设置为 `false`。现在,启动您的服务器,您应该能够连接。 - -:::tips -您应该始终在专用服务器环境中测试您的模组。这包括[仅客户端模组][client],因为这些在服务器上加载时不应做任何事情。 +:::tip +You should always test your mod in a dedicated server environment. This includes [client-only mods][client], as these should not do anything when loaded on the server. ::: [client]: ../concepts/sides.md diff --git a/docs/gettingstarted/modfiles.md b/docs/gettingstarted/modfiles.md index 733cda92c..a22d2a059 100644 --- a/docs/gettingstarted/modfiles.md +++ b/docs/gettingstarted/modfiles.md @@ -1,55 +1,169 @@ # Mod Files -Mod文件负责确定哪些模组被打包到您的JAR文件中,显示在“Mods”菜单中的信息,以及您的模组在游戏中应如何加载。 +The mod files are responsible for determining what mods are packaged into your JAR, what information to display within the 'Mods' menu, and how your mod should be loaded in the game. ## gradle.properties -`gradle.properties`文件保存了您的模组的各种常见属性,例如模组ID或模组版本。在构建过程中,Gradle会读取这些文件中的值,并将它们内联到各种位置,如[mods.toml][modstoml]文件中。这样,您只需要在一个地方更改值,然后它们就会为您在所有地方应用。 +The `gradle.properties` file holds various common properties of your mod, such as the mod id or mod version. During building, Gradle reads the values in these files and inlines them in various places, such as the [mods.toml][modstoml] file. This way, you only need to change values in one place, and they are then applied everywhere for you. + +Most values are also explained as comments in [the MDK's `gradle.properties` file]. + +| Property | Description | Example | +|---------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-------------------------------------------| +| `org.gradle.jvmargs` | Allows you to pass extra JVM arguments to Gradle. Most commonly, this is used to assign more/less memory to Gradle. Note that this is for Gradle itself, not Minecraft. | `org.gradle.jvmargs=-Xmx3G` | +| `org.gradle.daemon` | Whether Gradle should use the daemon when building. | `org.gradle.daemon=false` | +| `org.gradle.debug` | Whether Gradle is set to debug mode. Debug mode mainly means more Gradle log output. Note that this is for Gradle itself, not Minecraft. | `org.gradle.debug=false` | +| `minecraft_version` | The Minecraft version you are modding on. Must match with `neo_version`. | `minecraft_version=1.20.2` | +| `minecraft_version_range` | The Minecraft version range this mod can use, as a [Maven Version Range][mvr]. Note that [snapshots, pre-releases and release candidates][mcversioning] are not guaranteed to sort properly, as they do not follow maven versioning. | `minecraft_version_range=[1.20.2,1.20.3)` | +| `neo_version` | The NeoForge version you are modding on. Must match with `minecraft_version`. See [NeoForge Versioning][neoversioning] for more information on how NeoForge versioning works. | `minecraft_version=1.20.2` | +| `neo_version_range` | The NeoForge version range this mod can use, as a [Maven Version Range][mvr]. | `minecraft_version_range=[1.20.2,1.20.3)` | +| `loader_version_range` | The version range of the mod loader this mod can use, as a [Maven Version Range][mvr]. Note that the loader versioning is decoupled from NeoForge versioning. | `loader_version_range=[1,)` | +| `mod_id` | The id of your mod. This should be something unique and memorable, as having two mods with the same id will prevent the game from loading. The mod id shows up in a lot of places, for example as the namespace for all your registered things, or as the namespace for your resource and data packs. | `mod_id=examplemod` | +| `mod_name` | The human-readable display name of your mod. By default, this can only be seen in the mod list, however, mods such as [JEI][jei] prominently display mod names in item tooltips as well. | `mod_name=Example Mod` | +| `mod_license` | The license your mod is provided under. It is suggested that this is set to the [SPDX identifier][spdx] you are using and/or a link to the license. You can visit https://choosealicense.com/ to help pick the license you want to use. | `mod_license=MIT` | +| `mod_version` | The version of your mod, shown in the mod list. See [the page on Versioning][versioning] for more information. | `mod_version=1.0` | +| `mod_group_id` | See [The Group ID][group]. | `mod_group_id=com.example.examplemod` | +| `mod_authors` | The authors of the mod, shown in the mod list. | `mod_authors=ExampleModder` | +| `mod_description` | The description of the mod, as a multiline string, shown in the mod list. Newline characters (`\n`) can be used and will be replaced properly. | `mod_authors=Example mod description.` | +| `pack_format_number` | The version number of your mod's data and resource pack. Mojang bumps this without something that could be considered a consistent scheme, so it's best to just look up what the current number is. As of Minecraft 1.20.2, the pack version is `18`. | `pack_version_number=18` | + +### The Group ID + +While the `group` property in the `build.gradle` is only necessary if you plan to publish your mod to a maven, it is considered good practice to always properly set this. This is done for you through the `gradle.properties`'s `mod_group_id` property. + +The group id should be set to your top-level package. See [Packaging][packaging] for more information. + +```text +// In your gradle.properties file +mod_group_id=com.example +``` + +The packages within your java source (`src/main/java`) should also now conform to this structure, with an inner package representing the mod id: -大多数值也在[MDK的`gradle.properties`文件]中以注释形式解释。 +```text +com +- example (top-level package specified in group property) + - mymod (the mod id) + - MyMod.java (renamed ExampleMod.java) +``` ## mods.toml -位于`src/main/resources/META-INF/mods.toml`的`mods.toml`文件是一个[TOML][toml]格式的文件,定义了您的模组的元数据。它还包含了有关如何将您的模组加载到游戏中的附加信息,以及显示在“Mods”菜单中的显示信息。[MDK提供的`mods.toml`文件][mdkmodstoml]包含解释每个条目的注释,这里将更详细地解释。 +The `mods.toml` file, located at `src/main/resources/META-INF/mods.toml`, is a file in [TOML][toml] format that defines the metadata of your mod(s). It also contains additional information on how your mod(s) should be loaded into the game, as well as display information that is displayed within the 'Mods' menu. The [`mods.toml` file provided by the MDK][mdkmodstoml] contains comments explaining every entry, they will be explained here in more detail. + +The `mods.toml` can be separated into three parts: the non-mod-specific properties, which are linked to the mod file; the mod properties, with a section for each mod; and the dependency configurations, with a section for each mod's or mods' dependencies. Some of the properties associated with the `mods.toml` file are mandatory; mandatory properties require a value to be specified, otherwise an exception will be thrown. + +:::note +In the default MDK, Gradle replaces various properties in this file with the values specified in the `gradle.properties` file. For example, the line `license="${mod_license}"` means that the `license` field is replaced by the `mod_license` property from `gradle.properties`. Values that are replaced like this should be changed in the `gradle.properties` instead of changing them here. +::: + +### Non-Mod-Specific Properties + +Non-mod-specific properties are properties associated with the JAR itself, indicating how to load the mod(s) and any additional global metadata. + +| Property | Type | Default | Description | Example | +|:---------------------|:-------:|:-------------:|:--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------:|:-------------------------------------------------------------------------------| +| `modLoader` | string | **mandatory** | The language loader used by the mod(s). Can be used to support alternative language structures, such as Kotlin objects for the main file, or different methods of determining the entrypoint, such as an interface or method. NeoForge provides the Java loader [`"javafml"`][javafml] and the lowcode/nocode loader [`"lowcodefml"`][lowcodefml]. | `modLoader="javafml"` | +| `loaderVersion` | string | **mandatory** | The acceptable version range of the language loader, expressed as a [Maven Version Range][mvr]. For `javafml` and `lowcodefml`, this is currently version `1`. | `loaderVersion="[1,)"` | +| `license` | string | **mandatory** | The license the mod(s) in this JAR are provided under. It is suggested that this is set to the [SPDX identifier][spdx] you are using and/or a link to the license. You can visit https://choosealicense.com/ to help pick the license you want to use. | `license="MIT"` | +| `showAsResourcePack` | boolean | `false` | When `true`, the mod(s)'s resources will be displayed as a separate resource pack on the 'Resource Packs' menu, rather than being combined with the 'Mod resources' pack. | `showAsResourcePack=true` | +| `services` | array | `[]` | An array of services your mod uses. This is consumed as part of the created module for the mod from NeoForge's implementation of the Java Platform Module System. | `services=["net.neoforged.neoforgespi.language.IModLanguageProvider"]` | +| `properties` | table | `{}` | A table of substitution properties. This is used by `StringSubstitutor` to replace `${file.}` with its corresponding value. | `properties={"example"="1.2.3"}` (can then be referenced by `${file.example}`) | +| `issueTrackerURL` | string | *nothing* | A URL representing the place to report and track issues with the mod(s). | `"https://github.com/neoforged/NeoForge/issues"` | + +:::note +The `services` property is functionally equivalent to specifying the [`uses` directive in a module][uses], which allows [loading a service of a given type][serviceload]. -`mods.toml`可以分为三部分:非模组特定属性,这些属性与模组文件相关联;模组属性,每个模组有一个部分;依赖配置,每个模组的依赖项有一个部分。与`mods.toml`文件关联的某些属性是强制性的;强制性属性需要指定一个值,否则会抛出异常。 +Alternatively, it can be defined in a service file inside the `src/main/resources/META-INF/services` folder, where the file name is the fully-qualified name of the service, and the file content is the name of the service to load (see also [this example from the AtlasViewer mod][atlasviewer]). +::: + +### Mod-Specific Properties + +Mod-specific properties are tied to the specified mod using the `[[mods]]` header. This is an [array of tables][array]; all key/value properties will be attached to that mod until the next header. + +```toml +# Properties for examplemod1 +[[mods]] +modId = "examplemod1" + +# Properties for examplemod2 +[[mods]] +modId = "examplemod2" +``` + +| Property | Type | Default | Description | Example | +|:----------------|:-------:|:---------------------------:|:------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------:|:----------------------------------------------------------------| +| `modId` | string | **mandatory** | The unique identifier representing this mod. The id must match `^[a-z][a-z0-9_]{1,63}$` (a string 2-64 characters; starts with a lowercase letter; made up of lowercase letters, numbers, or underscores). | `modId="examplemod"` | +| `namespace` | string | value of `modId` | An override namespace for the mod. The namespace much match `^[a-z][a-z0-9_.-]{1,63}$` (a string 2-64 characters; starts with a lowercase letter; made up of lowercase letters, numbers, underscores, dots, or dashes). Currently unused. | `namespace="example"` | +| `version` | string | `"1"` | The version of the mod, preferably in a [variation of Maven versioning][versioning]. When set to `${file.jarVersion}`, it will be replaced with the value of the `Implementation-Version` property in the JAR's manifest (displays as `0.0NONE` in a development environment). | `version="1.20.2-1.0.0"` | +| `displayName` | string | value of `modId` | The display name of the mod. Used when representing the mod on a screen (e.g., mod list, mod mismatch). | `displayName="Example Mod"` | +| `description` | string | `'''MISSING DESCRIPTION'''` | The description of the mod shown in the mod list screen. It is recommended to use a [multiline literal string][multiline]. | `description='''This is an example.'''` | +| `logoFile` | string | *nothing* | The name and extension of an image file used on the mods list screen. The logo must be in the root of the JAR or directly in the root of the source set (e.g. `src/main/resources` for the main source set). | `logoFile="example_logo.png"` | +| `logoBlur` | boolean | `true` | Whether to use `GL_LINEAR*` (true) or `GL_NEAREST*` (false) to render the `logoFile`. In simpler terms, this means whether the logo should be blurred or not when trying to scale the logo. | `logoBlur=false` | +| `updateJSONURL` | string | *nothing* | A URL to a JSON used by the [update checker][update] to make sure the mod you are playing is the latest version. | `updateJSONURL="https://example.github.io/update_checker.json"` | +| `features` | table | `{}` | See [features]. | `features={java_version="[17,)"}` | +| `modproperties` | table | `{}` | A table of key/values associated with this mod. Unused by NeoForge, but is mainly for use by mods. | `modproperties={example="value"}` | +| `modUrl` | string | *nothing* | A URL to the download page of the mod. Currently unused. | `modUrl="https://neoforged.net/"` | +| `credits` | string | *nothing* | Credits and acknowledges for the mod shown on the mod list screen. | `credits="The person over here and there."` | +| `authors` | string | *nothing* | The authors of the mod shown on the mod list screen. | `authors="Example Person"` | +| `displayURL` | string | *nothing* | A URL to the display page of the mod shown on the mod list screen. | `displayURL="https://neoforged.net/"` | +| `displayTest` | string | `"MATCH_VERSION"` | See [sides]. | `displayTest="NONE"` | + +:::note +Some properties (`displayName` and `description`) can also be localized using language files. See [Translating Mod Metadata][i18n] for more detail. +::: -### 非模组特定属性 +#### Features -非模组特定属性与JAR本身相关联,指示如何加载模组以及任何额外的全局元数据。 +The features system allows mods to demand that certain settings, software, or hardware are available when loading the system. When a feature is not satisfied, mod loading will fail, informing the user about the requirement. Currently, NeoForge provides the following features: -### 模组特定属性 +| Feature | Description | Example | +|:---------------:|:---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------:|:------------------------------------| +| `java_version` | The acceptable version range of the Java version, expressed as a [Maven Version Range][mvr]. This should be the supported version used by Minecraft. | `features={java_version="[17,)"}` | +| `openGLVersion` | The acceptable version range of the OpenGL version, expressed as a [Maven Version Range][mvr]. Minecraft requires OpenGL 3.2 or newer. If you want to require a newer OpenGL version, you can do so here. | `features={openGLVersion="[4.6,)"}` | -模组特定属性通过`[[mods]]`头部与指定的模组关联。这是一个[表的数组][array];所有键/值属性都将附加到该模组,直到下一个头部。 +### Dependency Configurations -### 依赖配置 +Mods can specify their dependencies, which are checked by NeoForge before loading the mods. These configurations are created using the [array of tables][array] `[[dependencies.]]`, where `modid` is the identifier of the mod that consumes the dependency. -模组可以指定它们的依赖关系,NeoForge在加载模组之前会检查这些配置。这些配置是使用`[[dependencies.]]`创建的,其中`modid`是消耗依赖项的模组的标识符。 +| Property | Type | Default | Description | Example | +|:---------------|:------:|:-------------:|:---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:---------------------------------------------| +| `modId` | string | **mandatory** | The identifier of the mod added as a dependency. | `modId="jei"` | +| `type` | string | `"required"` | Specifies the nature of this dependency: `"required"` is the default and prevents the mod from loading if this dependency is missing; `"optional"` will not prevent the mod from loading if the dependency is missing, but still validates that the dependency is compatible; `"incompatible"` prevents the mod from loading if this dependency is present; `"discouraged"` still allows the mod to load if the dependency is present, but presents a warning to the user. | `type="incompatible"` | +| `reason` | string | *nothing* | An optional user-facing message to describe why this dependency is required, or why it is incompatible. | +| `versionRange` | string | `""` | The acceptable version range of the language loader, expressed as a [Maven Version Range][mvr]. An empty string matches any version. | `versionRange="[1, 2)"` | +| `ordering` | string | `"NONE"` | Defines if the mod must load before (`"BEFORE"`) or after (`"AFTER"`) this dependency. If the ordering does not matter, return `"NONE"` | `ordering="AFTER"` | +| `side` | string | `"BOTH"` | The [physical side][sides] the dependency must be present on: `"CLIENT"`, `"SERVER"`, or `"BOTH"`. | `side="CLIENT"` | +| `referralUrl` | string | *nothing* | A URL to the download page of the dependency. Currently unused. | `referralUrl="https://library.example.com/"` | + +:::danger +The `ordering` of two mods may cause a crash due to a cyclic dependency, for example if mod A must load `"BEFORE"` mod B and at the same time, mod B must load `"BEFORE"` mod A. +::: -## Mod 入口点 +## Mod Entrypoints -现在`mods.toml`已经填写完毕,我们需要为模组提供一个入口点。入口点本质上是执行模组的起点。入口点本身由`mods.toml`中使用的语言加载器确定。 +Now that the `mods.toml` is filled out, we need to provide an entrypoint for the mod. Entrypoints are essentially the starting point for executing the mod. The entrypoint itself is determined by the language loader used in the `mods.toml`. -### `javafml` 和 `@Mod` +### `javafml` and `@Mod` -`javafml`是NeoForge为Java编程语言提供的语言加载器。入口点是使用带有`@Mod`注解的公共类定义的。`@Mod`的值必须包含`mods.toml`中指定的模组ID之一。从那里开始,所有初始化逻辑(例如[注册事件][events]或[添加`DeferredRegister`][registration])可以在类的构造函数中指定。 +`javafml` is a language loader provided by NeoForge for the Java programming language. The entrypoint is defined using a public class with the `@Mod` annotation. The value of `@Mod` must contain one of the mod ids specified within the `mods.toml`. From there, all initialization logic (e.g. [registering events][events] or [adding `DeferredRegister`s][registration]) can be specified within the constructor of the class. ```java -@Mod("examplemod") // 必须与mods.toml中的模组ID匹配 +@Mod("examplemod") // Must match a mod id in the mods.toml public class Example { - public Example(IEventBus modBus) { // 参数是模组特定的事件总线,例如用于注册和事件 - // 在这里初始化逻辑 + public Example(IEventBus modBus) { // The parameter is the mod-specific event bus, needed e.g. for registration and events + // Initialize logic here } } ``` -:::tips -`mods.toml`文件中的模组和`@Mod`入口点必须有1对1的匹配。这意味着对于每个定义的模组,必须有一个带有该模组ID的`@Mod`注解。 +:::note +There must be a 1-to-1 matching of mods in the `mods.toml` file and `@Mod` entrypoints. This means that for every mod defined, there must be exactly one `@Mod` annotation with that mod's id. ::: ### `lowcodefml` -`lowcodefml`是一种语言加载器,用作以模组形式分发数据包和资源包,而无需代码内入口点。它被指定为`lowcodefml`而不是`nocodefml`,因为未来可能需要最小的代码添加。 +`lowcodefml` is a language loader used as a way to distribute datapacks and resource packs as mods without the need of an in-code entrypoint. It is specified as `lowcodefml` rather than `nocodefml` for minor additions in the future that might require minimal coding. [array]: https://toml.io/en/v1.0.0#array-of-tables [atlasviewer]: https://github.com/XFactHD/AtlasViewer/blob/1.20.2/neoforge/src/main/resources/META-INF/services/xfacthd.atlasviewer.platform.services.IPlatformHelper diff --git a/docs/gettingstarted/structuring.md b/docs/gettingstarted/structuring.md index 113e5ee52..c91ba153b 100644 --- a/docs/gettingstarted/structuring.md +++ b/docs/gettingstarted/structuring.md @@ -1,80 +1,78 @@ -# 结构化您的模组 +# Structuring Your Mod -结构化模组有利于维护、贡献并提供对底层代码库的更清晰理解。以下是一些建议,源自 Java、Minecraft 和 NeoForge。 +Structured mods are beneficial for maintenance, making contributions, and providing a clearer understanding of the underlying codebase. Some of the recommendations from Java, Minecraft, and NeoForge are listed below. :::note -您不必遵循下面的建议;您可以按照自己认为合适的方式结构化您的模组。然而,还是强烈建议这样做。 +You do not have to follow the advice below; you can structure your mod any way you see fit. However, it is still highly recommended to do so. ::: -## 包结构 +## Packaging -在构建您的模组时,选择一个独特的顶级包结构。许多程序员会为不同的类、接口等使用相同的名称。Java允许在不同的包中有相同名称的类。因此,如果两个类有相同的包和相同的名称,只有一个类会被加载,很可能会导致游戏崩溃。 +When structuring your mod, pick a unique, top-level package structure. Many programmers will use the same name for different classes, interfaces, etc. Java allows classes to have the same name as long as they are in different packages. As such, if two classes have the same package with the same name, only one would be loaded, most likely causing the game to crash. ``` a.jar - com.example.ExampleClass b.jar - - com.example.ExampleClass // 这个类通常不会被加载 + - com.example.ExampleClass // This class will not normally be loaded ``` -当涉及到加载模块时,这一点尤其相关。如果两个包在不同的模块下有同名的类文件,这将导致模组加载器在启动时崩溃,因为模组模块被导出到游戏和其他模组。 +This is even more relevant when it comes to loading modules. If there are class files in two packages under the same name in separate modules, this will cause the mod loader to crash on startup since mod modules are exported to the game and other mods. ``` -模块 A - - 包 X - - 类 I - - 类 J -模块 B - - 包 X // 这个包将导致模组加载器崩溃,因为已经有一个模块导出了包 X - - 类 R - - 类 S - - 类 T +module A + - package X + - class I + - class J +module B + - package X // This package will cause the mod loader to crash, as there already is a module with package X being exported + - class R + - class S + - class T ``` -因此,您的顶级包应该是您拥有的东西:域名、电子邮件地址、网站(或子域)等。它甚至可以是您的名字或用户名,只要您能保证它在预期目标中具有唯一可识别性。此外,顶级包还应与您的[group id][group]匹配。 +As such, your top level package should be something that you own: a domain, email address, a (subdomain of a) website, etc. It can even be your name or username as long as you can guarantee that it will be uniquely identifiable within the expected target. Furthermore, the top-level package should also match your [group id][group]. -| 类型 | 值 | 顶级包 | -|:---------:|:--------------:|:----------------------| -| 域名 | example.com | `com.example` | -| 子域名 | example.github.io | `io.github.example` | -| 电邮 | example@gmail.com | `com.gmail.example` | +| Type | Value | Top-Level Package | +|:---------:|:-----------------:|:--------------------| +| Domain | example.com | `com.example` | +| Subdomain | example.github.io | `io.github.example` | +| Email | example@gmail.com | `com.gmail.example` | -下一级包应该是您的模组的ID(例如,`com.example.examplemod`,其中`examplemod`是模组ID)。这将保证,除非您有两个模组ID相同的模组(这种情况永远不应该发生),否则您的包不应该有任何加载问题。 +The next level package should then be your mod's id (e.g. `com.example.examplemod` where `examplemod` is the mod id). This will guarantee that, unless you have two mods with the same id (which should never be the case), your packages should not have any issues loading. -您可以在[Oracle的教程页面][naming]上找到一些额外的命名约定。 +You can find some additional naming conventions on [Oracle's tutorial page][naming]. -### 子包组织 +### Sub-package Organization -除了顶级包,强烈建议将模组的类分配到子包中。有两种主要的方法来做到这一点: +In addition to the top-level package, it is highly recommend to break your mod's classes between subpackages. There are two major methods on how to do so: -* **按功能分组**:为具有共同目的的类制作子包。例如,可以将方块放在`block`下,物品放在`item`下,实体放在`entity`下等。Minecraft本身使用类似的结构(有一些例外)。 -* **按逻辑分组**:为具有共同逻辑的类制作子包。例如,如果您正在创建一个新类型的工作台,您可以将其方块、菜单、物品等放在`feature.crafting_table`下。 +* **Group By Function**: Make subpackages for classes with a common purpose. For example, blocks can be under `block`, items under `item`, entities under `entity`, etc. Minecraft itself uses a similar structure (with some exceptions). +* **Group By Logic**: Make subpackages for classes with a common logic. For example, if you were creating a new type of crafting table, you would put its block, menu, item, and more under `feature.crafting_table`. -#### 客户端、服务器和数据包 +#### Client, Server, and Data Packages -通常,只针对给定侧或运行时的代码应该与其他类隔离在一个单独的子包中。例如,与[数据生成][datagen]相关的代码应该放在`data`包中,只在专用服务器上的代码应该放在`server`包中。 +In general, code only for a given side or runtime should be isolated from the other classes in a separate subpackage. For example, code related to [data generation][datagen] should go in a `data` package, and code only on the dedicated server should go in a `server` package. -强烈建议将[仅客户端代码][sides] +It is highly recommended that [client-only code][sides] should be isolated in a `client` subpackage. This is because dedicated servers have no access to any of the client-only packages in Minecraft and will crash if your mod tries to access them anyway. As such, having a dedicated package provides a decent sanity check to verify you are not reaching across sides within your mod. -隔离在`client`子包中。这是因为专用服务器无法访问Minecraft中的任何客户端专用包,并且如果您的模组试图访问它们,将会崩溃。因此,拥有一个专用包提供了一个很好的检查方法,以验证您没有在模组内跨侧访问。 +## Class Naming Schemes -## 类命名方案 +A common class naming scheme makes it easier to decipher the purpose of the class or to easily locate specific classes. -常见的类命名方案使得更容易理解类的用途或轻松找到特定类。 +Classes are commonly suffixed with its type, for example: -类通常以其类型为后缀,例如: - -* 一个名为 `PowerRing` 的 `Item` -> `PowerRingItem`。 -* 一个名为 `NotDirt` 的 `Block` -> `NotDirtBlock`。 -* 一个 `Oven` 的菜单 -> `OvenMenu`。 +* An `Item` called `PowerRing` -> `PowerRingItem`. +* A `Block` called `NotDirt` -> `NotDirtBlock`. +* A menu for an `Oven` -> `OvenMenu`. :::tip -Mojang 通常遵循类似的结构来命名所有类,除了实体。这些实体只用它们的名字表示(例如,`Pig`,`Zombie`等)。 +Mojang typically follows a similar structure for all classes except entities. Those are represented by just their names (e.g. `Pig`, `Zombie`, etc.). ::: -## 从多种方法中选择一种 +## Choose One Method from Many -执行特定任务有许多方法:注册对象、监听事件等。通常建议使用单一方法来完成给定任务。这可以提高可读性,并避免可能发生的奇怪交互或冗余(例如,您的事件监听器运行两次)。 +There are many methods for performing a certain task: registering an object, listening for events, etc. It's generally recommended to be consistent by using a single method to accomplish a given task. This improves readability and avoids weird interactions or redundancies that may occur (e.g. your event listener running twice). [group]: index.md#the-group-id [naming]: https://docs.oracle.com/javase/tutorial/java/package/namingpkgs.html diff --git a/docs/gettingstarted/versioning.md b/docs/gettingstarted/versioning.md index c17930719..b6203418d 100644 --- a/docs/gettingstarted/versioning.md +++ b/docs/gettingstarted/versioning.md @@ -1,88 +1,84 @@ -# 版本管理 +# Versioning -本文将详细解释 Minecraft 和 NeoForge 的版本管理方式,并为模组版本提供一些建议。 +This article will break down how versioning works in Minecraft and NeoForge, and will give some recommendations for mod versioning as well. ## Minecraft -Minecraft 使用[语义化版本控制][semver](semantic versioning,简称 "semver"),格式为 `major.minor.patch`。例如,Minecraft 1.20.2 的主版本号(major)为 1,次版本号(minor)为 20,修订号(patch)为 2。 +Minecraft uses [semantic versioning][semver]. Semantic versioning, or "semver" for short, has the format `major.minor.patch`. So for example, Minecraft 1.20.2 has the major version 1, the minor version 20 and the patch version 2. -自从 2011 年 Minecraft 1.0 发布以来,Minecraft 的主版本号一直使用 `1`。在此之前,版本号方案经常变化,有诸如 `a1.1`(Alpha 1.1)、`b1.7.3`(Beta 1.7.3)甚至 `infdev` 版本,这些版本根本没有遵循明确的版本控制方案。由于 `1` 作为主版本号已经持续了十多年,并且鉴于 Minecraft 2 的内部笑话,通常认为这一情况不太可能改变。 +Minecraft has used `1` as the major version since 2011, when Minecraft 1.0 was introduced. Before that, the versioning scheme changed often, and there were versions like `a1.1` (Alpha 1.1), `b1.7.3` (Beta 1.7.3) or even the `infdev` versions, which didn't follow a clear versioning scheme at all. Due to the `1` major version holding up for over a decade now, and due to the in-joke that is Minecraft 2, it is generally considered unlikely that this is ever going to change. -### 快照版 +### Snapshots -快照版偏离了标准的 semver 方案。它们被标记为 `YYwWWa`,其中 `YY` 代表年份的最后两位数字(例如 `23`),`WW` 代表那年的周数(例如 `01`)。例如,快照 `23w01a` 是 2023 年第一周发布的快照。 +Snapshots deviate from the standard semver scheme. They are labeled as `YYwWWa`, where `YY` represents the last two digits of the year (e.g. `23`) and `WW` represents the week of that year (e.g. `01`). So for example, snapshot `23w01a` is the snapshot released in the first week of 2023. -`a` 后缀用于同一周发布两个快照的情况(第二个快照则被命名为 `23w01b`)。Mojang 过去偶尔使用过这种方式。此外,还有像 `20w14infinite` 这样的快照,它是[2020 年无限维度愚人节玩笑][infinite]。 +The `a` suffix exists for occasions where two snapshots get released in the same week (where the second snapshot would then be named something like `23w01b`). Mojang has occasionally used this in the past. The alternative suffix has also been used for snapshots like `20w14infinite`, which was the [2020 infinite dimensions April Fool's joke][infinite]. -### 预发布和候选发布版本 +### Pre-releases and Release Candidates -当快照周期接近完成时,Mojang 开始发布所谓的预发布版本。预发布版本被视为功能完整的版本,专注于修复bug。它们使用 semver 格式并附加 `-preX` 后缀。例如,1.20.2 的第一个预发布版本被命名为 `1.20.2-pre1`。通常会有多个预发布版本,相应地使用 `-pre2`、`-pre3` 等后缀。 +When a snapshot cycle is coming completion, Mojang starts releasing so-called pre-releases. Pre-releases are deemed feature-complete for a version and focus solely on bugfixes. They use the semver notation for the version it is for, suffixed by `-preX`. So for example, the first pre-release for 1.20.2 was named `1.20.2-pre1`. There can be and usually are multiple pre-releases, which are accordingly suffixed with `-pre2`, `-pre3`, etc. -类似地,当预发布周期完成时,Mojang 发布第一个候选发布版本(后缀为 `-rc1`,例如 `1.20.2-rc1`)。Mojang 的目标是发布一个候选版本,如果没有进一步的 bug 出现,则可以发布该版本。然而,如果出现意外的 bug,则可能会有 `-rc2`、`-rc3` 等版本,类似于预发布版本。 +Similarly, when the pre-release cycle completes, Mojang releases Release Candidate 1 (suffixing the version with `-rc1`, for example `1.20.2-rc1`). Mojang aims to have one release candidate that they can release if no further bugs occur. However, if an unexpected bug occurs, then there can also be an `-rc2`, `-rc3`, etc. version, similar to pre-releases. ## NeoForge -NeoForge 使用一种调整过的 semver 系统:主版本号是 Minecraft 的次版本号,次版本号是 Minecraft 的修订号,而修订号则是“实际”的 NeoForge 版本。例如,NeoForge 20.2.59 是 Minecraft 1.20.2 的第 60 版(从 0 开始计数)。由于 `1` 作为开头的数字不太可能改变,请参见[上文][minecraft]了解原因。 +NeoForge uses an adapted semver system: The major version is Minecraft's minor version, the minor version is Minecraft's patch version, and the patch version is the "actual" NeoForge version. So for example, NeoForge 20.2.59 is the 60th version (we start at 0) for Minecraft 1.20.2. The `1` at the beginning is omitted because it is very unlikely that it will ever change, see [above][minecraft] for why that is the case. -NeoForge 的一些位置还使用了[Maven 版本范围][mvr],例如 [`mods.toml`][modstoml] 文件中的 Minecraft 和 NeoForge 版本范围。这些主要与 semver 兼容,但有些例外(例如,它不考虑 `pre` 标签)。 +A few places in NeoForge also use [Maven version ranges][mvr], for example the Minecraft and NeoForge version ranges in the [`mods.toml`][modstoml] file. These are mostly, but not fully compatible with semver (the `pre`-tag is not considered by it, for example). -## 模组 +## Mods -没有最佳的版本控制系统。不同的开发风格、项目范围等都会影响选择哪种版本控制系统的决定。有时,也可以组合使用多种版本控制系统。本节试图概述一些 +There is no definitive best versioning system. Different styles of development, scopes of projects, etc. all influence the decision of what versioning system to use. Sometimes, versioning system can also be combined. This section attempts to give an overview over some commonly used versioning systems, with real-life examples. -常用的版本控制系统,并提供现实生活中的示例。 - -通常,模组的文件名看起来像 `modid-.jar`。所以如果我们的模组ID是 `examplemod`,版本是 `1.2.3`,我们的模组文件将被命名为 `examplemod-1.2.3.jar`。 +Usually, a mod's file name looks like `modid-.jar`. So if our mod id is `examplemod` and our version is `1.2.3`, our mod file would be named `examplemod-1.2.3.jar`. :::note -版本控制系统是建议,而不是严格执行的规则。特别是关于何时更改("bump")版本,以及以何种方式更改。如果您想使用不同的版本控制系统,没有人会阻止您。 +Versioning systems are suggestions, rather than strictly enforced rules. This is especially true with regard to when the version is changed ("bumped"), and in what way. If you want to use a different versioning system, nobody is going to stop you. ::: -### 语义化版本控制 +### Semantic Versioning -语义化版本控制("semver")包括三个部分:`major.minor.patch`。当对代码库进行重大更改时,主版本号会提升,这通常与重大新功能和错误修复相关。次版本号在引入次要功能时提升,修订号只包括错误修复时提升。 +Semantic versioning ("semver") consists of three parts: `major.minor.patch`. The major version is bumped when major changes are made to the codebase, which usually correlates with major new features and bugfixes. The minor version is bumped when minor features are introduced, and patch bumps happen when an update only includes bugs. -通常认为任何 `0.x.x` 版本都是开发版本,而第一个(完整)发布版本应该提升到 `1.0.0`。 +It is generally agreed upon that any version `0.x.x` is a development version, and with the first (full) release, the version should be bumped to `1.0.0`. -"次要功能提升次版本号,错误修复提升修订号" 的规则在实践中经常被忽视。一个流行的例子是 Minecraft 本身,它通过次版本号进行重大功能更新,通过修订号进行次要功能更新,并在快照中修复错误(见上文)。 +The "minor for features, patch for bugfixes" rule is often disregarded in practice. A popular example for this is Minecraft itself, which does major features through the minor version number, minor features through the patch number, and bugfixes in snapshots (see above). -根据模组更新的频率,版本号可能会有所增减。例如,[Supplementaries][supplementaries]目前的版本为`2.6.31`(撰写本文时)。在`patch`版本中,出现三位或四位数字的情况完全有可能。 +Depending on how often a mod is updated, these numbers can be smaller or larger. For example, [Supplementaries][supplementaries] is on version `2.6.31` (at the time of writing). Triple- or even quadruple-digit numbers, especially in the `patch`, are absolutely possible. -### “简化”与“扩展”语义化版本控制 +### "Reduced" and "Expanded" Semver -有时候,我们可以看到只有两个数字的语义化版本控制,这种是一种“简化”语义化版本控制,或称为“2部分”语义化版本控制。这种版本号只包含`major.minor`模式。它通常用于只添加几个简单物体的小型模组,这类模组很少需要更新(除了Minecraft版本更新),常常永远停留在`1.0`版本。 +Sometimes, semver can be seen with only two numbers. This is a sort of "reduced" semver, or "2-part" semver. Their version numbers only have a `major.minor` scheme. This is commonly used by small mods that only add a few simple objects and thus rarely need updates (except Minecraft version updates), often staying at version `1.0` forever. -而“扩展”语义化版本控制,或称为“4部分”语义化版本控制,包括四个数字(比如`1.0.0.0`)。根据模组的不同,其格式可能是`major.api.minor.patch`,或`major.minor.patch.hotfix`,或是完全不同的格式——没有统一的标准方式。 +"Expanded" semver, or "4-part" semver, has four numbers (so something like `1.0.0.0`). Depending on the mod, the format can be `major.api.minor.patch`, or `major.minor.patch.hotfix`, or something different entirely - there is no standard way to do it. -对于`major.api.minor.patch`,`major`版本与`api`版本是分离的。这意味着`major`(功能)位和`api`位可以独立提升。这种方式通常用于那些提供API供其他模组开发者使用的模组。例如,[Mekanism][mekanism]当前的版本是10.4.5.19(撰写本文时)。 +For `major.api.minor.patch`, the `major` version is decoupled from the `api` version. This means that the `major` (feature) bit and the `api` bit can be bumped independently. This is commonly used by mods that expose an API for other modders to use. For example, [Mekanism][mekanism] is currently on version 10.4.5.19 (at the time of writing). -对于`major.minor.patch.hotfix`,则是将修订级别分为两部分。这是[Create][create]模组使用的方法,目前版本为0.5.1f(撰写本文时)。注意,Create模组将hotfix表示为一个字母,而不是第四个数字,以保持与常规语义化版本控制的兼容。 +For `major.minor.patch.hotfix`, the patch level is split into two. This is the approach used by the [Create][create] mod, which is currently on version 0.5.1f (at the time of writing). Note that Create denotes the hotfix as a letter instead of a fourth number, in order to stay compatible with regular semver. :::info -简化语义化版本控制、扩展语义化版本控制、2部分语义化版本控制和4部分语义化版本控制并非官方术语或标准化格式。 +Reduced semver, expanded semver, 2-part semver and 4-part semver are not official terms or standardized formats in any way. ::: -### Alpha、Beta、Release阶段 - -像Minecraft本身一样,模组开发通常也会经历软件工程中熟知的`alpha`/`beta`/`release`阶段,其中`alpha`代表不稳定/实验版本(有时也称为`experimental`或`snapshot`),`beta`代表半稳定版本,而`release`则代表稳定版本(有时用`stable`代替`release`)。 +### Alpha, Beta, Release -一些模组利用它们的主要版本号来表示Minecraft版本的更新。例如,[JEI][jei]使用`13.x.x.x`表示Minecraft 1.19.2,`14.x.x.x`表示1.19.4,以及`15.x.x.x`表示1.20.1(不存在1.19.3和1.20.0的版本)。其他一些模组则在模组名称后附加标签,例如[Minecolonies][minecolonies]模组,当前版本为`1.1.328-BETA`(撰写本文时)。 +Like Minecraft itself, modding is often done in the classical `alpha`/`beta`/`release` stages known from software engineering, where `alpha` denotes an unstable/experimental version (sometimes also called `experimental` or `snapshot`), `beta` denotes a semi-stable version, and `release` denotes a stable version (sometimes called `stable` instead of `release`). -### 包含Minecraft版本 +Some mods use their major version to denote a Minecraft version bump. An example of this is [JEI][jei], which uses `13.x.x.x` for Minecraft 1.19.2, `14.x.x.x` for 1.19.4, and `15.x.x.x` for 1.20.1 (there are no versions for 1.19.3 and 1.20.0). Others append the tag to the mod name, for example the [Minecolonies][minecolonies] mod, which is on `1.1.328-BETA` at the time of writing. -在模组文件名中包含其适用的Minecraft版本是常见做法。这使得最终用户更容易确定模组适用于哪个版本的Minecraft。这通常发生在模组版本之前或之后,前者比后者更常见。例如,JEI的最新版本`16.0.0.28`(撰写本文时) +### Including the Minecraft Version -适用于1.20.2,可能表示为`jei-1.20.2-16.0.0.28`或`jei-16.0.0.28-1.20.2`。 +It is common to include the Minecraft version a mod is for in the filename. This makes it easier for end users to easily find out what Minecraft version a mod is for. A common place for this is either before or after the mod version, with the former being more widespread than the latter. For example, JEI version `16.0.0.28` (latest at the time of writing) for 1.20.2 would become `jei-1.20.2-16.0.0.28` or `jei-16.0.0.28-1.20.2`. -### 包含模组加载器 +### Including the Mod Loader -正如您可能知道的那样,NeoForge并非唯一的模组加载器,许多模组开发者在多个平台上开发。因此,需要一种方法来区分同一个模组、相同版本但适用于不同模组加载器的两个文件。 +As you probably know, NeoForge is not the only mod loader out there, and many mod developers develop on multiple platforms. As a result, a way to distinguish between two files of the same mod of the same version, but for different mod loaders is needed. -通常,这是通过在名称中包含模组加载器来实现的。例如,`jei-neoforge-1.20.2-16.0.0.28`、`jei-1.20.2-neoforge-16.0.0.28`或`jei-1.20.2-16.0.0.28-neoforge`都是有效的命名方式。对于其他模组加载器,`neoforge`部分会被替换为`forge`、`fabric`、`quilt`或您可能正在使用的其他模组加载器。 +Usually, this is done by including the mod loader somewhere in the name. `jei-neoforge-1.20.2-16.0.0.28`, `jei-1.20.2-neoforge-16.0.0.28` or `jei-1.20.2-16.0.0.28-neoforge` are all valid ways to do it. For other mod loaders, the `neoforge` bit would be replaced with `forge`, `fabric`, `quilt` or whatever different mod loader you might be developing on alongside NeoForge. -### Maven备注 +### A Note on Maven -Maven——用于依赖托管的系统,其版本控制系统在一些细节上与语义化版本控制不同(尽管基本的`major.minor.patch`模式仍然相同)。NeoForge的某些部分使用了相关的[Maven版本范围(MVR)][mvr]系统。在选择您的版本控制方案时,您应确保它与MVR兼容,否则模组将无法依赖您模组的特定版本! +Maven, the system used for dependency hosting, uses a versioning system that differs from semver in some details (though the general `major.minor.patch` pattern remains the same). The related [Maven Versioning Range (MVR)][mvr] system is used in some places in NeoForge (see [above][neoforge]). When choosing your versioning scheme, you should make sure it is compatible with MVR, as otherwise, mods will not be able to depend on specific versions of your mod! [create]: https://www.curseforge.com/minecraft/mc-mods/create [infinite]: https://minecraft.wiki/w/Java_Edition_20w14∞ diff --git a/docs/gui/menus.md b/docs/gui/menus.md index 3d3c0513b..3744046cc 100644 --- a/docs/gui/menus.md +++ b/docs/gui/menus.md @@ -1,26 +1,22 @@ -根据您的要求,这里是对上述Markdown文档进行的中文翻译,尽量使语言流畅且专业: +# Menus ---- - -# 菜单 - -菜单是图形用户界面(GUI)后端的一种类型,它们处理与某些数据持有者交互的逻辑。菜单本身并不持有数据。它们是视图,允许用户间接修改内部数据持有者的状态。因此,数据持有者不应直接与任何菜单耦合,而是传递数据引用以调用和修改。 +Menus are one type of backend for Graphical User Interfaces, or GUIs; they handle the logic involved in interacting with some represented data holder. Menus themselves are not data holders. They are views which allow to user to indirectly modify the internal data holder state. As such, a data holder should not be directly coupled to any menu, instead passing in the data references to invoke and modify. ## `MenuType` -菜单是动态创建和删除的,因此它们不是注册对象。因此,另一个工厂对象被注册以便轻松创建和引用菜单的*类型*。对于一个菜单,这些就是`MenuType`。 +Menus are created and removed dynamically and as such are not registry objects. As such, another factory object is registered instead to easily create and refer to the *type* of the menu. For a menu, these are `MenuType`s. -`MenuType`必须[注册]。 +`MenuType`s must be [registered]. ### `MenuSupplier` -通过将`MenuSupplier`和`FeatureFlagSet`传递给其构造函数来创建`MenuType`。`MenuSupplier`代表一个函数,它接受容器的id和查看菜单的玩家的库存,返回一个新创建的[`AbstractContainerMenu`][acm]。 +A `MenuType` is created by passing in a `MenuSupplier` and a `FeatureFlagSet` to its constructor. A `MenuSupplier` represents a function which takes in the id of the container and the inventory of the player viewing the menu, and returns a newly created [`AbstractContainerMenu`][acm]. ```java -// 对于某个DeferredRegister> REGISTER +// For some DeferredRegister> REGISTER public static final RegistryObject> MY_MENU = REGISTER.register("my_menu", () -> new MenuType(MyMenu::new, FeatureFlags.DEFAULT_FLAGS)); -// 在MyMenu中,一个AbstractContainerMenu子类 +// In MyMenu, an AbstractContainerMenu subclass public MyMenu(int containerId, Inventory playerInv) { super(MY_MENU.get(), containerId); // ... @@ -28,118 +24,116 @@ public MyMenu(int containerId, Inventory playerInv) { ``` :::note -容器标识符对每个玩家是唯一的。这意味着在两个不同的玩家上相同的容器id将代表两个不同的菜单,即使他们正在查看同一个数据持有者。 +The container identifier is unique for an individual player. This means that the same container id on two different players will represent two different menus, even if they are viewing the same data holder. ::: -`MenuSupplier`通常负责在客户端创建菜单,使用虚拟数据引用来存储和交互服务器数据持有者同步的信息。 +The `MenuSupplier` is usually responsible for creating a menu on the client with dummy data references used to store and interact with the synced information from the server data holder. ### `IContainerFactory` -如果客户端需要额外的信息(例如,数据持有者在世界中的位置),则可以使用子类`IContainerFactory`。除了容器id和玩家库存外,这还提供了一个`FriendlyByteBuf`,可以存储从服务器发送的额外信息。可以通过`IForgeMenuType#create`使用`IContainerFactory`创建`MenuType`。 +If additional information is needed on the client (e.g. the position of the data holder in the world), then the subclass `IContainerFactory` can be used instead. In addition to the container id and the player inventory, this also provides a `FriendlyByteBuf` which can store additional information that was sent from the server. A `MenuType` can be created using an `IContainerFactory` via `IForgeMenuType#create`. ```java -// 对于某个DeferredRegister> REGISTER +// For some DeferredRegister> REGISTER public static final RegistryObject> MY_MENU_EXTRA = REGISTER.register("my_menu_extra", () -> IForgeMenuType.create(MyMenu::new)); -// 在MyMenuExtra中,一个AbstractContainerMenu子类 +// In MyMenuExtra, an AbstractContainerMenu subclass public MyMenuExtra(int containerId, Inventory playerInv, FriendlyByteBuf extraData) { super(MY_MENU_EXTRA.get(), containerId); - // 存储缓冲区的额外数据 + // Store extra data from buffer // ... } ``` ## `AbstractContainerMenu` -所有菜单都继承自`AbstractContainerMenu`。一个菜单需要两个参数,[`MenuType`][mt],代表菜单本身的类型,以及容器id,代表当前访问者的菜单的唯一标识符。 +All menus are extended from `AbstractContainerMenu`. A menu takes in two parameters, the [`MenuType`][mt], which represents the type of the menu itself, and the container id, which represents the unique identifier of the menu for the current accessor. :::caution -玩家一次最多只能打开100个唯一的菜单。 +The player can only have 100 unique menus open at once. ::: -每个菜单应包含两个构造函数:一个用于在服务器上初始化菜单,另一个用于在客户端初始化菜单。用于初始化客户端菜单的构造函数是提供给`MenuType`的。 +Each menu should contain two constructors: one used to initialize the menu on the server and one used to initialize the menu on the client. The constructor used to initialize the menu on the client is the one supplied to the `MenuType`. Any fields that the server menu constructor contains should have some default for the client menu constructor. ```java -// 客户端菜单构造函数 +// Client menu constructor public MyMenu(int containerId, Inventory playerInventory) { this(containerId, playerInventory); } -// 服务器菜单构造函数 +// Server menu constructor public MyMenu(int containerId, Inventory playerInventory) { // ... } ``` -每个菜单实现必须实现两个方法:`#stillValid`和[`#quickMoveStack`][qms]。 - -### `#stillValid`和`ContainerLevelAccess` +Each menu implementation must implement two methods: `#stillValid` and [`#quickMoveStack`][qms]. -`#stillValid` +### `#stillValid` and `ContainerLevelAccess` -确定是否应该为给定玩家保持菜单打开。这通常指向静态的`#stillValid`,它需要一个`ContainerLevelAccess`、玩家和菜单所附属的`Block`。客户端菜单必须始终为此方法返回`true`,这是静态`#stillValid`的默认设置。此实现检查玩家是否在数据存储对象所在位置的八个方块范围内。 +`#stillValid` determines whether the menu should remain open for a given player. This is typically directed to the static `#stillValid` which takes in a `ContainerLevelAccess`, the player, and the `Block` this menu is attached to. The client menu must always return `true` for this method, which the static `#stillValid` does default to. This implementation checks whether the player is within eight blocks of where the data storage object is located. -`ContainerLevelAccess`提供了当前级别和块所在位置的封闭范围。在服务器上构造菜单时,可以通过调用`ContainerLevelAccess#create`创建新的访问权限。客户端菜单构造函数可以传递`ContainerLevelAccess#NULL`,这将不做任何事。 +A `ContainerLevelAccess` supplies the current level and location of the block within an enclosed scope. When constructing the menu on the server, a new access can be created by calling `ContainerLevelAccess#create`. The client menu constructor can pass in `ContainerLevelAccess#NULL`, which will do nothing. ```java -// 客户端菜单构造函数 +// Client menu constructor public MyMenuAccess(int containerId, Inventory playerInventory) { this(containerId, playerInventory, ContainerLevelAccess.NULL); } -// 服务器菜单构造函数 +// Server menu constructor public MyMenuAccess(int containerId, Inventory playerInventory, ContainerLevelAccess access) { // ... } -// 假设此菜单附属于RegistryObject MY_BLOCK +// Assume this menu is attached to RegistryObject MY_BLOCK @Override public boolean stillValid(Player player) { return AbstractContainerMenu.stillValid(this.access, player, MY_BLOCK.get()); } ``` -### 数据同步 +### Data Synchronization -需要在服务器和客户端上存在某些数据才能显示给玩家。为此,菜单实现了基本的数据同步层,以便每当当前数据与上次同步到客户端的数据不匹配时进行检查。对于玩家来说,这每个游戏刻都会检查。 +Some data needs to be present on both the server and the client to display to the player. To do this, the menu implements a basic layer of data synchronization such that whenever the current data does not match the data last synced to the client. For players, this is checked every tick. -Minecraft默认支持两种数据同步形式:通过`Slot`的`ItemStack`和通过`DataSlot`的整数。`Slot`和`DataSlot`是持有可以在屏幕上被玩家修改的数据存储引用的视图,前提是动作有效。这些可以通过在构造函数中的`#addSlot`和`#addDataSlot`添加到菜单中。 +Minecraft supports two forms of data synchronization by default: `ItemStack`s via `Slot`s and integers via `DataSlot`s. `Slot`s and `DataSlot`s are views which hold references to data storages that can be be modified by the player in a screen, assuming the action is valid. These can be added to a menu within the constructor through `#addSlot` and `#addDataSlot`. :::note -由于NeoForge不推荐使用`Container`,转而使用[`IItemHandler`能力][cap],下文将围绕使用这种能力的变体:`SlotItemHandler`进行解释。 +Since `Container`s used by `Slot`s are deprecated by NeoForge in favor of using the [`IItemHandler` capability][cap], the rest of the explanation will revolve around using the capability variant: `SlotItemHandler`. ::: -`SlotItemHandler`包含四个参数:代表堆栈所在库存的`IItemHandler`,此槽特别代表的堆栈的索引,以及槽在屏幕上相对于`AbstractContainerScreen#leftPos`和`#topPos`的左上位置的x和y位置。客户端菜单构造函数应始终提供一个相同大小的空库存实例。 +A `SlotItemHandler` contains four parameters: the `IItemHandler` representing the inventory the stacks are within, the index of the stack this slot is specifically representing, and the x and y position of where the top-left position of the slot will render on the screen relative to `AbstractContainerScreen#leftPos` and `#topPos`. The client menu constructor should always supply an empty instance of an inventory of the same size. -在大多数情况下,首先添加菜单包含的任何槽,然后是玩家的库存,最后是玩家的快捷栏。要从菜单访问任何单独的`Slot`,必须根据添加槽的顺序计算索引。 +In most cases, any slots the menu contains is first added, followed by the player's inventory, and finally concluded with the player's hotbar. To access any individual `Slot` from the menu, the index must be calculated based upon the order of which slots were added. -`DataSlot`是一个抽象类,应实现getter和setter以引用数据存储对象中存储的数据。客户端菜单构造函数应始终通过`DataSlot#standalone`提供一个新实例。 +A `DataSlot` is an abstract class which should implement a getter and setter to reference the data stored in the data storage object. The client menu constructor should always supply a new instance via `DataSlot#standalone`. -这些,连同槽一起,应在每次初始化新菜单时重新创建。 +These, along with slots, should be recreated every time a new menu is initialized. :::caution -尽管`DataSlot`存储一个整数,但由于其通过网络发送值的方式,它实际上限制为一个**短整型**(-32768至32767)。整数的16个高阶位被忽略。 +Although a `DataSlot` stores an integer, it is effectively limited to a **short** (-32768 to 32767) because of how it sends the value across the network. The 16 high-order bits of the integer are ignored. ::: ```java -// 假设我们有一个大小为5的数据对象库存 -// 假设每次服务器菜单初始化时都构建了一个DataSlot +// Assume we have an inventory from a data object of size 5 +// Assume we have a DataSlot constructed on each initialization of the server menu -// 客户端菜单构造函数 +// Client menu constructor public MyMenuAccess(int containerId, Inventory playerInventory) { this(containerId, playerInventory, new ItemStackHandler(5), DataSlot.standalone()); } -// 服务器菜单构造函数 +// Server menu constructor public MyMenuAccess(int containerId, Inventory playerInventory, IItemHandler dataInventory, DataSlot dataSingle) { - // 检查数据库存大小是否为某个固定值 - // 然后,为数据库存添加槽位 + // Check if the data inventory size is some fixed value + // Then, add slots for data inventory this.addSlot(new SlotItemHandler(dataInventory, /*...*/)); - // 为玩家库存添加槽位 + // Add slots for player inventory this.addSlot(new Slot(playerInventory, /*...*/)); - // 为处理的整数添加数据槽位 + // Add data slots for handled integers this.addDataSlot(dataSingle); // ... @@ -148,22 +142,22 @@ public MyMenuAccess(int containerId, Inventory playerInventory, IItemHandler dat #### `ContainerData` -如果需要将多个整数同步到客户端,可以使用`ContainerData`来引用这些整数。这个接口功能类似于索引查找,其中每个索引代表一个不同的整数。如果将`ContainerData`通过`#addDataSlots`方法添加到菜单中,则可以直接在数据对象本身中构建`ContainerData`。该方法为接口指定的数据量创建新的`DataSlot`。客户端菜单构造函数应始终通过`SimpleContainerData`提供新实例。 +If multiple integers need to be synced to the client, a `ContainerData` can be used to reference the integers instead. This interface functions as an index lookup such that each index represents a different integer. `ContainerData`s can also be constructed in the data object itself if the `ContainerData` is added to the menu through `#addDataSlots`. The method creates a new `DataSlot` for the amount of data specified by the interface. The client menu constructor should always supply a new instance via `SimpleContainerData`. ```java -// 假设我们有一个大小为3的ContainerData +// Assume we have a ContainerData of size 3 -// 客户端菜单构造函数 +// Client menu constructor public MyMenuAccess(int containerId, Inventory playerInventory) { this(containerId, playerInventory, new SimpleContainerData(3)); } -// 服务器菜单构造函数 +// Server menu constructor public MyMenuAccess(int containerId, Inventory playerInventory, ContainerData dataMultiple) { - // 检查ContainerData大小是否为某个固定值 + // Check if the ContainerData size is some fixed value checkContainerDataCount(dataMultiple, 3); - // 为处理的整数添加数据槽位 + // Add data slots for handled integers this.addDataSlots(dataMultiple); // ... @@ -171,137 +165,139 @@ public MyMenuAccess(int containerId, Inventory playerInventory, ContainerData da ``` :::caution -由于`ContainerData`委托给`DataSlot`,这些也被限制为一个**短整型**(-32768至32767)。 +As `ContainerData` delegates to `DataSlot`s, these are also limited to a **short** (-32768 to 32767). ::: #### `#quickMoveStack` -`#quickMoveStack`是任何菜单必须实现的第二个方法。每当一个堆栈被快速移动或通过Shift点击从其当前槽中移出时,就会调用此方法,直到堆栈完全移出其前一个槽或没有其他地方可以放置堆栈为止。该方法返回被快速移动的槽中的堆栈副本。 - -通常使用 `#moveItemStackTo` 在插槽之间移动堆叠物品,该方法将堆叠物品移动到第一个可用的插槽。它接受要移动的堆叠物品、要尝试将堆叠物品移动到的第一个插槽索引(包括)、最后一个插槽索引(不包括),以及是否从第一个到最后一个插槽进行检查(当为 `false` 时)或从最后一个到第一个插槽进行检查(当为 `true` 时)。 +`#quickMoveStack` is the second method that must be implemented by any menu. This method is called whenever a stack has been shift-clicked, or quick moved, out of its current slot until the stack has been fully moved out of its previous slot or there is no other place for the stack to go. The method returns a copy of the stack in the slot being quick moved. -在 Minecraft 的各种实现中,该方法在逻辑上相当一致: +Stacks are typically moved between slots using `#moveItemStackTo`, which moves the stack into the first available slot. It takes in the stack to be moved, the first slot index (inclusive) to try and move the stack to, the last slot index (exclusive), and whether to check the slots from first to last (when `false`) or from last to first (when `true`). + +Across Minecraft implementations, this method is fairly consistent in its logic: ```java -// 假设我们有一个大小为 5 的数据存储库 -// 存储库有 4 个输入插槽(索引 1 - 4),输出到一个结果插槽(索引 0) -// 我们还有 27 个玩家存储库插槽和 9 个快捷栏插槽 -// 因此,实际插槽的索引如下: -// - 数据存储库:结果(0)、输入(1 - 4) -// - 玩家存储库(5 - 31) -// - 玩家快捷栏(32 - 40) +// Assume we have a data inventory of size 5 +// The inventory has 4 inputs (index 1 - 4) which outputs to a result slot (index 0) +// We also have the 27 player inventory slots and the 9 hotbar slots +// As such, the actual slots are indexed like so: +// - Data Inventory: Result (0), Inputs (1 - 4) +// - Player Inventory (5 - 31) +// - Player Hotbar (32 - 40) @Override public ItemStack quickMoveStack(Player player, int quickMovedSlotIndex) { - // 快速移动的插槽堆叠物品 + // The quick moved slot stack ItemStack quickMovedStack = ItemStack.EMPTY; - // 快速移动的插槽 + // The quick moved slot Slot quickMovedSlot = this.slots.get(quickMovedSlotIndex) - // 如果插槽在有效范围内且插槽不为空 + // If the slot is in the valid range and the slot is not empty if (quickMovedSlot != null && quickMovedSlot.hasItem()) { - // 获取要移动的原始堆叠物品 + // Get the raw stack to move ItemStack rawStack = quickMovedSlot.getItem(); - // 将插槽堆叠设置为原始堆叠的副本 + // Set the slot stack to a copy of the raw stack quickMovedStack = rawStack.copy(); /* - 以下快速移动逻辑可以简化为在数据存储库中,尝试移动到玩家存储库/快捷栏,反之亦然,对于无法转换数据的容器(例如箱子)。 + The following quick move logic can be simplified to if in data inventory, + try to move to player inventory/hotbar and vice versa for containers + that cannot transform data (e.g. chests). */ - // 如果快速移动在数据存储库结果插槽上执行 + // If the quick move was performed on the data inventory result slot if (quickMovedSlotIndex == 0) { - // 尝试将结果插槽移动到玩家存储库/快捷栏 + // Try to move the result slot into the player inventory/hotbar if (!this.moveItemStackTo(rawStack, 5, 41, true)) { - // 如果无法移动,不再快速移动 + // If cannot move, no longer quick move return ItemStack.EMPTY; } - // 对结果插槽快速移动执行逻辑 + // Perform logic on result slot quick move slot.onQuickCraft(rawStack, quickMovedStack); } - // 否则如果快速移动在玩家存储库或快捷栏插槽上执行 + // Else if the quick move was performed on the player inventory or hotbar slot else if (quickMovedSlotIndex >= 5 && quickMovedSlotIndex < 41) { - // 尝试将玩家存储库/快捷栏插槽移动到数据存储库输入插槽 + // Try to move the inventory/hotbar slot into the data inventory input slots if (!this.moveItemStackTo(rawStack, 1, 5, false)) { - // 如果无法移动且在玩家存储库插槽中,尝试移动到快捷栏 + // If cannot move and in player inventory slot, try to move to hotbar if (quickMovedSlotIndex < 32) { if (!this.moveItemStackTo(rawStack, 32, 41, false)) { - // 如果无法移动,不再快速移动 + // If cannot move, no longer quick move return ItemStack.EMPTY; } } - // 否则尝试将快捷栏移动到玩家存储库插槽 + // Else try to move hotbar into player inventory slot else if (!this.moveItemStackTo(rawStack, 5, 32, false)) { - // 如果无法移动,不再快速移动 + // If cannot move, no longer quick move return ItemStack.EMPTY; } } } - // 否则如果快速移动在数据存储库输入插槽上,则尝试移动到玩家存储库/快捷栏 + // Else if the quick move was performed on the data inventory input slots, try to move to player inventory/hotbar else if (!this.moveItemStackTo(rawStack, 5, 41, false)) { - // 如果无法移动,不再快速移动 + // If cannot move, no longer quick move return ItemStack.EMPTY; } if (rawStack.isEmpty()) { - // 如果原始堆叠完全移出插槽,则将插槽设置为空堆叠 + // If the raw stack has completely moved out of the slot, set the slot to the empty stack quickMovedSlot.set(ItemStack.EMPTY); } else { - // 否则,通知插槽堆叠数量已更改 + // Otherwise, notify the slot that that the stack count has changed quickMovedSlot.setChanged(); } /* - 如果菜单不表示可以转换堆叠的容器(例如箱子),则可以删除以下 if 语句和 Slot#onTake 调用。 + The following if statement and Slot#onTake call can be removed if the + menu does not represent a container that can transform stacks (e.g. + chests). */ if (rawStack.getCount() == quickMovedStack.getCount()) { - // 如果原始堆叠无法移动到另一个插槽,则不再快速移动 + // If the raw stack was not able to be moved to another slot, no longer quick move return ItemStack.EMPTY; } - // 执行堆叠剩余部分后移动的逻辑 + // Execute logic on what to do post move with the remaining stack quickMovedSlot.onTake(player, rawStack); } - return quickMovedStack; // 返回插槽堆叠 + return quickMovedStack; // Return the slot stack } ``` -## 打开菜单 +## Opening a Menu -一旦菜单类型已注册,菜单本身已完成,并且 [screen] 已附加,玩家就可以打开菜单。可以通过在逻辑服务器上调用 `NetworkHooks#openScreen` 来打开菜单。该方法接受打开菜单的玩家、服务器端菜单的 `MenuProvider`,以及可选的 `FriendlyByteBuf`,如果需要向客户端同步额外数据。 +Once a menu type has been registered, the menu itself has been finished, and a [screen] has been attached, a menu can then be opened by the player. Menus can be opened by calling `NetworkHooks#openScreen` on the logical server. The method takes in the player opening the menu, the `MenuProvider` of the server side menu, and optionally a `FriendlyByteBuf` if extra data needs to be synced to the client. :::note -只有在使用 [`IContainerFactory`][icf] 创建菜单类型时,才应使用带有 `FriendlyByteBuf` 参数的 `NetworkHooks - -#openScreen`。 +`NetworkHooks#openScreen` with the `FriendlyByteBuf` parameter should only be used if a menu type was created using an [`IContainerFactory`][icf]. ::: #### `MenuProvider` -`MenuProvider` 是一个包含两个方法的接口:`#createMenu`,用于创建菜单的服务器实例,以及 `#getDisplayName`,返回包含菜单标题的组件以传递给 [screen]。`#createMenu` 方法包含三个参数:菜单的容器 ID、打开菜单的玩家的库存,以及打开菜单的玩家。 +A `MenuProvider` is an interface that contains two methods: `#createMenu`, which creates the server instance of the menu, and `#getDisplayName`, which returns a component containing the title of the menu to pass to the [screen]. The `#createMenu` method contains three parameter: the container id of the menu, the inventory of the player who opened the menu, and the player who opened the menu. -可以使用 `SimpleMenuProvider` 轻松创建 `MenuProvider`,它接受一个方法引用来创建服务器菜单和菜单的标题。 +A `MenuProvider` can easily be created using `SimpleMenuProvider`, which takes in a method reference to create the server menu and the title of the menu. ```java -// 在某些实现中 +// In some implementation NetworkHooks.openScreen(serverPlayer, new SimpleMenuProvider( (containerId, playerInventory, player) -> new MyMenu(containerId, playerInventory), Component.translatable("menu.title.examplemod.mymenu") )); ``` -### 常见实现 +### Common Implementations -通常通过某种玩家交互方式(例如右键单击方块或实体)打开菜单。 +Menus are typically opened on a player interaction of some kind (e.g. when a block or entity is right-clicked). -#### 方块实现 +#### Block Implementation -方块通常通过覆盖 `BlockBehaviour#use` 来实现菜单。如果在逻辑客户端上,交互返回 `InteractionResult#SUCCESS`。否则,打开菜单并返回 `InteractionResult#CONSUME`。 +Blocks typically implement a menu by overriding `BlockBehaviour#use`. If on the logical client, the interaction returns `InteractionResult#SUCCESS`. Otherwise, it opens the menu and returns `InteractionResult#CONSUME`. -`MenuProvider` 应该通过覆盖 `BlockBehaviour#getMenuProvider` 来实现。原版方法使用此方法在旁观模式下查看菜单。 +The `MenuProvider` should be implemented by overriding `BlockBehaviour#getMenuProvider`. Vanilla methods use this to view the menu in spectator mode. ```java -// 在某个 Block 子类中 +// In some Block subclass @Override public MenuProvider getMenuProvider(BlockState state, Level level, BlockPos pos) { return new SimpleMenuProvider(/* ... */); @@ -317,12 +313,12 @@ public InteractionResult use(BlockState state, Level level, BlockPos pos, Player ``` :::note -这是实现逻辑的最简单方法,不是唯一的方法。如果希望方块仅在满足某些条件时打开菜单,则需要事先将一些数据同步到客户端,以返回 `InteractionResult#PASS` 或 `#FAIL`,如果条件不满足。 +This is the simplest way to implement the logic, not the only way. If you want the block to only open the menu under certain conditions, then some data will need to be synced to the client beforehand to return `InteractionResult#PASS` or `#FAIL` if the conditions are not met. ::: -#### 生物实现 +#### Mob Implementation -生物通常通过覆盖 `Mob#mobInteract` 来实现菜单。这与方块实现类似,唯一的区别是生物本身应该实现 `MenuProvider` 以支持旁观模式查看。 +Mobs typically implement a menu by overriding `Mob#mobInteract`. This is done similarly to the block implementation with the only difference being that the `Mob` itself should implement `MenuProvider` to support spectator mode viewing. ```java public class MyMob extends Mob implements MenuProvider { @@ -339,7 +335,7 @@ public class MyMob extends Mob implements MenuProvider { ``` :::note -再次强调,这是实现逻辑的最简单方法,不是唯一的方法。 +Once again, this is the simplest way to implement the logic, not the only way. ::: [registered]: ../concepts/registries.md#methods-for-registering diff --git a/docs/gui/screens.md b/docs/gui/screens.md index 8824e99b5..4bde01e18 100644 --- a/docs/gui/screens.md +++ b/docs/gui/screens.md @@ -1,237 +1,233 @@ -## 屏幕 +# Screens -在 Minecraft 中,屏幕通常是所有图形用户界面(GUI)的基础,用于接收用户输入、在服务器上验证输入,并将结果同步回客户端。它们可以与[菜单](menus)结合使用,创建用于类似库存的视图的通信网络,或者它们可以是独立的,由模组开发者通过自己的[网络](../networking/index.md)实现来处理。 +Screens are typically the base of all Graphical User Interfaces (GUIs) in Minecraft: taking in user input, verifying it on the server, and syncing the resulting action back to the client. They can be combined with [menus] to create an communication network for inventory-like views, or they can be standalone which modders can handle through their own [network] implementations. -屏幕由许多部分组成,这使得理解“屏幕”在 Minecraft 中实际上是什么变得困难。因此,本文档将介绍屏幕的每个组件以及它是如何应用的,然后讨论屏幕本身。 +Screens are made up of numerous parts, making it difficult to fully understand what a 'screen' actually is in Minecraft. As such, this document will go over each of the screen's components and how it is applied before discussing the screen itself. -## 相对坐标 +## Relative Coordinates -无论何时渲染任何东西,都需要一些标识符来指定其出现的位置。使用许多抽象化,Minecraft 的大多数渲染调用在一个坐标平面上接收 x、y 和 z 值。X 值从左到右增加,y 值从上到下增加,z 值从远到近增加。但是,这些坐标不是固定的范围。它们可以根据屏幕的大小和在选项中指定的比例而变化。因此,必须特别注意确保在渲染时的坐标值相对于可变的屏幕大小适当缩放。 +Whenever anything is rendered, there needs to be some identifier which specifies where it will appear. With numerous abstractions, most of Minecraft's rendering calls takes in an x, y, and z value in a coordinate plane. X values increase from left to right, y from top to bottom, and z from far to near. However, the coordinates are not fixed to a specified range. They can change depending on the size of the screen and the scale at which is specified within the options. As such, extra care must be taken to make sure the values of the coordinates while rendering scale properly to the changeable screen size. -有关如何使您的坐标相对的信息将在[屏幕](#屏幕)部分中提供。 +Information on how to relativize your coordinates will be within the [screen] section. :::caution -如果选择使用固定坐标或不正确地缩放屏幕,则渲染的对象可能会看起来奇怪或错位。检查坐标是否正确相对化的简单方法是单击您的视频设置中的“GUI 比例”按钮。此值用作除数以确定 GUI 应以哪个比例渲染。 +If you choose to use fixed coordinates or incorrectly scale the screen, the rendered objects may look strange or misplaced. An easy way to check if you relativized your coordinates correctly is to click the 'Gui Scale' button in your video settings. This value is used as the divisor to the width and height of your display when determining the scale at which a GUI should render. ::: -## GUI 图形 +## Gui Graphics -Minecraft 中渲染的任何 GUI 通常都是使用 `GuiGraphics` 进行的。`GuiGraphics` 是几乎所有渲染方法的第一个参数;它包含用于渲染常用对象的基本方法。这些方法分为五类:彩色矩形、字符串和纹理、物品和工具提示。还有一个用于渲染组件的额外方法(`#enableScissor` / `#disableScissor`)。`GuiGraphics` 还公开了 `PoseStack`,它应用了必要的转换,以便将组件正确渲染到屏幕上。此外,颜色采用[ARGB](https://en.wikipedia.org/wiki/RGBA_color_model#ARGB32)格式。 +Any GUI rendered by Minecraft is typically done using `GuiGraphics`. `GuiGraphics` is the first parameter to almost all rendering methods; it contains basic methods to render commonly used objects. These fall into five categories: colored rectangles, strings, and textures, items, and tooltips. There is also an additional method for rendering a snippet of a component (`#enableScissor` / `#disableScissor`). `GuiGraphics` also exposes the `PoseStack` which applies the transformations necessary to properly render where the component should be rendered. Additionally, colors are in the [ARGB][argb] format. -### 彩色矩形 +### Colored Rectangles -通过位置颜色着色器绘制彩色矩形。可以绘制三种类型的彩色矩形。 +Colored rectangles are drawn through a position color shader. There are three types of colored rectangles that can be drawn. -首先是水平和垂直的一像素宽线,分别是 `#hLine` 和 `#vLine`。`#hLine` 接受两个 x 坐标,定义了左侧和右侧(包括在内),顶部 y 坐标和颜色。`#vLine` 接受左侧 x 坐标、两个 y 坐标,定义了顶部和底部(包括在内),以及颜色。 +First, there is a colored horizontal and vertical one-pixel wide line, `#hLine` and `#vLine` respectively. `#hLine` takes in two x coordinates defining the left and right (inclusively), the top y coordinate, and the color. `#vLine` takes in the left x coordinate, two y coordinates defining the top and bottom (inclusively), and the color. -其次是 `#fill` 方法,它绘制一个矩形到屏幕上。线方法内部调用此方法。它接受左侧 x 坐标、顶部 y 坐标、右侧 x 坐标、底部 y 坐标和颜色。 +Second, there is the `#fill` method, which draws a rectangle to the screen. The line methods internally call this method. This takes in the left x coordinate, the top y coordinate, the right x coordinate, the bottom y coordinate, and the color. -最后是 `#fillGradient` 方法,它绘制一个具有垂直渐变的矩形。它接受右侧 x 坐标、底部 y 坐标、左侧 x 坐标、顶部 y 坐标、z 坐标、底部和顶部颜色。 +Finally, there is the `#fillGradient` method, which draws a rectangle with a vertical gradient. This takes in the right x coordinate, the bottom y coordinate, the left x coordinate, the top y coordinate, the z coordinate, and the bottom and top colors. -### 字符串 +### Strings -字符串通过其 `Font` 绘制,通常包含正常、透明和偏移模式的自己的着色器。可以渲染两种对齐的字符串,每种字符串都带有背景阴影:左对齐字符串 (`#drawString`) 和居中对齐字符串 (`#drawCenteredString`)。这两种方法都接受要绘制字符串的字体、字符串本身、x 坐标(分别是字符串的左侧或中心)、顶部 y 坐标和颜色。 +Strings are drawn through its `Font`, typically consisting of their own shaders for normal, see through, and offset mode. There are two alignment of strings that can be rendered, each with a back shadow: a left-aligned string (`#drawString`) and a center-aligned string (`#drawCenteredString`). These both take in the font the string will be rendered in, the string to draw, the x coordinate representing the left or center of the string respectively, the top y coordinate, and the color. :::note -字符串通常应该作为 [`Component`](../resources/client/i18n.md#components) 传递,因为它们处理各种用例,包括该方法的其他两个重载。 +Strings should typically be passed in as [`Component`s][component] as they handle a variety of usecases, including the two other overloads of the method. ::: -### 纹理 +### Textures -纹理通过贴图绘制,因此方法名为 `#blit`,在这种情况下,它复制图像的 +Textures are drawn through blitting, hence the method name `#blit`, which, for this purpose, copies the bits of an image and draws them directly to the screen. These are drawn through a position texture shader. While there are many different `#blit` overloads, we will only discuss two static `#blit`s. -位并直接将其绘制到屏幕上。这些是通过位置纹理着色器绘制的。虽然有许多不同的 `#blit` 重载,但我们只讨论两种静态 `#blit`。 - -第一个静态 `#blit` 接受六个整数,假定正在渲染的纹理位于一个 256 x 256 的 PNG 文件中。它接受左侧 x 和顶部 y 屏幕坐标,PNG 内部的左侧 x 和顶部 y 坐标,以及要渲染的图像的宽度和高度。 +The first static `#blit` takes in six integers and assumes the texture being rendered is on a 256 x 256 PNG file. It takes in the left x and top y screen coordinate, the left x and top y coordinate within the PNG, and the width and height of the image to render. :::tip -必须指定 PNG 文件的大小,以便将坐标归一化为获取关联的 UV 值。 +The size of the PNG file must be specified so that the coordinates can be normalized to obtain the associated UV values. ::: -第一个调用的静态 `#blit` 将此扩展为九个整数,仅假定图像位于 PNG 文件中。它接受左侧 x 和顶部 y 屏幕坐标,z 坐标(称为 blit 偏移量),PNG 内部的左侧 x 和顶部 y 坐标,要渲染的图像的宽度和高度,以及 PNG 文件的宽度和高度。 +The static `#blit` which the first calls expands this to nine integers, only assuming the image is on a PNG file. It takes in the left x and top y screen coordinate, the z coordinate (referred to as the blit offset), the left x and top y coordinate within the PNG, the width and height of the image to render, and the width and height of the PNG file. -#### Blit 偏移量 +#### Blit Offset -在渲染纹理时,z 坐标通常设置为 blit 偏移量。偏移量负责在查看屏幕时正确分层渲染。具有较小 z 坐标的渲染在背景中渲染,反之,具有较大 z 坐标的渲染在前景中渲染。可以通过 `#translate` 方法直接在 `PoseStack` 本身上设置 z 偏移量。一些 `GuiGraphics` 方法内部应用了一些基本的偏移逻辑(例如物品渲染)。 +The z coordinate when rendering a texture is typically set to the blit offset. The offset is responsible for properly layering renders when viewing a screen. Renders with a smaller z coordinate are rendered in the background and vice versa where renders with a larger z coordinate are rendered in the foreground. The z offset can be set directly on the `PoseStack` itself via `#translate`. Some basic offset logic is applied internally in some methods of `GuiGraphics` (e.g. item rendering). :::caution -设置 blit 偏移量时,必须在渲染对象完成后重置它。否则,屏幕中的其他对象可能会以不正确的层次渲染,导致图形问题。建议在平移前推送当前姿势,然后在所有以偏移量渲染的对象完成后弹出。 +When setting the blit offset, you must reset it after rendering your object. Otherwise, other objects within the screen may be rendered in an incorrect layer causing graphical issues. It is recommended to push the current pose before translating and then popping after all rendering at the offset is completed. ::: -## 可渲染对象 +## Renderable -`Renderable` 实际上是指可以被渲染的对象。这些包括屏幕、按钮、聊天框、列表等。`Renderable` 只有一个方法:`#render`。此方法接受用于将事物渲染到屏幕上的 `GuiGraphics`、鼠标的 x 和 y 位置(按相对屏幕大小缩放)、以及帧之间的时间差(自上一帧以来经过了多少个 tick)。 +`Renderable`s are essentially objects that are rendered. These include screens, buttons, chat boxes, lists, etc. `Renderable`s only have one method: `#render`. This takes in the `GuiGraphics` used to render things to the screen, the x and y positions of the mouse scaled to the relative screen size, and the tick delta (how many ticks have passed since the last frame). -一些常见的可渲染对象是屏幕和“小部件”:通常渲染在屏幕上的可交互元素,如 `Button`、它的子类型 `ImageButton`,以及 `EditBox`,用于在屏幕上输入文本。 +Some common renderables are screens and 'widgets': interactable elements which typically render on the screen such as `Button`, its subtype `ImageButton`, and `EditBox` which is used to input text on the screen. -## GUI 事件侦听器 +## GuiEventListener -Minecraft 中呈现的任何屏幕都实现了 `GuiEventListener`。`GuiEventListener` 负责处理用户与屏幕的交互。这些包括鼠标(移动、点击、释放、拖动、滚动、悬停)和键盘(按下、释放、键入)的输入。每个方法返回关联动作是否成功地影响了屏幕。按钮、聊天框、列表等小部件也实现了此接口。 +Any screen rendered in Minecraft implements `GuiEventListener`. `GuiEventListener`s are responsible for handling user interaction with the screen. These include inputs from the mouse (movement, clicked, released, dragged, scrolled, mouseover) and keyboard (pressed, released, typed). Each method returns whether the associated action affected the screen successfully. Widgets like buttons, chat boxes, lists, etc. also implement this interface. -### 容器事件处理程序 +### ContainerEventHandler -与 `GuiEventListener` 几乎同义的是它们的子类型:`ContainerEventHandler`。这些负责处理在包含小部件的屏幕上的用户交互,管理当前聚焦的是哪个小部件以及如何应用相关交互。`ContainerEventHandler` 添加了三个额外功能:可交互的子元素、拖动和聚焦。 +Almost synonymous with `GuiEventListener`s are their subtype: `ContainerEventHandler`s. These are responsible for handling user interaction on screens which contain widgets, managing which is currently focused and how the associated interactions are applied. `ContainerEventHandler`s add three additional features: interactable children, dragging, and focusing. -事件处理程序保存了子元素,用于确定元素的交互顺序。在鼠标事件处理程序中(不包括拖动),鼠标悬停在上面的第一个子元素将执行其逻辑。 +Event handlers hold children which are used to determine the interaction order of elements. During the mouse event handlers (excluding dragging), the first child in the list that the mouse hovers over has their logic executed. -通过鼠标拖动元素,通过 `#mouseClicked` 和 `#mouseReleased` 实现更精确的逻辑。 +Dragging an element with the mouse, implemented via `#mouseClicked` and `#mouseReleased`, provides more precisely executed logic. -聚焦允许首先检查特定的子元素,并在事件执行期间处理它,例如在键盘事件或拖动鼠标期间。焦点通常通过 `#setFocused` 设置。此外,可以使用 `#nextFocusPath` 循环可交互的子元素,选择基于传递的 `FocusNavigationEvent` 的子元素。 +Focusing allows for a specific child to be checked first and handled during an event's execution, such as during keyboard events or dragging the mouse. Focus is typically set through `#setFocused`. In addition, interactable children can be cycled using `#nextFocusPath`, selecting the child based upon the `FocusNavigationEvent` passed in. :::note -屏幕通过 `AbstractContainerEventHandler` 实现了 `ContainerEventHandler`,它添加了拖动和聚焦子元素的设置和获取逻辑。 +Screens implement `ContainerEventHandler` through `AbstractContainerEventHandler`, which adds in the setter and getter logic for dragging and focusing children. ::: ## NarratableEntry -`NarratableEntry` 是可以通过 Minecraft 的辅助功能叙述功能讲述的元素。每个元素可以根据悬停或选择的内容提供不同的叙述,通常由焦点、悬停,然后是所有其他情况优先。 +`NarratableEntry`s are elements which can be spoken about through Minecraft's accessibility narration feature. Each element can provide different narration depending on what is hovered or selected, prioritized typically by focus, hovering, and then all other cases. -`NarratableEntry` 有三个方法:确定元素的优先级的一个方法 (`#narrationPriority`),确定是否说出叙述的一个方法 (`#isActive`),最后是向其关联输出提供叙述的一个方法 (`#updateNarration`)。 +`NarratableEntry`s have three methods: one which determines the priority of the element (`#narrationPriority`), one which determines whether to speak the narration (`#isActive`), and finally one which supplies the narration to its associated output, spoken or read (`#updateNarration`). :::note -Minecraft 中的所有小部件都是 `NarratableEntry`,因此通常不需要手动实现它,如果使用可用的子类型。 +All widgets from Minecraft are `NarratableEntry`s, so it typically does not need to be manually implemented if using an available subtype. ::: -## 屏幕子类型 +## The Screen Subtype -有了上述所有知识,就可以构建一个基本的屏幕了。为了更方便理解,屏幕的组件将按照它们通常出现的顺序而被提及。 +With all of the above knowledge, a basic screen can be constructed. To make it easier to understand, the components of a screen will be mentioned in the order they are typically encountered. -首先,所有的屏幕都需要一个组件 `Component` 来表示屏幕的标题。这个组件通常由其子类型绘制到屏幕上。它仅在基本屏幕中用于叙述消息。 +First, all screens take in a `Component` which represents the title of the screen. This component is typically drawn to the screen by one of its subtypes. It is only used in the base screen for the narration message. ```java -// 在某个屏幕的子类中 +// In some Screen subclass public MyScreen(Component title) { super(title); } ``` -### 初始化 +### Initialization -屏幕初始化后,将调用 `#init` 方法。`#init` 方法设置屏幕的初始设置,从 `ItemRenderer` 和 `Minecraft` 实例到由游戏缩放的相对宽度和高度。在这个方法中进行任何设置,比如添加小部件或预计算相对坐标。如果游戏窗口大小改变,将通过调用 `#init` 方法来重新初始化屏幕。 +Once a screen has been initialized, the `#init` method is called. The `#init` method sets the initial settings inside the screen from the `ItemRenderer` and `Minecraft` instance to the relative width and height as scaled by the game. Any setup such as adding widgets or precomputing relative coordinates should be done in this method. If the game window is resized, the screen will be reinitialized by calling the `#init` method. -有三种方法可以向屏幕添加小部件,每种方法都有不同的作用: +There are three ways to add a widget to a screen, each serving a separate purpose: -方法 | 描述 -:---: | :--- -`#addWidget` | 添加一个可交互且叙述的小部件,但不渲染。 -`#addRenderableOnly` | 添加一个仅渲染的小部件;不可交互也不叙述。 -`#addRenderableWidget` | 添加一个既可交互、叙述又可渲染的小部件。 +Method | Description +:---: | :--- +`#addWidget` | Adds a widget that is interactable and narrated, but not rendered. +`#addRenderableOnly` | Adds a widget that will only be rendered; it is not interactable or narrated. +`#addRenderableWidget` | Adds a widget that is interactable, narrated, and rendered. -通常情况下,最常用的是 `#addRenderableWidget`。 +Typically, `#addRenderableWidget` will be used most often. ```java -// 在某个屏幕的子类中 +// In some Screen subclass @Override protected void init() { super.init(); - // 添加小部件和预计算的值 + // Add widgets and precomputed values this.addRenderableWidget(new EditBox(/* ... */)); } ``` -### 屏幕更新 +### Ticking Screens -屏幕也会使用 `#tick` 方法进行更新,以执行一些用于渲染目的的客户端逻辑。最常见的示例是用于闪烁光标的 `EditBox`。 +Screens also tick using the `#tick` method to perform some level of client side logic for rendering purposes. The most common example is the `EditBox` for the blinking cursor. ```java -// 在某个屏幕的子类中 +// In some Screen subclass @Override public void tick() { super.tick(); - // 添加 EditBox 中的更新逻辑 + // Add ticking logic for EditBox in editBox this.editBox.tick(); } ``` -### 输入处理 +### Input Handling -由于屏幕是 `GuiEventListener` 的子类型,输入处理程序也可以被覆盖,例如处理特定的[按键][keymapping]逻辑。 +Since screens are subtypes of `GuiEventListener`s, the input handlers can also be overridden, such as for handling logic on a specific [key press][keymapping]. -### 屏幕渲染 +### Rendering the Screen -最后,屏幕通过作为 `Renderable` 子类型而提供的 `#render` 方法进行渲染。正如前面提到的,`#render` 方法在每一帧绘制屏幕上的所有内容,比如背景、小部件、工具提示等。默认情况下,`#render` 方法只会将小部件渲染到屏幕上。 +Finally, screens are rendered through the `#render` method provided by being a `Renderable` subtype. As mentioned, the `#render` method draws the everything the screen has to render every frame, such as the background, widgets, tooltips, etc. By default, the `#render` method only renders the widgets to the screen. -屏幕中通常不由子类型处理的两个最常见的渲染内容是背景和工具提示。 +The two most common things rendered within a screen that is typically not handled by a subtype is the background and the tooltips. -背景可以使用 `#renderBackground` 进行渲染,其中一个方法接受一个 v 偏移量,用于在渲染屏幕时绘制选项背景,当背后的级别无法显示时使用。 +The background can be rendered using `#renderBackground`, with one method taking in a v Offset for the options background whenever a screen is rendered when the level behind it cannot be. -工具提示通过 `GuiGraphics#renderTooltip` 或 `GuiGraphics#renderComponentTooltip` 进行渲染,它们可以接受正在渲染的文本组件、可选的自定义工具提示组件以及工具提示应该在屏幕上渲染的 x / y 相对坐标。 +Tooltips are rendered through `GuiGraphics#renderTooltip` or `GuiGraphics#renderComponentTooltip` which can take in the text components being rendered, an optional custom tooltip component, and the x / y relative coordinates on where the tooltip should be rendered on the screen. ```java -// 在某个屏幕的子类中 +// In some Screen subclass -// mouseX 和 mouseY 表示光标在屏幕上的缩放坐标 +// mouseX and mouseY indicate the scaled coordinates of where the cursor is in on the screen @Override public void render(GuiGraphics graphics, int mouseX, int mouseY, float partialTick) { - // 通常首先渲染背景 + // Background is typically rendered first this.renderBackground(graphics); - // 在小部件之前渲染东西(背景纹理) + // Render things here before widgets (background textures) - // 如果这是屏幕的直接子类,则渲染小部件 + // Then the widgets if this is a direct child of the Screen super.render(graphics, mouseX, mouseY, partialTick); - // 在小部件之后渲染东西(工具提示) + // Render things after widgets (tooltips) } ``` -### 关闭屏幕 +### Closing the Screen -当屏幕关闭时,两个方法处理清理工作:`#onClose` 和 `#removed`。 +When a screen is closed, two methods handle the teardown: `#onClose` and `#removed`. -`#onClose` 在用户输入关闭当前屏幕时调用。通常用作销毁和保存屏幕内部流程的回调。这包括向服务器发送数据包。 +`#onClose` is called whenever the user makes an input to close the current screen. This method is typically used as a callback to destroy and save any internal processes in the screen itself. This includes sending packets to the server. -`#removed` 在屏幕改变之前调用,并释放给垃圾收集器。这处理任何在屏幕打开之前未重置回初始状态的内容。 +`#removed` is called just before the screen changes and is released to the garbage collector. This handles anything that hasn't been reset back to its initial state before the screen was opened. ```java -// 在某个屏幕的子类中 +// In some Screen subclass @Override public void onClose() { - // 在这里停止任何处理程序 + // Stop any handlers here - // 最后调用,以防干扰覆盖 + // Call last in case it interferes with the override super.onClose(); } @Override public void removed() { - // 在这里重置初始状态 + // Reset initial states here - // 最后调用,以防干扰覆盖 + // Call last in case it interferes with the override super.removed() ;} ``` ## `AbstractContainerScreen` -如果一个屏幕直接附加到一个[菜单][menus]上,则应该使用 `AbstractContainerScreen` 的子类。`AbstractContainerScreen` 作为菜单的渲染器和输入处理程序,并包含与插槽同步和交互的逻辑。因此,通常只需要覆盖或实现两个方法,就可以有一个可工作的容器屏幕。再次强调,为了更易于理解,容器屏幕的组件将按照它们通常出现的顺序进行说明。 - -通常,`AbstractContainerScreen` 需要三个参数:被打开的容器菜单(由泛型 `T` 表示)、玩家库存(仅用于显示名称 +If a screen is directly attached to a [menu][menus], then an `AbstractContainerScreen` should be subclassed instead. An `AbstractContainerScreen` acts as the renderer and input handler of a menu and contains logic for syncing and interacting with slots. As such, only two methods typically need to be overridden or implemented to have a working container screen. Once again, to make it easier to understand, the components of a container screen will be mentioned in the order they are typically encountered. -)和屏幕本身的标题。在这里,可以设置一些定位字段: +An `AbstractContainerScreen` typically requires three parameters: the container menu being opened (represented by the generic `T`), the player inventory (only for the display name), and the title of the screen itself. Within here, a number of positioning fields can be set: -字段 | 描述 +Field | Description :---: | :--- -`imageWidth` | 背景使用的纹理的宽度。这通常位于一个 256 x 256 的 PNG 内,默认为 176。 -`imageHeight` | 背景使用的纹理的高度。这通常位于一个 256 x 256 的 PNG 内,默认为 166。 -`titleLabelX` | 屏幕标题将被渲染的相对 x 坐标。 -`titleLabelY` | 屏幕标题将被渲染的相对 y 坐标。 -`inventoryLabelX` | 玩家库存名称将被渲染的相对 x 坐标。 -`inventoryLabelY` | 玩家库存名称将被渲染的相对 y 坐标。 +`imageWidth` | The width of the texture used for the background. This is typically inside a PNG of 256 x 256 and defaults to 176. +`imageHeight` | The width of the texture used for the background. This is typically inside a PNG of 256 x 256 and defaults to 166. +`titleLabelX` | The relative x coordinate of where the screen title will be rendered. +`titleLabelY` | The relative y coordinate of where the screen title will be rendered. +`inventoryLabelX` | The relative x coordinate of where the player inventory name will be rendered. +`inventoryLabelY` | The relative y coordinate of where the player inventory name will be rendered. :::caution -在前面的部分中,提到应该在 `#init` 方法中设置预计算的相对坐标。这仍然适用,因为这里提到的值不是预计算的坐标,而是静态值和相对化的坐标。 +In a previous section, it mentioned that precomputed relative coordinates should be set in the `#init` method. This still remains true, as the values mentioned here are not precomputed coordinates but static values and relativized coordinates. -图像值是静态且不变的,因为它们代表背景纹理的大小。为了在渲染时更方便,`#init` 方法中预计算了两个附加值 (`leftPos` 和 `topPos`),标记了背景将被渲染的左上角位置。标签坐标是相对于这些值的。 +The image values are static and non changing as they represent the background texture size. To make things easier when rendering, two additional values (`leftPos` and `topPos`) are precomputed in the `#init` method which marks the top left corner of where the background will be rendered. The label coordinates are relative to these values. -`leftPos` 和 `topPos` 也被用作渲染背景的便捷方式,因为它们已经表示传递到 `#blit` 方法的位置。 +The `leftPos` and `topPos` is also used as a convenient way to render the background as they already represent the position to pass into the `#blit` method. :::caution ```java -// 在某个 AbstractContainerScreen 子类中 +// In some AbstractContainerScreen subclass public MyContainerScreen(MyMenu menu, Inventory playerInventory, Component title) { super(menu, playerInventory, title); @@ -239,99 +235,103 @@ public MyContainerScreen(MyMenu menu, Inventory playerInventory, Component title this.inventoryLabelX = 10; /* - * 如果 'imageHeight' 被改变,'inventoryLabelY' 也必须被 - * 改变,因为该值取决于 'imageHeight' 的值。 + * If the 'imageHeight' is changed, 'inventoryLabelY' must also be + * changed as the value depends on the 'imageHeight' value. */ } ``` -### 菜单访问 +### Menu Access -由于菜单被传递到屏幕中,现在可以通过 `menu` 字段访问任何在菜单中并通过插槽、数据插槽或自定义系统同步的值。 +As the menu is passed into the screen, any values that were within the menu and synced (either through slots, data slots, or a custom system) can now be accessed through the `menu` field. -### 容器更新 +### Container Tick -当玩家活着并且正在查看屏幕时,容器屏幕在 `#tick` 方法中进行更新,通过 `#containerTick` 进行。这基本上取代了容器屏幕内部的 `#tick` 方法,最常见的用法是进行食谱书的更新。 +Container screens tick within the `#tick` method when the player is alive and looking at the screen via `#containerTick`. This essentially takes the place of `#tick` within container screens, with its most common usage being to tick the recipe book. ```java -// 在某个 AbstractContainerScreen 子类中 +// In some AbstractContainerScreen subclass @Override protected void containerTick() { super.containerTick(); - // 在这里更新内容 + // Tick things here } ``` -### 渲染容器屏幕 +### Rendering the Container Screen -容器屏幕的渲染涉及三种方法:`#renderBg`,用于渲染背景纹理,`#renderLabels`,用于在背景上方渲染任何文本,以及 `#render`,它包含前两种方法,并提供了一个灰色背景和工具提示。 +The container screen is rendered across three methods: `#renderBg`, which renders the background textures, `#renderLabels`, which renders any text on top of the background, and `#render` which encompass the previous two methods in addition to providing a grayed out background and tooltips. -从 `#render` 开始,最常见的覆盖(通常是唯一的情况)添加了背景,调用 super 来渲染容器屏幕,并最后在其上渲染工具提示。 +Starting with `#render`, the most common override (and typically the only case) adds the background, calls the super to render the container screen, and finally renders the tooltips on top of it. ```java -// 在某个 AbstractContainerScreen 子类中 +// In some AbstractContainerScreen subclass @Override public void render(GuiGraphics graphics, int mouseX, int mouseY, float partialTick) { this.renderBackground(graphics); super.render(graphics, mouseX, mouseY, partialTick); /* - * 这个方法由容器屏幕添加,用于渲染鼠标悬停插槽的工具提示。 + * This method is added by the container screen to render + * the tooltip of the hovered slot. */ this.renderTooltip(graphics, mouseX, mouseY); } ``` -在 super 中,调用了 `#renderBg` 来渲染屏幕的背景。最标准的表示使用了三个方法调用:两个用于设置,一个用于绘制背景纹理。 +Within the super, `#renderBg` is called to render the background of the screen. The most standard representation uses three method calls: two for setup and one to draw the background texture. ```java -// 在某个 AbstractContainerScreen 子类中 +// In some AbstractContainerScreen subclass -// 背景纹理的位置(assets//) +// The location of the background texture (assets//) private static final ResourceLocation BACKGROUND_LOCATION = new ResourceLocation(MOD_ID, "textures/gui/container/my_container_screen.png"); @Override protected void renderBg(GuiGraphics graphics, float partialTick, int mouseX, int mouseY) { /* - * 设置着色器使用的纹理位置。虽然最多可以设置12个纹理,但 'blit' 中使用的着色器 - * 只查看第一个纹理索引。 + * Sets the texture location for the shader to use. While up to + * 12 textures can be set, the shader used within 'blit' only + * looks at the first texture index. */ RenderSystem.setShaderTexture(0, BACKGROUND_LOCATION); /* - * 将背景纹理渲染到屏幕上。'leftPos' 和 'topPos' 应该已经表示纹理应该渲染的 - * 左上角位置,因为它们是从 'imageWidth' 和 'imageHeight' 预计算出来的。两个 - * 零表示在 256 x 256 PNG 文件内的整数 u/v 坐标。 + * Renders the background texture to the screen. 'leftPos' and + * 'topPos' should already represent the top left corner of where + * the texture should be rendered as it was precomputed from the + * 'imageWidth' and 'imageHeight'. The two zeros represent the + * integer u/v coordinates inside the 256 x 256 PNG file. */ graphics.blit(BACKGROUND_LOCATION, this.leftPos, this.topPos, 0, 0, this.imageWidth, this.imageHeight); } ``` -最后,调用 `#renderLabels` 来在背景上方但工具提示下方渲染任何文本。这简单地使用字体来绘制关联的组件。 +Finally, `#renderLabels` is called to render any text above the background, but below the tooltips. This simply calls uses the font to draw the associated components. ```java -// 在某个 AbstractContainerScreen 子类中 +// In some AbstractContainerScreen subclass @Override protected void renderLabels(GuiGraphics graphics, int mouseX, int mouseY) { super.renderLabels(graphics, mouseX, mouseY); - // 假设我们有一些 Component 'label' - // 'label' 将被绘制在 'labelX' 和 'labelY' 处 + // Assume we have some Component 'label' + // 'label' is drawn at 'labelX' and 'labelY' graphics.drawString(this.font, this.label, this.labelX, this.labelY, 0x404040); } ``` :::note -在渲染标签时,**不需要**指定 `leftPos` 和 `topPos` 偏移量。这些已经在 `PoseStack` 中被转换,因此该方法内的所有内容都相对于这些坐标进行绘制。 +When rendering the label, you do **not** need to specify the `leftPos` and `topPos` offset. Those have already been translated within the `PoseStack` so everything within this method is drawn relative to those coordinates. ::: -## 注册 AbstractContainerScreen +## Registering an AbstractContainerScreen -要将 `AbstractContainerScreen` 与菜单一起使用,需要对其进行注册。可以在 [**mod 事件总线**][modbus] 的 `RegisterMenuScreensEvent` 中调用 `register` 方法来完成。 +To use an `AbstractContainerScreen` with a menu, it needs to be registered. This can be done by calling `register` within the `RegisterMenuScreensEvent` on the [**mod event bus**][modbus]. ```java -// 事件在 mod 事件总线上监听 +// Event is listened to on the mod event bus private void registerScreens(RegisterMenuScreensEvent event) { event.register(MY_MENU.get(), MyContainerScreen::new); } diff --git a/docs/items/bewlr.md b/docs/items/bewlr.md index 44f3d6d32..fcabedad8 100644 --- a/docs/items/bewlr.md +++ b/docs/items/bewlr.md @@ -1,22 +1,22 @@ -### BlockEntityWithoutLevelRenderer +BlockEntityWithoutLevelRenderer +======================= +`BlockEntityWithoutLevelRenderer` is a method to handle dynamic rendering on items. This system is much simpler than the old `ItemStack` system, which required a `BlockEntity`, and did not allow access to the `ItemStack`. -`BlockEntityWithoutLevelRenderer` 是一种处理物品的动态渲染方法。这个系统比旧的 `ItemStack` 系统简单得多,因为旧系统需要一个 `BlockEntity`,并且无法访问 `ItemStack`。 - -使用 BlockEntityWithoutLevelRenderer +Using BlockEntityWithoutLevelRenderer -------------------------- -BlockEntityWithoutLevelRenderer 允许你使用 `public void renderByItem(ItemStack itemStack, ItemDisplayContext ctx, PoseStack poseStack, MultiBufferSource bufferSource, int combinedLight, int combinedOverlay)` 来渲染你的物品。 +BlockEntityWithoutLevelRenderer allows you to render your item using `public void renderByItem(ItemStack itemStack, ItemDisplayContext ctx, PoseStack poseStack, MultiBufferSource bufferSource, int combinedLight, int combinedOverlay)`. -为了使用 BEWLR,`Item` 必须首先满足一个条件:它的模型对于 `BakedModel#isCustomRenderer` 返回 true。如果没有自定义渲染器,它将使用默认的 `ItemRenderer#getBlockEntityRenderer`。一旦返回 true,物品的 BEWLR 将被访问以进行渲染。 +In order to use an BEWLR, the `Item` must first satisfy the condition that its model returns true for `BakedModel#isCustomRenderer`. If it does not have one, it will use the default `ItemRenderer#getBlockEntityRenderer`. Once that returns true, the Item's BEWLR will be accessed for rendering. :::note -如果 `Block#getRenderShape` 设置为 `RenderShape#ENTITYBLOCK_ANIMATED`,`Block` 也会使用 BEWLR 进行渲染。 +`Block`s also render using a BEWLR if `Block#getRenderShape` is set to `RenderShape#ENTITYBLOCK_ANIMATED`. ::: -要为物品设置 BEWLR,必须在 `Item#initializeClient` 中消费 `IClientItemExtensions` 的匿名实例。在匿名实例中,应该重写 `IClientItemExtensions#getCustomRenderer` 以返回你的 BEWLR 的实例: +To set the BEWLR for an Item, an anonymous instance of `IClientItemExtensions` must be consumed within `Item#initializeClient`. Within the anonymous instance, `IClientItemExtensions#getCustomRenderer` should be overridden to return the instance of your BEWLR: ```java -// 在你的物品类中 +// In your item class @Override public void initializeClient(Consumer consumer) { consumer.accept(new IClientItemExtensions() { @@ -30,7 +30,7 @@ public void initializeClient(Consumer consumer) { ``` :::caution -每个模组应该只有一个自定义 BEWLR 实例。 +Each mod should only have one instance of a custom BEWLR. ::: -就是这样,使用 BEWLR 不需要额外的设置。 +That is it, no additional setup is necessary to use a BEWLR. diff --git a/docs/items/index.md b/docs/items/index.md index 5765e113e..0e1221d47 100644 --- a/docs/items/index.md +++ b/docs/items/index.md @@ -1,89 +1,87 @@ -# 物品 +# Items -除了方块外,物品是 Minecraft 的关键组成部分。方块构成了你周围的世界,而物品存在于物品栏中。 +Along with blocks, items are a key component of Minecraft. While blocks make up the world around you, items exist within inventories. -## 物品到底是什么? +## What Even Is an Item? -在我们进一步创建物品之前,了解物品究竟是什么,以及它与方块的区别是什么,是非常重要的。让我们通过一个例子来说明这一点: +Before we get further into creating items, it is important to understand what an item actually is, and what distinguishes it from, say, a [block][block]. Let's illustrate this using an example: -- 在游戏世界中,你遇到了一个泥土方块并想要挖掘它。这是一个 **方块**,因为它被放置在世界中。(实际上,它不是一个方块,而是一个方块状态。请参阅 [方块状态文章][blockstates] 以获取更详细的信息。) - - 并非所有方块在破坏时都会掉落自己(例如树叶),有关更多信息,请参阅 [战利品表][loottables] 文章。 -- 一旦你 [挖掘了方块][breaking],它就会被移除(即被替换为空气方块),并且泥土掉落。掉落的泥土是一个 **物品实体**。这意味着像其他实体(猪、僵尸、箭等)一样,它可以被水推动,或者被火和岩浆燃烧。 -- 一旦你捡起泥土物品实体,它就会成为你物品栏中的一个 **物品堆叠**。物品堆叠简单地说就是一个物品的实例,带有一些额外的信息,比如堆叠大小。 -- 物品堆叠由它们对应的 **物品**(我们正在创建的东西)支持,物品持有所有物品之间相同的信息(例如,每把铁剑的最大耐久度为 250),而物品堆叠持有在两个类似物品之间可能不同的信息(例如,一把铁剑剩余 100 次使用,而另一把铁剑剩余 200 次使用)。有关通过物品和物品堆叠执行的操作和通过物品堆叠执行的操作的更多信息,请继续阅读。 - - 物品与物品堆叠之间的关系大致与 [方块][block] 和 [方块状态][blockstates] 之间的关系相同,即方块状态始终由方块支持。这不是一个非常准确的比较(物品堆叠不是单例,例如),但它可以给出一个关于这里概念的好基本理解。 +- In the world, you encounter a dirt block and want to mine it. This is a **block**, because it is placed in the world. (Actually, it is not a block, but a blockstate. See the [Blockstates article][blockstates] for more detailed information.) + - Not all blocks drop themselves when breaking (e.g. leaves), see the article on [loot tables][loottables] for more information. +- Once you have [mined the block][breaking], it is removed (= replaced with an air block) and the dirt drops. The dropped dirt is an item **entity**. This means that like other entities (pigs, zombies, arrows, etc.), it can inherently be moved by things like water pushing on it, or burned by fire and lava. +- Once you pick up the dirt item entity, it becomes an **item stack** in your inventory. An item stack is, simply put, an instance of an item with some extra information, such as the stack size. +- Item stacks are backed by their corresponding **item** (which is what we're creating). Items hold information that is the same across all items (for example, every iron sword has a max durability of 250), while item stacks hold information that can be different between two similar items (for example, one iron sword has 100 uses left, while another iron sword has 200 uses left). For more information on what is done through items and what is done through item stacks, read on. + - The relationship between items and item stacks is roughly the same as between [blocks][block] and [blockstates][blockstates], in that a blockstate is always backed by a block. It's not a really accurate comparison (item stacks aren't singletons, for example), but it gives a good basic idea about what the concept is here. -## 创建一个物品 +## Creating an Item -现在我们了解了物品是什么,让我们创建一个吧! +Now that we understand what an item is, let's create one! -与基本方块一样,对于不需要特殊功能的基本物品(如棍子、糖等),可以直接使用 `Item` 类。为此,在注册期间,使用 `Item.Properties` 参数实例化 `Item`。可以使用 `Item.Properties#of` 创建此 `Item.Properties` 参数,并通过调用其方法来自定义它: +Like with basic blocks, for basic items that need no special functionality (think sticks, sugar, etc.), the `Item` class can be used directly. To do so, during registration, instantiate `Item` with a `Item.Properties` parameter. This `Item.Properties` parameter can be created using `Item.Properties#of`, and it can be customized by calling its methods: -- `stacksTo` - 设置此物品的最大堆叠大小。默认为 64。例如末影珍珠或其他只能堆叠到 16 的物品使用了这个值。 -- `durability` - 设置此物品的耐久度。默认为 0,表示“无耐久度”。例如,铁制工具在此处使用了 250。请注意,设置耐久度会自动将堆叠大小锁定为 1。 -- `craftRemainder` - 设置此物品的制作剩余物品。Vanilla 在制作后留下空桶时使用了这个值。 -- `fireResistant` - 使使用此物品的物品实体对火和岩浆免疫。许多下界物品都使用了这个。 -- `setNoRepair` - 禁用此物品的铁砧和合成网格修复。Vanilla 中未使用。 -- `rarity` - 设置此物品的稀有度。当前,这只是改变物品的颜色。`Rarity` 是一个由四个值 `COMMON`(白色,默认)、`UNCOMMON`(黄色)、`RARE`(青色)和 `EPIC`(浅紫色) 组成的枚举。请注意,模组可能会添加更多的稀有度类型。 -- `requiredFeatures` - 设置此物品所需的功能标志。这主要用于小版本中 Vanilla 的功能锁定系统。除非你要集成 Vanilla 中由功能标志锁定的系统,否则不建议使用这个。 -- `food` - 设置此物品的 [`FoodProperties`][food]。 +- `stacksTo` - Sets the max stack size of this item. Defaults to 64. Used e.g. by ender pearls or other items that only stack to 16. +- `durability` - Sets the durability of this item. Defaults to 0, which means "no durability". For example, iron tools use 250 here. Note that setting the durability automatically locks the stack size to 1. +- `craftRemainder` - Sets the crafting remainder of this item. Vanilla uses this for filled buckets that leave behind empty buckets after crafting. +- `fireResistant` - Makes item entities that use this item immune to fire and lava. Used by various netherite items. +- `setNoRepair` - Disables anvil and crafting grid repairing for this item. Unused in vanilla. +- `rarity` - Sets the rarity of this item. Currently, this simply changes the item's color. `Rarity` is an enum consisting of the four values `COMMON` (white, default), `UNCOMMON` (yellow), `RARE` (aqua) and `EPIC` (light purple). Be aware that mods may add more rarity types. +- `requiredFeatures` - Sets the required feature flags for this item. This is mainly used for vanilla's feature locking system in minor versions. It is discouraged to use this, unless you're integrating with a system locked behind feature flags by vanilla. +- `food` - Sets the [`FoodProperties`][food] of this item. -有关示例,或查看 Minecraft 中使用的各种值,请查看 `Items` 类。 +For examples, or to look at the various values used by Minecraft, have a look at the `Items` class. -### 食物 +### Food -`Item` 类提供了食物物品的默认功能,这意味着你不需要单独的类来处理。要使你的物品可食用,你只需要通过 `Item.Properties` 的 `food` 方法设置其上的 `FoodProperties`。 +The `Item` class provides default functionality for food items, meaning you don't need a separate class for that. To make your item edible, all you need to do is set the `FoodProperties` on it through the `food` method in `Item.Properties`. -使用 `FoodProperties.Builder` 创建 `FoodProperties`。然后,你可以在其上设置各种属性: +`FoodProperties` are created using a `FoodProperties.Builder`. You can then set various properties on it: -- `nutrition` - 可能是最明显的部分。设置恢复多少饥饿点。以半个饥饿点为单位计数,所以例如,Minecraft 的牛排恢复了 8 个饥饿点。 -- `saturationMod` - 用于计算 [进食][hunger] 时恢复的饱和度值的饱和度修 +- `nutrition` - Probably the most obvious part. Sets how many hunger points are restored. Counts in half hunger points, so for example, Minecraft's steak restores 8 hunger points. +- `saturationMod` - The saturation modifier used in calculating the [saturation value][hunger] restored when eating this food. The calculation is `min(2 * nutrition * saturationMod, playerNutrition)`, meaning that using `0.5` will make the effective saturation value the same as the nutrition value. +- `meat` - Whether this item should be considered meat or not. Used e.g. for determining if healing dogs with this food is possible. +- `alwaysEat` - Whether this item can always be eaten, even if the hunger bar is full. `false` by default, `true` for golden apples and other items that provide bonuses beyond just filling the hunger bar. +- `fast` - Whether fast eating should be enabled for this food. `false` by default, `true` for dried kelp in vanilla. +- `effect` - Adds a [`MobEffectInstance`][mobeffectinstance] to apply when eating this item. The second parameter denotes the probability of the effect being applied; for example, Rotten Flesh has an 80% chance (= 0.8) of applying the Hunger effect when eaten. This method comes in two variants; you should use the one that takes in a supplier (the other one directly takes a mob effect instance and is deprecated by NeoForge due to classloading issues). +- `build` - Once you've set everything you want to set, call `build` to get a `FoodProperties` object for further use. -饰符。计算公式为 `min(2 * nutrition * saturationMod, playerNutrition)`,这意味着使用 `0.5` 将使有效的饱和度值与营养值相同。 -- `meat` - 是否应将此物品视为肉类。用于确定是否可以使用此食物治愈狗。 -- `alwaysEat` - 此物品是否始终可以食用,即使饥饿条已满。默认为 `false`,例如金苹果等提供了除填充饥饿条之外的奖励的物品为 `true`。 -- `fast` - 是否为此食物启用快速进食。默认为 `false`,例如 Vanilla 中的干海带为 `true`。 -- `effect` - 在吃这个物品时添加一个 [`MobEffectInstance`][mobeffectinstance]。第二个参数表示应用效果的概率;例如,腐肉在被吃时有 80% 的几率(= 0.8)应用饥饿效果。这个方法有两个变体;你应该使用一个带有提供者的(另一个直接使用了一个 mob 效果实例,并因为类加载问题而被 NeoForge 废弃)。 -- `build` - 一旦你设置了你想设置的所有内容,调用 `build` 获取一个用于进一步使用的 `FoodProperties` 对象。 +For examples, or to look at the various values used by Minecraft, have a look at the `Foods` class. -有关示例,或查看 Minecraft 中使用的各种值,请查看 `Foods` 类。 +To get the `FoodProperties` for an item, call `Item#getFoodProperties(ItemStack, LivingEntity)`. This may return null, since not every item is edible. To determine whether an item is edible, call `Item#isEdible()` or null-check the result of the `getFoodProperties` call. -要获取物品的 `FoodProperties`,请调用 `Item#getFoodProperties(ItemStack, LivingEntity)`。这可能返回 null,因为并非每个物品都是可食用的。要确定物品是否可食用,请调用 `Item#isEdible()` 或对 `getFoodProperties` 调用的结果进行空检查。 +### More Functionality -### 更多功能 +Directly using `Item` only allows for very basic items. If you want to add functionality, for example right-click interactions, a custom class that extends `Item` is required. The `Item` class has many methods that can be overridden to do different things; see the classes `Item` and `IItemExtension` for more information. -直接使用 `Item` 仅允许非常基本的物品。如果你想添加功能,例如右键交互,需要一个扩展 `Item` 的自定义类。`Item` 类有许多可以重写以执行不同操作的方法;有关更多信息,请参阅类 `Item` 和 `IItemExtension`。 - -物品的两种最常见用途是左键单击和右键单击。对于左键单击,请参阅 [破坏方块][breaking] 和 攻击实体(工作中)。对于右键单击,请参阅 [交互管道][interactionpipeline]。 +The two most common use cases for items are left-clicking and right-clicking. For left-clicking, see [Breaking a Block][breaking] and Attacking an Entity (WIP). For right-clicking, see [The Interaction Pipeline][interactionpipeline]. ### `DeferredRegister.Items` -所有注册表都使用 `DeferredRegister` 来注册它们的内容,物品也不例外。然而,由于添加新物品是大量模组中一个至关重要的功能,NeoForge 提供了 `DeferredRegister.Items` 帮助类,它扩展了 `DeferredRegister` 并提供了一些特定于物品的辅助程序: +All registries use `DeferredRegister` to register their contents, and items are no exceptions. However, due to the fact that adding new items is such an essential feature of an overwhelming amount of mods, NeoForge provides the `DeferredRegister.Items` helper class that extends `DeferredRegister` and provides some item-specific helpers: ```java public static final DeferredRegister.Items ITEMS = DeferredRegister.createItems(ExampleMod.MOD_ID); public static final Supplier EXAMPLE_ITEM = ITEMS.registerItem( "example_item", - Item::new, // 将属性传递给工厂。 - new Item.Properties() // 要使用的属性。 + Item::new, // The factory that the properties will be passed into. + new Item.Properties() // The properties to use. ); ``` -在内部,这只是通过将属性参数应用于提供的物品工厂(通常是构造函数)来调用 `ITEMS.register("example_item", () -> new Item(new Item.Properties()))`。 +Internally, this will simply call `ITEMS.register("example_item", () -> new Item(new Item.Properties()))` by applying the properties parameter to the provided item factory (which is commonly the constructor). -如果你想使用 `Item::new`,可以完全省略工厂,并使用 `simple` 方法变体: +If you want to use `Item::new`, you can leave out the factory entirely and use the `simple` method variant: ```java public static final Supplier EXAMPLE_ITEM = ITEMS.registerSimpleItem( "example_item", - new Item.Properties() // 要使用的属性。 + new Item.Properties() // The properties to use. ); ``` -这和上一个示例的效果完全相同,但稍微更短一些。当然,如果你想使用 `Item` 的子类而不是 `Item` 本身,则必须使用前一种方法。 +This does the exact same as the previous example, but is slightly shorter. Of course, if you want to use a subclass of `Item` and not `Item` itself, you will have to use the previous method instead. -这两种方法也有省略 `new Item.Properties()` 参数的重载: +Both of these methods also have overloads that omit the `new Item.Properties()` parameter: ```java public static final Supplier EXAMPLE_ITEM = ITEMS.registerItem("example_item", Item::new); @@ -91,7 +89,7 @@ public static final Supplier EXAMPLE_ITEM = ITEMS.registerItem("example_it public static final Supplier EXAMPLE_ITEM = ITEMS.registerSimpleItem("example_item"); ``` -最后,还有块项目的快捷方式: +Finally, there's also shortcuts for block items: ```java public static final Supplier EXAMPLE_BLOCK_ITEM = ITEMS.registerSimpleBlockItem("example_block", ExampleBlocksClass.EXAMPLE_BLOCK, new Item.Properties()); @@ -104,88 +102,86 @@ public static final Supplier EXAMPLE_BLOCK_ITEM = ITEMS.registerSimpl ``` :::note -如果您将注册的方块保存在单独的类中,则应该在项目类之前对方块类进行类加载。 +If you keep your registered blocks in a separate class, you should classload your blocks class before your items class. ::: -### 资源 +### Resources -如果你注册了你的物品并获得了你的物品(通过 `/give` 命令或通过 [创造模式标签][creativetabs]),你会发现它缺少正确的模型和纹理。这是因为纹理和模型是由 Minecraft 的资源系统处理的。 +If you register your item and get your item (via `/give` or through a [creative tab][creativetabs]), you will find it to be missing a proper model and texture. This is because textures and models are handled by Minecraft's resource system. -要为物品应用一个简单的纹理,你必须添加一个物品模型 JSON 文件和一个纹理 PNG 文件。有关更多信息,请参阅 [资源][resources] 部分。 +To apply a simple texture to an item, you must add an item model JSON and a texture PNG. See the section on [resources][resources] for more information. -## `ItemStack`(物品堆叠) +## `ItemStack`s -与方块和方块状态一样,大多数情况下你期望使用一个 `Item` 实际上都是使用 `ItemStack`。`ItemStack` 表示容器中一个或多个物品的堆叠,例如一个物品栏。与方块和方块状态一样,方法应该被 `Item` 重写,并在 `ItemStack` 上调用,`Item` 中的许多方法都会传入一个 `ItemStack` 实例。 +Like with blocks and blockstates, most places where you'd expect an `Item` actually use an `ItemStack` instead. `ItemStack`s represent a stack of one or multiple items in a container, e.g. an inventory. Again like with blocks and blockstates, methods should be overridden by the `Item` and called on the `ItemStack`, and many methods in `Item` get an `ItemStack` instance passed in. -`ItemStack` 由三个主要部分组成: +An `ItemStack` consists of three major parts: -- 它所表示的 `Item`,可通过 `itemstack.getItem()` 获取。 -- 堆叠大小,通常在 1 和 64 之间,通过 `itemstack.getCount()` 获取,通过 `itemstack.setCount(int)` 或 `itemstack.shrink(int)` 可更改。 -- 额外的 [NBT][nbt] 数据,其中存储了堆叠特定的数据。可通过 `itemstack.getTag()` 获取,或者通过 `itemstack.getOrCreateTag()` 获取,它考虑了尚不存在标签的情况。还存在许多其他与 NBT 相关的方法,最重要的是 `hasTag()` 和 `setTag()`。 - - 值得注意的是,带有空 NBT 的 `ItemStack` 与根本没有 NBT 的 `ItemStack` 不同。这意味着它们不会堆叠,尽管它们在功能上是等效的。 +- The `Item` it represents, obtainable through `itemstack.getItem()`. +- The stack size, typically between 1 and 64, obtainable through `itemstack.getCount()` and changeable through `itemstack.setCount(int)` or `itemstack.shrink(int)`. +- The extra [NBT][nbt] data, where stack-specific data is stored. Obtainable through `itemstack.getTag()`, or alternatively through `itemstack.getOrCreateTag()` which accounts for no tag existing yet. A variety of other NBT-related methods exist as well, the most important being `hasTag()` and `setTag()`. + - It is worth nothing that `ItemStack`s with empty NBT are not the same as `ItemStack`s with no NBT at all. This means that they will not stack, despite being functionally equivalent to one another. -要创建一个新的 `ItemStack`,请调用 `new ItemStack(Item)`,传入支持的物品。默认情况下,这使用数量为 1 和没有 NBT 数据;如果需要,有接受数量和 NBT 数据的构造函数重载。 +To create a new `ItemStack`, call `new ItemStack(Item)`, passing in the backing item. By default, this uses a count of 1 and no NBT data; there are constructor overloads that accept a count and NBT data as well if needed. -`ItemStack` 是可变对象(见下文),但有时需要将它们视为不可变的。如果你需要修改一个被视为不可变的 `ItemStack`,可以使用 `itemstack.copy()` 克隆堆栈。 +`ItemStack`s are mutable objects (see below), however it is sometimes required to treat them as immutables. If you need to modify an `ItemStack` that is to be treated immutable, you can clone the stack using `itemstack.copy()`. -如果要表示堆叠没有物品,可以使用 `ItemStack.EMPTY`。要检查一个 `ItemStack` 是否为空,请调用 `itemstack.isEmpty()`。 +If you want to represent that a stack has no item, use `ItemStack.EMPTY`. If you want to check whether an `ItemStack` is empty, call `itemstack.isEmpty()`. -### `ItemStack` 的可变性 +### Mutability of `ItemStack`s -`ItemStack` 是可变对象。这意味着如果你调用例如 `setCount`、`setTag` 或 `getOrCreateTag`,`ItemStack` 本身将被修改。Vanilla 广泛使用了 `ItemStack` 的可变性,许多方法依赖于它。例如,`itemstack.split(int)` 从调用者堆栈中分离给定数量的堆栈,同时修改调用者并在过程中返回一个新的 `ItemStack`。 +`ItemStack`s are mutable objects. This means that if you call for example `setCount`, `setTag` or `getOrCreateTag`, the `ItemStack` itself will be modified. Vanilla uses the mutability of `ItemStack`s extensively, and several methods rely on it. For example, `itemstack.split(int)` splits the given amount off the stack it is called on, both modifying the caller and returning a new `ItemStack` in the process. -然而,当处理多个 `ItemStack` 时,有时可能会出现问题。最常见的情况是处理库存槽时,因为你必须考虑到当前由光标选择的 `ItemStack`,以及你正在尝试插入/提取的 `ItemStack`。 +However, this can sometimes lead to issues when dealing with multiple `ItemStack`s at once. The most common instance where this arises is when handling inventory slots, since you have to consider both the `ItemStack` currently selected by the cursor, as well as the `ItemStack` you are trying to insert to/extract from. :::tip -如果不确定,最好安全起见并对堆栈进行 `#copy()`。 +When in doubt, better be safe than sorry and `#copy()` the stack. ::: -## 创造模式标签 +## Creative Tabs -默认情况下,你的物品只能通过 `/give` 命令获得,并不会出现在创造模式的物品栏中。让我们来改变这一点吧! +By default, your item will only be available through `/give` and not appear in the creative inventory. Let's change that! -将你的物品添加到创造模式菜单中的方式取决于你想要添加到哪个标签。 +The way you get your item into the creative menu depends on what tab you want to add it to. -### 现有的创造模式标签 +### Existing Creative Tabs :::note -这种方法用于将你的物品添加到 Minecraft 的标签,或其他模组的标签中。要将物品添加到你自己的标签中,请参见下文。 +This method is for adding your items to Minecraft's tabs, or to other mods' tabs. To add items to your own tabs, see below. ::: -可以通过 `BuildCreativeModeTabContentsEvent` 将物品添加到现有的 `CreativeModeTab` 中,该事件在 [模组事件总线][modbus] 上触发,仅在 [逻辑客户端][sides] 上触发。通过调用 `event#accept` 来添加物品。 +An item can be added to an existing `CreativeModeTab` via the `BuildCreativeModeTabContentsEvent`, which is fired on the [mod event bus][modbus], only on the [logical client][sides]. Add items by calling `event#accept`. ```java -//MyItemsClass.MY_ITEM 是 Supplier,MyBlocksClass.MY_BLOCK 是 Supplier +//MyItemsClass.MY_ITEM is a Supplier, MyBlocksClass.MY_BLOCK is a Supplier @SubscribeEvent public static void buildContents(BuildCreativeModeTabContentsEvent event) { - // 这是我们要添加到的标签吗? + // Is this the tab we want to add to? if (event.getTabKey() == CreativeModeTabs.INGREDIENTS) { event.accept(MyItemsClass.MY_ITEM); - // 接受一个 ItemLike。这假设 MY_BLOCK 有一个相应的物品。 + // Accepts an ItemLike. This assumes that MY_BLOCK has a corresponding item. event.accept(MyBlocksClass.MY_BLOCK); } } ``` -事件还提供了一些额外信息,例如 `getFlags()` 来获取已启用的功能标志列表,或 `hasPermissions()` 来检查玩家是否有权限查看操作员物品标签。 - -### 自定义创造模式标签 +The event also provides some extra information, such as `getFlags()` to get the list of enabled feature flags, or `hasPermissions()` to check if the player has permissions to view the operator items tab. -`CreativeModeTab` 是一个注册表,意味着自定义的 `CreativeModeTab` 必须被 [注册][registering]。创建创造模式标签使用一个构建器系统 +### Custom Creative Tabs -,该构建器通过 `CreativeModeTab#builder` 获得。构建器提供了设置标题、图标、默认物品以及许多其他属性的选项。此外,NeoForge 还提供了额外的方法来自定义标签的图像、标签和槽颜色,标签应该排序在哪里等等。 +`CreativeModeTab`s are a registry, meaning custom `CreativeModeTab`s must be [registered][registering]. Creating a creative tab uses a builder system, the builder is obtainable through `CreativeModeTab#builder`. The builder provides options to set the title, icon, default items, and a number of other properties. In addition, NeoForge provides additional methods to customize the tab's image, label and slot colors, where the tab should be ordered, etc. ```java -//CREATIVE_MODE_TABS 是 DeferredRegister +//CREATIVE_MODE_TABS is a DeferredRegister public static final Supplier EXAMPLE_TAB = CREATIVE_MODE_TABS.register("example", () -> CreativeModeTab.builder() - // 设置标签的标题。不要忘记添加一个翻译! + //Set the title of the tab. Don't forget to add a translation! .title(Component.translatable("itemGroup." + MOD_ID + ".example")) - // 设置标签的图标。 + //Set the icon of the tab. .icon(() -> new ItemStack(MyItemsClass.EXAMPLE_ITEM.get())) - // 将你的物品添加到标签中。 + //Add your items to the tab. .displayItems((params, output) -> { output.accept(MyItemsClass.MY_ITEM); - // 接受一个 ItemLike。这假设 MY_BLOCK 有一个相应的物品。 + // Accepts an ItemLike. This assumes that MY_BLOCK has a corresponding item. output.accept(MyBlocksClass.MY_BLOCK); }) .build() @@ -204,3 +200,5 @@ public static final Supplier EXAMPLE_TAB = CREATIVE_MODE_TABS.r [modbus]: ../concepts/events.md#event-buses [nbt]: ../datastorage/nbt.md [registering]: ../concepts/registries.md#methods-for-registering +[resources]: ../resources/index.md#assets +[sides]: ../concepts/sides.md diff --git a/docs/items/interactionpipeline.md b/docs/items/interactionpipeline.md index 7643241f5..8f05f0aeb 100644 --- a/docs/items/interactionpipeline.md +++ b/docs/items/interactionpipeline.md @@ -1,71 +1,71 @@ The Interaction Pipeline ======================== -本页面旨在使玩家右键单击的相当复杂和令人困惑的过程更容易理解,并澄清应在何处以及为什么使用哪种结果。 +This page aims to make the fairly complex and confusing process of things being right-clicked by the player more understandable, as well as clarifying what result to use where and why. -右键单击时发生了什么? +What Happens When I Right-Click? -------------------------------- -当你在世界中的任何地方右键单击时,会发生一系列的事情,这取决于你当前正在查看的内容以及你手中的 `ItemStack`。会调用返回两种结果类型之一的一些方法。如果显式地返回了成功或失败,大多数情况下这些方法将取消管线。为了易读起见,这里将“显式成功或显式失败”称为“明确结果”。 - -- 用右鼠标按钮和主手触发 `InputEvent.InteractionKeyMappingTriggered`。如果事件被取消,管线结束。 -- 检查了几种情况,例如你不处于旁观模式,或者你主手中的 `ItemStack` 的所有必需特性标志都已启用。如果这些检查中至少有一个失败,管线结束。 -- 根据你的视线朝向的内容不同,会发生不同的事情: - - 如果你的视线朝向一个在你可触及范围内且不在世界边界之外的实体: - - 触发 `PlayerInteractEvent.EntityInteractSpecific`。如果事件被取消,管线结束。 - - 将在你所看的实体上调用 `Entity#interactAt`。如果它返回了明确结果,管线结束。 - - 如果你想为你自己的实体添加行为,请重写此方法。如果你想为一个原版实体添加行为,请使用事件。 - - 如果实体打开了一个界面(例如村民交易 GUI 或箱子矿车 GUI),管线结束。 - - 触发 `PlayerInteractEvent.EntityInteract`。如果事件被取消,管线结束。 - - 将在你所看的实体上调用 `Entity#interact`。如果它返回了明确结果,管线结束。 - - 如果你想为你自己的实体添加行为,请重写此方法。如果你想为一个原版实体添加行为,请使用事件。 - - 对于 `Mob`,`Entity#interact` 的重写处理了像使用生成蛋时拴绳和产生孩子这样的事情,然后将特定于 mob 的处理推迟到 `Mob#mobInteract`。`Entity#interact` 的结果规则也适用于这里。 - - 如果你所看的实体是一个 `LivingEntity`,将在你主手中的 `ItemStack` 上调用 `Item#interactLivingEntity`。如果它返回了明确结果,管线结束。 - - 如果你的视线朝向一个在你可触及范围内且不在世界边界之外的方块: - - 触发 `PlayerInteractEvent.RightClickBlock`。如果事件被取消,管线结束。你也可以在这个事件中具体地否定只有方块或物品的使用。 - - 调用 `IItemExtension#onItemUseFirst`。如果它返回了明确结果,管线结束。 - - 如果玩家没有潜行并且事件没有否定方块的使用,将调用 `Block#use`。如果它返回了明确结果,管线结束。 - - 如果事件没有否定物品的使用,将调用 `Item#useOn`。如果它返回了明确结果,管线结束。 -- 调用 `Item#use`。如果它返回了明确结果,管线结束。 -- 上述过程再次运行,这次是用副手而不是主手。 - -结果类型 +When you right-click anywhere in the world, a number of things happen, depending on what you are currently looking at and what `ItemStack`s are in your hands. A number of methods returning one of two result types (see below) are called. Most of these methods cancel the pipeline if an explicit success or an explicit failure is returned. For the sake of readability, this "explicit success or explicit failure" will be called a "definitive result" from now on. + +- `InputEvent.InteractionKeyMappingTriggered` is fired with the right mouse button and the main hand. If the event is canceled, the pipeline ends. +- Several circumstances are checked, for example that you are not in spectator mode or that all required feature flags for the `ItemStack` in your main hand are enabled. If at least one of these checks fails, the pipeline ends. +- Depending on what you are looking at, different things happen: + - If you are looking at an entity that is within your reach and not outside the world border: + - `PlayerInteractEvent.EntityInteractSpecific` is fired. If the event is canceled, the pipeline ends. + - `Entity#interactAt` will be called **on the entity you are looking at**. If it returns a definitive result, the pipeline ends. + - If you want to add behavior for your own entity, override this method. If you want to add behavior for a vanilla entity, use the event. + - If the entity opens an interface (for example a villager trading GUI or a chest minecart GUI), the pipeline ends. + - `PlayerInteractEvent.EntityInteract` is fired. If the event is canceled, the pipeline ends. + - `Entity#interact` is called **on the entity you are looking at**. If it returns a definitive result, the pipeline ends. + - If you want to add behavior for your own entity, override this method. If you want to add behavior for a vanilla entity, use the event. + - For `Mob`s, the override of `Entity#interact` handles things like leashing and spawning babies when the `ItemStack` in your main hand is a spawn egg, and then defers mob-specific handling to `Mob#mobInteract`. The rules for results for `Entity#interact` apply here as well. + - If the entity you are looking at is a `LivingEntity`, `Item#interactLivingEntity` is called on the `ItemStack` in your main hand. If it returns a definitive result, the pipeline ends. + - If you are looking at a block that is within your reach and not outside the world border: + - `PlayerInteractEvent.RightClickBlock` is fired. If the event is canceled, the pipeline ends. You may also specifically deny only block or item usage in this event. + - `IItemExtension#onItemUseFirst` is called. If it returns a definitive result, the pipeline ends. + - If the player is not sneaking and the event does not deny block usage, `Block#use` is called. If it returns a definitive result, the pipeline ends. + - If the event does not deny item usage, `Item#useOn` is called. If it returns a definitive result, the pipeline ends. +- `Item#use` is called. If it returns a definitive result, the pipeline ends. +- The above process runs a second time, this time with the off hand instead of the main hand. + +Result Types ------------ -有两种不同的结果类型:`InteractionResult` 和 `InteractionResultHolder`。`InteractionResult` 大多数情况下使用,只有 `Item#use` 使用 `InteractionResultHolder`。 +There are two different types of results: `InteractionResult`s and `InteractionResultHolder`s. `InteractionResult` is used most of the time, only `Item#use` uses `InteractionResultHolder`. -`InteractionResult` 是一个枚举,包含五个值:`SUCCESS`、`CONSUME`、`CONSUME_PARTIAL`、`PASS` 和 `FAIL`。此外,方法 `InteractionResult#sidedSuccess` 可用,它在服务器端返回 `SUCCESS`,在客户端返回 `CONSUME`。 +`InteractionResult` is an enum consisting of five values: `SUCCESS`, `CONSUME`, `CONSUME_PARTIAL`, `PASS` and `FAIL`. Additionally, the method `InteractionResult#sidedSuccess` is available, which returns `SUCCESS` on the server and `CONSUME` on the client. -`InteractionResultHolder` 是 `InteractionResult` 的包装器,它为 `T` 添加了额外的上下文。`T` 可以是任何东西,但在 99.99% 的情况下,它是一个 `ItemStack`。`InteractionResultHolder` 为枚举值提供了包装方法(`#success`、`#consume`、`#pass` 和 `#fail`),以及 `#sidedSuccess` 方法,它在服务器上调用 `#success`,在客户端上调用 `#consume`。 +`InteractionResultHolder` is a wrapper around `InteractionResult` that adds additional context for `T`. `T` can be anything, but in 99.99 percent of cases, it is an `ItemStack`. `InteractionResultHolder` provides wrapper methods for the enum values (`#success`, `#consume`, `#pass` and `#fail`), as well as `#sidedSuccess`, which calls `#success` on the server and `#consume` on the client. -一般来说,不同的值意味着以下内容: +Generally, the different values mean the following: -- `InteractionResult#sidedSuccess`(或需要时 `InteractionResultHolder#sidedSuccess`)应该在操作应该被认为成功,并且你想要挥动手臂时使用。管线将结束。 -- `InteractionResult.SUCCESS`(或需要时 `InteractionResultHolder#success`)应该在操作应该被认为成功,并且你想要挥动手臂时使用,但只在一侧使用。只有在出于某种原因希望在另一逻辑侧返回不同值时才使用此选项。管线将结束。 -- `InteractionResult.CONSUME`(或需要时 `InteractionResultHolder#consume`)应该在操作应该被认为成功,但你不想要挥动手臂时使用。管线将结束。 -- `InteractionResult.CONSUME_PARTIAL` 在大多数情况下与 `InteractionResult.CONSUME` 相同,唯一的区别在于它在 [`Item#useOn`][itemuseon] 中的使用方式。 -- `InteractionResult.FAIL`(或需要时 `InteractionResult +- `InteractionResult#sidedSuccess` (or `InteractionResultHolder#sidedSuccess` where needed) should be used if the operation should be considered successful, and you want the arm to swing. The pipeline will end. +- `InteractionResult.SUCCESS` (or `InteractionResultHolder#success` where needed) should be used if the operation should be considered successful, and you want the arm to swing, but only on one side. Only use this if you want to return a different value on the other logical side for whatever reason. The pipeline will end. +- `InteractionResult.CONSUME` (or `InteractionResultHolder#consume` where needed) should be used if the operation should be considered successful, but you do not want the arm to swing. The pipeline will end. +- `InteractionResult.CONSUME_PARTIAL` is mostly identical to `InteractionResult.CONSUME`, the only difference is in its usage in [`Item#useOn`][itemuseon]. +- `InteractionResult.FAIL` (or `InteractionResultHolder#fail` where needed) should be used if the item functionality should be considered failed and no further interaction should be performed. The pipeline will end. This can be used everywhere, but it should be used with care outside of `Item#useOn` and `Item#use`. In many cases, using `InteractionResult.PASS` makes more sense. +- `InteractionResult.PASS` (or `InteractionResultHolder#pass` where needed) should be used if the operation should be considered neither successful nor failed. The pipeline will continue. This is the default behavior (unless otherwise specified). -Holder#fail`)应该在物品功能被认为失败并且不应再进行进一步交互时使用。管线将结束。这可以用在任何地方,但在 `Item#useOn` 和 `Item#use` 之外使用时需要小心。在许多情况下,使用 `InteractionResult.PASS` 更有意义。 -- `InteractionResult.PASS`(或需要时 `InteractionResultHolder#pass`)应该在操作既不应被认为成功也不应被认为失败时使用。管线将继续。这是默认行为(除非另有规定)。 - -一些方法具有特殊的行为或要求,这些将在下面的章节中解释。 +Some methods have special behavior or requirements, which are explained in the below chapters. `IItemExtension#onItemUseFirst` --------------------------- -`InteractionResult#sidedSuccess` 和 `InteractionResult.CONSUME` 在这里没有效果。在这里只应该使用 `InteractionResult.SUCCESS`、`InteractionResult.FAIL` 或 `InteractionResult.PASS`。 +`InteractionResult#sidedSuccess` and `InteractionResult.CONSUME` don't have an effect here. Only `InteractionResult.SUCCESS`, `InteractionResult.FAIL` or `InteractionResult.PASS` should be used here. `Item#useOn` ------------ -如果你希望操作被视为成功,但你不希望手臂摆动或奖励一个 `ITEM_USED` 统计点,请使用 `InteractionResult.CONSUME_PARTIAL`。 +If you want the operation to be considered successful, but you do not want the arm to swing or an `ITEM_USED` stat point to be awarded, use `InteractionResult.CONSUME_PARTIAL`. `Item#use` ---------- -这是唯一一个返回类型为 `InteractionResultHolder` 的实例。`InteractionResultHolder` 中的结果 `ItemStack` 将替换发起使用的 `ItemStack`,如果它已更改。 +This is the only instance where the return type is `InteractionResultHolder`. The resulting `ItemStack` in the `InteractionResultHolder` replaces the `ItemStack` the usage was initiated with, if it has changed. + +The default implementation of `Item#use` returns `InteractionResultHolder#consume` when the item is edible and the player can eat the item (because they are hungry, or because the item is always edible), `InteractionResultHolder#fail` when the item is edible but the player cannot eat the item, and `InteractionResultHolder#pass` if the item is not edible. -当物品可食用并且玩家可以吃掉物品时(因为他们饥饿了,或者因为物品总是可食用时),`Item#use` 的默认实现返回 `InteractionResultHolder#consume`;当物品可食用但玩家无法吃掉物品时,返回 `InteractionResultHolder#fail`;如果物品不可食用,则返回 `InteractionResultHolder#pass`。 +Returning `InteractionResultHolder#fail` here while considering the main hand will prevent offhand behavior from running. If you want offhand behavior to run (which you usually want), return `InteractionResultHolder#pass` instead. -在考虑主手时返回 `InteractionResultHolder#fail` 将阻止运行副手行为。如果你希望运行副手行为(通常是这样),请改为返回 `InteractionResultHolder#pass`。 +[itemuseon]: #itemuseon diff --git a/docs/items/mobeffects.md b/docs/items/mobeffects.md index 17ef1d09c..a8c9e98ad 100644 --- a/docs/items/mobeffects.md +++ b/docs/items/mobeffects.md @@ -1,18 +1,18 @@ # Mob Effects & Potions -状态效果,有时称为药水效果,并在代码中称为 `MobEffect`,是每个游戏刻对实体产生影响的效果。本文解释了如何使用它们,效果与药水之间的区别,以及如何添加自定义效果。 +Status effects, sometimes known as potion effects and referred to in-code as `MobEffect`s, are effects that influence an entity every tick. This article explains how to use them, what the difference between an effect and a potion is, and how to add your own. -## 术语 +## Terminology -- `MobEffect` 每个游戏刻对实体产生影响。与[方块][block]或[物品][item]一样,`MobEffect` 是注册对象,这意味着它们必须[注册][registration]并且是单例的。 - - **即时效果** 是一种特殊类型的效果,设计用于应用一次游戏刻。原版有两种即时效果,即即时治疗和即时伤害。 -- `MobEffectInstance` 是 `MobEffect` 的实例,具有持续时间、增幅和一些其他设置(见下文)。`MobEffectInstance` 对于 `MobEffect` 就像 [`ItemStack`][itemstack] 对于 `Item` 一样。 -- `Potion` 是一组 `MobEffectInstance`。原版主要用于四种药水物品(后文),但可以随意应用于任何物品。物品如何使用所设置的药水取决于物品本身。 -- **药水物品** 是指应设置药水的物品。这是一个非正式的术语,原版中有四种药水物品:药水、溅射药水、挥发药水和毒箭;但是模组可能会添加更多。 +- A `MobEffect` affects an entity every tick. Like [blocks][block] or [items][item], `MobEffect`s are registry objects, meaning they must be [registered][registration] and are singletons. + - An **instant mob effect** is a special kind of mob effect that is designed to be applied for one tick. Vanilla has two instant effects, Instant Health and Instant Harming. +- A `MobEffectInstance` is an instance of a `MobEffect`, with a duration, amplifier and some other properties set (see below). `MobEffectInstance`s are to `MobEffect`s what [`ItemStack`s][itemstack] are to `Item`s. +- A `Potion` is a collection of `MobEffectInstance`s. Vanilla mainly uses potions for the four potion items (read on), however, they can be applied to any item at will. It is up to the item if and how the item then uses the potion set on it. +- A **potion item** is an item that is meant to have a potion set on it. This is an informal term, the vanilla `PotionItem` class has nothing to do with this (it refers to the "normal" potion item). Minecraft currently has four potion items: potions, splash potions, lingering potions, and tipped arrows; however more may be added by mods. ## `MobEffect`s -要创建自己的 `MobEffect`,请扩展 `MobEffect` 类: +To create your own `MobEffect`, extend the `MobEffect` class: ```java public class MyMobEffect extends MobEffect { @@ -22,37 +22,38 @@ public class MyMobEffect extends MobEffect { @Override public void applyEffectTick(LivingEntity entity, int amplifier) { - // 在这里应用你的效果逻辑。 + // Apply your effect logic here. } - // 决定是否在此游戏刻应用效果。例如,恢复效果每 x 个游戏刻应用一次,具体取决于游戏刻和增幅。 + // Whether the effect should apply this tick. Used e.g. by the Regeneration effect that only applies + // once every x ticks, depending on the tick count and amplifier. @Override public boolean shouldApplyEffectTickThisTick(int tickCount, int amplifier) { - return tickCount % 2 == 0; // 用你想要的检查替换此处 + return tickCount % 2 == 0; // replace this with whatever check you want } - // 当效果首次添加到实体时调用的实用方法。 + // Utility method that is called when the effect is first added to the entity. @Override public void onEffectStarted(LivingEntity entity, int amplifier) { } } ``` -像所有注册对象一样,`MobEffect` 必须像下面这样注册: +Like all registry objects, `MobEffect`s must be registered, like so: ```java -// MOB_EFFECTS 是一个 DeferredRegister +//MOB_EFFECTS is a DeferredRegister public static final Supplier MY_MOB_EFFECT = MOB_EFFECTS.register("my_mob_effect", () -> new MyMobEffect( - // 可以是 BENEFICIAL、NEUTRAL 或 HARMFUL。用于确定此效果的药水工具提示颜色。 + //Can be either BENEFICIAL, NEUTRAL or HARMFUL. Used to determine the potion tooltip color of this effect. MobEffectCategory.BENEFICIAL, - // 效果粒子的颜色。 + //The color of the effect particles. 0xffffff )); ``` -如果你的效果仅用作标记,你也可以直接使用 `MobEffect` 类,就像你可以使用 `Block` 或 `Item` 类一样。 +If you want your effect to act solely as a marker, you can also directly use the `MobEffect` class, like you can with the `Block` or `Item` classes. -`MobEffect` 类还为受影响实体添加属性修改器提供了默认功能。例如,速度效果会为移动速度添加属性修改器。效果属性修改器添加如下: +The `MobEffect` class also provides default functionality for adding attribute modifiers to affected entities. For example, the speed effect adds an attribute modifier for movement speed. Effect attribute modifiers are added like so: ```java public static final String MY_MOB_EFFECT_UUID = "01234567-89ab-cdef-0123-456789abcdef"; @@ -62,14 +63,12 @@ public static final Supplier MY_MOB_EFFECT = MOB_EFFECTS.register(" ``` :::note -使用的 UUID 必须是有效且唯一的 UUIDv4,因为出于某种原因,Mojang 决定在此处使用 UUID 而不是一些基于文本的标识符。最好 - -通过在线生成器获得 UUID,例如 [uuidgenerator.net][uuidgen]。 +The UUID used must be a valid and unique UUIDv4, as for some reason, Mojang decided to use UUIDs here instead of some text-based identifier. A UUID is best obtained through an online generator, for example [uuidgenerator.net][uuidgen]. ::: ### `InstantenousMobEffect` -如果要创建即时效果,可以使用助手类 `InstantenousMobEffect` 而不是常规的 `MobEffect` 类,如下所示: +If you want to create an instant effect, you can use the helper class `InstantenousMobEffect` instead of the regular `MobEffect` class, like so: ```java public class MyMobEffect extends InstantenousMobEffect { @@ -79,103 +78,105 @@ public class MyMobEffect extends InstantenousMobEffect { @Override public void applyEffectTick(LivingEntity entity, int amplifier) { - // 在这里应用你的效果逻辑。 + // Apply your effect logic here. } } ``` -然后,像平常一样注册你的效果。 +Then, register your effect like normal. -### 事件 +### Events -许多效果在其他地方应用它们的逻辑。例如,飘浮效果在生物移动处理程序中应用。对于模组 `MobEffect`,通常最好在[事件处理程序][events]中应用它们。NeoForge 还提供了一些与效果相关的事件: +Many effects have their logic applied in other places. For example, the levitation effect is applied in the living entity movement handler. For modded `MobEffect`s, it often makes sense to apply them in an [event handler][events]. NeoForge also provides a few events related to effects: -- `MobEffectEvent.Applicable` 在游戏检查是否可以将 `MobEffectInstance` 应用于实体时触发。此事件可用于拒绝或强制向目标添加效果实例。 -- `MobEffectEvent.Added` 当 `MobEffectInstance` 添加到目标时触发。此事件包含有关可能存在于目标上的先前 `MobEffectInstance` 的信息。 -- `MobEffectEvent.Expired` 当 `MobEffectInstance` 到期时触发,即计时器归零时。 -- `MobEffectEvent.Remove` 当通过除到期之外的方式从实体中移除效果时触发,例如通过喝牛奶或通过命令。 +- `MobEffectEvent.Applicable` is fired when the game checks whether a `MobEffectInstance` can be applied to an entity. This event can be used to deny or force adding the effect instance to the target. +- `MobEffectEvent.Added` is fired when the `MobEffectInstance` is added to the target. This event contains information about a previous `MobEffectInstance` that may have been present on the target. +- `MobEffectEvent.Expired` is fired when the `MobEffectInstance` expires, i.e. the timer goes to zero. +- `MobEffectEvent.Remove` is fired when the effect is removed from the entity through means other than expiring, e.g. through drinking milk or via commands. ## `MobEffectInstance`s -简单来说,`MobEffectInstance` 是应用于实体的效果。通过调用构造函数创建 `MobEffectInstance`: +A `MobEffectInstance` is, simply put, an effect applied to an entity. Creating a `MobEffectInstance` is done by calling the constructor: ```java MobEffectInstance instance = new MobEffectInstance( - // 要使用的 mob 效果。 + // The mob effect to use. MobEffects.REGENERATION, - // 使用的持续时间,以游戏刻为单位。如果未指定,默认为 0。 + // The duration to use, in ticks. Defaults to 0 if not specified. 500, - // 要使用的增幅。这是效果的 “强度”,例如,Strength I、Strength II 等;从 0 开始。如果未指定,默认为 0。 + // The amplifier to use. This is the "strength" of the effect, i.e. Strength I, Strength II, etc; + // starting at 0. Defaults to 0 if not specified. 0, - // 是否为 “环境” 效果,表示它由环境源应用,Minecraft 目前有信标和导管。如果未指定,默认为 false。 + // Whether the effect is an "ambient" effect, meaning it is being applied by an ambient source, + // of which Minecraft currently has the beacon and the conduit. Defaults to false if not specified. false, - // 效果是否在库存中可见。如果未指定,默认为 true。 + // Whether the effect is visible in the inventory. Defaults to true if not specified. true, - // 是否在右上角可见效果图标。如果未指定,默认为 true。 + // Whether an effect icon is visible in the top right corner. Defaults to true if not specified. true ); ``` -有几种构造函数重载,分别省略最后 1-5 个参数。 +Several constructor overloads are available, omitting the last 1-5 parameters, respectively. :::info -`MobEffectInstance` 是可变的。如果需要副本,请调用 `new MobEffectInstance(oldInstance)`。 +`MobEffectInstance`s are mutable. If you need a copy, call `new MobEffectInstance(oldInstance)`. ::: -### 使用 `MobEffectInstance` +### Using `MobEffectInstance`s -可以将 `MobEffectInstance` 添加到实体,如下所示: +A `MobEffectInstance` can be added to an entity like so: ```java MobEffectInstance instance = new MobEffectInstance(...); entity.addEffect(instance); ``` -类似地,也可以从实体中移除 `MobEffectInstance`。由于 `MobEffectInstance` 覆盖了实体上的相同 `MobEffect` 的预先存在的 `MobEffectInstance`,因此每个 `MobEffect` 和实体只能有一个 `MobEffectInstance`。因此,在移除时只需指定 `MobEffect` 即可: +Similarly, `MobEffectInstance` can also be removed from an entity. Since a `MobEffectInstance` overwrites pre-existing `MobEffectInstance`s of the same `MobEffect` on the entity, there can only ever be one `MobEffectInstance` per `MobEffect` and entity. As such, specifying the `MobEffect` suffices when removing: ```java entity.removeEffect(MobEffects.REGENERATION); ``` :::info -`MobEffect` 只能应用于 `LivingEntity` 或其子类,例如玩家和生物。例如物品或投掷雪球无法受到 `MobEffect` 的影响。 +`MobEffect`s can only be applied to `LivingEntity` or its subclasses, i.e. players and mobs. Things like items or thrown snowballs cannot be affected by `MobEffect`s. ::: ## `Potion`s -`Potion`s 是通过调用 `Potion` 的构造函数并传递你想要的 `MobEffectInstance`s 来创建的。例如: +`Potion`s are created by calling the constructor of `Potion` with the `MobEffectInstance`s you want the potion to have. For example: ```java -// POTIONS 是一个 DeferredRegister +//POTIONS is a DeferredRegister public static final Supplier MY_POTION = POTIONS.register("my_potion", () -> new Potion(new MobEffectInstance(MY_MOB_EFFECT.get(), 3600))); ``` -请注意,`new Potion` 的参数是可变参数。这意味着你可以向药水添加任意数量的效果。这也意味着可以创建空药水,即不含任何效果的药水。只需调用 `new Potion()` 即可!(顺便说一句,这就是原版如何添加 `awkward` 药水的方式。) +Note that the parameter of `new Potion` is a vararg. This means that you can add as many effects as you want to the potion. This also means that it is possible to create empty potions, i.e. potions that don't have any effects. Simply call `new Potion()` and you're done! (This is how vanilla adds the `awkward` potion, by the way.) -药水的名称可以作为第一个构造函数参数传递。它用于翻译;例如,原版中的长效和强效药水变种使用此参数,使其与基本变种具有相同的名称。名称不是必需的;如果省略了名称,将从注册表中查询名称。 +The name of the potion can be passed as the first constructor argument. It is used for translating; for example, the long and strong potion variants in vanilla use this to have the same names as their base variant. The name is not required; if it is omitted, the name will be queried from the registry. -`PotionUtils` 类提供了与药水相关的各种辅助方法,例如 `getPotion` 和 `setPotion` 用于物品堆栈(这可以是任何类型的物品,不仅限于药水物品),或者 `getColor` 用于获取药水的显示颜色。 +The `PotionUtils` class offers various helper methods related to potions, such as `getPotion` and `setPotion` for item stacks (this can be any kind of item and is not limited to potion items), or `getColor` for getting a potion's display color. -### 酿造 +### Brewing -现在,你的药水已经添加,药水物品可以使用你的药水了。但是,在生存模式下没有办法获得你的药水,所以让我们改变一下! +Now that your potion is added, potion items are available for your potion. However, there is no way to obtain your potion in survival, so let's change that! -传统上,药水是在酿造台上制作的。不幸的是,Mojang 没有为酿造配方提供 [数据包][datapack] 支持,因此我们必须有点老派,通过代码添加我们的配方。操作如下: +Potions are traditionally made in the Brewing Stand. Unfortunately, Mojang does not provide [datapack][datapack] support for brewing recipes, so we have to be a little old-fashioned and add our recipes through code. This is done like so: ```java -// 酿造成分。这是在酿造台顶部的物品。 +// The brewing ingredient. This is the item at the top of the brewing stand. Ingredient brewingIngredient = Ingredient.of(Items.FEATHER); BrewingRecipeRegistry.addRecipe( - // 输入药水成分,通常是一种混合药水。这是在酿造台底部的物品。 - // 不一定要是药水,但通常是。 + // The input potion ingredient, often an awkward potion. This is the item at the bottom of the brewing stand. + // Does not need to be a potion, but typically is. PotionUtils.setPotion(new ItemStack(Items.POTION), Potions.AWKWARD), - // 我们的酿造成分。 + // Our brewing ingredient. brewingIngredient, - // 结果物品堆栈。不一定要是药水,但通常是。 + // The resulting item stack. Does not need to be a potion, but typically is. PotionUtils.setPotion(new ItemStack(Items.POTION), MY_POTION) ); -// 对于溅射药水和挥发药水,我们还需要单独处理。 -// 原版的毒箭配方由 Minecraft 的毒箭特殊配方处理。 +// We also need to do this separately for splash potions and lingering potions. +// The tipped arrow recipe is taken care of by Minecraft's tipped arrow special crafting handler. BrewingRecipeRegistry.addRecipe( PotionUtils.setPotion(new ItemStack(Items.SPLASH_POTION), Potions.AWKWARD), brewingIngredient, @@ -188,7 +189,7 @@ BrewingRecipeRegistry.addRecipe( ); ``` -这应该在设置期间的某个时间调用,例如在 [`FMLCommonSetupEvent`][commonsetup] 中。确保将此代码包装到 `event.enqueueWork()` 调用中,因为酿造配方注册表不是线程安全的。 +This should be called some time during setup, for example during [`FMLCommonSetupEvent`][commonsetup]. Make sure to wrap this into an `event.enqueueWork()` call, as the brewing recipe registry is not thread-safe. [block]: ../blocks/index.md [commonsetup]: ../concepts/events.md#event-buses diff --git a/docs/items/tools.md b/docs/items/tools.md index dc840ab85..d37fcfb70 100644 --- a/docs/items/tools.md +++ b/docs/items/tools.md @@ -1,10 +1,10 @@ -## 工具与护甲 +# Tools & Armor -工具是其主要用途是破坏[方块][block]的[物品][item]。许多模组添加了新的工具套装(例如铜工具)或新的工具类型(例如锤子)。 +Tools are [items][item] whose primary use is to break [blocks][block]. Many mods add new tool sets (for example copper tools) or new tool types (for example hammers). -## 自定义工具套装 +## Custom Tool Sets -工具套装通常包含五种物品:镐、斧、铲、锄和剑。(剑在传统意义上不是工具,但为了保持一致性也包括在内。)所有这些物品都有对应的类:`PickaxeItem`、`AxeItem`、`ShovelItem`、`HoeItem` 和 `SwordItem`。工具的类层次结构如下所示: +A tool set typically consists of five items: a pickaxe, an axe, a shovel, a hoe and a sword. (Swords aren't tools in the classical sense, but are included here for consistency as well.) All of those items have their corresponding class: `PickaxeItem`, `AxeItem`, `ShovelItem`, `HoeItem` and `SwordItem`, respectively. The class hierarchy of tools looks as follows: ```text Item @@ -17,71 +17,70 @@ Item - SwordItem ``` -`TieredItem` 是一个包含了特定 `Tier`(详见下文)的辅助类。`DiggerItem` 包含了设计用于破坏方块的物品的辅助功能。请注意,其他通常被认为是工具的物品,例如剪刀,不包含在此层次结构中。它们直接扩展了 `Item`,并自行处理破坏逻辑。 +`TieredItem` is a class that contains helpers for items with a certain `Tier` (read on). `DiggerItem` contains helpers for items that are designed to break blocks. Note that other items usually considered tools, such as shears, are not included in this hierarchy. Instead, they directly extend `Item` and hold the breaking logic themselves. -要创建标准工具套装,首先必须定义一个 `Tier`。有关参考值,请参阅 Minecraft 的 `Tiers` 枚举。以下示例使用铜工具,你可以在此处使用你自己的材料并根据需要调整值。 +To create a standard set of tools, you must first define a `Tier`. For reference values, see Minecraft's `Tiers` enum. This example uses copper tools, you can use your own material here and adjust the values as needed. ```java -// 我们将铜放在石头和铁之间。 +// We place copper somewhere between stone and iron. public static final Tier COPPER_TIER = new SimpleTier( - // 确定此工具的等级。由于这是一个整数,没有很好的方法将我们的工具放在石头和铁之间。 - // 石头是 1,铁是 2。 + // Determines the level of this tool. Since this is an int, there is no good way to place our tool between stone and iron. + // NeoForge introduces the TierSortingRegistry to solve this problem, see below for more information. Use a best-effort approximation here. + // Stone is 1, iron is 2. 1, - // 确定等级的耐久性。 - // 石头是 131,铁是 250。 + // Determines the durability of the tier. + // Stone is 131, iron is 250. 200, - // 确定等级的挖掘速度。剑不使用此值。 - // 石头使用 4,铁使用 6。 + // Determines the mining speed of the tier. Unused by swords. + // Stone uses 4, iron uses 6. 5f, - // 确定攻击伤害加成。不同的工具使用不同的方式。例如,剑会造成 (getAttackDamageBonus() + 4) 的伤害。 - // 石头使用 1,铁使用 2,对应于剑的 5 和 6 攻击伤害;现在我们的剑造成 5.5 伤害。 + // Determines the attack damage bonus. Different tools use this differently. For example, swords do (getAttackDamageBonus() + 4) damage. + // Stone uses 1, iron uses 2, corresponding to 5 and 6 attack damage for swords, respectively; our sword does 5.5 damage now. 1.5f, - // 确定等级的附魔能力。这代表了此工具上附魔的好坏程度。 - // 金使用 22,我们将铜稍微低于这个值。 + // Determines the enchantability of the tier. This represents how good the enchantments on this tool will be. + // Gold uses 22, we put copper slightly below that. 20, - // 确定此工具可以破坏的方块的标签。详见下文。 + // The tag that determines what blocks this tool can break. See below for more information. MyBlockTags.NEEDS_COPPER_TOOL, - // 确定等级的修复原料。使用 Supplier 进行延迟初始化。 + // Determines the repair ingredient of the tier. Use a supplier for lazy initializing. () -> Ingredient.of(Tags.Items.INGOTS_COPPER) ); ``` -现在我们有了我们的 `Tier`,我们可以用它来注册工具。所有工具的构造函数都有相同的四个参数。 +Now that we have our `Tier`, we can use it for registering tools. All tool constructors have the same four parameters. ```java -// ITEMS 是一个 DeferredRegister +//ITEMS is a DeferredRegister public static final Supplier COPPER_SWORD = ITEMS.register("copper_sword", () -> new SwordItem( - // 要使用的等级。 + // The tier to use. COPPER_TIER, - // 类型特定的攻击伤害加成。剑为 3,铲为 1.5,镐为 1,斧和锄有所不同。 + // The type-specific attack damage bonus. 3 for swords, 1.5 for shovels, 1 for pickaxes, varying for axes and hoes. 3, - // 类型特定的攻击速度修正。玩家的默认攻击速度为 4,所以我们使用 -2.4f 来达到期望的值 1.6f。 - // 剑为 -2.4f,铲为 -3f,镐为 -2.8f,斧和锄有所不同。 + // The type-specific attack speed modifier. The player has a default attack speed of 4, so to get to the desired + // value of 1.6f, we use -2.4f. -2.4f for swords, -3f for shovels, -2.8f for pickaxes, varying for axes and hoes. -2.4f, - // 物品属性。我们不需要在这里设置耐久性,因为 TieredItem 会为我们处理。 + // The item properties. We don't need to set the durability here because TieredItem handles that for us. new Item.Properties() )); public static final Supplier COPPER_AXE = ITEMS.register("copper_axe", () -> new AxeItem(...)); public static final Supplier COPPER_PICKAXE = ITEMS.register("copper_pickaxe", () -> new PickaxeItem(...)); -public static final - - Supplier COPPER_SHOVEL = ITEMS.register("copper_shovel", () -> new ShovelItem(...)); +public static final Supplier COPPER_SHOVEL = ITEMS.register("copper_shovel", () -> new ShovelItem(...)); public static final Supplier COPPER_HOE = ITEMS.register("copper_hoe", () -> new HoeItem(...)); ``` -### 标签 +### Tags -创建 `Tier` 时,它被分配了一个包含需要此工具(或更好的工具)才能破坏的方块[标签][tags]。例如,`minecraft:needs_iron_tool` 标签包含了钻石矿石(以及其他方块),而 `minecraft:needs_diamond_tool` 标签包含了方块如黑曜石和远古残骸。 +When creating a `Tier`, it is assigned a block [tag][tags] containing blocks that need this tool (or a better one) to be broken. For example, the `minecraft:needs_iron_tool` tag contains Diamond Ores (among others), and the `minecraft:needs_diamond_tool` tag contains blocks like Obsidian and Ancient Debris. -如果你满意的话,你可以重用这些标签中的一个来制作你的工具。例如,如果我们想要我们的铜工具只是更耐用的石头工具,我们可以传入 `BlockTags.NEEDS_STONE_TOOL`。 +You can reuse one of these tags for your tool if you're fine with that. For example, if we wanted our copper tools to just be more durable stone tools, we'd pass in `BlockTags.NEEDS_STONE_TOOL`. -或者,我们可以创建自己的标签,操作如下: +Alternatively, we can create our own tag, like so: ```java public static final TagKey NEEDS_COPPER_TOOL = TagKey.create(BuiltInRegistries.BLOCK.key(), new ResourceLocation(MOD_ID, "needs_copper_tool")); ``` -然后,我们填充我们的标签。例如,让铜能够开采金矿石、金块和红石矿石,但不能开采钻石或绿宝石。 (红石块已经可以被石头工具开采了。)标签文件位于 `src/main/resources/data/mod_id/tags/blocks/needs_copper_tool.json`(其中 `mod_id` 是你的模组 ID): +And then, we populate our tag. For example, let's make copper able to mine gold ores, gold blocks and redstone ore, but not diamonds or emeralds. (Redstone blocks are already mineable by stone tools.) The tag file is located at `src/main/resources/data/mod_id/tags/blocks/needs_copper_tool.json` (where `mod_id` is your mod id): ```json { @@ -96,11 +95,11 @@ public static final TagKey NEEDS_COPPER_TOOL = TagKey.create(BuiltInRegis } ``` -最后,我们可以像上面看到的那样将我们的标签传递给我们的等级创建。 +Finally, we can pass our tag into our tier creation, as seen above. ### `TierSortingRegistry` -为了使游戏真正选择你的等级位于另外两个等级之间,你必须将其注册到 `TierSortingRegistry`。这必须在物品注册之前发生,将 `static` 初始化器放在与你的等级定义相同的类中是一个不错的选择。如果你不将你的等级添加到注册表中,它将退回到原版所做的操作。 +In order to make the game actually pick up your tier as between two others, you must register it to the `TierSortingRegistry`. This must happen before item registration, a `static` initializer in the same class as your tier definition is a good spot for that. If you do not add your tier to the registry, it will fall back to what vanilla would do. ```java public static final Tier COPPER_TIER = new SimpleTier(...); @@ -108,19 +107,19 @@ public static final Tier COPPER_TIER = new SimpleTier(...); static { TierSortingRegistry.registerTier( COPPER_TIER, - // 用于内部解析的名称。如果适用,可以使用 Minecraft 命名空间。 + //The name to use for internal resolution. May use the Minecraft namespace if appropriate. new ResourceLocation("minecraft", "copper"), - // 被认为低于正在添加的类型的一系列等级。例如,石头低于铜。 - // 我们不需要在这里添加木头和金,因为这些已经低于石头了。 + //A list of tiers that are considered lower than the type being added. For example, stone is lower than copper. + //We don't need to add wood and gold here because those are already lower than stone. List.of(Tiers.STONE), - // 被认为高于正在添加的类型的一系列等级。例如,铁高于铜。 - // 我们不需要在这里添加钻石和下界合金,因为这些已经高于铁了。 + //A list of tiers that are considered higher than the type being added. For example, iron is higher than copper. + //We don't need to add diamond and netherite here because those are already higher than iron. List.of(Tiers.IRON) ); } ``` -可以将其他等级的 ID 传递到这些列表中,作为 `Tier` 的替代或补充。例如,假设我们想要使我们的材料被认为比铁和 [Mekanism 工具][mektools] 的钨更弱,我们可以这样做: +Instead of or in addition to a `Tier`, you can also pass in other tiers' ids into these lists. For example, say we want to make our material be considered weaker than both iron and the Osmium tools from [Mekanism Tools][mektools], we could do that like so: ```java public static final Tier COPPER_TIER = new SimpleTier(...); @@ -130,119 +129,67 @@ static { COPPER_TIER, new ResourceLocation("minecraft", "copper"), List.of(Tiers.STONE), - // 我们可以在这里混合和匹配 Tiers 和 ResourceLocations。 + //We can mix and match Tiers and ResourceLocations here. List.of(Tiers.IRON, new ResourceLocation("mekanism", "osmium")) ); } ``` -## 工具与护甲 +This works for both the lower and higher tiers. If multiple options are available, the system will choose the strictest bounds available. -工具是主要用于破坏[方块][block]的[物品][item]。许多模组添加了新的工具套装(例如铜工具)或新的工具类型(例如锤子)。 +:::caution +Be aware that circular dependencies may occur if this is set up incorrectly, so make sure that your bounds actually make sense and don't all cross-reference one another. +::: -### 自定义工具套装 +If you want to check if a tier is applicable for a block state, call `TierSortingRegistry#isCorrectTierForDrops`. -工具套装通常由五种物品组成:镐、斧、铲、锄和剑。(剑在传统意义上不是工具,但为了保持一致性,也包括在内。)所有这些物品都有各自对应的类:`PickaxeItem`、`AxeItem`、`ShovelItem`、`HoeItem` 和 `SwordItem`。工具的类层次结构如下所示: +## Custom Tool Types -``` -Item -- TieredItem - - DiggerItem - - AxeItem - - HoeItem - - PickaxeItem - - ShovelItem - - SwordItem -``` +Custom tool types can be created by extending `DiggerItem` (or `TieredItem` if you are making custom weapon types). They don't need too big of a setup, it is an item class like any other, with all implications that has. -`TieredItem` 是一个包含某个 `Tier` 的辅助类(详见下文)。`DiggerItem` 包含了用于破坏方块的物品的辅助方法。请注意,其他通常被视为工具的物品(如剪刀)不包括在这个层次结构中。相反,它们直接扩展 `Item` 并自行处理破坏逻辑。 +One thing worth noting is the parameters of `DiggerItem`. The first four parameters are the same as for its subclasses (see the explanation for `SwordItem` above), while the fifth parameter is the `mineable` tag for the tool type. Generally, the format here is `:mineable/`, though `forge` can be used as the namespace too if you expect other mods to add similar tools. For example, [Farmer's Delight][farmersdelight] uses a `forge:mineable/knives` tag. -要创建标准的工具套装,首先必须定义一个 `Tier`。参考 Minecraft 的 `Tiers` 枚举获取参考值。以下示例使用铜工具,你可以在此处使用你自己的材料并根据需要调整值。 +If you plan on making a multitool-like item (i.e. an item that combines two or more tools into one, e.g. an axe and a pickaxe as one item), it is best to extend `AxeItem` if applicable. This is because enchantment checks for things like Sharpness or Knockback are hardcoded to `instanceof AxeItem`. -```java -// 我们将铜放在石头和铁之间。 -public static final Tier COPPER_TIER = new SimpleTier( - // 确定此工具的等级。由于这是一个整数,因此没有好的方法将我们的工具放置在石头和铁之间。 - // NeoForge 引入了 TierSortingRegistry 来解决这个问题,有关更多信息,请参见下文。在此处尽力估计。 - // 石头为 1,铁为 2。 - 1, - // 确定等级的耐久度。 - // 石头为 131,铁为 250。 - 200, - // 确定等级的挖掘速度。斧头不使用此项。 - // 石头使用 4,铁使用 6。 - 5f, - // 确定攻击伤害奖励。不同的工具使用方式不同。例如,剑会造成 (getAttackDamageBonus() + 4) 的伤害。 - // 石头使用 1,铁使用 2,对应于剑的伤害分别为 5 和 6;我们的剑现在造成 5.5 的伤害。 - 1.5f, - // 确定等级的附魔性。这代表了这个工具上附魔的好坏程度。 - // 金使用 22,我们稍微低于这个值。 - 20, - // 决定这个工具可以破坏哪些方块的标签。更多信息请参见下文。 - MyBlockTags.NEEDS_COPPER_TOOL, - // 确定等级的修复材料。使用供应商进行延迟初始化。 - () -> Ingredient.of(Tags.Items.INGOTS_COPPER) -); -``` - -现在我们有了我们的 `Tier`,我们可以在注册工具时使用它。所有工具构造函数都具有相同的四个参数。 - -```java -// ITEMS 是一个 DeferredRegister -public static final Supplier COPPER_SWORD = ITEMS.register("copper_sword", () -> new SwordItem( - // 要使用的等级。 - COPPER_TIER, - // 类型特定的攻击伤害奖励。剑为 3,铲子为 1.5,镐子为 1,斧头和锄头的值各不相同。 - 3, - // 类型特定的攻击速度修饰符。玩家的默认攻击速度为 4,所以要达到期望的值 1.6f,我们使用 -2.4f。对于剑,值为 -2.4f,铲子为 -3f,镐子为 -2.8f,斧头和锄头的值各不相同。 - -2.4f, - // 物品属性。我们不需要在此设置耐久度,因为 TieredItem 会为我们处理。 - new Item.Properties() -)); -public static final Supplier COPPER_AXE = ITEMS.register("copper_axe", () -> new AxeItem(...)); -public static final Supplier COPPER_PICKAXE = ITEMS.register("copper_pickaxe", () -> new PickaxeItem(...)); -public static final Supplier COPPER +## `ToolAction`s -_SHOVEL = ITEMS.register("copper_shovel", () -> new ShovelItem(...)); -``` - -### 工具动作 +`ToolAction`s are an abstraction over what a tool can and cannot do. This includes both left-click and right-click behavior. NeoForge provides default `ToolAction`s in the `ToolActions` class: -工具动作是工具能够执行和不能执行的操作的抽象。这包括左键和右键行为。NeoForge 在 `ToolActions` 类中提供了默认的 `ToolAction`: +- Digging actions. These exist for all four `DiggerItem` types as mentioned above, as well as sword and shears digging. +- Axe right-click actions for stripping (logs), scraping (oxidized copper) and unwaxing (waxed copper). +- Shear actions for harvesting (honeycombs), carving (pumpkins) and disarming (tripwires). +- Actions for shovel flattening (dirt paths), sword sweeping, hoe tilling, shield blocking, and fishing rod casting. -- 挖掘动作。这些适用于上文提到的所有四种 `DiggerItem` 类型,以及剑和剪刀挖掘。 -- 斧头右键动作用于去皮(原木)、刮(氧化铜)和去蜡(蜡质铜)。 -- 剪刀动作用于收获(蜜蜂巢)、雕刻(南瓜)和解除武装(绊线)。 -- 铲子平整(土径)、剑扫射、锄头耕作、盾牌阻挡和钓鱼竿抛出的动作。 +To create your own `ToolAction`s, use `ToolAction#get` - it will create a new `ToolAction` if needed. Then, in a custom tool type, override `IItemExtension#canPerformAction` as needed. -要创建自己的 `ToolAction`,请使用 `ToolAction#get` - 它会在需要时创建一个新的 `ToolAction`。然后,在自定义工具类型中根据需要覆盖 `IItemExtension#canPerformAction`。 +To query if an `ItemStack` can perform a certain `ToolAction`, call `IItemStackExtension#canPerformAction`. Note that this works on any `Item`, not just tools. -要查询一个 `ItemStack` 是否可以执行某个 `ToolAction`,请调用 `IItemStackExtension#canPerformAction`。请注意,这适用于任何 `Item`,而不仅仅是工具。 +## Armor -### 护甲 - -与工具类似,护甲也使用一个等级系统(尽管不同)。工具中称为 `Tier` 的东西在护甲中称为 `ArmorMaterial`。就像上面一样,这个例子展示了如何添加铜护甲;这可以根据需要进行调整。有关原始数值,请参见 `ArmorMaterials` 枚举。 +Similar to tools, armor uses a tier system (although a different one). What is called `Tier` for tools is called `ArmorMaterial` for armors. Like above, this example shows how to add copper armor; this can be adapted as needed. For the vanilla values, see the `ArmorMaterials` enum. ```java -// 我们将铜放在锁链甲和铁之间。 +// We place copper somewhere between chainmail and iron. public static final ArmorMaterial COPPER_ARMOR_MATERIAL = new ArmorMaterial() { - // 护甲材料的名称。主要用于确定护甲纹理的位置。应包含一个前导的模组标识符以确保唯一性,否则当两个模组尝试添加相同的护甲材料时可能会出现问题。(如果省略模组标识符,则将使用 "minecraft" 命名空间。) + // The name of the armor material. Mainly determines where the armor texture is. Should contain + // a leading mod id to guarantee uniqueness, otherwise there may be issues when two mods + // try to add the same armor material. (If the mod id is omitted, the "minecraft" namespace will be used.) @Override public String getName() { return "modid:copper"; } - // StringRepresentable 的重写。通常应与 getName() 返回相同的值。 + // Override for StringRepresentable. Should generally return the same as getName(). @Override public String getSerializedName() { return getName(); } - // 确定此护甲材料的耐久度,具体取决于护甲部件是什么。 - // ArmorItem.Type 是四个值的枚举:HELMET、CHESTPLATE、LEGGINGS 和 BOOTS。 - // Vanilla 护甲材料通过使用一个基础值并将其与类型特定的常量相乘来确定这一点。 - // 这些常量是 13(BOOTS)、15(LEGGINGS)、16(CHESTPLATE)和 11(HELMET)。 - // 锁链甲和铁都使用 15 作为基础值,所以我们也使用它。 + // Determines the durability of this armor material, depending on what armor piece it is. + // ArmorItem.Type is an enum of four values: HELMET, CHESTPLATE, LEGGINGS and BOOTS. + // Vanilla armor materials determine this by using a base value and multiplying it with a type-specific constant. + // The constants are 13 for BOOTS, 15 for LEGGINGS, 16 for CHESTPLATE and 11 for HELMET. + // Both chainmail and iron use 15 as the base value, so we'll use it as well. @Override public int getDurabilityForType(ArmorItem.Type type) { return switch (type) { @@ -253,7 +200,7 @@ public static final ArmorMaterial COPPER_ARMOR_MATERIAL = new ArmorMaterial() { }; } - // 确定此护甲材料的防御值,具体取决于护甲部件是什么。 + // Determines the defense value of this armor material, depending on what armor piece it is. @Override public int getDurabilityForType(ArmorItem.Type type) { return switch (type) { @@ -264,55 +211,59 @@ public static final ArmorMaterial COPPER_ARMOR_MATERIAL = new ArmorMaterial() { }; } - // 返回护甲的坚韧度值。坚韧度值是包含在伤害计算中的额外值,有关更多信息,请参见 Minecraft Wiki 上的护甲机制文章:https://minecraft.wiki/w/Armor#Armor_toughness - // 只有钻石和下界合金在这里的值大于 0,所以我们只返回 0。 + // Returns the toughness value of the armor. The toughness value is an additional value included in + // damage calculation, for more information, refer to the Minecraft Wiki's article on armor mechanics: + // https://minecraft.wiki/w/Armor#Armor_toughness + // Only diamond and netherite have values greater than 0 here, so we just return 0. @Override public float getToughness() { return 0; } - // 返回护甲的抗击退值。穿戴这种护甲时,玩家对击退具有一定程度的免疫。如果玩家从所有护甲部件中获得的总击退抗性值大于或等于 1,则它们将根本不受到任何击退。 - // 只有下界合金在这里的值大于 0,所以我们只返回 0。 + // Returns the knockback resistance value of the armor. While wearing this armor, the player is + // immune to knockback to some degree. If the player has a total knockback resistance value of 1 or greater + // from all armor pieces combined, they will not take any knockback at all. + // Only netherite has values greater than 0 here, so we just return 0. @Override public float getKnockbackResistance() { return 0; } - // 确定等级的附魔性。这代表了这个护甲上的附魔有多好。 - // 金使用 25,我们将铜放在稍低的位置。 + // Determines the enchantability of the tier. This represents how good the enchantments on this armor will be. + // Gold uses 25, we put copper slightly below that. @Override public int getEnchantmentValue(ArmorItem.Type type) { return 20; } - // 确定装备这件护甲时播放的声音。 + // Determines the sound played when equipping this armor. @Override public SoundEvent getEquipSound() { return SoundEvents.ARMOR_EQUIP_GENERIC; } - // 确定这件护甲的修复物品。 + // Determines the repair item for this armor. @Override public Ingredient getRepairIngredient() { return Ingredient.of(Tags.Items.INGOTS_COPPER); } - // 可选地,您还可以在这里重写 #getArmorTexture。此方法返回一个 ResourceLocation,用于确定存储护甲位置的位置,以防您希望将其存储在非默认位置。 - -有关示例,请参见 Tier 中的默认实现。 + // Optionally, you can also override #getArmorTexture here. This method returns a ResourceLocation + // that determines where the armor location is stored, in case you want to store it in a non-default location. + // See the default implementation in Tier for an example. } ``` -然后,在物品注册中使用该护甲材料。 +And then, we use that armor material in item registration. ```java -// ITEMS 是一个 DeferredRegister +//ITEMS is a DeferredRegister public static final Supplier COPPER_HELMET = ITEMS.register("copper_helmet", () -> new ArmorItem( - // 要使用的护甲材料。 + // The armor material to use. COPPER_ARMOR_MATERIAL, - // 要使用的护甲类型。 + // The armor type to use. ArmorItem.Type.HELMET, - // 物品属性。我们不需要在此设置耐久度,因为 ArmorItem 会为我们处理。 + // The item properties. We don't need to set the durability here because ArmorItem handles that for us. new Item.Properties() )); public static final Supplier COPPER_CHESTPLATE = ITEMS.register("copper_chestplate", () -> new ArmorItem(...)); @@ -320,9 +271,9 @@ public static final Supplier COPPER_LEGGINGS = ITEMS.register("copper public static final Supplier COPPER_BOOTS = ITEMS.register("copper_boots", () -> new ArmorItem(...)); ``` -除了通常的资源外,护甲还需要一个穿戴时的护甲纹理,它将在装备护甲时渲染在玩家模型上。该纹理必须位于 `src/main/resources/assets//textures/models/armor/_layer_1.png`(头盔、胸甲和靴子的纹理),以及相同目录中的 `_layer_2.png`(护腿的纹理)。 +Besides the usual resources, armors also need a worn armor texture that will be rendered over the player model when the armor is equipped. This texture must be located at `src/main/resources/assets//textures/models/armor/_layer_1.png` for the helmet, chestplate and boots textures, and in the same directory at `_layer_2.png` for the leggings. -创建护甲纹理时,最好在基于标准护甲纹理的基础上进行工作,以确定每个部分的位置。 +When creating your armor texture, it is a good idea to work on top of the vanilla armor texture to see which part goes where. [block]: ../blocks/index.md [farmersdelight]: https://www.curseforge.com/minecraft/mc-mods/farmers-delight diff --git a/docs/legacy/porting.md b/docs/legacy/porting.md index 9e50a0119..cc7c0480b 100644 --- a/docs/legacy/porting.md +++ b/docs/legacy/porting.md @@ -1,17 +1,17 @@ -移植到 Minecraft 1.20 +Porting to Minecraft 1.20 ========================= -在这里,您可以找到有关如何从旧版本移植到当前版本的入门指南。某些版本被合并在一起,因为该特定版本从未被广泛使用。 +Here you can find a list of primers on how to port from old versions to the current version. Some versions are lumped together since that particular version never saw much usage. -| 从 -> 到 | 入门指南 | +| From -> To | Primer | |:-----------------:|:----------------------------------------| -| 1.12 -> 1.13/1.14 | [williewillus 的入门指南][112to114] | -| 1.14 -> 1.15 | [williewillus 的入门指南][114to115] | -| 1.15 -> 1.16 | [50ap5ud5 的入门指南][115to116] | -| 1.16 -> 1.17 | [50ap5ud5 的入门指南][116to117] | -| 1.19.2 -> 1.19.3 | [ChampionAsh5357 的入门指南][1192to1193] | -| 1.19.3 -> 1.19.4 | [ChampionAsh5357 的入门指南][1193to1194] | -| 1.19.4 -> 1.20.0 | [ChampionAsh5357 的入门指南][1194to120] | +| 1.12 -> 1.13/1.14 | [Primer by williewillus][112to114] | +| 1.14 -> 1.15 | [Primer by williewillus][114to115] | +| 1.15 -> 1.16 | [Primer by 50ap5ud5][115to116] | +| 1.16 -> 1.17 | [Primer by 50ap5ud5][116to117] | +| 1.19.2 -> 1.19.3 | [Primer by ChampionAsh5357][1192to1193] | +| 1.19.3 -> 1.19.4 | [Primer by ChampionAsh5357][1193to1194] | +| 1.19.4 -> 1.20.0 | [Primer by ChampionAsh5357][1194to120] | [112to114]: https://gist.github.com/williewillus/353c872bcf1a6ace9921189f6100d09a [114to115]: https://gist.github.com/williewillus/30d7e3f775fe93c503bddf054ef3f93e diff --git a/docs/misc/config.md b/docs/misc/config.md index e6f94f3eb..6ba9abdc6 100644 --- a/docs/misc/config.md +++ b/docs/misc/config.md @@ -1,136 +1,136 @@ -配置 +Configuration ============= -配置定义了可应用于模组实例的设置和用户偏好。NeoForge 使用 [TOML][toml] 文件并使用 [NightConfig][nightconfig] 进行读取的配置系统。 +Configurations define settings and consumer preferences that can be applied to a mod instance. NeoForge uses a configuration system using [TOML][toml] files and read with [NightConfig][nightconfig]. -创建配置 +Creating a Configuration ------------------------ -可以使用 `IConfigSpec` 的子类型来创建配置。NeoForge 通过 `ModConfigSpec` 实现了该类型,并通过 `ModConfigSpec.Builder` 启用其构建。该构建器可以通过 `Builder#push` 将配置值分成部分以创建一个部分,通过 `Builder#pop` 离开一个部分。然后,可以使用以下两种方法之一构建配置: +A configuration can be created using a subtype of `IConfigSpec`. NeoForge implements the type via `ModConfigSpec` and enables its construction through `ModConfigSpec.Builder`. The builder can separate the config values into sections via `Builder#push` to create a section and `Builder#pop` to leave a section. Afterwards, the configuration can be built using one of two methods: - 方法 | 描述 + Method | Description :--- | :--- -`build` | 创建 `ModConfigSpec`。 -`configure` | 创建持有配置值的类和 `ModConfigSpec` 的一对。 +`build` | Creates the `ModConfigSpec`. +`configure` | Creates a pair of the class holding the config values and the `ModConfigSpec`. :::note -`ModConfigSpec.Builder#configure` 通常与 `static` 块和一个类一起使用,该类作为其构造函数的一部分接受 `ModConfigSpec.Builder` 来附加和保存值: +`ModConfigSpec.Builder#configure` is typically used with a `static` block and a class that takes in `ModConfigSpec.Builder` as part of its constructor to attach and hold the values: ```java -// 在某个配置类中 +// In some config class ExampleConfig(ModConfigSpec.Builder builder) { - // 在此定义值的最终字段 + // Define values here in final fields } -// 某处可以访问构造函数 +// Somewhere the constructor is accessible static { Pair pair = new ModConfigSpec.Builder() .configure(ExampleConfig::new); - // 将配对值存储在某个常量字段中 + // Store pair values in some constant field } ``` ::: -每个配置值可以提供额外的上下文以提供附加行为。必须在完全构建配置值之前定义上下文: +Each config value can be supplied with additional context to provide additional behavior. Contexts must be defined before the config value is fully built: -| 方法 | 描述 | +| Method | Description | |:---------------|:------------------------------------------------------------------------------------------------------------| -| `comment` | 提供配置值功能的描述。可以为多行注释提供多个字符串。 | -| `translation` | 为配置值的名称提供翻译键。 | -| `worldRestart` | 必须在更改配置值之前重新启动世界。 | +| `comment` | Provides a description of what the config value does. Can provide multiple strings for a multiline comment. | +| `translation` | Provides a translation key for the name of the config value. | +| `worldRestart` | The world must be restarted before the config value can be changed. | ### ConfigValue -可以使用提供的上下文(如果已定义)使用任何 `#define` 方法构建配置值。 +Config values can be built with the provided contexts (if defined) using any of the `#define` methods. -所有配置值方法至少接受两个组件: +All config value methods take in at least two components: -* 表示变量名称的路径:一个 `.` 分隔的字符串,表示配置值所在的部分 -* 当没有有效配置时的默认值 +* A path representing the name of the variable: a `.` separated string representing the sections the config value is in +* The default value when no valid configuration is present -`ConfigValue` 特定的方法接受两个额外的组件: +The `ConfigValue` specific methods take in two additional components: -* 验证器,以确保反序列化的对象有效 -* 表示配置值的数据类型的类 +* A validator to make sure the deserialized object is valid +* A class representing the data type of the config value ```java -// 对于某个 ModConfigSpec.Builder builder +// For some ModConfigSpec.Builder builder ConfigValue value = builder.comment("Comment") .define("config_value_name", defaultValue); ``` -还可以使用 `ConfigValue#get` 获取值。值还被缓存以防止从文件中进行多次读取。 +The values themselves can be obtained using `ConfigValue#get`. The values are additionally cached to prevent multiple readings from files. -#### 额外的配置值类型 +#### Additional Config Value Types -* **范围值** - * 描述:值必须在定义的边界之间 - * 类型:`Comparable` - * 方法名:`#defineInRange` - * 额外组件: - * 配置值可能的最小值和最大值 - * 表示配置值的数据类型的类 +* **Range Values** + * Description: Value must be between the defined bounds + * Class Type: `Comparable` + * Method Name: `#defineInRange` + * Additional Components: + * The minimum and maximum the config value may be + * A class representing the data type of the config value :::note -`DoubleValue`、`IntValue` 和 `LongValue` 是范围值,它们将类指定为 `Double`、`Integer` 和 `Long`,分别。 +`DoubleValue`s, `IntValue`s, and `LongValue`s are range values which specify the class as `Double`, `Integer`, and `Long` respectively. ::: -* **白名单值** - * 描述:值必须在提供的集合中 - * 类型:`T` - * 方法名:`#defineInList` - * 额外组件: - * 配置可以是哪些值的集合 - -* **列表值** - * 描述:值是一系列条目 - * 类型:`List` - * 方法名:`#defineList`,如果列表可以为空,则为 `#defineListAllowEmpty` - * 额外组件: - * 验证器,以确保从列表中反序列化的元素有效 - -* **枚举值** - * 描述:在提供的集合中的枚举值 - * 类型:`Enum` - * 方法名:`#defineEnum` - * 额外组件: - * 一个 getter,将字符串或整数转换为枚举 - * 配置可以是哪些值的集合 - -* **布尔值** - * 描述:一个 `boolean` 值 - * 类型:`Boolean` - * 方法名:`#define` - -注册配置 +* **Whitelisted Values** + * Description: Value must be in supplied collection + * Class Type: `T` + * Method Name: `#defineInList` + * Additional Components: + * A collection of the allowed values the configuration can be + +* **List Values** + * Description: Value is a list of entries + * Class Type: `List` + * Method Name: `#defineList`, `#defineListAllowEmpty` if list can be empty + * Additional Components: + * A validator to make sure a deserialized element from the list is valid + +* **Enum Values** + * Description: An enum value in the supplied collection + * Class Type: `Enum` + * Method Name: `#defineEnum` + * Additional Components: + * A getter to convert a string or integer into an enum + * A collection of the allowed values the configuration can be + +* **Boolean Values** + * Description: A `boolean` value + * Class Type: `Boolean` + * Method Name: `#define` + +Registering a Configuration --------------------------- -一旦构建了 `ModConfigSpec`,就必须注册它以允许 NeoForge 加载、跟踪和根据需要同步配置设置。配置应该在模组构造函数中通过 `ModLoadingContext#registerConfig` 注册。可以使用给定的类型(表示配置所属的一侧)、`ModConfigSpec` 和可选的特定文件名为配置注册。 +Once a `ModConfigSpec` has been built, it must be registered to allow NeoForge to load, track, and sync the configuration settings as required. Configurations should be registered in the mod constructor via `ModLoadingContext#registerConfig`. A configuration can be registered with a given type representing the side the config belongs to, the `ModConfigSpec`, and optionally a specific file name for the configuration. ```java -// 在具有 ModConfigSpec CONFIG 的模组构造函数中 +// In the mod constructor with a ModConfigSpec CONFIG ModLoadingContext.get().registerConfig(Type.COMMON, CONFIG); ``` -以下是可用的配置类型列表: +Here is a list of the available configuration types: -| 类型 | 加载 | 同步到客户端 | 客户端位置 | 服务器位置 | 默认文件后缀 | +| Type | Loaded | Synced to Client | Client Location | Server Location | Default File Suffix | |:------:|:----------------:|:----------------:|:--------------------------------------------:|:------------------------------------:|:--------------------| -| CLIENT | 仅客户端 | 否 | `.minecraft/config` | N/A | `-client` | -| COMMON | 两边都有 | 否 | `.minecraft/config` | `/config` | `-common` | -| SERVER | 仅服务器 | 是 | `.minecraft/saves//serverconfig` | `/world/serverconfig` | `-server` | +| CLIENT | Client Side Only | No | `.minecraft/config` | N/A | `-client` | +| COMMON | On Both Sides | No | `.minecraft/config` | `/config` | `-common` | +| SERVER | Server Side Only | Yes | `.minecraft/saves//serverconfig` | `/world/serverconfig` | `-server` | :::tip -NeoForge 在其代码库中记录了[配置类型][type]。 +NeoForge documents the [config types][type] within their codebase. ::: -配置事件 +Configuration Events -------------------- -可以使用 `ModConfigEvent$Loading` 和 `ModConfigEvent$Reloading` 事件在加载或重新加载配置时执行的操作。必须将这些事件[注册][events]到模组事件总线上。 +Operations that occur whenever a config is loaded or reloaded can be done using the `ModConfigEvent$Loading` and `ModConfigEvent$Reloading` events. The events must be [registered][events] to the mod event bus. :::caution -这些事件适用于模组的所有配置;应使用提供的 `ModConfig` 对象来指示正在加载或重新加载的配置。 +These events are called for all configurations for the mod; the `ModConfig` object provided should be used to denote which configuration is being loaded or reloaded. ::: [toml]: https://toml.io/ diff --git a/docs/misc/debugprofiler.md b/docs/misc/debugprofiler.md index 15e821aed..f59e33b56 100644 --- a/docs/misc/debugprofiler.md +++ b/docs/misc/debugprofiler.md @@ -1,22 +1,23 @@ -# 调试性能分析器 +# Debug Profiler -Minecraft 提供了一个调试性能分析器,它提供系统数据、当前游戏设置、JVM 数据、级别数据和边界刻信息,以找到耗时的代码。考虑到诸如 `TickEvent` 和刻动 `BlockEntities` 等因素,这对于想要找到卡顿来源的模组开发者和服务器所有者非常有用。 +Minecraft provides a Debug Profiler that provides system data, current game settings, JVM data, level data, and sided tick information to find time consuming code. Considering things like `TickEvent`s and ticking `BlockEntities`, this can be very useful for modders and server owners that want to find a lag source. -## 使用调试性能分析器 +## Using the Debug Profiler -调试性能分析器非常简单易用。它需要使用调试按键组合 `F3 + L` 来启动分析器。10 秒后,它将自动停止;但是,您也可以通过再次按下该组合键来提前停止。 +The Debug Profiler is very simple to use. It requires the debug keybind `F3 + L` to start the profiler. After 10 seconds, it will automatically stop; however, it can be stopped earlier by pressing the keybind again. :::note -自然而然,您只能分析实际被执行的代码路径。您想要分析的 `实体` 和 `BlockEntities` 必须存在于级别中才会出现在结果中。 +Naturally, you can only profile code paths that are actually being reached. `Entities` and `BlockEntities` that you want to profile must exist in the level to show up in the results. ::: -在停止调试器后,它将在运行目录的 `debug/profiling` 子目录中创建一个新的 zip 文件。文件名将以日期和时间格式化为 `yyyy-mm-dd_hh_mi_ss-WorldName-VersionNumber.zip` +After you have stopped the debugger, it will create a new zip within the `debug/profiling` subdirectory in your run directory. +The file name will be formatted with the date and time as `yyyy-mm-dd_hh_mi_ss-WorldName-VersionNumber.zip` -## 阅读性能分析结果 +## Reading a Profiling result -在每个边界文件夹 (`client` 和 `server`) 中,您会找到一个包含结果数据的 `profiling.txt` 文件。在顶部,它首先告诉您在运行的毫秒数以及在此期间运行了多少个刻。 +Within each sided folder (`client` and `server`), you will find a `profiling.txt` file containing the result data. At the top, it first tells you how long in milliseconds it was running and how many ticks ran in that time. -在此之下,您会发现类似于以下片段的信息: +Below that, you will find information similar to the snippet below: ``` [00] levels - 96.70%/96.70% [01] | Level Name - 99.76%/96.47% @@ -28,18 +29,19 @@ Minecraft 提供了一个调试性能分析器,它提供系统数据、当前 [05] | | | | | minecraft:furnace - 33.35%/0.14% [05] | | | | | minecraft:chest - 2.39%/0.01% ``` -这里是每个部分的简要解释 +Here is a small explanation of what each part means | [02] | tick | 99.31% | 95.81% | | :----------------------- | :---------------------- | :----------- | :----------- | -| 该部分的深度 | 该部分的名称 | 它花费的时间与其父部分的百分比。对于层级 0,它是一次刻所花费时间的百分比。对于层级 1,它是其父部分所花费时间的百分比。 | 第二个百分比告诉您它从整个刻中花费了多少时间。 +| The Depth of the section | The Name of the Section | The percentage of time it took in relation to it's parent. For Layer 0, it is the percentage of the time a tick takes. For Layer 1, it is the percentage of the time its parent takes. | The second percentage tells you how much time it took from the entire tick. -## 对自己的代码进行性能分析 +## Profiling your own code -调试性能分析器对 `Entity` 和 `BlockEntity` 有基本支持。如果您想分析其他内容,您可能需要手动创建您的部分,如下所示: +The Debug Profiler has basic support for `Entity` and `BlockEntity`. If you would like to profile something else, you may need to manually create your sections like so: ```java ProfilerFiller#push(yourSectionName : String); -//您想要分析的代码 +//The code you want to profile ProfilerFiller#pop(); ``` -您可以从 `Level`、`MinecraftServer` 或 `Minecraft` 实例获取 `ProfilerFiller` 实例。现在,您只需要在结果文件中搜索您的部分名称即可。 +You can obtain the `ProfilerFiller` instance from a `Level`, `MinecraftServer`, or `Minecraft` instance. +Now you just need to search the results file for your section name. diff --git a/docs/misc/gametest.mdx b/docs/misc/gametest.mdx index 981b1c40c..7562122c9 100644 --- a/docs/misc/gametest.mdx +++ b/docs/misc/gametest.mdx @@ -1,21 +1,22 @@ -# 游戏测试 +Game Tests +========== -游戏测试是一种运行游戏内单元测试的方法。该系统被设计为可扩展并且并行运行,以有效地运行大量不同的测试。测试对象的交互和行为只是该框架的众多应用之一。 +Game Tests are a way to run in-game unit tests. The system was designed to be scalable and in parallel to run large numbers of different tests efficiently. Testing object interactions and behaviors are simply a few of the many applications of this framework. -创建游戏测试 ------------- +Creating a Game Test +-------------------- -标准的游戏测试遵循三个基本步骤: +A standard Game Test follows three basic steps: -1. 加载一个结构或模板,其中包含要测试的交互或行为所在的场景。 -2. 一个方法执行逻辑来执行场景上的操作。 -3. 方法逻辑执行。如果达到了成功的状态,则测试成功。否则,测试失败,并将结果存储在场景旁边的讲台上。 +1. A structure, or template, is loaded holding the scene on which the interaction or behavior is tested. +1. A method conducts the logic to perform on the scene. +1. The method logic executes. If a successful state is reached, then the test succeeds. Otherwise, the test fails and the result is stored within a lectern adjacent to the scene. -因此,要创建一个游戏测试,必须有一个存在的模板,其中包含场景的初始起始状态,以及一个提供执行逻辑的方法。 +As such, to create a Game Test, there must be an existing template holding the initial start state of the scene and a method which provides the logic of execution. -### 测试方法 +### The Test Method -游戏测试方法是一个 `Consumer` 引用,意味着它接受一个 `GameTestHelper` 并且不返回任何内容。为了使游戏测试方法被识别,它必须有一个 `@GameTest` 注解: +A Game Test method is a `Consumer` reference, meaning it takes in a `GameTestHelper` and returns nothing. For a Game Test method to be recognized, it must have a `@GameTest` annotation: ```java public class ExampleGameTests { @@ -26,100 +27,98 @@ public class ExampleGameTests { } ``` -`@GameTest` 注解还包含配置游戏测试运行方式的成员。 +The `@GameTest` annotation also contains members which configure how the game test should run. ```java -// 在某个类中 +// In some class @GameTest( - setupTicks = 20L, // 测试花费 20 刻来设置执行环境 - required = false // 失败会被记录,但不会影响批次的执行 + setupTicks = 20L, // The test spends 20 ticks to set up for execution + required = false // The failure is logged but does not affect the execution of the batch ) public static void exampleConfiguredTest(GameTestHelper helper) { // Do stuff } ``` -#### 相对定位 +#### Relative Positioning -所有 `GameTestHelper` 方法都将结构模板场景中的相对坐标转换为其绝对坐标,使用结构方块的当前位置。为了方便相对和绝对定位之间的转换,可以分别使用 `GameTestHelper#absolutePos` 和 `GameTestHelper#relativePos`。 +All `GameTestHelper` methods translate relative coordinates within the structure template scene to its absolute coordinates using the structure block's current location. To allow for easy conversion between relative and absolute positioning, `GameTestHelper#absolutePos` and `GameTestHelper#relativePos` can be used respectively. -通过在游戏中加载结构并使用 [测试命令][test] 将玩家放置在所需位置,最后运行 `/test pos` 命令,可以获取结构模板的相对位置。该命令将以可复制的文本组件的形式在聊天中导出相对于玩家所在位置的最近结构的坐标。这个导出的文本可以作为最终的局部变量使用。 +The relative position of a structure template can be obtained in-game by loading the structure via the [test command][test], placing the player at the wanted location, and finally running the `/test pos` command. This will grab the coordinates of the player relative to the closest structure within 200 blocks of the player. The command will export the relative position as a copyable text component in the chat to be used as a final local variable. :::tip -`/test pos` 生成的本地变量可以通过将其附加到命令的末尾来指定其引用名称: +The local variable generated by `/test pos` can specify its reference name by appending it to the end of the command: ```bash -/test pos # 导出 'final BlockPos = new BlockPos(...);' +/test pos # Exports 'final BlockPos = new BlockPos(...);' ``` -::: +:::tip -#### 成功完成 +#### Successful Completion -游戏测试方法负责一件事情:在有效完成时标记测试为成功。如果在达到超时之前(由 `GameTest#timeoutTicks` 定义)没有达到成功状态,则测试会自动失败。 +A Game Test method is responsible for one thing: marking the test was successful on a valid completion. If no success state was achieved before the timeout is reached (as defined by `GameTest#timeoutTicks`), then the test automatically fails. -在 `GameTestHelper` 中有许多抽象方法可以用来定义成功状态;然而,有四个是非常重要的需要注意的。 +There are many abstracted methods within `GameTestHelper` which can be used to define a successful state; however, four are extremely important to be aware of. -方法 | 描述 -:---: | :--- -`#succeed` | 将测试标记为成功。 -`#succeedIf` | 立即测试提供的 `Runnable`,如果没有抛出 `GameTestAssertException`,则成功。如果在当前刻没有成功,则标记为失败。 -`#succeedWhen` | 每刻测试提供的 `Runnable`,直到超时,如果在其中一刻检查不会抛出 `GameTestAssertException` 则成功。 -`#succeedOnTickWhen` | 在指定的刻上测试提供的 `Runnable`,如果没有抛出 `GameTestAssertException`,则成功。如果在任何其他刻上成功,则标记为失败。 +Method | Description +:---: | :--- +`#succeed` | The test is marked as successful. +`#succeedIf` | The supplied `Runnable` is tested immediately and succeeds if no `GameTestAssertException` is thrown. If the test does not succeed on the immediate tick, then it is marked as a failure. +`#succeedWhen` | The supplied `Runnable` is tested every tick until timeout and succeeds if the check on one of the ticks does not throw a `GameTestAssertException`. +`#succeedOnTickWhen` | The supplied `Runnable` is tested on the specified tick and will succeed if no `GameTestAssertException` is thrown. If the `Runnable` succeeds on any other tick, then it is marked as a failure. :::caution -游戏测试会每刻执行一次,直到测试被标记为成功为止。因此,为了成功标记某一特定刻上的成功,必须在之前的任何刻上都失败。 +Game Tests are executed every tick until the test is marked as a success. As such, methods which schedule success on a given tick must be careful to always fail on any previous tick. ::: -#### 安排操作 +#### Scheduling Actions -并不是所有的操作在测试开始时都会发生。可以安排在特定时间或间隔发生的操作: +Not all actions will occur when a test begins. Actions can be scheduled to occur at specific times or intervals: -方法 | 描述 +Method | Description :---: | :--- -`#runAtTickTime` | 在指定的刻上运行操作。 -`#runAfterDelay` | 在当前刻之后的 `x` 刻运行操作。 -`#onEachTick` | 每一刻都运行操作。 +`#runAtTickTime` | The action is ran on the specified tick. +`#runAfterDelay` | The action is ran `x` ticks after the current tick. +`#onEachTick` | The action is ran every tick. -#### 断言 +#### Assertions -在游戏测试的任何时候,都可以进行断言来检查给定条件是否为真。在 `GameTestHelper` 中有许多断言方法;然而,简化为在适当状态未满足时抛出 `GameTestAssertException`。 +At any time during a Game Test, an assertion can be made to check if a given condition is true. There are numerous assertion methods within `GameTestHelper`; however, it simplifies to throwing a `GameTestAssertException` whenever the appropriate state is not met. -### 生成的测试方法 +### Generated Test Methods -如果游戏测试方法需要动态生成,可以创建一个测试方法生成器。这些方法不接受任何参数,并返回一个 `TestFunction` 集合。为了使测试方法生成器被识别,它必须有一个 `@GameTestGenerator` 注解: +If Game Test methods need to be generated dynamically, a test method generator can be created. These methods take in no parameters and return a collection of `TestFunction`s. For a test method generator to be recognized, it must have a `@GameTestGenerator` annotation: ```java public class ExampleGameTests { @GameTestGenerator public static Collection exampleTests() { - // 返回一个 TestFunction 集合 + // Return a collection of TestFunctions } } ``` #### TestFunction -`TestFunction` 是由 `@GameTest` 注解和运行测试的方法所包装的信息。 +A `TestFunction` is the boxed information held by the `@GameTest` annotation and the method running the test. :::tip -使用 `@GameTest` 注解的任何方法都会使用 `GameTestRegistry#turnMethodIntoTestFunction` 将其转换为 `TestFunction`。该方法可用作创建 `TestFunction` 的参考,而不需要使用注解。 +Any methods annotated using `@GameTest` are translated into a `TestFunction` using `GameTestRegistry#turnMethodIntoTestFunction`. That method can be used as a reference for creating `TestFunction`s without the use of the annotation. ::: -### 批次处理 - -游戏测试可以以批次方式而不是按照注册顺序执行。可以通过具有相同的提供的 `GameTest#batch` 字符串将测试添加到 +### Batching -批次中。 +Game Tests can be executed in batches instead of registration order. A test can be added to a batch by having the same supplied `GameTest#batch` string. -仅有批处理本身并不提供任何有用的功能。然而,批处理可以用于在测试运行的当前级别上执行设置和拆卸状态。通过将方法标记为 `@BeforeBatch` 进行设置,或标记为 `@AfterBatch` 进行拆卸。`#batch` 方法必须与游戏测试中提供的字符串匹配。 +On its own, batching does not provide anything useful. However, batching can be used to perform setup and teardown states on the current level the tests are running in. This is done by annotating a method with either `@BeforeBatch` for setup or `@AfterBatch` for takedown. The `#batch` methods must match the string supplied to the game test. -批处理方法是 `Consumer` 引用,意味着它们接受一个 `ServerLevel` 并且不返回任何内容: +Batch methods are `Consumer` references, meaning they take in a `ServerLevel` and return nothing: ```java public class ExampleGameTests { @BeforeBatch(batch = "firstBatch") public static void beforeTest(ServerLevel level) { - // 执行设置 + // Perform setup } @GameTest(batch = "firstBatch") @@ -129,14 +128,14 @@ public class ExampleGameTests { } ``` -注册游戏测试 ----------- +Registering a Game Test +----------------------- -游戏测试必须注册才能在游戏中运行。有两种方法可以实现:通过 `@GameTestHolder` 注解或 `RegisterGameTestsEvent`。这两种注册方法仍然要求测试方法被注解为 `@GameTest`、`@GameTestGenerator`、`@BeforeBatch` 或 `@AfterBatch` 中的一种。 +A Game Test must be registered to be ran in-game. There are two methods of doing so: via the `@GameTestHolder` annotation or `RegisterGameTestsEvent`. Both registration methods still require the test methods to be annotated with either `@GameTest`, `@GameTestGenerator`, `@BeforeBatch`, or `@AfterBatch`. ### GameTestHolder -`@GameTestHolder` 注解注册类型(类、接口、枚举或记录)中的任何测试方法。`@GameTestHolder` 包含一个方法,具有多种用途。在此示例中,提供的 `#value` 必须是 mod 的 id;否则,测试将不会在默认配置下运行。 +The `@GameTestHolder` annotation registers any test methods within the type (class, interface, enum, or record). `@GameTestHolder` contains a single method which has multiple uses. In this instance, the supplied `#value` must be the mod id of the mod; otherwise, the test will not run under default configurations. ```java @GameTestHolder(MODID) @@ -147,124 +146,124 @@ public class ExampleGameTests { ### RegisterGameTestsEvent -`RegisterGameTestsEvent` 也可以通过 `#register` 注册类或方法。事件监听器必须 [添加][event] 到 mod 事件总线上。以这种方式注册的测试方法必须在每个标注了 `@GameTest` 的方法中提供它们的 mod id 给 `GameTest#templateNamespace`。 +`RegisterGameTestsEvent` can also register either classes or methods using `#register`. The event listener must be [added][event] to the mod event bus. Test methods registered this way must supply their mod id to `GameTest#templateNamespace` on every method annotated with `@GameTest`. ```java -// 在某个类中 +// In some class public void registerTests(RegisterGameTestsEvent event) { event.register(ExampleGameTests.class); } -// 在 ExampleGameTests 中 +// In ExampleGameTests @GameTest(templateNamespace = MODID) public static void exampleTest3(GameTestHelper helper) { - // 执行设置 + // Perform setup } ``` :::note -提供给 `GameTestHolder#value` 和 `GameTest#templateNamespace` 的值可以与当前 mod id 不同。在 [buildscript][namespaces] 中的配置需要更改。 +The value supplied to `GameTestHolder#value` and `GameTest#templateNamespace` can be different from the current mod id. The configuration within the [buildscript][namespaces] would need to be changed. ::: -结构模板 ------------ +Structure Templates +------------------- -游戏测试在由结构或模板加载的场景中执行。所有模板都定义了场景的尺寸以及将要加载的初始数据(方块和实体)。模板必须以 `.nbt` 文件的形式存储在 `data//structures` 目录中。 +Game Tests are performed within scenes loaded by structures, or templates. All templates define the dimensions of the scene and the initial data (blocks and entities) that will be loaded. The template must be stored as an `.nbt` file within `data//structures`. :::tip -可以使用结构方块创建并保存结构模板。 +A structure template can be created and saved using a structure block. ::: -模板的位置由以下几个因素确定: +The location of the template is specified by a few factors: -- 如果指定了模板的命名空间。 -- 如果类应该作为模板名称的前缀。 -- 如果指定了模板的名称。 +* If the namespace of the template is specified. +* If the class should be prepended to the name of the template. +* If the name of the template is specified. -模板的命名空间由 `GameTest#templateNamespace` 确定,如果未指定,则由 `GameTestHolder#value` 确定,如果两者都未指定,则由 `minecraft` 确定。 +The namespace of the template is determined by `GameTest#templateNamespace`, then `GameTestHolder#value` if not specified, then `minecraft` if neither is specified. -如果将 `@PrefixGameTestTemplate` 应用于带有测试注解的类或方法,并且设置为 `false`,则不会将简单类名添加到模板名称的前面。否则,简单类名将被转换为小写并添加到模板名称的前后,中间用点分隔。 +The simple class name is not prepended to the name of the template if the `@PrefixGameTestTemplate` is applied to a class or method with the test annotations and set to `false`. Otherwise, the simple class name is made lowercase and prepended and followed by a dot before the template name. -模板的名称由 `GameTest#template` 确定。如果未指定,则使用方法的小写名称。 +The name of the template is determined by `GameTest#template`. If not specified, then the lowercase name of the method is used instead. ```java -// 所有结构的 modid 将为 MODID +// Modid for all structures will be MODID @GameTestHolder(MODID) public class ExampleGameTests { - // 类名作为前缀,未指定模板名称 - // 模板位置为 'modid:examplegametests.exampletest' + // Class name is prepended, template name is not specified + // Template Location at 'modid:examplegametests.exampletest' @GameTest public static void exampleTest(GameTestHelper helper) { /*...*/ } - // 类名不作为前缀,未指定模板名称 - // 模板位置为 'modid:exampletest2' + // Class name is not prepended, template name is not specified + // Template Location at 'modid:exampletest2' @PrefixGameTestTemplate(false) @GameTest public static void exampleTest2(GameTestHelper helper) { /*...*/ } - // 类名作为前缀,指定了模板名称 - // 模板位置为 'modid:examplegametests.test_template' + // Class name is prepended, template name is specified + // Template Location at 'modid:examplegametests.test_template' @GameTest(template = "test_template") public static void exampleTest3(GameTestHelper helper) { /*...*/ } - // 类名不作为前缀,指定了模板名称 - // 模板位置为 'modid:test_template2' + // Class name is not prepended, template name is specified + // Template Location at 'modid:test_template2' @PrefixGameTestTemplate(false) @GameTest(template = "test_template2") public static void exampleTest4(GameTestHelper helper) { /*...*/ } } ``` -运行游戏测试 +Running Game Tests ------------------ -可以使用 `/test` 命令运行游戏测试。`test` 命令是高度可配置的;然而,只有几个对于运行测试至关重要: +Game Tests can be run using the `/test` command. The `test` command is highly configurable; however, only a few are of importance to running tests: -子命令 | 描述 +Subcommand | Description :---: | :--- -`run` | 运行指定的测试:`run `。 -`runall` | 运行所有可用的测试。 -`runthis` | 在玩家附近 15 格内运行最近的测试。 -`runthese` | 运行玩家 200 格内的测试。 -`runfailed` | 运行上次运行失败的所有测试。 +`run` | Runs the specified test: `run `. +`runall` | Runs all available tests. +`runthis` | Runs the nearest test to the player within 15 blocks. +`runthese` | Runs tests within 200 blocks of the player. +`runfailed` | Runs all tests that failed in the previous run. :::note -子命令遵循测试命令:`/test `。 +Subcommands follow the test command: `/test `. ::: -构建脚本配置 ------------------- +Buildscript Configurations +-------------------------- -游戏测试提供了在构建脚本(`build.gradle` 文件)中运行和集成到不同设置中的额外配置设置。 +Game Tests provide additional configuration settings within a buildscript (the `build.gradle` file) to run and integrate into different settings. -### 启用其他命名空间 +### Enabling Other Namespaces -如果构建脚本已按照[推荐方式设置][buildscript],那么只有当前 mod id 下的游戏测试将被启用。要启用其他命名空间加载游戏测试,运行配置必须将属性 `forge.enabledGameTestNamespaces` 设置为一个字符串,其中指定每个命名空间,用逗号分隔。如果属性为空或未设置,则将加载所有命名空间。 +If the buildscript was [setup as recommended][buildscript], then only Game Tests under the current mod id would be enabled. To enable other namespaces to load Game Tests from, a run configuration must set the property `forge.enabledGameTestNamespaces` to a string specifying each namespace separated by a comma. If the property is empty or not set, then all namespaces will be loaded. ```gradle -// 在运行配置中 +// Inside a run configuration property 'forge.enabledGameTestNamespaces', 'modid1,modid2,modid3' ``` :::caution -命名空间之间不能有空格;否则,命名空间将无法正确加载。 +There must be no spaces in-between namespaces; otherwise, the namespace will not be loaded correctly. ::: -### 游戏测试服务器运行配置 +### Game Test Server Run Configuration -游戏测试服务器是一个特殊配置,运行一个构建服务器。构建服务器返回需要的失败游戏测试数的退出代码。所有失败的测试,无论是必需的还是可选的,都将被记录。可以使用 `gradlew runGameTestServer` 来运行此服务器。 +The Game Test Server is a special configuration which runs a build server. The build server returns an exit code of the number of required, failed Game Tests. All failed tests, whether required or optional, are logged. This server can be run using `gradlew runGameTestServer`.
- FG5 的重要信息 + Important infromation on FG5 :::caution -由于 Gradle 的工作方式的一个怪异之处,如果一个任务强制系统退出,默认情况下 Gradle 守护进程将被终止,导致 Gradle 运行器报告构建失败。ForgeGradle 默认设置了对运行任务的强制退出,以便任何子项目都不会按顺序执行。然而,这样一来,游戏测试服务器将总是失败。 +Due to a quirk in how Gradle works, by default, if a task forces a system exit, the Gradle daemon will be killed, causing the Gradle runner to report a build failure. ForgeGradle sets by default a force exit on run tasks such that any subprojects are not executed in sequence. However, as such, the Game Test Server will always fail. -可以通过在运行配置中禁用强制退出来解决此问题,使用 `#setForceExit` 方法: +This can be fixed by disabling the force exit on the run configuration using the `#setForceExit` method: ```gradle -// 游戏测试服务器运行配置 +// Game Test Server run configuration gameTestServer { // ... setForceExit false @@ -273,15 +272,14 @@ gameTestServer { :::
-### 在其他运行配置中启用游戏测试 -默认情况下,只有 `client`、`server` 和 `gameTestServer` 运行配置中启用了游戏测试。如果另一个运行配置应该运行游戏测试,那么必须将 `forge.enableGameTest` 属性设置为 `true`。 +### Enabling Game Tests in Other Run Configurations -```gradle -// 在运行配置中 -property +By default, only the `client`, `server`, and `gameTestServer` run configurations have Game Tests enabled. If another run configuration should run Game Tests, then the `forge.enableGameTest` property must be set to `true`. - 'forge.enableGameTest', 'true' +```gradle +// Inside a run configuration +property 'forge.enableGameTest', 'true' ``` [test]: #running-game-tests diff --git a/docs/misc/keymappings.md b/docs/misc/keymappings.md index 20d66d57f..ecbf21526 100644 --- a/docs/misc/keymappings.md +++ b/docs/misc/keymappings.md @@ -1,129 +1,127 @@ # Key Mappings -一个按键映射或键绑定定义了应与输入相关联的特定操作:鼠标单击、按键等。每当客户端可以接受输入时,都可以检查键映射定义的每个操作。 此外,每个按键映射都可以通过[控制选项菜单][控制]分配给任何输入。 +A key mapping, or key binding, defines a particular action that should be tied to an input: mouse click, key press, etc. Each action defined by a key mapping can be checked whenever the client can take an input. Furthermore, each key mapping can be assigned to any input through the [Controls option menu][controls]. -## 注册一个`KeyMapping` +## Registering a `KeyMapping` -可以通过仅在物理客户端上监听 [**mod 事件总线**][modbus] 上的 `RegisterKeyMappingsEvent` 并调用 `#register` 来注册 `KeyMapping`。 +A `KeyMapping` can be registered by listening to the `RegisterKeyMappingsEvent` on the [**mod event bus**][modbus] only on the physical client and calling `#register`. ```java -// 在某个仅限物理客户端的类中 +// In some physical client only class -// KeyMapping 是惰性初始化的,因此在注册之前它不存在 +// Key mapping is lazily initialized so it doesn't exist until it is registered public static final Lazy EXAMPLE_MAPPING = Lazy.of(() -> /*...*/); -// 事件仅在物理客户端上的 mod 事件总线上 +// Event is on the mod event bus only on the physical client @SubscribeEvent public void registerBindings(RegisterKeyMappingsEvent event) { event.register(EXAMPLE_MAPPING.get()); } ``` -## 创建 `KeyMapping` +## Creating a `KeyMapping` -可以使用其构造函数创建 `KeyMapping`。`KeyMapping` 接受一个[翻译键][tk],用于定义映射的名称,映射的默认输入以及在[控制选项菜单][controls]中将映射放置在其中的类别的[翻译键][tk]。 +A `KeyMapping` can be created using it's constructor. The `KeyMapping` takes in a [translation key][tk] defining the name of the mapping, the default input of the mapping, and the [translation key][tk] defining the category the mapping will be put within in the [Controls option menu][controls]. :::tip -可以通过提供未由原版提供的自定义类别 [翻译键][tk] 来将 `KeyMapping` 添加到自定义类别中。自定义类别翻译键应包含 mod id(例如 `key.categories.examplemod.examplecategory`)。 +A `KeyMapping` can be added to a custom category by providing a category [translation key][tk] not provided by vanilla. Custom category translation keys should contain the mod id (e.g. `key.categories.examplemod.examplecategory`). ::: -### 默认输入 +### Default Inputs -每个键映射都与一个默认输入关联。这是通过 `InputConstants$Key` 提供的。每个输入由一个 `InputConstants$Type` 组成,用于定义提供输入的设备,以及一个整数,用于定义设备上关联的标识符。 +Each key mapping has a default input associated with it. This is provided through `InputConstants$Key`. Each input consists of an `InputConstants$Type`, which defines what device is providing the input, and an integer, which defines the associated identifier of the input on the device. -原版提供了三种输入类型:`KEYSYM`,它使用提供的 `GLFW` 键令定义键盘,`SCANCODE`,它使用平台特定的扫描码定义键盘,以及 `MOUSE`,它定义了鼠标。 +Vanilla provides three types of inputs: `KEYSYM`, which defines a keyboard through the provided `GLFW` key tokens, `SCANCODE`, which defines a keyboard through the platform-specific scancode, and `MOUSE`, which defines a mouse. :::note -强烈建议使用 `KEYSYM` 而不是 `SCANCODE` 用于键盘,因为 `GLFW` 键令与任何特定的系统无关。您可以在 [GLFW 文档][keyinput] 上阅读更多信息。 +It is highly recommended to use `KEYSYM` over `SCANCODE` for keyboards as `GLFW` key tokens are not tied to any particular system. You can read more on the [GLFW docs][keyinput]. ::: -整数取决于所提供的类型。所有输入代码都在 `GLFW` 中定义:`KEYSYM` 令牌以 `GLFW_KEY_*` 为前缀,而 `MOUSE` 代码以 `GLFW_MOUSE_*` 为前缀。 +The integer is dependent on the type provided. All input codes are defined in `GLFW`: `KEYSYM` tokens are prefixed with `GLFW_KEY_*` while `MOUSE` codes are prefixed with `GLFW_MOUSE_*`. ```java new KeyMapping( - "key.examplemod.example1", // 将使用此翻译键进行本地化 - InputConstants.Type.KEYSYM, // 默认映射在键盘上 - GLFW.GLFW_KEY_P, // 默认键为 P - "key.categories.misc" // 映射将位于杂项类别中 + "key.examplemod.example1", // Will be localized using this translation key + InputConstants.Type.KEYSYM, // Default mapping is on the keyboard + GLFW.GLFW_KEY_P, // Default key is P + "key.categories.misc" // Mapping will be in the misc category ) ``` :::note -如果键映射不应映射到默认键,则应将输入设置为 `InputConstants#UNKNOWN`。原版构造函数将要求您通过 `InputConstants$Key#getValue` 提取输入代码,而 Forge 构造函数可以提供原始输入字段。 +If the key mapping should not be mapped to a default, the input should be set to `InputConstants#UNKNOWN`. The vanilla constructor will require you to extract the input code via `InputConstants$Key#getValue` while the Forge constructor can be supplied the raw input field. ::: ### `IKeyConflictContext` -并非所有映射都在每个上下文中使用。某些映射仅在 GUI 中使用,而其他映射则仅在游戏中使用。为了避免在不同上下文中使用相同键的映射相互冲突,可以分配一个 `IKeyConflictContext`。 +Not all mappings are used in every context. Some mappings are only used in a GUI, while others are only used purely in game. To avoid mappings of the same key used in different contexts conflicting with each other, an `IKeyConflictContext` can be assigned. -每个冲突上下文都包含两种方法:`#isActive`,定义映射是否可以在当前游戏状态下使用,以及 `#conflicts`,定义映射是否与同一冲突上下文中的键或不同冲突上下文中的键冲突。 +Each conflict context contains two methods: `#isActive`, which defines if the mapping can be used in the current game state, and `#conflicts`, which defines whether the mapping conflicts with a key in the same or different conflict context. -目前,Forge 通过 `KeyConflictContext` 定义了三个基本上下文:`UNIVERSAL`,默认为意味着键可以在每个上下文中使用,`GUI`,意味着映射只能在打开 `Screen` 时使用,以及 `IN_GAME`,意味着映射只能在未打开 `Screen` 时使用。可以通过实现 `IKeyConflictContext` 来创建新的冲突上下文。 +Currently, Forge defines three basic contexts through `KeyConflictContext`: `UNIVERSAL`, which is the default meaning the key can be used in every context, `GUI`, which means the mapping can only be used when a `Screen` is open, and `IN_GAME`, which means the mapping can only be used if a `Screen` is not open. New conflict contexts can be created by implementing `IKeyConflictContext`. ```java new KeyMapping( "key.examplemod.example2", - KeyConflictContext.GUI, // 只能在打开屏幕时使用映射 - InputConstants.Type.MOUSE, // 默认映射在鼠标上 - GLFW.GLFW_MOUSE_BUTTON_LEFT, // 默认鼠标输入为左键 - "key.categories.examplemod.examplecategory" // 映射将位于新示例类别中 + KeyConflictContext.GUI, // Mapping can only be used when a screen is open + InputConstants.Type.MOUSE, // Default mapping is on the mouse + GLFW.GLFW_MOUSE_BUTTON_LEFT, // Default mouse input is the left mouse button + "key.categories.examplemod.examplecategory" // Mapping will be in the new example category ) ``` ### `KeyModifier` -模组可能不希望映射在按下修饰键时具有相同的行为(例如 `G` vs `CTRL + G`)。为解决这个问题,Forge 在构造函数中添加了一个额外的参数,以接受一个 `KeyModifier`,该修饰符可以应用控制(`KeyModifier#CONTROL`)、shift(`KeyModifier#SHIFT`)或 alt(`KeyModifier#ALT`)到任何输入。`KeyModifier#NONE` 是默认值,不会应用任何修饰符。 +Modders may not want mappings to have the same behavior if a modifier key is held at the same (e.g. `G` vs `CTRL + G`). To remedy this, Forge adds an additional parameter to the constructor to take in a `KeyModifier` which can apply control (`KeyModifier#CONTROL`), shift (`KeyModifier#SHIFT`), or alt (`KeyModifier#ALT`) to any input. `KeyModifier#NONE` is the default and will apply no modifier. -可以通过按住修饰键和相关输入来将修饰符添加到[控制选项菜单][controls]中。 +A modifier can be added in the [controls option menu][controls] by holding down the modifier key and the associated input. ```java new KeyMapping( "key.examplemod.example3", KeyConflictContext.UNIVERSAL, - KeyModifier.SHIFT, // 默认映射需要按住 shift - InputConstants.Type.KEYSYM, // 默认映射在键盘上 - GLFW.GLFW_KEY_G, // 默认键为 G + KeyModifier.SHIFT, // Default mapping requires shift to be held down + InputConstants.Type.KEYSYM, // Default mapping is on the keyboard + GLFW.GLFW_KEY_G, // Default key is G "key.categories.misc" ) ``` -## 检查 `KeyMapping` +## Checking a `KeyMapping` -可以检查 `KeyMapping` 来查看是否已单击它。根据何时,映射可以在条件中用于应用相关逻辑。 +A `KeyMapping` can be checked to see whether it has been clicked. Depending on when, the mapping can be used in a conditional to apply the associated logic. -### 在游戏中 +### Within the Game -在游戏中,应通过监听[**Forge 事件总线**][forgebus]上的 `ClientTickEvent` 并在 while 循环中检查 `KeyMapping#consumeClick` 来检查映射是否已被单击。`#consumeClick` 仅在执行输入的次数且尚未处理之前返回 `true`,因此不会 - -无限制地阻止游戏。 +Within the game, a mapping should be checked by listening to `ClientTickEvent` on the [**Forge event bus**][forgebus] and checking `KeyMapping#consumeClick` within a while loop. `#consumeClick` will return `true` only the number of times the input was performed and not already previously handled, so it won't infinitely stall the game. ```java -// 事件仅在 Forge 事件总线上的物理客户端上 +// Event is on the Forge event bus only on the physical client public void onClientTick(ClientTickEvent event) { - if (event.phase == TickEvent.Phase.END) { // 由于每个刻度事件都调用两次,因此只调用一次代码 + if (event.phase == TickEvent.Phase.END) { // Only call code once as the tick event is called twice every tick while (EXAMPLE_MAPPING.get().consumeClick()) { - // 在此处执行单击时要执行的逻辑 + // Execute logic to perform on click here } } } ``` :::caution -不要将 `InputEvent` 用作 `ClientTickEvent` 的替代方案。键盘和鼠标输入各有单独的事件,因此它们不会处理任何额外的输入。 +Do not use the `InputEvent`s as an alternative to `ClientTickEvent`. There are separate events for keyboard and mouse inputs only, so they wouldn't handle any additional inputs. ::: -### 在 GUI 内 +### Inside a GUI -在 GUI 中,可以在 `GuiEventListener` 方法之一中使用 `IForgeKeyMapping#isActiveAndMatches` 检查映射。可以检查的最常见方法是 `#keyPressed` 和 `#mouseClicked`。 +Within a GUI, a mapping can be checked within one of the `GuiEventListener` methods using `IForgeKeyMapping#isActiveAndMatches`. The most common methods which can be checked are `#keyPressed` and `#mouseClicked`. -`#keyPressed` 接受 `GLFW` 键令、平台特定的扫描码以及按下的修饰键的位字段。可以通过使用 `InputConstants#getKey` 创建输入来将键与映射进行检查。修饰符已经在映射方法本身中进行了检查。 +`#keyPressed` takes in the `GLFW` key token, the platform-specific scan code, and a bitfield of the held down modifiers. A key can be checked against a mapping by creating the input using `InputConstants#getKey`. The modifiers are already checked within the mapping methods itself. ```java -// 在某个 Screen 子类中 +// In some Screen subclass @Override public boolean keyPressed(int key, int scancode, int mods) { if (EXAMPLE_MAPPING.get().isActiveAndMatches(InputConstants.getKey(key, scancode))) { - // 在此处执行按键按下时要执行的逻辑 + // Execute logic to perform on key press here return true; } return super.keyPressed(x, y, button); @@ -131,17 +129,17 @@ public boolean keyPressed(int key, int scancode, int mods) { ``` :::note -如果您不拥有要检查 **键** 的屏幕,则可以监听[**Forge 事件总线**][forgebus]上的 `ScreenEvent$KeyPressed` 的 `Pre` 或 `Post` 事件。 +If you do not own the screen which you are trying to check a **key** for, you can listen to the `Pre` or `Post` events of `ScreenEvent$KeyPressed` on the [**Forge event bus**][forgebus] instead. ::: -`#mouseClicked` 接受鼠标的 x 位置、y 位置和单击的按钮。鼠标按钮可以使用 `InputConstants$Type#getOrCreate` 与 `MOUSE` 输入创建输入进行与映射的检查。 +`#mouseClicked` takes in the mouse's x position, y position, and the button clicked. A mouse button can be checked against a mapping by creating the input using `InputConstants$Type#getOrCreate` with the `MOUSE` input. ```java -// 在某个 Screen 子类中 +// In some Screen subclass @Override public boolean mouseClicked(double x, double y, int button) { if (EXAMPLE_MAPPING.get().isActiveAndMatches(InputConstants.TYPE.MOUSE.getOrCreate(button))) { - // 在此处执行鼠标单击时要执行的逻辑 + // Execute logic to perform on mouse click here return true; } return super.mouseClicked(x, y, button); @@ -149,11 +147,11 @@ public boolean mouseClicked(double x, double y, int button) { ``` :::note -如果您不拥有要检查 **鼠标** 的屏幕,则可以监听[**Forge 事件总线**][forgebus]上的 `ScreenEvent$MouseButtonPressed` 的 `Pre` 或 `Post` 事件。 +If you do not own the screen which you are trying to check a **mouse** for, you can listen to the `Pre` or `Post` events of `ScreenEvent$MouseButtonPressed` on the [**Forge event bus**][forgebus] instead. ::: [modbus]: ../concepts/events.md#event-buses -[controls]: https://minecraft.fandom.com/wiki/Controls +[controls]: https://minecraft.wiki/w/Options#Controls [tk]: ../resources/client/i18n.md#components [keyinput]: https://www.glfw.org/docs/3.3/input_guide.html#input_key [forgebus]: ../concepts/events.md#registering-an-event-handler diff --git a/docs/misc/resourcelocation.md b/docs/misc/resourcelocation.md index ee7682cd0..89cc8e4fb 100644 --- a/docs/misc/resourcelocation.md +++ b/docs/misc/resourcelocation.md @@ -1,48 +1,48 @@ -# 资源位置 +# Resource Locations -`ResourceLocation` 是 Minecraft 中最重要的内容之一。它们用作[注册表][registries]中的键,作为数据或资源文件的标识符,作为代码中模型的引用,以及许多其他地方。`ResourceLocation` 由两部分组成:命名空间和路径,由 `:` 分隔。 +`ResourceLocation`s are one of the most important things in Minecraft. They are used as keys in [registries][registries], as identifiers for data or resource files, as references to models in code, and in a lot of other places. A `ResourceLocation` consists of two parts: a namespace and a path, separated by a `:`. -命名空间表示资源位置所指的 mod、资源包或数据包。例如,具有模组 ID `examplemod` 的模组将使用 `examplemod` 命名空间。Minecraft 使用 `minecraft` 命名空间。可以根据需要定义额外的命名空间,只需创建相应的数据文件夹,这通常是由数据包执行的,以将其逻辑与 Vanilla 分开。 +The namespace denotes what mod, resource pack or datapack the location refers to. For example, a mod with the mod id `examplemod` will use the `examplemod` namespace. Minecraft uses the `minecraft` namespace. Extra namespaces can be defined at will simply by creating a corresponding data folder, this is usually done by datapacks to keep their logic separate from the point where they integrate with vanilla. -路径是指你的命名空间内的任何对象的引用。例如,`minecraft:cow` 是指 `minecraft` 命名空间中名为 `cow` 的对象 - 通常此位置将用于从实体注册表中获取 cow 实体。另一个示例是 `examplemod:example_item`,它可能用于从项注册表中获取模组的 `example_item`。 +The path is a reference to whatever object you want, inside your namespace. For example, `minecraft:cow` is a reference to something named `cow` in the `minecraft` namespace - usually this location would be used to get the cow entity from the entity registry. Another example would be `examplemod:example_item`, which would probably be used to get your mod's `example_item` from the item registry. -`ResourceLocation` 只能包含小写字母、数字、下划线、点和连字符。路径可能还包含斜杠。请注意,由于 Java 模块的限制,模组 ID 不得包含连字符,这意味着模组命名空间也不得包含连字符(路径仍然允许包含)。 +`ResourceLocation`s may only contain lowercase letters, digits, underscores, dots and hyphens. Paths may additionally contain forward slashes. Note that due to Java module restrictions, mod ids may not contain hyphens, which by extension means that mod namespaces may not contain hyphens either (they are still permitted in paths). :::info -`ResourceLocation` 本身并不表示我们要使用它的对象的类型。例如,名为 `minecraft:dirt` 的对象存在于多个位置。由接收 `ResourceLocation` 的对象决定将对象与其关联。 +A `ResourceLocation` on its own says nothing about what kind of objects we are using it for. Objects named `minecraft:dirt` exist in multiple places, for example. It is up to whatever receives the `ResourceLocation` to associate an object with it. ::: -可以通过调用 `new ResourceLocation("examplemod", "example_item")` 或 `new ResourceLocation("examplemod:example_item")` 来创建新的 `ResourceLocation` 实例。如果使用后者,并且字符串不包含 `:`,则整个字符串将用作路径,而 `minecraft` 将用作命名空间。因此,例如 `new ResourceLocation("example_item")` 将导致 `minecraft:example_item`。 +A new `ResourceLocation` can be created by calling `new ResourceLocation("examplemod", "example_item")` or `new ResourceLocation("examplemod:example_item")`. If the latter is used with a string that does not contain a `:`, the entire string will be used as the path, and `minecraft` will be used as the namespace. So for example, `new ResourceLocation("example_item")` will result in `minecraft:example_item`. -可以使用 `ResourceLocation#getNamespace()` 和 `#getPath()` 分别检索 `ResourceLocation` 的命名空间和路径,并通过 `ResourceLocation#toString()` 检索组合形式。 +The namespace and path of a `ResourceLocation` can be retrieved using `ResourceLocation#getNamespace()` and `#getPath()`, respectively, and the combined form can be retrieved through `ResourceLocation#toString`. -`ResourceLocation` 是不可变的。`ResourceLocation` 上的所有实用方法,例如 `withPrefix` 或 `withSuffix`,都返回一个新的 `ResourceLocation`。 +`ResourceLocation`s are immutable. All utility methods on `ResourceLocation`, such as `withPrefix` or `withSuffix`, return a new `ResourceLocation`. -## 解析 `ResourceLocation` +## Resolving `ResourceLocation`s -某些位置,例如注册表,直接使用 `ResourceLocation`。然而,其他一些位置将根据需要解析 `ResourceLocation`。例如: +Some places, for example registries, use `ResourceLocation`s directly. Some other places, however, will resolve the `ResourceLocation` as needed. For example: -- `ResourceLocation` 用作 GUI 背景的标识符。例如,熔炉 GUI 使用资源位置 `minecraft:textures/gui/container/furnace.png`。这映射到磁盘上的文件 `assets/minecraft/textures/gui/container/furnace.png`。请注意,在此资源位置中需要 `.png` 后缀。 -- `ResourceLocation` 用作方块模型的标识符。例如,泥土的方块模型使用资源位置 `minecraft:block/dirt`。这映射到磁盘上的文件 `assets/minecraft/models/block/dirt.json`。请注意,在此资源位置中不需要 `.json` 后缀。还请注意,此资源位置自动映射到 `models` 子文件夹。 -- `ResourceLocation` 用作配方的标识符。例如,铁块的合成配方使用资源位置 `minecraft:iron_block`。这映射到磁盘上的文件 `data/minecraft/recipes/iron_block.json`。请注意,在此资源位置中不需要 `.json` 后缀。还请注意,此资源位置自动映射到 `recipes` 子文件夹。 +- `ResourceLocation`s are used as identifiers for GUI background. For example, the furnace GUI uses the resource location `minecraft:textures/gui/container/furnace.png`. This maps to the file `assets/minecraft/textures/gui/container/furnace.png` on disk. Note that the `.png` suffix is required in this resource location. +- `ResourceLocation`s are used as identifiers for block models. For example, the block model of dirt uses the resource location `minecraft:block/dirt`. This maps to the file `assets/minecraft/models/block/dirt.json` on disk. Note that the `.json` suffix is not required here. Note as well that this resource location automatically maps into the `models` subfolder. +- `ResourceLocation`s are used as identifiers for recipes. For example, the iron block crafting recipe uses the resource location `minecraft:iron_block`. This maps to the file `data/minecraft/recipes/iron_block.json` on disk. Note that the `.json` suffix is not required here. Note as well that this resource location automatically maps into the `recipes` subfolder. -`ResourceLocation` 是否需要文件后缀,以及资源位置解析为什么内容,取决于使用情况。 +Whether the `ResourceLocation` expects a file suffix, or what exactly the resource location resolves to, depends on the use case. -## `ModelResourceLocation` +## `ModelResourceLocation`s -`ModelResourceLocation` 是一种特殊类型的资源位置,包含第三部分,称为变体。Minecraft 主要用于区分模型的不同变体,在不同的显示上下文中使用不同的变体(例如三叉戟,在第一人称、第三人称和库存中有不同的模型)。对于项,变体始终为 `inventory`,对于块状态,变体是由属性-值对的逗号分隔字符串组成的(例如 `facing=north,waterlogged=false`),对于没有块状态属性的块为空。 +`ModelResourceLocation`s are a special kind of resource location that includes a third part, called the variant. Minecraft uses these mainly to differentiate between different variants of models, where the different variants are used in different display contexts (for example with tridents, which have different models in first person, third person and inventories). The variant is always `inventory` for items, and the comma-delimited string of property-value pairs for blockstates (for example `facing=north,waterlogged=false`, empty for blocks with no blockstate properties). -变体附加到常规资源位置,以及 `#`。例如,钻石剑的项模型的完整名称是 `minecraft:diamond_sword#inventory`。然而,在大多数情况下,`inventory` 变体可以省略。 +The variant is appended to the regular resource location, along with a `#`. For example, the full name of the diamond sword's item model is `minecraft:diamond_sword#inventory`. However, in most contexts, the `inventory` variant can be omitted. -`ModelResourceLocation` 是一个[仅客户端][sides]的类。这意味着引用该类的服务器将因为 `NoClassDefFoundError` 而崩溃。 +`ModelResourceLocation` is a [client only][sides] class. This means that servers referencing this class will crash with a `NoClassDefFoundError`. -## `ResourceKey` +## `ResourceKey`s -`ResourceKey` 将注册表 ID 与注册表名称结合在一起。一个示例是具有注册表 ID `minecraft:item` 和注册表名称 `minecraft:diamond_sword` 的注册表键。与 `ResourceLocation` 不同,`ResourceKey` 实际上指代一个唯一的元素,因此能够清楚地识别一个元素。它们通常用于许多不同的注册表相互接触的情况。一个常见的用例是数据包,特别是世界生成。 +`ResourceKey`s combine a registry id with a registry name. An example would be a registry key with the registry id `minecraft:item` and the registry name `minecraft:diamond_sword`. Unlike a `ResourceLocation`, `ResourceKey`s actually refer to a unique element, thus being able to clearly identify an element. They are most commonly used in contexts where many different registries come in contact with one another. A common use case are datapacks, especially worldgen. -可以通过静态方法 `ResourceKey#create(ResourceKey>, ResourceLocation)` 创建新的 `ResourceKey`。这里的第二个参数是注册表名称,而第一个参数是所谓的注册表键。注册表键是一种特殊的 `ResourceKey`,其注册表是根注册表(即所有其他注册表的注册表)。可以通过 `ResourceKey#createRegistryKey(ResourceLocation)` 创建所需注册表的注册表键。 +A new `ResourceKey` can be created through the static method `ResourceKey#create(ResourceKey>, ResourceLocation)`. The second parameter here is the registry name, while the first parameter is what is known as a registry key. Registry keys are a special kind of `ResourceKey` whose registry is the root registry (i.e. the registry of all other registries). A registry key can be created via `ResourceKey#createRegistryKey(ResourceLocation)` with the desired registry's id. -`ResourceKey` 在创建时进行了内部化。这意味着可以并且鼓励通过引用相等性(`==`)进行比较,但它们的创建相对较昂贵。 +`ResourceKey`s are interned at creation. This means that comparing by reference equality (`==`) is possible and encouraged, but their creation is comparatively expensive. [registries]: ../concepts/registries.md [sides]: ../concepts/sides.md diff --git a/docs/misc/updatechecker.md b/docs/misc/updatechecker.md index 3f6194d43..85d7960ce 100644 --- a/docs/misc/updatechecker.md +++ b/docs/misc/updatechecker.md @@ -1,61 +1,61 @@ -Forge更新检查器 +Forge Update Checker ==================== -Forge提供了一个非常轻量级的、可选择的更新检查框架。如果任何mod有可用的更新,它将在主菜单的“Mods”按钮和mod列表上显示一个闪烁的图标,以及相应的更新日志。它*不会*自动下载更新。 +Forge provides a very lightweight, opt-in, update-checking framework. If any mods have an available update, it will show a flashing icon on the 'Mods' button of the main menu and mod list along with the respective changelogs. It *does not* download updates automatically. -入门指南 +Getting Started --------------- -首先,您需要在您的 `mods.toml` 文件中指定 `updateJSONURL` 参数。该参数的值应该是一个指向更新JSON文件的有效URL。此文件可以托管在您自己的Web服务器、GitHub或任何您想要的地方,只要所有使用您的mod的用户都可以可靠地访问它。 +The first thing you want to do is specify the `updateJSONURL` parameter in your `mods.toml` file. The value of this parameter should be a valid URL pointing to an update JSON file. This file can be hosted on your own web server, GitHub, or wherever you want as long as it can be reliably reached by all users of your mod. -更新JSON格式 +Update JSON format ------------------ -JSON本身具有相对简单的格式,如下所示: +The JSON itself has a relatively simple format as follows: ```js { - "homepage": "<您的mod的主页/下载页面>", + "homepage": "", "": { - "": "<此版本的更新日志>", - // 列出给定Minecraft版本的所有版本的您的mod,以及它们的更新日志 + "": "", + // List all versions of your mod for the given Minecraft version, along with their changelogs // ... }, "promos": { "-latest": "", - // 声明给定Minecraft版本的最新的“最新”版本的您的mod + // Declare the latest "bleeding-edge" version of your mod for the given Minecraft version "-recommended": "", - // 声明给定Minecraft版本的最新的“稳定”版本的您的mod + // Declare the latest "stable" version of your mod for the given Minecraft version // ... } } ``` -这相当容易理解,但一些注意事项: +This is fairly self-explanatory, but some notes: + +* The link under `homepage` is the link the user will be shown when the mod is outdated. +* Forge uses an internal algorithm to determine whether one version string of your mod is "newer" than another. Most versioning schemes should be compatible, but see the `ComparableVersion` class if you are concerned about whether your scheme is supported. Adherence to [Maven versioning][mvnver] is highly recommended. +* The changelog string can be separated into lines using `\n`. Some prefer to include a abbreviated changelog, then link to an external site that provides a full listing of changes. +* Manually inputting data can be chore. You can configure your `build.gradle` to automatically update this file when building a release as Groovy has native JSON parsing support. Doing this is left as an exercise to the reader. -* `homepage` 下的链接是当mod过期时将向用户显示的链接。 -* Forge使用内部算法来确定您的mod的一个版本字符串是否比另一个版本字符串“更新”。大多数版本方案都应该是兼容的,但如果您担心您的方案是否受支持,请参阅 `ComparableVersion` 类。强烈建议遵循[Maven版本规范][mvnver]。 -* 更新日志字符串可以使用 `\n` 分隔成行。一些人喜欢包含一个简短的更新日志,然后链接到一个提供完整更改列表的外部网站。 -* 手动输入数据可能会很烦琐。您可以将您的 `build.gradle` 配置为在构建发布时自动更新此文件,因为 Groovy 具有原生的JSON解析支持。将此操作留给读者作为练习。 +- Some examples can be found here for [nocubes][], [Corail Tombstone][corail] and [Chisels & Bits 2][chisel]. -- 这里有一些示例,[nocubes][], [Corail Tombstone][corail] 和 [Chisels & Bits 2][chisel]。 - -检索更新检查结果 +Retrieving Update Check Results ------------------------------- -您可以使用 `VersionChecker#getResult(IModInfo)` 检索 Forge 更新检查器的结果。您可以通过 `ModContainer#getModInfo` 获取您的 `IModInfo`。您可以在构造函数中使用 `ModLoadingContext.get().getActiveContainer()`,`ModList.get().getModContainerById()` 或 `ModList.get().getModContainerByObject()` 获取您的 `ModContainer`。您可以使用 `ModList.get().getModContainerById()` 获取任何其他 mod 的 `ModContainer`。返回的对象具有一个 `#status` 方法,该方法指示版本检查的状态。 +You can retrieve the results of the Forge Update Checker using `VersionChecker#getResult(IModInfo)`. You can obtain your `IModInfo` via `ModContainer#getModInfo`. You can get your `ModContainer` using `ModLoadingContext.get().getActiveContainer()` inside your constructor, `ModList.get().getModContainerById()`, or `ModList.get().getModContainerByObject()`. You can obtain any other mod's `ModContainer` using `ModList.get().getModContainerById()`. The returned object has a method `#status` which indicates the status of the version check. -| 状态 | 描述 | +| Status | Description | |----------------:|:------------| -| `FAILED` | 版本检查器无法连接到提供的URL。 | -| `UP_TO_DATE` | 当前版本等于推荐版本。 | -| `AHEAD` | 如果没有最新版本,当前版本比推荐版本新。 | -| `OUTDATED` | 有新的推荐或最新版本。 | -| `BETA_OUTDATED` | 有新的最新版本。 | -| `BETA` | 当前版本等于或比最新版本更新。 | -| `PENDING` | 请求的结果尚未完成,因此您应该稍后重试。 | - -返回的对象还将具有目标版本和任何在 `update.json` 中指定的更新日志行。 +| `FAILED` | The version checker could not connect to the URL provided. | +| `UP_TO_DATE` | The current version is equal to the recommended version. | +| `AHEAD` | The current version is newer than the recommended version if there is not latest version. | +| `OUTDATED` | There is a new recommended or latest version. | +| `BETA_OUTDATED` | There is a new latest version. | +| `BETA` | The current version is equal to or newer than the latest version. | +| `PENDING` | The result requested has not finished yet, so you should try again in a little bit. | + +The returned object will also have the target version and any changelog lines as specified in `update.json`. [mvnver]: ../gettingstarted/versioning.md [nocubes]: https://cadiboo.github.io/projects/nocubes/update.json diff --git a/docs/networking/configuration-tasks.md b/docs/networking/configuration-tasks.md index e8f40c032..1e94b3c5d 100644 --- a/docs/networking/configuration-tasks.md +++ b/docs/networking/configuration-tasks.md @@ -1,27 +1,27 @@ -# 使用配置任务 +# Using Configuration Tasks -客户端和服务器的网络协议有一个特定的阶段,服务器可以在玩家实际加入游戏之前配置客户端。 -这个阶段称为配置阶段,例如,原版服务器用它来向客户端发送资源包信息。 +The networking protocol for the client and server has a specific phase where the server can configure the client before the player actually joins the game. +This phase is called the configuration phase, and is for example used by the vanilla server to send the resource pack information to the client. -这个阶段也可以被 mod 用来在玩家加入游戏之前配置客户端。 +This phase can also be used by mods to configure the client before the player joins the game. -## 注册配置任务 -使用配置阶段的第一步是注册一个配置任务。 -这可以通过在 `OnGameConfigurationEvent` 事件中注册新的配置任务来完成。 +## Registering a configuration task +The first step to using the configuration phase is to register a configuration task. +This can be done by registering a new configuration task in the `OnGameConfigurationEvent` event. ```java @SubscribeEvent public static void register(final OnGameConfigurationEvent event) { event.register(new MyConfigurationTask()); } ``` -`OnGameConfigurationEvent` 事件在 mod 总线上触发,并暴露了服务器用来配置相关客户端的当前监听器。 -Modder 可以使用暴露的监听器来判断客户端是否运行了 mod,并在是这样的情况下注册一个配置任务。 +The `OnGameConfigurationEvent` event is fired on the mod bus, and exposes the current listener used by the server to configure the relevant client. +A modder can use the exposed listener to figure out if the client is running the mod, and if so, register a configuration task. -## 实现配置任务 -配置任务是一个简单的接口:`ICustomConfigurationTask`。 -这个接口有两个方法:`void run(Consumer sender);`,和 `ConfigurationTask.Type type();` 返回配置任务的类型。 -类型用于标识配置任务。 -下面是一个配置任务的示例: +## Implementing a configuration task +A configuration task is a simple interface: `ICustomConfigurationTask`. +This interface has two methods: `void run(Consumer sender);`, and `ConfigurationTask.Type type();` which returns the type of the configuration task. +The type is used to identify the configuration task. +An example of a configuration task is shown below: ```java public record MyConfigurationTask implements ICustomConfigurationTask { public static final ConfigurationTask.Type TYPE = new ConfigurationTask.Type(new ResourceLocation("mymod:my_task")); @@ -39,14 +39,14 @@ public record MyConfigurationTask implements ICustomConfigurationTask { } ``` -## 确认配置任务 -您的配置在服务器上执行,服务器需要知道何时可以执行下一个配置任务。 -这可以通过确认所述配置任务的执行来完成。 +## Acknowledging a configuration task +Your configuration is executed on the server, and the server needs to know when the next configuration task can be executed. +This is done by acknowledging the execution of said configuration task. -有两种主要方式可以实现这一点: +There are two primary ways of achieving this: -### 捕获监听器 -当客户端不需要确认配置任务时,可以捕获监听器,并可以直接在服务器端确认配置任务。 +### Capturing the listener +When the client does not need to acknowledge the configuration task, then the listener can be captured, and the configuration task can be acknowledged directly on the server side. ```java public record MyConfigurationTask(ServerConfigurationListener listener) implements ICustomConfigurationTask { public static final ConfigurationTask.Type TYPE = new ConfigurationTask.Type(new ResourceLocation("mymod:my_task")); @@ -64,25 +64,25 @@ public record MyConfigurationTask(ServerConfigurationListener listener) implemen } } ``` -要使用这样的配置任务,需要在 `OnGameConfigurationEvent` 事件中捕获监听器。 +To use such a configuration task, the listener needs to be captured in the `OnGameConfigurationEvent` event. ```java @SubscribeEvent public static void register(final OnGameConfigurationEvent event) { event.register(new MyConfigurationTask(event.listener())); } ``` -然后,在当前配置任务完成后,下一个配置任务将立即执行,客户端不需要确认配置任务。 -此外,服务器将不会等待客户端正确处理发送的载荷。 +Then the next configuration task will be executed immediately after the current configuration task has completed, and the client does not need to acknowledge the configuration task. +Additionally, the server will not wait for the client to properly process the send payloads. -### 确认配置任务 -当客户端需要确认配置任务时,您将需要向客户端发送自己的载荷: +### Acknowledging the configuration task +When the client needs to acknowledge the configuration task, then you will need to send your own payload to the client: ```java public record AckPayload() implements CustomPacketPayload { public static final ResourceLocation ID = new ResourceLocation("mymod:ack"); @Override public void write(final FriendlyByteBuf buffer) { - // 无需写入数据 + // No data to write } @Override @@ -91,14 +91,14 @@ public record AckPayload() implements CustomPacketPayload { } } ``` -当服务器端配置任务发送的有效载荷被正确处理时,您可以向服务器发送此载荷以确认配置任务。 +When a payload from a server side configuration task is properly processed you can send this payload to the server to acknowledge the configuration task. ```java public void onMyData(MyData data, ConfigurationPayloadContext context) { context.submitAsync(() -> { blah(data.name()); }) .exceptionally(e -> { - // 处理异常 + // Handle exception context.packetHandler().disconnect(Component.translatable("my_mod.configuration.failed", e.getMessage())); return null; }) @@ -107,16 +107,16 @@ public void onMyData(MyData data, ConfigurationPayloadContext context) { }); } ``` -其中 `onMyData` 是处理由服务器端配置任务发送的载荷的处理程序。 +Where `onMyData` is the handler for the payload that was sent by the server side configuration task. -当服务器接收到此载荷时,将确认配置任务,并将执行下一个配置任务: +When the server receives this payload it will acknowledge the configuration task, and the next configuration task will be executed: ```java public void onAck(AckPayload payload, ConfigurationPayloadContext context) { context.taskCompletedHandler().onTaskCompleted(MyConfigurationTask.TYPE); } ``` -其中 `onAck` 是处理由客户端发送的载荷的处理程序。 +Where `onAck` is the handler for the payload that was sent by the client. -## 阻塞登录过程 -当配置未被确认时,服务器将永远等待,客户端将永远无法加入游戏。 -因此,始终确认配置任务非常重要,除非配置任务失败,然后您可以断开客户端的连接。 +## Stalling the login process +When the configuration is not acknowledged, then the server will wait forever, and the client will never join the game. +So it is important to always acknowledge the configuration task, unless the configuration task failed, then you can disconnect the client. diff --git a/docs/networking/entities.md b/docs/networking/entities.md index 07af05890..a63e958d1 100644 --- a/docs/networking/entities.md +++ b/docs/networking/entities.md @@ -1,29 +1,29 @@ -# 实体 +# Entities -除了常规的网络消息外,还提供了各种其他系统来处理实体数据的同步。 +In addition to regular network messages, there are various other systems provided to handle synchronizing entity data. -## 生成数据 -自 1.20.2 版本以来,Mojang 引入了 Bundle 数据包的概念,用于将实体生成数据包一起发送。 -这允许更多的数据与生成数据包一起发送,并且使得数据的发送更有效率。 +## Spawn Data +Since 1.20.2 Mojang introduced the concept of Bundle packets, which are used to send entity spawn packets together. +This allows for more data to be sent with the spawn packet, and for that data to be sent more efficiently. -您可以通过实现以下接口向 Forge 发送的生成数据包添加额外数据。 +You can add extra data to the spawn packet Forge sends by implementing the following interface. ### IEntityWithComplexSpawn -如果您的实体具有在客户端上需要但随时间不变的数据,则可以使用此接口将其添加到实体生成数据包中。`#writeSpawnData` 和 `#readSpawnData` 控制如何将数据编码到/从网络缓冲区中解码。 -或者,您可以重写 `sendPairingData(...)` 方法,该方法在实体与客户端配对时调用。此方法在服务器上调用,可用于在生成数据包的同一捆绑包中向客户端发送附加负载。 +If your entity has data that is needed on the client, but does not change over time, then it can be added to the entity spawn packet using this interface. `#writeSpawnData` and `#readSpawnData` control how the data should be encoded to/decoded from the network buffer. +Alternatively you can override the method `sendPairingData(...)` which is called when the entity is paired with a client. This method is called on the server, and can be used to send additional payloads to the client within the same bundle as the spawn packet. -## 动态数据 -### 数据参数 +## Dynamic Data +### Data Parameters -这是将实体数据从服务器同步到客户端的主要原始系统。因此,有许多可用于参考的原始示例。 +This is the main vanilla system for synchronizing entity data from the server to the client. As such, a number of vanilla examples are available to refer to. -首先,您需要为要保持同步的数据获取一个 `EntityDataAccessor`。这应该作为您的实体类中的 `static final` 字段存储,通过调用 `SynchedEntityData#defineId` 并传递实体类和该类型数据的序列化程序来获得。可用的序列化程序实现可以在 `EntityDataSerializers` 类的静态常量中找到。 +Firstly, you need a `EntityDataAccessor` for the data you wish to keep synchronized. This should be stored as a `static final` field in your entity class, obtained by calling `SynchedEntityData#defineId` and passing the entity class and a serializer for that type of data. The available serializer implementations can be found as static constants within the `EntityDataSerializers` class. :::caution -您应该 __仅__ 为您自己的实体创建数据参数,在该实体类内部。 -为您无法控制的实体添加参数可能会导致用于通过网络发送该数据的 ID 不同步,导致难以调试的崩溃。 +You should __only__ create data parameters for your own entities, _within that entity's class_. +Adding parameters to entities you do not control can cause the IDs used to send that data over the network to become desynchronized, causing difficult to debug crashes. ::: -然后,重写 `Entity#defineSynchedData` 并为每个数据参数调用 `this.entityData.define(...)`,传递参数和要使用的初始值。记得始终先调用 `super` 方法! +Then, override `Entity#defineSynchedData` and call `this.entityData.define(...)` for each of your data parameters, passing the parameter and an initial value to use. Remember to always call the `super` method first! -然后,您可以通过实体的 `entityData` 实例获取和设置这些值。所做的更改将自动同步到客户端。 +You can then get and set these values via your entity's `entityData` instance. Changes made will be synchronized to the client automatically. diff --git a/docs/networking/index.md b/docs/networking/index.md index 6a0cb4b8c..d11abe4bd 100644 --- a/docs/networking/index.md +++ b/docs/networking/index.md @@ -1,18 +1,18 @@ -# 网络通信 +# Networking -服务器和客户端之间的通信是成功实现模组的基础。 +Communication between servers and clients is the backbone of a successful mod implementation. -网络通信有两个主要目标: +There are two primary goals in network communication: -1. 确保客户端视图与服务器视图“同步” - - 在坐标 (X, Y, Z) 处的花刚刚生长了 -2. 让客户端告诉服务器有关玩家状态变化的信息 - - 玩家按下了一个键 +1. Making sure the client view is "in sync" with the server view + - The flower at coordinates (X, Y, Z) just grew +2. Giving the client a way to tell the server that something has changed about the player + - the player pressed a key -实现这些目标最常见的方式是在客户端和服务器之间传递消息。这些消息通常会被结构化,按特定的排列方式包含数据,以便于发送和接收。 +The most common way to accomplish these goals is to pass messages between the client and the server. These messages will usually be structured, containing data in a particular arrangement, for easy sending and receiving. -NeoForge 提供了一种技术来促进通信,主要建立在 [netty][] 之上。 -通过监听 `RegisterPayloadHandlerEvent` 事件,可以注册特定类型的 [负载][payloads]、其读取器和处理函数到注册器中。 +There is a technique provided by NeoForge to facilitate communication mostly built on top of [netty][]. +This technique can be used by listening for the `RegisterPayloadHandlerEvent` event, and then registering a specific type of [payloads][], its reader, and its handler function to the registrar. [netty]: https://netty.io "Netty Website" [payloads]: ./payload.md "Registering custom Payloads" diff --git a/docs/networking/payload.md b/docs/networking/payload.md index b549ec2e4..c36ea99ec 100644 --- a/docs/networking/payload.md +++ b/docs/networking/payload.md @@ -1,6 +1,6 @@ -# 注册负载 +# Registering Payloads -负载是在客户端和服务器之间发送任意数据的一种方法。它们使用从 `RegisterPayloadHandlerEvent` 事件中获取的 `IPayloadRegistrar` 进行注册,该事件可以为给定的命名空间检索到。 +Payloads are a way to send arbitrary data between the client and the server. They are registered using the `IPayloadRegistrar` that can be retrieved for a given namespace from the `RegisterPayloadHandlerEvent` event. ```java @SubscribeEvent public static void register(final RegisterPayloadHandlerEvent event) { @@ -8,12 +8,12 @@ public static void register(final RegisterPayloadHandlerEvent event) { } ``` -假设我们想要发送以下数据: +Assuming we want to send the following data: ```java public record MyData(String name, int age) {} ``` -然后,我们可以实现 `CustomPacketPayload` 接口来创建一个可用于发送和接收此数据的负载。 +Then we can implement the `CustomPacketPayload` interface to create a payload that can be used to send and receive this data. ```java public record MyData(String name, int age) implements CustomPacketPayload { @@ -35,10 +35,10 @@ public record MyData(String name, int age) implements CustomPacketPayload { } } ``` -从上面的示例中可以看出,`CustomPacketPayload` 接口要求我们实现 `write` 和 `id` 方法。`write` 方法负责将数据写入缓冲区,而 `id` 方法负责返回此负载的唯一标识符。 -然后,我们还需要一个读取器来稍后进行注册,在这里我们可以使用自定义构造函数从缓冲区中读取数据。 +As you can see from the example above the `CustomPacketPayload` interface requires us to implement the `write` and `id` methods. The `write` method is responsible for writing the data to the buffer, and the `id` method is responsible for returning a unique identifier for this payload. +We then also need a reader to register this later on, here we can use a custom constructor to read the data from the buffer. -最后,我们可以使用注册器注册此负载: +Finally, we can register this payload with the registrar: ```java @SubscribeEvent public static void register(final RegisterPayloadHandlerEvent event) { @@ -48,17 +48,17 @@ public static void register(final RegisterPayloadHandlerEvent event) { .server(ServerPayloadHandler.getInstance()::handleData)); } ``` -分解上面的代码,我们可以注意到几件事情: -- 注册器有一个 `play` 方法,可用于注册在游戏播放阶段发送的负载。 - - 此代码中未显示的方法还有 `configuration` 和 `common`,但它们也可以用于为配置阶段注册负载。`common` 方法可用于同时为配置和游戏播放阶段注册负载。 -- `MyData` 的构造函数被用作方法引用,以创建负载的读取器。 -- 注册方法的第三个参数是一个回调,用于注册负载到达客户端或服务器端时的处理程序。 - - `client` 方法用于在负载到达客户端时注册处理程序。 - - `server` 方法用于在负载到达服务器端时注册处理程序。 - - 在注册器本身上还有一个次要的注册方法 `play`,它接受客户端和服务器端的处理程序,可以用于同时为两端注册处理程序。 +Dissecting the code above we can notice a couple of things: +- The registrar has a `play` method, that can be used for registering payloads which are send during the play phase of the game. + - Not visible in this code are the methods `configuration` and `common`, however they can also be used to register payloads for the configuration phase. The `common` method can be used to register payloads for both the configuration and play phase simultaneously. +- The constructor of `MyData` is used as a method reference to create a reader for the payload. +- The third argument for the registration method is a callback that can be used to register the handlers for when the payload arrives at either the client or server side. + - The `client` method is used to register a handler for when the payload arrives at the client side. + - The `server` method is used to register a handler for when the payload arrives at the server side. + - There is additionally a secondary registration method `play` on the registrar itself that accepts a handler for both the client and server side, this can be used to register a handler for both sides at once. -现在我们已经注册了负载,我们需要实现一个处理程序。 -在此示例中,我们将特别关注客户端端处理程序,但服务器端处理程序非常相似。 +Now that we have registered the payload we need to implement a handler. +For this example we will specifically take a look at the client side handler, however the server side handler is very similar. ```java public class ClientPayloadHandler { @@ -69,30 +69,30 @@ public class ClientPayloadHandler { } public void handleData(final MyData data, final PlayPayloadContext context) { - // 处理数据,在网络线程上 + // Do something with the data, on the network thread blah(data.name()); - // 在主游戏线程上处理数据 + // Do something with the data, on the main thread context.workHandler().submitAsync(() -> { blah(data.age()); }) .exceptionally(e -> { - // 处理异常 + // Handle exception context.packetHandler().disconnect(Component.translatable("my_mod.networking.failed", e.getMessage())); return null; }); } } ``` -这里需要注意几件事情: -- 此处处理方法获取负载和上下文对象。上下文对象对于播放和配置阶段是不同的,如果注册了一个通用负载,则需要接受两个上下文的超类型。 -- 负载方法的处理程序在网络线程上调用,因此重要的是在此处进行所有繁重的工作,而不是阻塞主游戏线程。 -- 如果要在主游戏线程上运行代码,可以使用上下文的 `workHandler` 提交任务到主线程。 - - `workHandler` 将返回一个在主线程上完成的 `CompletableFuture`,可以用于提交任务到主线程。 - - 注意:返回的是 `CompletableFuture`,这意味着您可以将多个任务链接在一起,并在单个位置处理异常。 - - 如果不在 `CompletableFuture` 中处理异常,则它将被忽略,**您将不会收到任何通知**。 +Here a couple of things are of note: +- The handling method here gets the payload, and a contextual object. The contextual object is different for the play and configuration phase, and if you register a common packet, then it will need to accept the super type of both contexts. +- The handler of the payload method is invoked on the networking thread, so it is important to do all the heavy work here, instead of blocking the main game thread. +- If you want to run code on the main game thread you can use the `workHandler` of the context to submit a task to the main thread. + - The `workHandler` will return a `CompletableFuture` that will be completed on the main thread, and can be used to submit tasks to the main thread. + - Notice: A `CompletableFuture` is returned, this means that you can chain multiple tasks together, and handle exceptions in a single place. + - If you do not handle the exception in the `CompletableFuture` then it will be swallowed, **and you will not be notified of it**. -现在您知道了如何为您的模组促进客户端和服务器之间的通信,您可以开始实现自己的负载。 -有了自己的负载,您就可以使用它们来配置客户端和服务器,使用[配置任务][]。 +Now that you know how you can facilitate the communication between the client and the server for your mod, you can start implementing your own payloads. +With your own payloads you can then use those to configure the client and server using [Configuration Tasks][] -[配置任务]: ./configuration-tasks.md +[Configuration Tasks]: ./configuration-tasks.md diff --git a/docs/resources/client/i18n.md b/docs/resources/client/i18n.md index 54bb13b3c..e5be489e4 100644 --- a/docs/resources/client/i18n.md +++ b/docs/resources/client/i18n.md @@ -1,35 +1,35 @@ -# I18n 与 L10n +# I18n and L10n -I18n(国际化的简称)是一种设计程序以适应多种语言的方法。L10n(本地化的简称)是将文本翻译成用户语言的过程。Minecraft使用`Component`来实现这些功能。 +I18n (short for internationalization) is the way of designing a program to work with multiple languages. L10n (short for localization) is the process of translating text into the user's language. Minecraft implements these using `Component`s. -## `Component`组件 +## `Component`s -`Component`是有元数据的文本片段,元数据包括如文本格式化等内容。它可以通过以下方式之一创建(以下都是`Component`接口中的静态方法): +A `Component` is a piece of text with metadata, with the metadata including things such as text formatting. It can be created in one of the following ways (all of the following are static methods in the `Component` interface): -| 方法 | 描述 | -|-----------------|--------------------------------------------------------------------------------------------------| -| `empty` | 创建一个空组件。 | -| `literal` | 创建一个具有给定文本的组件,并直接显示该文本,而不进行翻译。 | -| `nullToEmpty` | 对于给定的null创建一个空组件,否则创建一个文字组件。 | -| `translatable` | 创建一个可翻译的组件。给定的字符串随后会被解析为翻译键(见下文)。 | -| `keybind` | 创建一个包含给定按键绑定的(翻译后的)显示名称的组件。 | -| `nbt` | 创建一个表示给定路径上的[NBT][nbt]的组件。 | -| `score` | 创建一个包含记分板目标值的组件。 | -| `selector` | 创建一个组件,包含给定[实体选择器][selector]的实体名称列表。 | +| Method | Description | +|----------------|-------------------------------------------------------------------------------------------------------| +| `empty` | Creates an empty component. | +| `literal` | Creates a component with the given text and directly displays that text without translating. | +| `nullToEmpty` | Creates an empty component when given null, and a literal component otherwise. | +| `translatable` | Creates a translatable component. The given string is then resolved as a translation key (see below). | +| `keybind` | Creates a component containing the (translated) display name of the given keybind. | +| `nbt` | Creates a component representing the [NBT][nbt] at the given path. | +| `score` | Creates a component containing a scoreboard objective value. | +| `selector` | Creates a component containing a list of entity names for a given [entity selector][selector]. | -`Component.translatable()`还有一个可变参数,接受字符串插值元素。这与Java的`String#format`类似,但总是使用`%s`代替`%i`、`%d`、`%f`和任何其他格式说明符,在需要时调用`#toString()`。 +`Component.translatable()` additionally has a vararg parameter that accepts string interpolation elements. This works similar to Java's `String#format`, but always uses `%s` instead of `%i`, `%d`, `%f` and any other format specifier, calling `#toString()` where needed. -每个`Component`都可以使用`#getString()`来解析。解析通常是惰性的,这意味着服务器可以指定一个`Component`,将其发送给客户端,然后客户端会各自解析`Component`(不同语言可能导致不同的文本)。Minecraft中的许多地方也会直接接受`Component`并为你解决解析问题。 +Every `Component` can be resolved using `#getString()`. Resolving is generally lazy, meaning that the server can specify a `Component`, send it to the clients, and the clients will each resolve the `Component`s on their own (where different languages may result in different text). Many places in Minecraft will also directly accept `Component`s and take care of resolving for you. :::caution -永远不要让服务器翻译`Component`。总是将`Component`发送到客户端并在那里解析它们。 +Never let a server translate a `Component`. Always send `Component`s to the client and resolve them there. ::: -### 文本格式化 +### Text Formatting -`Component`可以使用`Style`进行格式化。`Style`是不可变的,修改时会创建一个新的`Style`对象,因此允许一次创建,然后根据需要重复使用。 +`Component`s can be formatted using `Style`s. `Style`s are immutable, creating a new `Style` object when modified, and thus allowing them to be created once and then be reused as needed. -`Style.EMPTY`通常可以用作工作的基础。可以通过`Style#applyTo(Style other)`合并多个`Style`,该方法返回一个新的`Style`,它从被`applyTo()`方法调用的`Style`中取得设置,除非相应的设置不存在,在这种情况下,则使用作为参数传入的`Style`中的设置。然后可以这样将`Style`应用到组件上: +`Style.EMPTY` can generally be used as a base to work off. Multiple `Style`s can be merged with `Style#applyTo(Style other)`, which returns a new `Style` that takes settings from the `Style` the `applyTo()` method was called on unless the respective setting is absent, in which case the setting from the `Style` passed in as a parameter is used. `Style`s can then be applied to components like so: ```java Component text = Component.literal("Hello World!"); @@ -52,7 +52,7 @@ text.setStyle(merged); text.withStyle(Style.EMPTY.withColor(0xFF0000)); ``` -另一个更复杂的格式化选项是使用点击和悬停事件: +Another, more elaborate option of formatting is to use click and hover events: ```java // We have a total of 6 options for a click event, and a total of 3 options for a hover event. @@ -87,11 +87,11 @@ Style clickable = Style.EMPTY.withClickEvent(clickEvent); Style hoverable = Style.EMPTY.withHoverEvent(hoverEvent); ``` -## 语言文件 +## Language Files -语言文件是包含从翻译键(见下文)到实际名称的映射的JSON文件。它们位于`assets//lang/language_name.json`。例如,对于一个id为`examplemod`的mod,US English的翻译将位于`assets/examplemod/lang/en_us.json`。可以在[这里][mcwikilang]找到Minecraft支持的所有语言的完整列表。 +Language files are JSON files that contain mappings from translation keys (see below) to actual names. They are located at `assets//lang/language_name.json`. For example, US English translations for a mod with id `examplemod` would be located at `assets/examplemod/lang/en_us.json`. A full list of languages supported by Minecraft can be found [here][mcwikilang]. -一个语言文件通常看起来是这样的: +A language file generally looks like this: ```json { @@ -100,30 +100,30 @@ Style hoverable = Style.EMPTY.withHoverEvent(hoverEvent); } ``` -### 翻译键 +### Translation Keys -翻译键是在翻译中使用的键。在许多情况下,它们遵循格式`registry.modid.name`。例如,一个id为`examplemod`的mod提供了一个名为`example_block`的方块,可能会希望为键`block.examplemod.example_block`提供翻译。然而,你基本上可以使用任何字符串作为翻译键。 +Translation keys are the keys used in translations. In many cases, they follow the format `registry.modid.name`. For example, a mod with the id `examplemod` that provides a block named `example_block` will probably want to provide translations for the key `block.examplemod.example_block`. However, you can use basically any string as a translation key. -如果选定语言中没有与翻译键相关联的翻译,游戏将回退到US English(`en_us`),除非已经选择了US English。如果US English也没有翻译,翻译将静默失败,并代之以显示原始翻译键。 +If a translation key does not have an associated translation in the selected language, the game will fall back to US English (`en_us`), if that is not already the selected language. If US English does not have a translation either, the translation will fail silently, and the raw translation key will be displayed instead. -Minecraft的一些地方为你提供了获取翻译键的辅助方法。例如,方块和物品都提供了`#getDescriptionId`方法。这些不仅可以被查询,而且在需要时也可以被覆盖。一个常见的用例是,根据它们的[NBT][nbt]值有不同名称的物品。这些通常会覆盖带有[`ItemStack`][itemstack]参数的`#getDescriptionId`变体,并根据堆栈的NBT返回不同的值。另一个常见的用例是`BlockItem`,它覆盖该方法以使用关联方块的翻译键。 +Some places in Minecraft offer you helper methods to get a translation keys. For example, both blocks and items provide `#getDescriptionId` methods. These can not only be queried, but also overridden if needed. A common use case are items that have different names depending on their [NBT][nbt] value. These will usually override the variant of `#getDescriptionId` that has an [`ItemStack`][itemstack] parameter, and return different values based on the stack's NBT. Another common use case are `BlockItem`s, which override the method to use the associated block's translation key instead. :::tip -翻译键的唯一目的是用于本地化。不要用它们来处理游戏逻辑,游戏逻辑应该使用[注册名称][regname]。 +The only purpose of translation keys is for localization. Do not use them for game logic, that's what [registry names][regname] are for. ::: -### 翻译Mod元数据 +### Translating Mod Metadata -从NeoForge 20.4.179版本开始,翻译文件可以使用以下键(其中`modid`需替换为实际的mod id)覆盖[mod信息][modstoml]的某些部分: +Starting with NeoForge 20.4.179, translation files can override certain parts of [mod info][modstoml] using the following keys (where `modid` is to be replaced with the actual mod id): -| | 翻译键 | 覆盖内容 | -|--------------|--------------------------------------------|-------------------------------------------------------------------------------| -| 显示名称 | `fml.menu.mods.info.displayname.modid` | 可在`[[mods]]`部分中放置一个名为`displayName`的字段代替。 | -| 描述 | `fml.menu.mods.info.description.modid` | 可在`[[mods]]`部分中放置一个名为`description`的字段代替。 | +| | Translation Key | Overriding | +|--------------|----------------------------------------|------------------------------------------------------------------------------| +| Display Name | `fml.menu.mods.info.displayname.modid` | A field named `displayName` may be placed in the `[[mods]]` section instead. | +| Description | `fml.menu.mods.info.description.modid` | A field named `description` may be placed in the `[[mods]]` section instead. | -### 数据生成 +### Datagen -语言文件可以[通过数据生成][datagen]来创建。要这样做,请扩展`LanguageProvider`类并在`addTranslations()`方法中添加你的翻译: +Language files can be [datagenned][datagen]. To do so, extend the `LanguageProvider` class and add your translations in the `addTranslations()` method: ```java public class MyLanguageProvider extends LanguageProvider { @@ -170,7 +170,7 @@ public class MyLanguageProvider extends LanguageProvider { } ``` -然后,在`GatherDataEvent`中像注册其他提供者一样注册这个提供者。 +Then, register the provider like any other provider in the `GatherDataEvent`. [datagen]: ../index.md#data-generation [itemstack]: ../../items/index.md#itemstacks diff --git a/docs/resources/client/models/bakedmodel.md b/docs/resources/client/models/bakedmodel.md index 214e08d5f..58ffd0735 100644 --- a/docs/resources/client/models/bakedmodel.md +++ b/docs/resources/client/models/bakedmodel.md @@ -1,130 +1,124 @@ -# 烘焙模型 +# Baked Models -`BakedModel` 是带有纹理的形状在代码中的表示形式。它们可以来自多个来源,例如通过 `UnbakedModel#bake`(默认模型加载器)或 `IUnbakedGeometry#bake`([自定义模型加载器][modelloader])的调用生成。一些[方块实体渲染器][ber]也使用烘焙模型。模型的复杂度没有限制。 +`BakedModel`s are the in-code representation of a shape with textures. They can originate from multiple sources, for example from a call to `UnbakedModel#bake` (default model loader) or `IUnbakedGeometry#bake` ([custom model loaders][modelloader]). Some [block entity renderers][ber] also make use of baked models. There is no limit to how complex a model may be. -模型存储在 `ModelManager` 中,可以通过 `Minecraft.getInstance().modelManager` 访问。然后,您可以调用 `ModelManager#getModel` 通过其 [`ResourceLocation`][rl] 或 [`ModelResourceLocation`][mrl] 获取特定模型。模组基本上总是重用之前自动加载和烘焙的模型。 +Models are stored in the `ModelManager`, which can be accessed through `Minecraft.getInstance().modelManager`. Then, you can call `ModelManager#getModel` to get a certain model by its [`ResourceLocation`][rl] or [`ModelResourceLocation`][mrl]. Mods will basically always reuse a model that was previously automatically loaded and baked. -## `BakedModel` 的方法 +## Methods of `BakedModel` ### `getQuads` -烘焙模型最重要的方法是 `getQuads`。此方法负责返回 `BakedQuad` 的列表,这些列表随后可以发送到 GPU。在建模程序中(以及大多数其他游戏中),四边形类似于三角形,然而由于 Minecraft 通常关注于方形,开发者选择使用四边形(4个顶点)而非三角形(3个顶点)进行渲染。`getQuads` 有五个参数可用: +The most important method of a baked model is `getQuads`. This method is responsible for returning a list of `BakedQuad`s, which can then be sent to the GPU. A quad compares to a triangle in a modeling program (and in most other games), however due to Minecraft's general focus on squares, the developers elected to use quads (4 vertices) instead of triangles (3 vertices) for rendering in Minecraft. `getQuads` has five parameters that can be used: -- `BlockState`:正在渲染的[方块状态][blockstate]。可能为空,表示正在渲染物品。 -- `Direction`:正在剔除的面的方向。可能为空,这意味着应该返回不可遮挡的四边形。 -- `RandomSource`:客户端绑定的随机源,您可以用于随机化。 -- `ModelData`:使用的额外模型数据。这可能包含方块实体渲染所需的额外数据。由 `BakedModel#getModelData` 提供。 -- `RenderType`:用于渲染方块的[渲染类型][rendertype]。可能为空,表示应返回此模型使用的所有渲染类型的四边形。否则,它是 `BakedModel#getRenderTypes` 返回的渲染类型之一(见下文)。 +- A `BlockState`: The [blockstate] being rendered. May be null, indicating that an item is being rendered. +- A `Direction`: The direction of the face being culled against. May be null, which means quads that cannot be occluded should be returned. +- A `RandomSource`: A client-bound random source you can use for randomization. +- A `ModelData`: The extra model data to use. This may contain additional data from the block entity needed for rendering. Supplied by `BakedModel#getModelData`. +- A `RenderType`: The [render type][rendertype] to use for rendering the block. May be null, indicating that the quads for all render types used by this model should be returned. Otherwise, it is one of the render types returned by `BakedModel#getRenderTypes` (see below). -模型应该大量缓存。这是因为即使区块只有在其中一个方块更改时才重建,但此方法中的计算仍需尽可能快速,并且理想情况下由于该方法将被每个区块部分调用多次(每个给定模型使用的渲染类型的次数 * 每个模型使用的渲染类型的数量 * 每个区块部分 4096 个方块),因此应该进行大量缓存。此外,[方块实体渲染器][ber]或实体渲染器实际上可能每帧调用此方法几次。 +Models should heavily cache. This is because even though chunks are only rebuilt when a block in them changes, the computations done in this method still need to be as fast as possible and should ideally be cached heavily due to the amount of times this method will be called per chunk section (up to seven times per RenderType used by a given model * amount of RenderTypes used by the respective model * 4096 blocks per chunk section). In addition, [BERs][ber] or entity renderers may actually call this method several times per frame. -### `applyTransform` 和 `getTransforms` +### `applyTransform` and `getTransforms` -`applyTransform` 允许在应用模型的透视变换时应用自定义逻辑,包括返回完全不同的模型。此方法由 NeoForge 添加,替代了 vanilla 的 `getTransforms()` 方法,后者只允许您自定义变换本身,而不能自定义应用方式。然而,`applyTransform` 的默认实现遵循 `getTransforms`,所以如果您只需要自定义变换,也可以重写 `getTransforms` 并完成它。`applyTransforms` 提供三个参数: +`applyTransform` allows for applying custom logic when applying perspective transformations to the model, including returning a completely separate model. This method is added by NeoForge as a replacement for the vanilla `getTransforms()` method, which only allows you to customize the transforms themselves, but not the way they are applied. However, `applyTransform`'s default implementation defers to `getTransforms`, so if you only need custom transforms, you can also override `getTransforms` and be done with it. `applyTransforms` offers three parameters: -- `ItemDisplayContext`:模型正在转换到的[透视][perspective]。 -- `PoseStack`:用于渲染的姿势堆栈。 -- `boolean`:是否使用修改后的值进行左手渲染,而不是默认的右手渲染;如果渲染的手是左手(副手,或在选项中启用 - -左手模式的主手),则为 `true`。 +- An `ItemDisplayContext`: The [perspective] the model is being transformed to. +- A `PoseStack`: The pose stack used for rendering. +- A `boolean`: Whether to use modified values for left-hand rendering instead of the default right hand rendering; `true` if the rendered hand is the left hand (off hand, or main hand if left hand mode is enabled in the options) :::note -`applyTransform` 和 `getTransforms` 仅适用于物品模型。 +`applyTransform` and `getTransforms` only apply to item models. ::: -### 其他 - -您可能会重写和/或查询的其他 `BakedModel` 方法包括: - -| 签名 | 效果 | -|---|---| -| `TriState useAmbientOcclusion()` | 是否使用[环境光遮蔽][ao]。接受 `BlockState`、`RenderType` 和 `ModelData` 参数,并返回 `TriState`,这不仅允许强制禁用 AO,还允许强制启用 AO。有两个重载,每个都返回一个 `boolean` 参数,并只接受 `BlockState` 或无参数;这两个都已弃用,将被第一个变体取代。 | -| `boolean isGui3d()` | 此模型在 GUI 插槽中是否渲染为 3D 或平面。 | -| `boolean usesBlockLight()` | 在照亮模型时是否使用 3D 照明(`true`)或正面的平面照明(`false`)。 | -| `boolean isCustomRenderer()` | 如果为真,跳过正常渲染并调用关联的 [`BlockEntityWithoutLevelRenderer`][bewlr] 的 `renderByItem` 方法。如果为假,则通过默认渲染器渲染。 | -| `ItemOverrides getOverrides()` | 返回与此模型相关的 [`ItemOverrides`][itemoverrides]。这仅在物品模型中相关。 | -| `ModelData getModelData(BlockAndTintGetter, BlockPos, BlockState, ModelData)` | 返回用于模型的模型数据。此方法传递一个现有的 `ModelData`,如果方块有关联的方块实体,则为 `BlockEntity#getModelData()` 的结果,如果不是这种情况,则为 `ModelData.EMPTY`。此方法可用于需要模型数据但没有方块实体的方块,例如具有连接纹理的方块。 | -| `TextureAtlasSprite getParticleIcon(ModelData)` | 返回用于模型的粒子精灵。可以使用模型数据为不同的模型数据值使用不同的粒子精灵。NeoForge 添加,替换了没有参数的 vanilla `getParticleIcon()` 重载。 | -| `ChunkRenderTypeSet getRenderTypes(BlockState, RandomSource, ModelData)` | 返回包含用于渲染方块模型的渲染类型的 `ChunkRenderTypeSet`。`ChunkRenderTypeSet` 是一个支持集合的有序 `Iterable`。默认回退到从模型 JSON [获取渲染类型][rendertype]。仅用于方块模型,物品模型使用下面的重载。 | -| `List getRenderTypes(ItemStack, boolean)` | 返回包含用于渲染物品模型的渲染类型的 `List`。默认回退到正常的模型绑定渲染类型查找,这总是产生一个元素的列表。仅用于物品模型,方块模型使用上面的重载。 | - -## 透视 - -Minecraft 的渲染引擎总共识别 8 种透视类型(如果包括代码中的回退,则为 9 种)用于物品渲染。这些在模型 JSON 的 `display` 块中使用,并在代码中通过 `ItemDisplayContext` 枚举表示。 - -| 枚举值 | JSON 键 | 用途 | -|---|---|---| -| `THIRD_PERSON_RIGHT_HAND` | `"thirdperson_righthand"` | 第三人称右手(F5 视图,或其他玩家上) | -| `THIRD_PERSON_LEFT_HAND` | `"thirdperson_lefthand"` | 第三人称左手(F5 视图,或其他玩家上) | -| `FIRST_PERSON_RIGHT_HAND` | `"firstperson_righthand"` | 第一人称右手 | -| `FIRST_PERSON_LEFT_HAND` | `"firstperson_lefthand"` | 第一人 - -称左手 | -| `HEAD` | `"head"` | 玩家头部装备槽中(通常只能通过命令实现) | -| `GUI` | `"gui"` | 背包,玩家快捷栏 | -| `GROUND` | `"ground"` | 掉落物;注意,掉落物的旋转是由掉落物渲染器处理的,而非模型 | -| `FIXED` | `"fixed"` | 物品框 | -| `NONE` | `"none"` | 代码中的回退用途,不应在 JSON 中使用 | +### Others + +Other methods in `BakedModel` that you may override and/or query include: + +| Signature | Effect | +|-------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `TriState useAmbientOcclusion()` | Whether to use [ambient occlusion][ao] or not. Accepts a `BlockState`, `RenderType` and `ModelData` parameter and returns a `TriState` which allows not only force-disabling AO but also force-enabling AO. Has two overloads that each return a `boolean` parameter and accept either only a `BlockState` or no parameters at all; both of these are deprecated for removal in favor of the first variant. | +| `boolean isGui3d()` | Whether this model renders as 3d or flat in GUI slots. | +| `boolean usesBlockLight()` | Whether to use 3D lighting (`true`) or flat lighting from the front (`false`) when lighting the model. | +| `boolean isCustomRenderer()` | If true, skips normal rendering and calls an associated [`BlockEntityWithoutLevelRenderer`][bewlr]'s `renderByItem` method instead. If false, renders through the default renderer. | +| `ItemOverrides getOverrides()` | Returns the [`ItemOverrides`][itemoverrides] associated with this model. This is only relevant on item models. | +| `ModelData getModelData(BlockAndTintGetter, BlockPos, BlockState, ModelData)` | Returns the model data to use for the model. This method is passed an existing `ModelData` that is either the result of `BlockEntity#getModelData()` if the block has an associated block entity, or `ModelData.EMPTY` if that is not the case. This method can be used for blocks that need model data, but do not have a block entity, for example for blocks with connected textures. | +| `TextureAtlasSprite getParticleIcon(ModelData)` | Returns the particle sprite to use for the model. May use the model data to use different particle sprites for different model data values. NeoForge-added, replacing the vanilla `getParticleIcon()` overload with no parameters. | +| `ChunkRenderTypeSet getRenderTypes(BlockState, RandomSource, ModelData)` | Returns a `ChunkRenderTypeSet` containing the render type(s) to use for rendering the block model. A `ChunkRenderTypeSet` is a set-backed ordered `Iterable`. By default falls back to [getting the render type from the model JSON][rendertype]. Only used for block models, item models use the overload below. | +| `List getRenderTypes(ItemStack, boolean)` | Returns a `List` containing the render type(s) to use for rendering the item model. By default falls back to the normal model-bound render type lookup, which always yields a list with one element. Only used for item models, block models use the overload above. | + +## Perspectives + +Minecraft's render engine recognizes a total of 8 perspective types (9 if you include the in-code fallback) for item rendering. These are used in a model JSON's `display` block, and represented in code through the `ItemDisplayContext` enum. + +| Enum value | JSON key | Usage | +|---------------------------|---------------------------|------------------------------------------------------------------------------------------------------------------| +| `THIRD_PERSON_RIGHT_HAND` | `"thirdperson_righthand"` | Right hand in third person (F5 view, or on other players) | +| `THIRD_PERSON_LEFT_HAND` | `"thirdperson_lefthand"` | Left hand in third person (F5 view, or on other players) | +| `FIRST_PERSON_RIGHT_HAND` | `"firstperson_righthand"` | Right hand in first person | +| `FIRST_PERSON_LEFT_HAND` | `"firstperson_lefthand"` | Left hand in first person | +| `HEAD` | `"head"` | When in a player's head armor slot (often only achievable via commands) | +| `GUI` | `"gui"` | Inventories, player hotbar | +| `GROUND` | `"ground"` | Dropped items; note that the rotation of the dropped item is handled by the dropped item renderer, not the model | +| `FIXED` | `"fixed"` | Item frames | +| `NONE` | `"none"` | Fallback purposes in code, should not be used in JSON | ## `ItemOverrides` -`ItemOverrides` 是一个类,提供了一种方式让烘焙模型处理 [`ItemStack`][itemstack] 的状态并通过 `#resolve` 方法返回新的烘焙模型。`#resolve` 有五个参数: +`ItemOverrides` is a class that provides a way for baked models to process the state of an [`ItemStack`][itemstack] and return a new baked model through the `#resolve` method. `#resolve` has five parameters: -- `BakedModel`:原始模型。 -- `ItemStack`:正在渲染的物品堆栈。 -- `ClientLevel`:模型正在其中渲染的级别。这应该只用于查询级别,不得以任何方式修改。可能为空。 -- `LivingEntity`:模型渲染在其上的实体。可能为空,例如在[方块实体渲染器][ber]中渲染时。 -- `int`:用于随机化的种子。 +- A `BakedModel`: The original model. +- An `ItemStack`: The item stack being rendered. +- A `ClientLevel`: The level the model is being rendered in. This should only be used for querying the level, not mutating it in any way. May be null. +- A `LivingEntity`: The entity the model is rendered on. May be null, e.g. when rendering from a [block entity renderer][ber]. +- An `int`: A seed for randomizing. -`ItemOverrides` 还持有模型的覆盖选项作为 `BakedOverride`。`BakedOverride` 的对象是模型的 [`overrides`][overrides] 块在代码中的表示形式。它可以由烘焙模型使用,根据其内容返回不同的模型。可以通过 `ItemOverrides#getOverrides()` 检索 `ItemOverrides` 实例的所有 `BakedOverride` 列表。 +`ItemOverrides` also hold the model's override options as `BakedOverride`s. An object of `BakedOverride` is an in-code representation of a model's [`overrides`][overrides] block. It can be used by baked models to return different models depending on its contents. A list of all `BakedOverride`s of an `ItemOverrides` instance can be retrieved through `ItemOverrides#getOverrides()`. ## `BakedModelWrapper` -`BakedModelWrapper` 可用于修改已存在的 `BakedModel`。`BakedModelWrapper` 是 `BakedModel` 的一个子类,它在构造函数中接受另一个 `BakedModel`(“原始”模型),并默认将所有方法重定向到原始模型。然后,您的实现可以选择只覆盖某些方法,如下所示: +A `BakedModelWrapper` can be used to modify an already existing `BakedModel`. `BakedModelWrapper` is a subclass of `BakedModel` that accepts another `BakedModel` (the "original" model) in the constructor and by default redirects all methods to the original model. Your implementation can then override only select methods, like so: ```java -// 泛型参数可以是 BakedModel 的更具体的子类。 -// 如果是这样,构造参数必须匹配该类型。 +// The generic parameter may optionally be a more specific subclass of BakedModel. +// If it is, the constructor parameter must match that type. public class MyBakedModelWrapper extends BakedModelWrapper { - // 将原始模型传递给 super。 + // Pass the original model to super. public MyBakedModelWrapper(BakedModel originalModel) { super(originalModel); } - // 在这里覆盖您想要的方法。如果需要,也可以访问 originalModel。 + // Override whatever methods you want here. You may also access originalModel if needed. } ``` -编写您的模型包装类后,必须将包装器应用于它应影响的模型。在[客户端][sides]的[事件处理器][event]中为 `ModelEvent.ModifyBakingResult` 这样做: +After writing your model wrapper class, you must apply the wrappers to the models it should affect. Do so in a [client-side][sides] [event handler][event] for `ModelEvent.ModifyBakingResult`: ```java @SubscribeEvent public static void modifyBakingResult(ModelEvent.ModifyBakingResult event) { - // 对于方块模型 + // For block models event.getModels().computeIfPresent( - // 要修改的模型的模型资源位置。从 - // BlockModelShaper#stateToModelLocation 获取,参数为受影响的方块状态。 + // The model resource location of the model to modify. Get it from + // BlockModelShaper#stateToModelLocation with the blockstate to be affected as a parameter. BlockModelShaper.stateToModelLocation(MyBlocksClass.EXAMPLE_BLOCK.defaultBlockState()), - // 一个带有位置和原始模型为参数的 BiFunction,返回新模型。 + // A BiFunction with the location and the original models as parameters, returning the new model. (location, model) -> new MyBakedModelWrapper(model); ); - // 对于物品模型 + // For item models event.getModels().computeIfPresent( - // 要修改的模型的模型资源位置。 + // The model resource location of the model to modify. new ModelResourceLocation("examplemod", "example_item", "inventory"), - // 一个带有位置和原始模型为参数的 BiFunction,返回新模型。 + // A BiFunction with the location and the original models as parameters, returning the new model. (location, model) -> new MyBakedModelWrapper(model); ); } ``` :::warning -通常建议在可能的情况下使用[自定义模型加载器][modelloader - -]而不是在 `ModelEvent.ModifyBakingResult` 中包装烘焙模型。如果需要,自定义模型加载器也可以使用 `BakedModelWrapper`。 +It is generally encouraged to use a [custom model loader][modelloader] over wrapping baked models in `ModelEvent.ModifyBakingResult` when possible. Custom model loaders can also use `BakedModelWrapper`s if needed. ::: -[ao]: https://zh.wikipedia.org/wiki/环境光遮蔽 +[ao]: https://en.wikipedia.org/wiki/Ambient_occlusion [ber]: ../../../blockentities/ber.md [bewlr]: ../../../items/bewlr.md [blockstate]: ../../../blocks/states.md diff --git a/docs/resources/client/models/datagen.md b/docs/resources/client/models/datagen.md index b84b30b07..a80d59719 100644 --- a/docs/resources/client/models/datagen.md +++ b/docs/resources/client/models/datagen.md @@ -1,55 +1,53 @@ -# 模型数据生成 +# Model Datagen -与大多数JSON数据一样,方块和物品模型可以通过[数据生成][datagen]创建。由于物品和方块模型之间有一些共同之处,因此部分数据生成代码也是相同的。 +Like most JSON data, block and item models can be [datagenned][datagen]. Since some things are common between item and block models, so is some of the datagen code. -## 模型数据生成类 +## Model Datagen Classes ### `ModelBuilder` -每个模型都以某种形式的`ModelBuilder`开始 - 通常是`BlockModelBuilder`或`ItemModelBuilder`,具体取决于您要生成的内容。它包含模型的所有属性:其父级、纹理、元素、变换、加载器等。每个属性都可以通过一个方法设置: +Every model starts out as a `ModelBuilder` of some sort - usually a `BlockModelBuilder` or an `ItemModelBuilder`, depending on what you are generating. It contains all the properties of the model: its parent, its textures, its elements, its transforms, its loader, etc. Each of the properties can be set by a method: -| 方法 | 效果 | -|----------------------------------------------------|----------------------------------------------------------------------------------------------------------| -| `#texture(String key, ResourceLocation texture)` | 添加具有给定键和给定纹理位置的纹理变量。有一个重载,其中第二个参数是`String`。 | -| `#renderType(ResourceLocation renderType)` | 设置渲染类型。有一个参数为`String`的重载。有效值的列表见`RenderType`类。 | -| `#ao(boolean ao)` | 设置是否使用[环境光遮蔽][ao]。 | -| `#guiLight(GuiLight light)` | 设置GUI光源。可以是`GuiLight.FRONT`或`GuiLight.SIDE`。 | -| `#element()` | 添加一个新的`ElementBuilder`(相当于向模型添加一个新[element][elements])。返回该`ElementBuilder`以便进一步修改。| -| `#transforms()` | 返回构建器的`TransformVecBuilder`,用于设置模型的`display`。 | -| `#customLoader(BiFunction customLoaderFactory)` | 使用给定的工厂使该模型使用[自定义加载器][custommodelloader],因此使用自定义加载器构建器。这会改变构建器类型,因此可能会使用不同的方法,这取决于加载器的实现。NeoForge提供了一些开箱即用的自定义加载器,详见链接文章(包括数据生成)。| +| Method | Effect | +|--------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `#texture(String key, ResourceLocation texture)` | Adds a texture variable with the given key and the given texture location. Has an overload where the second parameter is a `String`. | +| `#renderType(ResourceLocation renderType)` | Sets the render type. Has an overload where the parameter is a `String`. For a list of valid values, see the `RenderType` class. | +| `#ao(boolean ao)` | Sets whether to use [ambient occlusion][ao] or not. | +| `#guiLight(GuiLight light)` | Sets the GUI light. May be `GuiLight.FRONT` or `GuiLight.SIDE`. | +| `#element()` | Adds a new `ElementBuilder` (equivalent to adding a new [element][elements] to the model). Returns said `ElementBuilder` for further modification. | +| `#transforms()` | Returns the builder's `TransformVecBuilder`, used for setting the `display` on a model. | +| `#customLoader(BiFunction customLoaderFactory)` | Using the given factory, makes this model use a [custom loader][custommodelloader], and thus, a custom loader builder. This changes the builder type, and as such may use different methods, depending on the loader's implementation. NeoForge provides a few custom loaders out of the box, see the linked article for more info (including datagen). | :::tip -虽然可以通过数据生成创建复杂和详细的模型,但建议使用如[Blockbench][blockbench]之类的建模软件创建更复杂的模型,然后直接使用导出的模型或作为其他模型的父级。 +While elaborate and complex models can be created through datagen, it is recommended to instead use modeling software such as [Blockbench][blockbench] to create more complex models and then have the exported models be used, either directly or as parents for other models. ::: ### `ModelProvider` -方块和物品模型数据生成都利用了`ModelProvider`的子类,分别命名为`BlockModelProvider`和`ItemModelProvider`。虽然物品模型数据生成直接扩展`ItemModelProvider`,但方块模型数据生成使用`BlockStateProvider`基类,它内部有一个可以通过`BlockStateProvider#models()`访问的`BlockModelProvider`。此外,`BlockStateProvider`还有自己的内部`ItemModelProvider`,可通过`BlockStateProvider#itemModels()`访问。`ModelProvider`最重要的部分是`getBuilder(String path)`方法,它返回给定位置的`BlockModelBuilder`(或`ItemModelBuilder`)。 +Both block and item model datagen utilize subclasses of `ModelProvider`, named `BlockModelProvider` and `ItemModelProvider`, respectively. While item model datagen directly extends `ItemModelProvider`, block model datagen uses the `BlockStateProvider` base class, which has an internal `BlockModelProvider` that can be accessed via `BlockStateProvider#models()`. Additionally, `BlockStateProvider` also has its own internal `ItemModelProvider`, accessible via `BlockStateProvider#itemModels()`. The most important part of `ModelProvider` is the `getBuilder(String path)` method, which returns a `BlockModelBuilder` (or `ItemModelBuilder`) at the given location. -然而,`ModelProvider`还包含各种辅助方法。可能最重要的辅助方法是`withExistingParent(String name, ResourceLocation parent)`,它返回一个新的构建器(通过`getBuilder(name)`)并将给定的`ResourceLocation`设置为模型父级。另外两个非常常见的辅助器是`mcLoc(String name)`,返回带有命名空间`minecraft`和给定路径的`ResourceLocation`,以及`modLoc(String name)`,做同样的事情但使用提供者的mod id(通常是您的mod id)而不是`minecraft`。此外,它还提供了各种辅助方法,这些方法是`#withExistingParent`的快捷方式,用于常见事物如板条、楼梯、栅栏、门等。 +However, `ModelProvider` also contains various helper methods. The most important helper method is probably `withExistingParent(String name, ResourceLocation parent)`, which returns a new builder (via `getBuilder(name)`) and sets the given `ResourceLocation` as model parent. Two other very common helpers are `mcLoc(String name)`, which returns a `ResourceLocation` with the namespace `minecraft` and the given name as path, and `modLoc(String name)`, which does the same but with the provider's mod id (so usually your mod id) instead of `minecraft`. Furthermore, it provides various helper methods that are shortcuts for `#withExistingParent` for common things such as slabs, stairs, fences, doors, etc. ### `ModelFile` -最后一个重要的类是`ModelFile`。`ModelFile`是 +Finally, the last important class is `ModelFile`. A `ModelFile` is an in-code representation of a model JSON on disk. `ModelFile` is an abstract class and has two inner subclasses `ExistingModelFile` and `UncheckedModelFile`. An `ExistingModelFile`'s existence is verified using an `ExistingFileHelper`, while an `UncheckedModelFile` is assumed to be existent without further checking. In addition, a `ModelBuilder` is considered to be a `ModelFile` as well. -磁盘上模型JSON的代码表示形式。`ModelFile`是一个抽象类,有两个内部子类`ExistingModelFile`和`UncheckedModelFile`。使用`ExistingFileHelper`验证`ExistingModelFile`的存在,而`UncheckedModelFile`被假定为存在而无需进一步检查。此外,`ModelBuilder`也被视为`ModelFile`。 +## Block Model Datagen -## 方块模型数据生成 - -现在,要实际生成方块状态和方块模型文件,请扩展`BlockStateProvider`并重写`registerStatesAndModels()`方法。请注意,方块模型总是放置在`models/block`子文件夹中,但引用相对于`models`(即它们必须总是以`block/`为前缀)。在大多数情况下,选择众多预定义辅助方法之一是有意义的: +Now, to actually generate blockstate and block model files, extend `BlockStateProvider` and override the `registerStatesAndModels()` method. Note that block models will always be placed in the `models/block` subfolder, but references are relative to `models` (i.e. they must always be prefixed with `block/`). In most cases, it makes sense to choose from one of the many predefined helper methods: ```java public class MyBlockStateProvider extends BlockStateProvider { - // 参数值由GatherDataEvent提供。 + // Parameter values are provided by GatherDataEvent. public MyBlockStateProvider(PackOutput output, ExistingFileHelper existingFileHelper) { - // 用您自己的mod id替换"examplemod"。 + // Replace "examplemod" with your own mod id. super(output, "examplemod", existingFileHelper); } @Override protected void registerStatesAndModels() { - // 占位符,其用法应替换为实际值。请参阅上文了解如何使用模型构建器, - // 以及下文了解模型构建器提供的辅助方法。 + // Placeholders, their usages should be replaced with real values. See above for how to use the model builder, + // and below for the helpers the model builder offers. ModelFile exampleModel = models().withExistingParent("minecraft:block/cobblestone"); Block block = MyBlocksClass.EXAMPLE_BLOCK.get(); ResourceLocation exampleTexture = modLoc("block/example_texture"); @@ -58,93 +56,91 @@ public class MyBlockStateProvider extends BlockStateProvider { ResourceLocation sideTexture = modLoc("block/example_texture_front"); ResourceLocation frontTexture = modLoc("block/example_texture_front"); - // 创建一个简单的方块模型,每个面都使用相同的纹理。 - // 纹理必须位于assets//textures/block/.png, - // 其中分别是方块的注册名的命名空间和路径。 - // 用于大多数(完整)方块,如木板、圆石或砖块。 + // Create a simple block model with the same texture on each side. + // The texture must be located at assets//textures/block/.png, where + // and are the block's registry name's namespace and path, respectively. + // Used by the majority of (full) blocks, such as planks, cobblestone or bricks. simpleBlock(block); - // 接受要使用的模型文件的重载。 + // Overload that accepts a model file to use. simpleBlock(block, exampleModel); - // 接受一个或多个(变量参数)ConfiguredModel对象的重载。 - // 有关ConfiguredModel的更多信息,请参见下文。 + // Overload that accepts one or multiple (vararg) ConfiguredModel objects. + // See below for more info about ConfiguredModel. simpleBlock(block, ConfiguredModel.builder().build()); - // 添加一个带有方块名称的物品模型文件,以给定的模型文件为父级,供方块物品使用。 + // Adds an item model file with the block's name, parenting the given model file, for a block item to pick up. simpleBlockItem(block, exampleModel); - // 调用#simpleBlock()(模型文件重载)和#simpleBlockItem的简写。 + // Shorthand for calling #simpleBlock() (model file overload) and #simpleBlockItem. simpleBlockWithItem(block, exampleModel); - // 添加一个木材方块模型。需要两个纹理,位于assets//textures/block/.png和 - // assets//textures/block/_top.png,分别引用侧面和顶部纹理。 - // 请注意,这里的方块输入仅限于RotatedPillarBlock,这是原版木材使用的类。 + // Adds a log block model. Requires two textures at assets//textures/block/.png and + // assets//textures/block/_top.png, referencing the side and top texture, respectively. + // Note that the block input here is limited to RotatedPillarBlock, which is the class vanilla logs use. logBlock(block); - // 类似于#logBlock,但纹理命名为_side.png和_end.png而不是 - // .png和_top.png。由石英柱和类似方块使用。 - // 有一个重载允许您指定不同的纹理基本名称,然后根据需要后缀为_side和_end, - // 一个重载允许您指定两个资源位置 - // 为侧面和端部纹理,以及一个重载允许指定侧面和端部模型文件。 + // Like #logBlock, but the textures are named _side.png and _end.png instead of + // .png and _top.png, respectively. Used by quartz pillars and similar blocks. + // Has an overload that allow you to specify a different texture base name, that is then suffixed + // with _side and _end as needed, an overload that allows you to specify two resource locations + // for the side and end textures, and an overload that allows specifying side and end model files. axisBlock(block); - // #logBlock和#axisBlock的变体,另外允许指定渲染类型。 - // 有字符串和资源位置变体用于渲染类型, - // 与#logBlock和#axisBlock的所有变体结合使用。 + // Variants of #logBlock and #axisBlock that additionally allow for render types to be specified. + // Comes in string and resource location variants for the render type, + // in all combinations with all variants of #logBlock and #axisBlock. logBlockWithRenderType(block, "minecraft:cutout"); axisBlockWithRenderType(block, mcLoc("cutout_mipped")); - // 指定一个具有侧面纹理、前面纹理和顶部纹理的水平可旋转方块模型。 - // 底部将使用侧面纹理。如果不需要前面或顶部纹理, - // 只需传入侧面纹理两次。例如,用于熔炉和类似方块。 + // Specifies a horizontally-rotatable block model with a side texture, a front texture, and a top texture. + // The bottom will use the side texture as well. If you don't need the front or top texture, + // just pass in the side texture twice. Used by e.g. furnaces and similar blocks. horizontalBlock(block, sideTexture, frontTexture, topTexture); - // 指定一个将根据需要旋转的模型文件的水平可旋转方块模型。 - // 有一个重载,而不是模型文件接受一个Function, - // 允许不同的旋转使用不同的模型。例如,用于石切机。 + // Specifies a horizontally-rotatable block model with a model file that will be rotated as needed. + // Has an overload that instead of a model file accepts a Function, + // allowing for different rotations to use different models. Used e.g. by the stonecutter. horizontalBlock(block, exampleModel); - // 指定一个附着在面上的水平可旋转方块模型,例如按钮或拉杆。 - // 考虑到在地面和天花板上放置方块,并相应旋转它们。 - // 像#horizontalBlock一样,有一个重载接受一个Function。 + // Specifies a horizontally-rotatable block model that is attached to a face, e.g. for buttons or levers. + // Accounts for placing the block on the ground and on the ceiling, and rotates them accordingly. + // Like #horizontalBlock, has an overload that accepts a Function instead. horizontalFaceBlock(block, exampleModel); - // 类似于#horizontalBlock,但用于可向上和向下旋转的方块。 - // 同样,有一个重载接受一个Function。 + // Similar to #horizontalBlock, but for blocks that are rotatable in all directions, including up and down. + // Again, has an overload that accepts a Function instead. directionalBlock(block, exampleModel); } } ``` -另外,`BlockStateProvider`中存在以下常见方块模型的辅助方法: - -- 楼梯 -- 板条 -- 按钮 -- 压力板 -- 标志 -- 栅栏 -- 栅栏门 -- 墙 -- 窗格 -- 门 -- 活板门 - -在某些情况下,方块状态不需要特殊处理,但模型需要。在这种情况下,可通过`BlockStateProvider#models()`访问的`BlockModelProvider`提供了一些额外的辅助方法,所有这些方法都接受第一个参数为名称,并且大多数与完整立方体有关。它们通常用作例如`simpleBlock`的模型文件参数。辅助方法包括支持`BlockStateProvider`中的方法,以及: - -- `withExistingParent`: 前面已经提到,此方法返回一个带有给定父级的新模型构建器。父级必须已经存在或在模型之前创建。 -- `getExistingFile`: 在模型提供者的`ExistingFileHelper`中执行查找,如果存在则返回相应的`ModelFile`,否则抛出`IllegalStateException`。 -- `singleTexture`: 接受一个父级和一个纹理位置,返回一个带有给定父级的模型,并将纹理变量`texture`设置为给定的纹理位置。 -- `sideBottomTop`: 接受一个父级和三个纹理位置,返回一个模型,其侧面、底部和顶部纹理设置为三个纹理位置。 -- `cube`: 接受六个纹理资源位置,分别用于六个面,返回一个完整立方体模型,其六个面设置为六个纹理。 -- `cubeAll`: 接受一个纹理位置,返回一个完整立方体模型,将给定纹理应用于所有六个面。如果愿意,可以将其视为`singleTexture`和`cube`的混合体。 -- `cubeTop`: 接受两个纹理位置,返回一个完整立方体模型,第一个纹理 - -应用于侧面和底部,第二个纹理应用于顶部。 -- `cubeBottomTop`: 接受三个纹理位置,返回一个完整立方体模型,其侧面、底部和顶部纹理设置为三个纹理位置。如果愿意,可以将其视为`cube`和`sideBottomTop`的混合体。 -- `cubeColumn`和`cubeColumnHorizontal`: 接受两个纹理位置,返回一个“立立”或“横卧”的柱状立方体模型,其侧面和端部纹理设置为两个纹理位置。由`BlockStateProvider#logBlock`、`BlockStateProvider#axisBlock`及其变体使用。 -- `orientable`: 接受三个纹理位置,返回一个带有“前面”纹理的立方体。这三个纹理位置分别是侧面、前面和顶部纹理。 -- `orientableVertical`: `orientable`的变体,省略了顶部参数,改为使用侧面参数。 -- `orientableWithBottom`: `orientable`的变体,其具有一个在前面和顶部参数之间的底部纹理的第四参数。 -- `crop`: 接受一个纹理位置,返回一个带有给定纹理的类似作物的模型,如四种原版作物所使用的。 -- `cross`: 接受一个纹理位置,返回一个带有给定纹理的十字模型,如花、树苗和许多其他植被方块所使用的。 -- `torch`: 接受一个纹理位置,返回一个带有给定纹理的火把模型。 -- `wall_torch`: 接受一个纹理位置,返回一个带有给定纹理的壁挂火把模型(壁挂火把是与立火把不同的方块)。 -- `carpet`: 接受一个纹理位置,返回一个带有给定纹理的地毯模型。 - -最后,别忘了在事件中注册您的方块状态提供者: +Additionally, helpers for the following common block models exist in `BlockStateProvider`: + +- Stairs +- Slabs +- Buttons +- Pressure Plates +- Signs +- Fences +- Fence Gates +- Walls +- Panes +- Doors +- Trapdoors + +In some cases, the blockstates don't need special casing, but the models do. For this case, the `BlockModelProvider`, accessible via `BlockStateProvider#models()`, provides a few additional helpers, all of which accept a name as the first parameter and most of which are in some way related to full cubes. They will typically be used as model file parameters for e.g. `simpleBlock`. The helpers include supporting methods for the ones in `BlockStateProvider`, as well as: + +- `withExistingParent`: Already mentioned before, this method returns a new model builder with the given parent. The parent must either already exist or be created before the model. +- `getExistingFile`: Performs a lookup in the model provider's `ExistingFileHelper`, returning the corresponding `ModelFile` if present and throwing an `IllegalStateException` otherwise. +- `singleTexture`: Accepts a parent and a single texture location, returning a model with the given parent, and with the texture variable `texture` set to the given texture location. +- `sideBottomTop`: Accepts a parent and three texture locations, returning a model with the given parent and the side, bottom and top textures set to the three texture locations. +- `cube`: Accepts six texture resource locations for the six sides, returning a full cube model with the six sides set to the six textures. +- `cubeAll`: Accepts a texture location, returning a full cube model with the given texture applied to all six sides. A mix between `singleTexture` and `cube`, if you will. +- `cubeTop`: Accepts two texture locations, returning a full cube model with the first texture applied to the sides and the bottom, and the second texture applied to the top. +- `cubeBottomTop`: Accepts three texture locations, returning a full cube model with the side, bottom and top textures set to the three texture locations. A mix between `cube` and `sideBottomTop`, if you will. +- `cubeColumn` and `cubeColumnHorizontal`: Accepts two texture locations, returning a "standing" or "laying" pillar cube model with the side and end textures set to the two texture locations. Used by `BlockStateProvider#logBlock`, `BlockStateProvider#axisBlock` and their variants. +- `orientable`: Accepts three texture locations, returning a cube with a "front" texture. The three texture locations are the side, front and top texture, respectively. +- `orientableVertical`: Variant of `orientable` that omits the top parameter, instead using the side parameter as well. +- `orientableWithBottom`: Variant of `orientable` that has a fourth parameter for a bottom texture between the front and top parameter. +- `crop`: Accepts a texture location, returning a crop-like model with the given texture, as used by the four vanilla crops. +- `cross`: Accepts a texture location, returning a cross model with the given texture, as used by flowers, saplings and many other foliage blocks. +- `torch`: Accepts a texture location, returning a torch model with the given texture. +- `wall_torch`: Accepts a texture location, returning a wall torch model with the given texture (wall torches are separate blocks from standing torches). +- `carpet`: Accepts a texture location, returning a carpet model with the given texture. + +Finally, don't forget to register your block state provider to the event: ```java @SubscribeEvent @@ -153,7 +149,7 @@ public static void gatherData(GatherDataEvent event) { PackOutput output = generator.getPackOutput(); ExistingFileHelper existingFileHelper = event.getExistingFileHelper(); - // 其他提供者在这里 + // other providers here generator.addProvider( event.includeClient(), new MyBlockStateProvider(output, existingFileHelper) @@ -163,86 +159,84 @@ public static void gatherData(GatherDataEvent event) { ### `ConfiguredModel.Builder` -如果默认辅助工具不能满足您的需求,您也可以使用`ConfiguredModel.Builder`直接构建模型对象,然后在`VariantBlockStateBuilder`中使用它们构建`variants`块状态文件,或在`MultiPartBlockStateBuilder`中构建`multipart`块状态文件: +If the default helpers won't do it for you, you can also directly build model objects using a `ConfiguredModel.Builder` and then use them in a `VariantBlockStateBuilder` to build a `variants` blockstate file, or in a `MultiPartBlockStateBuilder` to build a `multipart` blockstate file: ```java -// 创建一个ConfiguredModel.Builder。或者,您可以使用下面演示的方式之一 -// (VariantBlockStateBuilder.PartialBlockstate#modelForState或MultiPartBlockStateBuilder#part)在适用的情况下。 +// Create a ConfiguredModel.Builder. Alternatively, you can use one of the ways demonstrated below +// (VariantBlockStateBuilder.PartialBlockstate#modelForState or MultiPartBlockStateBuilder#part) where applicable. ConfiguredModel.Builder builder = ConfiguredModel.builder() -// 使用一个模型文件。如前所述,可以是ExistingModelFile、UncheckedModelFile, -// 或某种类型的ModelBuilder。参见上文了解如何使用ModelBuilder。 +// Use a model file. As mentioned previously, can either be an ExistingModelFile, an UncheckedModelFile, +// or some sort of ModelBuilder. See above for how to use ModelBuilder. .modelFile(models().withExistingParent("minecraft:block/cobblestone")) - // 设置绕x轴和y轴的旋转。 + // Set rotations around the x and y axes. .rotationX(90) .rotationY(180) - // 设置uv锁定。 + // Set a uvlock. .uvlock(true) - // 设置权重。 + // Set a weight. .weight(5); -// 构建配置模型。返回类型是一个数组 -// 以考虑同一块状态中可能有多个模型。 +// Build the configured model. The return type is an array +// to account for multiple possible models in the same blockstate. ConfiguredModel[] model = builder.build(); -// 获取一个变体块状态构建器。 +// Get a variant block state builder. VariantBlockStateBuilder variantBuilder = getVariantBuilder(MyBlocksClass.EXAMPLE_BLOCK.get()); -// 创建一个部分状态并设置属性。 -VariantBlockStateBuilder.PartialBlockstate partialState = variantBuilder.partialState - -(); -// 为部分块状态添加一个或多个模型。模型是变量参数。 +// Create a partial state and set properties on it. +VariantBlockStateBuilder.PartialBlockstate partialState = variantBuilder.partialState(); +// Add one or multiple models for a partial blockstate. The models are a vararg parameter. variantBuilder.addModels(partialState, - // 至少指定一个ConfiguredModel.Builder,如上所见。通过#modelForState创建。 + // Specify at least one ConfiguredModel.Builder, as seen above. Create through #modelForState(). partialState.modelForState() .modelFile(models().withExistingParent("minecraft:block/cobblestone")) .uvlock(true) ); -// 或者,forAllStates(Function)为每个可能的状态创建一个模型。 -// 传递的函数将为每个可能的状态调用一次。 +// Alternatively, forAllStates(Function) creates a model for every state. +// The passed function will be called once for each possible state. variantBuilder.forAllStates(state -> { - // 根据状态的属性返回一个ConfiguredModel。 - // 例如,以下代码将根据方块的水平旋转旋转模型。 + // Return a ConfiguredModel depending on the state's properties. + // For example, the following code will rotate the model depending on the horizontal rotation of the block. return ConfiguredModel.builder() .modelFile(models().withExistingParent("minecraft:block/cobblestone")) .rotationY((int) state.getValue(BlockStateProperties.HORIZONTAL_FACING).toYRot()) .build(); }); -// 获取一个多部分块状态构建器。 +// Get a multipart block state builder. MultiPartBlockStateBuilder multipartBuilder = getMultipartBuilder(MyBlocksClass.EXAMPLE_BLOCK.get()); -// 添加一个新部分。从.part()开始,以.end()结束。 +// Add a new part. Starts with .part() and ends with .end(). multipartBuilder.addPart(multipartBuilder.part() - // 步骤一:构建模型。multipartBuilder.part()返回一个ConfiguredModel.Builder, - // 意味着上面看到的所有方法都可以在这里使用。 + // Step one: Build the model. multipartBuilder.part() returns a ConfiguredModel.Builder, + // meaning that all methods seen above can be used here as well. .modelFile("minecraft:block/cobblestone") - // 调用.addModel()。现在模型已构建,我们可以进入步骤二:添加部分数据。 + // Call .addModel(). Now that the model is built, we can proceed to step two: add the part data. .addModel() - // 为部分添加条件。需要一个属性 - // 和至少一个属性值;属性值是变量参数。 + // Add a condition for the part. Requires a property + // and at least one property value; property values are a vararg. .condition(BlockStateProperties.FACING, Direction.NORTH, Direction.SOUTH) - // 将多部分条件设置为或运算而不是默认的与运算。 + // Set the multipart conditions to be ORed instead of the default ANDing. .useOr() - // 创建一个嵌套条件组。 + // Creates a nested condition group. .nestedGroup() - // 向嵌套组添加一个条件。 + // Adds a condition to the nested group. .condition(BlockStateProperties.FACING, Direction.NORTH) - // 仅将这个条件组设置为或运算而不是与运算。 + // Sets only this condition group to be ORed instead of ANDed. .useOr() - // 创建另一个嵌套条件组。嵌套组的数量没有限制。 + // Creates yet another nested condition group. There is no limit on how many groups can be nested. .nestedGroup() - // 结束嵌套条件组,返回到拥有的部分构建器或条件组级别。 - // 这里调用两次,因为我们当前有两个嵌套组。 + // Ends the nested condition group, returning to the owning part builder or condition group level. + // Called twice here since we currently have two nested groups. .endNestedGroup() .endNestedGroup() - // 结束部分构建器并将生成的部分添加到多部分构建器中。 + // End the part builder and add the resulting part to the multipart builder. .end() ); ``` -## 物品模型数据生成 +## Item Model Datagen -生成物品模型相对简单得多,这主要是因为我们直接在`ItemModelProvider`上操作,而不是使用像`BlockStateProvider`这样的中间类,这当然是因为物品模型没有与方块状态文件等价的文件,而是直接使用。 +Generating item models is considerably simpler, which is mainly due to the fact that we operate directly on an `ItemModelProvider` instead of using an intermediate class like `BlockStateProvider`, which is of course because item models don't have an equivalent to blockstate files and are instead used directly. -与上面类似,我们创建一个类并让它扩展基础提供者,在这种情况下是`ItemModelProvider`。由于我们直接在`ModelProvider`的子类中,所有的`models()`调用都变成了`this`(或被省略)。 +Similar to above, we create a class and have it extend the base provider, in this case `ItemModelProvider`. Since we are directly in a subclass of `ModelProvider`, all `models()` calls become `this` (or are omitted). ```java public class MyItemModelProvider extends ItemModelProvider { @@ -252,20 +246,20 @@ public class MyItemModelProvider extends ItemModelProvider { @Override protected void registerModels() { - // 方块物品通常使用其相应的方块模型作为父级。 + // Block items generally use their corresponding block models as parent. withExistingParent(MyItemsClass.EXAMPLE_BLOCK_ITEM.get(), modLoc("block/example_block")); - // 物品通常使用一个简单的父级和一个纹理。最常见的父级是item/generated和item/handheld。 - // 在这个例子中,物品纹理位于assets/examplemod/textures/item/example_item.png。 - // 如果您想要一个更复杂的模型,您可以使用 getBuilder(),然后从中进行工作,就像使用块模型一样。 - withExistingParent(MyItemsClass.EXAMPLE_ITEM.get(), mcLoc("item/ generated")).texture("layer0", "item/example_item"); - // 上面的行很常见,因此有一个快捷方式。 请注意项目注册表名称和 - // 相对于纹理/项目的纹理路径必须匹配。 + // Items generally use a simple parent and one texture. The most common parents are item/generated and item/handheld. + // In this example, the item texture would be located at assets/examplemod/textures/item/example_item.png. + // If you want a more complex model, you can use getBuilder() and then work from that, like you would with block models. + withExistingParent(MyItemsClass.EXAMPLE_ITEM.get(), mcLoc("item/generated")).texture("layer0", "item/example_item"); + // The above line is so common that there is a shortcut for it. Note that the item registry name and the + // texture path, relative to textures/item, must match. basicItem(MyItemsClass.EXAMPLE_ITEM.get()); } } ``` -与所有数据提供者一样,不要忘记将您的提供者注册到该事件: +And like all data providers, don't forget to register your provider to the event: ```java @SubscribeEvent diff --git a/docs/resources/client/models/index.md b/docs/resources/client/models/index.md index 04f3b3324..968b8e4d1 100644 --- a/docs/resources/client/models/index.md +++ b/docs/resources/client/models/index.md @@ -1,106 +1,104 @@ -模型是JSON文件,确定方块或物品的视觉形状和纹理。模型由立方体元素组成,每个元素都有自己的大小,然后每个面都被分配一个纹理。 - -每个物品通过其注册名称被分配一个物品模型。例如,注册名称为 `examplemod:example_item` 的物品将被分配到 `assets/examplemod/models/item/example_item.json` 中的模型。对于方块来说,情况稍微复杂一些,因为它们首先被分配一个方块状态文件。更多信息请参见[下文][bsfile]。 - -## 规范 - -_另请参阅:[Minecraft Wiki][mcwiki]上的[模型][mcwikimodel]_ - -模型是一个具有以下可选属性的JSON文件: - -- `loader`:NeoForge添加的。设置自定义模型加载器。有关更多信息,请参阅[自定义模型加载器][custommodelloader]。 -- `parent`:设置父模型,格式为相对于 `models` 文件夹的[资源位置][rl]。所有父属性将首先应用,然后被声明模型中设置的属性覆盖。常见的父模型包括: - - `minecraft:block/block`:所有方块模型的通用父模型。 - - `minecraft:block/cube`:所有使用1x1x1立方体模型的模型的父模型。 - - `minecraft:block/cube_all`:使用相同纹理在所有六个面上的立方体模型变种,例如圆石或木板。 - - `minecraft:block/cube_bottom_top`:使用相同纹理在所有四个水平面上,并在顶部和底部使用单独的纹理的立方体模型变种。常见示例包括砂岩或镶嵌石英。 - - `minecraft:block/cube_column`:具有侧面纹理和底部和顶部纹理的立方体模型变种。示例包括木头原木,以及石英和紫珀柱。 - - `minecraft:block/cross`:使用两个具有相同纹理的平面,一个顺时针旋转45°,另一个逆时针旋转45°,从上方看形成X形(因此得名)。示例包括大多数植物,例如草、树苗和花朵。 - - `minecraft:item/generated`:经典的2D平面物品模型的父模型。大多数物品都使用此模型。由于其四边形是从纹理生成的,因此会忽略 `elements` 块。 - - `minecraft:item/handheld`:用于看起来实际由玩家持有的2D平面物品模型的父模型。主要由工具使用。作为 `item/generated` 的子模型,因此它也会忽略 `elements` 块。 - - `minecraft:builtin/entity`:指定除 `particle` 外没有其他纹理。如果这是父模型,则[`BakedModel#isCustomRenderer()`][iscustomrenderer]将返回 `true`,以允许使用 [`BlockEntityWithoutLevelRenderer`][bewlr]。 - - 方块物品通常(但不总是)使用其对应的方块模型作为父模型。例如,圆石物品模型使用父模型 `minecraft:block/cobblestone`。 -- `ambientocclusion`:是否启用[环境光遮蔽][ao]。仅在方块模型上有效。默认为 `true`。如果您的自定义方块模型具有奇怪的阴影,请尝试将其设置为 `false`。 -- `render_type`:参见[渲染类型][rendertype]。 -- `gui_light`:可以是 `"front"` 或 `"side"`。如果是 `"front"`,光将来自前方,对于平面2D模型很有用。如果是 `"side"`,光将来自侧面,对于3D模型(尤其是方块模型)很有用。默认为 `"side"`。仅在物品模型上有效。 -- `textures`:一个子对象,将名称(称为纹理变量)映射到[纹理位置][textures]。然后可以在[elements]中使用纹理变量。它们也可以在元素中指定,但在子模型中保留未指定。 - - 方块模型还应指定一个 `particle` 纹理。当坠落在、穿越或破坏方块时,将使用此纹理。 - - 物品模型还可以使用层纹理,命名为 `layer0`、`layer1` 等,其中具有较高索引的层会呈现在具有较低索引的层上方(例如 `layer1` 将呈现在 `layer0` 上方)。仅在父模型为 `item/generated` 时有效,最多支持5层(`layer0` 到 `layer4`)。 -- `elements`:立方体[元素]的列表。 -- `overrides`:[覆盖模型][overrides]的列表。仅在物品模型上有效。 -- `display`:包含不同[视角]的不同显示选项的子对象,请参见链接的文章以获取可能的键。仅在物品模型上有效,但通常在方块模型中指定,以便物品模型可以继承显示选项。每个视角都是一个可选的子对象,可能包含以下选项,按顺序应用: - - `translation`:模型的平移,指定为 `[x, y, z]`。 - - `rotation`:模型的旋转,指定为 `[x, y, z]`。 - - `scale`:模型的 - -缩放,指定为 `[x, y, z]`。 - - `right_rotation`:NeoForge添加的。在缩放后应用的第二个旋转,指定为 `[x, y, z]`。 -- `transform`:参见[根变换][roottransforms]。 +# Models + +Models are JSON files that determine the visual shape and texture(s) of a block or item. A model consists of cuboid elements, each with their own size, that then get assigned a texture to each face. + +Each item gets an item model assigned to it by its registry name. For example, an item with the registry name `examplemod:example_item` would get the model at `assets/examplemod/models/item/example_item.json` assigned to it. For blocks, this is a bit more complicated, as they get assigned a blockstate file first. See [below][bsfile] for more information. + +## Specification + +_See also: [Model][mcwikimodel] on the [Minecraft Wiki][mcwiki]_ + +A model is a JSON file with the following optional properties in the root tag: + +- `loader`: NeoForge-added. Sets a custom model loader. See [Custom Model Loaders][custommodelloader] for more information. +- `parent`: Sets a parent model, in the form of a [resource location][rl] relative to the `models` folder. All parent properties will be applied and then overridden by the properties set in the declaring model. Common parents include: + - `minecraft:block/block`: The common parent of all block models. + - `minecraft:block/cube`: Parent of all models that use a 1x1x1 cube model. + - `minecraft:block/cube_all`: Variant of the cube model that uses the same texture on all six sides, for example cobblestone or planks. + - `minecraft:block/cube_bottom_top`: Variant of the cube model that uses the same texture on all four horizontal sides, and separate textures on the top and the bottom. Common examples include sandstone or chiseled quartz. + - `minecraft:block/cube_column`: Variant of the cube model that has a side texture and a bottom and top texture. Examples include wooden logs, as well as quartz and purpur pillars. + - `minecraft:block/cross`: Model that uses two planes with the same texture, one rotated 45° clockwise and the other rotated 45° counter-clockwise, forming an X when viewed from above (hence the name). Examples include most plants, e.g. grass, saplings and flowers. + - `minecraft:item/generated`: Parent for classic 2D flat item models. Used by most items in the game. Ignores an `elements` block since its quads are generated from the textures. + - `minecraft:item/handheld`: Parent for 2D flat item models that appear to be actually held by the player. Used predominantly by tools. Submodel of `item/generated`, which causes it to ignore the `elements` block as well. + - `minecraft:builtin/entity`: Specifies no textures other than `particle`. If this is the parent, [`BakedModel#isCustomRenderer()`][iscustomrenderer] returns `true` to allow use of a [`BlockEntityWithoutLevelRenderer`][bewlr]. + - Block items commonly (but not always) use their corresponding block models as parent. For example, the cobblestone item model uses the parent `minecraft:block/cobblestone`. +- `ambientocclusion`: Whether to enable [ambient occlusion][ao] or not. Only effective on block models. Defaults to `true`. If your custom block model has weird shading, try setting this to `false`. +- `render_type`: See [Render Types][rendertype]. +- `gui_light`: Can be `"front"` or `"side"`. If `"front"`, light will come from the front, useful for flat 2D models. If `"side"`, light will come from the side, useful for 3D models (especially block models). Defaults to `"side"`. Only effective on item models. +- `textures`: A sub-object that maps names (known as texture variables) to [texture locations][textures]. Texture variables can then be used in [elements]. They can also be specified in elements, but left unspecified in order for child models to specify them. + - Block models should additionally specify a `particle` texture. This texture is used when falling on, running across, or breaking the block. + - Item models can also use layer textures, named `layer0`, `layer1`, etc., where layers with a higher index are rendered above those with a lower index (e.g. `layer1` would be rendered above `layer0`). Only works if the parent is `item/generated`, and only works for up to 5 layers (`layer0` through `layer4`). +- `elements`: A list of cuboid [elements]. +- `overrides`: A list of [override models][overrides]. Only effective on item models. +- `display`: A sub-object that holds the different display options for different [perspectives], see linked article for possible keys. Only effective on item models, but often specified in block models so that item models can inherit the display options. Every perspective is an optional sub-object that may contain the following options, which are applied in that order: + - `translation`: The translation of the model, specified as `[x, y, z]`. + - `rotation`: The rotation of the model, specified as `[x, y, z]`. + - `scale`: The scale of the model, specified as `[x, y, z]`. + - `right_rotation`: NeoForge-added. A second rotation that is applied after scaling, specified as `[x, y, z]`. +- `transform`: See [Root Transforms][roottransforms]. :::tip -如果您在确定如何精确指定某些内容方面遇到困难,请查看执行类似操作的原版模型。 +If you're having trouble finding out how exactly to specify something, have a look at a vanilla model that does something similar. ::: -### 渲染类型 +### Render Types -使用可选的 NeoForge 添加的 `render_type` 字段,您可以为模型设置渲染类型。如果未设置(如所有原版模型),游戏将退回到 `ItemBlockRenderTypes` 中硬编码的渲染类型。如果 `ItemBlockRenderTypes` 中也不存在该方块的渲染类型,它将退回到 `minecraft:solid`。原版提供以下默认渲染类型: +Using the optional NeoForge-added `render_type` field, you can set a render type for your model. If this is not set (as is the case in all vanilla models), the game will fall back to the render types hardcoded in `ItemBlockRenderTypes`. If `ItemBlockRenderTypes` doesn't contain the render type for that block either, it will fall back to `minecraft:solid`. Vanilla provides the following default render types: -- `minecraft:solid`:用于完全实心的方块,例如石头。 -- `minecraft:cutout`:用于任何像素完全实心或完全透明的方块,即具有完全不透明或完全透明的像素,例如玻璃。 -- `minecraft:cutout_mipped`:`minecraft:cutout` 的变体,将在较大距离上缩小纹理以避免视觉伪影([mipmapping])。由于通常不希望物品上使用mipmapping并且可能会导致伪影,因此不会对物品渲染应用mipmapping。例如,用于树叶。 -- `minecraft:cutout_mipped_all`:`minecraft:cutout_mipped` 的变体,将mipmapping应用于物品模型。 -- `minecraft:translucent`:用于任何像素可能部分透明的方块,例如有色玻璃。 -- `minecraft:tripwire`:用于具有被渲染到天气目标的特殊要求的方块,即绊线。 +- `minecraft:solid`: Used for fully solid blocks, such as stone. +- `minecraft:cutout`: Used for blocks where any pixel is either fully solid or fully transparent, i.e. with either full or no transparency, for example glass. +- `minecraft:cutout_mipped`: Variant of `minecraft:cutout` that will scale down textures at large distances to avoid visual artifacts ([mipmapping]). Does not apply the mipmapping to item rendering, as it is usually undesired on items and may cause artifacts. Used for example by leaves. +- `minecraft:cutout_mipped_all`: Variant of `minecraft:cutout_mipped` which applies mipmapping to item models as well. +- `minecraft:translucent`: Used for blocks where any pixel may be partially transparent, for example stained glass. +- `minecraft:tripwire`: Used by blocks with the special requirement of being rendered to the weather target, i.e. tripwire. -选择正确的渲染类型在某种程度上是一个性能问题。实心渲染比切割渲染快,切割渲染比半透明渲染快。因此,您应该为您的用例指定最严格的适用渲染类型,因为它也将是最快的。 +Selecting the correct render type is a question of performance to some degree. Solid rendering is faster than cutout rendering, and cutout rendering is faster than translucent rendering. Because of this, you should specify the "strictest" render type applicable for your use case, as it will also be the fastest. -如果愿意,您也可以添加自己的渲染类型。要这样做,请订阅 [mod 总线][modbus] [事件] `RegisterNamedRenderTypesEvent` 并 `#register` 您的渲染类型。`#register` 具有三个或四个参数: +If you want, you can also add your own render types. To do so, subscribe to the [mod bus][modbus] [event] `RegisterNamedRenderTypesEvent` and `#register` your render types. `#register` has three or four parameters: -- 渲染类型的名称。将以您的mod id作为前缀。例如,在此处使用 `"my_cutout"` 将为您提供 `examplemod:my_cutout` 作为新的可供您使用的渲染类型(前提是您的mod id为 `examplemod`)。 -- 分块渲染类型。可以使用 `RenderType.chunkBufferLayers()` 返回的列表中的任何类型。 -- 实体渲染类型。必须是具有 `DefaultVertexFormat.NEW_ENTITY` 顶点格式的渲染类型。 -- 可选项:神奇的渲染类型。必须是具有 `DefaultVertexFormat.NEW_ENTITY` 顶点格式的渲染类型。如果将图形模式设置为 _Fabulous!_,则将使用此渲染类型而不是常规实体渲染类型。如果省略,将回退到常规渲染类型。通常建议在渲染类型在某种程度上使用透明度时设置。 +- The name of the render type. Will be prefixed with your mod id. For example, using `"my_cutout"` here will provide `examplemod:my_cutout` as a new render type for you to use (provided that your mod id is `examplemod`, of course). +- The chunk render type. Any of the types in the list returned by `RenderType.chunkBufferLayers()` can be used. +- The entity render type. Must be a render type with the `DefaultVertexFormat.NEW_ENTITY` vertex format. +- Optional: The fabulous render type. Must be a render type with the `DefaultVertexFormat.NEW_ENTITY` vertex format. Will be used instead of the regular entity render type if the graphics mode is set to _Fabulous!_. If omitted, falls back to the regular render type. Generally recommended to set if the render type uses transparency in some way. -### 元素 +### Elements -元素是立方体对象的JSON表示。它具有以下属性: +An element is a JSON representation of a cuboid object. It has the following properties: -- `from`:立方体起始角的坐标,指定为 `[x, y, z]`。以1/16方块单位指定。例如,`[0, 0, 0]` 将是“左下”角,`[8, 8, 8]` 将是中心,`[16, 16, 16]` 将是“右上”角。 -- `to`:立方体结束角的坐标,指定为 `[x, y, z]`。与 `from` 一样,这是以1/16方块单位指定的。 +- `from`: The coordinate of the start corner of the cuboid, specified as `[x, y, z]`. Specified in 1/16 block units. For example, `[0, 0, 0]` would be the "bottom left" corner, `[8, 8, 8]` would be the center, and `[16, 16, 16]` would be the "top right" corner of the block. +- `to`: The coordinate of the end corner of the cuboid, specified as `[x, y, z]`. Like `from`, this is specified in 1/16 block units. :::tip -Minecraft中的值在范围 `[-16, 32]` 内。但是,强烈不建议超过 `[0, 16]`,因为这将导致光照和/或剔除问题。 +Values in `from` and `to` are limited by Minecraft to the range `[-16, 32]`. However, it is highly discouraged to exceed `[0, 16]`, as that will lead to lighting and/or culling issues. ::: -- `neoforge_data`:请参见[额外的面数据][extrafacedata]。 -- `faces`:包含最多6个面的数据的对象,分别命名为 `north`、`south`、`east`、`west`、`up` 和 `down`。每个面都具有以下数据: - - `uv`:面的uv,指定为 `[u1, v1, u2, v2]`,其中 `u1, v1` 是左上角uv坐标,`u2, v2` 是右下角uv坐标。 - - `texture`:面使用的纹理。必须是以 `#` 为前缀的纹理变量。例如,如果您的模型有一个名为 `wood` 的纹理,则可以使用 `#wood` 引用该纹理。在技术上是可选的,如果缺少将使用缺失的纹理。 - - `rotation`:可选。以顺时针90、180或270度旋转纹理。 - - `cullface`:可选。告诉渲染引擎在指定方向上有一个完整方块触碰时跳过渲染面。方向可以是 `north`、`south`、`east`、`west`、`up` 或 `down`。 - - `tint +- `neoforge_data`: See [Extra Face Data][extrafacedata]. +- `faces`: An object containing data for of up to 6 faces, named `north`, `south`, `east`, `west`, `up` and `down`, respectively. Every face has the following data: + - `uv`: The uv of the face, specified as `[u1, v1, u2, v2]`, where `u1, v1` is the top left uv coordinates and `u2, v2` is the bottom right uv coordinates. + - `texture`: The texture to use for the face. Must be a texture variable prefixed with a `#`. For example, if your model had a texture named `wood`, you would use `#wood` to reference that texture. Technically optional, will use the missing texture if absent. + - `rotation`: Optional. Rotates the texture clockwise by 90, 180 or 270 degrees. + - `cullface`: Optional. Tells the render engine to skip rendering the face when there is a full block touching it in the specified direction. The direction can be `north`, `south`, `east`, `west`, `up` or `down`. + - `tintindex`: Optional. Specifies a tint index that may be used by a color handler, see [Tinting][tinting] for more information. Defaults to -1, which means no tinting. + - `neoforge_data`: See [Extra Face Data][extrafacedata]. -index`:可选。指定颜色处理程序可能使用的染色索引,有关更多信息,请参见[着色][tinting]。默认为-1,表示不染色。 - - `neoforge_data`:请参见[额外的面数据][extrafacedata]。 +Additionally, it can specify the following optional properties: -此外,它还可以指定以下可选属性: +- `shade`: Only for block models. Optional. Whether the faces of this element should have direction-dependent shading on it or not. Defaults to true. +- `rotation`: A rotation of the object, specified as a sub object containing the following data: + - `angle`: The rotation angle, in degrees. Can be -45 through 45 in steps of 22.5 degrees. + - `axis`: The axis to rotate around. It is currently not possible to rotate an object around more than one axis. + - `origin`: Optional. The origin point to rotate around, specified as `[x, y, z]`. Note that these are absolute values, i.e. they are not relative to the cube's position. If unspecified, will use `[0, 0, 0]`. -- `shade`:仅适用于方块模型。可选。此元素的面是否应该有方向相关的阴影。默认为 true。 -- `rotation`:对象的旋转,指定为包含以下数据的子对象: - - `angle`:旋转角度,以度为单位。可以是 -45 到 45,步长为22.5度。 - - `axis`:围绕旋转的轴。目前无法围绕多个轴旋转对象。 - - `origin`:可选。旋转的原点,指定为 `[x, y, z]`。请注意,这些是绝对值,即它们不是相对于立方体位置的。如果未指定,将使用 `[0, 0, 0]`。 +#### Extra Face Data -#### 额外的面数据 +Extra face data (`neoforge_data`) can be applied to both an element and a single face of an element. It is optional in all contexts where it is available. If both element-level and face-level extra face data is specified, the face-level data will override the element-level data. Extra data can specify the following data: -额外的面数据(`neoforge_data`)可以应用于元素和元素的单个面。在所有可用的上下文中,它都是可选的。如果同时指定了元素级和面级额外面数据,则面级数据将覆盖元素级数据。额外的数据可以指定以下数据: +- `color`: Tints the face with the given color. Must be an ARGB value. Can be specified as a string or as a decimal integer (JSON does not support hex literals). Defaults to `0xFFFFFFFF`. This can be used as a replacement for tinting if the color values are constant. +- `block_light`: Overrides the block light value used for this face. Defaults to 0. +- `sky_light`: Overrides the sky light value used for this face. Defaults to 0. +- `ambient_occlusion`: Disables or enables ambient occlusion for this face. Defaults to the value set in the model. -- `color`:使用给定颜色对面进行染色。必须是ARGB值。可以指定为字符串或十进制整数(JSON不支持十六进制文字)。默认为 `0xFFFFFFFF`。如果颜色值是恒定的,可以用作对染色的替代。 -- `block_light`:覆盖用于此面的块光照值。默认为0。 -- `sky_light`:覆盖用于此面的天空光照值。默认为0。 -- `ambient_occlusion`:为此面禁用或启用环境光遮蔽。默认为模型中设置的值。 - -使用自定义的 `neoforge:item_layers` 加载器,还可以指定要应用于 `item/generated` 模型中所有几何图形的额外面数据。在以下示例中,第1层将以红色染色并以完全亮度发光: +Using the custom `neoforge:item_layers` loader, you can also specify extra face data to apply to all the geometry in an `item/generated` model. In the following example, layer 1 will be tinted red and glow at full brightness: ```json5 { @@ -121,11 +119,12 @@ index`:可选。指定颜色处理程序可能使用的染色索引,有关 } ``` -### 覆盖模型 +### Overrides + +Item overrides can assign a different model to an item based on a float value, called the override value. For example, bows and crossbows use this to change the texture depending on how long they have been drawn. Overrides have both a model and a code side to them. -物品覆盖可以根据浮点值(称为覆盖值)为物品分配不同的模型。例如,弓和十字弓使用此功能根据它们已经拉开的时间来更改纹理。覆盖模型有模型和代码两个方面。 +The model can specify one or multiple override models that should be used when the override value is equal to or greater than the given threshold value. For example, the bow uses two different properties `pulling` and `pull`. `pulling` is treated as a boolean value, with 1 being interpreted as pulling and 0 as not pulling, while `pull` represents how much the bow is currently pulled. It then uses these properties to specify usage of three alternative models when charged to below 65% (`pulling` 1, no `pull` value), 65% (`pulling` 1, `pull` 0.65) and 90% (`pulling` 1, `pull` 0.9). If multiple models apply (because the value keeps on becoming bigger), the last element of the list matches, so make sure your order is correct. The overrides look as follows: -模型可以指定一个或多个覆盖模型,当覆盖值等于或大于给定的阈值时应使用。例如,弓使用两个不同的属性 `pulling` 和 `pull`。 `pulling` 被视为布尔值,其中1被解释为正在拉动,0被解释为未拉动,而 `pull` 表示弓当前拉伸的程度。然后,它使用这些属性来指定在充能至低于65%时(`pulling` 1,没有 `pull` 值),65%时(`pulling` 1,`pull` 0.65)和90%时(`pulling` 1,`pull` 0.9)使用三种不同的替代模型。如果多个模型适用(因为值不断变大),则匹配列表的最后一个元素,因此请确保您的顺序是正确的。覆盖模型如下所示: ```json5 { // other stuff here @@ -157,7 +156,7 @@ index`:可选。指定颜色处理程序可能使用的染色索引,有关 } ``` -事情的代码方面相当简单。假设我们想要向我们的物品添加一个名为 `examplemod:property` 的属性,我们会在[客户端][side]的[event handler][eventhandler]中使用以下代码: +The code side of things is pretty simple. Assuming that we want to add a property named `examplemod:property` to our item, we would use the following code in a [client-side][side] [event handler][eventhandler]: ```java @SubscribeEvent @@ -178,14 +177,14 @@ public static void onClientSetup(FMLClientSetupEvent event) { ``` :::info -原版 Minecraft 仅允许 0 到 1 之间的浮点值。NeoForge 对此进行了补充,以允许任意的浮点值。 +Vanilla Minecraft only allows for float values between 0 and 1. NeoForge patches this to allow arbitrary float values. ::: -### 根变换 +### Root Transforms -在模型的顶层添加 `transform` 属性会告诉加载器在应用 [blockstate 文件][bsfile](用于方块模型)中的旋转或 `display` 块中的变换(用于物品模型)之前,应该对所有几何图形应用一个变换。这是由 NeoForge 添加的。 +Adding the `transform` property at the top level of a model tells the loader that a transformation to all geometry should be applied right before the rotations in a [blockstate file][bsfile] (for block models) or the transformations in a `display` block (for item models) are applied. This is added by NeoForge. -根变换可以通过两种方式指定。第一种方式是作为一个名为 `matrix` 的单个属性,其中包含一个 3x4 的变换矩阵(行主序,最后一行被省略),以嵌套的 JSON 数组形式表示。矩阵是按照平移、左旋转、缩放、右旋转和变换原点的顺序组合而成。示例如下: +The root transforms can be specified in two ways. The first way would be as a single property named `matrix` containing a transformation 3x4 matrix (row major order, last row is omitted) in the form of a nested JSON array. The matrix is the composition of the translation, left rotation, scale, right rotation and the transformation origin in that order. An example would look like this: ```json5 { @@ -200,42 +199,40 @@ public static void onClientSetup(FMLClientSetupEvent event) { } ``` -根据Minecraft译名标准化,以下是翻译后的文档: - -第二种方式是指定一个包含以下条目的JSON对象,按以下顺序应用: +The second way is to specify a JSON object containing any combination of the following entries, applied in that order: -- `translation`:相对位移。指定为一个三维向量(`[x, y, z]`),如果缺失默认为`[0, 0, 0]`。 -- `rotation` 或 `left_rotation`:在缩放之前应用于平移原点的旋转。默认不旋转。可以用以下方式指定: - - 一个带有单一轴到旋转映射的JSON对象,例如 `{"x": 90}` - - 一个包含单一轴到旋转映射的JSON对象的数组,按照指定的顺序应用,例如 `[{"x": 90}, {"y": 45}, {"x": -22.5}]` - - 一个包含三个值的数组,每个值分别指定每个轴的旋转,例如 `[90, 45, -22.5]` - - 一个包含四个值的数组,直接指定一个四元数,例如 `[0.38268346, 0, 0, 0.9238795]`(= X轴45度旋转) -- `scale`:相对于平移原点的缩放。指定为一个三维向量(`[x, y, z]`),如果缺失默认为`[1, 1, 1]`。 -- `post_rotation` 或 `right_rotation`:在缩放之后应用于平移原点的旋转。默认不旋转。指定方式与`rotation`相同。 -- `origin`:用于旋转和缩放的原点。转换也作为最后一步移到这里。指定为一个三维向量(`[x, y, z]`)或使用三个内置值之一 `"corner"`(=`[0, 0, 0]`),`"center"`(=`[0.5, 0.5, 0.5]`)或 `"opposing-corner"`(=`[1, 1, 1]`,默认值)。 +- `translation`: The relative translation. Specified as a three-dimensional vector (`[x, y, z]`) and defaults to `[0, 0, 0]` if absent. +- `rotation` or `left_rotation`: Rotation around the translated origin to be applied before scaling. Defaults to no rotation. Specified in one of the following ways: + - A JSON object with a single axis to rotation mapping, e.g. `{"x": 90}` + - An array of JSON objects with a single axis to rotation mapping each, applied in the order they are specified in, e.g. `[{"x": 90}, {"y": 45}, {"x": -22.5}]` + - An array with three values that each specify the rotation around each axis, e.g. `[90, 45, -22.5]` + - An array with four values directly specifying a quaternion, e.g. `[0.38268346, 0, 0, 0.9238795]` (= 45 degrees around the X axis) +- `scale`: The scale relative to the translated origin. Specified as a three-dimensional vector (`[x, y, z]`) and defaults to `[1, 1, 1]` if absent. +- `post_rotation` or `right_rotation`: Rotation around the translated origin to be applied after scaling. Defaults to no rotation. Specified the same as `rotation`. +- `origin`: Origin point used for rotation and scaling. The transformation is also moved here as a final step. Specified either as a three-dimensional vector (`[x, y, z]`) or using one of the three builtin values `"corner"` (= `[0, 0, 0]`), `"center"` (= `[0.5, 0.5, 0.5]`) or `"opposing-corner"` (= `[1, 1, 1]`, default). -## 方块状态文件 +## Blockstate Files -参见:[Minecraft Wiki][mcwiki]上的[方块状态文件][mcwikiblockstate] +_See also: [Blockstate files][mcwikiblockstate] on the [Minecraft Wiki][mcwiki]_ -方块状态文件由游戏用于为不同的[方块状态]分配不同的模型。每个注册到游戏的方块必须有一个确切的方块状态文件。指定方块模型到方块状态有两种相互排斥的方式:通过变体或者多部件。 +Blockstate files are used by the game to assign different models to different [blockstates]. There must be exactly one blockstate file per block registered to the game. Specifying block models for blockstates works in two mutually exclusive ways: via variants or via multipart. -在`variants`块内,每个方块状态都有一个元素。这是将方块状态与模型相关联的主要方式,被绝大多数方块使用。 -- 键是没有方块名的方块状态的字符串表示,例如对于非含水的台阶是`"type=top,waterlogged=false"`,或者对于没有属性的方块是`""`。值得注意的是,未使用的属性可以省略。例如,如果`waterlogged`属性对所选模型无影响,则两个对象`type=top,waterlogged=false`和`type=top,waterlogged=true`可以被合并为一个`type=top`对象。这也意味着对于每个方块,空字符串都是有效的。 -- 值要么是单一的模型对象,要么是模型对象的数组。如果使用了模型对象的数组,将从中随机选择一个模型。一个模型对象包含以下数据: - - `model`:模型文件位置的路径,相对于命名空间的`models`文件夹,例如`minecraft:block/cobblestone`。 - - `x`和`y`:模型在x轴/y轴的旋转。限制为90度的步进。每个都是可选的,默认为0。 - - `uvlock`:旋转模型时是否锁定UV。可选的,默认为false。 - - `weight`:仅在模型对象数组中有用。给对象一个权重,用于选择随机模型对象。可选的,默认为1。 +Inside a `variants` block, there is an element for each blockstate. This is the predominant way of associating blockstates with models, used by the vast majority of blocks. +- The key is the string representation of the blockstate without the block name, so for example `"type=top,waterlogged=false"` for a non-waterlogged top slab, or `""` for a block with no properties. It is worth noting that unused properties may be omitted. For example, if the `waterlogged` property has no influence on the model chosen, two objects `type=top,waterlogged=false` and `type=top,waterlogged=true` may be collapsed into one `type=top` object. This also means that an empty string is valid for every block. +- The value is either a single model object or an array of model objects. If an array of model objects is used, a model will be randomly chosen from it. A model object consists of the following data: + - `model`: A path to a model file location, relative to the namespace's `models` folder, for example `minecraft:block/cobblestone`. + - `x` and `y`: Rotation of the model on the x-axis/y-axis. Limited to steps of 90 degrees. Optional each, defaults to 0. + - `uvlock`: Whether to lock the UVs of the model when rotating or not. Optional, defaults to false. + - `weight`: Only useful with arrays of model objects. Gives the object a weight, used when choosing a random model object. Optional, defaults to 1. -相反,在`multipart`块内,元素根据方块状态的属性组合。这种方法主要被栅栏和围墙使用,它们根据布尔属性启用四个方向的部分。一个多部分元素由两个部分组成:`when`块和`apply`块。 +In contrast, inside a `multipart` block, elements are combined depending on the properties of the blockstate. This method is mainly used by fences and walls, who enable the four directional parts based on boolean properties. A multipart element consists of two parts: a `when` block and an `apply` block. -- `when`块指定了一个方块状态的字符串表示,或者一个必须满足元素应用的属性列表。这些列表可以被命名为`"OR"`或`"AND"`,对其内容执行相应的逻辑操作。单个方块状态和列表值都可以通过用`|`分隔它们来指定多个实际值(例如 `facing=east|facing=west`)。 -- `apply`块指定了要使用的模型对象或模型对象数组。这与`variants`块的工作方式完全相同。 +- The `when` block specifies either a string representation of a blockstate or a list of properties that must be met for the element to apply. The lists can either be named `"OR"` or `"AND"`, performing the respective logical operation on its contents. Both single blockstate and list values can additionally specify multiple actual values by separating them with `|` (for example `facing=east|facing=west`). +- The `apply` block specifies the model object or an array of model objects to use. This works exactly like with a `variants` block. -## 着色 +## Tinting -有些方块,如草或树叶,会根据它们的位置和/或属性改变它们的纹理。[模型元素][elements]可以在它们的面上指定一个染色指数,这将允许颜色处理器处理相应的面。代码方面通过两个事件来处理,一个是方块颜色处理器,另一个是物品颜色处理器。它们的工作方式非常相似,让我们先看一下方块处理器: +Some blocks, such as grass or leaves, change their texture color based on their location and/or properties. [Model elements][elements] can specify a tint index on their faces, which will allow a color handler to handle the respective faces. The code side of things works through two events, one for block color handlers and one for item color handlers. They both work pretty similar, so let's have a look at a block handler first: ```java @SubscribeEvent @@ -252,7 +249,7 @@ public static void registerBlockColorHandlers(RegisterColorHandlersEvent.Block e } ``` -物品处理器的工作方式几乎相同,只是命名和lambda参数有所不同: +Item handlers work pretty much the same, except for some naming and the lambda parameters: ```java @SubscribeEvent @@ -266,11 +263,11 @@ public static void registerItemColorHandlers(RegisterColorHandlersEvent.Item eve } ``` -请注意,`item/generated`模型为其各个层指定了染色指数 - `layer0`有染色指数0,`layer1`有染色指数1,等等。另外,记住方块物品是物品,而不是方块,需要物品颜色处理器来着色。 +Be aware that the `item/generated` model specifies tint indices for its various layers - `layer0` has tint index 0, `layer1` has tint index 1, etc. Also, remember that block items are items, not blocks, and require an item color handler to be colored. -## 注册额外的模型 +## Registering Additional Models -一些并未与某个方块或物品有所关联,但在其他上下文(例如[方块实体渲染器][ber])中仍然需要的模型,可以通过`ModelEvent.RegisterAdditional`来注册: +Models that are not associated with a block or item in some way, but are still required in other contexts (e.g. [block entity renderers][ber]), can be registered through `ModelEvent.RegisterAdditional`: ```java // Client-side mod bus event handler diff --git a/docs/resources/client/models/modelloaders.md b/docs/resources/client/models/modelloaders.md index 3852f1fb8..dcb86d901 100644 --- a/docs/resources/client/models/modelloaders.md +++ b/docs/resources/client/models/modelloaders.md @@ -1,16 +1,16 @@ -# 自定义模型加载器 +# Custom Model Loaders -一个模型就是一个形状。它可以是一个立方体、一组立方体、一组三角形,或者任何其他的几何形状(或几何形状的组合)。在大多数上下文中,模型是如何定义的并不重要,因为最终一切都会在内存中变成`BakedModel`。因此,NeoForge增加了能够注册自定义模型加载器的功能,这些加载器可以将任何你想要的模型转换成游戏使用的`BakedModel`。 +A model is simply a shape. It can be a cube, a collection of cubes, a collection of triangles, or any other geometrical shape (or collection of geometrical shape). For most contexts, it is not relevant how a model is defined, as everything will end up as a `BakedModel` in memory anyway. As such, NeoForge adds the ability to register custom model loaders that can transform any model you want into a `BakedModel` for the game to use. -一个方块模型的入口点仍然是模型JSON文件。然而,你可以在JSON的根部指定一个`loader`字段,它会将默认加载器替换为你自己的加载器。一个自定义模型加载器可能会忽略默认加载器所需的所有字段。 +The entry point for a block model remains the model JSON file. However, you can specify a `loader` field in the root of the JSON that will swap out the default loader for your own loader. A custom model loader may ignore all fields the default loader requires. -## 内建模型加载器 +## Builtin Model Loaders -除了默认模型加载器,NeoForge还提供了几个内建的加载器,每个加载器都有不同的用途。 +Besides the default model loader, NeoForge offers several builtin loaders, each serving a different purpose. -### 组合模型 +### Composite Model -组合模型可以用来在父模型中指定不同的模型部分,并且只在子模型中应用其中的一些。以下面的例子最能说明这一点。考虑以下位于`examplemod:example_composite_model`的父模型: +A composite model can be used to specify different model parts in the parent and only apply some of them in a child. This is best illustrated by an example. Consider the following parent model at `examplemod:example_composite_model`: ```json5 { @@ -31,7 +31,7 @@ } ``` -然后,我们可以在`examplemod:example_composite_model`的子模型中禁用和启用单独的部分: +Then, we can disable and enable individual parts in a child model of `examplemod:example_composite_model`: ```json5 { @@ -44,11 +44,11 @@ } ``` -要[datagen][modeldatagen]这个模型,使用自定义加载器类`CompositeModelBuilder`。 +To [datagen][modeldatagen] this model, use the custom loader class `CompositeModelBuilder`. -### 动态流体容器模型 +### Dynamic Fluid Container Model -动态流体容器模型,也称为动态桶模型,它最常见的使用场景是用于表示流体容器的物品(如桶或罐)并想在模型中显示流体。这只有在流体量是固定的(例如只有岩浆和细雪)的情况下才可行,如果流体是任意的,那么请使用[`BlockEntityWithoutLevelRenderer`][bewlr]。 +The dynamic fluid container model, also called dynamic bucket model after its most common use case, is used for items that represent a fluid container (such as a bucket or a tank) and want to show the fluid within the model. This only works if there is a fixed amount of fluids (e.g. only lava and powder snow) that can be used, use a [`BlockEntityWithoutLevelRenderer`][bewlr] instead if the fluid is arbitrary. ```json5 { @@ -71,7 +71,7 @@ } ``` -很多时候,动态流体容器模型会直接使用桶模型。这是通过指定`neoforge:item_bucket`父模型来实现的,如下所示: +Very often, dynamic fluid container models will directly use the bucket model. This is done by specifying the `neoforge:item_bucket` parent model, like so: ```json5 { @@ -83,11 +83,11 @@ } ``` -要[datagen][modeldatagen]这个模型,使用自定义加载器类`DynamicFluidContainerModelBuilder`。请注意,出于对旧版本支持的考虑,这个类还提供了一个设置`apply_tint`属性的方法,这个属性现在已不再使用。 +To [datagen][modeldatagen] this model, use the custom loader class `DynamicFluidContainerModelBuilder`. Be aware that for legacy support reasons, this class also provides a method to set the `apply_tint` property, which is no longer used. -### 元素模型 +### Elements Model -一个元素模型由方块模型[elements][elements]和一个可选的[根变换][transform]组成。主要用于常规模型渲染之外的场景,例如在[BER][ber]中。 +An elements model consists of block model [elements][elements] and an optional [root transform][transform]. Intended mainly for usage outside regular model rendering, for example within a [BER][ber]. ```json5 { @@ -97,9 +97,9 @@ } ``` -### 空模型 +### Empty Model -一个空模型什么都不渲染。 +An empty model just renders nothing at all. ```json5 { @@ -107,12 +107,12 @@ } ``` -### 物品层模型 +### Item Layer Model -物品层模型是标准`item/generated`模型的一个变种,提供了以下额外的功能: +Item layer models are a variant of the standard `item/generated` model that offer the following additional features: -- 无限数量的层(而不是默认的5层) -- 每一层的[渲染类型][rendertype] +- Unlimited amount of layers (instead of the default 5) +- Per-layer [render types][rendertype] ```json5 { @@ -135,11 +135,11 @@ } ``` -要[datagen][modeldatagen]这个模型,使用自定义加载器类`ItemLayerModelBuilder`。 +To [datagen][modeldatagen] this model, use the custom loader class `ItemLayerModelBuilder`. -### OBJ模型 +### OBJ Model -OBJ模型加载器允许您在游戏中使用Wavefront `.obj` 3D模型,允许在模型中包含任意形状(包括三角形、圆形等)。`.obj`模型必须放在`models`文件夹(或其子文件夹)中,并且必须提供一个同名的`.mtl`文件(或手动设置),所以例如,位于`models/block/example.obj`的OBJ模型必须有一个对应的MTL文件位于`models/block/example.mtl`。 +The OBJ model loader allows you to use Wavefront `.obj` 3D models in the game, allowing for arbitrary shapes (including triangles, circles, etc.) to be included in a model. The `.obj` model must be placed in the `models` folder (or a subfolder thereof), and a `.mtl` file with the same name must be provided (or set manually), so for example, an OBJ model at `models/block/example.obj` must have a corresponding MTL file at `models/block/example.mtl`. ```json5 { @@ -168,11 +168,11 @@ OBJ模型加载器允许您在游戏中使用Wavefront `.obj` 3D模型,允许 } ``` -要[datagen][modeldatagen]这个模型,使用自定义加载器类`ObjModelBuilder`。 +To [datagen][modeldatagen] this model, use the custom loader class `ObjModelBuilder`. -### 独立变换模型 +### Separate Transforms Model -独立变换模型可用于根据视角切换不同的模型。视角与[normal model][model]中的`display`块相同。这通过指定一个基础模型(作为后备)然后为每个视角指定覆盖模型来实现。注意,如果您愿意,每个这样的模型都可以是完整的模型,但通常最简单的方法是使用那个模型的子模型来引用另一个模型,如下所示: +A separate transforms model can be used to switch between different models based on the perspective. The perspectives are the same as for the `display` block in a [normal model][model]. This works by specifying a base model (as a fallback) and then specifying per-perspective override models. Note that each of these can be fully-fledged models if you so desire, but it is usually easiest to just refer to another model by using a child model of that model, like so: ```json5 { @@ -190,24 +190,24 @@ OBJ模型加载器允许您在游戏中使用Wavefront `.obj` 3D模型,允许 } ``` -要[datagen][modeldatagen]这个模型,使用自定义加载器类`SeparateTransformsModelBuilder`。 +To [datagen][modeldatagen] this model, use the custom loader class `SeparateTransformsModelBuilder`. -## 创建自定义模型加载器 +## Creating Custom Model Loaders -要创建自己的模型加载器,您需要三个类加上一个事件处理程序: +To create your own model loader, you need three classes, plus an event handler: -- 一个几何体加载器类 -- 一个几何体类 -- 一个动态的[baked model][bakedmodel]类 -- 一个用于`ModelEvent.RegisterGeometryLoaders`的[客户端][sides] [事件处理程序][event],用于注册几何体加载器 +- A geometry loader class +- A geometry class +- A dynamic [baked model][bakedmodel] class +- A [client-side][sides] [event handler][event] for `ModelEvent.RegisterGeometryLoaders` that registers the geometry loader -为了说明这些类是如何连接的,我们将跟随一个模型的加载过程: +To illustrate how these classes are connected, we will follow a model being loaded: -- 在模型加载期间,带有`loader`属性设置为您的加载器的模型JSON被传递给您的几何体加载器。然后,几何体加载器读取模型JSON并使用模型JSON的属性返回一个几何体对象。 -- 在模型烘焙期间,几何体被烘焙,返回一个动态烘焙模型。 -- 在模型渲染期间,动态烘焙模型用于渲染。 +- During model loading, a model JSON with the `loader` property set to your loader is passed to your geometry loader. The geometry loader then reads the model JSON and returns a geometry object using the model JSON's properties. +- During model baking, the geometry is baked, returning a dynamic baked model. +- During model rendering, the dynamic baked model is used for rendering. -让我们通过一个基本的类设置进一步说明。几何体加载器类命名为`MyGeometryLoader`,几何体类命名为`MyGeometry`,动态烘焙模型类命名为`MyDynamicModel`: +Let's illustrate this further through a basic class setup. The geometry loader class is named `MyGeometryLoader`, the geometry class is named `MyGeometry`, and the dynamic baked model class is named `MyDynamicModel`: ```java public class MyGeometryLoader implements IGeometryLoader { @@ -328,7 +328,7 @@ public class MyDynamicModel implements IDynamicBakedModel { } ``` -在所有操作完成后,不要忘记实际注册您的加载器,否则所有的工作都将白费: +When all is done, don't forget to actually register your loader, otherwise all the work will have been for nothing: ```java // Client-side mod bus event handler @@ -338,9 +338,9 @@ public static void registerGeometryLoaders(ModelEvent.RegisterGeometryLoaders ev } ``` -### 数据生成 +### Datagen -当然,我们也可以对我们的模型进行[数据生成]。为此,我们需要一个扩展`CustomLoaderBuilder`的类: +Of course, we can also [datagen] our models. To do so, we need a class that extends `CustomLoaderBuilder`: ```java // This assumes a block model. Use ItemModelBuilder as the generic parameter instead @@ -371,7 +371,7 @@ public class MyLoaderBuilder extends CustomLoaderBuilder { } ``` -要使用这个加载器构建器,在块(或物品)[模型数据生成][modeldatagen]期间执行以下操作: +To use this loader builder, do the following during block (or item) [model datagen][modeldatagen]: ```java // This assumes a BlockStateProvider. Use getBuilder("my_cool_block") directly in an ItemModelProvider. @@ -380,15 +380,15 @@ public class MyLoaderBuilder extends CustomLoaderBuilder { MyLoaderBuilder loaderBuilder = models().getBuilder("my_cool_block").customLoader(MyLoaderBuilder::new); ``` -然后,在`loaderBuilder`上调用你的字段设置器。 +Then, call your field setters on the `loaderBuilder`. -#### 可见性 +#### Visibility -`CustomLoaderBuilder`的默认实现有应用可见性的方法。你可以选择在你的模型加载器中使用或忽视`visibility`属性。目前,只有[复合模型加载器][composite]使用了这个属性。 +The default implementation of `CustomLoaderBuilder` holds methods for applying visibility. You may choose to use or ignore the `visibility` property in your model loader. Currently, only the [composite model loader][composite] makes use of this property. -### 重用默认模型加载器 +### Reusing the Default Model Loader -在某些情况下,重用 Vanilla 模型加载器并在其基础上构建你的模型逻辑,而不是直接替换它,是有意义的。我们可以使用一个巧妙的技巧来实现这个目标:在模型加载器中,我们只需移除`loader`属性,然后将其发送回模型解析器,让其误以为现在是一个常规模型。然后我们将它传给几何体,在那里烘焙模型几何体(就像默认的几何体处理器那样),并将其传递给动态模型,在那里我们可以以我们想要的方式使用模型的quads: +In some contexts, it makes sense to reuse the vanilla model loader and just building your model logic on top of that instead of outright replacing it. We can do so using a neat trick: In the model loader, we simply remove the `loader` property and send it back to the model deserializer, tricking it into thinking that it is a regular model now. We then pass it to the geometry, bake the model geometry there (like the default geometry handler would) and pass it along to the dynamic model, where we can then use the model's quads in whatever way we want: ```java public class MyGeometryLoader implements IGeometryLoader { diff --git a/docs/resources/client/particles.md b/docs/resources/client/particles.md index e7f3e9bf6..1ed0126e0 100644 --- a/docs/resources/client/particles.md +++ b/docs/resources/client/particles.md @@ -1,12 +1,12 @@ -# 粒子效果 +# Particles -粒子效果是能够美化游戏并增加沉浸感的2D效果。它们可以在客户端和服务器端[side]产生,但由于它们主要是视觉效果,关键部分只存在于物理(和逻辑)客户端。 +Particles are 2D effects that polish the game and add immersion. They can be spawned both client and server [side], but being mostly visual in nature, critical parts exist only on the physical (and logical) client side. -## 注册粒子效果 +## Registering Particles ### `ParticleType` -粒子效果是使用`ParticleType`注册的。这与`EntityType`或`BlockEntityType`类似,有一个`Particle`类 - 每个产生的粒子都是该类的一个实例 -,然后有`ParticleType`类,保存一些共有信息,用于注册。`ParticleType`是一个[注册表],这意味着我们想要使用`DeferredRegister`来注册它们,就像所有其他注册的对象一样: +Particles are registered using `ParticleType`s. These work similar to `EntityType`s or `BlockEntityType`s, in that there's a `Particle` class - every spawned particle is an instance of that class -, and then there's the `ParticleType` class, holding some common information, that is used for registration. `ParticleType`s are a [registry], which means that we want to register them using a `DeferredRegister` like all other registered objects: ```java public class MyParticleTypes { @@ -28,14 +28,14 @@ public class MyParticleTypes { ``` :::info -如果您需要在服务器端处理粒子效果,那么`ParticleType`是必需的。客户端也可以直接使用`Particle`。 +A `ParticleType` is only necessary if you need to work with particles on the server side. The client can also use `Particle`s directly. ::: ### `Particle` -`Particle`是稍后被生成到世界中并显示给玩家的实体。虽然你可以扩展`Particle`并自己实现一些功能,但在许多情况下,扩展`TextureSheetParticle`可能会更好,因为这个类为你提供了如动画和缩放等功能的助手,而且还为你实现了实际的渲染(如果直接扩展`Particle`,你需要自己实现这些功能)。 +A `Particle` is what is later spawned into the world and displayed to the player. While you may extend `Particle` and implement things yourself, in many cases it will be better to extend `TextureSheetParticle` instead, as this class provides helpers for things such as animating and scaling, and also does the actual rendering for you (all of which you'd need to implement yourself if extending `Particle` directly). -`Particle`的大多数属性是由如`gravity`,`lifetime`,`hasPhysics`,`friction`等字段控制的。唯一有意义的自我实现方法是`tick`和`move`,这两个方法都正如你所期望的那样进行操作。因此,自定义的粒子类通常很简短,例如,只包括一个构造函数,设置一些字段并让超类处理剩下的事情。一个基本的实现可能看起来像这样: +Most properties of `Particle`s are controlled by fields such as `gravity`, `lifetime`, `hasPhysics`, `friction`, etc. The only two methods that make sense to implement yourself are `tick` and `move`, both of which do exactly what you'd expect. As such, custom particle classes are often short, consisting e.g. only of a constructor that sets some fields and lets the superclass handle the rest. A basic implementation would look somewhat like this: ```java public class MyParticle extends TextureSheetParticle { @@ -62,7 +62,7 @@ public class MyParticle extends TextureSheetParticle { ### `ParticleProvider` -接下来,粒子类型必须注册一个`ParticleProvider`。`ParticleProvider`是一个仅在客户端的类,负责通过`createParticle`方法实际创建我们的`Particle`。虽然这里可以包含更复杂的代码,但许多粒子提供器的实现可能非常简单,如下所示: +Next, particle types must register a `ParticleProvider`. `ParticleProvider` is a client-only class responsible for actually creating our `Particle`s through the `createParticle` method. While more elaborate code can be included here, many particle providers are as simple as this: ```java // The generic type of ParticleProvider must match the type of the particle type this provider is for. @@ -86,7 +86,7 @@ public class MyParticleProvider implements ParticleProvider } ``` -然后,您的粒子提供器必须在[客户端][side] [mod bus][modbus] [event] `RegisterParticleProvidersEvent`中与粒子类型关联: +Your particle provider must then be associated with the particle type in the [client-side][side] [mod bus][modbus] [event] `RegisterParticleProvidersEvent`: ```java @SubscribeEvent @@ -99,9 +99,9 @@ public static void registerParticleProviders(RegisterParticleProvidersEvent even } ``` -### 粒子定义 +### Particle Definitions -最后,我们必须将我们的粒子类型与一个纹理关联起来。与物品被关联到一个物品模型相似,我们将我们的粒子类型与所谓的粒子定义(或粒子描述)关联起来。粒子定义是`assets//particles`目录中的一个JSON文件,它的名称与粒子类型相同(例如,对于上述示例是`my_particle.json`)。粒子定义JSON的格式如下: +Finally, we must associate our particle type with a texture. Similar to how items are associated with an item model, we associate our particle type with what is known as a particle definition (or particle description). A particle definition is a JSON file in the `assets//particles` directory and has the same name as the particle type (so for example `my_particle.json` for the above example). The particle definition JSON has the following format: ```json5 { @@ -116,15 +116,15 @@ public static void registerParticleProviders(RegisterParticleProvidersEvent even } ``` -请注意,仅当使用精灵集粒子时才需要粒子定义文件。单精灵粒子直接映射到`assets//textures/particle/.png`的纹理文件,特殊粒子提供器可以做任何你想做的事情。 +Note that a particle definition file is only necessary when using a sprite set particle. Single sprite particles directly map to the texture file at `assets//textures/particle/.png`, and special particle providers can do whatever you want anyway. :::danger -不匹配的精灵集粒子工厂列表和粒子定义文件,即没有相应粒子工厂的粒子描述,或者反之亦然,将会抛出异常! +A mismatched list of sprite set particle factories and particle definition files, i.e. a particle description without a corresponding particle factory, or vice versa, will throw an exception! ::: -### 数据生成 +### Datagen -粒子定义文件也可以通过扩展`ParticleDescriptionProvider`并覆写`#addDescriptions()`方法来进行[数据生成][datagen]: +Particle definition files can also be [datagenned][datagen] by extending `ParticleDescriptionProvider` and overriding the `#addDescriptions()` method: ```java public class MyParticleDescriptionProvider extends ParticleDescriptionProvider { @@ -158,7 +158,7 @@ public class MyParticleDescriptionProvider extends ParticleDescriptionProvider { } ``` -不要忘了向 `GatherDataEvent` 添加提供器: +Don't forget to add the provider to the `GatherDataEvent`: ```java @SubscribeEvent @@ -175,9 +175,9 @@ public static void gatherData(GatherDataEvent event) { } ``` -### 自定义 `ParticleType` +### Custom `ParticleType`s -虽然在大多数情况下,`SimpleParticleType`就足够了,但有时需要在服务器端为粒子附加额外的数据。这就需要一个自定义的`ParticleType`和一个关联的自定义`ParticleOptions`。让我们从`ParticleOptions`开始,因为这是实际存储信息的地方: +While for most cases `SimpleParticleType` suffices, it is sometimes necessary to attach additional data to the particle on the server side. This is where a custom `ParticleType` and an associated custom `ParticleOptions` are required. Let's start with the `ParticleOptions`, as that is where the information is actually stored: ```java public class MyParticleOptions implements ParticleOptions { @@ -214,7 +214,7 @@ public class MyParticleOptions implements ParticleOptions { } ``` -然后我们在我们的自定义`ParticleType`中使用这个`ParticleOptions`实现... +We then use this `ParticleOptions` implementation in our custom `ParticleType`... ```java public class MyParticleType extends ParticleType { @@ -237,7 +237,7 @@ public class MyParticleType extends ParticleType { } ``` -... 并在注册过程中引用它: +... and reference it during registration: ```java public static final Supplier MY_CUSTOM_PARTICLE = PARTICLE_TYPES.register( @@ -245,13 +245,13 @@ public static final Supplier MY_CUSTOM_PARTICLE = PARTICLE_TYPES () -> new MyParticleType(false)); ``` -## 生成粒子 +## Spawning Particles -作为之前的提醒,服务器只知道`ParticleType`和`ParticleOption`,而客户端直接使用与`ParticleType`关联的`ParticleProvider`提供的`Particle`。因此,生成粒子的方式根据你所在的方面有很大的不同。 +As a reminder from before, the server only knows `ParticleType`s and `ParticleOption`s, while the client works directly with `Particle`s provided by `ParticleProvider`s that are associated with a `ParticleType`. Consequently, the ways in which particles are spawned are vastly different depending on the side you are on. -- **通用代码**:调用`Level#addParticle`或`Level#addAlwaysVisibleParticle`。这是创建对所有人都可见的粒子的首选方式。 -- **客户端代码**:使用通用代码方式。或者,选择你喜欢的粒子类创建一个`new Particle()`,并用那个粒子调用`Minecraft.getInstance().particleEngine#add(Particle)`。注意,这种方式添加的粒子只会显示给客户端,因此其他玩家看不到。 -- **服务器代码**:调用`ServerLevel#sendParticles`。在原版中被`/particle`命令使用。 +- **Common code**: Call `Level#addParticle` or `Level#addAlwaysVisibleParticle`. This is the preferred way of creating particles that are visible to everyone. +- **Client code**: Use the common code way. Alternatively, create a `new Particle()` with the particle class of your choice and call `Minecraft.getInstance().particleEngine#add(Particle)` with that particle. Note that particles added this way will only display for the client and thus not be visible to other players. +- **Server code**: Call `ServerLevel#sendParticles`. Used in vanilla by the `/particle` command. [datagen]: ../index.md#data-generation [event]: ../../concepts/events.md diff --git a/docs/resources/client/sounds.md b/docs/resources/client/sounds.md index 48c46f082..c3e7d69f7 100644 --- a/docs/resources/client/sounds.md +++ b/docs/resources/client/sounds.md @@ -1,24 +1,24 @@ -# 声音 +# Sounds -虽然声音对于任何事情都不是必需的,但它们可以使模组感觉更加细腻和生动。Minecraft为你提供了各种注册和播放声音的方式,本文将对此进行说明。 +Sounds, while not required for anything, can make a mod feel much more nuanced and alive. Minecraft offers you various ways to register and play sounds, which will be laid out in this article. -## 术语 +## Terminology -Minecraft的声音引擎使用多种术语来指代不同的事物: +The Minecraft sound engine uses a variety of terms to refer to different things: -- **声音事件**:声音事件是一个在代码中的触发器,它告诉声音引擎播放某个特定的声音。`SoundEvent`也是你需要注册到游戏中的东西。 -- **声音类别**或**声音源**:声音类别是声音的粗略分组,可以单独切换。声音选项GUI中的滑块代表这些类别:`master`(主音量)、`block`(方块)、`player`(玩家)等等。在代码中,它们可以在`SoundSource`枚举中找到。 -- **声音定义**:将声音事件映射到一个或多个声音对象的映射,加上一些可选的元数据。声音定义位于命名空间的[`sounds.json`文件][soundsjson]中。 -- **声音对象**:由声音文件位置加上一些可选的元数据组成的JSON对象。 -- **声音文件**:磁盘上的声音文件。Minecraft仅支持`.ogg`格式的声音文件。 +- **Sound event**: A sound event is an in-code trigger that tells the sound engine to play a certain sound. `SoundEvent`s are also the things you register to the game. +- **Sound category** or **sound source**: Sound categories are rough groupings of sounds that can be individually toggled. The sliders in the sound options GUI represent these categories: `master`, `block`, `player` etc. In code, they can be found in the `SoundSource` enum. +- **Sound definition**: A mapping of a sound event to one or multiple sound objects, plus some optional metadata. Sound definitions are located in a namespace's [`sounds.json` file][soundsjson]. +- **Sound object**: A JSON object consisting of a sound file location, plus some optional metadata. +- **Sound file**: An on-disk sound file. Minecraft only supports `.ogg` sound files. :::danger -由于OpenAL(Minecraft的音频库)的实现方式,为了让你的声音具有衰减效果——即根据玩家与声源的距离声音变小或变大——你的声音文件必须是单声道(单通道)。立体声(多通道)声音文件不会受到衰减的影响,并且总是在玩家的位置播放,这使它们成为环境声音和背景音乐的理想选择。也看看[MC-146721][bug]。 +Due to the implementation of OpenAL (Minecraft's audio library), for your sound to have attenuation - that is, for it to get quieter and louder depending on the player's distance to it -, your sound file must be mono (single channel). Stereo (multichannel) sound files will not be subject to attenuation and always play at the player's location, making them ideal for ambient sounds and background music. See also [MC-146721][bug]. ::: -## 创建`SoundEvent` +## Creating `SoundEvent`s -`SoundEvent`是[注册对象][registration],意味着它们必须通过`DeferredRegister`注册到游戏中,并且是单例的: +`SoundEvent`s are [registered objects][registration], meaning that they must be registered to the game through a `DeferredRegister` and be singletons: ```java public class MySoundsClass { @@ -41,7 +41,7 @@ public class MySoundsClass { } ``` -当然,不要忘记在[模组构造器][modctor]中将你的注册表添加到[模组事件总线][modbus]中: +Of course, don't forget to add your registry to the [mod event bus][modbus] in the [mod constructor][modctor]: ```java public ExampleMod(IEventBus modBus) { @@ -50,13 +50,13 @@ public ExampleMod(IEventBus modBus) { } ``` -然后,你就有了一个声音事件! +And voilà, you have a sound event! ## `sounds.json` -_另见:[Minecraft Wiki][mcwiki]上的[sounds.json][mcwikisounds]_ +_See also: [sounds.json][mcwikisounds] on the [Minecraft Wiki][mcwiki]_ -现在,为了将你的声音事件连接到实际的声音文件,我们需要创建声音定义。一个命名空间的所有声音定义都存储在一个名为`sounds.json`的文件中,也就是声音定义文件,直接放在命名空间的根目录下。每个声音定义都是声音事件id(如`my_sound`)到JSON声音对象的映射。注意,声音事件id不指定命名空间,因为这已经由声音定义文件所在的命名空间确定。一个示例的`sounds.json`看起来像这样: +Now, to connect your sound event to actual sound files, we need to create sound definitions. All sound definitions for a namespace are stored in a single file named `sounds.json`, also known as the sound definitions file, directly in the namespace's root. Every sound definition is a mapping of sound event id (e.g. `my_sound`) to a JSON sound object. Note that the sound event ids do not specify a namespace, as that is already determined by the namespace the sound definitions file is in. An example `sounds.json` would look something like this: ```json5 { @@ -106,11 +106,11 @@ _另见:[Minecraft Wiki][mcwiki]上的[sounds.json][mcwikisounds]_ } ``` -### 合并 +### Merging -与大多数其他资源文件不同,`sounds.json`文件不会覆盖它们下面的包中的值。相反,它们被合并在一起,然后解释为一个组合的`sounds.json`文件。考虑在两个不同资源包RP1和RP2中的两个`sounds.json`文件里定义了声音`sound_1`、`sound_2`、`sound_3`和`sound_4`,其中RP2位于RP1下面: +Unlike most other resource files, `sounds.json` do not overwrite values in packs below them. Instead, they are merged together and then interpreted as one combined `sounds.json` file. Consider sounds `sound_1`, `sound_2`, `sound_3` and `sound_4` being defined in two `sounds.json` files from two different resource packs RP1 and RP2, where RP2 is placed below RP1: -RP1中的`sounds.json`: +`sounds.json` in RP1: ```json5 { @@ -139,7 +139,7 @@ RP1中的`sounds.json`: } ``` -RP2中的`sounds.json`: +`sounds.json` in RP2: ```json5 { @@ -168,7 +168,7 @@ RP2中的`sounds.json`: } ``` -游戏最终会使用的组合(合并)的`sounds.json`文件,在内存中看起来会像这样(这个文件从不会被写在任何地方): +The combined (merged) `sounds.json` file the game would then go on and use to load sounds would look something look this (only in memory, this file is never written anywhere): ```json5 { @@ -202,43 +202,43 @@ RP2中的`sounds.json`: } ``` -## 播放声音 +## Playing Sounds -Minecraft提供了各种播放声音的方法,有时不清楚应该使用哪一个。所有的方法都接受一个`SoundEvent`,可以是你自己的,也可以是原版的(原版声音事件可以在`SoundEvents`类中找到)。以下的方法描述,客户端和服务器分别指的是[逻辑客户端和逻辑服务器][sides]。 +Minecraft offers various methods to play sounds, and it is sometimes unclear which one should be used. All methods accept a `SoundEvent`, which can either be your own or a vanilla one (vanilla sound events are found in the `SoundEvents` class). For the following method descriptions, client and server refer to the [logical client and logical server][sides], respectively. ### `Level` - `playSound(Player player, double x, double y, double z, SoundEvent soundEvent, SoundSource soundSource, float volume, float pitch)` - - 客户端行为:如果传入的玩家是本地玩家,则在给定位置为玩家播放声音事件,否则无操作。 - - 服务器行为:向所有除传入的玩家以外的玩家发送一个数据包,指示客户端在给定位置为玩家播放声音事件。 - - 用法:从将在两侧运行的客户端启动的代码中调用。服务器不会对发起播放的玩家播放声音,以防止对他们播放两次声音事件。或者,从服务器启动的代码(如[block entity][be])中调用,并使用`null`作为玩家,对所有人播放声音。 + - Client behavior: If the player passed in is the local player, play the sound event to the player at the given location, otherwise no-op. + - Server behavior: A packet instructing the client to play the sound event to the player at the given location is sent to all players except the one passed in. + - Usage: Call from client-initiated code that will run on both sides. The server not playing it to the initiating player prevents playing the sound event twice to them. Alternatively, call from server-initiated code (e.g. a [block entity][be]) with a `null` player to play the sound to everyone. - `playSound(Player player, BlockPos pos, SoundEvent soundEvent, SoundSource soundSource, float volume, float pitch)` - - 转发到第一个方法,其中`x`、`y`和`z`分别取`pos.getX() + 0.5`、`pos.getY() + 0.5`和`pos.getZ() + 0.5`的值。 + - Forwards to the first method with `x`, `y` and `z` taking the values of `pos.getX() + 0.5`, `pos.getY() + 0.5` and `pos.getZ() + 0.5`, respectively. - `playLocalSound(double x, double y, double z, SoundEvent soundEvent, SoundSource soundSource, float volume, float pitch, boolean distanceDelay)` - - 客户端行为:在给定位置为玩家播放声音。不向服务器发送任何内容。如果`distanceDelay`是`true`,则根据距离玩家的距离延迟声音。 - - 服务器行为:无操作。 - - 用法:从服务器发送的自定义数据包中调用。原版用这个方法播放雷声。 + - Client behavior: Plays the sound to the player at the given location. Does not send anything to the server. If `distanceDelay` is `true`, delays the sound based on the distance to the player. + - Server behavior: No-op. + - Usage: Called from custom packets sent from the server. Vanilla uses this for thunder sounds. ### `ClientLevel` - `playLocalSound(BlockPos pos, SoundEvent soundEvent, SoundSource soundSource, float volume, float pitch, boolean distanceDelay)` - - 转发到`Level#playLocalSound`,其中`x`、`y`和`z`分别取`pos.getX() + 0.5`、`pos.getY() + 0.5`和`pos.getZ() + 0.5`的值。 + - Forwards to `Level#playLocalSound` with `x`, `y` and `z` taking the values of `pos.getX() + 0.5`, `pos.getY() + 0.5` and `pos.getZ() + 0.5`, respectively. ### `Entity` - `playSound(SoundEvent soundEvent, float volume, float pitch)` - - 转发到`Level#playSound`,其中玩家为`null`,声音源为`SoundSource.ENTITY`,实体的位置为x/y/z,其他参数为传入的参数。 + - Forwards to `Level#playSound` with `null` as the player, `SoundSource.ENTITY` as the sound source, the entity's position for x/y/z, and the other parameters passed in. ### `Player` -- `playSound(SoundEvent soundEvent, float volume, float pitch)` (覆盖`Entity`中的方法) - - 转发到`Level#playSound`,其中玩家为`this`,声音源为`SoundSource.PLAYER`,玩家的位置为x/y/z,其他参数为传入的参数。因此,客户端/服务器的行为模仿`Level#playSound`: - - 客户端行为:在给定位置为客户端玩家播放声音事件。 - - 服务器行为:除了调用此方法的玩家,对给定位置附近的所有人播放声音事件。 +- `playSound(SoundEvent soundEvent, float volume, float pitch)` (overrides the method in `Entity`) + - Forwards to `Level#playSound` with `this` as the player, `SoundSource.PLAYER` as the sound source, the player's position for x/y/z, and the other parameters passed in. As such, the client/server behavior mimics the one from `Level#playSound`: + - Client behavior: Play the sound event to the client player at the given location. + - Server behavior: Play the sound event to everyone near the given location except the player this method was called on. -## 数据生成 +## Datagen -声音文件本身当然不能被[数据生成][datagen],但是`sounds.json`文件可以。为了做到这一点,我们扩展`SoundDefinitionsProvider`并覆盖`registerSounds()`方法: +Sound files themselves can of course not be [datagenned][datagen], but `sounds.json` files can. To do so, we extend `SoundDefinitionsProvider` and override the `registerSounds()` method: ```java public class MySoundDefinitionsProvider extends SoundDefinitionsProvider { @@ -283,7 +283,7 @@ public class MySoundDefinitionsProvider extends SoundDefinitionsProvider { } ``` -与所有数据提供器一样,不要忘记将提供者注册到事件中: +As with every data provider, don't forget to register the provider to the event: ```java @SubscribeEvent diff --git a/docs/resources/client/textures.md b/docs/resources/client/textures.md index 896eb020c..f9579a2c6 100644 --- a/docs/resources/client/textures.md +++ b/docs/resources/client/textures.md @@ -1,12 +1,12 @@ -# 纹理 +# Textures -Minecraft中的所有纹理都是PNG文件,位于命名空间的`textures`文件夹内。不支持JPG、GIF和其他图像格式。指向纹理的[资源位置][rl]的路径通常相对于`textures`文件夹,所以例如,资源位置`examplemod:block/example_block`指的是`assets/examplemod/textures/block/example_block.png`路径的纹理文件。 +All textures in Minecraft are PNG files located within a namespace's `textures` folder. JPG, GIF and other image formats are not supported. The path of [resource locations][rl] referring to textures is generally relative to the `textures` folder, so for example, the resource location `examplemod:block/example_block` refers to the texture file at `assets/examplemod/textures/block/example_block.png`. -纹理的大小通常应该是2的幂,例如16x16或32x32。与旧版本不同,现代Minecraft本身就支持大于16x16的方块和物品纹理大小。对于那些你自己渲染出来的不是2的幂的纹理(例如GUI背景),可以在下一可用的2的幂大小(通常是256x256)创建一个空文件,然后在该文件的左上角添加你的纹理,让文件的其余部分保持空白。然后,可以在使用该纹理的代码中设置实际的纹理大小。 +Textures should generally be in sizes that are powers of two, for example 16x16 or 32x32. Unlike older versions, modern Minecraft natively supports block and item texture sizes greater than 16x16. For textures that are not in powers of two that you render yourself anyway (for example GUI backgrounds), create an empty file in the next available power-of-two size (often 256x256), and add your texture in the top left corner of that file, leaving the rest of the file empty. The actual size of the drawn texture can then be set in the code that uses the texture. -## 纹理元数据 +## Texture Metadata -纹理元数据可以在一个与纹理完全同名的文件中指定,但需要添加一个`.mcmeta`后缀。例如,位于`textures/block/example.png`的动画纹理需要一个伴随的`textures/block/example.png.mcmeta`文件。`.mcmeta`文件有以下格式(所有的都是可选的): +Texture metadata can be specified in a file named exactly the same as the texture, with an additional `.mcmeta` suffix. For example, an animated texture at `textures/block/example.png` would need an accompanying `textures/block/example.png.mcmeta` file. The `.mcmeta` file has the following format (all optional): ```json5 { @@ -45,11 +45,11 @@ Minecraft中的所有纹理都是PNG文件,位于命名空间的`textures`文 } ``` -## 动画纹理 +## Animated Textures -Minecraft本身支持方块和物品的动画纹理。动画纹理由一个纹理文件组成,不同的动画阶段位于彼此的下方(例如,一个带有8个阶段的动画16x16纹理将通过一个16x128的PNG文件表示)。 +Minecraft natively supports animated textures for blocks and items. Animated textures consist of a texture file where the different animation stages are located below each other (for example, an animated 16x16 texture with 8 phases would be represented through a 16x128 PNG file). -为了确实被动画化而不仅仅是显示为扭曲的纹理,纹理元数据中必须有一个`animation`对象。子对象可以是空的,但可以包含以下可选条目: +To actually be animated and not just be displayed as a distorted texture, there must be an `animation` object in the texture metadata. The sub-object can be empty, but may contain the following optional entries: ```json5 { diff --git a/docs/resources/index.md b/docs/resources/index.md index 57215a819..e6f610974 100644 --- a/docs/resources/index.md +++ b/docs/resources/index.md @@ -1,91 +1,87 @@ -# 资源 +# Resources -资源是游戏使用的外部文件,但不包括代码。在 Minecraft 生态系统中,最常见的资源类型是纹理,但还有许多其他类型的资源。当然,所有这些资源都需要代码端的消费系统,因此本节也将对这些系统进行分组。 +Resources are external files that are used by the game, but are not code. The most prominent kinds of resources are textures, however, many other types of resources exist in the Minecraft ecosystem. Of course, all these resources require a consumer on the code side, so the consuming systems are grouped in this section as well. -Minecraft 通常有两种资源:客户端资源,称为资产,以及服务器端资源,称为数据。资产主要是显示信息,例如纹理、显示模型、翻译或声音,而数据包括影响游戏玩法的各种内容,如战利品表、配方或世界生成信息。它们分别从资源包和数据包中加载。NeoForge 为每个模组生成内置的资源包和数据包。 +Minecraft generally has two kinds of resources: client-side resources, known as assets, and server-side resources, known as data. Assets are mostly display-only information, for example textures, display models, translations, or sounds, while data includes various things that affect gameplay, such as loot tables, recipes, or worldgen information. They are loaded from resource packs and data packs, respectively. NeoForge generates a built-in resource and data pack for every mod. -无论资源包还是数据包,通常都需要一个 [`pack.mcmeta` 文件][packmcmeta],在过去的 Forge 版本中也是如此。然而,NeoForge 为您在运行时生成了这些文件,因此您无需再担心。 +Both resource and data packs normally require a [`pack.mcmeta` file][packmcmeta], this was also the case in past Forge versions. However, NeoForge generates these at runtime for you, so you don't need to worry about it anymore. -如果您对某个格式感到困惑,请查看原版资源。您的 NeoForge 开发环境不仅包含原版代码,还包含原版资源。它们可以在 External Resources 部分(IntelliJ)/Project Libraries 部分(Eclipse)中找到,名称为 `ng_dummy_ng.net.minecraft:client:client-extra:`(对于 Minecraft 资源)或 `ng_dummy_ng.net.neoforged:neoforge:`(对于 NeoForge 资源)。 +If you are confused about the format of something, have a look at the vanilla resources. Your NeoForge development environment not only contains vanilla code, but also vanilla resources. They can be found in the External Resources section (IntelliJ)/Project Libraries section (Eclipse), under the name `ng_dummy_ng.net.minecraft:client:client-extra:` (for Minecraft resources) or `ng_dummy_ng.net.neoforged:neoforge:` (for NeoForge resources). -## 资产 +## Assets -_另见:[Minecraft Wiki 上的资源包][mcwikiresourcepacks]_ +_See also: [Resource Packs][mcwikiresourcepacks] on the [Minecraft Wiki][mcwiki]_ -资产,或客户端资源,是所有仅在[客户端][sides]上相关的资源。它们从资源包中加载,有时也被称为旧术语纹理包(源自旧版本,当时它们只能影响纹理)。资源包基本上是一个 `assets` 文件夹。`assets` 文件夹包含资源包包含的各种命名空间的子文件夹;每个命名空间是一个子文件夹。例如,一个模组的资源包可能包含 `coolmod` 命名空间,但可能还包括其他命名空间,例如 `minecraft`。 +Assets, or client-side resources, are all resources that are only relevant on the [client][sides]. They are loaded from resource packs, sometimes also known by the old term texture packs (stemming from old versions when they could only affect textures). A resource pack is basically an `assets` folder. The `assets` folder contains subfolders for the various namespaces the resource pack includes; every namespace is one subfolder. For example, a resource pack for a mod with the id `coolmod` will probably contain a `coolmod` namespace, but may additionally include other namespaces, such as `minecraft`. -NeoForge 自动将所有模组资源包收集到 `Mod resources` 包中,该包位于资源包菜单中的 Selected Packs 边的底部。当前无法禁用 `Mod resources` 包。但是,位于 `Mod resources` 包上方的资源包可以覆盖位于其下的资源包中定义的资源。这种机制允许资源包制作者覆盖您的模组资源,并允许模组开发者覆盖 Minecraft 资源。 +NeoForge automatically collects all mod resource packs into the `Mod resources` pack, which sits at the bottom of the Selected Packs side in the resource packs menu. It is currently not possible to disable the `Mod resources` pack. However, resource packs that sit above the `Mod resources` pack override resources defined in a resource pack below them. This mechanic allows resource pack makers to override your mod's resources, and also allows mod developers to override Minecraft resources if needed. -资源包可以包含 [模型][models]、[方块状态文件][bsfile]、[纹理][textures]、[声音][sounds]、[粒子定义][particles] 和 [翻译文件][translations]。 +Resource packs can contain [models][models], [blockstate files][bsfile], [textures][textures], [sounds][sounds], [particle definitions][particles] and [translation files][translations]. -## 数据 +## Data -_另见:[Minecraft Wiki 上的数据包][mcwikidatapacks]_ +_See also: [Data Packs][mcwikidatapacks] on the [Minecraft Wiki][mcwiki]_ -与资产不同,数据是所有[服务器][sides]资源的术语。与资源包类似,数据通过数据包加载。像资源包一样,数据包由 [`pack.mcmeta` 文件][packmcmeta] 和一个名为 `data` 的根文件夹组成。然后,同样像资源包一样,`data` 文件夹包含数据包包含的各种命名空间的子文件夹;每个命名空间是一个子文件夹。例如,一个模组的数据包可能包含 `coolmod` 命名空间,但可能还包括其他命名空间,例如 `minecraft`。 +In contrast to assets, data is the term for all [server][sides] resources. Similar to resource packs, data is loaded through data packs (or datapacks). Like a resource pack, a data pack consists of a [`pack.mcmeta` file][packmcmeta] and a root folder, named `data`. Then, again like with resource packs, that `data` folder contains subfolders for the various namespaces the resource pack includes; every namespace is one subfolder. For example, a data pack for a mod with the id `coolmod` will probably contain a `coolmod` namespace, but may additionally include other namespaces, such as `minecraft`. -NeoForge 在创建新世界时自动应用所有模组数据包。当前无法禁用模组数据包。但是,大多数数据文件可以通过具有更高优先级的数据包覆 - -盖(因此可以通过替换为空文件来删除)。通过将数据包放置在世界的 `datapacks` 子文件夹中,然后通过 [`/datapack`][datapackcmd] 命令启用或禁用它们,可以启用或禁用额外的数据包。 +NeoForge automatically applies all mod data packs to a new world upon creation. It is currently not possible to disable mod data packs. However, most data files can be overridden (and thus be removed by replacing them with an empty file) by a data pack with a higher priority. Additional data packs can be enabled or disabled by placing them in a world's `datapacks` subfolder and then enabling or disabling them through the [`/datapack`][datapackcmd] command. :::info -目前没有内置的方法将一组自定义数据包应用到每个世界。然而,有许多模组可以实现这一点。 +There is currently no built-in way to apply a set of custom data packs to every world. However, there are a number of mods that achieve this. ::: -数据包可能包含影响以下事物的文件夹: +Data packs may contain folders with files affecting the following things: -| 文件夹名称 | 内容 | -|---------|------| -| `advancements` | [进度][advancements] | -| `damage_type` | 伤害类型 | -| `loot_tables` | [战利品表][loottables] | -| `recipes` | [配方][recipes] | -| `structures` | 结构 | -| `tags` | [标签][tags] | -| `dimension`, `dimension_type`, `worldgen`, `neoforge/biome_modifiers` | 世界生成文件 | -| `neoforge/global_loot_modifiers` | [全局战利品修饰器][glm] | +| Folder name | Contents | +|-----------------------------------------------------------------------|------------------------------| +| `advancements` | [Advancements][advancements] | +| `damage_type` | Damage types | +| `loot_tables` | [Loot tables][loottables] | +| `recipes` | [Recipes][recipes] | +| `structures` | Structures | +| `tags` | [Tags][tags] | +| `dimension`, `dimension_type`, `worldgen`, `neoforge/biome_modifiers` | Worldgen files | +| `neoforge/global_loot_modifiers` | [Global loot modifiers][glm] | -此外,它们还可能包含一些与命令集成的系统的子文件夹。这些系统很少与模组一起使用,但无论如何都值得一提: +Additionally, they may also contain subfolders for some systems that integrate with commands. These systems are rarely used in conjunction with mods, but worth mentioning regardless: -| 文件夹名称 | 内容 | -|---------|------| -| `chat_type` | [聊天类型][chattype] | -| `functions` | [功能][function] | -| `item_modifiers` | [物品修饰器][itemmodifier] | -| `predicates` | [条件判断][predicate] | +| Folder name | Contents | +|------------------|--------------------------------| +| `chat_type` | [Chat types][chattype] | +| `functions` | [Functions][function] | +| `item_modifiers` | [Item modifiers][itemmodifier] | +| `predicates` | [Predicates][predicate] | ## `pack.mcmeta` -_另见:[Minecraft Wiki 上的 `pack.mcmeta` (资源包)][packmcmetaresourcepack] 和 `pack.mcmeta` (数据包)][packmcmetadatapack]_ - -`pack.mcmeta` 文件保存资源包或数据包的元数据。对于模组来说,NeoForge 让这个文件变得多余,因为 `pack.mcmeta` 是合成生成的。如果您仍需要一个 `pack.mcmeta` 文件,完整的规范可以在链接的 Minecraft Wiki 文章中找到。 +_See also: [`pack.mcmeta` (Resource Pack)][packmcmetaresourcepack] and [`pack.mcmeta` (Data Pack)][packmcmetadatapack] on the [Minecraft Wiki][mcwiki]_ -## 数据生成 +`pack.mcmeta` files hold the metadata of a resource or data pack. For mods, NeoForge makes this file obsolete, as the `pack.mcmeta` is generated synthetically. In case you still need a `pack.mcmeta` file, the full specification can be found in the linked Minecraft Wiki articles. -数据生成,俗称 datagen,是一种以编程方式生成 JSON 资源文件的方式,以避免手动编写它们时的繁琐和容易出错的过程。这个名字有点误导,因为它适用于资产和数据。 +## Data Generation -Datagen 通过为您生成的客户端和服务器运行配置旁的数据运行配置来运行。数据运行配置遵循[模组生命周期][lifecycle],直到注册事件触发之后。然后触发 [`GatherDataEvent`][event],在该事件中,您可以注册您要生成的对象,以数据提供者的形式,将所述对象写入磁盘,并结束过程。 +Data generation, colloquially known as datagen, is a way to programmatically generate JSON resource files, in order to avoid the tedious and error-prone process of writing them by hand. The name is a bit misleading, as it works for assets as well as data. -所有数据提供者都扩展了 `DataProvider` 接口,通常需要重写一个方法。以下是 Minecraft 和 NeoForge 提供的一些值得注意的数据生成器(链接文章提供了更多信息,如辅助方法): +Datagen is run through the Data run configuration, which is generated for you alongside the Client and Server run configurations. The data run configuration follows the [mod lifecycle][lifecycle] until after the registry events are fired. It then fires the [`GatherDataEvent`][event], in which you can register your to-be-generated objects in the form of data providers, writes said objects to disk, and ends the process. -| 类 | 方法 | 生成 | 方面 | 备注 | -|---|----|-----|----|----| -| [`BlockStateProvider`][blockstateprovider] | `registerStatesAndModels()` | 方块状态文件,方块模型 | 客户端 | | -| [`ItemModelProvider`][itemmodelprovider] | `registerModels()` | 物品模型 | 客户端 | | -| [`LanguageProvider`][langprovider] | `addTranslations()` | 翻译 | 客户端 | 构造函数还需要传递语言。 | -| [`ParticleDescriptionProvider`][particleprovider] | `addDescriptions()` | 粒子定义 | 客户端 | | -| [`SoundDefinitionsProvider`][soundprovider] | `registerSounds()` | 声音定义 | 客户端 | | -| [`AdvancementProvider`][advancementprovider] | `generate()` | 进度 | 服务器 | 确保使用 NeoForge 变体,而不是 Minecraft 本身 +All data providers extend the `DataProvider` interface and usually require one method to be overridden. The following is a list of noteworthy data generators Minecraft and NeoForge offer (the linked articles add further information, such as helper methods): -。 | -| [`LootTableProvider`][loottableprovider] | `generate()` | 战利品表 | 服务器 | 需要额外的方法和类才能正常工作,详情请参阅链接文章。 | -| [`RecipeProvider`][recipeprovider] | `buildRecipes(RecipeOutput)` | 配方 | 服务器 | | -| [多个 `TagsProvider` 的子类][tagsprovider] | `addTags(HolderLookup.Provider)` | 标签 | 服务器 | 存在几个专门的子类,例如 `BlockTagsProvider`。如果您需要的不存在,请扩展 `TagsProvider`(或适用时扩展 `IntrinsicHolderTagsProvider`),将您的标签类型作为泛型参数。 | -| [`DatapackBuiltinEntriesProvider`][datapackprovider] | N/A | 数据包内置条目,例如世界生成 | 服务器 | 详情请参阅链接文章。 | -| [`DataMapProvider`][datamapprovider] | `gather()` | 数据映射条目 | 服务器 | | -| [`GlobalLootModifierProvider`][glmprovider] | `start()` | 全局战利品修饰器 | 服务器 | | +| Class | Method | Generates | Side | Notes | +|------------------------------------------------------|----------------------------------|-----------------------------------------|--------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| [`BlockStateProvider`][blockstateprovider] | `registerStatesAndModels()` | Blockstate files, block models | Client | | +| [`ItemModelProvider`][itemmodelprovider] | `registerModels()` | Item models | Client | | +| [`LanguageProvider`][langprovider] | `addTranslations()` | Translations | Client | Also requires passing the language in the constructor. | +| [`ParticleDescriptionProvider`][particleprovider] | `addDescriptions()` | Particle definitions | Client | | +| [`SoundDefinitionsProvider`][soundprovider] | `registerSounds()` | Sound definitions | Client | | +| [`AdvancementProvider`][advancementprovider] | `generate()` | Advancements | Server | Make sure to use the NeoForge variant, not the Minecraft one. | +| [`LootTableProvider`][loottableprovider] | `generate()` | Loot tables | Server | Requires extra methods and classes to work properly, see linked article for details. | +| [`RecipeProvider`][recipeprovider] | `buildRecipes(RecipeOutput)` | Recipes | Server | | +| [Various subclasses of `TagsProvider`][tagsprovider] | `addTags(HolderLookup.Provider)` | Tags | Server | Several specialized subclasses exist, for example `BlockTagsProvider`. If the one you need doesn't exist, extend `TagsProvider` (or `IntrinsicHolderTagsProvider` if applicable) with your tag type as the generic parameter. | +| [`DatapackBuiltinEntriesProvider`][datapackprovider] | N/A | Datapack builtin entries, e.g. worldgen | Server | See linked article for details. | +| [`DataMapProvider`][datamapprovider] | `gather()` | Data map entries | Server | | +| [`GlobalLootModifierProvider`][glmprovider] | `start()` | Global loot modifiers | Server | | -所有这些提供者都遵循相同的模式。首先,创建一个子类并添加您自己要生成的资源。然后,在[事件处理器][eventhandler]中添加提供者。使用 `RecipeProvider` 的一个示例: +All of these providers follow the same pattern. First, you create a subclass and add your own resources to be generated. Then, you add the provider to the event in an [event handler][eventhandler]. An example using a `RecipeProvider`: ```java public class MyRecipeProvider extends RecipeProvider { @@ -95,7 +91,7 @@ public class MyRecipeProvider extends RecipeProvider { @Override protected void buildRecipes(RecipeOutput output) { - // 在这里注册您的配方。 + // Register your recipes here. } } @@ -103,74 +99,72 @@ public class MyRecipeProvider extends RecipeProvider { public class MyDatagenHandler { @SubscribeEvent public static void gatherData(GatherDataEvent event) { - // 数据生成器可能需要这些作为构造函数参数。 - // 详情请参阅下文每个部分。 + // Data generators may require some of these as constructor parameters. + // See below for more details on each of these. DataGenerator generator = event.getGenerator(); PackOutput output = generator.getPackOutput(); ExistingFileHelper existingFileHelper = event.getExistingFileHelper(); CompletableFuture lookupProvider = event.getLookupProvider(); - // 注册提供者。 + // Register the provider. generator.addProvider( - // 一个布尔值,确定是否实际生成数据。 - // 事件提供了确定这一点的方法: + // A boolean that determines whether the data should actually be generated. + // The event provides methods that determine this: // event.includeClient(), event.includeServer(), - // event.includeDev() 和 event.includeReports()。 - // 由于配方是服务器数据,我们只在服务器数据生成中运行它们。 + // event.includeDev() and event.includeReports(). + // Since recipes are server data, we only run them in a server datagen. event.includeServer(), - // 我们的提供者。 + // Our provider. new MyRecipeProvider(output) ); - // 在这里注册其他数据提供者。 + // Other data providers here. } } ``` -事件提供了一些上下文供您使用: - -- `event.getGenerator()` 返回您注册提供者的 `DataGenerator`。 -- `event.getPackOutput()` 返回 `PackOutput`,一些提供者用它来确定文件输出位置。 -- `event.getExistingFileHelper()` 返回 `ExistingFileHelper`,用于提供者需要引用其他文件的事物(例如,可以指定父文件的方块模型)。 -- `event.getLookupProvider()` 返回 `CompletableFuture`,主要用于标签和数据生成注册表引用其他尚未存在的元素。 -- `event.includeClient()`、`event.includeServer()`、`event.includeDev()` 和 `event.includeReports()` 是 `boolean` 方法,允许您检查是否启用了特定的命令行参数。 +The event offers some context for you to use: -### 命令行参数 +- `event.getGenerator()` returns the `DataGenerator` that you register the providers to. +- `event.getPackOutput()` returns a `PackOutput` that is used by some providers to determine their file output location. +- `event.getExistingFileHelper()` returns an `ExistingFileHelper` that is used by providers for things that can reference other files (for example block models, which can specify a parent file). +- `event.getLookupProvider()` returns a `CompletableFuture` that is mainly used by tags and datagen registries to reference other, potentially not yet existing elements. +- `event.includeClient()`, `event.includeServer()`, `event.includeDev()` and `event.includeReports()` are `boolean` methods that allow you to check whether specific command line arguments (see below) are enabled. -数据生成器可以接受几个命令行参数: +### Command Line Arguments -- `--mod examplemod`: 告诉数据生成器为此模组运行数据生成。NeoGradle 为所属模组 ID 自动添加此项,如果您有多个模组在一个项目中,请添加此项。 -- `--output path/to/folder`: 告诉数据生成器输出到给定文件夹。建议使用 Gradle 的 `file(...).getAbsolutePath()` 为您生成绝对路径(相对于项目根目录的路径)。默认为 `file('src/generated/resources').getAbsolutePath()`。 -- `--existing path/to/folder`: 告诉数据生成器 +The data generator can accept several command line arguments: -在检查现有文件时考虑给定文件夹。与输出一样,建议使用 Gradle 的 `file(...).getAbsolutePath()`。 -- `--existing-mod examplemod`: 告诉数据生成器在检查现有文件时考虑给定模组的 JAR 文件中的资源。 -- 生成器模式(所有这些都是布尔参数,不需要任何额外的参数): - - `--includeClient`: 是否生成客户端资源(资产)。在运行时检查 `GatherDataEvent#includeClient()`。 - - `--includeServer`: 是否生成服务器资源(数据)。在运行时检查 `GatherDataEvent#includeServer()`。 - - `--includeDev`: 是否运行开发工具。通常不应由模组使用。在运行时检查 `GatherDataEvent#includeDev()`。 - - `--includeReports`: 是否转储注册对象列表。在运行时检查 `GatherDataEvent#includeReports()`。 - - `--all`: 启用所有生成器模式。 +- `--mod examplemod`: Tells the data generator to run datagen for this mod. Automatically added by NeoGradle for the owning mod id, add this if you e.g. have multiple mods in one project. +- `--output path/to/folder`: Tells the data generator to output into the given folder. It is recommended to use Gradle's `file(...).getAbsolutePath()` to generate an absolute path for you (with a path relative to the project root directory). Defaults to `file('src/generated/resources').getAbsolutePath()`. +- `--existing path/to/folder`: Tells the data generator to consider the given folder when checking for existing files. Like with the output, it is recommended to use Gradle's `file(...).getAbsolutePath()`. +- `--existing-mod examplemod`: Tells the data generator to consider the resources in the given mod's JAR file when checking for existing files. +- Generator modes (all of these are boolean arguments and do not need any additional arguments): + - `--includeClient`: Whether to generate client resources (assets). Check at runtime with `GatherDataEvent#includeClient()`. + - `--includeServer`: Whether to generate server resources (data). Check at runtime with `GatherDataEvent#includeServer()`. + - `--includeDev`: Whether to run dev tools. Generally shouldn't be used by mods. Check at runtime with `GatherDataEvent#includeDev()`. + - `--includeReports`: Whether to dump a list of registered objects. Check at runtime with `GatherDataEvent#includeReports()`. + - `--all`: Enable all generator modes. -所有参数可以通过在 `build.gradle` 中添加以下内容来添加到运行配置中: +All arguments can be added to the run configurations by adding the following to your `build.gradle`: ```groovy runs { - // 这里有其他运行配置 + // other run configurations here data { - programArguments.addAll '--arg1', 'value1', '--arg2', 'value2', '--all' // 布尔参数没有值 + programArguments.addAll '--arg1', 'value1', '--arg2', 'value2', '--all' // boolean args have no value } } ``` -例如,要复制默认参数,您可以指定以下内容: +For example, to replicate the default arguments, you could specify the following: ```groovy runs { - // 这里有其他运行配置 + // other run configurations here data { - programArguments.addAll '--mod', 'examplemod', // 插入您自己的模组 ID + programArguments.addAll '--mod', 'examplemod', // insert your own mod id '--output', file('src/generated/resources').getAbsolutePath(), '--includeClient', '--includeServer' @@ -202,7 +196,7 @@ runs { [mcwikidatapacks]: https://minecraft.wiki/w/Data_pack [mcwikiresourcepacks]: https://minecraft.wiki/w/Resource_pack [models]: client/models/index.md -[packmcmeta]: #pack.mcmeta +[packmcmeta]: #packmcmeta [packmcmetadatapack]: https://minecraft.wiki/w/Data_pack#pack.mcmeta [packmcmetaresourcepack]: https://minecraft.wiki/w/Resource_pack#Contents [particleprovider]: client/particles.md#datagen diff --git a/docs/resources/server/advancements.md b/docs/resources/server/advancements.md index b5e4d59e5..5cb920bf5 100644 --- a/docs/resources/server/advancements.md +++ b/docs/resources/server/advancements.md @@ -1,14 +1,14 @@ -# 成就 +# Advancements -成就是玩家可以完成的任务,可能会推进游戏的进程。玩家可能直接参与的任何动作都可以触发成就。 +Advancements are tasks that can be achieved by the player which may advance the progress of the game. Advancements can trigger based on any action the player may be directly involved in. -所有原版内的成就实现都通过JSON进行数据驱动。这意味着创建新的成就不需要mod,只需要一个[data pack][datapack]。关于如何创建并将这些成就放入mod的`resources`的完整列表,可以在[Minecraft Wiki][wiki]上找到。此外,根据存在的信息(加载的模组,存在的项目等),成就可以有条件地[加载和默认][conditional]。和其他数据驱动的功能一样,成就可以通过[data generators][datagen]来生成。 +All advancement implementations within vanilla are data driven via JSON. This means that a mod is not necessary to create a new advancement, only a [data pack][datapack]. A full list on how to create and put these advancements within the mod's `resources` can be found on the [Minecraft Wiki][wiki]. Additionally, advancements can be [loaded conditionally and defaulted][conditional] depending on what information is present (mod loaded, item exists, etc.). As with other data driven features, advancements can be generated via [data generators][datagen]. -## 成就条件 +## Advancement Criteria -为了解锁一个成就,必须满足指定的条件。条件通过触发器进行跟踪,当执行某个动作时触发:杀死实体,改变库存,繁殖动物等。每当一个成就被加载到游戏中,定义的条件就会被读取并添加为触发器的监听器。然后调用一个触发器函数(通常命名为`#trigger`),检查所有监听器是否当前状态满足成就条件。只有在完成所有要求并获得成就后,成就的条件监听器才会被移除。 +To unlock an advancement, the specified criteria must be met. Criteria are tracked through triggers which execute when a certain action is performed: killing an entity, changing an inventory, breading animals, etc. Any time an advancement is loaded into the game, the criteria defined are read and added as listeners to the trigger. Afterwards a trigger function is called (usually named `#trigger`) which checks all listeners as to whether the current state meets the conditions of the advancement criteria. The criteria listeners for the advancement are only removed once the advancement has been obtained by completing all requirements. -要求被定义为一个字符串数组的数组,表示在成就上指定的条件的名称。一旦满足一个条件的字符串数组,成就就完成了: +Requirements are defined as an array of string arrays representing the name of the criteria specified on the advancement. An advancement is completed once one string array of criteria has been met: ```js // In some advancement JSON @@ -37,17 +37,17 @@ ] ``` -原版定义的条件触发器列表可以在`CriteriaTriggers`中找到。此外,JSON格式在[Minecraft Wiki][triggers]上有定义。 +A list of criteria triggers defined by vanilla can be found in `CriteriaTriggers`. Additionally, the JSON formats are defined on the [Minecraft Wiki][triggers]. -### 自定义条件触发器 +### Custom Criteria Triggers -自定义条件触发器由两部分组成:触发器,它在你指定的某个时候在代码中被激活,通过调用`#trigger`,和实例,它定义了触发器应在何种条件下授予条件。触发器扩展了`SimpleCriterionTrigger`,而实例实现了`SimpleCriterionTrigger.SimpleInstance`。泛型值`T`代表触发器实例类型。 +Custom criteria triggers are made up of two parts: the trigger, which is activated in code at some point you specify by calling `#trigger`, and the instance which defines the conditions under which the trigger should award the criterion. The trigger extends `SimpleCriterionTrigger` while the instance implements `SimpleCriterionTrigger.SimpleInstance`. The generic value `T` represents the trigger instance type. -### The SimpleCriterionTrigger.SimpleInstance实现 +### The SimpleCriterionTrigger.SimpleInstance Implementation -`SimpleCriterionTrigger.SimpleInstance`代表在`criteria`对象中定义的单个条件。触发器实例负责保存定义的条件,并返回输入是否匹配条件。 +The `SimpleCriterionTrigger.SimpleInstance` represents a single criteria defined in the `criteria` object. Trigger instances are responsible for holding the defined conditions, and returning whether the inputs match the condition. -条件通常通过构造函数传入。`SimpleCriterionTrigger.SimpleInstance`接口只需要一个函数,名为`#player`,它返回玩家必须满足的条件,作为一个`Optional`。如果子类是一个Java记录,有一个这种类型的`player`参数(如下所示),那么自动生成的`#player`方法就足够了。 +Conditions are usually passed in through the constructor. The `SimpleCriterionTrigger.SimpleInstance` interface requires only one function, called `#player`, which returns the conditions the player must meet as an `Optional`. If the subclass is a Java record with a `player` parameter of this type (as below), the automatically generated `#player` method will suffice. ```java public record ExampleTriggerInstance(Optional player, ItemPredicate item) implements SimpleCriterionTrigger.SimpleInstance { @@ -56,7 +56,7 @@ public record ExampleTriggerInstance(Optional player, Ite ``` :::note -通常,触发器实例具有静态辅助方法,这些方法可以根据实例的参数构造完整的`Criterion`对象。这使得这些实例可以在数据生成期间轻松创建,但这是可选的。 +Typically, trigger instances have static helper methods which construct the full `Criterion` object from the arguments to the instance. This allows these instances to be easily created during data generation, but are optional. ```java // In this example, EXAMPLE_TRIGGER is a DeferredHolder> @@ -66,7 +66,7 @@ public static Criterion instance(ContextAwarePredicate p ``` ::: -最后,应该添加一个方法,该方法输入当前的数据状态,并返回用户是否满足了必要的条件。玩家的条件已经通过`SimpleCriterionTrigger#trigger(ServerPlayer, Predicate)`进行了检查。大多数触发器实例将这个方法称为`#matches`。 +Finally, a method should be added which takes in the current data state and returns whether the user has met the necessary conditions. The conditions of the player are already checked through `SimpleCriterionTrigger#trigger(ServerPlayer, Predicate)`. Most trigger instances call this method `#matches`. ```java // This method is unique for each instance and is as such not overridden @@ -78,9 +78,9 @@ public boolean matches(ItemStack stack) { ### SimpleCriterionTrigger -`SimpleCriterionTrigger`子类负责指定一个编解码器来[序列化]触发器实例`T`,并提供一个方法来检查触发器实例并在成功时运行已附加的监听器。 +The `SimpleCriterionTrigger` subclass is responsible for specifying a codec to [serialize] the trigger instance `T` and supplying a method to check trigger instances and run attached listeners on success. -后者是通过定义一个方法来检查所有的触发器实例,并在满足条件时运行监听器来完成的。该方法接收`ServerPlayer`和`SimpleCriterionTrigger.SimpleInstance`子类中匹配方法定义的其他数据。该方法应内部调用`SimpleCriterionTrigger#trigger`以正确处理检查所有监听器。大多数触发器实例将这个方法称为`#trigger`。 +The latter is done by defining a method to check all trigger instances and run the listeners if their condition is met. This method takes in the `ServerPlayer` and whatever other data defined by the matching method in the `SimpleCriterionTrigger.SimpleInstance` subclass. This method should internally call `SimpleCriterionTrigger#trigger` to properly handle checking all listeners. Most trigger instances call this method `#trigger`. ```java // This method is unique for each trigger and is as such not a method to override @@ -92,11 +92,11 @@ public void trigger(ServerPlayer player, ItemStack stack) { } ``` -最后,实例必须在`Registries.TRIGGER_TYPE`注册表上注册。关于如何进行注册的技巧可以在[Registries][registration]下找到。 +Finally, instances must be registered on the `Registries.TRIGGER_TYPE` registry. Techniques for doing so can be found under [Registries][registration]. -### 序列化 +### Serialization -必须定义一个[编解码器]来序列化和反序列化触发器实例。原版通常在实例实现中创建这个编解码器作为一个常量,然后通过触发器的`#codec`方法返回。 +A [codec] must be defined to serialize and deserialize the trigger instance. Vanilla typically creates this codec as a constant within the instance implementation that is then returned by the trigger's `#codec` method. ```java @@ -113,7 +113,8 @@ class ExampleTrigger extends SimpleCriterionTrigger instance.group( ExtraCodecs.strictOptionalField(EntityPredicate.ADVANCEMENT_CODEC, "player").forGetter(ExampleTriggerInstance::player), @@ -121,9 +122,9 @@ RecordCodecBuilder.create(instance -> instance.group( ).apply(instance, ExampleTriggerInstance::new)); `````` -### 调用触发器 +### Calling the Trigger -每当执行正在检查的动作时,应该调用由`SimpleCriterionTrigger`子类定义的`#trigger`方法。 +Whenever the action being checked is performed, the `#trigger` method defined by the `SimpleCriterionTrigger` subclass should be called. ```java // In some piece of code where the action is being performed @@ -134,9 +135,9 @@ public void performExampleAction(ServerPlayer player, ItemStack stack) { } ``` -## 成就奖励 +## Advancement Rewards -当一个成就完成时,可能会给予奖励。这些奖励可以是经验点数、战利品表、食谱书中的食谱,或者作为创造者玩家执行的[函数]的组合。 +When an advancement is completed, rewards may be given out. These can be a combination of experience points, loot tables, recipes for the recipe book, or a [function] executed as a creative player. ```js // In some advancement JSON diff --git a/docs/resources/server/glm.md b/docs/resources/server/glm.md index 91058726f..b935aac05 100644 --- a/docs/resources/server/glm.md +++ b/docs/resources/server/glm.md @@ -1,34 +1,34 @@ -全局战利品修改器 (Global Loot Modifiers) +Global Loot Modifiers =========== -全局战利品修改器是一种数据驱动的方法,用于处理收获物品的修改,无需覆盖数十到数百个原版战利品表,或处理可能需要与其他模组的战利品表互动而不知道哪些模组可能已加载的效果。全局战利品修改器也是叠加的,而不是最后加载胜出的方式,类似于标签。 +Global Loot Modifiers are a data-driven method of handling modification of harvested drops without the need to overwrite dozens to hundreds of vanilla loot tables or to handle effects that would require interactions with another mod's loot tables without knowing what mods may be loaded. Global Loot Modifiers are also stacking, rather than last-load-wins, similar to tags. -注册全局战利品修改器 +Registering a Global Loot Modifier ------------------------------- -您将需要做4件事: +You will need 4 things: -1. 创建一个`global_loot_modifiers.json`。 - * 这将告诉Forge有关您的修改器,并且其工作方式类似于[标签]。 -2. 表示您的修改器的序列化json。 - * 这将包含有关您的修改的所有数据,并允许数据包调整您的效果。 -3. 一个扩展了`IGlobalLootModifier`的类。 - * 使您的修改器工作的操作代码。大多数模组开发者可以扩展`LootModifier`,因为它提供了基本功能。 -4. 最后,一个编解码器来编码和解码您的操作类。 - * 这被[注册]为任何其他`IForgeRegistryEntry`。 +1. Create a `global_loot_modifiers.json`. + * This will tell Forge about your modifiers and works similar to [tags]. +2. A serialized json representing your modifier. + * This will contain all of the data about your modification and allows data packs to tweak your effect. +3. A class that extends `IGlobalLootModifier`. + * The operational code that makes your modifier work. Most modders can extend `LootModifier` as it supplies base functionality. +4. Finally, a codec to encode and decode your operational class. + * This is [registered] as any other `IForgeRegistryEntry`. -`global_loot_modifiers.json` +The `global_loot_modifiers.json` ------------------------------- -`global_loot_modifiers.json`代表要加载到游戏中的所有战利品修改器。这个文件**必须**被放置在`data/forge/loot_modifiers/global_loot_modifiers.json`内。 +The `global_loot_modifiers.json` represents all loot modifiers to be loaded into the game. This file **MUST** be placed within `data/forge/loot_modifiers/global_loot_modifiers.json`. :::danger -`global_loot_modifiers.json`将只在`forge`命名空间下读取。如果文件位于模组的命名空间下,则会被忽略。 +`global_loot_modifiers.json` will only be read in the `forge` namespace. The file will be neglected if it is under the mod's namespace. ::: -`entries`是将要加载的修改器的*有序列表*。指定的[ResourceLocation][resloc]指向`data//loot_modifiers/.json`内的相关条目。这主要与数据包制作者相关,用于解决来自不同模组的修改器之间的冲突。 +`entries` is an *ordered list* of the modifiers that will be loaded. The [ResourceLocation][resloc]s specified points to their associated entry within `data//loot_modifiers/.json`. This is primarily relevant to data pack makers for resolving conflicts between modifiers from separate mods. -`replace`为`true`时,会将从在全局列表中追加战利品修改器的行为改为完全替换全局列表条目。模组开发者会想使用`false`来与其他模组的实现兼容。数据包制作者可能会想使用`true`来指定他们的覆盖项。 +`replace`, when `true`, changes the behavior from appending loot modifiers to the global list to replacing the global list entries entirely. Modders will want to use `false` for compatibility with other mod implementations. Datapack makers may want to specify their overrides with `true`. ```js { @@ -42,20 +42,20 @@ } ``` -序列化的JSON +The Serialized JSON ------------------------------- -此文件包含与您的修改器相关的所有可能变量,包括在修改任何战利品之前必须满足的条件。应尽可能避免硬编码的值,以便数据包制作者可以在需要时调整平衡。 +This file contains all of the potential variables related to your modifier, including the conditions that must be met prior to modifying any loot. Avoid hard-coded values wherever possible so that data pack makers can adjust balance if they wish to. -`type`代表用来读取关联的JSON文件的[编解码器]的注册名称。这必须始终存在。 +`type` represents the registry name of the [codec] used to read the associated JSON file. This must always be present. -`conditions`应该代表此修改器激活的战利品表条件。条件应尽量避免硬编码,以便数据包创建者可以尽可能灵活地调整条件。这也必须始终存在。 +`conditions` should represent the loot table conditions for this modifier to activate. Conditions should avoid being hardcoded to allow datapack creators as much flexibility to adjust the criteria. This must also be always present. :::caution -虽然`conditions`应该代表激活修改器所需的条件,但只有在使用捆绑的Forge类时才是这种情况。如果使用`LootModifier`作为子类,所有的条件将被**并在一起**,并检查是否应该应用修改器。 +Although `conditions` should represent what is needed for the modifier to activate, this is only the case if using the bundled Forge classes. If using `LootModifier` as a subclass, all conditions will be **ANDed** together and checked to see if the modifier should be applied. ::: -也可以指定由修改器定义并由序列化器读取的任何其他属性。 +Any additional properties read by the serializer and defined by the modifier can also be specified. ```js // Within data/examplemod/loot_modifiers/example_glm.json @@ -74,25 +74,25 @@ `IGlobalLootModifier` --------------------- -为了提供全局战利品修改器指定的功能,必须指定一个`IGlobalLootModifier`的实现。这些是每次序列化器从JSON解码信息并将其提供到此对象时生成的实例。 +To supply the functionality a global loot modifier specifies, a `IGlobalLootModifier` implementation must be specified. These are instances generated each time a serializer decodes the information from JSON and supplies it into this object. -为了创建一个新的修改器,需要定义两个方法:`#apply`和`#codec`。`#apply`接收将要生成的当前战利品以及上下文信息,如当前级别或额外定义的参数。它返回要生成的掉落物列表。 +There are two methods that needs to be defined in order to create a new modifier: `#apply` and `#codec`. `#apply` takes in the current loot that will be generated along with the context information such as the currently level or additional defined parameters. It returns the list of drops to generate. :::info -从任一修改器返回的掉落物列表将按照它们注册的顺序提供给其他修改器。因此,修改过的战利品可以被其他战利品修改器修改。 +The returned list of drops from any one modifier is fed into other modifiers in the order they are registered. As such, modified loot can be modified by another loot modifier. ::: -`#codec`返回已注册的[编解码器],用于将修改器编码并解码为JSON。 +`#codec` returns the registered [codec] used to encode and decode the modifier to/from JSON. -### `LootModifier`子类 +### The `LootModifier` Subclass -`LootModifier`是`IGlobalLootModifier`的一个抽象实现,它提供了大多数模组开发者可以轻松扩展和实现的基本功能。这在现有的接口上进行了扩展,通过定义`#apply`方法来检查条件,以确定是否要修改生成的战利品。 +`LootModifier` is an abstract implementation of `IGlobalLootModifier` to provide the base functionality which most modders can easily extend and implement. This expands upon the existing interface by defining the `#apply` method to check the conditions to determine whether or not to modify the generated loot. -在子类实现中有两件事需要注意:一个是必须接受一个`LootItemCondition`数组的构造器,另一个是`#doApply`方法。 +There are two things of note within the subclass implementation: the constructor which must take in an array of `LootItemCondition`s and the `#doApply` method. -`LootItemCondition`数组定义了在战利品可以被修改之前必须为真的条件列表。提供的条件将被**并在一起**,这意味着所有的条件都必须为真。 +The array of `LootItemCondition`s define the list of conditions that must be true before the loot can be modified. The supplied conditions are **ANDed** together, meaning that all conditions must be true. -`#doApply`方法的工作方式与`#apply`方法相同,只是它只在所有条件返回真时才执行。 +The `#doApply` method works the same as the `#apply` method except that it only executes once all conditions return true. ```java public class ExampleModifier extends LootModifier { @@ -115,12 +115,12 @@ public class ExampleModifier extends LootModifier { } ``` -战利品修改器编解码器 +The Loot Modifier Codec ----------------------- -将JSON与`IGlobalLootModifier`实例连接起来的是一个[`Codec`][codecdef],其中`T`代表要使用的`IGlobalLootModifier`的类型。 +The connector between the JSON and the `IGlobalLootModifier` instance is a [`Codec`][codecdef], where `T` represents the type of the `IGlobalLootModifier` to use. -为了方便起见,已经提供了一个战利品条件编解码器,可以通过`LootModifier#codecStart`轻松地添加到类似记录的编解码器中。这用于关联战利品修改器的[数据生成][datagen]。 +For ease of convenience, a loot conditions codec has been provided for an easy addition to a record-like codec via `LootModifier#codecStart`. This is utilized for [data generation][datagen] of the associated loot modifier. ```java // For some DeferredRegister> REGISTRAR @@ -137,7 +137,7 @@ public static final RegistryObject> = REGISTRAR.register( ); ``` -[示例][examples]可以在Forge Git仓库中找到,包括精准采集和烧炼效果的示例。 +[Examples][examples] can be found on the Forge Git repository, including silk touch and smelting effects. [tags]: ./tags.md [resloc]: ../../misc/resourcelocation.md diff --git a/docs/resources/server/index.md b/docs/resources/server/index.md index 6c50b1c47..3c097338c 100644 --- a/docs/resources/server/index.md +++ b/docs/resources/server/index.md @@ -1,13 +1,13 @@ -数据包 (Datapacks) +Datapacks ========= -在1.13版本中,Mojang向基础游戏中添加了[数据包][datapack]。它们允许通过`data`目录修改逻辑服务器的文件。这包括进度、战利品表、结构、配方、标签等。Forge和您的模组也可以有数据包。因此,任何用户都可以修改在此目录内定义的所有配方、战利品表和其他数据。 +In 1.13, Mojang added [datapacks][datapack] to the base game. They allow for the modification of the files for logical servers through the `data` directory. This includes advancements, loot_tables, structures, recipes, tags, etc. Forge, and your mod, can also have datapacks. Any user can therefore modify all the recipes, loot tables, and other data defined within this directory. -### 创建一个数据包 -数据包存储在项目资源中的`data`目录内。 -您的模组可以有多个数据域,因为您可以添加或修改已存在的数据包,比如原版的、Forge的或其他模组的。 -然后,您可以按照[这里][createdatapack]找到的步骤创建任何数据包。 +### Creating a Datapack +Datapacks are stored within the `data` directory within your project's resources. +Your mod can have multiple data domains, since you can add or modify already existing datapacks, like vanilla's, forge's, or another mod's. +You can then follow the steps found [here][createdatapack] to create any datapack. -额外阅读资料:[资源位置][resourcelocation] +Additional reading: [Resource Locations][resourcelocation] [datapack]: https://minecraft.wiki/w/Data_pack [createdatapack]: https://minecraft.wiki/w/Tutorials/Creating_a_data_pack diff --git a/docs/resources/server/loottables.md b/docs/resources/server/loottables.md index 99c785315..2e80c2f17 100644 --- a/docs/resources/server/loottables.md +++ b/docs/resources/server/loottables.md @@ -1,50 +1,50 @@ -战利品表 (Loot Tables) +Loot Tables =========== -战利品表是控制在不同动作或场景发生时应该发生什么的逻辑文件。虽然原版系统纯粹与物品生成有关,但这个系统可以扩展以执行任意数量的定义动作。 +Loot tables are logic files which dictate what should happen when various actions or scenarios occur. Although the vanilla system deals purely with item generation, the system can be expanded to perform any number of defined actions. -数据驱动的表 +Data-Driven Tables ------------------ -原版中的大多数战利品表都是通过JSON数据驱动的。这意味着创建一个新的战利品表不需要模组,只需要一个[数据包][datapack]。有关如何创建这些战利品表并将它们放入模组的`resources`文件夹的完整列表可以在[Minecraft Wiki][wiki]上找到。 +Most loot tables within vanilla are data driven via JSON. This means that a mod is not necessary to create a new loot table, only a [Data pack][datapack]. A full list on how to create and put these loot tables within the mod's `resources` folder can be found on the [Minecraft Wiki][wiki]. -使用战利品表 +Using a Loot Table ------------------ -战利品表通过其`ResourceLocation`引用,它指向`data//loot_tables/.json`。可以使用`LootDataResolver#getLootTable`获取与引用相关联的`LootTable`,其中`LootDataResolver`可以通过`MinecraftServer#getLootData`获得。 +A loot table is referenced by its `ResourceLocation` which points to `data//loot_tables/.json`. The `LootTable` associated with the reference can be obtained using `LootDataResolver#getLootTable`, where `LootDataResolver` can be obtained via `MinecraftServer#getLootData`. -战利品表总是带有给定的参数生成的。`LootParams`包含生成表的等级、用于更好生成的幸运值、定义场景上下文的`LootContextParam`,以及在激活时应进行的任何动态信息。`LootParams`可以使用`LootParams$Builder`构造器的构造函数创建,并通过向`LootParams$Builder#create`传入`LootContextParamSet`构建。 +A loot table is always generated with given parameters. The `LootParams` contains the level the table is generated in, luck for better generation, the `LootContextParam`s which define scenario context, and any dynamic information that should occur on activation. The `LootParams` can be created using the constructor of the `LootParams$Builder` builder, and built via `LootParams$Builder#create` by passing in the `LootContextParamSet`. -战利品表也可能有一些上下文。`LootContext`接受构建的`LootParams`,并可以设置一些随机种子实例。上下文是通过构建器`LootContext$Builder`创建的,并通过向`LootContext$Builder#create`传入一个可为空的`ResourceLocation`来构建,代表要使用的随机实例。 +A loot table may also have some context. The `LootContext` takes in the built `LootParams` and can set some random seeded instance. The context is created via the builder `LootContext$Builder` and built using `LootContext$Builder#create` by passing in a nullable `ResourceLocation` representing the random instance to use. -`LootTable`可以使用可用的方法之一生成`ItemStack`,这些方法可能接受`LootParams`或`LootContext`: +A `LootTable` can be used to generate `ItemStack`s using one of the available methods which may take in a `LootParams` or a `LootContext`: -方法 | 描述 -:---: | :--- -`getRandomItemsRaw` | 消费战利品表生成的物品。 -`getRandomItems` | 返回战利品表生成的物品。 -`fill` | 用生成的战利品填充一个容器。 +Method | Description +:---: | :--- +`getRandomItemsRaw` | Consumes the items generated by the loot table. +`getRandomItems` | Returns the items generated by the loot table. +`fill` | Fills a container with the generated loot table. :::note -战利品表是为生成物品而构建的,所以这些方法期望对`ItemStack`进行一些处理。 +Loot tables were built for generating items, so the methods expect some handling for the `ItemStack`s. ::: -额外功能 +Additional Features ------------------- -Forge为战利品表提供了一些额外的行为,以更好地控制系统。 +Forge provides some additional behavior to loot tables for greater control of the system. ### `LootTableLoadEvent` -`LootTableLoadEvent`是在Forge事件总线上触发的一个[事件],每当一个战利品表被加载时就会触发。如果事件被取消,则将加载一个空的战利品表。 +`LootTableLoadEvent` is an [event] fired on the Forge event bus which is fired whenever a loot table is loaded. If the event is canceled, then an empty loot table will be loaded instead. :::info -不要通过这个事件修改战利品表的掉落。这些修改应该使用[全局战利品修改器][glm]来完成。 +Do **not** modify a loot table's drops through this event. Those modifications should be done using [global loot modifiers][glm]. ::: -### 战利品池名称 +### Loot Pool Names -可以使用`name`键命名战利品池。任何未命名的战利品池将以`custom#`为前缀,后面跟着池的哈希码。 +Loot pools can be named using the `name` key. Any non-named loot pool will be the hash code of the pool prefixed by `custom#`. ```js // For some loot pool @@ -59,21 +59,22 @@ Forge为战利品表提供了一些额外的行为,以更好地控制系统。 } ``` -### 掠夺修饰符 +### Looting Modifiers -现在,战利品表除了掠夺附魔以外,还受到Forge事件总线上的`LootingLevelEvent`的影响。 +Loot tables are now affected by the `LootingLevelEvent`, on the Forge event bus, in addition to the looting enchantment. -### 额外的上下文参数 +### Additional Context Parameters -Forge扩展了某些参数集来考虑可能适用的缺失上下文。`LootContextParamSets#CHEST`现在允许使用`LootContextParams#KILLER_ENTITY`,因为宝藏矿车是可以被破坏(或“杀死”)的实体。`LootContextParamSets#FISHING`也允许使用`LootContextParams#KILLER_ENTITY`,因为钓鱼钩也是一个实体,当玩家收回它时,它被收回(或“杀死”)。 +Forge extends certain parameter sets to account for missing contexts which may be applicable. `LootContextParamSets#CHEST` now allows for a `LootContextParams#KILLER_ENTITY` as chest minecarts are entities which can be broken (or 'killed'). `LootContextParamSets#FISHING` also allows for a `LootContextParams#KILLER_ENTITY` since the fishing hook is also an entity which is retracted (or 'killed') when the player retrieves it. -### 多项物品熔炼 +### Multiple Items on Smelting -使用`SmeltItemFunction`时,熔炼的配方现在会返回结果中的实际物品数量,而不是单个熔炼物品(例如,如果熔炼配方返回3个物品,而且有3个掉落,那么结果将是9个熔炼物品,而不是3个)。 +When using the `SmeltItemFunction`, a smelted recipe will now return the actual number of items from the result instead of a single smelted item (e.g. if a smelting recipe returns 3 items and there are 3 drops, then the result would be 9 smelted items instead of 3). -### 战利品表ID条件 +### Loot Table Id Condition + +Forge adds an additional `LootItemCondition` which allows certain items to generate for a specific table. This is typically used within [global loot modifiers][glm]. -Forge添加了一个额外的`LootItemCondition`,它允许某些物品为特定表生成。这通常在[全局战利品修改器][glm]中使用。 ```js // In some loot pool or pool entry { @@ -87,9 +88,9 @@ Forge添加了一个额外的`LootItemCondition`,它允许某些物品为特 } ``` -### 工具是否能执行动作条件 +### Can Tool Perform Action Condition -Forge添加了一个额外的`LootItemCondition`,用于检查给定的`LootContextParams#TOOL`是否能执行指定的`ToolAction`。这允许战利品表更精确地根据玩家使用的工具来调整掉落物,不仅限于工具的类型,还包括其能够执行的动作,例如挖掘、砍伐等。 +Forge adds an additional `LootItemCondition` which checks whether the given `LootContextParams#TOOL` can perform the specified `ToolAction`. ```js // In some loot pool or pool entry diff --git a/docs/resources/server/recipes/custom.md b/docs/resources/server/recipes/custom.md index fdf8a7fdd..1c77d2109 100644 --- a/docs/resources/server/recipes/custom.md +++ b/docs/resources/server/recipes/custom.md @@ -1,26 +1,26 @@ -自定义配方 +Custom Recipes ============== -每个配方定义都由三个组件组成:持有数据和处理提供的输入的执行逻辑的`Recipe`实现,表示配方将在其中使用的类别或上下文的`RecipeType`,以及处理配方数据的解码和网络通信的`RecipeSerializer`。如何选择使用配方取决于实现者。 +Every recipe definition is made up of three components: the `Recipe` implementation which holds the data and handles the execution logic with the provided inputs, the `RecipeType` which represents the category or context the recipe will be used in, and the `RecipeSerializer` which handles decoding and network communication of the recipe data. How one chooses to use the recipe is up to the implementor. -配方 +Recipe ------ -`Recipe`接口描述了配方数据和执行逻辑。这包括匹配输入和提供相关的结果。由于配方子系统默认执行物品转换,因此通过`Container`子类型提供输入。 +The `Recipe` interface describes the recipe data and the execution logic. This includes matching the inputs and providing the associated result. As the recipe subsystem performs item transformations by default, the inputs are supplied through a `Container` subtype. -:::note -应该将传入配方的`Container`视为其内容是不可变的。任何可变操作都应该在输入的副本上执行,通过`ItemStack#copy`。 +:::caution +The `Container`s passed into the recipe should be treated as if its contents were immutable. Any mutable operations should be performed on a copy of the input through `ItemStack#copy`. ::: -要能够从管理器获取配方实例,`#matches`必须返回true。此方法检查提供的容器,看看相关的输入是否有效。可以通过调用`Ingredient#test`来使用`Ingredient`进行验证。 +To be able to obtain a recipe instance from the manager, `#matches` must return true. This method checks against the provided container to see whether the associated inputs are valid. `Ingredient`s can be used for validation by calling `Ingredient#test`. -如果选择了配方,那么就使用`#assemble`进行构建,这可能使用来自输入的数据来创建结果。 +If the recipe has been chosen, it is then built using `#assemble` which may use data from the inputs to create the result. -:::note -`#assemble`应该总是产生一个唯一的`ItemStack`。如果不确定`#assemble`是否这样做,那么在返回之前在结果上调用`ItemStack#copy`。 +:::tip +`#assemble` should always produce a unique `ItemStack`. If unsure whether `#assemble` does so, call `ItemStack#copy` on the result before returning. ::: -大多数其他的方法纯粹是为了与配方书的集成。 +Most of the other methods are purely for integration with the recipe book. ```java public record ExampleRecipe(Ingredient input, int data, ItemStack output) implements Recipe { @@ -29,17 +29,17 @@ public record ExampleRecipe(Ingredient input, int data, ItemStack output) implem ``` :::note -虽然在上面的例子中使用了记录(record),但在您自己的实现中不要求这样做。 +While a record is used in the above example, it is not required to do so in your own implementation. ::: RecipeType ---------- -`RecipeType`负责定义配方将在其中使用的类别或上下文。例如,如果一个配方要在熔炉中熔炼,它会有一个`RecipeType#SMELTING`的类型。在高炉中爆炸熔炼将会有一个`RecipeType#BLASTING`的类型。 +`RecipeType` is responsible for defining the category or context the recipe will be used within. For example, if a recipe was going to be smelted in a furnace, it would have a type of `RecipeType#SMELTING`. Being blasted in a blast furnace would have a type of `RecipeType#BLASTING`. -如果没有现有的类型与配方将要使用的上下文匹配,那么必须[注册][forge]一个新的`RecipeType`。 +If none of the existing types match what context the recipe will be used within, then a new `RecipeType` must be [registered][forge]. -然后,新的配方子类型必须通过`Recipe#getType`返回`RecipeType`实例。 +The `RecipeType` instance must then be returned by `Recipe#getType` in the new recipe subtype. ```java // For some RegistryObject EXAMPLE_TYPE @@ -53,17 +53,17 @@ public RecipeType getType() { RecipeSerializer ---------------- -`RecipeSerializer`负责解码JSON文件,并且负责与网络上相关的`Recipe`子类型进行通信。每个由序列化器解码的配方被保存为`RecipeManager`内的一个唯一实例。必须[注册][forge]一个`RecipeSerializer`。 +A `RecipeSerializer` is responsible for decoding JSONs and communicating across the network for an associated `Recipe` subtype. Each recipe decoded by the serializer is saved as a unique instance within the `RecipeManager`. A `RecipeSerializer` must be [registered][forge]. -对于`RecipeSerializer`,只需要实现三个方法: +Only three methods need to be implemented for a `RecipeSerializer`: - 方法 | 描述 - :---: | :--- -fromJson | 将JSON解码为`Recipe`子类型。 -toNetwork | 将`Recipe`编码到缓冲区以发送给客户端。配方标识符不需要被编码。 -fromNetwork | 从服务器发送的缓冲区解码`Recipe`。配方标识符不需要被解码。 + Method | Description + :---: | :--- +fromJson | Decodes a JSON into the `Recipe` subtype. +toNetwork | Encodes a `Recipe` to the buffer to send to the client. The recipe identifier does not need to be encoded. +fromNetwork | Decodes a `Recipe` from the buffer sent from the server. The recipe identifier does not need to be decoded. -然后,`RecipeSerializer`实例必须通过新配方子类型的`Recipe#getSerializer`返回。 +The `RecipeSerializer` instance must then be returned by `Recipe#getSerializer` in the new recipe subtype. ```java // For some RegistryObject EXAMPLE_SERIALIZER @@ -75,13 +75,14 @@ public RecipeSerializer getSerializer() { ``` :::tip -有一些有用的方法可以使阅读和写入配方数据更加容易。`Ingredient`可以使用`#fromJson`、`#toNetwork`和`#fromNetwork`,而`ItemStack`可以使用`CraftingHelper#getItemStack`、`FriendlyByteBuf#writeItem`和`FriendlyByteBuf#readItem`。 +There are some useful methods to make reading and writing data for recipes easier. `Ingredient`s can use `#fromJson`, `#toNetwork`, and `#fromNetwork` while `ItemStack`s can use `CraftingHelper#getItemStack`, `FriendlyByteBuf#writeItem`, and `FriendlyByteBuf#readItem`. ::: -构建JSON +Building the JSON ----------------- -自定义配方JSON存储在与其他[配方][json]相同的地方。指定的`type`应表示**配方序列化器**的注册名。在解码过程中,序列化器指定任何额外的数据。 +Custom Recipe JSONs are stored in the same place as other [recipes][json]. The specified `type` should represent the registry name of the **recipe serializer**. Any additional data is specified by the serializer during decoding. + ```js { // The custom serializer registry name @@ -96,10 +97,10 @@ public RecipeSerializer getSerializer() { } ``` -非物品逻辑 +Non-Item Logic -------------- -如果物品不被用作配方的输入或结果的一部分,那么[`RecipeManager`][manager]提供的正常方法将不会有用。相反,应该向自定义`Recipe`实例添加一个额外的方法,用于测试配方的有效性和/或提供结果。从那里,可以通过`RecipeManager#getAllRecipesFor`获取特定`RecipeType`的所有配方,然后使用新实现的方法检查和/或提供结果。 +If items are not used as part of the input or result of a recipe, then the normal methods provided in [`RecipeManager`][manager] will not be useful. Instead, an additional method for testing a recipe's validity and/or supplying the result should be added to the custom `Recipe` instance. From there, all the recipes for that specific `RecipeType` can be obtained via `RecipeManager#getAllRecipesFor` and then checked and/or supplied the result using the newly implemented methods. ```java // In some Recipe subimplementation ExampleRecipe @@ -120,10 +121,10 @@ public Optional getRecipeFor(Level level, BlockPos pos) { } ``` -数据生成 +Data Generation --------------- -所有自定义配方,无论输入或输出数据如何,都可以使用`RecipeProvider`将其创建为用于[数据生成][datagen]的`FinishedRecipe`。 +All custom recipes, regardless of input or output data, can be created into a `FinishedRecipe` for [data generation][datagen] using the `RecipeProvider`. [forge]: ../../../concepts/registries.md#methods-for-registering [json]: https://minecraft.wiki/w/Recipe#JSON_format diff --git a/docs/resources/server/recipes/incode.md b/docs/resources/server/recipes/incode.md index 907a03d39..00497a10a 100644 --- a/docs/resources/server/recipes/incode.md +++ b/docs/resources/server/recipes/incode.md @@ -1,33 +1,33 @@ -非数据包配方 +Non-Datapack Recipes ==================== -并非所有配方都足够简单或已迁移到使用数据驱动的配方。一些子系统仍需要在代码库中进行修补,以支持添加新的配方。 +Not all recipes are simplistic enough or migrated to using data-driven recipes. Some subsystems still need to be patched within the codebase to provide support for adding new recipes. -酿造配方 +Brewing Recipes --------------- -酿造是仍然存在于代码中的少数配方之一。酿造配方作为`PotionBrewing`内的引导程序的一部分添加,用于其容器、容器配方和药水混合。为了扩展现有系统,Forge允许通过在`FMLCommonSetupEvent`中调用`BrewingRecipeRegistry#addRecipe`来添加酿造配方。 +Brewing is one of the few recipes that still exist in code. Brewing recipes are added as part of a bootstrap within `PotionBrewing` for their containers, container recipes, and potion mixes. To expand upon the existing system, Forge allows brewing recipes to be added by calling `BrewingRecipeRegistry#addRecipe` in `FMLCommonSetupEvent`. :::caution -`BrewingRecipeRegistry#addRecipe`必须在同步工作队列中通过`#enqueueWork`调用,因为该方法不是线程安全的。 +`BrewingRecipeRegistry#addRecipe` must be called within the synchronous work queue via `#enqueueWork` as the method is not thread-safe. ::: -默认实现接受一个输入成分,一个催化剂成分,和一个堆叠输出以进行标准实现。此外,也可以提供一个`IBrewingRecipe`实例来执行转换。 +The default implementation takes in an input ingredient, a catalyst ingredient, and a stack output for a standard implementation. Additionally, an `IBrewingRecipe` instance can be supplied instead to do the transformations. ### IBrewingRecipe -`IBrewingRecipe`是一种伪[`Recipe`][recipe]接口,它检查输入和催化剂是否有效,并在满足条件时提供相关的输出。这通过`#isInput`、`#isIngredient`和`#getOutput`实现。输出方法可以访问输入和催化剂堆叠以构造结果。 +`IBrewingRecipe` is a pseudo-[`Recipe`][recipe] interface that checks whether the input and catalyst is valid and provides the associated output if so. This is provided through `#isInput`, `#isIngredient`, and `#getOutput` respectively. The output method has access to the input and catalyst stacks to construct the result. :::caution -在`ItemStack`或`CompoundTag`之间复制数据时,确保使用它们各自的`#copy`方法创建唯一的实例。 +When copying data between `ItemStack`s or `CompoundTag`s, make sure to use their respective `#copy` methods to create unique instances. ::: -没有类似于原版的添加额外药水容器或药水混合的包装器。需要添加一个新的`IBrewingRecipe`实现来复制这种行为。 +There is no wrapper for adding additional potion containers or potion mixes similar to vanilla. A new `IBrewingRecipe` implementation will need to be added to replicate this behavior. -铁砧配方 +Anvil Recipes ------------- -铁砧负责接受一个受损的输入,给出一些材料或类似的输入,减少输入结果上的一些损伤。因此,其系统不容易被数据驱动。然而,因为铁砧配方是一个输入物品加上一些数量的材料等于一些输出物品,当用户有足够的经验等级时,它可以通过`AnvilUpdateEvent`修改为创建一个伪配方系统。这取决于输入和材料,并允许开发者指定输出、经验等级成本,以及用于输出的材料数量。事件还可以通过[取消][cancel]来阻止任何输出。 +Anvils are responsible for taking a damaged input and given some material or a similar input, remove some of the damage on the input result. As such, its system is not easily data-driven. However, as anvil recipes are an input with some number of materials equals some output when the user has the required experience levels, it can be modified to create a pseudo-recipe system via `AnvilUpdateEvent`. This takes in the input and materials and allows the modder to specify the output, experience level cost, and number of materials to use for the output. The event can also prevent any output by [canceling][cancel] it. ```java // Checks whether the left and right items are correct @@ -41,15 +41,15 @@ public void updateAnvil(AnvilUpdateEvent event) { } ``` -更新事件必须[附加]到Forge事件总线上。 +The update event must be [attached] to the Forge event bus. -织布机配方 +Loom Recipes ------------ -织布机负责将染料和图案(来自织布机或来自物品)应用于旗帜。虽然旗帜和染料必须分别是`BannerItem`或`DyeItem`,但可以在织布机中创建和应用自定义图案。可以通过[注册]一个`BannerPattern`来创建旗帜图案。 +Looms are responsible for applying a dye and pattern (either from the loom or from an item) to a banner. While the banner and the dye must be a `BannerItem` or `DyeItem` respectively, custom patterns can be created and applied in the loom. Banner Patterns can be created by [registering] a `BannerPattern`. :::caution -在`minecraft:no_item_required`标签中的`BannerPattern`会作为一个选项出现在织布机中。不在此标签中的图案必须有一个相应的`BannerPatternItem`,并且有一个相关联的标签,才能使用。 +`BannerPattern`s which are in the `minecraft:no_item_required` tag appear as an option in the loom. Patterns not in this tag must have an accompanying `BannerPatternItem` to be used along with an associated tag. ::: ```java diff --git a/docs/resources/server/recipes/ingredients.md b/docs/resources/server/recipes/ingredients.md index 7c5e661e6..7d5d3629d 100644 --- a/docs/resources/server/recipes/ingredients.md +++ b/docs/resources/server/recipes/ingredients.md @@ -1,20 +1,20 @@ -配料 +Ingredients =========== -`Ingredient`是针对基于物品的输入的谓词处理器,它检查某个`ItemStack`是否满足作为配方有效输入的条件。所有采用输入的[原版配方][recipes]都使用一个`Ingredient`或一系列`Ingredient`,然后合并为一个单一的`Ingredient`。 +`Ingredient`s are predicate handlers for item-based inputs which check whether a certain `ItemStack` meets the condition to be a valid input in a recipe. All [vanilla recipes][recipes] that take inputs use an `Ingredient` or a list of `Ingredient`s, which is then merged into a single `Ingredient`. -自定义配料 +Custom Ingredients ------------------ -可以通过将`type`设置为[配料序列化器][serializer]的名称来指定自定义配料,[复合配料][compound]除外。当未指定类型时,`type`默认为原版配料`minecraft:item`。自定义配料也可以轻松地用于[数据生成][datagen]。 +Custom ingredients can be specified by setting `type` to the name of the [ingredient's serializer][serializer], with the exception of [compound ingredients][compound]. When no type is specified, `type` defaults to the vanilla ingredient `minecraft:item`. Custom ingredients can also easily be used in [data generation][datagen]. -### Forge 类型 +### Forge Types -Forge为程序员提供了一些额外的`Ingredient`类型。 +Forge provides a few additional `Ingredient` types for programmers to implement. #### CompoundIngredient -虽然它们在功能上是相同的,但复合配料替换了在配方中实现配料列表的方式。它们作为一个集合OR操作,传入的堆叠必须至少在提供的配料中的一个中。这个更改是为了让自定义配料能在列表中正常工作。因此,**无需**指定类型。 +Though they are functionally identical, Compound ingredients replaces the way one would implement a list of ingredients would in a recipe. They work as a set OR where the passed in stack must be within at least one of the supplied ingredients. This change was made to allow custom ingredients to work correctly within lists. As such, **no type** needs to be specified. ```js // For some input @@ -32,7 +32,7 @@ Forge为程序员提供了一些额外的`Ingredient`类型。 #### StrictNBTIngredient -`StrictNBTIngredient`会比较`ItemStack`上的物品、损坏和共享标签(由`IForgeItem#getShareTag`定义)以确保完全等价。这可以通过将`type`指定为`forge:nbt`来使用。 +`StrictNBTIngredient`s compare the item, damage, and the share tags (as defined by `IForgeItem#getShareTag`) on an `ItemStack` for exact equivalency. This can be used by specifying the `type` as `forge:nbt`. ```js // For some input @@ -47,7 +47,7 @@ Forge为程序员提供了一些额外的`Ingredient`类型。 ### PartialNBTIngredient -`PartialNBTIngredient`是[`StrictNBTIngredient`][nbt]的一个更宽松的版本,因为它们只比较一个或一组物品,并且只比较在共享标签(由`IForgeItem#getShareTag`定义)中指定的键。这可以通过将`type`指定为`forge:partial_nbt`来使用。 +`PartialNBTIngredient`s are a looser version of [`StrictNBTIngredient`][nbt] as they compare against a single or set of items and only keys specified within the share tag (as defined by `IForgeItem#getShareTag`). This can be used by specifying the `type` as `forge:partial_nbt`. ```js // For some input @@ -76,7 +76,7 @@ Forge为程序员提供了一些额外的`Ingredient`类型。 ### IntersectionIngredient -`IntersectionIngredient`作为一个集合AND操作,传入的堆叠必须匹配所有提供的配料。至少必须向此提供两种配料。这可以通过将`type`指定为`forge:intersection`来使用。 +`IntersectionIngredient`s work as a set AND where the passed in stack must match all supplied ingredients. There must be at least two ingredients supplied to this. This can be used by specifying the `type` as `forge:intersection`. ```js // For some input @@ -98,7 +98,7 @@ Forge为程序员提供了一些额外的`Ingredient`类型。 ### DifferenceIngredient -`DifferenceIngredient`作为一个集合减法(SUB)操作,传入的堆叠必须匹配第一个配料,但不得匹配第二个配料。这可以通过将`type`指定为`forge:difference`来使用。 +`DifferenceIngredient`s work as a set subtraction (SUB) where the passed in stack must match the first ingredient but must not match the second ingredient. This can be used by specifying the `type` as `forge:difference`. ```js // For some input @@ -113,40 +113,40 @@ Forge为程序员提供了一些额外的`Ingredient`类型。 } ``` -创建自定义配料 +Creating Custom Ingredients --------------------------- -可以通过为创建的`Ingredient`子类实现`IIngredientSerializer`来创建自定义配料。 +Custom ingredients can be created by implementing `IIngredientSerializer` for the created `Ingredient` subclass. -:::提示 -自定义配料应该子类化`AbstractIngredient`,因为它为实施提供了一些有用的抽象。 +:::tip +Custom ingredients should subclass `AbstractIngredient` as it provides some useful abstractions for ease of implementation. ::: -### Ingredient子类 +### Ingredient Subclass -每个配料子类要实施三个重要的方法: +There are three important methods to implement for each ingredient subclass: - 方法 | 描述 + Method | Description :---: | :--- -getSerializer | 返回用于读写配料的[序列化器]。 -test | 如果输入对于这个配料是有效的,返回true。 -isSimple | 如果配料匹配堆栈的标签,则返回false。`AbstractIngredient`子类需要定义这种行为,而`Ingredient`子类默认返回`true`。 +getSerializer | Returns the [serializer] used to read and write the ingredient. +test | Returns true if the input is valid for this ingredient. +isSimple | Returns false if the ingredient matches on the stack's tag. `AbstractIngredient` subclasses will need to define this behavior, while `Ingredient` subclasses return `true` by default. -所有其他定义的方法留给读者根据需要使用配料子类。 +All other defined methods are left as an exercise to the reader to use as required for the ingredient subclass. ### IIngredientSerializer -`IIngredientSerializer`子类型必须实现三个方法: +`IIngredientSerializer` subtypes must implement three methods: - 方法 | 描述 + Method | Description :---: | :--- -parse (JSON) | 将`JsonObject`转化为`Ingredient`。 -parse (Network) | 读取网络缓冲区以解码一个`Ingredient`。 -write | 将一个`Ingredient`写入网络缓冲区。 +parse (JSON) | Converts a `JsonObject` to an `Ingredient`. +parse (Network) | Reads the network buffer to decode an `Ingredient`. +write | Writes an `Ingredient` to the network buffer. -此外,`Ingredient`子类应该实现`Ingredient#toJson`以用于[data generation][datagen]。`AbstractIngredient`子类将`#toJson`设置为抽象方法,要求实现该方法。 +Additionally, `Ingredient` subclasses should implement `Ingredient#toJson` for use with [data generation][datagen]. `AbstractIngredient` subclasses make `#toJson` an abstract method requiring the method to be implemented. -然后,应声明一个静态实例来保存初始化的序列化器,然后使用`CraftingHelper#register`在`RecipeSerializer`的`RegisterEvent`或`FMLCommonSetupEvent`期间注册。`Ingredient`子类在`Ingredient#getSerializer`中返回序列化器的静态实例。 +Afterwards, a static instance should be declared to hold the initialized serializer and then registered using `CraftingHelper#register` either during the `RegisterEvent` for `RecipeSerializer`s or during `FMLCommonSetupEvent`. The `Ingredient` subclass return the static instance of the serializer in `Ingredient#getSerializer`. ```java // In some serializer class @@ -167,7 +167,7 @@ public IIngredientSerializer getSerializer() { ``` :::tip -如果使用`FMLCommonSetupEvent`来注册配料序列化器,必须通过`FMLCommonSetupEvent#enqueueWork`将其加入到同步工作队列,因为`CraftingHelper#register`不是线程安全的。 +If using `FMLCommonSetupEvent` to register an ingredient serializer, it must be enqueued to the synchronous work queue via `FMLCommonSetupEvent#enqueueWork` as `CraftingHelper#register` is not thread-safe. ::: [recipes]: https://minecraft.wiki/w/Recipe#List_of_recipe_types diff --git a/docs/resources/server/tags.md b/docs/resources/server/tags.md index b5f88eeb3..e5a7855de 100644 --- a/docs/resources/server/tags.md +++ b/docs/resources/server/tags.md @@ -1,15 +1,15 @@ -标签 (Tags) +Tags ==== -标签是游戏中对象的广义集合,用于将相关事物分组在一起并提供快速的成员检查。 +Tags are generalized sets of objects in the game used for grouping related things together and providing fast membership checks. -声明您自己的分组 +Declaring Your Own Groupings ---------------------------- -标签在您的模组的[数据包][datapack]中声明。例如,一个给定标识符为`modid:foo/tagname`的`TagKey`将引用在`/data//tags/blocks/foo/tagname.json`的标签。`Block`、`Item`、`EntityType`、`Fluid`和`GameEvent`的标签使用它们的文件夹位置的复数形式,而所有其他注册表使用单数版本(`EntityType`使用文件夹`entity_types`,而`Potion`则使用文件夹`potion`)。 -同样,您可以通过声明自己的JSON来附加或覆盖在其他域中声明的标签,比如Vanilla。 -例如,要将您自己模组的树苗添加到Vanilla的树苗标签,您需要在`/data/minecraft/tags/blocks/saplings.json`中指定,如果`replace`选项为false,那么Vanilla将在重新加载时将所有内容合并到一个标签中。 -如果`replace`为true,则指定`replace`的json之前的所有条目将被删除。 -列出的不存在的值将导致标签出错,除非该值使用`id`字符串和`required`布尔值列出且设置为false,如下例: +Tags are declared in your mod's [datapack][datapack]. For example, a `TagKey` with a given identifier of `modid:foo/tagname` will reference a tag at `/data//tags/blocks/foo/tagname.json`. Tags for `Block`s, `Item`s, `EntityType`s, `Fluid`s, and `GameEvent`s use the plural forms for their folder location while all other registries use the singular version (`EntityType` uses the folder `entity_types` while `Potion` would use the folder `potion`). +Similarly, you may append to or override tags declared in other domains, such as Vanilla, by declaring your own JSONs. +For example, to add your own mod's saplings to the Vanilla sapling tag, you would specify it in `/data/minecraft/tags/blocks/saplings.json`, and Vanilla will merge everything into one tag at reload, if the `replace` option is false. +If `replace` is true, then all entries before the json specifying `replace` will be removed. +Values listed that are not present will cause the tag to error unless the value is listed using an `id` string and `required` boolean set to false, as in the following example: ```js { @@ -25,48 +25,48 @@ } ``` -请参阅[Vanilla wiki][tags]了解基本语法的描述。 +See the [Vanilla wiki][tags] for a description of the base syntax. -此外,Forge在Vanilla语法上进行了扩展。 -您可以声明一个与`values`数组格式相同的`remove`数组。列在这里的任何值都将从标签中删除。这作为Vanilla `replace`选项的更细粒度版本。 +There is also a Forge extension on the Vanilla syntax. +You may declare a `remove` array of the same format as the `values` array. Any values listed here will be removed from the tag. This acts as a finer grained version of the Vanilla `replace` option. -在代码中使用标签 + +Using Tags In Code ------------------ -所有注册表的标签都会在登录和重新加载时自动从服务器发送到任何远程客户端。`Block`、`Item`、`EntityType`、`Fluid`和`GameEvent`是特殊情况,因为它们有`Holder`,允许通过对象本身访问可用的标签。 +Tags for all registries are automatically sent from the server to any remote clients on login and reload. `Block`s, `Item`s, `EntityType`s, `Fluid`s, and `GameEvent`s are special cased as they have `Holder`s allowing for available tags to be accessible through the object itself. :::note -未来版本的Minecraft中可能会删除侵入式的`Holder`。如果它们被删除,下面的方法可以用来查询相关的`Holder`。 +Intrusive `Holder`s may be removed in a future version of Minecraft. If they are, the below methods can be used instead to query the associated `Holder`s. ::: ### ITagManager -Forge包装的注册表提供了一个额外的帮助器,通过`ITagManager`来创建和管理标签,可以通过`IForgeRegistry#tags`获得。标签可以使用`#createTagKey`或`#createOptionalTagKey`创建。也可以分别使用`#getTag`或`#getReverseTag`检查标签或注册对象。 - -#### 自定义注册表 +Forge wrapped registries provide an additional helper for creating and managing tags through `ITagManager` which can be obtained via `IForgeRegistry#tags`. Tags can be created using using `#createTagKey` or `#createOptionalTagKey`. Tags or registry objects can also be checked for either or using `#getTag` or `#getReverseTag` respectively. -自定义注册表可以在构建它们的`DeferredRegister`时通过`#createTagKey`或`#createOptionalTagKey`创建标签。然后可以通过调用`DeferredRegister#makeRegistry`获得的`IForgeRegistry`来检查它们的标签或注册对象。 +#### Custom Registries -### 引用标签 +Custom registries can create tags when constructing their `DeferredRegister` via `#createTagKey` or `#createOptionalTagKey` respectively. Their tags or registry objects can then checked for either using the `IForgeRegistry` obtained by calling `DeferredRegister#makeRegistry`. -有四种创建标签包装的方法: +### Referencing Tags -方法 | 适用于 -:---: | :--- -`*Tags#create` | `BannerPattern`、`Biome`、`Block`、`CatVariant`、`DamageType`、`EntityType`、`FlatLevelGeneratorPreset`、`Fluid`、`GameEvent`、`Instrument`、`Item`、`PaintingVariant`、`PoiType`、`Structure`和`WorldPreset`,其中`*`代表这些类型之一。 -`ITagManager#createTagKey` | Forge包装的vanilla注册表,注册表可以从`ForgeRegistries`获得。 -`DeferredRegister#createTagKey` | 自定义forge注册表。 -`TagKey#create` | 没有forge包装的vanilla注册表,注册表可以从`Registry`获得。 +There are four methods of creating a tag wrapper: -注册对象可以通过它们的`Holder`或对于vanilla或forge注册表对象分别通过`ITag`/`IReverseTag`检查它们的标签或注册对象。 +Method | For +:---: | :--- +`*Tags#create` | `BannerPattern`, `Biome`, `Block`, `CatVariant`, `DamageType`, `EntityType`, `FlatLevelGeneratorPreset`, `Fluid`, `GameEvent`, `Instrument`, `Item`, `PaintingVariant`, `PoiType`, `Structure`, and `WorldPreset` where `*` represents one of these types. +`ITagManager#createTagKey` | Forge wrapped vanilla registries, registries can be obtained from `ForgeRegistries`. +`DeferredRegister#createTagKey` | Custom forge registries. +`TagKey#create` | Vanilla registries without forge wrappers, registries can be obtained from `Registry`. -Vanilla注册表对象可以使用`Registry#getHolder`或`Registry#getHolderOrThrow`抓取它们关联的holder,然后使用`Holder#is`比较注册表对象是否有标签。 +Registry objects can check their tags or registry objects either through their `Holder` or through `ITag`/`IReverseTag` for vanilla or forge registry objects respectively. -Forge注册表对象可以使用`ITagManager#getTag`或`ITagManager#getReverseTag`抓取它们的标签定义,然后分别使用`ITag#contains`或`IReverseTag#containsTag`比较注册表对象是否有标签。 +Vanilla registry objects can grab their associated holder using either `Registry#getHolder` or `Registry#getHolderOrThrow` and then compare if the registry object has a tag using `Holder#is`. -包含标签的注册表对象包含一个称为`#is`的方法,在它们的注册表对象或状态感知类中,用以检查对象是否属于某个标签。 +Forge registry objects can grab their tag definition using either `ITagManager#getTag` or `ITagManager#getReverseTag` and then compare if a registry object has a tag using `ITag#contains` or `IReverseTag#containsTag` respectively. -作为一个示例: +Tag-holding registry objects contain a method called `#is` in either their registry object or state-aware class to check whether the object belongs to a certain tag. +As an example: ```java public static final TagKey myItemTag = ItemTags.create(new ResourceLocation("mymod", "myitemgroup")); @@ -86,30 +86,32 @@ ResourceKey villagerTypeKey = /*...*/; boolean isInVillagerTypeGroup = BuiltInRegistries.VILLAGER_TYPE.getHolder(villagerTypeKey).map(holder -> holder.is(myVillagerTypeTag)).orElse(false); ``` -约定 +Conventions ----------- -有几个约定将有助于在生态系统中促进兼容性: +There are several conventions that will help facilitate compatibility in the ecosystem: -* 如果有Vanilla标签适合您的方块或物品,请将其添加到该标签中。参见[Vanilla标签列表][taglist]。 -* 如果有Forge标签适合您的方块或物品,请将其添加到该标签中。可以在[GitHub][forgetags]上查看Forge声明的标签列表。 -* 如果您觉得有一组东西应该被社区共享,请使用`forge`命名空间而不是您的mod id。 -* 标签命名约定应遵循Vanilla约定。特别是,物品和方块分组应使用复数而不是单数(例如,`minecraft:logs`,`minecraft:saplings`)。 -* 物品标签应按照它们的类型排序到子目录中(例如,`forge:ingots/iron`,`forge:nuggets/brass`等)。 +* If there is a Vanilla tag that fits your block or item, add it to that tag. See the [list of Vanilla tags][taglist]. +* If there is a Forge tag that fits your block or item, add it to that tag. The list of tags declared by Forge can be seen on [GitHub][forgetags]. +* If there is a group of something you feel should be shared by the community, use the `forge` namespace instead of your mod id. +* Tag naming conventions should follow Vanilla conventions. In particular, item and block groupings are plural instead of singular (e.g. `minecraft:logs`, `minecraft:saplings`). +* Item tags should be sorted into subdirectories according to their type (e.g. `forge:ingots/iron`, `forge:nuggets/brass`, etc.). -从OreDictionary迁移 + +Migration from OreDictionary ---------------------------- -* 对于配方,可以直接在vanilla配方格式中使用标签(见下文)。 -* 要在代码中匹配物品,请参阅上述部分。 -* 如果您正在声明一种新类型的物品分组,请遵循一些命名约定: - * 使用`domain:type/material`。当名称是所有modders都应采用的常见名称时,使用`forge`域。 - * 例如,铜锭应在`forge:ingots/brass`标签下注册,钴粒则应在`forge:nuggets/cobalt`标签下注册。 +* For recipes, tags can be used directly in the vanilla recipe format (see below). +* For matching items in code, see the section above. +* If you are declaring a new type of item grouping, follow a couple naming conventions: + * Use `domain:type/material`. When the name is a common one that all modders should adopt, use the `forge` domain. + * For example, brass ingots should be registered under the `forge:ingots/brass` tag and cobalt nuggets under the `forge:nuggets/cobalt` tag. + -在配方和成就中使用标签 +Using Tags in Recipes and Advancements -------------------------------------- -Vanilla直接支持标签。有关使用详细信息,请参阅相应的Vanilla wiki页面,包括[配方]和[成就]。 +Tags are directly supported by Vanilla. See the respective Vanilla wiki pages for [recipes] and [advancements] for usage details. [datapack]: ./index.md [tags]: https://minecraft.wiki/w/Tag#JSON_format diff --git a/docusaurus.config.js b/docusaurus.config.js index c6636dce3..9bf188b4f 100644 --- a/docusaurus.config.js +++ b/docusaurus.config.js @@ -28,8 +28,8 @@ const config = { // metadata like html lang. For example, if your site is Chinese, you may want // to replace "en" with "zh-Hans". i18n: { - defaultLocale: "en", - locales: ["en"], + defaultLocale: 'en', + locales: ['en','zh-Hans'], }, presets: [ diff --git a/i18n/zh-Hans/code.json b/i18n/zh-Hans/code.json new file mode 100644 index 000000000..e36f821a9 --- /dev/null +++ b/i18n/zh-Hans/code.json @@ -0,0 +1,424 @@ +{ + "theme.docs.versions.unmaintainedVersionLabel": { + "message": "此为 {siteTitle} {versionLabel} 版的文档,现已不再积极维护。", + "description": "The label used to tell the user that he's browsing an unmaintained doc version" + }, + "theme.docs.versions.LTSVersionLabel": { + "message": "This is documentation for {siteTitle} {versionLabel}, which is currently the LTS version.", + "description": "The label used to tell the user that they're browsing a LTS doc version" + }, + "theme.docs.versions.latestVersionSuggestionLabel": { + "message": "最新的文档请参阅 {latestVersionLink} ({versionLabel})。", + "description": "The label used to tell the user to check the latest version" + }, + "theme.docs.versions.latestVersionLinkLabel": { + "message": "最新版本", + "description": "The label used for the latest version suggestion link label" + }, + "theme.ErrorPageContent.title": { + "message": "页面已崩溃。", + "description": "The title of the fallback page when the page crashed" + }, + "theme.BackToTopButton.buttonAriaLabel": { + "message": "回到顶部", + "description": "The ARIA label for the back to top button" + }, + "theme.blog.archive.title": { + "message": "历史博文", + "description": "The page & hero title of the blog archive page" + }, + "theme.blog.archive.description": { + "message": "历史博文", + "description": "The page & hero description of the blog archive page" + }, + "theme.blog.paginator.navAriaLabel": { + "message": "博文列表分页导航", + "description": "The ARIA label for the blog pagination" + }, + "theme.blog.paginator.newerEntries": { + "message": "较新的博文", + "description": "The label used to navigate to the newer blog posts page (previous page)" + }, + "theme.blog.paginator.olderEntries": { + "message": "较旧的博文", + "description": "The label used to navigate to the older blog posts page (next page)" + }, + "theme.blog.post.paginator.navAriaLabel": { + "message": "博文分页导航", + "description": "The ARIA label for the blog posts pagination" + }, + "theme.blog.post.paginator.newerPost": { + "message": "较新一篇", + "description": "The blog post button label to navigate to the newer/previous post" + }, + "theme.blog.post.paginator.olderPost": { + "message": "较旧一篇", + "description": "The blog post button label to navigate to the older/next post" + }, + "theme.blog.post.plurals": { + "message": "{count} 篇博文", + "description": "Pluralized label for \"{count} posts\". Use as much plural forms (separated by \"|\") as your language support (see https://www.unicode.org/cldr/cldr-aux/charts/34/supplemental/language_plural_rules.html)" + }, + "theme.blog.tagTitle": { + "message": "{nPosts} 含有标签「{tagName}」", + "description": "The title of the page for a blog tag" + }, + "theme.tags.tagsPageLink": { + "message": "查看所有标签", + "description": "The label of the link targeting the tag list page" + }, + "theme.colorToggle.ariaLabel": { + "message": "切换浅色/暗黑模式(当前为{mode})", + "description": "The ARIA label for the navbar color mode toggle" + }, + "theme.colorToggle.ariaLabel.mode.dark": { + "message": "暗黑模式", + "description": "The name for the dark color mode" + }, + "theme.colorToggle.ariaLabel.mode.light": { + "message": "浅色模式", + "description": "The name for the light color mode" + }, + "theme.docs.breadcrumbs.navAriaLabel": { + "message": "页面路径", + "description": "The ARIA label for the breadcrumbs" + }, + "theme.docs.DocCard.categoryDescription": { + "message": "{count} 个项目", + "description": "The default description for a category card in the generated index about how many items this category includes" + }, + "theme.docs.paginator.navAriaLabel": { + "message": "文件选项卡", + "description": "The ARIA label for the docs pagination" + }, + "theme.docs.paginator.previous": { + "message": "上一页", + "description": "The label used to navigate to the previous doc" + }, + "theme.docs.paginator.next": { + "message": "下一页", + "description": "The label used to navigate to the next doc" + }, + "theme.docs.tagDocListPageTitle.nDocsTagged": { + "message": "{count} 篇文档带有标签", + "description": "Pluralized label for \"{count} docs tagged\". Use as much plural forms (separated by \"|\") as your language support (see https://www.unicode.org/cldr/cldr-aux/charts/34/supplemental/language_plural_rules.html)" + }, + "theme.docs.tagDocListPageTitle": { + "message": "{nDocsTagged}「{tagName}」", + "description": "The title of the page for a docs tag" + }, + "theme.docs.versionBadge.label": { + "message": "版本:{versionLabel}" + }, + "theme.docs.versions.unreleasedVersionLabel": { + "message": "此为 {siteTitle} {versionLabel} 版尚未发行的文档。", + "description": "The label used to tell the user that he's browsing an unreleased doc version" + }, + "theme.common.editThisPage": { + "message": "编辑此页", + "description": "The link label to edit the current page" + }, + "theme.common.headingLinkTitle": { + "message": "{heading}的直接链接", + "description": "Title for link to heading" + }, + "theme.lastUpdated.atDate": { + "message": "于 {date} ", + "description": "The words used to describe on which date a page has been last updated" + }, + "theme.lastUpdated.byUser": { + "message": "由 {user} ", + "description": "The words used to describe by who the page has been last updated" + }, + "theme.lastUpdated.lastUpdatedAtBy": { + "message": "最后{byUser}{atDate}更新", + "description": "The sentence used to display when a page has been last updated, and by who" + }, + "theme.navbar.mobileVersionsDropdown.label": { + "message": "选择版本", + "description": "The label for the navbar versions dropdown on mobile view" + }, + "theme.NotFound.title": { + "message": "找不到页面", + "description": "The title of the 404 page" + }, + "theme.tags.tagsListLabel": { + "message": "标签:", + "description": "The label alongside a tag list" + }, + "theme.admonition.caution": { + "message": "警告", + "description": "The default label used for the Caution admonition (:::caution)" + }, + "theme.admonition.danger": { + "message": "危险", + "description": "The default label used for the Danger admonition (:::danger)" + }, + "theme.admonition.info": { + "message": "信息", + "description": "The default label used for the Info admonition (:::info)" + }, + "theme.admonition.note": { + "message": "备注", + "description": "The default label used for the Note admonition (:::note)" + }, + "theme.admonition.tip": { + "message": "提示", + "description": "The default label used for the Tip admonition (:::tip)" + }, + "theme.admonition.warning": { + "message": "注意", + "description": "The default label used for the Warning admonition (:::warning)" + }, + "theme.AnnouncementBar.closeButtonAriaLabel": { + "message": "关闭", + "description": "The ARIA label for close button of announcement bar" + }, + "theme.blog.sidebar.navAriaLabel": { + "message": "最近博文导航", + "description": "The ARIA label for recent posts in the blog sidebar" + }, + "theme.CodeBlock.copied": { + "message": "复制成功", + "description": "The copied button label on code blocks" + }, + "theme.CodeBlock.copyButtonAriaLabel": { + "message": "复制代码到剪贴板", + "description": "The ARIA label for copy code blocks button" + }, + "theme.CodeBlock.copy": { + "message": "复制", + "description": "The copy button label on code blocks" + }, + "theme.CodeBlock.wordWrapToggle": { + "message": "切换自动换行", + "description": "The title attribute for toggle word wrapping button of code block lines" + }, + "theme.DocSidebarItem.expandCategoryAriaLabel": { + "message": "展开侧边栏分类 '{label}'", + "description": "The ARIA label to expand the sidebar category" + }, + "theme.DocSidebarItem.collapseCategoryAriaLabel": { + "message": "折叠侧边栏分类 '{label}'", + "description": "The ARIA label to collapse the sidebar category" + }, + "theme.NavBar.navAriaLabel": { + "message": "主导航", + "description": "The ARIA label for the main navigation" + }, + "theme.navbar.mobileLanguageDropdown.label": { + "message": "选择语言", + "description": "The label for the mobile language switcher dropdown" + }, + "theme.NotFound.p1": { + "message": "我们找不到您要找的页面。", + "description": "The first paragraph of the 404 page" + }, + "theme.NotFound.p2": { + "message": "请联系原始链接来源网站的所有者,并告知他们链接已损坏。", + "description": "The 2nd paragraph of the 404 page" + }, + "theme.TOCCollapsible.toggleButtonLabel": { + "message": "本页总览", + "description": "The label used by the button on the collapsible TOC component" + }, + "theme.blog.post.readMore": { + "message": "阅读更多", + "description": "The label used in blog post item excerpts to link to full blog posts" + }, + "theme.blog.post.readMoreLabel": { + "message": "阅读 {title} 的全文", + "description": "The ARIA label for the link to full blog posts from excerpts" + }, + "theme.blog.post.readingTime.plurals": { + "message": "阅读需 {readingTime} 分钟", + "description": "Pluralized label for \"{readingTime} min read\". Use as much plural forms (separated by \"|\") as your language support (see https://www.unicode.org/cldr/cldr-aux/charts/34/supplemental/language_plural_rules.html)" + }, + "theme.docs.breadcrumbs.home": { + "message": "主页面", + "description": "The ARIA label for the home page in the breadcrumbs" + }, + "theme.docs.sidebar.collapseButtonTitle": { + "message": "收起侧边栏", + "description": "The title attribute for collapse button of doc sidebar" + }, + "theme.docs.sidebar.collapseButtonAriaLabel": { + "message": "收起侧边栏", + "description": "The title attribute for collapse button of doc sidebar" + }, + "theme.docs.sidebar.navAriaLabel": { + "message": "文档侧边栏", + "description": "The ARIA label for the sidebar navigation" + }, + "theme.docs.sidebar.closeSidebarButtonAriaLabel": { + "message": "关闭导航栏", + "description": "The ARIA label for close button of mobile sidebar" + }, + "theme.docs.sidebar.toggleSidebarButtonAriaLabel": { + "message": "切换导航栏", + "description": "The ARIA label for hamburger menu button of mobile navigation" + }, + "theme.docs.sidebar.expandButtonTitle": { + "message": "展开侧边栏", + "description": "The ARIA label and title attribute for expand button of doc sidebar" + }, + "theme.docs.sidebar.expandButtonAriaLabel": { + "message": "展开侧边栏", + "description": "The ARIA label and title attribute for expand button of doc sidebar" + }, + "theme.navbar.mobileSidebarSecondaryMenu.backButtonLabel": { + "message": "← 回到主菜单", + "description": "The label of the back button to return to main menu, inside the mobile navbar sidebar secondary menu (notably used to display the docs sidebar)" + }, + "theme.SearchBar.seeAll": { + "message": "查看全部 {count} 个结果" + }, + "theme.SearchBar.label": { + "message": "搜索", + "description": "The ARIA label and placeholder for search button" + }, + "theme.SearchModal.searchBox.resetButtonTitle": { + "message": "清除查询", + "description": "The label and ARIA label for search box reset button" + }, + "theme.SearchModal.searchBox.cancelButtonText": { + "message": "取消", + "description": "The label and ARIA label for search box cancel button" + }, + "theme.SearchModal.startScreen.recentSearchesTitle": { + "message": "最近搜索", + "description": "The title for recent searches" + }, + "theme.SearchModal.startScreen.noRecentSearchesText": { + "message": "没有最近搜索", + "description": "The text when no recent searches" + }, + "theme.SearchModal.startScreen.saveRecentSearchButtonTitle": { + "message": "保存这个搜索", + "description": "The label for save recent search button" + }, + "theme.SearchModal.startScreen.removeRecentSearchButtonTitle": { + "message": "从历史记录中删除这个搜索", + "description": "The label for remove recent search button" + }, + "theme.SearchModal.startScreen.favoriteSearchesTitle": { + "message": "收藏", + "description": "The title for favorite searches" + }, + "theme.SearchModal.startScreen.removeFavoriteSearchButtonTitle": { + "message": "从收藏列表中删除这个搜索", + "description": "The label for remove favorite search button" + }, + "theme.SearchModal.errorScreen.titleText": { + "message": "无法获取结果", + "description": "The title for error screen of search modal" + }, + "theme.SearchModal.errorScreen.helpText": { + "message": "你可能需要检查网络连接。", + "description": "The help text for error screen of search modal" + }, + "theme.SearchModal.footer.selectText": { + "message": "选中", + "description": "The explanatory text of the action for the enter key" + }, + "theme.SearchModal.footer.selectKeyAriaLabel": { + "message": "Enter 键", + "description": "The ARIA label for the Enter key button that makes the selection" + }, + "theme.SearchModal.footer.navigateText": { + "message": "导航", + "description": "The explanatory text of the action for the Arrow up and Arrow down key" + }, + "theme.SearchModal.footer.navigateUpKeyAriaLabel": { + "message": "向上键", + "description": "The ARIA label for the Arrow up key button that makes the navigation" + }, + "theme.SearchModal.footer.navigateDownKeyAriaLabel": { + "message": "向下键", + "description": "The ARIA label for the Arrow down key button that makes the navigation" + }, + "theme.SearchModal.footer.closeText": { + "message": "关闭", + "description": "The explanatory text of the action for Escape key" + }, + "theme.SearchModal.footer.closeKeyAriaLabel": { + "message": "Esc 键", + "description": "The ARIA label for the Escape key button that close the modal" + }, + "theme.SearchModal.footer.searchByText": { + "message": "搜索提供", + "description": "The text explain that the search is making by Algolia" + }, + "theme.SearchModal.noResultsScreen.noResultsText": { + "message": "没有结果:", + "description": "The text explains that there are no results for the following search" + }, + "theme.SearchModal.noResultsScreen.suggestedQueryText": { + "message": "试试搜索", + "description": "The text for the suggested query when no results are found for the following search" + }, + "theme.SearchModal.noResultsScreen.reportMissingResultsText": { + "message": "认为这个查询应该有结果?", + "description": "The text for the question where the user thinks there are missing results" + }, + "theme.SearchModal.noResultsScreen.reportMissingResultsLinkText": { + "message": "请告知我们。", + "description": "The text for the link to report missing results" + }, + "theme.SearchModal.placeholder": { + "message": "搜索文档", + "description": "The placeholder of the input of the DocSearch pop-up modal" + }, + "theme.SearchPage.documentsFound.plurals": { + "message": "找到 {count} 份文件", + "description": "Pluralized label for \"{count} documents found\". Use as much plural forms (separated by \"|\") as your language support (see https://www.unicode.org/cldr/cldr-aux/charts/34/supplemental/language_plural_rules.html)" + }, + "theme.SearchPage.existingResultsTitle": { + "message": "「{query}」的搜索结果", + "description": "The search page title for non-empty query" + }, + "theme.SearchPage.emptyResultsTitle": { + "message": "在文档中搜索", + "description": "The search page title for empty query" + }, + "theme.SearchPage.inputPlaceholder": { + "message": "在此输入搜索字词", + "description": "The placeholder for search page input" + }, + "theme.SearchPage.inputLabel": { + "message": "搜索", + "description": "The ARIA label for search page input" + }, + "theme.SearchPage.algoliaLabel": { + "message": "通过 Algolia 搜索", + "description": "The ARIA label for Algolia mention" + }, + "theme.SearchPage.noResultsText": { + "message": "未找到任何结果", + "description": "The paragraph for empty search result" + }, + "theme.SearchPage.fetchingNewResults": { + "message": "正在获取新的搜索结果...", + "description": "The paragraph for fetching new search results" + }, + "theme.ErrorPageContent.tryAgain": { + "message": "重试", + "description": "The label of the button to try again rendering when the React error boundary captures an error" + }, + "theme.common.skipToMainContent": { + "message": "跳到主要内容", + "description": "The skip to content label used for accessibility, allowing to rapidly navigate to main content with keyboard tab/enter navigation" + }, + "theme.tags.tagsPageTitle": { + "message": "标签", + "description": "The title of the tag list page" + }, + "theme.unlistedContent.title": { + "message": "未列出页", + "description": "The unlisted content banner title" + }, + "theme.unlistedContent.message": { + "message": "此页面未列出。搜索引擎不会对其索引,只有拥有直接链接的用户才能访问。", + "description": "The unlisted content banner message" + } +} diff --git a/i18n/zh-Hans/docusaurus-plugin-content-blog/options.json b/i18n/zh-Hans/docusaurus-plugin-content-blog/options.json new file mode 100644 index 000000000..9239ff706 --- /dev/null +++ b/i18n/zh-Hans/docusaurus-plugin-content-blog/options.json @@ -0,0 +1,14 @@ +{ + "title": { + "message": "Blog", + "description": "The title for the blog used in SEO" + }, + "description": { + "message": "Blog", + "description": "The description for the blog used in SEO" + }, + "sidebar.title": { + "message": "Recent posts", + "description": "The label for the left sidebar" + } +} diff --git a/i18n/zh-Hans/docusaurus-plugin-content-docs-neogradle/current.json b/i18n/zh-Hans/docusaurus-plugin-content-docs-neogradle/current.json new file mode 100644 index 000000000..bede4b8bf --- /dev/null +++ b/i18n/zh-Hans/docusaurus-plugin-content-docs-neogradle/current.json @@ -0,0 +1,14 @@ +{ + "version.label": { + "message": "FG6", + "description": "The label for version current" + }, + "sidebar.ngSidebar.category.ForgeGradle Configurations": { + "message": "ForgeGradle Configurations", + "description": "The label for category ForgeGradle Configurations in sidebar ngSidebar" + }, + "sidebar.ngSidebar.category.Dependencies": { + "message": "Dependencies", + "description": "The label for category Dependencies in sidebar ngSidebar" + } +} diff --git a/i18n/zh-Hans/docusaurus-plugin-content-docs-neogradle/version-5.x.json b/i18n/zh-Hans/docusaurus-plugin-content-docs-neogradle/version-5.x.json new file mode 100644 index 000000000..8a8d0d233 --- /dev/null +++ b/i18n/zh-Hans/docusaurus-plugin-content-docs-neogradle/version-5.x.json @@ -0,0 +1,18 @@ +{ + "version.label": { + "message": "FG5", + "description": "The label for version 5.x" + }, + "sidebar.ngSidebar.category.ForgeGradle Documentation": { + "message": "ForgeGradle Documentation", + "description": "The label for category ForgeGradle Documentation in sidebar ngSidebar" + }, + "sidebar.ngSidebar.category.ForgeGradle Configurations": { + "message": "ForgeGradle Configurations", + "description": "The label for category ForgeGradle Configurations in sidebar ngSidebar" + }, + "sidebar.ngSidebar.category.Dependencies": { + "message": "Dependencies", + "description": "The label for category Dependencies in sidebar ngSidebar" + } +} diff --git a/i18n/zh-Hans/docusaurus-plugin-content-docs/current.json b/i18n/zh-Hans/docusaurus-plugin-content-docs/current.json new file mode 100644 index 000000000..b1aac2141 --- /dev/null +++ b/i18n/zh-Hans/docusaurus-plugin-content-docs/current.json @@ -0,0 +1,78 @@ +{ + "version.label": { + "message": "1.20.x", + "description": "当前版本的标签" + }, + "sidebar.mainSidebar.category.Getting Started with Neo": { + "message": "开始使用Neo", + "description": "侧边栏mainSidebar中“开始使用Neo”类别的标签" + }, + "sidebar.mainSidebar.category.Advanced Topics": { + "message": "高级主题", + "description": "侧边栏mainSidebar中“高级主题”类别的标签" + }, + "sidebar.mainSidebar.category.Block Entities": { + "message": "方块实体", + "description": "侧边栏mainSidebar中“方块实体”类别的标签" + }, + "sidebar.mainSidebar.category.Blocks": { + "message": "方块", + "description": "侧边栏mainSidebar中“方块”类别的标签" + }, + "sidebar.mainSidebar.category.Concepts": { + "message": "概念", + "description": "侧边栏mainSidebar中“概念”类别的标签" + }, + "sidebar.mainSidebar.category.Datagen": { + "message": "数据生成", + "description": "侧边栏mainSidebar中“数据生成”类别的标签" + }, + "sidebar.mainSidebar.category.Data Maps": { + "message": "数据映射", + "description": "侧边栏mainSidebar中“数据映射”类别的标签" + }, + "sidebar.mainSidebar.category.Data Storage": { + "message": "数据存储", + "description": "侧边栏mainSidebar中“数据存储”类别的标签" + }, + "sidebar.mainSidebar.category.GUIs": { + "message": "图形用户界面", + "description": "侧边栏mainSidebar中“图形用户界面”类别的标签" + }, + "sidebar.mainSidebar.category.Items": { + "message": "物品", + "description": "侧边栏mainSidebar中“物品”类别的标签" + }, + "sidebar.mainSidebar.category.Legacy": { + "message": "遗留", + "description": "侧边栏mainSidebar中“遗留”类别的标签" + }, + "sidebar.mainSidebar.category.Miscellaneous": { + "message": "杂项", + "description": "侧边栏mainSidebar中“杂项”类别的标签" + }, + "sidebar.mainSidebar.category.Networking": { + "message": "网络", + "description": "侧边栏mainSidebar中“网络”类别的标签" + }, + "sidebar.mainSidebar.category.Resources": { + "message": "资源", + "description": "侧边栏mainSidebar中“资源”类别的标签" + }, + "sidebar.mainSidebar.category.Client": { + "message": "客户端", + "description": "侧边栏mainSidebar中“客户端”类别的标签" + }, + "sidebar.mainSidebar.category.Models": { + "message": "模型", + "description": "侧边栏mainSidebar中“模型”类别的标签" + }, + "sidebar.mainSidebar.category.Datapacks": { + "message": "数据包", + "description": "侧边栏mainSidebar中“数据包”类别的标签" + }, + "sidebar.mainSidebar.category.Recipes": { + "message": "配方", + "description": "侧边栏mainSidebar中“配方”类别的标签" + } +} \ No newline at end of file diff --git a/i18n/zh-Hans/docusaurus-plugin-content-docs/current/advanced/_category_.json b/i18n/zh-Hans/docusaurus-plugin-content-docs/current/advanced/_category_.json new file mode 100644 index 000000000..c392eef49 --- /dev/null +++ b/i18n/zh-Hans/docusaurus-plugin-content-docs/current/advanced/_category_.json @@ -0,0 +1,3 @@ +{ + "label": "Advanced Topics" +} \ No newline at end of file diff --git a/i18n/zh-Hans/docusaurus-plugin-content-docs/current/advanced/accesstransformers.mdx b/i18n/zh-Hans/docusaurus-plugin-content-docs/current/advanced/accesstransformers.mdx new file mode 100644 index 000000000..829efaf09 --- /dev/null +++ b/i18n/zh-Hans/docusaurus-plugin-content-docs/current/advanced/accesstransformers.mdx @@ -0,0 +1,167 @@ +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +# Access Transformers + +Access Transformers (简称 ATs) 允许开发者扩大类、方法和字段的可见性,以及修改其 `final` 属性。这些工具使得模组开发者能够访问和修改通常无法访问的类成员。 + +[规范文档][specs]可以在 NeoForged 的 GitHub 上查看。 + +## 添加 ATs + +将 Access Transformer 添加到您的 mod 项目与在 `build.gradle` 中添加一行代码一样简单: + + + +Access Transformers 需要在 `build.gradle` 和 `mods.toml` 中声明: + +```groovy +// 在 build.gradle 中: +// 此部分也是指定映射版本的地方 +minecraft { + accessTransformers { + file('src/main/resources/META-INF/accesstransformer.cfg') + } +} +``` + +```toml +# 在 mods.toml 中: +[[accessTransformers]] +file="META-INF/accesstransformer.cfg" +``` + +AT 文件可以位于上面几行指定的任何位置,但如果没有指定其他文件,NeoForge 将默认搜索 `META-INF/accesstransformer.cfg`。 + +此外,可以指定多个 AT 文件并按顺序应用。 这对于具有多个软件包的大型模组非常有用。 + +```groovy +// In build.gradle: +minecraft { + accessTransformers { + file('src/main/resources/accesstransformer_main.cfg') + file('src/additions/resources/accesstransformer_additions.cfg') + } +} +``` + +```toml +# In mods.toml +[[accessTransformers]] +file="accesstransformer_main.cfg" + +[[accessTransformers]] +file="accesstransformer_additions.cfg" +``` + +添加或修改任何访问转换器后,必须刷新 Gradle 项目,以便使更改生效。 + + + +在你的模组项目中添加一个 AT 也很简单,只需在 `build.gradle` 中添加一行代码: + +```groovy +// 这一部分也是你指定映射版本的地方 +minecraft { + accessTransformer = file('src/main/resources/META-INF/accesstransformer.cfg') +} +``` + +在开发环境中,AT 文件可以放在上述指定的任何位置。然而,在非开发环境中加载时,NeoForge 只会搜索你的 JAR 文件中 `META-INF/accesstransformer.cfg` 的确切路径。 + +添加或修改访问转换器后,必须刷新 Gradle 项目,以便使更改生效。 + + + +## 注释 + +`#` 之后直到行尾的所有文本都将被视为注释并且不会被解析。 + +## 访问控制修饰符 + +访问控制修饰符指明目标将被转换到的新的成员的可见性。按可见性递减的顺序排列如下: + +* `public` - 对包内外的所有类可见 +* `protected` - 只对包内的类和子类可见 +* `default` - 只对包内的类可见 +* `private` - 只对类的内部可见 + +特殊修饰符 `+f` 和 `-f` 可以分别添加到上述的修饰符之后,用以添加或移除 `final` 修饰符,当应用时可防止子类化、方法覆盖或字段修改。 + +:::danger +指令只修改直接引用的方法;任何覆盖的方法都不会被转换。建议确保转换的方法没有未转换覆盖的方法,否则这会限制其可见性,并可能导致 JVM 报错。 + +可以安全转换的方法示例包括 `private` 方法、`final` 方法(或 `final` 类中的方法)以及 `static` 方法。 +::: + +## 目标和指令 + +### 类 + +定位类的格式如下: + +``` + +``` + +内部类通过组合外部类的完全限定名和内部类名,使用 `$` 作为分隔符来表示。 + +### 字段 + +定位字段的格式如下: + +``` + +``` + +### 方法 + +定位方法需要特殊的语法来表示方法参数和返回类型: + +``` + () +``` + +#### 指定类型 + +也称为"描述符":有关更多技术细节,请参见 [Java 虚拟机规范,SE 8,第 4.3.2 节和 4.3.3 节][jvmdescriptors]。 + +* `B` - `byte`,有符号字节 +* `C` - `char`,Unicode 字符代码点,使用 UTF-16 编码 +* `D` - `double`,双精度浮点值 +* `F` - `float`,单精度浮点值 +* `I` - `integer`,32 位整数 +* `J` - `long`,64 位整数 +* `S` - `short`,有符号短整数 +* `Z` - `boolean`,`true` 或 `false` 值 +* `[` - 表示数组的一个维度 + * 示例: `[[S` 表示 `short[][]` +* `L;` - 表示引用类型 + * 示例: `Ljava/lang/String;` 表示 `java.lang.String` 引 + +用类型 _(注意使用斜线而非点)_ +* `(` - 表示方法描述符,参数应在此处提供,如果没有参数则不提供 + * 示例: `(I)Z` 表示一个需要整型参数并返回布尔值的方法 +* `V` - 表示方法不返回值,只能在方法描述符的末尾使用 + * 示例: `()V` 表示一个没有参数并且不返回任何结果的方法 + +## 示例 + +``` +# 将 Crypt 中的 ByteArrayToKeyFunction 接口公开 +public net.minecraft.util.Crypt$ByteArrayToKeyFunction + +# 将 MinecraftServer 中的 'random' 字段设为 protected 并移除 final 修饰 +protected-f net.minecraft.server.MinecraftServer random + +# 将 Util 中的 'makeExecutor' 方法公开, +# 接受一个 String 并返回一个 ExecutorService +public net.minecraft.Util makeExecutor(Ljava/lang/String;)Ljava/util/concurrent/ExecutorService; + +# 将 UUIDUtil 中的 'leastMostToIntArray' 方法公开, +# 接受两个 long 并返回一个 int 数组 +public net.minecraft.core.UUIDUtil leastMostToIntArray(JJ)[I +``` + +[specs]: https://github.com/NeoForged/AccessTransformers/blob/main/FMLAT.md +[jvmdescriptors]: https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.3.2 diff --git a/i18n/zh-Hans/docusaurus-plugin-content-docs/current/blockentities/ber.md b/i18n/zh-Hans/docusaurus-plugin-content-docs/current/blockentities/ber.md new file mode 100644 index 000000000..a83d801aa --- /dev/null +++ b/i18n/zh-Hans/docusaurus-plugin-content-docs/current/blockentities/ber.md @@ -0,0 +1,28 @@ +BlockEntityRenderer +================== + +`BlockEntityRenderer` 或 `BER` 用于以无法用静态烘焙模型(JSON,OBJ,B3D,其他)表示的方式渲染块。块实体渲染器要求块有一个 `BlockEntity`。 + +创建 BER +-------------- + +要创建 BER,创建一个继承自 `BlockEntityRenderer` 的类。它需要一个泛型参数,指定块的 `BlockEntity` 类。泛型参数在 BER 的 `render` 方法中使用。 + +对于给定的 `BlockEntityType`,只存在一个 BER。因此,应将特定于等级中的单个实例的值存储在传递给渲染器的块实体中,而不是在 BER 本身中。例如,每帧递增的整数,如果存储在 BER 中,将会在该类型的等级中的每一个块实体中每帧递增。 + +### `render` + +每一帧都会调用这个方法来渲染块实体。 + +#### 参数 +* `blockEntity`:这是正在渲染的块实体的实例。 +* `partialTick`:自上一完整 tick以来已经过去的以 tick 的分数表示的时间。 +* `poseStack`:这是一个堆栈,可以持有四维矩阵条目,这些条目可以偏移到块实体的当前位置。 +* `bufferSource`:一个渲染缓冲区,能够访问顶点消费者。 +* `combinedLight`:块实体上当前光值的整数。 +* `combinedOverlay`:一个设置为块实体当前覆盖层的整数,通常是 `OverlayTexture#NO_OVERLAY` 或 655,360。 + +注册 BER +----------------- + +要注册 BER,你必须订阅模组事件总线上的 `EntityRenderersEvent$RegisterRenderers` 事件,并调用 `#registerBlockEntityRenderer`。 diff --git a/i18n/zh-Hans/docusaurus-plugin-content-docs/current/blockentities/index.md b/i18n/zh-Hans/docusaurus-plugin-content-docs/current/blockentities/index.md new file mode 100644 index 000000000..84a2627e0 --- /dev/null +++ b/i18n/zh-Hans/docusaurus-plugin-content-docs/current/blockentities/index.md @@ -0,0 +1,125 @@ +## 注册方块实体 + +方块实体的创建和移除是动态的,因此它们本身不是注册对象。要创建一个`BlockEntity`,你需要扩展`BlockEntity`类。相应地,另一个对象被注册以方便创建和引用动态对象的*类型*。对于`BlockEntity`,这些类型被称为`BlockEntityType`。 + +`BlockEntityType`可以像其他注册对象一样被[注册][registration]。使用`BlockEntityType.Builder#of`来构建`BlockEntityType`,它接受两个参数:一个`BlockEntityType.BlockEntitySupplier`,它接受一个`BlockPos`和`BlockState`来创建新的`BlockEntity`实例,以及一个可变数量的`Block`,这些方块可以附加到此`BlockEntity`。 + +```java +// 对于某个DeferredRegister> REGISTER +public static final RegistryObject> MY_BE = REGISTER.register("mybe", () -> BlockEntityType.Builder.of(MyBE::new, validBlocks).build(null)); + +// 在MyBE中,一个BlockEntity子类 +public MyBE(BlockPos pos, BlockState state) { + super(MY_BE.get(), pos, state); +} +``` + +## 创建方块实体 + +要创建一个`BlockEntity`并将其附加到一个`Block`,你的`Block`子类必须实现`EntityBlock`接口。必须实现方法`EntityBlock#newBlockEntity(BlockPos, BlockState)`并返回你的`BlockEntity`的新实例。 + +## 存储你的方块实体内的数据 + +为了保存数据,覆盖以下两个方法: +```java +BlockEntity#saveAdditional(CompoundTag tag) + +BlockEntity#load(CompoundTag tag) +``` +这些方法在包含`BlockEntity`的`LevelChunk`从标签加载/保存时调用。使用这些方法读写你的方块实体类中的字段。 + +:::note +每当你的数据发生变化时,你需要调用`BlockEntity#setChanged`;否则,在级别保存时可能会跳过包含你的`BlockEntity`的`LevelChunk`。 +::: + +:::danger +调用`super`方法非常重要! + +标签名`id`、`x`、`y`、`z`、`ForgeData`和`ForgeCaps`由`super`方法保留。 +::: + +## `BlockEntities` 的定时器 + +如果你需要一个定时的方块实体,例如跟踪熔炼过程中的进度,那么必须在`EntityBlock`内实现并覆盖另一个方法:`EntityBlock#getTicker(Level, BlockState, BlockEntityType)`。这可以实现不同的定时器,取决于用户所在的逻辑侧,或者只实现一个通用定时器。无论哪种情况,都必须返回一个`BlockEntityTicker`。由于这是一个功能接口,它可以仅采用表示定时器的方法: + +```java +// 在某个Block子类内 +@Nullable +@Override +public BlockEntityTicker getTicker(Level level, BlockState state, BlockEntityType type) { + return type == MyBlockEntityTypes.MYBE.get() ? MyBlockEntity::tick : null; +} + +// 在MyBlockEntity内 +public static void tick(Level level, BlockPos pos, BlockState state, MyBlockEntity blockEntity) { + // 执行任务 +} +``` + +:::note +这个方法每个tick都会被调用;因此,你应该避免在这里进行复杂的计算。如果可能,你应该每X个ticks进行更复杂的计算。(一秒钟内的ticks数量可能低于20,但不会更高) +::: + +## 将数据同步到客户端 + +有三种方法可以将数据同步到客户端:在LevelChunk加载时同步,在方块更新时同步,以及使用自定义网络消息同步。 + +### 在LevelChunk加载时同步 + +为此,你需要覆盖 +```java +BlockEntity#getUpdateTag() + +IForgeBlockEntity#handleUpdateTag(CompoundTag tag) +``` +第一个方法收集应该发送到客户端的数据,而第二个方法处理这些数据。如果你的`BlockEntity`不包含太多数据,你可能可以使用[存储你的方块实体内的数据][storing-data]部分中的方法。 + +:::caution +同步过多/无用的方块实体数据可能导致网络拥塞。你应该优化你的网络使用,只在客户端需要时发送客户端需要的信息。例如,通常没有必要在更新标签中发送方块实体的库存,因为这可以通过其[`AbstractContainerMenu`][menu]同步。 +::: + +### 在方块更新时同步 + +这种方法有点复杂,但你只需覆盖两个或三个方法。这里是它的一个小示例实现: +```java +@Override +public CompoundTag getUpdateTag() { + CompoundTag tag = new CompoundTag(); + // 将你的数据写入标签 + return tag; +} + +@Override +public Packet getUpdatePacket() { + // 从#getUpdateTag获取标签 + return ClientboundBlockEntityDataPacket.create(this); +} + +// 可以覆盖IForgeBlockEntity#onDataPacket。默认情况下,这将推迟到#load。 +``` +静态构造函数`ClientboundBlockEntityDataPacket#create`接受: + +* `BlockEntity`。 +* 一个可选的函数,从`BlockEntity`获取`CompoundTag`。默认情况下,这使用`BlockEntity#getUpdateTag`。 + +现在,要发送数据包,服务器上必须给出更新通知。 +```java +Level#sendBlockUpdated(BlockPos pos, BlockState oldState, BlockState newState, int flags) +``` +`pos`应该是你的`BlockEntity`的位置。 +对于`oldState`和`newState`,你可以传递该位置当前的`BlockState`。 +`flags`是一个位掩码,应包含`2`,这将同步更改到客户端。有关更多信息以及其他标志,请参阅`Block`。标志`2`等同于`Block#UPDATE_CLIENTS`。 + +### 使用自定义网络消息同步 + +这种同步方式可能是最复杂的,但通常是最优化的,因为你可以确保只有你需要同步的数据实际上被同步。你应该首先查看[`Networking`][networking]部分,特别是[`SimpleImpl`][simple_impl],然后再尝试这种方式。一旦你创建了自定义网络消息,你可以使用`SimpleChannel#send(PacketDistributor$PacketTarget, MSG)`将其发送给加载了`BlockEntity`的所有用户。 + +:::caution +进行安全检查非常重要,当消息到达玩家时,`BlockEntity`可能已经被销毁/替换!你还应该检查块是否已加载(`Level#hasChunkAt(BlockPos)`)。 +::: + +[registration]: ../concepts/registries.md#methods-for-registering +[storing-data]: #storing-data-within-your-blockentity +[menu]: ../gui/menus.md +[networking]: ../networking/index.md +[simple_impl]: ../networking/simpleimpl.md diff --git a/i18n/zh-Hans/docusaurus-plugin-content-docs/current/blocks/index.md b/i18n/zh-Hans/docusaurus-plugin-content-docs/current/blocks/index.md new file mode 100644 index 000000000..95f1376da --- /dev/null +++ b/i18n/zh-Hans/docusaurus-plugin-content-docs/current/blocks/index.md @@ -0,0 +1,235 @@ +## 统一方块的规则 + +在开始之前,你需要明白在游戏中每个方块都只有一个。一个世界由成千上万个在不同位置引用该方块的实例组成。换句话说,同一个方块多次被显示。 + +因此,一个方块只应该在[注册]期间实例化一次。一旦注册了方块,你可以根据需要使用已注册的引用。 + +与大多数其他注册表不同,方块可以使用`DeferredRegister`的特殊版本,称为`DeferredRegister.Blocks`。 `DeferredRegister.Blocks`基本上就像`DeferredRegister`,但有一些细微的差别: + +- 它们是通过`DeferredRegister.createBlocks("yourmodid")`创建的,而不是通常的`DeferredRegister.create(...)`方法。 +- `#register`返回一个`DeferredBlock`,它扩展了`DeferredHolder`。 `T`是我们正在注册的方块类的类型。 +- 有一些帮助注册方块的方法。 更多详情请参见[下方]。 + +现在,让我们注册我们的方块: + +```java +//BLOCKS is a DeferredRegister.Blocks +public static final DeferredBlock MY_BLOCK = BLOCKS.register("my_block", () -> new Block(...)); +``` + +注册了方块后,所有对新的`my_block`的引用应使用此常量。例如,如果你想检查给定位置的方块是否是`my_block`,那么相应的代码看起来像这样: + +```java +level.getBlockState(position) //返回在给定位置放置的方块状态 + //highlight-next-line + .is(MyBlockRegistrationClass.MY_BLOCK.get()); +``` + +这种方法也有一个方便的效果,即`block1 == block2`有效,并且可以代替Java的`equals`方法使用(当然,使用`equals`仍然有效,但是因为它还是通过引用进行比较,所以没有意义)。 + +:::danger +不要在注册外部调用`new Block()`!一旦你那样做了,会出现问题: + +- 方块必须在注册表解锁时创建。NeoForge为您解锁注册表,并稍后再冻结它们,所以注册是您创建方块的时机窗口。 +- 如果你在注册表再次冻结时尝试创建和/或注册方块,游戏将崩溃并报告一个“null”方块,这可能会非常混乱。 +- 如果你仍然设法拥有一个悬空的方块实例,游戏在同步和保存时将不能识别它,并将其替换为空气。 +::: + +## 创建方块 + +如上所述,我们首先创建我们的`DeferredRegister.Blocks`: + +```java +public static final DeferredRegister.Blocks BLOCKS = DeferredRegister.createBlocks("yourmodid"); +``` + +### 基础方块 + +对于不需要特殊功能的简单方块(如圆石,木板等),可以直接使用`Block`类。要做到这一点,在注册期间,用`BlockBehaviour.Properties`参数实例化`Block`。可以使用`BlockBehaviour.Properties#of`创建此`BlockBehaviour.Properties`参数,并可以通过调用其方法进行定制。这其中最重要的方法是: + +- `destroyTime`-决定破坏方块所需的时间。 + - 石头的破坏时间为1.5,泥土为0.5,黑曜石为50,基岩为-1(不可破坏)。 +- `explosionResistance`-决定方块的抗爆性。 + - 石头的抗爆性为6.0,泥土为0.5,黑曜石为1,200,基岩为3,600,000。 +- `sound`-设置方块在被击中,打破或放置时的声音。 + - 默认值是`SoundType.STONE`。更多详细信息请参见[声音页面][sounds]。 +- `lightLevel`-设置方块的光线发射。接收一个带有`BlockState`参数的函数,返回0到15之间的值。 + 例如,萤石使用`state -> 15`,火炬使用`state -> 14`。 +- `摩擦` - 设置方块的摩擦(滑滑的程度)。 + - 默认值是0.6。冰使用0.98。 + +例如,一个简单的实现可能看起来像这样: + +```java +// BLOCKS is a DeferredRegister.Blocks +public static final DeferredBlock MY_BETTER_BLOCK = BLOCKS.register( + "my_better_block", + () -> new Block(BlockBehaviour.Properties.of() + //highlight-start + .destroyTime(2.0f) + .explosionResistance(10.0f) + .sound(SoundType.GRAVEL) + .lightLevel(state -> 7) + //highlight-end + )); +``` + +有关更多文档,请参阅`BlockBehaviour.Properties`的源代码。有关更多示例,或查看Minecraft使用的值,请查看`Blocks`类。 + +:::note +重要的是要理解,世界中的一个方块并不同于库存中的东西。库存中看起来像方块的其实是`BlockItem`,它是一种特殊类型的[物品],在使用时会放置一个方块。这也就意味着,创造标签页或最大堆叠大小等内容都由相应的`BlockItem`处理。 + +`BlockItem`必须与方块单独注册。这是因为方块不一定需要一个物品,例如,如果它不能被收集 (例如火)。 +### 更多功能 + +直接使用`Block`只能创造非常基本的方块。如果你想添加功能,像是玩家交互或不同的碰撞箱,就需要一个扩展了`Block`的自定义类。`Block`类有许多可以被重写以实现不同功能的方法;更多信息请参见`Block`、`BlockBehaviour`和`IBlockExtension`类。另外,请查看下方的[使用方块][usingblocks]部分,了解一些方块最常见的用例。 + +如果你想制作一个有不同变体的方块(想想一个有底部、顶部和双层变体的台阶),你应该使用[blockstates]。最后,如果你想要一个可以存储额外数据的方块(比如一个可以存储其库存的箱子),那么应该使用[block entity][blockentities]。这里的经验法则是,如果你有有限而且相当小的状态量(=最多几百个状态),使用blockstates;如果你有无限或近乎无限的状态量,使用方块实体。 + +### `DeferredRegister.Blocks` 辅助器 + +我们已经讨论了如何创建`DeferredRegister.Blocks`[上面],以及它返回`DeferredBlock`的内容。现在,让我们看看这个专门的`DeferredRegister`还有哪些辅助工具。我们先从`#registerBlock`开始: + +```java +public static final DeferredRegister.Blocks BLOCKS = DeferredRegister.createBlocks("yourmodid"); + +public static final DeferredBlock EXAMPLE_BLOCK = BLOCKS.registerBlock( + "example_block", + Block::new, // 将使用的属性传递到哪个工厂。 + BlockBehaviour.Properties.of() // 要使用的属性。 +); +``` + +在内部,这将简单地通过应用属性参数到所提供的方块工厂(通常是构造函数)来调用`BLOCKS.register("example_block", () -> new Block(BlockBehaviour.Properties.of()))`。 + +如果你想使用`Block::new`,可以完全不使用工厂: + +```java +public static final DeferredBlock EXAMPLE_BLOCK = BLOCKS.registerSimpleBlock( + "example_block", + BlockBehaviour.Properties.of() // 要使用的属性。 +); +``` + +这和之前的例子做的完全一样,只是稍微简洁了一些。当然,如果你想使用`Block`的子类而不是`Block`本身,你将不得不使用前面的方法。 + +### 资源 + +当你注册你的方块并将其放置在世界中时,你会发现它缺少如纹理等内容。这是因为[纹理]等内容是由Minecraft的资源系统处理的。要将纹理应用到方块上,你必须提供一个[模型]和一个与纹理和形状关联的[方块状态文件][bsfile]。阅读链接文章以获取更多信息。 + +## 使用方块 + +方块很少直接用来做事。实际上,可能在整个Minecraft中最常见的两个操作 - 获取位置上的方块,和设置位置上的方块 - 使用的是方块状态,而不是方块。一般的设计方法是让方块定义行为,但实际上通过方块状态来运行行为。因此,`BlockState`经常作为参数传递给`Block`的方法。有关如何使用方块状态的更多信息,以及如何从方块获取方块状态,请参见[使用方块状态][usingblockstates]。 + +在几种情况下,`Block`的多个方法在不同的时间被使用。以下小节列出了最常见的与方块相关的流程。除非另有说明,否则所有方法都在逻辑两侧调用,并应在两侧返回相同的结果。 + +### 放置方块 + +方块放置逻辑是从`BlockItem#useOn`(或其某些子类的实现,例如用于睡莲的`PlaceOnWaterBlockItem`)调用的。有关游戏如何到达这一点的更多信息,请参见[交互流程][interactionpipeline]。实际上,这意味着一旦`BlockItem`被右键点击(例如圆石物品),这个行为就被调用。 + +- 检查几个先决条件,例如你不是在旁观者模式下,所有要求的方块功能标志都已启用,或目标位置不在世界边界之外。如果至少有一个检查失败,流程结束。 +- 对当前位于被尝试放置方块的位置的方块调用`Block#canBeReplaced`。如果它返回`false`,流程结束。在这里返回`true`的显著案例是高草或雪层。 +- 调用`Block#getStateForPlacement`。这是根据上下文(包括位置,旋转和放置方块的侧面等信息)返回不同方块状态的地方。这对于例如可以以不同方向放置的方块很有用。 +- 用前一步获得的方块状态调用`Block#canSurvive`。如果返回`false`,流程结束。 +- 通过`Level#setBlock`调用将方块状态设置到游戏世界中。 + - 在那个`Level#setBlock`调用中,调用`Block#onPlace`。 +- 调用`Block#setPlacedBy`。 + +### 破坏方块 + +破坏方块稍微复杂一些,因为它需要时间。这个过程可以大致分为三个阶段:“启动”,“挖掘”和“实际破坏”。 + +- 当左键被点击时,进入“启动”阶段。 +- 现在,需要持续按住左键,进入“挖掘”阶段。**这个阶段的方法每个刻都会被调用。** +- 如果“继续”阶段没有被中断(通过释放左键)并且方块被打破,那么进入“实际破坏”阶段。 + +或者对于那些更喜欢伪代码的人: + +```java +leftClick(); +initiatingStage(); +while (leftClickIsBeingHeld()) { + miningStage(); + if (blockIsBroken()) { + actuallyBreakingStage(); + break; + } +} +``` + +以下小节进一步将这些阶段分解为实际的方法调用。 + +#### “启动”阶段 + +- 仅客户端:当左键和主手被触发时,会触发`InputEvent.InteractionKeyMappingTriggered`事件。如果事件被取消,流程结束。 +- 检查几个先决条件,例如你不是在旁观者模式下,主手中的`ItemStack`的所有必需功能标志都已启用,或被询问的方块不在世界边界之外。如果至少有一个检查失败,流程结束。 +- 触发`PlayerInteractEvent.LeftClickBlock`事件。如果事件被取消,流程结束。 + - 注意当事件在客户端被取消时,不会向服务器发送数据包,因此服务器上不会运行任何逻辑。 + - 然而,在服务器上取消此事件仍然会导致客户端代码运行,这可能会导致不同步! +- 调用`Block#attack`。 + +#### “挖掘”阶段 + +- 触发`PlayerInteractEvent.LeftClickBlock`事件。如果事件被取消,流程移动到“结束”阶段。 + - 注意当事件在客户端被取消时,不会向服务器发送数据包,因此服务器上不会运行任何逻辑。 + - 然而,在服务器上取消此事件仍然会导致客户端代码运行,这可能会导致不同步! +- 调用`Block#getDestroyProgress`并将其加到内部的破坏进度计数器上。 + - `Block#getDestroyProgress`返回一个介于0和1之间的浮点值,表示破坏进度计数器每个刻应该增加多少。 +- 相应地更新进度覆盖(破裂纹理)。 +- 如果破坏进度大于1.0(即完成,即方块应该被破坏),则退出“挖掘”阶段并进入“实际破坏”阶段。 + +#### “实际破坏”阶段 + +- 调用`Item#onBlockStartBreak`。如果它返回`true`(决定方块不应被破坏),流程移动到“结束”阶段。 +- 仅服务器:调用`IBlockExtension#canHarvestBlock`。这决定了方块是否可以被收获,即是否可以带着掉落物被破坏。 +- 调用`Block#onDestroyedByPlayer`。如果它返回`false`,流程移动到“结束”阶段。在`Block#onDestroyedByPlayer`调用中: + - 调用`Block#playerWillDestroy`。 + - 通过用`Blocks.AIR.defaultBlockState()`作为方块状态参数的`Level#setBlock`调用,从游戏世界中移除方块状态。 + - 在那个`Level#setBlock`调用中,调用`Block#onRemove`。 +- 调用`Block#destroy`。 +- 仅服务器:如果之前对`IBlockExtension#canHarvestBlock`的调用返回了`true`,则调用`Block#playerDestroy`。 +- 仅服务器:调用`IBlockExtension#getExpDrop`。 +- 仅服务器:如果之前`IBlockExtension#getExpDrop`调用的结果大于0,就调用`Block#popExperience`。 + +### 游戏刻 + +游戏刻是一种机制,它在每1/20秒或50毫秒(“一个游戏刻”)更新(游戏刻)游戏的某些部分。方块提供了不同的游戏刻方法,这些方法以不同的方式被调用。 + +#### 服务器游戏刻和游戏刻调度 + +`Block#tick`在两种情况下被调用:通过默认的[随机刻][randomtick](见下文),或通过调度的游戏刻。可以通过`Level#scheduleTick(BlockPos, Block, int)`创建调度的游戏刻,其中`int`表示延迟。这在vanilla的多个地方被使用,例如,大型滴叶的倾斜机制就严重依赖于这个系统。其他显著的使用者包括各种红石组件。 + +#### 客户端游戏刻 + +`Block#animateTick`仅在客户端,每帧被调用。这是发生客户端仅行为的地方,例如火炬粒子的生成。 + +#### 天气游戏刻 + +天气游戏刻由`Block#handlePrecipitation`处理,并独立于常规游戏刻运行。它仅在服务器上被调用,仅当以某种形式下雨时,有1/16的机会被调用。这被用于例如收集雨水或雪水的炼药锅。 + +#### 随机刻 + +随机刻系统独立于常规游戏刻运行。随机刻必须通过调用`BlockBehaviour.Properties`的`BlockBehaviour.Properties#randomTicks()`方法来启用。这使得方块可以是随机刻机制的一部分。 + +每个刻为一个区块中设定数量的方块执行随机刻。这个设定数量是通过`randomTickSpeed`游戏规则定义的。其默认值为3,每个刻,从区块中随机选择3个方块。如果这些方块启用了随机刻,则分别调用它们的`Block#randomTick`方法。 + +`Block#randomTick`默认调用`Block#tick`,这是通常应该被覆盖的。仅当你特别希望随机刻和常规(调度)游戏刻有不同行为时,才应覆盖`Block#randomTick`。 + +随机刻被Minecraft中的许多机制使用,例如植物生长、冰雪融化或铜氧化。 + +[above]: #one-block-to-rule-them-all +[below]: #deferredregisterblocks-helpers +[blockentities]: ../blockentities/index.md +[blockstates]: states.md +[bsfile]: ../resources/client/models/index.md#blockstate-files +[events]: ../concepts/events.md +[interactionpipeline]: ../items/interactionpipeline.md +[item]: ../items/index.md +[model]: ../resources/client/models/index.md +[randomtick]: #random-ticking +[registration]: ../concepts/registries.md#methods-for-registering +[resources]: ../resources/index.md#assets +[sounds]: ../resources/client/sounds.md +[textures]: ../resources/client/textures.md +[usingblocks]: #using-blocks +[usingblockstates]: states.md#using-blockstates diff --git a/i18n/zh-Hans/docusaurus-plugin-content-docs/current/blocks/states.md b/i18n/zh-Hans/docusaurus-plugin-content-docs/current/blocks/states.md new file mode 100644 index 000000000..9551668f3 --- /dev/null +++ b/i18n/zh-Hans/docusaurus-plugin-content-docs/current/blocks/states.md @@ -0,0 +1,151 @@ +Blockstates +=========== + +经常会遇到想要拥有不同状态的方块的情况。例如,小麦作物有八个生长阶段,为每个阶段制作一个单独的方块感觉并不合适。或者你有一个台阶或类台阶方块 - 一个底部状态、一个顶部状态,以及一个状态同时具有两者。 + +这就是Blockstates发挥作用的地方。Blockstates是一种表示方块可以具有的不同状态的简便方法,如生长阶段或台阶放置类型。 + +Blockstate 属性 +--------------------- + +Blockstates使用一套属性系统。一个方块可以具有多种类型的多个属性。例如,一个末地传送门框架有两个属性:它是否有一个眼睛(`eye`,2个选项)和放置的方向(`facing`,4个选项)。所以总的来说,末地传送门框架有8(2 * 4)个不同的Blockstates: + +``` +minecraft:end_portal_frame[facing=north,eye=false] +minecraft:end_portal_frame[facing=east,eye=false] +minecraft:end_portal_frame[facing=south,eye=false] +minecraft:end_portal_frame[facing=west,eye=false] +minecraft:end_portal_frame[facing=north,eye=true] +minecraft:end_portal_frame[facing=east,eye=true] +minecraft:end_portal_frame[facing=south,eye=true] +minecraft:end_portal_frame[facing=west,eye=true] +``` + +表示法 `blockid[property1=value1,property2=value,...]` 是表示文本形式Blockstate的标准方式,并且在游戏的某些地方使用,例如在命令中。 + +如果您的方块没有定义任何Blockstate属性,它仍然有一个Blockstate - 那就是没有任何属性的那个,因为没有属性需要指定。这可以表示为 `minecraft:oak_planks[]` 或者简单的 `minecraft:oak_planks`。 + +与方块一样,每个 `BlockState` 在内存中仅存在一个。这意味着可以并且应该使用 `==` 来比较 `BlockState`。`BlockState` 也是一个终极类,意味着它不能被扩展。**任何功能都在相应的[Block][block]类中!** + +何时使用Blockstates +----------------------- + +### Blockstates vs. 独立方块 + +一个好的经验法则是:**如果它有一个不同的名称,它应该是一个独立的方块**。一个例子是制作椅子方块:椅子的方向应该是一个属性,而不同类型的木头应该分成不同的方块。所以你会有一个椅子方块适用于每种木头类型,每个椅子方块有四个Blockstates(每个方向一个)。 + +### Blockstates vs. [方块实体][blockentity] + +这里的经验法则是:**如果你有一个有限的状态量,使用blockstate,如果你有一个无限或几乎无限的状态量,使用方块实体。** 方块实体可以存储任意量的数据,但比blockstates慢。 + +Blockstates和方块实体可以联合使用。例如,箱子使用Blockstate属性来表示诸如方向、是否被水淹没或成为双箱子等事物,同时通过方块实体存储库存、是否当前打开或与漏斗的互动等。 + +没有一个明确的答案来回答“对于Blockstate来说,多少状态太多了?”的问题,但我们建议,如果您需要超过8-9比特的数据(即超过几百种状态),您应该使用方块实体代替。 + +实现Blockstates +------------------------ + +要实现Blockstate属性,在您的方块类中创建或引用一个 `public static final Property` 常量。虽然您可以自由制作自己的 `Property` 实现,但游戏代码提供了几种便利实现,应该涵盖大多数用例: + +* `IntegerProperty` + * 实现 `Property`。定义一个持有整数值的属性。注意不支持负值。 + * 通过调用 `IntegerProperty#create(String propertyName, int minimum, int maximum)` 创建。 +* `BooleanProperty` + * 实现 `Property`。定义一个持有 `true` 或 `false` 值的属性。 + * 通过调用 `BooleanProperty#create(String propertyName)` 创建。 +* `EnumProperty>` + * 实现 `Property`。定义一个可以取枚举类值的属性。 + * 通过调用 `EnumProperty#create(String propertyName, Class enumClass)` 创建。 + * 还可以使用枚举值的子集(例如,16个`DyeColor`s中的4个),见 `EnumProperty#create` 的重载方法。 +* `DirectionProperty` + * `DirectionProperty` + * 扩展自 `EnumProperty`。定义了一个可以承载 `Direction`(方向)的属性。 + * 通过调用 `DirectionProperty#create(String propertyName)` 来创建。 + * 提供了几个便利的谓词方法。例如,要获取代表基本方向的属性,调用 `DirectionProperty.create("", Direction.Plane.HORIZONTAL)`;要获取X轴方向,调用 `DirectionProperty.create("", Direction.Axis.X)`。 + +类 `BlockStateProperties` 包含了共享的原版属性,这些属性应尽可能使用或引用,而不是创建自己的属性。 + +一旦你有了你的属性常量,在你的方块类中重写 `Block#createBlockStateDefinition(StateDefinition$Builder)`。在该方法中,调用 `StateDefinition.Builder#add(YOUR_PROPERTY);`。`StateDefinition.Builder#add` 有一个变长参数,所以如果你有多个属性,你可以一次性添加它们所有。 + +每个方块还有一个默认状态。如果没有指定其他内容,缺省状态使用每个属性的默认值。你可以通过从构造函数中调用 `Block#registerDefaultState(BlockState)` 方法来更改默认状态。 + +如果你希望改变放置方块时使用的 `BlockState`,请重写 `Block#getStateForPlacement(BlockPlaceContext)`。这可以用来设置方块的方向,比如基于玩家放置时的站立位置或方向。 + +为进一步说明,这是 `EndPortalFrameBlock` 类相关部分的样子: + +```java +public class EndPortalFrameBlock extends Block { + // 注意:直接使用 BlockStateProperties 中的值而不是在这里再次引用它们是可能的。 + // 然而,为了简单和可读性考虑,推荐像这样添加常量。 + public static final DirectionProperty FACING = BlockStateProperties.FACING; + public static final BooleanProperty EYE = BlockStateProperties.EYE; + + public EndPortalFrameBlock(BlockBehaviour.Properties pProperties) { + super(pProperties); + // stateDefinition.any() 返回一个内部集合中的随机 BlockState, + // 我们不在意,因为我们 anyway 要自己设置所有值 + registerDefaultState(stateDefinition.any() + .setValue(FACING, Direction.NORTH) + .setValue(EYE, false) + ); + } + + @Override + protected void createBlockStateDefinition(StateDefinition.Builder pBuilder) { + // 这里是属性实际被添加到状态的地方 + pBuilder.add(FACING, EYE); + } + + @Override + @Nullable + public BlockState getStateForPlacement(BlockPlaceContext pContext) { + // 根据 BlockPlaceContext 确定放置此方块时将使用的状态的 + // 代码 + } +} +``` + +使用 Blockstates +---------------- + +要从 `Block` 转换到 `BlockState`,调用 `Block#defaultBlockState()`。可以通过 `Block#registerDefaultState` 更改默认 blockstate,如上所述。 + +你可以通过调用 `BlockState#getValue(Property)` 来获取一个属性的值,传递你想获取值的属性。复用我们末地传送门框架的例子,这看起来像这样: + +```java +// EndPortalFrameBlock.FACING 是一个 DirectionProperty,因此可以用来从 BlockState 中获取一个 Direction +Direction direction = endPortalFrameBlockState.getValue(EndPortalFrameBlock.FACING); +``` + +如果你想获得一个具有不同值集的 `BlockState`,只需在现有的 block state 上调用 `BlockState#setValue(Property, T)` 并传入属性及其值。对于我们的杠杆来说,像这样: + +```java +endPortalFrameBlockState = endPortalFrameBlockState.setValue(EndPortalFrameBlock.FACING, Direction.SOUTH); +``` + +:::note +`BlockState` 是不可变的。这意味着当你调用 `#setValue(Property, T)` 时,你实际上不是在修改 blockstate。相反,内部进行了查找,你得到了你请求的 blockstate 对象,那是唯一存在的、具有这些确切属性值的对象。这也意味着,仅仅调用 `state#setValue` 而没有将其保存到一个变量中(例如回到 `state` 中)是无效的。 +::: + +要从场景中获取一个 `BlockState`,使用 `Level#getBlockState(BlockPos)`。 + +### `Level#setBlock` + +要在场景中设置一个 `BlockState`,使用 `Level#setBlock(BlockPos, BlockState, int)`。 + +`int` 参数需要额外的解释,因为它的含义不立即明显。它表示更新标志。 + +为了正确设置更新标志,`Block` 中有一些以 `UPDATE_` 开头的 `int` 常量。这些常量可以被按位或在一起(例如 `Block.UPDATE_NEIGHBORS | Block.UPDATE_CLIENTS`)如果你希望组合它们。 + +- `Block.UPDATE_NEIGHBORS` 向相邻方块发送更新。更具体地说,它调用了 `Block#neighborChanged`,它调用了许多方法,其中大部分以某种方式与红石相关。 +- `Block.UPDATE_CLIENTS` 将方块更新同步到客户端。 +- `Block.UPDATE_INVISIBLE` 显式不在客户端更新。这也覆盖了 `Block.UPDATE_CLIENTS`,导致更新不被同步。方块始终在服务器上更新。 +- `Block.UPDATE_IMMEDIATE` 强制在客户端的主线程上重新渲染。 +- `Block.UPDATE_KNOWN_SHAPE` 停止邻居更新递归。 +- `Block.UPDATE_SUPPRESS_DROPS` 禁止旧方块在该位置的掉落物。 +- `Block.UPDATE_MOVE_BY_PISTON` 仅被活塞代码用于表示方块被活塞移动。这主要是为了延迟光照引擎的更新。 +- `Block.UPDATE_ALL` 是 `Block.UPDATE_NEIGHBORS | Block.UPDATE_CLIENTS` 的别名。 +- `Block.UPDATE_ALL_IMMEDIATE` 是 `Block.UPDATE_NEIGHBORS | Block.UPDATE_CLIENTS | Block.UPDATE_IMMEDIATE` 的别名。 +- `Block.NONE` 是 `Block.UPDATE_INVISIBLE` 的别名。 + +还有一个便利方法 `Level#setBlockAndUpdate(BlockPos pos, BlockState state)`,它在内部调用 `setBlock(pos, state, Block.UPDATE_ALL)`。 diff --git a/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/_category_.json b/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/_category_.json new file mode 100644 index 000000000..5e1f44f64 --- /dev/null +++ b/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/_category_.json @@ -0,0 +1,3 @@ +{ + "label": "Concepts" +} \ No newline at end of file diff --git a/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/events.md b/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/events.md new file mode 100644 index 000000000..83debfc15 --- /dev/null +++ b/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/events.md @@ -0,0 +1,137 @@ +# 事件系统 + +NeoForge 的核心特性之一是其事件系统。在游戏中,各种事件根据游戏内的不同动作而触发。比如玩家右键点击、玩家或其他实体跳跃、方块渲染、游戏加载时等,都会触发相应的事件。模组开发者可以为这些事件编写处理函数,并在函数中实现他们期望的行为。 + +这些事件会在相应的事件总线上触发。其中最重要的是 `NeoForge.EVENT_BUS`。此外,在游戏启动期间,系统会为每个加载的模组生成一个独立的模组总线,并传递给模组的构造函数。许多模组总线事件是并行触发的,这与总在同一个线程上运行的主总线事件不同,这种设计显著提高了启动速度。更多细节,请参考[下文][modbus]。 + +## 注册事件处理函数 + +注册事件处理函数有多种方式。所有这些方式的共同点是,每个事件处理函数都是一个只接收单一事件参数并且不返回结果(即返回类型为 `void`)的方法。 + +### `IEventBus#addListener` + +最简单的注册方法是直接引用方法,如下所示: + +```java +@Mod("yourmodid") +public class YourMod { + public YourMod(IEventBus modBus) { + NeoForge.EVENT_BUS.addListener(YourMod::onLivingJump); + } + + // 每次实体跳跃时为其恢复半颗心的生命值。 + private static void onLivingJump(LivingJumpEvent event) { + Entity entity = event.getEntity(); + // 仅在服务器端进行治疗 + if (!entity.level().isClientSide()) { + entity.heal(1); + } + } +} +``` + +### `@SubscribeEvent` + +另一种方式是使用注解来驱动事件处理,为处理函数添加 `@SubscribeEvent` 注解。然后将包含该处理函数的类的实例传递给事件总线,从而注册该实例中所有带有 `@SubscribeEvent` 注解的事件处理函数: + +```java +public class EventHandler { + @SubscribeEvent + public void onLivingJump(LivingJumpEvent event) { + Entity entity = event.getEntity(); + if (!entity.level().isClientSide()) { + entity.heal(1); + } + } +} + +@Mod("yourmodid") +public class YourMod { + public YourMod(IEventBus modBus) { + NeoForge.EVENT_BUS.addListener(new EventHandler()); + } +} +``` + +你还可以通过将所有事件处理函数设置为静态,并直接传递类本身,而不是类的实例来实现: + +```java +public class EventHandler { + @SubscribeEvent + public static void onLivingJump(LivingJumpEvent event) { + Entity entity = event.getEntity(); + if (!entity.level().isClientSide()) { + entity.heal(1); + } + } +} + +@Mod("yourmodid") +public class YourMod { + public YourMod(IEventBus modBus) { + NeoForge.EVENT_BUS.addListener(EventHandler.class); + } +} +``` + +### `@Mod.EventBusSubscriber` + +我们可以进一步优化,将事件处理类标注为 `@Mod.EventBusSubscriber`。这个注解会被 NeoForge 自动识别,允许你从模组构造函数中移除所有与事件相关的代码。实际上,这等同于在模组构造结束时调用 `NeoForge.EVENT_BUS.register(EventHandler.class)`。这也意味着所有的处理函数必须设置为静态。 + +虽然不是必须的,但强烈建议在注解中指定 `modid` 参数,以便在处理模组冲突时能够更容易进行调试。 + +```java +@Mod.EventBusSubscriber(modid = "yourmodid") +public class EventHandler { + @SubscribeEvent + public static void onLivingJump(LivingJumpEvent event) { + Entity entity = event.getEntity(); + if (!entity.level().isClientSide()) { + entity.heal(1); + } + } +} +``` + +### 生命周期事件 + +大多数模组总 + +线事件被称为生命周期事件。生命周期事件在每个模组的生命周期中仅在启动时运行一次。很多这类事件是并行触发的,如果你想要在主线程上运行这些事件的代码,可以使用 `#enqueueWork(Runnable runnable)` 方法将它们加入队列。 + +生命周期事件通常按以下顺序进行: + +- 调用模组构造函数。在这里或下一步注册你的事件处理函数。 +- 所有的 `@Mod.EventBusSubscriber` 被调用。 +- 触发 `FMLConstructModEvent` 事件。 +- 触发注册事件,包括 [`NewRegistryEvent`][newregistry]、[`DataPackRegistryEvent.NewRegistry`][newdatapackregistry] 以及每个注册表的 [`RegisterEvent`][registerevent]。 +- 触发 `FMLCommonSetupEvent` 事件。这是进行各种杂项设置的阶段。 +- 根据服务器类型触发侧边设置事件:如果在客户端,则为 `FMLClientSetupEvent`;如果在服务器,则为 `FMLDedicatedServerSetupEvent`。 +- 处理 `InterModComms`(详情见下文)。 +- 触发 `FMLLoadCompleteEvent` 事件。 + +#### `InterModComms` + +`InterModComms` 是一个系统,允许模组开发者向其他模组发送消息以实现功能兼容。这个系统保存了模组的消息,所有方法都是线程安全的。主要通过两个事件推动:`InterModEnqueueEvent` 和 `InterModProcessEvent`。 + +在 `InterModEnqueueEvent` 期间,你可以使用 `InterModComms#sendTo` 向其他模组发送消息。这些方法接受要发送消息到的模组的 ID、与消息数据相关的键(以区分不同的消息),以及持有消息数据的 `Supplier`。发送者可以选择性指定。 + +接着,在 `InterModProcessEvent` 期间,你可以使用 `InterModComms#getMessages` 获取作为 `IMCMessage` 对象的所有接收到的消息的流。这些消息包含了数据的发送者、预期的接收者、数据键和实际数据的供应商。 + +### 其他模组总线事件 + +除了生命周期事件外,还有一些其他在模组总线上触发的杂项事件,主要是出于历史原因。这些事件通常不是并行运行的,与生命周期事件相反。例如: + +- `RegisterColorHandlersEvent` +- `ModelEvent.BakingCompleted` +- `TextureStitchEvent` + +:::warning +计划在未来版本中将大多数这些事件转移到主事件总线上。 +::: + +[modbus]: #event-buses +[newdatapackregistry]: registries.md#custom-datapack-registries +[newregistry]: registries.md#custom-registries +[registerevent]: registries.md#registerevent +[side]: sides.md diff --git a/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/registries.md b/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/registries.md new file mode 100644 index 000000000..c959c41e3 --- /dev/null +++ b/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/registries.md @@ -0,0 +1,320 @@ +# 注册 + +注册是将模组中的对象(如[物品][item]、[方块][block]、实体等)加入游戏并使其被游戏识别的过程。注册这些对象非常重要,因为如果不注册,游戏将无法识别这些对象,这将导致无法解释的行为和崩溃。 + +简而言之,注册表是围绕映射注册名称(下面将说明)到注册对象的映射的封装,这些注册对象通常称为注册表项。注册名在同一注册表中必须唯一,但同一注册名可以出现在多个注册表中。最常见的例子是方块(在`BLOCKS`注册表中)具有与其同名的物品形式(在`ITEMS`注册表中)。 + +每个注册对象都有一个唯一的名称,称为其注册名称。名称表示为[`ResourceLocation`][resloc]。例如,泥土方块的注册名称为`minecraft:dirt`,僵尸的注册名称为`minecraft:zombie`。当然,模组化对象不会使用`minecraft`命名空间;而是使用它们的模组ID。 + +## 原版与模组化 + +为了理解NeoForge的注册系统中所做的一些设计决策,我们首先看看Minecraft是如何处理这一问题的。我们将使用方块注册表作为例子,因为大多数其他注册表的工作方式相同。 + +注册表通常注册[单例][singleton]。这意味着所有注册表项实际上只存在一次。例如,你在游戏中看到的所有石块实际上都是同一个石块,被多次显示。如果你需要石块,可以通过引用已注册的方块实例来获取它。 + +Minecraft在`Blocks`类中注册所有方块。通过`register`方法,调用`Registry#register()`,其中第一个参数是方块注册表`BuiltInRegistries.BLOCK`。所有方块注册完成后,Minecraft会根据方块列表进行各种检查,例如验证所有方块是否已加载模型的自检。 + +这一切之所以能够工作,是因为`Blocks`类在Minecraft中足够早地被类加载。模组并不会被Minecraft自动类加载,因此需要一些变通方法。 + +## 注册方法 + +NeoForge提供了两种注册对象的方式:`DeferredRegister`类和`RegisterEvent`。请注意,前者是后者的封装,并且为了防止错误,推荐使用。 + +### `DeferredRegister` + +我们首先创建我们的`DeferredRegister`: + +```java +public static final DeferredRegister BLOCKS = DeferredRegister.create( + // 我们想要使用的注册表。 + // Minecraft的注册表可以在BuiltInRegistries中找到,NeoForge的注册表可以在NeoForgeRegistries中找到。 + // 模组也可以添加它们自己的注册表,具体请参考各个模组的文档或源代码。 + BuiltInRegistries.BLOCKS, + // 我们的模组ID。 + ExampleMod.MOD_ID +); +``` + +然后,我们可以将我们的注册表项添加为静态最终字段(有关在`new Block()`中添加什么参数,请参阅[关于方块的文章][block]): + +```java +public static final DeferredHolder EXAMPLE_BLOCK = BLOCKS.register( + "example_block" // 我们的注册名称。 + () -> new Block(...) // 我们想要注册的对象的供应商。 +); +``` + +`DeferredHolder`类持有我们的对象。类型参数`R`是我们正在注册到的注册表的类型(在这个例子中是`Block`)。类型参数`T`是我们供应商的类型。由于我们在这个例子中直接注册了一个`Block`,我们提供了`Block`作为第二个参数。如果我们 + +要注册一个`Block`的子类的对象,例如`SlabBlock`,我们将在此提供`SlabBlock`。 + +`DeferredHolder`是`Supplier`的子类。当我们需要时可以调用`DeferredHolder#get()`来获取我们注册的对象。`DeferredHolder`扩展`Supplier`的事实也允许我们使用`Supplier`作为我们字段的类型。这样,上面的代码块变为以下内容: + +```java +public static final Supplier EXAMPLE_BLOCK = BLOCKS.register( + "example_block" // 我们的注册名称。 + () -> new Block(...) // 我们想要注册的对象的供应商。 +); +``` + +请注意,有些地方明确要求使用`Holder`或`DeferredHolder`,而不仅仅接受任何`Supplier`。如果你需要其中的两者之一,最好将你的`Supplier`的类型更改回`Holder`或`DeferredHolder`。 + +最后,由于整个系统是围绕注册事件的封装,我们需要告诉`DeferredRegister`根据需要将自己附加到注册事件上: + +```java +// 这是我们的模组构造函数 +public ExampleMod(IModEventBus bus) { + // 高亮下一行 + ExampleBlocksClass.BLOCKS.register(bus); + // 这里还有其他内容 +} +``` + +:::info +有针对方块和物品的`DeferredRegister`的特化变体,它们提供辅助方法,分别称为[`DeferredRegister.Blocks`][defregblocks]和[`DeferredRegister.Items`][defregitems]。 +::: + +### `RegisterEvent` + +`RegisterEvent`是注册对象的第二种方式。这个[事件][event]在模组构造函数之后(因为这是`DeferredRegister`注册它们内部事件处理器的地方)和加载配置之前为每个注册表触发。`RegisterEvent`在模组事件总线上触发。 + +```java +@SubscribeEvent +public void register(RegisterEvent event) { + event.register( + // 这是注册表的注册键。 + // 从BuiltInRegistries获取vanilla注册表的, + // 或从NeoForgeRegistries.Keys获取NeoForge注册表的。 + BuiltInRegistries.BLOCKS, + // 在这里注册你的对象。 + registry -> { + registry.register(new ResourceLocation(MODID, "example_block_1"), new Block(...)); + registry.register(new ResourceLocation(MODID, "example_block_2"), new Block(...)); + registry.register(new ResourceLocation(MODID, "example_block_3"), new Block(...)); + } + ); +} +``` + +## 查询注册表 + +有时候,你可能会发现自己处于想要通过给定ID获取注册对象的情况,或者你想要获取某个注册对象的ID。由于注册表本质上是ID(`ResourceLocation`)到独立对象的映射,即可逆映射,这两种操作都是可行的: + +```java +BuiltInRegistries.BLOCKS.get(new ResourceLocation("minecraft", "dirt")); // 返回泥土方块 +BuiltInRegistries.BLOCKS.getKey(Blocks.DIRT); // 返回资源位置"minecraft:dirt" + +// 假设ExampleBlocksClass.EXAMPLE_BLOCK.get()是具有ID"yourmodid:example_block"的Supplier +BuiltInRegistries.BLOCKS.get(new ResourceLocation("yourmodid", "example_block")); // 返回示例方块 +BuiltInRegistries.BLOCKS.getKey(ExampleBlocksClass.EXAMPLE_BLOCK.get()); // 返回资源位置"yourmodid:example_block" +``` + +如果你只是想检查是否存在某个对象,这也是可能的,尽管只能用键: + +```java +BuiltInRegistries.BLOCKS.containsKey(new ResourceLocation("minecraft", "dirt")); // true +BuiltInRegistries.BLOCKS.containsKey(new ResourceLocation("create", "brass_ingot")); // 如果安装了Create则为true +``` + +正如最后一个示例所示,这适用于任何模组ID,因此是检查另一个模组中是否存在某个物品的完美方式。 + +最后,我们还可以迭代注册表中的所有条目,无论是键还 + +是条目(条目使用Java的`Map.Entry`类型): + +```java +for (ResourceLocation id : BuiltInRegistries.BLOCKS.keySet()) { + // ... +} +for (Map.Entry entry : BuiltInRegistries.BLOCKS.entrySet()) { + // ... +} +``` + +:::note +查询操作始终使用vanilla `Registry`,而不是`DeferredRegister`。这是因为`DeferredRegister`只是注册工具。 +::: + +:::danger +查询操作只有在注册完成后才安全使用。**不要在注册仍在进行时查询注册表!** +::: + +## 自定义注册表 + +自定义注册表允许你指定其他模组可能想要接入的附加系统。例如,如果你的模组要添加效果,你可以使效果成为一个注册表,从而允许其他模组添加效果到你的模组中,而无需你做任何其他事情。它还允许你自动执行一些操作,如同步条目。 + +让我们从创建[注册表键][resourcekey]和注册表本身开始: + +```java +// 我们在这里使用效果作为注册表的例子,不涉及效果实际是什么(因为这不重要)。 +// 当然,所有提到的效果都可以并且应该替换为你的注册表实际是什么。 +public static final ResourceKey> SPELL_REGISTRY_KEY = ResourceKey.createRegistryKey(new ResourceLocation("yourmodid", "spells")); +public static final Registry SPELL_REGISTRY = new RegistryBuilder<>(SPELL_REGISTRY_KEY) + // If you want to enable integer id syncing, for networking. + // These should only be used in networking contexts, for example in packets or purely networking-related NBT data. + .sync(true) + // The default key. Similar to minecraft:air for blocks. This is optional. + .defaultKey(new ResourceLocation("yourmodid", "empty")) + // Effectively limits the max count. Generally discouraged, but may make sense in settings such as networking. + .maxId(256) + // Build the registry. + .create(); +``` + +然后,通过将注册表注册到 `NewRegistryEvent` 中的根注册表来告诉游戏注册表存在: + +```java +@SubscribeEvent +static void registerRegistries(NewRegistryEvent event) { + event.register(SPELL_REGISTRY); +} +``` + +现在,您可以像使用任何其他注册表一样,通过`DeferredRegister`和`RegisterEvent`注册新的注册表内容: +```java +public static final DeferredRegister SPELLS = DeferredRegister.create("yourmodid", SPELL_REGISTRY); +public static final Supplier EXAMPLE_SPELL = SPELLS.register("example_spell", () -> new Spell(...)); + +// Alternatively: +@SubscribeEvent +public static void register(RegisterEvent event) { + event.register(SPELL_REGISTRY, registry -> { + registry.register(new ResourceLocation("yourmodid", "example_spell"), () -> new Spell(...)); + }); +} +``` + +# 数据包注册表 + +数据包注册表(也称为动态注册表或世界生成注册表)是一种特殊的注册表,它在世界加载时从数据包 JSON 文件中加载数据,而不是在游戏启动时加载。默认的数据包注册表主要包括大多数世界生成注册表和其他一些注册表。 + +数据包注册表允许通过 JSON 文件指定其内容。这意味着除了数据生成工具外,不需要任何代码(如果你不想自己编写 JSON 文件的话)。每个数据包注册表都有一个与之关联的编解码器(`Codec`),用于序列化,每个注册表的 ID 决定了其数据包路径: + +- Minecraft 的数据包注册表使用格式 `data/yourmodid/registrypath`(例如 `data/yourmodid/worldgen/biomes`,其中 `worldgen/biomes` 是注册表路径)。 +- 所有其他数据包注册表(NeoForge 或模组化的)使用格式 `data/yourmodid/registrynamespace/registrypath`(例如 `data/yourmodid/neoforge/loot_modifiers`,其中 `neoforge` 是注册表命名空间,`loot_modifiers` 是注册表路径)。 + +可以从 `RegistryAccess` 获取数据包注册表。如果在服务器上,可以通过调用 `ServerLevel#registryAccess()` 来检索此 `RegistryAccess`;如果在客户端,可以通过调用 `Minecraft.getInstance().connection#registryAccess()` 来检索(后者仅在实际连接到世界时有效,否则连接将为 null)。然后可以像使用任何其他注册表一样使用这些调用的结果来获取特定元素或遍历内容。 + +### 自定义数据包注册表 + +自定义数据包注册表不需要构建 `Registry`。相反,它们只需要一个注册表键和至少一个编解码器(`Codec`)来序列化和反序列化其内容。根据之前的法术示例,将我们的法术注册表注册为数据包注册表的过程如下所示: + +```java +public static final ResourceKey> SPELL_REGISTRY_KEY = ResourceKey.createRegistryKey(new ResourceLocation("yourmodid", "spells")); + +@SubscribeEvent +public static void registerDatapackRegistries(DataPackRegistryEvent.NewRegistry event) { + event.dataPackRegistry( + // 注册表键。 + SPELL_REGISTRY_KEY, + // 注册表内容的编解码器。 + Spell.CODEC, + // 网络编解码器。通常与普通编解码器相同。 + // 可能是普通编解码器的简化版本,省略了客户端不需要的数据。 + // 可能为 null。如果为 null,则注册表条目根本不会同步到客户端。 + // 可以省略,这在功能上与传递 null 相同(调用了一个带有两个参数的方法重载,该重载向普通的三参数方法传递 null)。 + Spell.CODEC + ); +} +``` + +### 数据包注册表的数据生成 + +由于手工编写所有 JSON 文件既繁琐又容易出错,NeoForge 提供了一个数据提供器来为你生成 JSON 文件。这适用于内置的和你自己的数据包注册表。 + +首先,我们创建一个 `RegistrySetBuilder` 并向其添加条目(一个 `RegistrySetBuilder` 可以包含多个注册表的条目): + +```java +new RegistrySetBuilder() + .add(Registries.CONFIGURED_FEATURE, bootstrap -> { + // 通过引导上下文注册配置特性。 + }) + .add(Registries.PLACED_FEATURE, bootstrap -> { + // 通过引导上下文注册放置特性。 + }); +``` + +`bootstrap` lambda 参数是我们实际用来注册对象的。要注册一个对象,我们这样调用 `#register`: + +```java +// 我们对象的资源键。 +public static final ResourceKey> EXAMPLE_CONFIGURED_FEATURE = ResourceKey.create( + Registries.CONFIGURED_FEATURE, + new ResourceLocation(MOD_ID, "example_configured_feature + +") +); + +new RegistrySetBuilder() + .add(Registries.CONFIGURED_FEATURE, bootstrap -> { + bootstrap.register( + // 我们配置特性的资源键。 + EXAMPLE_CONFIGURED_FEATURE, + // 实际的配置特性。 + new ConfiguredFeature<>(Feature.ORE, new OreConfiguration(...)) + ); + }) + .add(Registries.PLACED_FEATURE, bootstrap -> { + // ... + }); +``` + +如果需要,`BootstrapContext`(在 1.20.4 及以下版本中名称误写为 `BootstapContext`)还可以用来从另一个注册表查找条目: + +```java +public static final ResourceKey> EXAMPLE_CONFIGURED_FEATURE = ResourceKey.create( + Registries.CONFIGURED_FEATURE, + new ResourceLocation(MOD_ID, "example_configured_feature") +); +public static final ResourceKey EXAMPLE_PLACED_FEATURE = ResourceKey.create( + Registries.PLACED_FEATURE, + new ResourceLocation(MOD_ID, "example_placed_feature") +); + +new RegistrySetBuilder() + .add(Registries.CONFIGURED_FEATURE, bootstrap -> { + bootstrap.register(EXAMPLE_CONFIGURED_FEATURE, ...); + }) + .add(Registries.PLACED_FEATURE, bootstrap -> { + HolderGetter> otherRegistry = bootstrap.lookup(Registries.CONFIGURED_FEATURE); + bootstrap.register(EXAMPLE_PLACED_FEATURE, new PlacedFeature( + otherRegistry.getOrThrow(EXAMPLE_CONFIGURED_FEATURE), // 获取配置特性 + List.of() // 放置发生时无操作 - 替换为你的放置参数 + )); + }); +``` + +最后,我们在实际的数据提供器中使用我们的 `RegistrySetBuilder` 并将该数据提供器注册到事件中: + +```java +@SubscribeEvent +static void onGatherData(GatherDataEvent event) { + event.getGenerator().addProvider( + // 仅在生成服务器数据时运行数据包生成 + event.includeServer(), + // 创建提供器 + output -> new DatapackBuiltinEntriesProvider( + output, + event.getLookupProvider(), + // 我们的注册表集生成器来生成数据。 + new RegistrySetBuilder().add(...), + // 我们正在生成的模组 ID 集合。通常只有你自己的模组 ID。 + Set.of("yourmodid") + ) + ); +} +``` + +[block]: ../blocks/index.md +[blockentity]: ../blockentities/index.md +[codec]: ../datastorage/codecs.md +[datagen]: #data-generation-for-datapack-registries +[datagenindex]: ../resources/index.md#data-generation +[datapack]: ../resources/server/index.md +[defregblocks]: ../blocks/index.md#deferredregisterblocks-helpers +[defregitems]: ../items/index.md#deferredregisteritems +[event]: ./events.md +[item]: ../items/index.md +[resloc]: ../misc/resourcelocation.md +[resourcekey]: ../misc/resourcelocation.md#resourcekeys +[singleton]: https://en.wikipedia.org/wiki/Singleton_pattern diff --git a/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/sides.md b/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/sides.md new file mode 100644 index 000000000..e2154f25c --- /dev/null +++ b/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/sides.md @@ -0,0 +1,77 @@ +# 游戏两侧的区分 + +和许多其他程序一样,Minecraft 遵循客户端-服务器的概念,其中客户端负责显示数据,而服务器负责更新数据。当我们使用这些术语时,我们对其含义有一个相当直观的理解...对吗? + +事实证明,并非如此。很多混淆源于 Minecraft 有两种不同的“侧”的概念,这取决于上下文:物理侧和逻辑侧。 + +## 逻辑侧与物理侧 + +### 物理侧 + +当你打开 Minecraft 启动器,选择一个 Minecraft 安装并按下播放时,你启动了一个**物理客户端**。这里使用“物理”一词是在“这是一个客户端程序”的意义上。这尤其意味着客户端功能,如所有渲染相关的功能,都可以在这里使用。相比之下,**物理服务器**,也称为专用服务器,是在你启动一个 Minecraft 服务器 JAR 时打开的。虽然 Minecraft 服务器带有一个基本的 GUI,但它缺少所有仅限客户端的功能。最值得注意的是,各种客户端类在服务器 JAR 中缺失。在物理服务器上调用这些类将导致缺少类错误,即崩溃,因此我们需要对此进行防护。 + +### 逻辑侧 + +逻辑侧主要关注 Minecraft 的内部程序结构。**逻辑服务器**是游戏逻辑运行的地方。如时间和天气变化、实体更新、实体生成等都在服务器上运行。所有种类的数据,如库存内容,也都是服务器的责任。另一方面,**逻辑客户端**负责显示所有需要显示的内容。Minecraft 在一个名为 `net.minecraft.client` 的独立包中保留了所有客户端代码,并在一个名为渲染线程的独立线程中运行它,而其他所有内容都被视为公共代码(即客户端和服务器代码)。 + +### 两者有何区别? + +物理侧和逻辑侧之间的区别最好通过两种情况来说明: + +- 玩家加入一个**多人游戏**世界。这相当直接:玩家的物理(和逻辑)客户端连接到别处的一个物理(和逻辑)服务器——玩家不关心在哪里;只要他们能连接,这就是所有客户端知道的,也是所有客户端需要知道的。 +- 玩家加入一个**单人游戏**世界。这里的情况变得有趣。玩家的物理客户端启动了一个逻辑服务器,然后现在作为逻辑客户端,连接到同一台机器上的那个逻辑服务器。如果你熟悉网络,你可以把它想象为连接到`localhost`(只是概念上的;没有实际的套接字或类似的东西涉及)。 + +这两种情况也显示了主要问题:如果一个逻辑服务器可以使用你的代码,这并不保证物理服务器也能同样使用。这就是为什么你应该始终使用专用服务器进行测试,以检查意外行为。由于客户端和服务器分离不当导致的`NoClassDefFoundError`和`ClassNotFoundException`是模组制作中最常见的错误之一。另一个常见的错误是使用静态字段,并从两个逻辑侧访问它们;这特别棘手,因为通常没有迹象表明有什么问题。 + +:::tip +如果你需要将数据从一侧传输到另一侧,你必须[发送一个数据包 + +][networking]。 +::: + +在 NeoForge 代码库中,物理侧由一个名为 `Dist` 的枚举表示,而逻辑侧由一个名为 `LogicalSide` 的枚举表示。 + +:::info +从历史上看,服务器 JAR 拥有客户端没有的类。在现代版本中,这种情况已不复存在;如果愿意,可以认为物理服务器是物理客户端的一个子集。 +::: + +## 执行侧特定操作 + +### `Level#isClientSide()` + +这个布尔检查将是你最常用的检查侧的方式。在 `Level` 对象上查询此字段可以确定级别所属的**逻辑**侧:如果此字段为 `true`,则级别在逻辑客户端上运行。如果字段为 `false`,则级别在逻辑服务器上运行。据此,物理服务器将始终在此字段中包含 `false`,但我们不能假设 `false` 暗示物理服务器,因为此字段也可能在物理客户端内的逻辑服务器(即单人游戏世界)中为 `false`。 + +只有在需要确定是否运行游戏逻辑和其他机制时才使用此检查。例如,如果你想在玩家每次点击你的方块时对玩家造成伤害,或者让你的机器将泥土处理成钻石,你应该确保 `#isClientSide` 为 `false` 后再进行。在逻辑客户端应用游戏逻辑可能导致最好的情况下出现数据不同步(幽灵实体、不同步的统计数据等),在最坏的情况下导致崩溃。 + +:::tip +这个检查应该作为你的默认选择。每当你有一个 `Level` 可用时,就使用这个检查。 +::: + +### `FMLEnvironment.dist` + +`FMLEnvironment.dist` 是 `Level#isClientSide()` 检查的**物理**对应项。如果此字段为 `Dist.CLIENT`,你就在物理客户端上。如果字段为 `Dist.SERVER`,你就在物理服务器上。 + +检查物理环境在处理仅限客户端的类时非常重要。所有对客户端代码的调用都应始终包含在对 `Dist.CLIENT` 的检查中,并然后调用一个单独的类以防止意外的类加载: + +```java +public class SomeCommonClass { + public void someCommonMethod() { + // 仅当你在物理客户端上时,SomeClientClass 才会被加载 + if (FMLEnvironment.dist == Dist.CLIENT) { + SomeClientClass.someClientMethod(); + } + } +} + +public class SomeClientClass { + public static void someClientMethod() { + Minecraft.getInstance().whatever(); + } +} +``` + +:::tip +通常期望模组在任一侧都能工作。这特别意味着,如果你正在开发一个仅限客户端的模组,你应该验证该模组实际上在物理客户端上运行,并且在不运行的情况下无操作。 +::: + +[networking]: ../networking/index.md diff --git a/i18n/zh-Hans/docusaurus-plugin-content-docs/current/datagen/_category_.json b/i18n/zh-Hans/docusaurus-plugin-content-docs/current/datagen/_category_.json new file mode 100644 index 000000000..e3a3b9265 --- /dev/null +++ b/i18n/zh-Hans/docusaurus-plugin-content-docs/current/datagen/_category_.json @@ -0,0 +1,3 @@ +{ + "label": "Datagen" +} \ No newline at end of file diff --git a/i18n/zh-Hans/docusaurus-plugin-content-docs/current/datagen/advancements.md b/i18n/zh-Hans/docusaurus-plugin-content-docs/current/datagen/advancements.md new file mode 100644 index 000000000..bca701e5c --- /dev/null +++ b/i18n/zh-Hans/docusaurus-plugin-content-docs/current/datagen/advancements.md @@ -0,0 +1,68 @@ +# 成就生成 +====================== + +[成就] 可以通过构建一个新的 `AdvancementProvider` 并提供 `AdvancementSubProvider` 来为模组生成。成就可以手动创建和提供,或者为了方便,使用 `Advancement$Builder` 创建。提供者必须[添加][datagen]到 `DataGenerator` 中。 + +:::note +Forge 提供了一个扩展的 `AdvancementProvider`,名为 `ForgeAdvancementProvider`,它更适合生成成就。因此,本文档将使用 `ForgeAdvancementProvider` 以及子提供者接口 `ForgeAdvancementProvider$AdvancementGenerator`。 +::: + +```java +// 在 MOD 事件总线上 +@SubscribeEvent +public void gatherData(GatherDataEvent event) { + event.getGenerator().addProvider( + // 仅在生成服务器数据时运行生成器 + event.includeServer(), + output -> new ForgeAdvancementProvider( + output, + event.getLookupProvider(), + event.getExistingFileHelper(), + // 生成成就的子提供者 + List.of(subProvider1, subProvider2, /*...*/) + ) + ); +} +``` + +`ForgeAdvancementProvider$AdvancementGenerator` +----------------------------------------------- + +`ForgeAdvancementProvider$AdvancementGenerator` 负责生成成就,包含一个方法,该方法接收注册表查找、写入者 (`Consumer`) 和现有文件助手。 + +```java +// 在 ForgeAdvancementProvider$AdvancementGenerator 的某个子类中或作为 lambda 引用 + +@Override +public void generate(HolderLookup.Provider registries, Consumer writer, ExistingFileHelper existingFileHelper) { + // 在这里构建成就 +} +``` + +`Advancement$Builder` +--------------------- + +`Advancement$Builder` 是一个便利的实现,用于创建用于生成的 `Advancement`。它允许定义父成就、显示信息、完成成就时的奖励以及解锁成就的要求。只需指定要求即可创建一个 `Advancement`。 + +虽然不是必需的,但有几个方法是重要的: + +方法 | 描述 +:---: | :--- +`parent` | 设置此成就直接链接到的成就。可以指定成就的名称或如果由模组制作者生成,则指定成就本身。 +`display` | 设置显示在聊天、弹窗和成就屏幕上的信息。 +`rewards` | 设置完成此成就时获得的奖励。 +`addCriterion` | 为成就添加条件。 +`requirements` | 指定条件是否必须全部为真,或者至少有一个为真。可以使用额外的重载来混合这些操作。 + +一旦 `Advancement$Builder` 准备好建造,应调用 `#save` 方法,该方法需要写入者、成就的注册名和用于检查提供的父项是否存在的文件助手。 + +```java +// 在某个 ForgeAdvancementProvider$AdvancementGenerator#generate(registries, writer, existingFileHelper) 中 +Advancement example = Advancement.Builder.advancement() + .addCriterion("example_criterion", triggerInstance) // 如何解锁成就 + .save(writer, name, existingFileHelper); // 将数据添加到构建器 +``` + +[成就]: ../../resources/server/advancements.md +[datagen]: ../index.md#data-providers +[conditional]: ../../resources/server/conditional.md diff --git a/i18n/zh-Hans/docusaurus-plugin-content-docs/current/datagen/glm.md b/i18n/zh-Hans/docusaurus-plugin-content-docs/current/datagen/glm.md new file mode 100644 index 000000000..e7aafcebe --- /dev/null +++ b/i18n/zh-Hans/docusaurus-plugin-content-docs/current/datagen/glm.md @@ -0,0 +1,30 @@ +# 全局战利品修改器生成 +=============================== + +通过继承 `GlobalLootModifierProvider` 并实现 `#start` 方法,可以为模组生成[全局战利品修改器 (GLMs)][glm]。通过调用 `#add` 并指定修改器的名称和将被序列化的[修改器实例][instance],可以添加生成每个 GLM。实现后,必须将提供者[添加][datagen]到 `DataGenerator`。 + +```java +// 在 MOD 事件总线上 +@SubscribeEvent +public void gatherData(GatherDataEvent event) { + event.getGenerator().addProvider( + // 通知生成器仅在生成服务器数据时运行 + event.includeServer(), + output -> new MyGlobalLootModifierProvider(output, MOD_ID) + ); +} + +// 在某个 GlobalLootModifierProvider#start 中 +this.add("example_modifier", new ExampleModifier( + new LootItemCondition[] { + WeatherCheck.weather().setRaining(true).build() // 在下雨时执行 + }, + "val1", + 10, + Items.DIRT +)); +``` + +[glm]: ../../resources/server/glm.md +[instance]: ../../resources/server/glm.md#igloballootmodifier +[datagen]: ../index.md#data-providers diff --git a/i18n/zh-Hans/docusaurus-plugin-content-docs/current/datagen/loottables.md b/i18n/zh-Hans/docusaurus-plugin-content-docs/current/datagen/loottables.md new file mode 100644 index 000000000..da652a3ed --- /dev/null +++ b/i18n/zh-Hans/docusaurus-plugin-content-docs/current/datagen/loottables.md @@ -0,0 +1,148 @@ +# 战利品表生成 +===================== + +通过构建一个新的 `LootTableProvider` 并提供 `LootTableProvider$SubProviderEntry`,可以为模组生成[战利品表][loottable]。提供者必须被[添加][datagen]到 `DataGenerator`。 + +```java +// 在 MOD 事件总线上 +@SubscribeEvent +public void gatherData(GatherDataEvent event) { + event.getGenerator().addProvider( + // 告诉生成器仅在生成服务器数据时运行 + event.includeServer(), + output -> new MyLootTableProvider( + output, + // 指定需要生成的表的注册名称,或者可以留空 + Collections.emptySet(), + // 生成战利品的子提供者 + List.of(subProvider1, subProvider2, /*...*/) + ) + ); +} +``` + +`LootTableSubProvider` +---------------------- + +每个 `LootTableProvider$SubProviderEntry` 都接收一个 `LootTableSubProvider`,它为给定的 `LootContextParamSet` 生成战利品表。`LootTableSubProvider` 包含一个方法,该方法接收写入者(`BiConsumer`)来生成表。 + +```java +public class ExampleSubProvider implements LootTableSubProvider { + + // 用于创建封装 Supplier 的工厂方法 + public ExampleSubProvider() {} + + // 用于生成战利品表的方法 + @Override + public void generate(BiConsumer writer) { + // 在这里通过调用 writer#accept 生成战利品表 + } +} +``` + +然后可以将表添加到 `LootTableProvider#getTables` 中,适用于任何可用的 `LootContextParamSet`: + +```java +// 传递到 LootTableProvider 构造函数的列表中 +new LootTableProvider.SubProviderEntry( + ExampleSubProvider::new, + // 为 'empty' 参数集生成战利品表 + LootContextParamSets.EMPTY +) +``` + +### `BlockLootSubProvider` 和 `EntityLootSubProvider` 子类 + +对于 `LootContextParamSets#BLOCK` 和 `#ENTITY`,有特殊类型(分别是 `BlockLootSubProvider` 和 `EntityLootSubProvider`),它们提供额外的辅助方法来创建和验证是否存在战利品表。 + +`BlockLootSubProvider` 的构造函数接受一个物品列表,用于确定如果一个方块被爆炸时是否可以生成战利品表,以及一个 `FeatureFlagSet`,用于确定是否启用了方块以便为其生成战利品表。 + +```java +// 在某个 BlockLootSubProvider 子类中 +public MyBlockLootSubProvider() { + super(Collections.emptySet(), FeatureFlags.REGISTRY.allFlags()); +} +``` + +`EntityLootSubProvider` 的构造函数接受一个 `FeatureFlagSet`,用于确定是否启用了实体类型以便为其生成战利品表。 + +```java +// 在某个 EntityLootSubProvider 子类中 +public MyEntityLootSubProvider() { + super(FeatureFlags.REGISTRY.allFlags()); +} +``` + +要使用它们,所有已注册的对象必须分别提供给 `BlockLootSubProvider#getKnownBlocks` 和 `EntityLootSubProvider#getKnownEntityTypes`。这些方法确保迭代中的所有对象都有一个战利品表。 + +:::tip +如果使用 `DeferredRegister` 来注册模组的对象,则 `#getKnown*` 方法可以通过 `DeferredRegister#getEntries` 提供条目: + +```java +// 在某个 BlockLootSubProvider 子类中,用于某个 DeferredRegister BLOCK_REGISTRAR +@Override +protected Iterable getKnownBlocks() { + return BLOCK_REGISTRAR.getEntries() // 获取所有注册的条目 + .stream() // 流式处理封装的对象 + .flatMap(RegistryObject::stream) // 如果可用,则获取对象 + ::iterator; // 创建迭代器 +} +``` +::: + +通过实现 `#generate` 方法可以添加战利品表。 + +```java +// 在某个 BlockLootSubProvider 子类中 +@Override +public void + + generate() { + // 在这里添加战利品表 +} +``` + +战利品表生成器 +------------------- + +战利品表通过 `LootTableSubProvider` 接收为 `LootTable$Builder`。之后,指定的 `LootContextParamSet` 在 `LootTableProvider$SubProviderEntry` 中设置,然后通过 `#build` 构建。在构建之前,构建器可以指定条目、条件和修饰符,这些因素影响战利品表的功能。 + +:::note +战利品表的功能非常广泛,本文档无法全部涵盖。相反,每个组件将简要描述。每个组件的具体子类型可以使用 IDE 查找。它们的实现将留给读者作为练习。 +::: + +### LootTable + +战利品表是基本对象,可以使用 `LootTable#lootTable` 转换为所需的 `LootTable$Builder`。战利品表可以通过指定的方式构建,包括一系列池(通过 `#withPool` 应用)以及函数(通过 `#apply`),用于修改这些池的结果物品。 + +### LootPool + +战利品池代表一个执行操作的组,并可以使用 `LootPool#lootPool` 生成一个 `LootPool$Builder`。每个战利品池可以指定条目(通过 `#add`),这些条目定义池中的操作,条件(通过 `#when`)定义是否应执行池中的操作,以及函数(通过 `#apply`)修改条目的结果物品。每个池可以执行尽可能多的次数(通过 `#setRolls`)。此外,还可以指定额外的执行次数(通过 `#setBonusRolls`),这受到执行者幸运值的影响。 + +### LootPoolEntryContainer + +战利品条目定义了选中时要执行的操作,通常生成物品。每个条目都有一个相关的、[已注册][registered]的 `LootPoolEntryType`。它们还有自己的相关构建器,这些构建器是 `LootPoolEntryContainer$Builder` 的子类型。多个条目可以同时执行(通过 `#append`)或顺序执行,直到一个失败(通过 `#then`)。此外,条目可以在失败时默认为另一个条目(通过 `#otherwise`)。 + +### LootItemCondition + +战利品条件定义了执行某些操作所需满足的要求。每个条件都有一个相关的、[已注册][registered]的 `LootItemConditionType`。它们还有自己的相关构建器,这些构建器是 `LootItemCondition$Builder` 的子类型。默认情况下,指定的所有战利品条件必须为真,才能执行操作。也可以指定战利品条件,使得只需一个条件为真即可(通过 `#or`)。此外,条件的结果输出可以被反转(通过 `#invert`)。 + +### LootItemFunction + +战利品函数在将执行结果传递给输出之前修改结果。每个函数都有一个相关的、[已注册][registered]的 `LootItemFunctionType`。它们还有自己的相关构建器,这些构建器是 `LootItemFunction$Builder` 的子类型。 + +#### NbtProvider + +NBT 提供者是由 `CopyNbtFunction` 定义的特殊类型函数。它们定义了从哪里拉取标签信息。每个提供者都有一个相关的、[已注册][registered]的 `LootNbtProviderType`。 + +### NumberProvider + +数字提供者决定战利品池执行的次数。每个提供者都有一个相关的、[已注册][registered]的 `LootNumberProviderType`。 + +#### ScoreboardNameProvider + +记分板提供者是由 `ScoreboardValue` 定义的一种特殊类型的数字提供者。它们定义了从哪个记分板拉取执行次数的名字。每个提供者都有一个关联的[已注册][registered]`LootScoreProviderType`。 + +[loottable]: ../../resources/server/loottables.md +[datagen]: ../index.md#data-providers +[registered]: ../../concepts/registries.md diff --git a/i18n/zh-Hans/docusaurus-plugin-content-docs/current/datagen/recipes.md b/i18n/zh-Hans/docusaurus-plugin-content-docs/current/datagen/recipes.md new file mode 100644 index 000000000..0abd31642 --- /dev/null +++ b/i18n/zh-Hans/docusaurus-plugin-content-docs/current/datagen/recipes.md @@ -0,0 +1,197 @@ +配方生成 +================= + +可以通过继承 `RecipeProvider` 并实现 `#buildRecipes` 来为模组生成配方。一旦消费者接受了 `FinishedRecipe` 视图,就会为数据生成提供配方。 `FinishedRecipe` 可以手动创建和提供,或者为方便起见,使用 `RecipeBuilder` 创建。 + +实现后,必须将提供者[添加][datagen]到 `DataGenerator`。 + +```java +// 在MOD事件总线上 +@SubscribeEvent +public void gatherData(GatherDataEvent event) { + event.getGenerator().addProvider( + // 告诉生成器只在服务器数据生成时运行 + event.includeServer(), + MyRecipeProvider::new + ); +} +``` + +`RecipeBuilder` +--------------- + +`RecipeBuilder` 是创建 `FinishedRecipe` 以便于生成的便利实现。它提供了解锁、分组、保存和获取配方结果的基本定义。分别通过 `#unlockedBy`、 `#group`、 `#save` 和 `#getResult` 实现。 + +:::important +在原生配方构建器中不支持配方的 [`ItemStack` 输出][stack]。必须以不同的方式构建 `FinishedRecipe`,以便现有的原生配方序列化器可以生成这些数据。 +::: + +:::warning +正在生成的物品结果必须有一个有效的 `RecipeCategory` 指定,否则会抛出 `NullPointerException`。 +::: + +除了 [`SpecialRecipeBuilder`],所有的配方构建器都需要指定一个进步条件。所有的配方都会生成一个条件,如果玩家之前使用过这个配方,就会解锁这个配方。然而,必须指定一个额外的条件,允许玩家在没有任何先验知识的情况下获取到配方。如果指定的任何一个条件为真,那么玩家就会在配方书中获取到配方。 + +:::tip +配方条件通常使用 `InventoryChangeTrigger` 来在用户的库存中存在某些物品时解锁配方。 +::: + +### ShapedRecipeBuilder + +`ShapedRecipeBuilder` 用于生成有形状的配方。构建器可以通过 `#shaped` 初始化。可以在保存之前指定配方组、输入符号模式、成分的符号定义和配方解锁条件。 + +```java +// 在 RecipeProvider#buildRecipes(writer) 中 +ShapedRecipeBuilder builder = ShapedRecipeBuilder.shaped(RecipeCategory.MISC, result) + .pattern("a a") // 创建配方模式 + .define('a', item) // 定义符号代表的物品 + .unlockedBy("criteria", criteria) // 配方的解锁方式 + .save(writer); // 将数据添加到构建器中 +``` + +#### 额外的验证检查 + +在构建之前,有形状的配方会进行一些额外的验证检查: + +* 必须定义一个模式,并且需要输入超过一个物品。 +* 所有模式行的宽度必须相同。 +* 一个符号不能被定义多次。 +* 空格字符 (`' '`) 是为了表示插槽中没有物品而保留的,因此不能被定义。 +* 模式必须使用用户定义的所有符号。 + +### ShapelessRecipeBuilder + +`ShapelessRecipeBuilder` 用于生成无形状的配方。可以通过 `#shapeless` 初始化构建器。在保存之前,可以指定配方组、输入成分和配方解锁条件。 + +```java +// 在 RecipeProvider#buildRecipes(writer) 中 +ShapelessRecipeBuilder builder = ShapelessRecipeBuilder.shapeless(RecipeCategory.MISC, result) + .requires(item) // 将物品添加到配方中 + .unlockedBy("criteria", criteria) // 配方的解锁方式 + .save(writer); // 将数据添加到构建器中 +``` + +### SimpleCookingRecipeBuilder + +`SimpleCookingRecipeBuilder` 用于生成熔炼、爆炸、烟熏和营火烹饪配方。此外,使用 `SimpleCookingSerializer` 的自定义烹饪配方也可以使用这个构建器进行数据生成。可以分别通过 `#smelting`、 `#blasting`、 `#smoking`、 `#campfireCooking` 或 `#cooking` 初始化构建器。在保存之前,可以指定配方组和配方解锁条件。 + +```java +// 在 RecipeProvider#buildRecipes(writer) 中 +SimpleCookingRecipeBuilder builder = SimpleCookingRecipeBuilder.smelting(input, RecipeCategory.MISC, result, experience, cookingTime) + .unlockedBy("criteria", criteria) // 配方的解锁方式 + .save(writer); // 将数据添加到构建器中 +``` + +### SingleItemRecipeBuilder + +`SingleItemRecipeBuilder` 用于生成石切配方。此外,使用类似 `SingleItemRecipe$Serializer` 的序列化器的自定义单物品配方也可以使用这个构建器进行数据生成。可以通过 `#stonecutting` 或者构造函数分别初始化构建器。在保存之前,可以指定配方组和配方解锁条件。 + +```java +// 在 RecipeProvider#buildRecipes(writer) 中 +SingleItemRecipeBuilder builder = SingleItemRecipeBuilder.stonecutting(input, RecipeCategory.MISC, result) + .unlockedBy("criteria", criteria) // 配方的解锁方式 + .save(writer); // 将数据添加到构建器中 +``` + +非 `RecipeBuilder` 构建器 +---------------------------- + +一些配方构建器由于缺少所有先前提到的配方使用的功能而不实现 `RecipeBuilder`。 + +### SmithingTransformRecipeBuilder + +`SmithingTransformRecipeBuilder` 用于生成将物品转化的铁匠配方。此外,使用类似 `SmithingTransformRecipe$Serializer` 的序列化器的自定义配方也可以使用这个构建器进行数据生成。可以通过 `#smithing` 或者构造函数分别初始化构建器。在保存之前,可以指定配方解锁条件。 + +```java +// 在 RecipeProvider#buildRecipes(writer) 中 +SmithingTransformRecipeBuilder builder = SmithingTransformRecipeBuilder.smithing(template, base, addition, RecipeCategory.MISC, result) + .unlocks("criteria", criteria) // 配方的解锁方式 + .save(writer, name); // 将数据添加到构建器中 +``` + +### SmithingTrimRecipeBuilder + +`SmithingTrimRecipeBuilder` 用于生成用于装甲修剪的铁匠配方。此外,使用类似 `SmithingTrimRecipe$Serializer` 的序列化器的自定义升级配方也可以使用这个构建器进行数据生成。可以通过 `#smithingTrim` 或者构造函数分别初始化构建器。在保存之前,可以指定配方解锁条件。 + +```java +// 在 RecipeProvider#buildRecipes(writer) 中 +SmithingTrimRecipe builder = SmithingTrimRecipe.smithingTrim(template, base, addition, RecipeCategory.MISC) + .unlocks("criteria", criteria) // 配方的解锁方式 + .save(writer, name); // 将数据添加到构建器中 +``` + +### SpecialRecipeBuilder + +`SpecialRecipeBuilder` 用于为不能轻易约束到配方 JSON 格式(如染色护甲、焰火等)的动态配方生成空的 JSON。可以通过 `#special` 初始化构建器。 + +```java +// 在 RecipeProvider#buildRecipes(writer) 中 +SpecialRecipeBuilder.special(dynamicRecipeSerializer) + .save(writer, name); // 将数据添加到构建器中 +``` +条件性配方 +------------------- + +[条件性配方][conditional] 也可以通过 `ConditionalRecipe$Builder` 进行数据生成。可以使用 `#builder` 获取构建器。 + +可以先调用 `#addCondition` 来指定每个配方的条件,然后在所有条件都指定后调用 `#addRecipe`。这个过程可以重复多次,由程序员自行决定。 + +在所有配方都指定之后,可以在最后使用 `#generateAdvancement` 为每个配方添加进步(advancements)。或者,可以使用 `#setAdvancement` 设置条件性进步。 + +```java +// 在 RecipeProvider#buildRecipes(writer) 中 +ConditionalRecipe.builder() + // 添加配方的条件 + .addCondition(...) + // 当条件为真时返回配方 + .addRecipe(...) + + // 添加下一个配方的条件 + .addCondition(...) + // 当下一个条件为真时返回下一个配方 + .addRecipe(...) + + // 创建使用上述配方中的条件和解锁进步的条件性进步 + .generateAdvancement() + .build(writer, name); +``` + +### IConditionBuilder + +为了简化向条件性配方添加条件,而不必手动构造每个条件实例,扩展的 `RecipeProvider` 可以实现 `IConditionBuilder`。接口添加了轻松构造条件实例的方法。 + +```java +// 在 ConditionalRecipe$Builder#addCondition 中 +( + // 如果 'examplemod:example_item' + // 或者 'examplemod:example_item2' 存在 + // 并且 + // 非FALSE + + // 方法由 IConditionBuilder 定义 + and( + or( + itemExists("examplemod", "example_item"), + itemExists("examplemod", "example_item2") + ), + not( + FALSE() + ) + ) +) +``` + +自定义配方序列化器 +------------------------- + +通过创建一个可以构造 `FinishedRecipe` 的构建器,可以数据生成自定义配方序列化器。完成的配方将配方数据编码为JSON,并在存在时将其解锁进步也编码为JSON。此外,还需要指定配方的名称和序列化器,以知道写入位置以及加载时能解码对象的内容。一旦构造了 `FinishedRecipe`,只需将其传递给 `RecipeProvider#buildRecipes` 提供的 `Consumer`。 + +:::tip +`FinishedRecipe` 足够灵活,可以数据生成任何对象转换,不仅仅是物品。 +::: + +[datagen]: ../index.md#data-providers +[ingredients]: ../../resources/server/recipes/ingredients.md#forge-types +[stack]: ../../resources/server/recipes/index.md#recipe-itemstack-result +[conditional]: ../../resources/server/conditional.md +[special]: #specialrecipebuilder diff --git a/i18n/zh-Hans/docusaurus-plugin-content-docs/current/datagen/tags.md b/i18n/zh-Hans/docusaurus-plugin-content-docs/current/datagen/tags.md new file mode 100644 index 000000000..d72c35a66 --- /dev/null +++ b/i18n/zh-Hans/docusaurus-plugin-content-docs/current/datagen/tags.md @@ -0,0 +1,123 @@ +标签生成 +====== + +通过继承`TagsProvider`并实现`#addTags`方法,可以为一个mod生成[标签]。实现后,必须将提供器[添加][datagen]到`DataGenerator`中。 + +```java +// 在MOD事件总线上 +@SubscribeEvent +public void gatherData(GatherDataEvent event) { + event.getGenerator().addProvider( + // 通知生成器仅在生成服务器数据时运行 + event.includeServer(), + // 继承自net.neoforged.neoforge.common.data.BlockTagsProvider + output -> new MyBlockTagsProvider( + output, + event.getLookupProvider(), + MOD_ID, + event.getExistingFileHelper() + ) + ); +} +``` + +`TagsProvider` +-------------- + +标签提供器有两种用于生成标签的方法:通过`#tag`使用对象和其他标签创建一个标签,或者通过`#getOrCreateRawBuilder`使用其他对象类型的标签来生成标签数据。 + +:::note +通常,除非一个注册表包含来自不同注册表的对象的表示(例如,方块有项目表示以在库存中获得方块),否则提供器不会直接调用`#getOrCreateRawBuilder`。 +::: + +调用`#tag`时,会创建一个`TagAppender`,它作为链式消费者,用于向标签添加元素: + +方法 | 描述 +:---: | :--- +`add` | 通过其资源键将一个对象添加到标签中。 +`addOptional` | 通过其名称将一个对象添加到标签中。如果对象不存在,则加载时将跳过该对象。 +`addTag` | 通过其标签键将一个标签添加到标签中。内标签中的所有元素现在都是外标签的一部分。 +`addOptionalTag` | 通过其名称将一个标签添加到标签中。如果标签不存在,则加载时将跳过该标签。 +`replace` | 当为`true`时,此标签从其他数据包添加的所有先前加载的条目将被丢弃。如果在此之后加载了一个数据包,那么它仍然会将条目追加到标签中。 +`remove` | 通过其名称或键从标签中移除一个对象或标签。 + +```java +// 在某个TagProvider#addTags中 +this.tag(EXAMPLE_TAG) + .add(EXAMPLE_OBJECT) // 将一个对象添加到标签中 + .addOptional(new ResourceLocation("othermod", "other_object")) // 向标签添加来自其他mod的对象 + +this.tag(EXAMPLE_TAG_2) + .addTag(EXAMPLE_TAG) // 将一个标签添加到另一个标签 + .remove(EXAMPLE_OBJECT) // 从此标签中移除一个对象 +``` + +:::important +如果你的mod的标签对其他mod的标签有软依赖(也就是说,其他mod可能在运行时不存在),那么你应该使用可选的方法来引用其他mod的标签。 +::: + +### 现有的供应商 + +Minecraft中包含了一些可以被子类化的特定注册表的标签供应商。另外,一些供应商包含了额外的辅助方法,可以更容易地创建标签。 + +注册对象类型 | 标签供应商 +:---: | :--- +`方块` | `BlockTagsProvider`\* +`物品` | `ItemTagsProvider` +`实体类型` | `EntityTypeTagsProvider` +`流体` | `FluidTagsProvider` +`游戏事件` | `GameEventTagsProvider` +`生物群系` | `BiomeTagsProvider` +`平面世界生成预设` | `FlatLevelGeneratorPresetTagsProvider` +`世界预设` | `WorldPresetTagsProvider` +`结构` | `StructureTagsProvider` +`兴趣点类型` | `PoiTypeTagsProvider` +`旗帜图案` | `BannerPatternTagsProvider` +`猫的品种` | `CatVariantTagsProvider` +`画的种类` | `PaintingVariantTagsProvider` +`乐器` | `InstrumentTagsProvider` +`伤害类型` | `DamageTypeTagsProvider` + +\* `BlockTagsProvider` 是Forge所添加的 `TagsProvider`。 + +#### `ItemTagsProvider#copy` + +方块在物品栏中有物品形式的表示。因此,许多的方块标签也可以是物品标签。为了方便地生成与方块标签具有相同条目的物品标签,可以使用 `#copy` 方法,此方法接收要复制的方块标签和要复制到的物品标签。 + +```java +// 在 ItemTagsProvider#addTags 中 +this.copy(EXAMPLE_BLOCK_TAG, EXAMPLE_ITEM_TAG); +``` + +自定义标签提供者 +-------------------- + +通过 `TagsProvider` 的子类,可以创建自定义的标签提供者,这需要传入注册键以生成标签。 + +```java +public RecipeTypeTagsProvider(PackOutput output, CompletableFuture registries, ExistingFileHelper fileHelper) { + super(output, Registries.RECIPE_TYPE, registries, MOD_ID, fileHelper); +} +``` + +### 内在持有者标签提供者 + +其中一种特殊的 `TagProvider` 是 `IntrinsicHolderTagsProvider`。当通过这个提供者使用 `#tag` 创建标签时,对象本身可以通过 `#add` 将自己添加到标签中。为此,在构造函数中提供了一个将对象转换为其 `ResourceKey` 的函数。 + +```java +// `IntrinsicHolderTagsProvider` 的子类型 +public AttributeTagsProvider(PackOutput output, CompletableFuture registries, ExistingFileHelper fileHelper) { + super( + output, + ForgeRegistries.Keys.ATTRIBUTES, + registries, + attribute -> ForgeRegistries.ATTRIBUTES.getResourceKey(attribute).get(), + MOD_ID, + fileHelper + ); +} +``` + +[tags]: ../../resources/server/tags.md +[datagen]: ../index.md#data-providers +[custom]: ../../concepts/registries.md#custom-registries diff --git a/i18n/zh-Hans/docusaurus-plugin-content-docs/current/datamaps/_category_.json b/i18n/zh-Hans/docusaurus-plugin-content-docs/current/datamaps/_category_.json new file mode 100644 index 000000000..43a13824e --- /dev/null +++ b/i18n/zh-Hans/docusaurus-plugin-content-docs/current/datamaps/_category_.json @@ -0,0 +1,3 @@ +{ + "label": "Data Maps" +} \ No newline at end of file diff --git a/i18n/zh-Hans/docusaurus-plugin-content-docs/current/datamaps/index.md b/i18n/zh-Hans/docusaurus-plugin-content-docs/current/datamaps/index.md new file mode 100644 index 000000000..d368ceb3f --- /dev/null +++ b/i18n/zh-Hans/docusaurus-plugin-content-docs/current/datamaps/index.md @@ -0,0 +1,192 @@ +# 数据映射 + +注册表数据映射包含可附加到注册表对象的数据驱动、可重载的对象。 +这个系统允许更容易地数据驱动游戏行为,因为它们提供了如同步或冲突解决等功能,从而带来更好、更可配置的用户体验。 + +你可以将标签看作注册表对象 ➜ 布尔映射,而数据映射则是更灵活的注册表对象 ➜ 对象映射。 + +数据映射可以附加到静态的内置注册表和动态的数据驱动的数据包注册表上。 + +数据映射支持通过使用 `/reload` 命令或任何其他重新加载服务器资源的方法来重新加载。 + +## 注册 +数据映射类型应该静态创建,然后注册到 `RegisterDataMapTypesEvent`(在 [mod事件总线](../concepts/events)上触发)。`DataMapType` 可以使用 `DataMapType$Builder` 通过 `DataMapType#builder` 创建。 + +构建器提供了一个 `synced` 方法,可以用来标记数据映射为同步并将其发送给客户端。 + +一个简单的 `DataMapType` 有两个泛型参数:`R`(数据映射所针对的注册表的类型)和 `T`(被附加的值)。因此,可以将附加到 `Item` 的 `SomeObject` 的数据映射表示为 `DataMapType`。 + +数据映射使用 [编解码器](../datastorage/codecs.md) 进行序列化和反序列化。 + +以以下表示数据映射值的记录为例: +```java +public record DropHealing( + float amount, float chance +) { + public static final Codec CODEC = RecordCodecBuilder.create(in -> in.group( + Codec.FLOAT.fieldOf("amount").forGetter(DropHealing::amount), + Codec.floatRange(0, 1).fieldOf("chance").forGetter(DropHealing::chance) + ).apply(in, DropHealing::new)); +} +``` + +:::warning +值 (`T`) 应为 *不可变* 对象,否则如果对象附加到标签内的所有条目,则可能导致奇怪的行为(因为不会创建副本)。 +::: + +为了本例的目的,我们将使用此数据映射在玩家丢弃物品时治疗玩家。 +`DataMapType` 可以这样创建: +```java +public static final DataMapType DROP_HEALING = DataMapType.builder( + new ResourceLocation("mymod:drop_healing"), Registries.ITEM, DropHealing.CODEC +).build(); +``` +然后使用 `RegisterDataMapTypesEvent#register` 注册到 `RegisterDataMapTypesEvent`。 + +## 同步 +同步的数据映射将会将其值同步到客户端。可以使用 `DataMapType$Builder#synced(Codec networkCodec, boolean mandatory)` 标记数据映射为同步。 +然后将使用 `networkCodec` 同步数据映射的值。 +如果 `mandatory` 标志设置为 `true`,则不支持数据映射的客户端(包括 Vanilla 客户端)将无法连接到服务器,反之亦然。另一方面,非强制性的数据映射是可选的,因此它不会阻止任何客户端加入。 + +:::tip +单独的网络编解码器允许包大小更小,因为你可以选择发送哪些数据以及以什么格式发送。否则可以使用默认编解码器。 +::: + +## JSON结构和位置 +数据映射从位于 `mapNamespace/data_maps/registryNamespace/registryPath/mapPath.json` 的JSON文件加载,其中: +- `mapNamespace` 是数据映射ID的命名空间 +- `mapPath` 是数据映射ID的路径 +- `registryNamespace` 是注册表ID的命名空间;如果命名空间是 `minecraft`,则此值将被省略 +- `registryPath` 是注册表ID的路径 + +更多信息,请[查看专用页面](./structure.md)。 + +## 使用 +由于数据映射可以用于任何注册表,因此可以通过 `Holder` 查询它们,而不是通过实际的注册表对象。 +你可以使用 `Holder#getData(DataMapType)` 查询数据映射值。如果该对象没有附加值,方法将返回 `null`。 + +:::note +只有引用持有者会在该方法中返回值。`直接` 持有者 **不会**。通常,你只会遇到引用持有者(它们由 `Registry#wrapAsHolder`、`Registry#getHolder` 或不同的 `builtInRegistryHolder` 方法返回)。 +::: + +为了继续上面的示例,我们可以如下实现我们预期的行为: +```java +public static void onItemDrop(final ItemTossEvent event) { + final ItemStack stack = event.getEntity().getItem(); + // ItemStack 有一个 getItemHolder 方法,它将返回一个指向堆叠物的物品的 Holder + //高亮下一行 + final DropHealing value = stack.getItemHolder().getData(DROP_HEALING); + // 由于 getData 如果物品没有附加 drop healing 值将返回 null,我们保护它不为 null + if (value != null) { + // 这里我们简单地使用值 + if (event.getPlayer().level().getRandom().nextFloat() > value.chance()) { + event.getPlayer().heal(value.amount()); + } + } +} +``` + +## 高级数据映射 +高级数据映射是具有额外功能的数据映射。即,通过移除器合并值和选择性地移除它们的能力。对于值类似于集合(如 `Map` 或 `List`)的数据映射,强烈推荐实现某种形式的合并和移除器。 + +`AdvancedDataMapType` 除了 `T` 和 `R` 之外还有一个泛型:`VR extends DataMapValueRemover`。这个额外的泛型允许你通过提高类型安全性来生成移除对象。 + +### 创建 +你可以使用 `AdvancedDataMapType#builder` 创建 `AdvancedDataMapType`。与普通构建器不同,该方法返回的构建器将有两个额外的方法(`merger` 和 `remover`),并且它将返回一个 `AdvancedDataMapType`。注册方法保持不变。 + +### 合并器 +高级数据映射可以通过 `AdvancedDataMapType#merger` 提供一个 `DataMapValueMerger`。这个合并器将用于处理尝试将值附加到同一对象的数据包之间的冲突。 +合并器将给出两个冲突的值及其来源(作为 `Either, ResourceKey>`,因为值可以附加到标签内的所有条目,而不仅仅是个别条目),并期望返回实际附加的值。 +通常,合并器应简单地合并值,并且除非必要(即如果合并不可能),否则不应执行“硬”覆盖。如果一个包想要绕过合并器,它可以通过指定对象级别的 `replace` 字段来实现。 + +假设我们有一个将整数附加到物品的数据映射的情况: +```java +public class IntMerger implements DataMapValueMerger { + @Override + public Integer merge(Registry registry, Either, ResourceKey> first, Integer firstValue, Either, ResourceKey> second, Integer secondValue) { + //高亮下一行 + return firstValue + secondValue; + } +} +``` +如果两个数据包附加到同一对象,则上述合并器将合并值。所以如果第一个包将值 `12` 附加到 `minecraft:carrot`,第二个包将值 `15` 附加到 `minecraft:carrot`,最终的值将是 `27`。然而,如果第二个包指定对象级别的 `replace` 字段,最终值将是 `15`,因为不会调用合并器。 + +NeoForge 为合并列表、集合和映射提供了一些默认的合并器,位于 `DataMapValueMerger` 中。 + +默认合并器(`DataMapValueMerger#defaultMerger`)具有你期望的普通数据包的典型行为,其中最新的值(来自最高的数据包)将覆盖之前的值。 + +### 移除器 +高级数据映射可以通过 `AdvancedDataMapType#remover` 提供一个 `DataMapValueRemover`。移除器将允许选择性地移除数据映射值,有效地进行分解。 +虽然默认情况下一个数据包只能移除附加到注册表条目的整个对象,但有了移除器,它可以只从附加对象中移除特定的值(即,在映射的情况下,只移除具有给定键的项,或在列表的情况下,只移除具有特定属性的项)。 + +传递给构建器的编解码器将解码移除器实例。然后这些移除器将给出当前附加的值及其来源,并期望创建一个新对象来替换旧值。 +或者,一个空的 `Optional` 将导致值被完全移除。 + +一个从基于 `Map` 的数据映射中移除具有特定键的值的移除器示例: +```java +public record MapRemover(String key) implements DataMapValueRemover> { + public static final Codec CODEC = Codec.STRING.xmap(MapRemover::new, MapRemover::key); + + @Override + public Optional> remove(Map value, Registry registry, Either, ResourceKey> source, Item object) { + final Map newMap = new HashMap<>(value); + newMap.remove(key); + return Optional.of(newMap); + } +} +``` + +考虑到上述移除器,我们将字符串映射到物品的字符串。考虑以下数据映射 JSON 文件: +```js +{ + "values": { + //高亮开始 + "minecraft:carrot": { + "somekey1": "value1", + "somekey2": "value2" + } + //高亮结束 + } +} +``` +该文件将映射 `[somekey1=value1, somekey2=value2]` 附加到 `minecraft:carrot` 物品。现在,另一个数据包可以在其上面移除具有 `somekey1` 键的值,如下所示: +```js +{ + "remove": { + // 由于移除器被解码为字符串,我们可以在这里使用字符串作为值。如果它被解码为对象,我们将需要使用一个对象。 + //高亮下一行 + "minecraft:carrot": "somekey1" + } +} +``` +在读取和应用第二个数据包后,附加到 `minecraft:carrot` 物品的新值将是 `[somekey2=value2]`。 + +## 数据生成 +数据映射可以通过 `DataMapProvider` [生成](../datagen)。 +你应该扩展这个类,然后覆盖 `generate` 方法来创建你的条目,类似于标签生成。 + +考虑到起始的摔落治疗示例,我们可以如下生成一些值: +```java +public class DropHealingGen extends DataMapProvider { + + public DropHealingGen(PackOutput packOutput, CompletableFuture lookupProvider) { + super(packOutput, lookupProvider); + } + + @Override + protected void gather() { + // 在下面的示例中,我们不需要强制替换任何值,因为默认行为是没有提供合并器,所以第三个参数可以是 false。 + + // 如果你为你的数据映射提供了合并器,那么第三个参数将导致旧值被覆盖(如果设置为 true),而不调用合并器 + builder(DROP_HEALING) + // 始终给掉落任何 minecraft:fox_food 标签项的实体 12 心 + .add(ItemTags.FOX_FOOD, new DropHealing(12, 1f), false) + // 有 10% 的几率治疗掉落金合欢船的实体一点 + .add(Items.ACACIA_BOAT.builtInRegistryHolder(), new DropHealing(1, 0.1f), false); + } +} +``` + +:::tip +如果你想将值附加到可选依赖项添加的对象,有 `add` 重载接受原始 `ResourceLocation`。在这种情况下,你还应该通过 var-args 参数提供[一个加载条件](../resources/server/conditional),以避免崩溃。 +::: diff --git a/i18n/zh-Hans/docusaurus-plugin-content-docs/current/datamaps/neo_maps.md b/i18n/zh-Hans/docusaurus-plugin-content-docs/current/datamaps/neo_maps.md new file mode 100644 index 000000000..988278dab --- /dev/null +++ b/i18n/zh-Hans/docusaurus-plugin-content-docs/current/datamaps/neo_maps.md @@ -0,0 +1,128 @@ +# 内置数据映射 + +NeoForge 提供了一些数据映射,这些映射主要取代硬编码的代码内普通映射。 +这些数据映射可以在 `NeoForgeDataMaps` 中找到,并且始终是*可选的*以确保与普通客户端的兼容性。 + +## `neoforge:compostables` + +NeoForge 提供了一个允许配置 Composter 值的数据映射,作为`ComposterBlock#COMPOSTABLES`(现在已被忽略)的替代品。 +该数据映射位于`neoforge/data_maps/item/compostables.json`,其对象具有以下结构: + +```js +{ + // A 0 to 1 (inclusive) float representing the chance that the item will update the level of the composter + "chance": 1 +} +``` + +Example: + +```js +{ + "values": { + // Give acacia logs a 50% chance that they will fill a composter + "minecraft:acacia_log": { + "chance": 0.5 + } + } +} +``` + +## `neoforge:furnace_fuels` + +NeoForge 提供了一个数据映射,允许配置物品的燃烧时间。 +该数据图位于`neoforge/data_maps/item/furnace_fuels.json`,其对象具有以下结构: + +```js +{ + // A positive integer representing the item's burn time, in ticks + "burn_time": 1000 +} +``` + +Example: + +```js +{ + "values": { + // Give anvils a 2 seconds burn time + "minecraft:anvil": { + "burn_time": 40 + } + } +} +``` + +:::note +其他像 `IItemExtension#getBurnTime` 这样的内部代码方法将优先于数据映射,所以建议您在您的mod中使用数据映射来设置简单、静态的燃烧时间,这样用户可以按照他们的需求进行配置。 +::: + +:::warning + +原版游戏为 `minecraft:logs` 和 `minecraft:planks` 标签中的原木和木板添加了燃烧时间。但是,这些标签也包含了下界木材,因此添加了对 `#minecraft:non_flammable_wood` 中元素的移除。 然而,这种移除不会影响其他数据包或模组添加的任何值,所以如果您想改变木材标签的值,您需要自己添加对不可燃木材标签的移除。 + +::: + +## `neoforge:parrot_imitations` + +NeoForge 提供了一个数据映射,允许配置鹦鹉在模仿怪物时产生的声音,这可以替代 `Parrot#MOB_SOUND_MAP`(现在已被忽视)。这个数据映射位于 `neoforge/data_maps/entity_type/parrot_imitations.json`,其对象具有以下结构: + +```js +{ + // The ID of the sound that parrots will produce when imitating the mob + "sound": "minecraft:entity.parrot.imitate.creeper" +} +``` + +Example: + +```js +{ + "values": { + // Make parrots produce the ambient cave sound when imitating allays + "minecraft:allay": { + "sound": "minecraft:ambient.cave" + } + } +} +``` + +## `neoforge:raid_hero_gifts` + +NeoForge 提供了一个数据映射,允许配置如果你阻止了突袭,具有某个 `VillagerProfession` 的村民可能会赠送给你的礼物,这将替代 `GiveGiftToHero#GIFTS`(现在已经被忽略了)。这个数据映射位于 `neoforge/data_maps/villager_profession/raid_hero_gifts.json`,其对象具有以下结构: + +```js +{ + "values": { + "minecraft:armorer": { + // Make armorers give the raid hero the armorer gift loot table + "loot_table": "minecraft:gameplay/hero_of_the_village/armorer_gift" + }, + } +} +``` + +## `neoforge:vibration_frequencies` + +NeoForge 提供了一个数据映射,允许配置游戏事件发出的潜影贝探头频率,这将替代 `VibrationSystem#VIBRATION_FREQUENCY_FOR_EVENT`(现在已经被忽视了)。这个数据映射位于 `neoforge/data_maps/game_event/vibration_frequencies.json`,其对象具有以下结构: + +```js +{ + // An integer between 1 and 15 (inclusive) that indicates the vibration frequency of the event + "frequency": 2 +} +``` + +Example: + +```js +{ + "values": { + // Make the splash in water game event vibrate on the second frequency + "minecraft:splash": { + "frequency": 2 + } + } +} +``` + diff --git a/i18n/zh-Hans/docusaurus-plugin-content-docs/current/datamaps/structure.md b/i18n/zh-Hans/docusaurus-plugin-content-docs/current/datamaps/structure.md new file mode 100644 index 000000000..36bb29e69 --- /dev/null +++ b/i18n/zh-Hans/docusaurus-plugin-content-docs/current/datamaps/structure.md @@ -0,0 +1,103 @@ +# JSON结构 +对于此页面,我们将使用一个数据映射作为例子,其对象具有两个浮点键:`amount` 和 `chance`。该对象的编码可以在[这里](./index.md#registration)找到。 + +## 地址 +数据映射加载自位于 `mapNamespace/data_maps/registryNamespace/registryPath/mapPath.json` 的JSON文件,其中: +- `mapNamespace` 是数据映射ID的命名空间 +- `mapPath` 是数据映射ID的路径 +- `registryNamespace` 是注册表ID的命名空间 +- `registryPath` 是注册表ID的路径 + +:::note +如果是 `minecraft`,则省略注册表命名空间。 +::: + +示例: +- 对于名为 `mymod:drop_healing` 的数据映射,用于 `minecraft:item` 注册表(如示例中),路径将是 `mymod/data_maps/item/drop_healing.json`。 +- 对于名为 `somemod:somemap` 的数据映射,用于 `minecraft:block` 注册表,路径将是 `somemod/data_maps/block/somemap.json`。 +- 对于名为 `example:stuff` 的数据映射,用于 `somemod:custom` 注册表,路径将是 `example/data_maps/somemod/custom/stuff.json`。 + +## 全局 `replace` 字段 +JSON文件具有一个可选的全局 `replace` 字段,类似于标签,当其为 `true` 时,将移除该数据映射的所有先前附加值。这对于想要完全改变整个数据映射的数据包非常有用。 + +## 加载条件 +数据映射文件支持在根级别和条目级别通过 `neoforge:conditions` 数组支持[加载条件](../resources/server/conditional)。 + +## 添加值 +可以使用 `values` 映射将值附加到对象。每个键将代表要附加值的单个注册表条目的ID,或者一个以 `#` 开头的标签键。如果是一个标签,那么相同的值将附加到该标签的所有条目上。 +键将是要附加的对象。 + +```js +{ + "values": { + // 为胡萝卜项附加一个值 + "minecraft:carrot": { + "amount": 12, + "chance": 1 + }, + // 将一个值附加到 logs 标签的所有项上 + "#minecraft:logs": { + "amount": 1, + "chance": 0.1 + } + } +} +``` + +:::info +上述结构将在[高级数据映射](./index.md#advanced-data-maps)的情况下调用合并器。如果你不想为特定的对象调用合并器,那么你将不得不使用类似于这样的结构: +```js +{ + "values": { + // 覆盖胡萝卜项的值 + "minecraft:carrot": { + // 高亮下一行 + "replace": true, + // 新的值将在 value 子对象下 + "value": { + "amount": 12, + "chance": 1 + } + } + } +} +``` +::: + +## 移除值 + +JSON文件也可以通过使用 `remove` 数组,从对象中移除先前附加的值: +```js +{ + // 移除附加到苹果和土豆的值 + "remove": ["minecraft:apple", "minecraft:potato"] +} +``` +数组包含一系列要从其中移除值的注册表条目ID或标签。 + +:::warning +移除操作在当前JSON文件的值被附加后进行,所以你可以使用移除功能来移除通过标签附加到对象的值: +```js +{ + "values": { + "#minecraft:logs": 12 + }, + // 从金合欢原木移除值,这样所有原木除了金合欢都将附加值 12 + "remove": ["minecraft:acacia_log"] +} +``` +::: + +:::info +在提供自定义移除器的[高级数据映射](./index.md#advanced-data-maps)的情况下,可以通过将 `remove` 数组转换为映射来提供移除器的参数。 +假设移除器对象被串行化为字符串,并且为基于 `Map` 的数据映射移除具有给定键的值: +```js +{ + "remove": { + // 移除器将从值(这种情况下为 `somekey1`)反串行化 + // 并应用于附加到胡萝卜项的值 + "minecraft:carrot": "somekey1" + } +} +``` +::: diff --git a/i18n/zh-Hans/docusaurus-plugin-content-docs/current/datastorage/_category_.json b/i18n/zh-Hans/docusaurus-plugin-content-docs/current/datastorage/_category_.json new file mode 100644 index 000000000..7b4467f7c --- /dev/null +++ b/i18n/zh-Hans/docusaurus-plugin-content-docs/current/datastorage/_category_.json @@ -0,0 +1,3 @@ +{ + "label": "Data Storage" +} \ No newline at end of file diff --git a/i18n/zh-Hans/docusaurus-plugin-content-docs/current/datastorage/attachments.md b/i18n/zh-Hans/docusaurus-plugin-content-docs/current/datastorage/attachments.md new file mode 100644 index 000000000..408710921 --- /dev/null +++ b/i18n/zh-Hans/docusaurus-plugin-content-docs/current/datastorage/attachments.md @@ -0,0 +1,109 @@ +# 数据附件 + +数据附件系统允许模组在方块实体、区块、实体和物品堆叠上附加和存储额外数据。 + +_要存储额外的关卡数据,您可以使用 [SavedData](saveddata)。_ + +## 创建附件类型 + +要使用系统,您需要注册一个 `AttachmentType`。 +附件类型包含以下配置: +- 当数据第一次被访问时创建实例的默认值供应器。也用于比较有数据的堆叠和没有数据的堆叠。 +- 如果附件需要持久化,则需要一个可选的序列化器。 +- (如果配置了序列化器)`copyOnDeath` 标志,用于在死亡时自动复制实体数据(见下文)。 +- (高级)(如果配置了序列化器)自定义 `comparator`,用于检查两个物品堆叠的数据是否相同。 + +:::tip +如果您不希望您的附件持久化,不要提供序列化器。 +::: + +有几种方法提供附件序列化器:直接实现 `IAttachmentSerializer`,实现 `INBTSerializable` 并使用静态的 `AttachmentSerializer.serializable()` 方法创建构建器,或向构建器提供编解码器。 + +:::warning +避免使用编解码器序列化物品堆叠附件,因为它相对较慢。 +::: + +无论哪种方式,附件 **必须被注册** 到 `NeoForgeRegistries.ATTACHMENT_TYPES` 注册表中。以下是一个示例: +```java +// 为附件类型创建 DeferredRegister +private static final DeferredRegister> ATTACHMENT_TYPES = DeferredRegister.create(NeoForgeRegistries.ATTACHMENT_TYPES, MOD_ID); + +// 通过 INBTSerializable 序列化 +private static final Supplier> HANDLER = ATTACHMENT_TYPES.register( + "handler", () -> AttachmentType.serializable(() -> new ItemStackHandler(1)).build()); +// 通过编解码器序列化 +private static final Supplier> MANA = ATTACHMENT_TYPES.register( + "mana", () -> AttachmentType.builder(() -> 0).serialize(Codec.INT).build()); +// 无序列化 +private static final Supplier> SOME_CACHE = ATTACHMENT_TYPES.register( + "some_cache", () -> AttachmentType.builder(() -> new SomeCache()).build() +); + +// 在您的 mod 构造函数中,不要忘记将 DeferredRegister 注册到您的 mod 总线: +ATTACHMENT_TYPES.register(modBus); +``` + +## 使用附件类型 + +一旦附件类型注册后,它可以在任何持有对象上使用。 +如果没有数据,调用 `getData` 将附加一个新的默认实例。 + +```java +// 如果已存在 ItemStackHandler,则获取它,否则附加一个新的: +ItemStackHandler stackHandler = stack.getData(HANDLER); +// 获取当前玩家的法力值(如果有),否则附加 0: +int playerMana = player.getData(MANA); +// 等等... +``` + +如果不希望附加一个默认实例,可以添加一个 `hasData` 检查: +```java +// 检查堆叠是否有 HANDLER 附件,然后再进行任何操作。 +if (stack.hasData(HANDLER)) { + ItemStackHandler stackHandler = stack.getData(HANDLER); + // 对 stack.getData(HANDLER) 做些什么。 +} +``` + +数据也可以用 `setData` 更新: +```java +// 将法力值增加 10。 +player.setData(MANA, player.getData(MANA) + 10); +``` + +:::important +通常,当修改方块实体和区块时需要将其标记为脏数据(使用 `setChanged` 和 `setUnsaved(true)`)。这对于 `setData` 的调用是自动完成的: +```java +chunk.setData(MANA, chunk.getData(MANA) + 10); // 将自动调用 setUnsaved +``` +但如果您修改了从 `getData` 获取的数据(包括新创建的默认实例),则必须显式地将方块实体和区块标记为脏数据: +```java +var mana = chunk.getData(MUTABLE_MANA); +mana.set(10); +chunk.setUnsaved(true); // 必须手动完成,因为我们没有使用 setData +``` +::: + +## 与客户端共享数据 +目前,只有可序列化的物品堆叠附件在客户端和服务器之间同步。 +这是自动完成的。 + +要将方块实体、区块或实体附件同步到客户端,你需要自己[向客户端发送数据包][network]。 +对于区块,您可以使用 `ChunkWatchEvent.Sent` 来知道何时向玩家发送区块数据。 + +## 在玩家死亡时复制数据 +默认情况下,实体数据附件在玩家死亡时不会被复制。 +要在玩家死亡时自动复制附件,请在附件构建器中设置 `.copyOnDeath()`。 + +更复杂的处理可以通过 `PlayerEvent.Clone` 实现,通过从原始实体中读取数据并将其分配给新实体。在此事件中,可以使用 `#isWasDeath` 方法区分死亡后重生和从末地返回。这很重要,因为从末地返回时数据已经存在,因此要注意在这种情况下不要重复值。 + +例如: +```java +NeoForge.EVENT_BUS.register(PlayerEvent.Clone.class, event -> { + if (event.isWasDeath() && event.getOriginal().hasData(MY_DATA)) { + event.getEntity().getData(MY_DATA).fieldToCopy = event.getOriginal().getData(MY_DATA).fieldToCopy; + } +}); +``` + +[network]: ../networking/index.md diff --git a/i18n/zh-Hans/docusaurus-plugin-content-docs/current/datastorage/capabilities.md b/i18n/zh-Hans/docusaurus-plugin-content-docs/current/datastorage/capabilities.md new file mode 100644 index 000000000..ecbd044a7 --- /dev/null +++ b/i18n/zh-Hans/docusaurus-plugin-content-docs/current/datastorage/capabilities.md @@ -0,0 +1,300 @@ +# 功能 + +功能(Capabilities)允许以动态和灵活的方式公开特性,无需直接实现许多接口。 + +通常来说,每个功能都以接口的形式提供一个特性。 + +NeoForge 为方块、实体和物品堆叠添加了功能支持。 +这将在以下部分中更详细地解释。 + +## 为什么使用功能 + +功能旨在将**能做什么**与**如何做**分离开来,适用于方块、实体或物品堆叠。 +如果您正在考虑功能是否适合某项工作,请问自己以下问题: +1. 我只关心方块、实体或物品堆叠能做什么,而不关心它如何做吗? +2. 这个 **能做什么**,也就是行为,是否只对某些方块、实体或物品堆叠有效,而不是所有? +3. 这个 **如何做**,即行为的实现,是否依赖于具体的方块、实体或物品堆叠? + +以下是一些良好的功能使用示例: +- *“我希望我的流体容器能与其他模组的流体容器兼容,但我不知道每个流体容器的具体情况。”* - 是的,使用 `IFluidHandler` 功能。 +- *“我想计算某个实体中有多少物品,但我不知道实体可能如何存储它们。”* - 是的,使用 `IItemHandler` 功能。 +- *“我想给某个物品堆叠充能,但我不知道物品堆叠可能如何存储能量。”* - 是的,使用 `IEnergyStorage` 功能。 +- *“我想对玩家当前瞄准的任何方块应用颜色,但我不知道方块将如何变化。”* - 是的。NeoForge 没有提供给方块上色的功能,但你可以自己实现。 + +以下是不推荐使用功能的示例: +- *“我想检查某个实体是否在我的机器范围内。”* - 不,使用帮助方法代替。 + +## NeoForge 提供的功能 + +NeoForge 为以下三个接口提供了功能:`IItemHandler`,`IFluidHandler` 和 `IEnergyStorage`。 + +`IItemHandler` 公开了处理库存槽的接口。`IItemHandler` 类型的功能有: +- `Capabilities.ItemHandler.BLOCK`:方块的自动化可访问库存(用于箱子、机器等)。 +- `Capabilities.ItemHandler.ENTITY`:实体的库存内容(额外的玩家槽位、怪物/生物的库存/包)。 +- `Capabilities.ItemHandler.ENTITY_AUTOMATION`:实体的自动化可访问库存(船、矿车等)。 +- `Capabilities.ItemHandler.ITEM`:物品堆叠的内容(便携背包等)。 + +`IFluidHandler` 公开了处理流体库存的接口。`IFluidHandler` 类型的功能有: +- `Capabilities.FluidHandler.BLOCK`:方块的自动化可访问流体库存。 +- `Capabilities.FluidHandler.ENTITY`:实体的流体库存。 +- `Capabilities.FluidHandler.ITEM`:物品堆叠的流体库存。 +这个功能是特殊的 `IFluidHandlerItem` 类型,因为桶装液体的方式有所不同。 + +`IEnergyStorage` 公开了处理能量容器的接口。它基于 TeamCoFH 的 RedstoneFlux API。`IEnergyStorage` 类型的功能有: +- `Capabilities.EnergyStorage.BLOCK`:方块内部的能量。 +- `Capabilities.EnergyStorage.ENTITY`:实体内部的能量。 +- `Capabilities.EnergyStorage.ITEM`:物品堆叠内部的能量。 + +## 创建功能 + +NeoForge为方块、实体和物品堆叠支持功能性。功能性允许在一定逻辑下查找某些API的实现。在NeoForge中实现了以下几种功能性: +- `BlockCapability`:适用于方块和方块实体的功能性;行为依赖于特定的`Block`。 +- `EntityCapability`:适用于实体的功能性;行为依赖于特定的`EntityType`。 +- `ItemCapability`:适用于物品堆叠的功能性;行为依赖于特定的`Item`。 + +:::tip +为了与其他模组兼容,如果可能的话,我们建议使用NeoForge在`Capabilities`类中提供的功能性。否则,您可以按照本节所述创建自己的功能性。 +::: + +创建功能性是单个函数调用,结果对象应该存储在一个`static final`字段中。必须提供以下参数: +- 功能性的名称。多次创建相同名称的功能性将始终返回相同的对象。不同名称的功能性是**完全独立的**,可以用于不同的目的。 +- 正在查询的行为类型。这是`T`类型参数。 +- 查询中的附加上下文类型。这是`C`类型参数。 + +例如,以下是如何声明侧向感知方块`IItemHandler`的功能性: + +```java +public static final BlockCapability ITEM_HANDLER_BLOCK = + BlockCapability.create( + // 提供一个名称以唯一标识功能性。 + new ResourceLocation("mymod", "item_handler"), + // 提供查询的类型。在这里,我们希望查找`IItemHandler`实例。 + IItemHandler.class, + // 提供上下文类型。我们将允许查询接收额外的`Direction side`参数。 + Direction.class); +``` + +对于方块来说,`@Nullable Direction`是如此常见,以至于有一个专门的助手函数: + +```java +public static final BlockCapability ITEM_HANDLER_BLOCK = + BlockCapability.createSided( + // 提供一个名称以唯一标识功能性。 + new ResourceLocation("mymod", "item_handler"), + // 提供查询的类型。在这里,我们希望查找`IItemHandler`实例。 + IItemHandler.class); +``` + +如果不需要上下文,则应使用`Void`。对于无上下文的功能性也有专门的助手函数: + +```java +public static final BlockCapability ITEM_HANDLER_NO_CONTEXT = + BlockCapability.createVoid( + // 提供一个名称以唯一标识功能性。 + new ResourceLocation("mymod", "item_handler_no_context"), + // 提供查询的类型。在这里,我们希望查找`IItemHandler`实例。 + IItemHandler.class); +``` + +对于实体和物品堆叠,`EntityCapability`和`ItemCapability`分别存在类似的方法。 + +## 查询功能性 +一旦我们在一个静态字段中拥有了`BlockCapability`、`EntityCapability`或`ItemCapability`对象,我们就可以查询一个功能性。 + +对于实体和物品堆叠,我们可以尝试使用`getCapability`找到功能性的实现。如果结果是`null`,则没有可用的实现。 + +例如: + +```java +var object = entity.getCapability(CAP, context); +if (object != null) { + // 使用object +} +``` +```java +var object = stack.getCapability(CAP, context); +if (object != null) { + // 使用object +} +``` + +方块功能性的使用略有不同,因为没有方块实体的方块也可以拥有功能性。现在,查询是在一个`level`上进行的,有一个我们正在寻找的`pos`位置作为附加参数: + +```java +var object = level.getCapability(CAP, pos, context); +if (object != null) { + // 使用object +} +``` + +如果已知方块实体和/或方块状态,可以传递它们以节省查询时间: + +```java +var object = level.getCapability(CAP, pos, blockState, blockEntity, context); +if (object != null) { + // 使用object +} +``` + +为了给出一个更具体的示例,以下是如何从`Direction.NORTH`侧查询方块的`IItemHandler`功能性: + +```java +IItemHandler handler = level.getCapability(Capabilities.ItemHandler.BLOCK, pos, Direction.NORTH); +if (handler != null) { + // 使用handler进行某些物品相关操作。 +} +``` + +当查询某个功能性时,系统会在后台执行以下步骤: +1. 如果它们没有被提供的话,获取方块实体和方块状态。 +2. 获取注册的功能性提供者。(下文会有更多相关信息) +3. 遍历提供者并询问他们是否能提供该功能性。 +4. 提供者中的一个将返回功能性实例,可能会分配一个新对象。 + +尽管实现相当高效,但对于频繁进行的查询,例如每个游戏刻,这些步骤可能会占用大量服务器时间。`BlockCapabilityCache` 系统为在特定位置频繁查询的能力提供了巨大的速度提升。 + +:::tip +通常,`BlockCapabilityCache` 会被创建一次,然后存储在执行频繁功能性查询的对象的字段中。何时何地存储缓存取决于您。 +::: + +要创建缓存,请使用要查询的功能性,级别,位置和查询上下文调用 `BlockCapabilityCache.create`。 + +```java +// 声明字段: +private BlockCapabilityCache capCache; + +// 稍后,例如在方块实体的 `onLoad` 中: +this.capCache = BlockCapabilityCache.create( + Capabilities.ItemHandler.BLOCK, // 要缓存的功能性 + level, // 世界级别 + pos, // 目标位置 + Direction.NORTH // 上下文 +); +``` + +然后通过 `getCapability()` 查询缓存: +```java +IItemHandler handler = this.capCache.getCapability(); +if (handler != null) { + // 对某些与物品相关的操作使用 handler。 +} +``` + +**缓存会被垃圾收集器自动清除,无需注销。** + +也可以接收到功能性对象变更的通知!这包括功能性变化(`oldHandler != newHandler`)、变得不可用(`null`)或再次变得可用(不再是 `null`)。 + +创建缓存时需要两个额外的参数: +- 一个有效性检查,用于确定缓存是否仍然有效。 +在作为方块实体字段的最简单用法中,`() -> !this.isRemoved()` 就可以了。 +- 一个失效监听器,当功能性改变时被调用。 +这是您可以对功能性变更、移除或出现做出反应的地方。 + +```java +// 带有可选的失效监听器: +this.capCache = BlockCapabilityCache.create( + Capabilities.ItemHandler.BLOCK, // 要缓存的功能性 + level, // 世界级别 + pos, // 目标位置 + Direction.NORTH, // 上下文 + () -> !this.isRemoved(), // 有效性检查(因为缓存可能会比它所属的对象更久存在) + () -> onCapInvalidate() // 失效监听器 +); +``` + +## 方块功能性失效 +:::info +失效功能是专门针对方块功能性的。实体和物品堆叠的功能性不能被缓存,因此不需要失效处理。 +::: + +为了确保缓存可以正确更新它们存储的功能性,**模组开发者必须在功能性改变、出现或消失时调用 `level.invalidateCapabilities(pos)`**。 +```java +// 每当一个功能性改变、出现或消失时: +level.invalidateCapabilities(pos); +``` + +NeoForge已经处理了常见情况,例如区块的加载/卸载和方块实体的创建/移除,但其他情况需要模组开发者明确处理。例如,模组开发者必须在以下情况中使功能性失效: +- 如果先前返回的功能性不再有效。 +- 如果放置或状态变化的功能性提供方块(没有方块实体),通过覆写 `onPlace`。 +- 如果移除的功能性提供方块(没有方块实体),通过覆写 `onRemove`。 + +对于一个简单的方块示例,参考 `ComposterBlock.java` 文件。 + +更多信息,请参考 [`IBlockCapabilityProvider`][block-cap-provider] 的 javadoc。 + +## 注册功能性 +功能性*提供者*是最终提供功能性的东西。功能性提供者是一个函数,可以返回一个功能性实例,或者如果不能提供功能性,就返回 `null`。提供者特定于: +- 它们为之提供服务的给定功能性,以及 +- 它们为之提供服务的方块实例、方块实体类型、实体类型或物品实例。 + +它们需要在 `RegisterCapabilitiesEvent` 中注册。 + +方块提供者使用 `registerBlock` 进行注册。例如: +```java +private static void registerCapabilities(RegisterCapabilitiesEvent event) { + event.registerBlock( + Capabilities.ItemHandler.BLOCK, // 注册的功能性 + (level, pos, state, be, side) -> <返回 IItemHandler>, + // 注册的方块 + MY_ITEM_HANDLER_BLOCK, + MY_OTHER_ITEM_HANDLER_BLOCK); +} +``` + +通常,注册将特定于一些方块实体类型,因此提供了 `registerBlockEntity` 辅助方法: +```java + event.registerBlockEntity( + Capabilities.ItemHandler.BLOCK, // 注册的功能性 + MY_BLOCK_ENTITY_TYPE, // 注册的方块实体类型 + (myBlockEntity, side) -> <为 myBlockEntity 和 side 返回 IItemHandler>); +``` + +如果之前由方块或方块实体提供者返回的功能性不再有效,**您必须通过调用 `level.invalidateCapabilities(pos)` 来使缓存失效**。有关更多信息,请参考上文的[失效部分][invalidation]。 + +实体的注册类似,使用 `registerEntity`: +```java +event.registerEntity( + Capabilities.ItemHandler.ENTITY, // 要注册的功能性 + MY_ENTITY_TYPE, // 要注册的实体类型 + (myEntity, context) -> <返回 myEntity 的 IItemHandler>); +``` + +物品的注册也类似。注意,提供者接收堆叠: +```java +event.registerItem( + Capabilities.ItemHandler.ITEM, // 要注册的功能性 + (itemStack, context) -> <返回 itemStack 的 IItemHandler>, + // 要注册的物品 + MY_ITEM, + MY_OTHER_ITEM); +``` + +## 为所有对象注册功能性 + +如果由于某种原因您需要为所有方块、实体或物品注册一个提供者,您将需要遍历相应的注册表并为每个对象注册提供者。 + +例如,NeoForge使用这个系统为所有的 `BucketItem`(不包括子类)注册一个流体处理器功能性: +```java +// 作为参考,您可以在 `CapabilityHooks` 类中找到这段代码。 +for (Item item : BuiltInRegistries.ITEM) { + if (item.getClass() == BucketItem.class) { + event.registerItem(Capabilities.FluidHandler.ITEM, (stack, ctx) -> new FluidBucketWrapper(stack), item); + } +} +``` + +按照注册的顺序请求提供者提供功能性。如果您想在NeoForge已经为您的某个对象注册的提供者之前运行,请使用更高优先级注册您的 `RegisterCapabilitiesEvent` 处理器。例如: +```java +modBus.addListener(RegisterCapabilitiesEvent.class, event -> { + event.registerItem( + Capabilities.FluidHandler.ITEM, + (stack, ctx) -> new MyCustomFluidBucketWrapper(stack), + // 要注册的方块 + MY_CUSTOM_BUCKET); +}, EventPriority.HIGH); // 使用 HIGH 优先级在 NeoForge 之前注册! +``` +查看 [`CapabilityHooks`][capability-hooks] 以获取 NeoForge 本身注册的提供者列表。 + +[block-cap-provider]: https://github.com/neoforged/NeoForge/blob/1.20.x/src/main/java/net/neoforged/neoforge/capabilities/IBlockCapabilityProvider.java +[capability-hooks]: https://github.com/neoforged/NeoForge/blob/1.20.x/src/main/java/net/neoforged/neoforge/capabilities/CapabilityHooks.java +[invalidation]: #block-capability-invalidation diff --git a/i18n/zh-Hans/docusaurus-plugin-content-docs/current/datastorage/codecs.md b/i18n/zh-Hans/docusaurus-plugin-content-docs/current/datastorage/codecs.md new file mode 100644 index 000000000..4009381bb --- /dev/null +++ b/i18n/zh-Hans/docusaurus-plugin-content-docs/current/datastorage/codecs.md @@ -0,0 +1,446 @@ +# 编解码器 + +编解码器是 Mojang 的 [DataFixerUpper] 库中的一种序列化工具,用于描述对象在不同格式之间的转换方式,如将对象从 `JsonElement` 的 JSON 格式转换为 NBT 的 `Tag` 格式。 + +## 使用编解码器 + +编解码器主要用于将 Java 对象编码(或序列化)成某种数据格式,并将格式化的数据对象解码(或反序列化)回其关联的 Java 类型。这通常通过 `Codec#encodeStart` 和 `Codec#parse` 来实现。 + +### 动态操作 + +为了确定将数据编码和解码至哪种中间文件格式,`#encodeStart` 和 `#parse` 都需要一个 `DynamicOps` 实例来定义该格式中的数据。 + +[DataFixerUpper] 库包含了 `JsonOps`,用于对存储在 [`Gson`][gson] 的 `JsonElement` 实例中的 JSON 数据进行编解码。`JsonOps` 支持两种 `JsonElement` 序列化版本:`JsonOps#INSTANCE` 定义了标准的 JSON 文件,而 `JsonOps#COMPRESSED` 允许将数据压缩成单一字符串。 + +```java +// 假设 exampleCodec 代表一个 Codec +// 假设 exampleObject 为一个 ExampleJavaObject +// 假设 exampleJson 为一个 JsonElement + +// 将 Java 对象编码为常规 JsonElement +exampleCodec.encodeStart(JsonOps.INSTANCE, exampleObject); + +// 将 Java 对象编码为压缩的 JsonElement +exampleCodec.encodeStart(JsonOps.COMPRESSED, exampleObject); + +// 将 JsonElement 解码为 Java 对象 +// 假设 JsonElement 是正常解析的 +exampleCodec.parse(JsonOps.INSTANCE, exampleJson); +``` + +Minecraft 还提供了 `NbtOps` 用于对存储在 `Tag` 实例中的 NBT 数据进行编解码。可以通过 `NbtOps#INSTANCE` 来引用。 + +```java +// 假设 exampleCodec 代表一个 Codec +// 假设 exampleObject 为一个 ExampleJavaObject +// 假设 exampleNbt 为一个 Tag + +// 将 Java 对象编码为 Tag +exampleCodec.encodeStart(JsonOps.INSTANCE, exampleObject); + +// 将 Tag 解码为 Java 对象 +exampleCodec.parse(JsonOps.INSTANCE, exampleNbt); +``` + +#### 格式转换 + +`DynamicOps` 还可以单独用来在两种不同的编码格式之间转换。这可以通过使用 `#convertTo` 并提供 `DynamicOps` 格式和要转换的编码对象来完成。 + +```java +// 将 Tag 转换为 JsonElement +// 假设 exampleTag 为一个 Tag +JsonElement convertedJson = NbtOps.INSTANCE.convertTo(JsonOps.INSTANCE, exampleTag); +``` + +### 数据结果 + +使用编解码器编码或解码数据时返回的 `DataResult` 将根据转换是否成功,持有转换后的实例或一些错误数据。当转换成功时,由 `#result` 提供的 `Optional` 将包含成功转换的对象。如果转换失败,由 `#error` 提供的 `Optional` 将包含 `PartialResult`,后者持有错误消息和根据编解码器部分转换的对象。 + +此外,`DataResult` 上有许多方法可以用来将结果或错误转换为所需格式。例如,`#resultOrPartial` 将返回一个 `Optional`,在成功时包含结果,在失败时包含部分转换的对象。此方法接受一个字符串消费者以确定如何报告错误消息(如果存在)。 + +```java +// 假设 exampleCodec 代表一个 Codec +// 假设 exampleJson 为一个 JsonElement + +// 将 JsonElement 解码为 Java 对象 +DataResult result = exampleCodec.parse(JsonOps.INSTANCE, exampleJson); + +result + // 获取结果或部分错误时的错误消息 + .resultOrPartial(errorMessage -> /* 处理错误消息 */) + // 如果结果或部分存在,则进行某些操作 + .ifPresent(decodedObject + + -> /* 处理解码对象 */); +``` + +## 现有编解码器 + +### 原始类型 + +`Codec` 类包含了一些定义的原始类型的静态编解码器实例。 + +| 编解码器 | Java 类型 | +|------------|--------| +| `BOOL` | `Boolean` | +| `BYTE` | `Byte` | +| `SHORT` | `Short` | +| `INT` | `Integer` | +| `LONG` | `Long` | +| `FLOAT` | `Float` | +| `DOUBLE` | `Double` | +| `STRING` | `String` | +| `BYTE_BUFFER` | `ByteBuffer` | +| `INT_STREAM` | `IntStream` | +| `LONG_STREAM` | `LongStream` | +| `PASSTHROUGH` | `Dynamic` | +| `EMPTY` | `Unit` | + +* `Dynamic` 是一个在支持的 `DynamicOps` 格式中编码值的对象。这些通常用于将编码对象格式转换为其他编码对象格式。 +* `Unit` 是用于表示 `null` 对象的对象。 + +### 原版和 Forge + +Minecraft 和 Forge 定义了许多常见对象的编解码器。一些示例包括用于 `ResourceLocation` 的 `ResourceLocation#CODEC`,用于 `DateTimeFormatter#ISO_INSTANT` 格式的 `Instant` 的 `ExtraCodecs#INSTANT_ISO8601`,以及用于 `CompoundTag` 的 `CompoundTag#CODEC`。 + +:::警告 +使用 `JsonOps` 的 `CompoundTag` 不能从 JSON 解码数字列表。`JsonOps` 在转换时会将数字设置为其最窄类型。`ListTag` 强制其数据使用特定类型,因此不同类型的数字(例如 `64` 会是 `byte`,`384` 会是 `short`)在转换时会引发错误。 +::: + +原版和 Forge 注册也有针对注册表所包含对象类型的编解码器(例如 `Registry#BLOCK` 或 `ForgeRegistries#BLOCKS` 有一个 `Codec`)。`Registry#byNameCodec` 和 `IForgeRegistry#getCodec` 会将注册表对象编码为其注册名,或者如果压缩,则为整数标识符。原版注册表还有一个 `Registry#holderByNameCodec`,它将编码为注册名并解码为被 `Holder` 包装的注册表对象。 + +## 创建编解码器 + +可以为任何对象创建编解码器。为了便于理解,将显示等效的编码 JSON。 + +### 记录 + +编解码器可以通过使用记录来定义对象。每个记录编解码器定义了具有明确命名字段的任何对象。创建记录编解码器的方法有很多,但最简单的是通过 `RecordCodecBuilder#create`。 + +`RecordCodecBuilder#create` 接受一个函数,该函数定义了一个 `Instance` 并返回一个应用(`App`)到构建对象的对象。这可以与创建类*实例*和用于*应用*类的构造函数联系起来。 + +```java +// 一个需要创建编解码器的对象 +public class SomeObject { + + public SomeObject(String s, int i, boolean b) { /* ... */ } + + public String s() { /* ... */ } + + public int i() { /* ... */ } + + public boolean b() { /* ... */ } +} +``` + +#### 字段 + +`Instance` 可以使用 `#group` 定义多达 16 个字段。每个字段必须是一个定义了对象被制造的实例及对象类型的应用。满足此要求的最简单方式是使用 `Codec`,设置字段的解码名称,并设置用于编码字段的 getter。 + +字段可以使用 `#fieldOf` 从 `Codec` 创建,如果字段是必需的,或使用 `#optionalFieldOf` 创建,如果字段被包装在 `Optional` 中或默认存在。任一方法都需要包含编码对象中字段名称的字符串。然后可以使用 `#forGetter` 设置用于编码字段的 getter,它接受一个函数,该函数给定对象,返回字段数据。 + +从那里 + +,生成的产品可以通过 `#apply` 应用,以定义如何为应用构建对象。为了方便起见,分组字段应按照它们在构造函数中出现的顺序列出,以便函数可以简单地是一个构造函数方法引用。 + +```java +public static final Codec RECORD_CODEC = RecordCodecBuilder.create(instance -> // 给定一个实例 + instance.group( // 在实例中定义字段 + Codec.STRING.fieldOf("s").forGetter(SomeObject::s), // 字符串 + Codec.INT.optionalFieldOf("i", 0).forGetter(SomeObject::i), // 整数,默认为 0(如果字段不存在) + Codec.BOOL.fieldOf("b").forGetter(SomeObject::b) // 布尔 + ).apply(instance, SomeObject::new) // 定义如何创建对象 +); +``` + +```js +// 编码后的 SomeObject +{ + "s": "value", + "i": 5, + "b": false +} + +// 另一个编码后的 SomeObject +{ + "s": "value2", + // i 被省略,默认为 0 + "b": true +} +``` + +### 转换器 + +编解码器可以通过映射方法转换成等效或部分等效的表现形式。每个映射方法接收两个函数:一个用于将当前类型转换为新类型,另一个用于将新类型转换回当前类型。这是通过 `#xmap` 函数完成的。 + +```java +// 一个类 +public class ClassA { + + public ClassB toB() { /* ... */ } +} + +// 另一个等效的类 +public class ClassB { + + public ClassA toA() { /* ... */ } +} + +// 假设存在某个编解码器 A_CODEC +public static final Codec B_CODEC = A_CODEC.xmap(ClassA::toB, ClassB::toA); +``` + +如果类型部分等效,即转换过程中存在某些限制,则存在返回 `DataResult` 的映射函数,可用于在遇到异常或无效状态时返回错误状态。 + +是否 A 完全等同于 B | 是否 B 完全等同于 A | 转换方法 +:---: | :---: | :--- +是 | 是 | `#xmap` +是 | 否 | `#flatComapMap` +否 | 是 | `#comapFlatMap` +否 | 否 | `#flatXMap` + +```java +// 给定一个字符串编解码器转换为整数 +// 并非所有字符串都可以变成整数(A 与 B 非完全等效) +// 所有整数都可以变成字符串(B 与 A 完全等效) +public static final Codec INT_CODEC = Codec.STRING.comapFlatMap( + s -> { // 返回失败时包含错误的数据结果 + try { + return DataResult.success(Integer.valueOf(s)); + } catch (NumberFormatException e) { + return DataResult.error(s + " 不是一个整数。"); + } + }, + Integer::toString // 常规函数 +); +``` + +```js +// 将返回 5 +"5" + +// 将错误,不是整数 +"value" +``` + +#### 范围编解码器 + +范围编解码器是 `#flatXMap` 的实现,如果值不在设定的最小值和最大值之间,则返回错误的 `DataResult`。如果值超出范围,仍会提供部分结果。分别有整数、浮点和双精度通过 `#intRange`、`#floatRange` 和 `#doubleRange` 实现。 + +```java +public static final Codec RANGE_CODEC = Codec.intRange(0, 4); +``` + +```js +// 将有效,在 [0, 4] 内 +4 + +// 将错误,在 [0, 4] 外 +5 +``` + +### 默认值 + +如果编码或解码的结果失败,可以通过 `Codec#orElse` 或 `Codec#orElseGet` 提供默认值。 + +```java +public static final Codec DEFAULT_CODEC = Codec.INT.orElse(0); // 也可以通过 #orElseGet 提供值 +``` + +```js +// 不是整数,默认为 0 +"value" +``` + +### 单位 + +一个编解码器,提供代码中的值并不编码任何东西,可以使用 `Codec#unit` 表示。如果编解码器在数据对象中使用了一个不可编码的条目,这非常有用。 + +```java +public static final Codec> UNIT_CODEC = Codec.unit( + () -> ForgeRegistries.BLOCKS // 也可以是原始值 +); +``` + +```js +// 这里没有任何内容,将返回方块注册表编解码器 +``` + +### 列表 + +可以从对象编解码器生成一个对象列表的编解码器,通过 `Codec#listOf` 实现。 + +```java +// BlockPos#CODEC 是一个 Codec +public static final Codec> LIST_CODEC = BlockPos.CODEC.listOf(); +``` + +```js +// 编码的 List +[ + [1, 2, 3], // BlockPos(1, 2, 3) + [4, 5, 6], // BlockPos(4, 5, 6) + [7, 8, 9] // BlockPos(7, 8, 9) +] +`` + +` + +使用列表编解码器解码的列表对象存储在一个**不可变**列表中。如果需要可变列表,则应该对列表编解码器应用[变换器]。 + +### 映射 + +可以通过两个编解码器生成键和值对象映射的编解码器,通过 `Codec#unboundedMap` 实现。无界映射可以指定任何基于字符串的或转换为字符串的值作为键。 + +```java +// BlockPos#CODEC 是一个 Codec +public static final Codec> MAP_CODEC = Codec.unboundedMap(Codec.STRING, BlockPos.CODEC); +``` + +```js +// 编码的 Map +{ + "key1": [1, 2, 3], // key1 -> BlockPos(1, 2, 3) + "key2": [4, 5, 6], // key2 -> BlockPos(4, 5, 6) + "key3": [7, 8, 9] // key3 -> BlockPos(7, 8, 9) +} +``` + +使用无界映射编解码器解码的映射对象存储在一个**不可变**映射中。如果需要可变映射,则应对映射编解码器应用[变换器]。 + +:::警告 +无界映射只支持可以编码/解码为字符串的键。可以使用键值[对]列表编解码器来绕过此限制。 +::: + +### 对 + +可以通过两个编解码器生成对象对的编解码器,通过 `Codec#pair` 实现。 + +对编解码器通过首先解码对中的左对象,然后取剩下的编码对象部分并从中解码右对象来解码对象。因此,编解码器必须在解码后表达关于编码对象的某些信息(如[记录]),或者必须被增强为 `MapCodec` 并通过 `#codec` 转换为常规编解码器。这通常可以通过将编解码器作为某个对象的[字段]来实现。 + +```java +public static final Codec> PAIR_CODEC = Codec.pair( + Codec.INT.fieldOf("left").codec(), + Codec.STRING.fieldOf("right").codec() +); +``` + +```js +// 编码的 Pair +{ + "left": 5, // fieldOf 查找左对象的 'left' 键 + "right": "value" // fieldOf 查找右对象的 'right' 键 +} +``` + +:::tips +可以使用非字符串键的映射编解码器通过应用带有[变换器]的键值对列表来编码/解码。 +::: + +### Either 编解码器 + +可以通过两个编解码器生成一个针对某个对象数据的两种不同编解码方法的编解码器,使用 `Codec#either` 实现。 + +Either 编解码器首先尝试使用第一个编解码器解码对象。如果失败,它将尝试使用第二个编解码器。如果第二次也失败,那么 `DataResult` 将只包含第二次编解码器失败的错误。 + +```java +public static final Codec> EITHER_CODEC = Codec.either( + Codec.INT, + Codec.STRING +); +``` + +```js +// 编码 Either$Left +5 + +// 编码 Either$Right +"value" +``` + +:::tips +这可以与[变换器]结合使用,从两种不同的编码方法中获取特定对象。 +::: + +### 分派编解码器 + +编解码器可以拥有可以根据某些指定类型解码特定对象的子编解码器,通过 `Codec#dispatch` 实现。这通常用于包含编解码器的注册表,如规则测试或方块放置器。 + +分派编解码器首先尝试从某个字符串键(通常是 `type`)获取编码类型。从那里开始,解码类型,调用用于解码实际对象的特定编解码器的获取器。如果用于解码对象的 `DynamicOps` 压缩其映射,或者对象编解码器本身没有增强成 `MapCodec`(如记录或字段化原语),则对象需要存储在 `value` 键中。否则,对象将在与其余数据相同的级别上解码。 + +```java +// 定义我们的对象 +public abstract class ExampleObject { + + // 定义用于指定编码对象类型的方法 + public abstract Codec type(); +} + +// 创建存储字符串的简单对象 +public class StringObject extends ExampleObject { + + public StringObject(String s) { /* ... */ } + + public String s() { /* ... */ } + + public Codec type() { + // 一个注册的注册表对象 + // "string": + // Codec.STRING.xmap(StringObject::new, StringObject::s) + return STRING_OBJECT_CODEC.get(); + } +} + +// 创建存储字符串和整数的复杂对象 +public class ComplexObject extends ExampleObject { + + public ComplexObject(String s, int i) { /* ... */ } + + public String s() { /* ... */ } + + public int i() { /* ... */ } + + public Codec type() { + // 一个注册的注册表对象 + // "complex": + // RecordCodecBuilder.create(instance -> + // instance.group( + // Codec.STRING.fieldOf("s").forGetter(ComplexObject::s), + // Codec.INT.fieldOf("i").forGetter(ComplexObject::i) + // ).apply(instance, ComplexObject::new) + // ) + return COMPLEX_OBJECT_CODEC.get(); + } +} + +// 假设存在一个 IForgeRegistry> DISPATCH +public static final Codec = DISPATCH.getCodec() // 获取 Codec> + .dispatch( + ExampleObject::type, // 从特定对象获取编解码器 + Function.identity() // 从注册表获取编解码器 + ); +``` + +```js +// 简单对象 +{ + "type": "string", // 对于 StringObject + "value": "value" // 编解码器类型未从 MapCodec 增强,需要字段 +} + +// 复杂对象 +{ + "type": "complex", // 对于 ComplexObject + + // 编解码器类型从 MapCodec 增强,可以内联 + "s": "value", + "i": 0 +} +``` + +[DataFixerUpper]: https://github.com/Mojang/DataFixerUpper +[gson]: https://github.com/google/gson +[transformer]: #transformer-codecs +[pair]: #pair +[records]: #records +[field]: #fields diff --git a/i18n/zh-Hans/docusaurus-plugin-content-docs/current/datastorage/nbt.md b/i18n/zh-Hans/docusaurus-plugin-content-docs/current/datastorage/nbt.md new file mode 100644 index 000000000..f46adb011 --- /dev/null +++ b/i18n/zh-Hans/docusaurus-plugin-content-docs/current/datastorage/nbt.md @@ -0,0 +1,100 @@ +# Named Binary Tag (NBT) + +NBT 是 Minecraft 最初时期由 Notch 本人引入的一种格式,它在整个 Minecraft 代码库中广泛用于数据存储。 + +## 规范 + +NBT 规范与 JSON 规范类似,但有一些区别: + +- 存在字节、短整型、长整型和浮点型的明确类型,分别以 `b`、`s`、`l` 和 `f` 为后缀,类似于在 Java 代码中的表示方式。 + - 双精度浮点型也可以用 `d` 后缀,但这不是必需的,类似于 Java 代码中的可选 `i` 后缀不被允许。 + - 后缀不区分大小写。例如,`64b` 与 `64B` 相同,`0.5F` 与 `0.5f` 相同。 +- 布尔类型不存在,而是用字节表示。`true` 变为 `1b`,`false` 变为 `0b`。 + - 当前实现将所有非零值视为 `true`,因此 `2b` 也会被视为 `true`。 +- NBT 中不存在 `null` 的等效物。 +- 键周围的引号是可选的。所以 JSON 属性 `"duration": 20` 在 NBT 中可以表示为 `duration: 20` 或 `"duration": 20`。 +- 在 JSON 中被称为子对象的东西,在 NBT 中被称为**复合标签**(或简称复合)。 +- NBT 列表不能混合匹配类型,不同于 JSON。列表类型由第一个元素确定,或在代码中定义。 + - 然而,列表的列表可以混合匹配不同的列表类型。因此,一个列表包含两个列表,其中第一个是字符串列表,第二个是字节列表,是允许的。 +- 存在特殊的**数组**类型,它们不同于列表,但遵循包含元素在方括号中的模式。有三种数组类型: + - 字节数组,以 `B;` 开头。例如:`[B;0b,30b]` + - 整数数组,以 `I;` 开头。例如:`[I;0,-300]` + - 长整型数组,以 `L;` 开头。例如:`[L;0l,240l]` +- 列表、数组和复合标签中允许有尾随逗号。 + +## NBT 文件 + +Minecraft 广泛使用 `.nbt` 文件,例如 [datapacks][datapack] 中的结构文件。包含区域内容(即一系列区块)的区域文件(`.mca`),以及游戏中不同位置使用的各种 `.dat` 文件,也是 NBT 文件。 + +NBT 文件通常使用 GZip 压缩。因此,它们是二进制文件,不能直接编辑。 + +## NBT 在代码中的使用 + +与 JSON 类似,所有 NBT 对象都是封闭对象的子对象。让我们创建一个: + +```java +CompoundTag tag = new CompoundTag(); +``` + +现在我们可以将数据放入该标签: + +```java +tag.putInt("Color", 0xffffff); +tag.putString("Level", "minecraft:overworld"); +tag.putDouble("IAmRunningOutOfIdeasForNamesHere", 1d); +``` + +这里存在几个辅助方法,例如,`putIntArray` 也有一个便利方法,除了标准变体接受 `int[]` 外,还接受 `List`。 + +当然,我们也可以从该标签中获取值: + +```java +int color = tag.getInt("Color"); +String level = tag.getString("Level"); +double d = tag.getDouble("IAmRunningOutOfIdeasForNamesHere"); +``` + +如果不存在,数字类型将返回 0。字符串将返回 `""` 如果不存在。更复杂的类型(列表、数组、复合标签)如果不存在会抛出异常。 + +因此,我们 + +希望通过检查标签元素是否存在来进行防护: + +```java +boolean hasColor = tag.contains("Color"); +boolean hasColorMoreExplicitly = tag.contains("Color", Tag.TAG_INT); +``` + +`TAG_INT` 常量在 `Tag` 中定义,这是所有标签类型的超接口。大多数标签类型除了 `CompoundTag` 外大多是内部的,例如 `ByteTag` 或 `StringTag`,尽管如果你偶然遇到一些,直接的 `CompoundTag#get` 和 `#put` 方法可以与它们一起工作。 + +不过,有一个明显的例外:`ListTag`。处理这些是特别的,因为当通过 `CompoundTag#getList` 获取列表标签时,你还必须指定列表类型。例如,获取字符串列表会像这样工作: + +```java +ListTag list = tag.getList("SomeListHere", Tag.TAG_STRING); +``` + +类似地,创建 `ListTag` 时,也必须在创建过程中指定列表类型: + +```java +ListTag list = new ListTag(List.of("Value1", "Value2"), Tag.TAG_STRING); +``` + +最后,直接在其他 `CompoundTag` 中操作 `CompoundTag` 利用 `CompoundTag#get` 和 `#put`: + +```java +tag.put("Tag", new CompoundTag()); +tag.get("Tag"); +``` + +## NBT 的用途 + +NBT 在 Minecraft 中有很多用途。一些最常见的例子包括 [`ItemStack`][itemstack]、[`BlockEntity`][blockentity] 和 `Entity`。 + +## 另见 + +- [Minecraft Wiki 上的 NBT 格式][nbtwiki] + +[blockentity]: ../blockentities/index.md +[datapack]: ../resources/server/index.md +[itemstack]: ../items/index.md#itemstacks +[nbtwiki]: https://minecraft.wiki/w/NBT_format diff --git a/i18n/zh-Hans/docusaurus-plugin-content-docs/current/datastorage/saveddata.md b/i18n/zh-Hans/docusaurus-plugin-content-docs/current/datastorage/saveddata.md new file mode 100644 index 000000000..1e2247519 --- /dev/null +++ b/i18n/zh-Hans/docusaurus-plugin-content-docs/current/datastorage/saveddata.md @@ -0,0 +1,40 @@ +# 保存的数据系统 + +保存的数据(SD)系统可用于在各级别上保存额外数据。 + +_如果数据特定于某些方块实体、区块或实体,请考虑使用[数据附件](attachments)。_ + +## 声明 + +每个 SD 实现必须是 `SavedData` 类的子类型。有两个重要方法需要注意: + +* `save`:允许实现将 NBT 数据写入级别。 +* `setDirty`:在更改数据后必须调用的方法,以通知游戏需要写入的更改。如果不调用,`#save` 将不会被调用,原始数据将保持不变。 + +## 附加到级别 + +任何 `SavedData` 都是动态加载和/或附加到级别的。因此,如果一个级别上从未创建过,那么它将不存在。 + +`SavedData` 是从 `DimensionDataStorage` 创建和加载的,可以通过调用 `ServerChunkCache#getDataStorage` 或 `ServerLevel#getDataStorage` 访问。从那里,您可以通过调用 `DimensionDataStorage#computeIfAbsent` 来获取或创建您的 SD 实例。这将尝试获取当前存在的 SD 实例,或创建一个新实例并加载所有可用数据。 + +`DimensionDataStorage#computeIfAbsent` 接受两个参数。第一个是 `SavedData.Factory` 的实例,它包括一个供应商来构建一个新的 SD 实例和一个函数,以将 NBT 数据加载到 SD 并返回它。第二个参数是实施级别的 `data` 文件夹中存储的 `.dat` 文件的名称。名称必须是有效的文件名,不能包含 `/` 或 `\`。 + +例如,如果一个 SD 在下界被命名为 "example",则会在 `.//DIM-1/data/example.dat` 创建一个文件,并且会像这样实现: + +```java +// 在某个类中 +public ExampleSavedData create() { + return new ExampleSavedData(); +} + +public ExampleSavedData load(CompoundTag tag) { + ExampleSavedData data = this.create(); + // 加载保存的数据 + return data; +} + +// 在类中的某个方法内 +netherDataStorage.computeIfAbsent(new Factory<>(this::create, this::load), "example"); +``` + +如果一个 SD 不特定于一个级别,那么 SD 应该附加到主世界,可以从 `MinecraftServer#overworld` 获取。主世界是唯一从不完全卸载的维度,因此非常适合在其上存储多级别数据。 diff --git a/i18n/zh-Hans/docusaurus-plugin-content-docs/current/gettingstarted/_category_.json b/i18n/zh-Hans/docusaurus-plugin-content-docs/current/gettingstarted/_category_.json new file mode 100644 index 000000000..da8a48332 --- /dev/null +++ b/i18n/zh-Hans/docusaurus-plugin-content-docs/current/gettingstarted/_category_.json @@ -0,0 +1,4 @@ +{ + "label": "Getting Started with Neo", + "position": 1 +} \ No newline at end of file diff --git a/i18n/zh-Hans/docusaurus-plugin-content-docs/current/gettingstarted/index.md b/i18n/zh-Hans/docusaurus-plugin-content-docs/current/gettingstarted/index.md new file mode 100644 index 000000000..f9995afec --- /dev/null +++ b/i18n/zh-Hans/docusaurus-plugin-content-docs/current/gettingstarted/index.md @@ -0,0 +1,62 @@ +# 开始使用 NeoForge + +本节包含有关如何设置 NeoForge 工作区以及如何运行和测试您的模组的信息。 + +## 先决条件 + +- 熟悉 Java 编程语言,特别是其面向对象、多态、泛型和功能性特征。 +- 安装 Java 17 开发工具包(JDK)和 64 位 Java 虚拟机(JVM)。NeoForge 推荐并官方支持 [Microsoft 的 OpenJDK 构建][jdk],但其他 JDK 也应该可以工作。 + +:::warning +确保您正在使用 64 位 JVM。检查的一种方式是在终端运行 `java -version`。使用 32 位 JVM 可能会出现问题,因为很多东西已经不再支持 32 位 JVM 了。 +::: + +- 熟悉您选择的集成开发环境(IDE)。 + - NeoForge 官方支持 [IntelliJ IDEA][intellij] 和 [Eclipse][eclipse],这两者都集成了 Gradle 支持。但是,可以使用任何 IDE,从 Netbeans 或 Visual Studio Code 到 Vim 或 Emacs 都可以。 +- 熟悉 [Git][git] 和 [GitHub][github]。技术上这不是必需的,但它会让您的生活变得更加轻松。 + +## 设置工作区 + +- 打开 [Mod Developer Kit (MDK)][mdk] GitHub 仓库,点击“使用此模板”并将新创建的仓库克隆到您的本地机器。 + - 如果您不想使用 GitHub,或者想获取旧提交或非默认分支的模板(例如,对于旧版本),您也可以下载仓库的 ZIP 文件(在代码 -> 下载 ZIP 下)并解压。 +- 打开您的 IDE 并导入 Gradle 项目。Eclipse 和 IntelliJ IDEA 会为您自动完成此操作。如果您使用的 IDE 不支持此操作,您也可以通过 `gradlew` 终端命令来完成。 + - 首次进行此操作时,Gradle 将下载 NeoForge 的所有依赖项,包括 Minecraft 本身,并对其进行反编译。这可能需要相当长的时间(取决于您的硬件和网络强度,最多可达一个小时)。 + - 每当您对 Gradle 文件进行更改时,需要重新加载 Gradle 更改,可以通过您的 IDE 中的“重新加载 Gradle”按钮或再次通过 `gradlew` 终端命令来完成。 + +## 自定义您的模组信息 + +您的模组的许多基本属性都可以在 `gradle.properties` 文件中更改。这包括模组名称或模组版本等基本事项。有关更多信息,请参阅 `gradle.properties` 文件中的注释,或查看 [关于 `gradle.properties` 文件的文档][properties]。 + +如果您想进一步修改构建过程,可以编辑 `build.gradle` 文件。NeoGradle,NeoForge 的 Gradle 插件,提供了几个配置选项,其中一些选项通过 `build.gradle` 文件中的注释进行了解释。有关完整文档,请参阅 [NeoGradle 文档][neogradle]。 + +:::warning +只有在您知道自己在做什么时才编辑 `build.gradle` 和 `settings.gradle` 文件。所有基本属性都可以通过 `gradle.properties` 设置。 +::: + +## 构建和测试您的模组 + +要构建您的模组,请运行 `gradlew build`。这将在 `build/libs` 中输出一个名为 `-.jar` 的文件。`` 和 `` 是通过 `build.gradle` 设置的属性,默认为 `gradle.properties` 文件中的 `mod_id` 和 `mod_version` 值;如果需要,这可以在 `build.gradle` 中更改。然后可以将生成的 JAR 文件放置在启用 NeoForge 的 Minecraft 设置的 `mods` 文件夹中,或 + +上传到模组分发平台。 + +要在测试环境中运行您的模组,您可以使用生成的运行配置或使用相关任务(例如 `gradlew runClient`)。这将从相应的运行目录(例如 `runs/client` 或 `runs/server`)启动 Minecraft,以及任何指定的源集。默认 MDK 包括 `main` 源集,因此在 `src/main/java` 中编写的任何代码都将被应用。 + +### 服务器测试 + +如果您正在运行一个专用服务器,无论是通过运行配置还是 `gradlew runServer`,服务器将立即关闭。您需要通过编辑运行目录中的 `eula.txt` 文件来接受 Minecraft EULA。 + +一旦接受,服务器将加载并在 `localhost`(或默认的 `127.0.0.1`)下可用。然而,您仍然无法加入,因为服务器默认会进入在线模式,这需要认证(开发玩家没有)。要解决此问题,请再次停止您的服务器并将 `server.properties` 文件中的 `online-mode` 属性设置为 `false`。现在,启动您的服务器,您应该能够连接。 + +:::tips +您应该始终在专用服务器环境中测试您的模组。这包括[仅客户端模组][client],因为这些在服务器上加载时不应做任何事情。 +::: + +[client]: ../concepts/sides.md +[eclipse]: https://www.eclipse.org/downloads/ +[git]: https://www.git-scm.com/ +[github]: https://github.com/ +[intellij]: https://www.jetbrains.com/idea/ +[jdk]: https://learn.microsoft.com/en-us/java/openjdk/download#openjdk-17 +[mdk]: https://github.com/neoforged/MDK +[neogradle]: https://docs.neoforged.net/neogradle/docs/ +[properties]: modfiles.md#gradleproperties diff --git a/i18n/zh-Hans/docusaurus-plugin-content-docs/current/gettingstarted/modfiles.md b/i18n/zh-Hans/docusaurus-plugin-content-docs/current/gettingstarted/modfiles.md new file mode 100644 index 000000000..733cda92c --- /dev/null +++ b/i18n/zh-Hans/docusaurus-plugin-content-docs/current/gettingstarted/modfiles.md @@ -0,0 +1,79 @@ +# Mod Files + +Mod文件负责确定哪些模组被打包到您的JAR文件中,显示在“Mods”菜单中的信息,以及您的模组在游戏中应如何加载。 + +## gradle.properties + +`gradle.properties`文件保存了您的模组的各种常见属性,例如模组ID或模组版本。在构建过程中,Gradle会读取这些文件中的值,并将它们内联到各种位置,如[mods.toml][modstoml]文件中。这样,您只需要在一个地方更改值,然后它们就会为您在所有地方应用。 + +大多数值也在[MDK的`gradle.properties`文件]中以注释形式解释。 + +## mods.toml + +位于`src/main/resources/META-INF/mods.toml`的`mods.toml`文件是一个[TOML][toml]格式的文件,定义了您的模组的元数据。它还包含了有关如何将您的模组加载到游戏中的附加信息,以及显示在“Mods”菜单中的显示信息。[MDK提供的`mods.toml`文件][mdkmodstoml]包含解释每个条目的注释,这里将更详细地解释。 + +`mods.toml`可以分为三部分:非模组特定属性,这些属性与模组文件相关联;模组属性,每个模组有一个部分;依赖配置,每个模组的依赖项有一个部分。与`mods.toml`文件关联的某些属性是强制性的;强制性属性需要指定一个值,否则会抛出异常。 + +### 非模组特定属性 + +非模组特定属性与JAR本身相关联,指示如何加载模组以及任何额外的全局元数据。 + +### 模组特定属性 + +模组特定属性通过`[[mods]]`头部与指定的模组关联。这是一个[表的数组][array];所有键/值属性都将附加到该模组,直到下一个头部。 + +### 依赖配置 + +模组可以指定它们的依赖关系,NeoForge在加载模组之前会检查这些配置。这些配置是使用`[[dependencies.]]`创建的,其中`modid`是消耗依赖项的模组的标识符。 + +## Mod 入口点 + +现在`mods.toml`已经填写完毕,我们需要为模组提供一个入口点。入口点本质上是执行模组的起点。入口点本身由`mods.toml`中使用的语言加载器确定。 + +### `javafml` 和 `@Mod` + +`javafml`是NeoForge为Java编程语言提供的语言加载器。入口点是使用带有`@Mod`注解的公共类定义的。`@Mod`的值必须包含`mods.toml`中指定的模组ID之一。从那里开始,所有初始化逻辑(例如[注册事件][events]或[添加`DeferredRegister`][registration])可以在类的构造函数中指定。 + +```java +@Mod("examplemod") // 必须与mods.toml中的模组ID匹配 +public class Example { + public Example(IEventBus modBus) { // 参数是模组特定的事件总线,例如用于注册和事件 + // 在这里初始化逻辑 + } +} +``` + +:::tips +`mods.toml`文件中的模组和`@Mod`入口点必须有1对1的匹配。这意味着对于每个定义的模组,必须有一个带有该模组ID的`@Mod`注解。 +::: + +### `lowcodefml` + +`lowcodefml`是一种语言加载器,用作以模组形式分发数据包和资源包,而无需代码内入口点。它被指定为`lowcodefml`而不是`nocodefml`,因为未来可能需要最小的代码添加。 + +[array]: https://toml.io/en/v1.0.0#array-of-tables +[atlasviewer]: https://github.com/XFactHD/AtlasViewer/blob/1.20.2/neoforge/src/main/resources/META-INF/services/xfacthd.atlasviewer.platform.services.IPlatformHelper +[events]: ../concepts/events.md +[features]: #features +[group]: #the-group-id +[i18n]: ../resources/client/i18n.md#translating-mod-metadata +[javafml]: #javafml-and-mod +[jei]: https://www.curseforge.com/minecraft/mc-mods/jei +[lowcodefml]: #lowcodefml +[mcversioning]: versioning.md#minecraft +[mdkgradleproperties]: https://github.com/neoforged/MDK/blob/main/gradle.properties +[mdkmodstoml]: https://github.com/neoforged/MDK/blob/main/src/main/resources/META-INF/mods.toml +[modstoml]: #modstoml +[mojmaps]: https://github.com/neoforged/NeoForm/blob/main/Mojang.md +[multiline]: https://toml.io/en/v1.0.0#string +[mvr]: https://maven.apache.org/enforcer/enforcer-rules/versionRanges.html +[neoversioning]: versioning.md#neoforge +[packaging]: ./structuring.md#packaging +[registration]: ../concepts/registries.md#deferredregister +[serviceload]: https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/ServiceLoader.html#load(java.lang.Class) +[sides]: ../concepts/sides.md +[spdx]: https://spdx.org/licenses/ +[toml]: https://toml.io/ +[update]: ../misc/updatechecker.md +[uses]: https://docs.oracle.com/javase/specs/jls/se17/html/jls-7.html#jls-7.7.3 +[versioning]: ./versioning.md diff --git a/i18n/zh-Hans/docusaurus-plugin-content-docs/current/gettingstarted/structuring.md b/i18n/zh-Hans/docusaurus-plugin-content-docs/current/gettingstarted/structuring.md new file mode 100644 index 000000000..113e5ee52 --- /dev/null +++ b/i18n/zh-Hans/docusaurus-plugin-content-docs/current/gettingstarted/structuring.md @@ -0,0 +1,82 @@ +# 结构化您的模组 + +结构化模组有利于维护、贡献并提供对底层代码库的更清晰理解。以下是一些建议,源自 Java、Minecraft 和 NeoForge。 + +:::note +您不必遵循下面的建议;您可以按照自己认为合适的方式结构化您的模组。然而,还是强烈建议这样做。 +::: + +## 包结构 + +在构建您的模组时,选择一个独特的顶级包结构。许多程序员会为不同的类、接口等使用相同的名称。Java允许在不同的包中有相同名称的类。因此,如果两个类有相同的包和相同的名称,只有一个类会被加载,很可能会导致游戏崩溃。 + +``` +a.jar + - com.example.ExampleClass +b.jar + - com.example.ExampleClass // 这个类通常不会被加载 +``` + +当涉及到加载模块时,这一点尤其相关。如果两个包在不同的模块下有同名的类文件,这将导致模组加载器在启动时崩溃,因为模组模块被导出到游戏和其他模组。 + +``` +模块 A + - 包 X + - 类 I + - 类 J +模块 B + - 包 X // 这个包将导致模组加载器崩溃,因为已经有一个模块导出了包 X + - 类 R + - 类 S + - 类 T +``` + +因此,您的顶级包应该是您拥有的东西:域名、电子邮件地址、网站(或子域)等。它甚至可以是您的名字或用户名,只要您能保证它在预期目标中具有唯一可识别性。此外,顶级包还应与您的[group id][group]匹配。 + +| 类型 | 值 | 顶级包 | +|:---------:|:--------------:|:----------------------| +| 域名 | example.com | `com.example` | +| 子域名 | example.github.io | `io.github.example` | +| 电邮 | example@gmail.com | `com.gmail.example` | + +下一级包应该是您的模组的ID(例如,`com.example.examplemod`,其中`examplemod`是模组ID)。这将保证,除非您有两个模组ID相同的模组(这种情况永远不应该发生),否则您的包不应该有任何加载问题。 + +您可以在[Oracle的教程页面][naming]上找到一些额外的命名约定。 + +### 子包组织 + +除了顶级包,强烈建议将模组的类分配到子包中。有两种主要的方法来做到这一点: + +* **按功能分组**:为具有共同目的的类制作子包。例如,可以将方块放在`block`下,物品放在`item`下,实体放在`entity`下等。Minecraft本身使用类似的结构(有一些例外)。 +* **按逻辑分组**:为具有共同逻辑的类制作子包。例如,如果您正在创建一个新类型的工作台,您可以将其方块、菜单、物品等放在`feature.crafting_table`下。 + +#### 客户端、服务器和数据包 + +通常,只针对给定侧或运行时的代码应该与其他类隔离在一个单独的子包中。例如,与[数据生成][datagen]相关的代码应该放在`data`包中,只在专用服务器上的代码应该放在`server`包中。 + +强烈建议将[仅客户端代码][sides] + +隔离在`client`子包中。这是因为专用服务器无法访问Minecraft中的任何客户端专用包,并且如果您的模组试图访问它们,将会崩溃。因此,拥有一个专用包提供了一个很好的检查方法,以验证您没有在模组内跨侧访问。 + +## 类命名方案 + +常见的类命名方案使得更容易理解类的用途或轻松找到特定类。 + +类通常以其类型为后缀,例如: + +* 一个名为 `PowerRing` 的 `Item` -> `PowerRingItem`。 +* 一个名为 `NotDirt` 的 `Block` -> `NotDirtBlock`。 +* 一个 `Oven` 的菜单 -> `OvenMenu`。 + +:::tip +Mojang 通常遵循类似的结构来命名所有类,除了实体。这些实体只用它们的名字表示(例如,`Pig`,`Zombie`等)。 +::: + +## 从多种方法中选择一种 + +执行特定任务有许多方法:注册对象、监听事件等。通常建议使用单一方法来完成给定任务。这可以提高可读性,并避免可能发生的奇怪交互或冗余(例如,您的事件监听器运行两次)。 + +[group]: index.md#the-group-id +[naming]: https://docs.oracle.com/javase/tutorial/java/package/namingpkgs.html +[datagen]: ../resources/index.md#data-generation +[sides]: ../concepts/sides.md diff --git a/i18n/zh-Hans/docusaurus-plugin-content-docs/current/gettingstarted/versioning.md b/i18n/zh-Hans/docusaurus-plugin-content-docs/current/gettingstarted/versioning.md new file mode 100644 index 000000000..c17930719 --- /dev/null +++ b/i18n/zh-Hans/docusaurus-plugin-content-docs/current/gettingstarted/versioning.md @@ -0,0 +1,100 @@ +# 版本管理 + +本文将详细解释 Minecraft 和 NeoForge 的版本管理方式,并为模组版本提供一些建议。 + +## Minecraft + +Minecraft 使用[语义化版本控制][semver](semantic versioning,简称 "semver"),格式为 `major.minor.patch`。例如,Minecraft 1.20.2 的主版本号(major)为 1,次版本号(minor)为 20,修订号(patch)为 2。 + +自从 2011 年 Minecraft 1.0 发布以来,Minecraft 的主版本号一直使用 `1`。在此之前,版本号方案经常变化,有诸如 `a1.1`(Alpha 1.1)、`b1.7.3`(Beta 1.7.3)甚至 `infdev` 版本,这些版本根本没有遵循明确的版本控制方案。由于 `1` 作为主版本号已经持续了十多年,并且鉴于 Minecraft 2 的内部笑话,通常认为这一情况不太可能改变。 + +### 快照版 + +快照版偏离了标准的 semver 方案。它们被标记为 `YYwWWa`,其中 `YY` 代表年份的最后两位数字(例如 `23`),`WW` 代表那年的周数(例如 `01`)。例如,快照 `23w01a` 是 2023 年第一周发布的快照。 + +`a` 后缀用于同一周发布两个快照的情况(第二个快照则被命名为 `23w01b`)。Mojang 过去偶尔使用过这种方式。此外,还有像 `20w14infinite` 这样的快照,它是[2020 年无限维度愚人节玩笑][infinite]。 + +### 预发布和候选发布版本 + +当快照周期接近完成时,Mojang 开始发布所谓的预发布版本。预发布版本被视为功能完整的版本,专注于修复bug。它们使用 semver 格式并附加 `-preX` 后缀。例如,1.20.2 的第一个预发布版本被命名为 `1.20.2-pre1`。通常会有多个预发布版本,相应地使用 `-pre2`、`-pre3` 等后缀。 + +类似地,当预发布周期完成时,Mojang 发布第一个候选发布版本(后缀为 `-rc1`,例如 `1.20.2-rc1`)。Mojang 的目标是发布一个候选版本,如果没有进一步的 bug 出现,则可以发布该版本。然而,如果出现意外的 bug,则可能会有 `-rc2`、`-rc3` 等版本,类似于预发布版本。 + +## NeoForge + +NeoForge 使用一种调整过的 semver 系统:主版本号是 Minecraft 的次版本号,次版本号是 Minecraft 的修订号,而修订号则是“实际”的 NeoForge 版本。例如,NeoForge 20.2.59 是 Minecraft 1.20.2 的第 60 版(从 0 开始计数)。由于 `1` 作为开头的数字不太可能改变,请参见[上文][minecraft]了解原因。 + +NeoForge 的一些位置还使用了[Maven 版本范围][mvr],例如 [`mods.toml`][modstoml] 文件中的 Minecraft 和 NeoForge 版本范围。这些主要与 semver 兼容,但有些例外(例如,它不考虑 `pre` 标签)。 + +## 模组 + +没有最佳的版本控制系统。不同的开发风格、项目范围等都会影响选择哪种版本控制系统的决定。有时,也可以组合使用多种版本控制系统。本节试图概述一些 + +常用的版本控制系统,并提供现实生活中的示例。 + +通常,模组的文件名看起来像 `modid-.jar`。所以如果我们的模组ID是 `examplemod`,版本是 `1.2.3`,我们的模组文件将被命名为 `examplemod-1.2.3.jar`。 + +:::note +版本控制系统是建议,而不是严格执行的规则。特别是关于何时更改("bump")版本,以及以何种方式更改。如果您想使用不同的版本控制系统,没有人会阻止您。 +::: + +### 语义化版本控制 + +语义化版本控制("semver")包括三个部分:`major.minor.patch`。当对代码库进行重大更改时,主版本号会提升,这通常与重大新功能和错误修复相关。次版本号在引入次要功能时提升,修订号只包括错误修复时提升。 + +通常认为任何 `0.x.x` 版本都是开发版本,而第一个(完整)发布版本应该提升到 `1.0.0`。 + +"次要功能提升次版本号,错误修复提升修订号" 的规则在实践中经常被忽视。一个流行的例子是 Minecraft 本身,它通过次版本号进行重大功能更新,通过修订号进行次要功能更新,并在快照中修复错误(见上文)。 + +根据模组更新的频率,版本号可能会有所增减。例如,[Supplementaries][supplementaries]目前的版本为`2.6.31`(撰写本文时)。在`patch`版本中,出现三位或四位数字的情况完全有可能。 + +### “简化”与“扩展”语义化版本控制 + +有时候,我们可以看到只有两个数字的语义化版本控制,这种是一种“简化”语义化版本控制,或称为“2部分”语义化版本控制。这种版本号只包含`major.minor`模式。它通常用于只添加几个简单物体的小型模组,这类模组很少需要更新(除了Minecraft版本更新),常常永远停留在`1.0`版本。 + +而“扩展”语义化版本控制,或称为“4部分”语义化版本控制,包括四个数字(比如`1.0.0.0`)。根据模组的不同,其格式可能是`major.api.minor.patch`,或`major.minor.patch.hotfix`,或是完全不同的格式——没有统一的标准方式。 + +对于`major.api.minor.patch`,`major`版本与`api`版本是分离的。这意味着`major`(功能)位和`api`位可以独立提升。这种方式通常用于那些提供API供其他模组开发者使用的模组。例如,[Mekanism][mekanism]当前的版本是10.4.5.19(撰写本文时)。 + +对于`major.minor.patch.hotfix`,则是将修订级别分为两部分。这是[Create][create]模组使用的方法,目前版本为0.5.1f(撰写本文时)。注意,Create模组将hotfix表示为一个字母,而不是第四个数字,以保持与常规语义化版本控制的兼容。 + +:::info +简化语义化版本控制、扩展语义化版本控制、2部分语义化版本控制和4部分语义化版本控制并非官方术语或标准化格式。 +::: + +### Alpha、Beta、Release阶段 + +像Minecraft本身一样,模组开发通常也会经历软件工程中熟知的`alpha`/`beta`/`release`阶段,其中`alpha`代表不稳定/实验版本(有时也称为`experimental`或`snapshot`),`beta`代表半稳定版本,而`release`则代表稳定版本(有时用`stable`代替`release`)。 + +一些模组利用它们的主要版本号来表示Minecraft版本的更新。例如,[JEI][jei]使用`13.x.x.x`表示Minecraft 1.19.2,`14.x.x.x`表示1.19.4,以及`15.x.x.x`表示1.20.1(不存在1.19.3和1.20.0的版本)。其他一些模组则在模组名称后附加标签,例如[Minecolonies][minecolonies]模组,当前版本为`1.1.328-BETA`(撰写本文时)。 + +### 包含Minecraft版本 + +在模组文件名中包含其适用的Minecraft版本是常见做法。这使得最终用户更容易确定模组适用于哪个版本的Minecraft。这通常发生在模组版本之前或之后,前者比后者更常见。例如,JEI的最新版本`16.0.0.28`(撰写本文时) + +适用于1.20.2,可能表示为`jei-1.20.2-16.0.0.28`或`jei-16.0.0.28-1.20.2`。 + +### 包含模组加载器 + +正如您可能知道的那样,NeoForge并非唯一的模组加载器,许多模组开发者在多个平台上开发。因此,需要一种方法来区分同一个模组、相同版本但适用于不同模组加载器的两个文件。 + +通常,这是通过在名称中包含模组加载器来实现的。例如,`jei-neoforge-1.20.2-16.0.0.28`、`jei-1.20.2-neoforge-16.0.0.28`或`jei-1.20.2-16.0.0.28-neoforge`都是有效的命名方式。对于其他模组加载器,`neoforge`部分会被替换为`forge`、`fabric`、`quilt`或您可能正在使用的其他模组加载器。 + +### Maven备注 + +Maven——用于依赖托管的系统,其版本控制系统在一些细节上与语义化版本控制不同(尽管基本的`major.minor.patch`模式仍然相同)。NeoForge的某些部分使用了相关的[Maven版本范围(MVR)][mvr]系统。在选择您的版本控制方案时,您应确保它与MVR兼容,否则模组将无法依赖您模组的特定版本! + +[create]: https://www.curseforge.com/minecraft/mc-mods/create +[infinite]: https://minecraft.wiki/w/Java_Edition_20w14∞ +[jei]: https://www.curseforge.com/minecraft/mc-mods/jei +[mekanism]: https://www.curseforge.com/minecraft/mc-mods/mekanism +[minecolonies]: https://www.curseforge.com/minecraft/mc-mods/minecolonies +[minecraft]: #minecraft +[modstoml]: modfiles.md#modstoml +[mvr]: https://maven.apache.org/enforcer/enforcer-rules/versionRanges.html +[mvr]: https://maven.apache.org/ref/3.5.2/maven-artifact/apidocs/org/apache/maven/artifact/versioning/ComparableVersion.html +[neoforge]: #neoforge +[pre]: #pre-releases +[rc]: #release-candidates +[semver]: https://semver.org/ +[supplementaries]: https://www.curseforge.com/minecraft/mc-mods/supplementaries diff --git a/i18n/zh-Hans/docusaurus-plugin-content-docs/current/gui/_category_.json b/i18n/zh-Hans/docusaurus-plugin-content-docs/current/gui/_category_.json new file mode 100644 index 000000000..59cfbca67 --- /dev/null +++ b/i18n/zh-Hans/docusaurus-plugin-content-docs/current/gui/_category_.json @@ -0,0 +1,3 @@ +{ + "label": "GUIs" +} \ No newline at end of file diff --git a/i18n/zh-Hans/docusaurus-plugin-content-docs/current/gui/menus.md b/i18n/zh-Hans/docusaurus-plugin-content-docs/current/gui/menus.md new file mode 100644 index 000000000..3d3c0513b --- /dev/null +++ b/i18n/zh-Hans/docusaurus-plugin-content-docs/current/gui/menus.md @@ -0,0 +1,351 @@ +根据您的要求,这里是对上述Markdown文档进行的中文翻译,尽量使语言流畅且专业: + +--- + +# 菜单 + +菜单是图形用户界面(GUI)后端的一种类型,它们处理与某些数据持有者交互的逻辑。菜单本身并不持有数据。它们是视图,允许用户间接修改内部数据持有者的状态。因此,数据持有者不应直接与任何菜单耦合,而是传递数据引用以调用和修改。 + +## `MenuType` + +菜单是动态创建和删除的,因此它们不是注册对象。因此,另一个工厂对象被注册以便轻松创建和引用菜单的*类型*。对于一个菜单,这些就是`MenuType`。 + +`MenuType`必须[注册]。 + +### `MenuSupplier` + +通过将`MenuSupplier`和`FeatureFlagSet`传递给其构造函数来创建`MenuType`。`MenuSupplier`代表一个函数,它接受容器的id和查看菜单的玩家的库存,返回一个新创建的[`AbstractContainerMenu`][acm]。 + +```java +// 对于某个DeferredRegister> REGISTER +public static final RegistryObject> MY_MENU = REGISTER.register("my_menu", () -> new MenuType(MyMenu::new, FeatureFlags.DEFAULT_FLAGS)); + +// 在MyMenu中,一个AbstractContainerMenu子类 +public MyMenu(int containerId, Inventory playerInv) { + super(MY_MENU.get(), containerId); + // ... +} +``` + +:::note +容器标识符对每个玩家是唯一的。这意味着在两个不同的玩家上相同的容器id将代表两个不同的菜单,即使他们正在查看同一个数据持有者。 +::: + +`MenuSupplier`通常负责在客户端创建菜单,使用虚拟数据引用来存储和交互服务器数据持有者同步的信息。 + +### `IContainerFactory` + +如果客户端需要额外的信息(例如,数据持有者在世界中的位置),则可以使用子类`IContainerFactory`。除了容器id和玩家库存外,这还提供了一个`FriendlyByteBuf`,可以存储从服务器发送的额外信息。可以通过`IForgeMenuType#create`使用`IContainerFactory`创建`MenuType`。 + +```java +// 对于某个DeferredRegister> REGISTER +public static final RegistryObject> MY_MENU_EXTRA = REGISTER.register("my_menu_extra", () -> IForgeMenuType.create(MyMenu::new)); + +// 在MyMenuExtra中,一个AbstractContainerMenu子类 +public MyMenuExtra(int containerId, Inventory playerInv, FriendlyByteBuf extraData) { + super(MY_MENU_EXTRA.get(), containerId); + // 存储缓冲区的额外数据 + // ... +} +``` + +## `AbstractContainerMenu` + +所有菜单都继承自`AbstractContainerMenu`。一个菜单需要两个参数,[`MenuType`][mt],代表菜单本身的类型,以及容器id,代表当前访问者的菜单的唯一标识符。 + +:::caution +玩家一次最多只能打开100个唯一的菜单。 +::: + +每个菜单应包含两个构造函数:一个用于在服务器上初始化菜单,另一个用于在客户端初始化菜单。用于初始化客户端菜单的构造函数是提供给`MenuType`的。 + +```java +// 客户端菜单构造函数 +public MyMenu(int containerId, Inventory playerInventory) { + this(containerId, playerInventory); +} + +// 服务器菜单构造函数 +public MyMenu(int containerId, Inventory playerInventory) { + // ... +} +``` + +每个菜单实现必须实现两个方法:`#stillValid`和[`#quickMoveStack`][qms]。 + +### `#stillValid`和`ContainerLevelAccess` + +`#stillValid` + +确定是否应该为给定玩家保持菜单打开。这通常指向静态的`#stillValid`,它需要一个`ContainerLevelAccess`、玩家和菜单所附属的`Block`。客户端菜单必须始终为此方法返回`true`,这是静态`#stillValid`的默认设置。此实现检查玩家是否在数据存储对象所在位置的八个方块范围内。 + +`ContainerLevelAccess`提供了当前级别和块所在位置的封闭范围。在服务器上构造菜单时,可以通过调用`ContainerLevelAccess#create`创建新的访问权限。客户端菜单构造函数可以传递`ContainerLevelAccess#NULL`,这将不做任何事。 + +```java +// 客户端菜单构造函数 +public MyMenuAccess(int containerId, Inventory playerInventory) { + this(containerId, playerInventory, ContainerLevelAccess.NULL); +} + +// 服务器菜单构造函数 +public MyMenuAccess(int containerId, Inventory playerInventory, ContainerLevelAccess access) { + // ... +} + +// 假设此菜单附属于RegistryObject MY_BLOCK +@Override +public boolean stillValid(Player player) { + return AbstractContainerMenu.stillValid(this.access, player, MY_BLOCK.get()); +} +``` + +### 数据同步 + +需要在服务器和客户端上存在某些数据才能显示给玩家。为此,菜单实现了基本的数据同步层,以便每当当前数据与上次同步到客户端的数据不匹配时进行检查。对于玩家来说,这每个游戏刻都会检查。 + +Minecraft默认支持两种数据同步形式:通过`Slot`的`ItemStack`和通过`DataSlot`的整数。`Slot`和`DataSlot`是持有可以在屏幕上被玩家修改的数据存储引用的视图,前提是动作有效。这些可以通过在构造函数中的`#addSlot`和`#addDataSlot`添加到菜单中。 + +:::note +由于NeoForge不推荐使用`Container`,转而使用[`IItemHandler`能力][cap],下文将围绕使用这种能力的变体:`SlotItemHandler`进行解释。 +::: + +`SlotItemHandler`包含四个参数:代表堆栈所在库存的`IItemHandler`,此槽特别代表的堆栈的索引,以及槽在屏幕上相对于`AbstractContainerScreen#leftPos`和`#topPos`的左上位置的x和y位置。客户端菜单构造函数应始终提供一个相同大小的空库存实例。 + +在大多数情况下,首先添加菜单包含的任何槽,然后是玩家的库存,最后是玩家的快捷栏。要从菜单访问任何单独的`Slot`,必须根据添加槽的顺序计算索引。 + +`DataSlot`是一个抽象类,应实现getter和setter以引用数据存储对象中存储的数据。客户端菜单构造函数应始终通过`DataSlot#standalone`提供一个新实例。 + +这些,连同槽一起,应在每次初始化新菜单时重新创建。 + +:::caution +尽管`DataSlot`存储一个整数,但由于其通过网络发送值的方式,它实际上限制为一个**短整型**(-32768至32767)。整数的16个高阶位被忽略。 +::: + +```java +// 假设我们有一个大小为5的数据对象库存 +// 假设每次服务器菜单初始化时都构建了一个DataSlot + +// 客户端菜单构造函数 +public MyMenuAccess(int containerId, Inventory playerInventory) { + this(containerId, playerInventory, new ItemStackHandler(5), DataSlot.standalone()); +} + +// 服务器菜单构造函数 +public MyMenuAccess(int containerId, Inventory playerInventory, IItemHandler dataInventory, DataSlot dataSingle) { + // 检查数据库存大小是否为某个固定值 + // 然后,为数据库存添加槽位 + this.addSlot(new SlotItemHandler(dataInventory, /*...*/)); + + // 为玩家库存添加槽位 + this.addSlot(new Slot(playerInventory, /*...*/)); + + // 为处理的整数添加数据槽位 + this.addDataSlot(dataSingle); + + // ... +} +``` + +#### `ContainerData` + +如果需要将多个整数同步到客户端,可以使用`ContainerData`来引用这些整数。这个接口功能类似于索引查找,其中每个索引代表一个不同的整数。如果将`ContainerData`通过`#addDataSlots`方法添加到菜单中,则可以直接在数据对象本身中构建`ContainerData`。该方法为接口指定的数据量创建新的`DataSlot`。客户端菜单构造函数应始终通过`SimpleContainerData`提供新实例。 + +```java +// 假设我们有一个大小为3的ContainerData + +// 客户端菜单构造函数 +public MyMenuAccess(int containerId, Inventory playerInventory) { + this(containerId, playerInventory, new SimpleContainerData(3)); +} + +// 服务器菜单构造函数 +public MyMenuAccess(int containerId, Inventory playerInventory, ContainerData dataMultiple) { + // 检查ContainerData大小是否为某个固定值 + checkContainerDataCount(dataMultiple, 3); + + // 为处理的整数添加数据槽位 + this.addDataSlots(dataMultiple); + + // ... +} +``` + +:::caution +由于`ContainerData`委托给`DataSlot`,这些也被限制为一个**短整型**(-32768至32767)。 +::: + +#### `#quickMoveStack` + +`#quickMoveStack`是任何菜单必须实现的第二个方法。每当一个堆栈被快速移动或通过Shift点击从其当前槽中移出时,就会调用此方法,直到堆栈完全移出其前一个槽或没有其他地方可以放置堆栈为止。该方法返回被快速移动的槽中的堆栈副本。 + +通常使用 `#moveItemStackTo` 在插槽之间移动堆叠物品,该方法将堆叠物品移动到第一个可用的插槽。它接受要移动的堆叠物品、要尝试将堆叠物品移动到的第一个插槽索引(包括)、最后一个插槽索引(不包括),以及是否从第一个到最后一个插槽进行检查(当为 `false` 时)或从最后一个到第一个插槽进行检查(当为 `true` 时)。 + +在 Minecraft 的各种实现中,该方法在逻辑上相当一致: + +```java +// 假设我们有一个大小为 5 的数据存储库 +// 存储库有 4 个输入插槽(索引 1 - 4),输出到一个结果插槽(索引 0) +// 我们还有 27 个玩家存储库插槽和 9 个快捷栏插槽 +// 因此,实际插槽的索引如下: +// - 数据存储库:结果(0)、输入(1 - 4) +// - 玩家存储库(5 - 31) +// - 玩家快捷栏(32 - 40) +@Override +public ItemStack quickMoveStack(Player player, int quickMovedSlotIndex) { + // 快速移动的插槽堆叠物品 + ItemStack quickMovedStack = ItemStack.EMPTY; + // 快速移动的插槽 + Slot quickMovedSlot = this.slots.get(quickMovedSlotIndex) + + // 如果插槽在有效范围内且插槽不为空 + if (quickMovedSlot != null && quickMovedSlot.hasItem()) { + // 获取要移动的原始堆叠物品 + ItemStack rawStack = quickMovedSlot.getItem(); + // 将插槽堆叠设置为原始堆叠的副本 + quickMovedStack = rawStack.copy(); + + /* + 以下快速移动逻辑可以简化为在数据存储库中,尝试移动到玩家存储库/快捷栏,反之亦然,对于无法转换数据的容器(例如箱子)。 + */ + + // 如果快速移动在数据存储库结果插槽上执行 + if (quickMovedSlotIndex == 0) { + // 尝试将结果插槽移动到玩家存储库/快捷栏 + if (!this.moveItemStackTo(rawStack, 5, 41, true)) { + // 如果无法移动,不再快速移动 + return ItemStack.EMPTY; + } + + // 对结果插槽快速移动执行逻辑 + slot.onQuickCraft(rawStack, quickMovedStack); + } + // 否则如果快速移动在玩家存储库或快捷栏插槽上执行 + else if (quickMovedSlotIndex >= 5 && quickMovedSlotIndex < 41) { + // 尝试将玩家存储库/快捷栏插槽移动到数据存储库输入插槽 + if (!this.moveItemStackTo(rawStack, 1, 5, false)) { + // 如果无法移动且在玩家存储库插槽中,尝试移动到快捷栏 + if (quickMovedSlotIndex < 32) { + if (!this.moveItemStackTo(rawStack, 32, 41, false)) { + // 如果无法移动,不再快速移动 + return ItemStack.EMPTY; + } + } + // 否则尝试将快捷栏移动到玩家存储库插槽 + else if (!this.moveItemStackTo(rawStack, 5, 32, false)) { + // 如果无法移动,不再快速移动 + return ItemStack.EMPTY; + } + } + } + // 否则如果快速移动在数据存储库输入插槽上,则尝试移动到玩家存储库/快捷栏 + else if (!this.moveItemStackTo(rawStack, 5, 41, false)) { + // 如果无法移动,不再快速移动 + return ItemStack.EMPTY; + } + + if (rawStack.isEmpty()) { + // 如果原始堆叠完全移出插槽,则将插槽设置为空堆叠 + quickMovedSlot.set(ItemStack.EMPTY); + } else { + // 否则,通知插槽堆叠数量已更改 + quickMovedSlot.setChanged(); + } + + /* + 如果菜单不表示可以转换堆叠的容器(例如箱子),则可以删除以下 if 语句和 Slot#onTake 调用。 + */ + if (rawStack.getCount() == quickMovedStack.getCount()) { + // 如果原始堆叠无法移动到另一个插槽,则不再快速移动 + return ItemStack.EMPTY; + } + // 执行堆叠剩余部分后移动的逻辑 + quickMovedSlot.onTake(player, rawStack); + } + + return quickMovedStack; // 返回插槽堆叠 +} +``` + +## 打开菜单 + +一旦菜单类型已注册,菜单本身已完成,并且 [screen] 已附加,玩家就可以打开菜单。可以通过在逻辑服务器上调用 `NetworkHooks#openScreen` 来打开菜单。该方法接受打开菜单的玩家、服务器端菜单的 `MenuProvider`,以及可选的 `FriendlyByteBuf`,如果需要向客户端同步额外数据。 + +:::note +只有在使用 [`IContainerFactory`][icf] 创建菜单类型时,才应使用带有 `FriendlyByteBuf` 参数的 `NetworkHooks + +#openScreen`。 +::: + +#### `MenuProvider` + +`MenuProvider` 是一个包含两个方法的接口:`#createMenu`,用于创建菜单的服务器实例,以及 `#getDisplayName`,返回包含菜单标题的组件以传递给 [screen]。`#createMenu` 方法包含三个参数:菜单的容器 ID、打开菜单的玩家的库存,以及打开菜单的玩家。 + +可以使用 `SimpleMenuProvider` 轻松创建 `MenuProvider`,它接受一个方法引用来创建服务器菜单和菜单的标题。 + +```java +// 在某些实现中 +NetworkHooks.openScreen(serverPlayer, new SimpleMenuProvider( + (containerId, playerInventory, player) -> new MyMenu(containerId, playerInventory), + Component.translatable("menu.title.examplemod.mymenu") +)); +``` + +### 常见实现 + +通常通过某种玩家交互方式(例如右键单击方块或实体)打开菜单。 + +#### 方块实现 + +方块通常通过覆盖 `BlockBehaviour#use` 来实现菜单。如果在逻辑客户端上,交互返回 `InteractionResult#SUCCESS`。否则,打开菜单并返回 `InteractionResult#CONSUME`。 + +`MenuProvider` 应该通过覆盖 `BlockBehaviour#getMenuProvider` 来实现。原版方法使用此方法在旁观模式下查看菜单。 + +```java +// 在某个 Block 子类中 +@Override +public MenuProvider getMenuProvider(BlockState state, Level level, BlockPos pos) { + return new SimpleMenuProvider(/* ... */); +} + +@Override +public InteractionResult use(BlockState state, Level level, BlockPos pos, Player player, InteractionHand hand, BlockHitResult result) { + if (!level.isClientSide && player instanceof ServerPlayer serverPlayer) { + NetworkHooks.openScreen(serverPlayer, state.getMenuProvider(level, pos)); + } + return InteractionResult.sidedSuccess(level.isClientSide); +} +``` + +:::note +这是实现逻辑的最简单方法,不是唯一的方法。如果希望方块仅在满足某些条件时打开菜单,则需要事先将一些数据同步到客户端,以返回 `InteractionResult#PASS` 或 `#FAIL`,如果条件不满足。 +::: + +#### 生物实现 + +生物通常通过覆盖 `Mob#mobInteract` 来实现菜单。这与方块实现类似,唯一的区别是生物本身应该实现 `MenuProvider` 以支持旁观模式查看。 + +```java +public class MyMob extends Mob implements MenuProvider { + // ... + + @Override + public InteractionResult mobInteract(Player player, InteractionHand hand) { + if (!this.level.isClientSide && player instanceof ServerPlayer serverPlayer) { + NetworkHooks.openScreen(serverPlayer, this); + } + return InteractionResult.sidedSuccess(this.level.isClientSide); + } +} +``` + +:::note +再次强调,这是实现逻辑的最简单方法,不是唯一的方法。 +::: + +[registered]: ../concepts/registries.md#methods-for-registering +[acm]: #abstractcontainermenu +[mt]: #menutype +[qms]: #quickmovestack +[cap]: ../datastorage/capabilities.md#neoforge-provided-capabilities +[screen]: ./screens.md +[icf]: #icontainerfactory diff --git a/i18n/zh-Hans/docusaurus-plugin-content-docs/current/gui/screens.md b/i18n/zh-Hans/docusaurus-plugin-content-docs/current/gui/screens.md new file mode 100644 index 000000000..8824e99b5 --- /dev/null +++ b/i18n/zh-Hans/docusaurus-plugin-content-docs/current/gui/screens.md @@ -0,0 +1,346 @@ +## 屏幕 + +在 Minecraft 中,屏幕通常是所有图形用户界面(GUI)的基础,用于接收用户输入、在服务器上验证输入,并将结果同步回客户端。它们可以与[菜单](menus)结合使用,创建用于类似库存的视图的通信网络,或者它们可以是独立的,由模组开发者通过自己的[网络](../networking/index.md)实现来处理。 + +屏幕由许多部分组成,这使得理解“屏幕”在 Minecraft 中实际上是什么变得困难。因此,本文档将介绍屏幕的每个组件以及它是如何应用的,然后讨论屏幕本身。 + +## 相对坐标 + +无论何时渲染任何东西,都需要一些标识符来指定其出现的位置。使用许多抽象化,Minecraft 的大多数渲染调用在一个坐标平面上接收 x、y 和 z 值。X 值从左到右增加,y 值从上到下增加,z 值从远到近增加。但是,这些坐标不是固定的范围。它们可以根据屏幕的大小和在选项中指定的比例而变化。因此,必须特别注意确保在渲染时的坐标值相对于可变的屏幕大小适当缩放。 + +有关如何使您的坐标相对的信息将在[屏幕](#屏幕)部分中提供。 + +:::caution +如果选择使用固定坐标或不正确地缩放屏幕,则渲染的对象可能会看起来奇怪或错位。检查坐标是否正确相对化的简单方法是单击您的视频设置中的“GUI 比例”按钮。此值用作除数以确定 GUI 应以哪个比例渲染。 +::: + +## GUI 图形 + +Minecraft 中渲染的任何 GUI 通常都是使用 `GuiGraphics` 进行的。`GuiGraphics` 是几乎所有渲染方法的第一个参数;它包含用于渲染常用对象的基本方法。这些方法分为五类:彩色矩形、字符串和纹理、物品和工具提示。还有一个用于渲染组件的额外方法(`#enableScissor` / `#disableScissor`)。`GuiGraphics` 还公开了 `PoseStack`,它应用了必要的转换,以便将组件正确渲染到屏幕上。此外,颜色采用[ARGB](https://en.wikipedia.org/wiki/RGBA_color_model#ARGB32)格式。 + +### 彩色矩形 + +通过位置颜色着色器绘制彩色矩形。可以绘制三种类型的彩色矩形。 + +首先是水平和垂直的一像素宽线,分别是 `#hLine` 和 `#vLine`。`#hLine` 接受两个 x 坐标,定义了左侧和右侧(包括在内),顶部 y 坐标和颜色。`#vLine` 接受左侧 x 坐标、两个 y 坐标,定义了顶部和底部(包括在内),以及颜色。 + +其次是 `#fill` 方法,它绘制一个矩形到屏幕上。线方法内部调用此方法。它接受左侧 x 坐标、顶部 y 坐标、右侧 x 坐标、底部 y 坐标和颜色。 + +最后是 `#fillGradient` 方法,它绘制一个具有垂直渐变的矩形。它接受右侧 x 坐标、底部 y 坐标、左侧 x 坐标、顶部 y 坐标、z 坐标、底部和顶部颜色。 + +### 字符串 + +字符串通过其 `Font` 绘制,通常包含正常、透明和偏移模式的自己的着色器。可以渲染两种对齐的字符串,每种字符串都带有背景阴影:左对齐字符串 (`#drawString`) 和居中对齐字符串 (`#drawCenteredString`)。这两种方法都接受要绘制字符串的字体、字符串本身、x 坐标(分别是字符串的左侧或中心)、顶部 y 坐标和颜色。 + +:::note +字符串通常应该作为 [`Component`](../resources/client/i18n.md#components) 传递,因为它们处理各种用例,包括该方法的其他两个重载。 +::: + +### 纹理 + +纹理通过贴图绘制,因此方法名为 `#blit`,在这种情况下,它复制图像的 + +位并直接将其绘制到屏幕上。这些是通过位置纹理着色器绘制的。虽然有许多不同的 `#blit` 重载,但我们只讨论两种静态 `#blit`。 + +第一个静态 `#blit` 接受六个整数,假定正在渲染的纹理位于一个 256 x 256 的 PNG 文件中。它接受左侧 x 和顶部 y 屏幕坐标,PNG 内部的左侧 x 和顶部 y 坐标,以及要渲染的图像的宽度和高度。 + +:::tip +必须指定 PNG 文件的大小,以便将坐标归一化为获取关联的 UV 值。 +::: + +第一个调用的静态 `#blit` 将此扩展为九个整数,仅假定图像位于 PNG 文件中。它接受左侧 x 和顶部 y 屏幕坐标,z 坐标(称为 blit 偏移量),PNG 内部的左侧 x 和顶部 y 坐标,要渲染的图像的宽度和高度,以及 PNG 文件的宽度和高度。 + +#### Blit 偏移量 + +在渲染纹理时,z 坐标通常设置为 blit 偏移量。偏移量负责在查看屏幕时正确分层渲染。具有较小 z 坐标的渲染在背景中渲染,反之,具有较大 z 坐标的渲染在前景中渲染。可以通过 `#translate` 方法直接在 `PoseStack` 本身上设置 z 偏移量。一些 `GuiGraphics` 方法内部应用了一些基本的偏移逻辑(例如物品渲染)。 + +:::caution +设置 blit 偏移量时,必须在渲染对象完成后重置它。否则,屏幕中的其他对象可能会以不正确的层次渲染,导致图形问题。建议在平移前推送当前姿势,然后在所有以偏移量渲染的对象完成后弹出。 +::: + +## 可渲染对象 + +`Renderable` 实际上是指可以被渲染的对象。这些包括屏幕、按钮、聊天框、列表等。`Renderable` 只有一个方法:`#render`。此方法接受用于将事物渲染到屏幕上的 `GuiGraphics`、鼠标的 x 和 y 位置(按相对屏幕大小缩放)、以及帧之间的时间差(自上一帧以来经过了多少个 tick)。 + +一些常见的可渲染对象是屏幕和“小部件”:通常渲染在屏幕上的可交互元素,如 `Button`、它的子类型 `ImageButton`,以及 `EditBox`,用于在屏幕上输入文本。 + +## GUI 事件侦听器 + +Minecraft 中呈现的任何屏幕都实现了 `GuiEventListener`。`GuiEventListener` 负责处理用户与屏幕的交互。这些包括鼠标(移动、点击、释放、拖动、滚动、悬停)和键盘(按下、释放、键入)的输入。每个方法返回关联动作是否成功地影响了屏幕。按钮、聊天框、列表等小部件也实现了此接口。 + +### 容器事件处理程序 + +与 `GuiEventListener` 几乎同义的是它们的子类型:`ContainerEventHandler`。这些负责处理在包含小部件的屏幕上的用户交互,管理当前聚焦的是哪个小部件以及如何应用相关交互。`ContainerEventHandler` 添加了三个额外功能:可交互的子元素、拖动和聚焦。 + +事件处理程序保存了子元素,用于确定元素的交互顺序。在鼠标事件处理程序中(不包括拖动),鼠标悬停在上面的第一个子元素将执行其逻辑。 + +通过鼠标拖动元素,通过 `#mouseClicked` 和 `#mouseReleased` 实现更精确的逻辑。 + +聚焦允许首先检查特定的子元素,并在事件执行期间处理它,例如在键盘事件或拖动鼠标期间。焦点通常通过 `#setFocused` 设置。此外,可以使用 `#nextFocusPath` 循环可交互的子元素,选择基于传递的 `FocusNavigationEvent` 的子元素。 + +:::note +屏幕通过 `AbstractContainerEventHandler` 实现了 `ContainerEventHandler`,它添加了拖动和聚焦子元素的设置和获取逻辑。 +::: + +## NarratableEntry + +`NarratableEntry` 是可以通过 Minecraft 的辅助功能叙述功能讲述的元素。每个元素可以根据悬停或选择的内容提供不同的叙述,通常由焦点、悬停,然后是所有其他情况优先。 + +`NarratableEntry` 有三个方法:确定元素的优先级的一个方法 (`#narrationPriority`),确定是否说出叙述的一个方法 (`#isActive`),最后是向其关联输出提供叙述的一个方法 (`#updateNarration`)。 + +:::note +Minecraft 中的所有小部件都是 `NarratableEntry`,因此通常不需要手动实现它,如果使用可用的子类型。 +::: + +## 屏幕子类型 + +有了上述所有知识,就可以构建一个基本的屏幕了。为了更方便理解,屏幕的组件将按照它们通常出现的顺序而被提及。 + +首先,所有的屏幕都需要一个组件 `Component` 来表示屏幕的标题。这个组件通常由其子类型绘制到屏幕上。它仅在基本屏幕中用于叙述消息。 + +```java +// 在某个屏幕的子类中 +public MyScreen(Component title) { + super(title); +} +``` + +### 初始化 + +屏幕初始化后,将调用 `#init` 方法。`#init` 方法设置屏幕的初始设置,从 `ItemRenderer` 和 `Minecraft` 实例到由游戏缩放的相对宽度和高度。在这个方法中进行任何设置,比如添加小部件或预计算相对坐标。如果游戏窗口大小改变,将通过调用 `#init` 方法来重新初始化屏幕。 + +有三种方法可以向屏幕添加小部件,每种方法都有不同的作用: + +方法 | 描述 +:---: | :--- +`#addWidget` | 添加一个可交互且叙述的小部件,但不渲染。 +`#addRenderableOnly` | 添加一个仅渲染的小部件;不可交互也不叙述。 +`#addRenderableWidget` | 添加一个既可交互、叙述又可渲染的小部件。 + +通常情况下,最常用的是 `#addRenderableWidget`。 + +```java +// 在某个屏幕的子类中 +@Override +protected void init() { + super.init(); + + // 添加小部件和预计算的值 + this.addRenderableWidget(new EditBox(/* ... */)); +} +``` + +### 屏幕更新 + +屏幕也会使用 `#tick` 方法进行更新,以执行一些用于渲染目的的客户端逻辑。最常见的示例是用于闪烁光标的 `EditBox`。 + +```java +// 在某个屏幕的子类中 +@Override +public void tick() { + super.tick(); + + // 添加 EditBox 中的更新逻辑 + this.editBox.tick(); +} +``` + +### 输入处理 + +由于屏幕是 `GuiEventListener` 的子类型,输入处理程序也可以被覆盖,例如处理特定的[按键][keymapping]逻辑。 + +### 屏幕渲染 + +最后,屏幕通过作为 `Renderable` 子类型而提供的 `#render` 方法进行渲染。正如前面提到的,`#render` 方法在每一帧绘制屏幕上的所有内容,比如背景、小部件、工具提示等。默认情况下,`#render` 方法只会将小部件渲染到屏幕上。 + +屏幕中通常不由子类型处理的两个最常见的渲染内容是背景和工具提示。 + +背景可以使用 `#renderBackground` 进行渲染,其中一个方法接受一个 v 偏移量,用于在渲染屏幕时绘制选项背景,当背后的级别无法显示时使用。 + +工具提示通过 `GuiGraphics#renderTooltip` 或 `GuiGraphics#renderComponentTooltip` 进行渲染,它们可以接受正在渲染的文本组件、可选的自定义工具提示组件以及工具提示应该在屏幕上渲染的 x / y 相对坐标。 + +```java +// 在某个屏幕的子类中 + +// mouseX 和 mouseY 表示光标在屏幕上的缩放坐标 +@Override +public void render(GuiGraphics graphics, int mouseX, int mouseY, float partialTick) { + // 通常首先渲染背景 + this.renderBackground(graphics); + + // 在小部件之前渲染东西(背景纹理) + + // 如果这是屏幕的直接子类,则渲染小部件 + super.render(graphics, mouseX, mouseY, partialTick); + + // 在小部件之后渲染东西(工具提示) +} +``` + +### 关闭屏幕 + +当屏幕关闭时,两个方法处理清理工作:`#onClose` 和 `#removed`。 + +`#onClose` 在用户输入关闭当前屏幕时调用。通常用作销毁和保存屏幕内部流程的回调。这包括向服务器发送数据包。 + +`#removed` 在屏幕改变之前调用,并释放给垃圾收集器。这处理任何在屏幕打开之前未重置回初始状态的内容。 + +```java +// 在某个屏幕的子类中 + +@Override +public void onClose() { + // 在这里停止任何处理程序 + + // 最后调用,以防干扰覆盖 + super.onClose(); +} + +@Override +public void removed() { + // 在这里重置初始状态 + + // 最后调用,以防干扰覆盖 + super.removed() +;} +``` + +## `AbstractContainerScreen` + +如果一个屏幕直接附加到一个[菜单][menus]上,则应该使用 `AbstractContainerScreen` 的子类。`AbstractContainerScreen` 作为菜单的渲染器和输入处理程序,并包含与插槽同步和交互的逻辑。因此,通常只需要覆盖或实现两个方法,就可以有一个可工作的容器屏幕。再次强调,为了更易于理解,容器屏幕的组件将按照它们通常出现的顺序进行说明。 + +通常,`AbstractContainerScreen` 需要三个参数:被打开的容器菜单(由泛型 `T` 表示)、玩家库存(仅用于显示名称 + +)和屏幕本身的标题。在这里,可以设置一些定位字段: + +字段 | 描述 +:---: | :--- +`imageWidth` | 背景使用的纹理的宽度。这通常位于一个 256 x 256 的 PNG 内,默认为 176。 +`imageHeight` | 背景使用的纹理的高度。这通常位于一个 256 x 256 的 PNG 内,默认为 166。 +`titleLabelX` | 屏幕标题将被渲染的相对 x 坐标。 +`titleLabelY` | 屏幕标题将被渲染的相对 y 坐标。 +`inventoryLabelX` | 玩家库存名称将被渲染的相对 x 坐标。 +`inventoryLabelY` | 玩家库存名称将被渲染的相对 y 坐标。 + +:::caution +在前面的部分中,提到应该在 `#init` 方法中设置预计算的相对坐标。这仍然适用,因为这里提到的值不是预计算的坐标,而是静态值和相对化的坐标。 + +图像值是静态且不变的,因为它们代表背景纹理的大小。为了在渲染时更方便,`#init` 方法中预计算了两个附加值 (`leftPos` 和 `topPos`),标记了背景将被渲染的左上角位置。标签坐标是相对于这些值的。 + +`leftPos` 和 `topPos` 也被用作渲染背景的便捷方式,因为它们已经表示传递到 `#blit` 方法的位置。 +:::caution + +```java +// 在某个 AbstractContainerScreen 子类中 +public MyContainerScreen(MyMenu menu, Inventory playerInventory, Component title) { + super(menu, playerInventory, title); + + this.titleLabelX = 10; + this.inventoryLabelX = 10; + + /* + * 如果 'imageHeight' 被改变,'inventoryLabelY' 也必须被 + * 改变,因为该值取决于 'imageHeight' 的值。 + */ +} +``` + +### 菜单访问 + +由于菜单被传递到屏幕中,现在可以通过 `menu` 字段访问任何在菜单中并通过插槽、数据插槽或自定义系统同步的值。 + +### 容器更新 + +当玩家活着并且正在查看屏幕时,容器屏幕在 `#tick` 方法中进行更新,通过 `#containerTick` 进行。这基本上取代了容器屏幕内部的 `#tick` 方法,最常见的用法是进行食谱书的更新。 + +```java +// 在某个 AbstractContainerScreen 子类中 +@Override +protected void containerTick() { + super.containerTick(); + + // 在这里更新内容 +} +``` + +### 渲染容器屏幕 + +容器屏幕的渲染涉及三种方法:`#renderBg`,用于渲染背景纹理,`#renderLabels`,用于在背景上方渲染任何文本,以及 `#render`,它包含前两种方法,并提供了一个灰色背景和工具提示。 + +从 `#render` 开始,最常见的覆盖(通常是唯一的情况)添加了背景,调用 super 来渲染容器屏幕,并最后在其上渲染工具提示。 + +```java +// 在某个 AbstractContainerScreen 子类中 +@Override +public void render(GuiGraphics graphics, int mouseX, int mouseY, float partialTick) { + this.renderBackground(graphics); + super.render(graphics, mouseX, mouseY, partialTick); + + /* + * 这个方法由容器屏幕添加,用于渲染鼠标悬停插槽的工具提示。 + */ + this.renderTooltip(graphics, mouseX, mouseY); +} +``` + +在 super 中,调用了 `#renderBg` 来渲染屏幕的背景。最标准的表示使用了三个方法调用:两个用于设置,一个用于绘制背景纹理。 + +```java +// 在某个 AbstractContainerScreen 子类中 + +// 背景纹理的位置(assets//) +private static final ResourceLocation BACKGROUND_LOCATION = new ResourceLocation(MOD_ID, "textures/gui/container/my_container_screen.png"); + +@Override +protected void renderBg(GuiGraphics graphics, float partialTick, int mouseX, int mouseY) { + /* + * 设置着色器使用的纹理位置。虽然最多可以设置12个纹理,但 'blit' 中使用的着色器 + * 只查看第一个纹理索引。 + */ + RenderSystem.setShaderTexture(0, BACKGROUND_LOCATION); + + /* + * 将背景纹理渲染到屏幕上。'leftPos' 和 'topPos' 应该已经表示纹理应该渲染的 + * 左上角位置,因为它们是从 'imageWidth' 和 'imageHeight' 预计算出来的。两个 + * 零表示在 256 x 256 PNG 文件内的整数 u/v 坐标。 + */ + graphics.blit(BACKGROUND_LOCATION, this.leftPos, this.topPos, 0, 0, this.imageWidth, this.imageHeight); +} +``` + +最后,调用 `#renderLabels` 来在背景上方但工具提示下方渲染任何文本。这简单地使用字体来绘制关联的组件。 + +```java +// 在某个 AbstractContainerScreen 子类中 +@Override +protected void renderLabels(GuiGraphics graphics, int mouseX, int mouseY) { + super.renderLabels(graphics, mouseX, mouseY); + + // 假设我们有一些 Component 'label' + // 'label' 将被绘制在 'labelX' 和 'labelY' 处 + graphics.drawString(this.font, this.label, this.labelX, this.labelY, 0x404040); +} +``` + +:::note +在渲染标签时,**不需要**指定 `leftPos` 和 `topPos` 偏移量。这些已经在 `PoseStack` 中被转换,因此该方法内的所有内容都相对于这些坐标进行绘制。 +::: + +## 注册 AbstractContainerScreen + +要将 `AbstractContainerScreen` 与菜单一起使用,需要对其进行注册。可以在 [**mod 事件总线**][modbus] 的 `RegisterMenuScreensEvent` 中调用 `register` 方法来完成。 + +```java +// 事件在 mod 事件总线上监听 +private void registerScreens(RegisterMenuScreensEvent event) { + event.register(MY_MENU.get(), MyContainerScreen::new); +} +``` + +[menus]: ./menus.md +[network]: ../networking/index.md +[screen]: #the-screen-subtype +[argb]: https://en.wikipedia.org/wiki/RGBA_color_model#ARGB32 +[component]: ../resources/client/i18n.md#components +[keymapping]: ../misc/keymappings.md#inside-a-gui +[modbus]: ../concepts/events.md#event-buses diff --git a/i18n/zh-Hans/docusaurus-plugin-content-docs/current/items/bewlr.md b/i18n/zh-Hans/docusaurus-plugin-content-docs/current/items/bewlr.md new file mode 100644 index 000000000..44f3d6d32 --- /dev/null +++ b/i18n/zh-Hans/docusaurus-plugin-content-docs/current/items/bewlr.md @@ -0,0 +1,36 @@ +### BlockEntityWithoutLevelRenderer + +`BlockEntityWithoutLevelRenderer` 是一种处理物品的动态渲染方法。这个系统比旧的 `ItemStack` 系统简单得多,因为旧系统需要一个 `BlockEntity`,并且无法访问 `ItemStack`。 + +使用 BlockEntityWithoutLevelRenderer +-------------------------- + +BlockEntityWithoutLevelRenderer 允许你使用 `public void renderByItem(ItemStack itemStack, ItemDisplayContext ctx, PoseStack poseStack, MultiBufferSource bufferSource, int combinedLight, int combinedOverlay)` 来渲染你的物品。 + +为了使用 BEWLR,`Item` 必须首先满足一个条件:它的模型对于 `BakedModel#isCustomRenderer` 返回 true。如果没有自定义渲染器,它将使用默认的 `ItemRenderer#getBlockEntityRenderer`。一旦返回 true,物品的 BEWLR 将被访问以进行渲染。 + +:::note +如果 `Block#getRenderShape` 设置为 `RenderShape#ENTITYBLOCK_ANIMATED`,`Block` 也会使用 BEWLR 进行渲染。 +::: + +要为物品设置 BEWLR,必须在 `Item#initializeClient` 中消费 `IClientItemExtensions` 的匿名实例。在匿名实例中,应该重写 `IClientItemExtensions#getCustomRenderer` 以返回你的 BEWLR 的实例: + +```java +// 在你的物品类中 +@Override +public void initializeClient(Consumer consumer) { + consumer.accept(new IClientItemExtensions() { + + @Override + public BlockEntityWithoutLevelRenderer getCustomRenderer() { + return myBEWLRInstance; + } + }); +} +``` + +:::caution +每个模组应该只有一个自定义 BEWLR 实例。 +::: + +就是这样,使用 BEWLR 不需要额外的设置。 diff --git a/i18n/zh-Hans/docusaurus-plugin-content-docs/current/items/index.md b/i18n/zh-Hans/docusaurus-plugin-content-docs/current/items/index.md new file mode 100644 index 000000000..5765e113e --- /dev/null +++ b/i18n/zh-Hans/docusaurus-plugin-content-docs/current/items/index.md @@ -0,0 +1,206 @@ +# 物品 + +除了方块外,物品是 Minecraft 的关键组成部分。方块构成了你周围的世界,而物品存在于物品栏中。 + +## 物品到底是什么? + +在我们进一步创建物品之前,了解物品究竟是什么,以及它与方块的区别是什么,是非常重要的。让我们通过一个例子来说明这一点: + +- 在游戏世界中,你遇到了一个泥土方块并想要挖掘它。这是一个 **方块**,因为它被放置在世界中。(实际上,它不是一个方块,而是一个方块状态。请参阅 [方块状态文章][blockstates] 以获取更详细的信息。) + - 并非所有方块在破坏时都会掉落自己(例如树叶),有关更多信息,请参阅 [战利品表][loottables] 文章。 +- 一旦你 [挖掘了方块][breaking],它就会被移除(即被替换为空气方块),并且泥土掉落。掉落的泥土是一个 **物品实体**。这意味着像其他实体(猪、僵尸、箭等)一样,它可以被水推动,或者被火和岩浆燃烧。 +- 一旦你捡起泥土物品实体,它就会成为你物品栏中的一个 **物品堆叠**。物品堆叠简单地说就是一个物品的实例,带有一些额外的信息,比如堆叠大小。 +- 物品堆叠由它们对应的 **物品**(我们正在创建的东西)支持,物品持有所有物品之间相同的信息(例如,每把铁剑的最大耐久度为 250),而物品堆叠持有在两个类似物品之间可能不同的信息(例如,一把铁剑剩余 100 次使用,而另一把铁剑剩余 200 次使用)。有关通过物品和物品堆叠执行的操作和通过物品堆叠执行的操作的更多信息,请继续阅读。 + - 物品与物品堆叠之间的关系大致与 [方块][block] 和 [方块状态][blockstates] 之间的关系相同,即方块状态始终由方块支持。这不是一个非常准确的比较(物品堆叠不是单例,例如),但它可以给出一个关于这里概念的好基本理解。 + +## 创建一个物品 + +现在我们了解了物品是什么,让我们创建一个吧! + +与基本方块一样,对于不需要特殊功能的基本物品(如棍子、糖等),可以直接使用 `Item` 类。为此,在注册期间,使用 `Item.Properties` 参数实例化 `Item`。可以使用 `Item.Properties#of` 创建此 `Item.Properties` 参数,并通过调用其方法来自定义它: + +- `stacksTo` - 设置此物品的最大堆叠大小。默认为 64。例如末影珍珠或其他只能堆叠到 16 的物品使用了这个值。 +- `durability` - 设置此物品的耐久度。默认为 0,表示“无耐久度”。例如,铁制工具在此处使用了 250。请注意,设置耐久度会自动将堆叠大小锁定为 1。 +- `craftRemainder` - 设置此物品的制作剩余物品。Vanilla 在制作后留下空桶时使用了这个值。 +- `fireResistant` - 使使用此物品的物品实体对火和岩浆免疫。许多下界物品都使用了这个。 +- `setNoRepair` - 禁用此物品的铁砧和合成网格修复。Vanilla 中未使用。 +- `rarity` - 设置此物品的稀有度。当前,这只是改变物品的颜色。`Rarity` 是一个由四个值 `COMMON`(白色,默认)、`UNCOMMON`(黄色)、`RARE`(青色)和 `EPIC`(浅紫色) 组成的枚举。请注意,模组可能会添加更多的稀有度类型。 +- `requiredFeatures` - 设置此物品所需的功能标志。这主要用于小版本中 Vanilla 的功能锁定系统。除非你要集成 Vanilla 中由功能标志锁定的系统,否则不建议使用这个。 +- `food` - 设置此物品的 [`FoodProperties`][food]。 + +有关示例,或查看 Minecraft 中使用的各种值,请查看 `Items` 类。 + +### 食物 + +`Item` 类提供了食物物品的默认功能,这意味着你不需要单独的类来处理。要使你的物品可食用,你只需要通过 `Item.Properties` 的 `food` 方法设置其上的 `FoodProperties`。 + +使用 `FoodProperties.Builder` 创建 `FoodProperties`。然后,你可以在其上设置各种属性: + +- `nutrition` - 可能是最明显的部分。设置恢复多少饥饿点。以半个饥饿点为单位计数,所以例如,Minecraft 的牛排恢复了 8 个饥饿点。 +- `saturationMod` - 用于计算 [进食][hunger] 时恢复的饱和度值的饱和度修 + +饰符。计算公式为 `min(2 * nutrition * saturationMod, playerNutrition)`,这意味着使用 `0.5` 将使有效的饱和度值与营养值相同。 +- `meat` - 是否应将此物品视为肉类。用于确定是否可以使用此食物治愈狗。 +- `alwaysEat` - 此物品是否始终可以食用,即使饥饿条已满。默认为 `false`,例如金苹果等提供了除填充饥饿条之外的奖励的物品为 `true`。 +- `fast` - 是否为此食物启用快速进食。默认为 `false`,例如 Vanilla 中的干海带为 `true`。 +- `effect` - 在吃这个物品时添加一个 [`MobEffectInstance`][mobeffectinstance]。第二个参数表示应用效果的概率;例如,腐肉在被吃时有 80% 的几率(= 0.8)应用饥饿效果。这个方法有两个变体;你应该使用一个带有提供者的(另一个直接使用了一个 mob 效果实例,并因为类加载问题而被 NeoForge 废弃)。 +- `build` - 一旦你设置了你想设置的所有内容,调用 `build` 获取一个用于进一步使用的 `FoodProperties` 对象。 + +有关示例,或查看 Minecraft 中使用的各种值,请查看 `Foods` 类。 + +要获取物品的 `FoodProperties`,请调用 `Item#getFoodProperties(ItemStack, LivingEntity)`。这可能返回 null,因为并非每个物品都是可食用的。要确定物品是否可食用,请调用 `Item#isEdible()` 或对 `getFoodProperties` 调用的结果进行空检查。 + +### 更多功能 + +直接使用 `Item` 仅允许非常基本的物品。如果你想添加功能,例如右键交互,需要一个扩展 `Item` 的自定义类。`Item` 类有许多可以重写以执行不同操作的方法;有关更多信息,请参阅类 `Item` 和 `IItemExtension`。 + +物品的两种最常见用途是左键单击和右键单击。对于左键单击,请参阅 [破坏方块][breaking] 和 攻击实体(工作中)。对于右键单击,请参阅 [交互管道][interactionpipeline]。 + +### `DeferredRegister.Items` + +所有注册表都使用 `DeferredRegister` 来注册它们的内容,物品也不例外。然而,由于添加新物品是大量模组中一个至关重要的功能,NeoForge 提供了 `DeferredRegister.Items` 帮助类,它扩展了 `DeferredRegister` 并提供了一些特定于物品的辅助程序: + +```java +public static final DeferredRegister.Items ITEMS = DeferredRegister.createItems(ExampleMod.MOD_ID); + +public static final Supplier EXAMPLE_ITEM = ITEMS.registerItem( + "example_item", + Item::new, // 将属性传递给工厂。 + new Item.Properties() // 要使用的属性。 +); +``` + +在内部,这只是通过将属性参数应用于提供的物品工厂(通常是构造函数)来调用 `ITEMS.register("example_item", () -> new Item(new Item.Properties()))`。 + +如果你想使用 `Item::new`,可以完全省略工厂,并使用 `simple` 方法变体: + +```java +public static final Supplier EXAMPLE_ITEM = ITEMS.registerSimpleItem( + "example_item", + new Item.Properties() // 要使用的属性。 +); +``` + +这和上一个示例的效果完全相同,但稍微更短一些。当然,如果你想使用 `Item` 的子类而不是 `Item` 本身,则必须使用前一种方法。 + +这两种方法也有省略 `new Item.Properties()` 参数的重载: + +```java +public static final Supplier EXAMPLE_ITEM = ITEMS.registerItem("example_item", Item::new); +// Variant that also omits the Item::new parameter +public static final Supplier EXAMPLE_ITEM = ITEMS.registerSimpleItem("example_item"); +``` + +最后,还有块项目的快捷方式: + +```java +public static final Supplier EXAMPLE_BLOCK_ITEM = ITEMS.registerSimpleBlockItem("example_block", ExampleBlocksClass.EXAMPLE_BLOCK, new Item.Properties()); +// Variant that omits the properties parameter: +public static final Supplier EXAMPLE_BLOCK_ITEM = ITEMS.registerSimpleBlockItem("example_block", ExampleBlocksClass.EXAMPLE_BLOCK); +// Variant that omits the name parameter, instead using the block's registry name: +public static final Supplier EXAMPLE_BLOCK_ITEM = ITEMS.registerSimpleBlockItem(ExampleBlocksClass.EXAMPLE_BLOCK, new Item.Properties()); +// Variant that omits both the name and the properties: +public static final Supplier EXAMPLE_BLOCK_ITEM = ITEMS.registerSimpleBlockItem(ExampleBlocksClass.EXAMPLE_BLOCK); +``` + +:::note +如果您将注册的方块保存在单独的类中,则应该在项目类之前对方块类进行类加载。 +::: + +### 资源 + +如果你注册了你的物品并获得了你的物品(通过 `/give` 命令或通过 [创造模式标签][creativetabs]),你会发现它缺少正确的模型和纹理。这是因为纹理和模型是由 Minecraft 的资源系统处理的。 + +要为物品应用一个简单的纹理,你必须添加一个物品模型 JSON 文件和一个纹理 PNG 文件。有关更多信息,请参阅 [资源][resources] 部分。 + +## `ItemStack`(物品堆叠) + +与方块和方块状态一样,大多数情况下你期望使用一个 `Item` 实际上都是使用 `ItemStack`。`ItemStack` 表示容器中一个或多个物品的堆叠,例如一个物品栏。与方块和方块状态一样,方法应该被 `Item` 重写,并在 `ItemStack` 上调用,`Item` 中的许多方法都会传入一个 `ItemStack` 实例。 + +`ItemStack` 由三个主要部分组成: + +- 它所表示的 `Item`,可通过 `itemstack.getItem()` 获取。 +- 堆叠大小,通常在 1 和 64 之间,通过 `itemstack.getCount()` 获取,通过 `itemstack.setCount(int)` 或 `itemstack.shrink(int)` 可更改。 +- 额外的 [NBT][nbt] 数据,其中存储了堆叠特定的数据。可通过 `itemstack.getTag()` 获取,或者通过 `itemstack.getOrCreateTag()` 获取,它考虑了尚不存在标签的情况。还存在许多其他与 NBT 相关的方法,最重要的是 `hasTag()` 和 `setTag()`。 + - 值得注意的是,带有空 NBT 的 `ItemStack` 与根本没有 NBT 的 `ItemStack` 不同。这意味着它们不会堆叠,尽管它们在功能上是等效的。 + +要创建一个新的 `ItemStack`,请调用 `new ItemStack(Item)`,传入支持的物品。默认情况下,这使用数量为 1 和没有 NBT 数据;如果需要,有接受数量和 NBT 数据的构造函数重载。 + +`ItemStack` 是可变对象(见下文),但有时需要将它们视为不可变的。如果你需要修改一个被视为不可变的 `ItemStack`,可以使用 `itemstack.copy()` 克隆堆栈。 + +如果要表示堆叠没有物品,可以使用 `ItemStack.EMPTY`。要检查一个 `ItemStack` 是否为空,请调用 `itemstack.isEmpty()`。 + +### `ItemStack` 的可变性 + +`ItemStack` 是可变对象。这意味着如果你调用例如 `setCount`、`setTag` 或 `getOrCreateTag`,`ItemStack` 本身将被修改。Vanilla 广泛使用了 `ItemStack` 的可变性,许多方法依赖于它。例如,`itemstack.split(int)` 从调用者堆栈中分离给定数量的堆栈,同时修改调用者并在过程中返回一个新的 `ItemStack`。 + +然而,当处理多个 `ItemStack` 时,有时可能会出现问题。最常见的情况是处理库存槽时,因为你必须考虑到当前由光标选择的 `ItemStack`,以及你正在尝试插入/提取的 `ItemStack`。 + +:::tip +如果不确定,最好安全起见并对堆栈进行 `#copy()`。 +::: + +## 创造模式标签 + +默认情况下,你的物品只能通过 `/give` 命令获得,并不会出现在创造模式的物品栏中。让我们来改变这一点吧! + +将你的物品添加到创造模式菜单中的方式取决于你想要添加到哪个标签。 + +### 现有的创造模式标签 + +:::note +这种方法用于将你的物品添加到 Minecraft 的标签,或其他模组的标签中。要将物品添加到你自己的标签中,请参见下文。 +::: + +可以通过 `BuildCreativeModeTabContentsEvent` 将物品添加到现有的 `CreativeModeTab` 中,该事件在 [模组事件总线][modbus] 上触发,仅在 [逻辑客户端][sides] 上触发。通过调用 `event#accept` 来添加物品。 + +```java +//MyItemsClass.MY_ITEM 是 Supplier,MyBlocksClass.MY_BLOCK 是 Supplier +@SubscribeEvent +public static void buildContents(BuildCreativeModeTabContentsEvent event) { + // 这是我们要添加到的标签吗? + if (event.getTabKey() == CreativeModeTabs.INGREDIENTS) { + event.accept(MyItemsClass.MY_ITEM); + // 接受一个 ItemLike。这假设 MY_BLOCK 有一个相应的物品。 + event.accept(MyBlocksClass.MY_BLOCK); + } +} +``` + +事件还提供了一些额外信息,例如 `getFlags()` 来获取已启用的功能标志列表,或 `hasPermissions()` 来检查玩家是否有权限查看操作员物品标签。 + +### 自定义创造模式标签 + +`CreativeModeTab` 是一个注册表,意味着自定义的 `CreativeModeTab` 必须被 [注册][registering]。创建创造模式标签使用一个构建器系统 + +,该构建器通过 `CreativeModeTab#builder` 获得。构建器提供了设置标题、图标、默认物品以及许多其他属性的选项。此外,NeoForge 还提供了额外的方法来自定义标签的图像、标签和槽颜色,标签应该排序在哪里等等。 + +```java +//CREATIVE_MODE_TABS 是 DeferredRegister +public static final Supplier EXAMPLE_TAB = CREATIVE_MODE_TABS.register("example", () -> CreativeModeTab.builder() + // 设置标签的标题。不要忘记添加一个翻译! + .title(Component.translatable("itemGroup." + MOD_ID + ".example")) + // 设置标签的图标。 + .icon(() -> new ItemStack(MyItemsClass.EXAMPLE_ITEM.get())) + // 将你的物品添加到标签中。 + .displayItems((params, output) -> { + output.accept(MyItemsClass.MY_ITEM); + // 接受一个 ItemLike。这假设 MY_BLOCK 有一个相应的物品。 + output.accept(MyBlocksClass.MY_BLOCK); + }) + .build() +); +``` + +[block]: ../blocks/index.md +[blockstates]: ../blocks/states.md +[breaking]: ../blocks/index.md#breaking-a-block +[creativetabs]: #creative-tabs +[food]: #food +[hunger]: https://minecraft.wiki/w/Hunger#Mechanics +[interactionpipeline]: interactionpipeline.md +[loottables]: ../resources/server/loottables.md +[mobeffectinstance]: mobeffects.md#mobeffectinstances +[modbus]: ../concepts/events.md#event-buses +[nbt]: ../datastorage/nbt.md +[registering]: ../concepts/registries.md#methods-for-registering diff --git a/i18n/zh-Hans/docusaurus-plugin-content-docs/current/items/interactionpipeline.md b/i18n/zh-Hans/docusaurus-plugin-content-docs/current/items/interactionpipeline.md new file mode 100644 index 000000000..7643241f5 --- /dev/null +++ b/i18n/zh-Hans/docusaurus-plugin-content-docs/current/items/interactionpipeline.md @@ -0,0 +1,71 @@ +The Interaction Pipeline +======================== + +本页面旨在使玩家右键单击的相当复杂和令人困惑的过程更容易理解,并澄清应在何处以及为什么使用哪种结果。 + +右键单击时发生了什么? +-------------------------------- + +当你在世界中的任何地方右键单击时,会发生一系列的事情,这取决于你当前正在查看的内容以及你手中的 `ItemStack`。会调用返回两种结果类型之一的一些方法。如果显式地返回了成功或失败,大多数情况下这些方法将取消管线。为了易读起见,这里将“显式成功或显式失败”称为“明确结果”。 + +- 用右鼠标按钮和主手触发 `InputEvent.InteractionKeyMappingTriggered`。如果事件被取消,管线结束。 +- 检查了几种情况,例如你不处于旁观模式,或者你主手中的 `ItemStack` 的所有必需特性标志都已启用。如果这些检查中至少有一个失败,管线结束。 +- 根据你的视线朝向的内容不同,会发生不同的事情: + - 如果你的视线朝向一个在你可触及范围内且不在世界边界之外的实体: + - 触发 `PlayerInteractEvent.EntityInteractSpecific`。如果事件被取消,管线结束。 + - 将在你所看的实体上调用 `Entity#interactAt`。如果它返回了明确结果,管线结束。 + - 如果你想为你自己的实体添加行为,请重写此方法。如果你想为一个原版实体添加行为,请使用事件。 + - 如果实体打开了一个界面(例如村民交易 GUI 或箱子矿车 GUI),管线结束。 + - 触发 `PlayerInteractEvent.EntityInteract`。如果事件被取消,管线结束。 + - 将在你所看的实体上调用 `Entity#interact`。如果它返回了明确结果,管线结束。 + - 如果你想为你自己的实体添加行为,请重写此方法。如果你想为一个原版实体添加行为,请使用事件。 + - 对于 `Mob`,`Entity#interact` 的重写处理了像使用生成蛋时拴绳和产生孩子这样的事情,然后将特定于 mob 的处理推迟到 `Mob#mobInteract`。`Entity#interact` 的结果规则也适用于这里。 + - 如果你所看的实体是一个 `LivingEntity`,将在你主手中的 `ItemStack` 上调用 `Item#interactLivingEntity`。如果它返回了明确结果,管线结束。 + - 如果你的视线朝向一个在你可触及范围内且不在世界边界之外的方块: + - 触发 `PlayerInteractEvent.RightClickBlock`。如果事件被取消,管线结束。你也可以在这个事件中具体地否定只有方块或物品的使用。 + - 调用 `IItemExtension#onItemUseFirst`。如果它返回了明确结果,管线结束。 + - 如果玩家没有潜行并且事件没有否定方块的使用,将调用 `Block#use`。如果它返回了明确结果,管线结束。 + - 如果事件没有否定物品的使用,将调用 `Item#useOn`。如果它返回了明确结果,管线结束。 +- 调用 `Item#use`。如果它返回了明确结果,管线结束。 +- 上述过程再次运行,这次是用副手而不是主手。 + +结果类型 +------------ + +有两种不同的结果类型:`InteractionResult` 和 `InteractionResultHolder`。`InteractionResult` 大多数情况下使用,只有 `Item#use` 使用 `InteractionResultHolder`。 + +`InteractionResult` 是一个枚举,包含五个值:`SUCCESS`、`CONSUME`、`CONSUME_PARTIAL`、`PASS` 和 `FAIL`。此外,方法 `InteractionResult#sidedSuccess` 可用,它在服务器端返回 `SUCCESS`,在客户端返回 `CONSUME`。 + +`InteractionResultHolder` 是 `InteractionResult` 的包装器,它为 `T` 添加了额外的上下文。`T` 可以是任何东西,但在 99.99% 的情况下,它是一个 `ItemStack`。`InteractionResultHolder` 为枚举值提供了包装方法(`#success`、`#consume`、`#pass` 和 `#fail`),以及 `#sidedSuccess` 方法,它在服务器上调用 `#success`,在客户端上调用 `#consume`。 + +一般来说,不同的值意味着以下内容: + +- `InteractionResult#sidedSuccess`(或需要时 `InteractionResultHolder#sidedSuccess`)应该在操作应该被认为成功,并且你想要挥动手臂时使用。管线将结束。 +- `InteractionResult.SUCCESS`(或需要时 `InteractionResultHolder#success`)应该在操作应该被认为成功,并且你想要挥动手臂时使用,但只在一侧使用。只有在出于某种原因希望在另一逻辑侧返回不同值时才使用此选项。管线将结束。 +- `InteractionResult.CONSUME`(或需要时 `InteractionResultHolder#consume`)应该在操作应该被认为成功,但你不想要挥动手臂时使用。管线将结束。 +- `InteractionResult.CONSUME_PARTIAL` 在大多数情况下与 `InteractionResult.CONSUME` 相同,唯一的区别在于它在 [`Item#useOn`][itemuseon] 中的使用方式。 +- `InteractionResult.FAIL`(或需要时 `InteractionResult + +Holder#fail`)应该在物品功能被认为失败并且不应再进行进一步交互时使用。管线将结束。这可以用在任何地方,但在 `Item#useOn` 和 `Item#use` 之外使用时需要小心。在许多情况下,使用 `InteractionResult.PASS` 更有意义。 +- `InteractionResult.PASS`(或需要时 `InteractionResultHolder#pass`)应该在操作既不应被认为成功也不应被认为失败时使用。管线将继续。这是默认行为(除非另有规定)。 + +一些方法具有特殊的行为或要求,这些将在下面的章节中解释。 + +`IItemExtension#onItemUseFirst` +--------------------------- + +`InteractionResult#sidedSuccess` 和 `InteractionResult.CONSUME` 在这里没有效果。在这里只应该使用 `InteractionResult.SUCCESS`、`InteractionResult.FAIL` 或 `InteractionResult.PASS`。 + +`Item#useOn` +------------ + +如果你希望操作被视为成功,但你不希望手臂摆动或奖励一个 `ITEM_USED` 统计点,请使用 `InteractionResult.CONSUME_PARTIAL`。 + +`Item#use` +---------- + +这是唯一一个返回类型为 `InteractionResultHolder` 的实例。`InteractionResultHolder` 中的结果 `ItemStack` 将替换发起使用的 `ItemStack`,如果它已更改。 + +当物品可食用并且玩家可以吃掉物品时(因为他们饥饿了,或者因为物品总是可食用时),`Item#use` 的默认实现返回 `InteractionResultHolder#consume`;当物品可食用但玩家无法吃掉物品时,返回 `InteractionResultHolder#fail`;如果物品不可食用,则返回 `InteractionResultHolder#pass`。 + +在考虑主手时返回 `InteractionResultHolder#fail` 将阻止运行副手行为。如果你希望运行副手行为(通常是这样),请改为返回 `InteractionResultHolder#pass`。 diff --git a/i18n/zh-Hans/docusaurus-plugin-content-docs/current/items/mobeffects.md b/i18n/zh-Hans/docusaurus-plugin-content-docs/current/items/mobeffects.md new file mode 100644 index 000000000..17ef1d09c --- /dev/null +++ b/i18n/zh-Hans/docusaurus-plugin-content-docs/current/items/mobeffects.md @@ -0,0 +1,200 @@ +# Mob Effects & Potions + +状态效果,有时称为药水效果,并在代码中称为 `MobEffect`,是每个游戏刻对实体产生影响的效果。本文解释了如何使用它们,效果与药水之间的区别,以及如何添加自定义效果。 + +## 术语 + +- `MobEffect` 每个游戏刻对实体产生影响。与[方块][block]或[物品][item]一样,`MobEffect` 是注册对象,这意味着它们必须[注册][registration]并且是单例的。 + - **即时效果** 是一种特殊类型的效果,设计用于应用一次游戏刻。原版有两种即时效果,即即时治疗和即时伤害。 +- `MobEffectInstance` 是 `MobEffect` 的实例,具有持续时间、增幅和一些其他设置(见下文)。`MobEffectInstance` 对于 `MobEffect` 就像 [`ItemStack`][itemstack] 对于 `Item` 一样。 +- `Potion` 是一组 `MobEffectInstance`。原版主要用于四种药水物品(后文),但可以随意应用于任何物品。物品如何使用所设置的药水取决于物品本身。 +- **药水物品** 是指应设置药水的物品。这是一个非正式的术语,原版中有四种药水物品:药水、溅射药水、挥发药水和毒箭;但是模组可能会添加更多。 + +## `MobEffect`s + +要创建自己的 `MobEffect`,请扩展 `MobEffect` 类: + +```java +public class MyMobEffect extends MobEffect { + public MyMobEffect(MobEffectCategory category, int color) { + super(category, color); + } + + @Override + public void applyEffectTick(LivingEntity entity, int amplifier) { + // 在这里应用你的效果逻辑。 + } + + // 决定是否在此游戏刻应用效果。例如,恢复效果每 x 个游戏刻应用一次,具体取决于游戏刻和增幅。 + @Override + public boolean shouldApplyEffectTickThisTick(int tickCount, int amplifier) { + return tickCount % 2 == 0; // 用你想要的检查替换此处 + } + + // 当效果首次添加到实体时调用的实用方法。 + @Override + public void onEffectStarted(LivingEntity entity, int amplifier) { + } +} +``` + +像所有注册对象一样,`MobEffect` 必须像下面这样注册: + +```java +// MOB_EFFECTS 是一个 DeferredRegister +public static final Supplier MY_MOB_EFFECT = MOB_EFFECTS.register("my_mob_effect", () -> new MyMobEffect( + // 可以是 BENEFICIAL、NEUTRAL 或 HARMFUL。用于确定此效果的药水工具提示颜色。 + MobEffectCategory.BENEFICIAL, + // 效果粒子的颜色。 + 0xffffff +)); +``` + +如果你的效果仅用作标记,你也可以直接使用 `MobEffect` 类,就像你可以使用 `Block` 或 `Item` 类一样。 + +`MobEffect` 类还为受影响实体添加属性修改器提供了默认功能。例如,速度效果会为移动速度添加属性修改器。效果属性修改器添加如下: + +```java +public static final String MY_MOB_EFFECT_UUID = "01234567-89ab-cdef-0123-456789abcdef"; +public static final Supplier MY_MOB_EFFECT = MOB_EFFECTS.register("my_mob_effect", () -> new MyMobEffect(...) + .addAttributeModifier(Attribute.ATTACK_DAMAGE, MY_MOB_EFFECT_UUID, 2.0, AttributeModifier.Operation.ADD) +); +``` + +:::note +使用的 UUID 必须是有效且唯一的 UUIDv4,因为出于某种原因,Mojang 决定在此处使用 UUID 而不是一些基于文本的标识符。最好 + +通过在线生成器获得 UUID,例如 [uuidgenerator.net][uuidgen]。 +::: + +### `InstantenousMobEffect` + +如果要创建即时效果,可以使用助手类 `InstantenousMobEffect` 而不是常规的 `MobEffect` 类,如下所示: + +```java +public class MyMobEffect extends InstantenousMobEffect { + public MyMobEffect(MobEffectCategory category, int color) { + super(category, color); + } + + @Override + public void applyEffectTick(LivingEntity entity, int amplifier) { + // 在这里应用你的效果逻辑。 + } +} +``` + +然后,像平常一样注册你的效果。 + +### 事件 + +许多效果在其他地方应用它们的逻辑。例如,飘浮效果在生物移动处理程序中应用。对于模组 `MobEffect`,通常最好在[事件处理程序][events]中应用它们。NeoForge 还提供了一些与效果相关的事件: + +- `MobEffectEvent.Applicable` 在游戏检查是否可以将 `MobEffectInstance` 应用于实体时触发。此事件可用于拒绝或强制向目标添加效果实例。 +- `MobEffectEvent.Added` 当 `MobEffectInstance` 添加到目标时触发。此事件包含有关可能存在于目标上的先前 `MobEffectInstance` 的信息。 +- `MobEffectEvent.Expired` 当 `MobEffectInstance` 到期时触发,即计时器归零时。 +- `MobEffectEvent.Remove` 当通过除到期之外的方式从实体中移除效果时触发,例如通过喝牛奶或通过命令。 + +## `MobEffectInstance`s + +简单来说,`MobEffectInstance` 是应用于实体的效果。通过调用构造函数创建 `MobEffectInstance`: + +```java +MobEffectInstance instance = new MobEffectInstance( + // 要使用的 mob 效果。 + MobEffects.REGENERATION, + // 使用的持续时间,以游戏刻为单位。如果未指定,默认为 0。 + 500, + // 要使用的增幅。这是效果的 “强度”,例如,Strength I、Strength II 等;从 0 开始。如果未指定,默认为 0。 + 0, + // 是否为 “环境” 效果,表示它由环境源应用,Minecraft 目前有信标和导管。如果未指定,默认为 false。 + false, + // 效果是否在库存中可见。如果未指定,默认为 true。 + true, + // 是否在右上角可见效果图标。如果未指定,默认为 true。 + true +); +``` + +有几种构造函数重载,分别省略最后 1-5 个参数。 + +:::info +`MobEffectInstance` 是可变的。如果需要副本,请调用 `new MobEffectInstance(oldInstance)`。 +::: + +### 使用 `MobEffectInstance` + +可以将 `MobEffectInstance` 添加到实体,如下所示: + +```java +MobEffectInstance instance = new MobEffectInstance(...); +entity.addEffect(instance); +``` + +类似地,也可以从实体中移除 `MobEffectInstance`。由于 `MobEffectInstance` 覆盖了实体上的相同 `MobEffect` 的预先存在的 `MobEffectInstance`,因此每个 `MobEffect` 和实体只能有一个 `MobEffectInstance`。因此,在移除时只需指定 `MobEffect` 即可: + +```java +entity.removeEffect(MobEffects.REGENERATION); +``` + +:::info +`MobEffect` 只能应用于 `LivingEntity` 或其子类,例如玩家和生物。例如物品或投掷雪球无法受到 `MobEffect` 的影响。 +::: + +## `Potion`s + +`Potion`s 是通过调用 `Potion` 的构造函数并传递你想要的 `MobEffectInstance`s 来创建的。例如: + +```java +// POTIONS 是一个 DeferredRegister +public static final Supplier MY_POTION = POTIONS.register("my_potion", () -> new Potion(new MobEffectInstance(MY_MOB_EFFECT.get(), 3600))); +``` + +请注意,`new Potion` 的参数是可变参数。这意味着你可以向药水添加任意数量的效果。这也意味着可以创建空药水,即不含任何效果的药水。只需调用 `new Potion()` 即可!(顺便说一句,这就是原版如何添加 `awkward` 药水的方式。) + +药水的名称可以作为第一个构造函数参数传递。它用于翻译;例如,原版中的长效和强效药水变种使用此参数,使其与基本变种具有相同的名称。名称不是必需的;如果省略了名称,将从注册表中查询名称。 + +`PotionUtils` 类提供了与药水相关的各种辅助方法,例如 `getPotion` 和 `setPotion` 用于物品堆栈(这可以是任何类型的物品,不仅限于药水物品),或者 `getColor` 用于获取药水的显示颜色。 + +### 酿造 + +现在,你的药水已经添加,药水物品可以使用你的药水了。但是,在生存模式下没有办法获得你的药水,所以让我们改变一下! + +传统上,药水是在酿造台上制作的。不幸的是,Mojang 没有为酿造配方提供 [数据包][datapack] 支持,因此我们必须有点老派,通过代码添加我们的配方。操作如下: + +```java +// 酿造成分。这是在酿造台顶部的物品。 +Ingredient brewingIngredient = Ingredient.of(Items.FEATHER); +BrewingRecipeRegistry.addRecipe( + // 输入药水成分,通常是一种混合药水。这是在酿造台底部的物品。 + // 不一定要是药水,但通常是。 + PotionUtils.setPotion(new ItemStack(Items.POTION), Potions.AWKWARD), + // 我们的酿造成分。 + brewingIngredient, + // 结果物品堆栈。不一定要是药水,但通常是。 + PotionUtils.setPotion(new ItemStack(Items.POTION), MY_POTION) +); +// 对于溅射药水和挥发药水,我们还需要单独处理。 +// 原版的毒箭配方由 Minecraft 的毒箭特殊配方处理。 +BrewingRecipeRegistry.addRecipe( + PotionUtils.setPotion(new ItemStack(Items.SPLASH_POTION), Potions.AWKWARD), + brewingIngredient, + PotionUtils.setPotion(new ItemStack(Items.SPLASH_POTION), MY_POTION) +); +BrewingRecipeRegistry.addRecipe( + PotionUtils.setPotion(new ItemStack(Items.LINGERING_POTION), Potions.AWKWARD), + brewingIngredient, + PotionUtils.setPotion(new ItemStack(Items.LINGERING_POTION), MY_POTION) +); +``` + +这应该在设置期间的某个时间调用,例如在 [`FMLCommonSetupEvent`][commonsetup] 中。确保将此代码包装到 `event.enqueueWork()` 调用中,因为酿造配方注册表不是线程安全的。 + +[block]: ../blocks/index.md +[commonsetup]: ../concepts/events.md#event-buses +[datapack]: ../resources/server/index.md +[events]: ../concepts/events.md +[item]: index.md +[itemstack]: index.md#itemstacks +[registration]: ../concepts/registries.md +[uuidgen]: https://www.uuidgenerator.net/version4 diff --git a/i18n/zh-Hans/docusaurus-plugin-content-docs/current/items/tools.md b/i18n/zh-Hans/docusaurus-plugin-content-docs/current/items/tools.md new file mode 100644 index 000000000..dc840ab85 --- /dev/null +++ b/i18n/zh-Hans/docusaurus-plugin-content-docs/current/items/tools.md @@ -0,0 +1,331 @@ +## 工具与护甲 + +工具是其主要用途是破坏[方块][block]的[物品][item]。许多模组添加了新的工具套装(例如铜工具)或新的工具类型(例如锤子)。 + +## 自定义工具套装 + +工具套装通常包含五种物品:镐、斧、铲、锄和剑。(剑在传统意义上不是工具,但为了保持一致性也包括在内。)所有这些物品都有对应的类:`PickaxeItem`、`AxeItem`、`ShovelItem`、`HoeItem` 和 `SwordItem`。工具的类层次结构如下所示: + +```text +Item +- TieredItem + - DiggerItem + - AxeItem + - HoeItem + - PickaxeItem + - ShovelItem + - SwordItem +``` + +`TieredItem` 是一个包含了特定 `Tier`(详见下文)的辅助类。`DiggerItem` 包含了设计用于破坏方块的物品的辅助功能。请注意,其他通常被认为是工具的物品,例如剪刀,不包含在此层次结构中。它们直接扩展了 `Item`,并自行处理破坏逻辑。 + +要创建标准工具套装,首先必须定义一个 `Tier`。有关参考值,请参阅 Minecraft 的 `Tiers` 枚举。以下示例使用铜工具,你可以在此处使用你自己的材料并根据需要调整值。 + +```java +// 我们将铜放在石头和铁之间。 +public static final Tier COPPER_TIER = new SimpleTier( + // 确定此工具的等级。由于这是一个整数,没有很好的方法将我们的工具放在石头和铁之间。 + // 石头是 1,铁是 2。 + 1, + // 确定等级的耐久性。 + // 石头是 131,铁是 250。 + 200, + // 确定等级的挖掘速度。剑不使用此值。 + // 石头使用 4,铁使用 6。 + 5f, + // 确定攻击伤害加成。不同的工具使用不同的方式。例如,剑会造成 (getAttackDamageBonus() + 4) 的伤害。 + // 石头使用 1,铁使用 2,对应于剑的 5 和 6 攻击伤害;现在我们的剑造成 5.5 伤害。 + 1.5f, + // 确定等级的附魔能力。这代表了此工具上附魔的好坏程度。 + // 金使用 22,我们将铜稍微低于这个值。 + 20, + // 确定此工具可以破坏的方块的标签。详见下文。 + MyBlockTags.NEEDS_COPPER_TOOL, + // 确定等级的修复原料。使用 Supplier 进行延迟初始化。 + () -> Ingredient.of(Tags.Items.INGOTS_COPPER) +); +``` + +现在我们有了我们的 `Tier`,我们可以用它来注册工具。所有工具的构造函数都有相同的四个参数。 + +```java +// ITEMS 是一个 DeferredRegister +public static final Supplier COPPER_SWORD = ITEMS.register("copper_sword", () -> new SwordItem( + // 要使用的等级。 + COPPER_TIER, + // 类型特定的攻击伤害加成。剑为 3,铲为 1.5,镐为 1,斧和锄有所不同。 + 3, + // 类型特定的攻击速度修正。玩家的默认攻击速度为 4,所以我们使用 -2.4f 来达到期望的值 1.6f。 + // 剑为 -2.4f,铲为 -3f,镐为 -2.8f,斧和锄有所不同。 + -2.4f, + // 物品属性。我们不需要在这里设置耐久性,因为 TieredItem 会为我们处理。 + new Item.Properties() +)); +public static final Supplier COPPER_AXE = ITEMS.register("copper_axe", () -> new AxeItem(...)); +public static final Supplier COPPER_PICKAXE = ITEMS.register("copper_pickaxe", () -> new PickaxeItem(...)); +public static final + + Supplier COPPER_SHOVEL = ITEMS.register("copper_shovel", () -> new ShovelItem(...)); +public static final Supplier COPPER_HOE = ITEMS.register("copper_hoe", () -> new HoeItem(...)); +``` + +### 标签 + +创建 `Tier` 时,它被分配了一个包含需要此工具(或更好的工具)才能破坏的方块[标签][tags]。例如,`minecraft:needs_iron_tool` 标签包含了钻石矿石(以及其他方块),而 `minecraft:needs_diamond_tool` 标签包含了方块如黑曜石和远古残骸。 + +如果你满意的话,你可以重用这些标签中的一个来制作你的工具。例如,如果我们想要我们的铜工具只是更耐用的石头工具,我们可以传入 `BlockTags.NEEDS_STONE_TOOL`。 + +或者,我们可以创建自己的标签,操作如下: + +```java +public static final TagKey NEEDS_COPPER_TOOL = TagKey.create(BuiltInRegistries.BLOCK.key(), new ResourceLocation(MOD_ID, "needs_copper_tool")); +``` + +然后,我们填充我们的标签。例如,让铜能够开采金矿石、金块和红石矿石,但不能开采钻石或绿宝石。 (红石块已经可以被石头工具开采了。)标签文件位于 `src/main/resources/data/mod_id/tags/blocks/needs_copper_tool.json`(其中 `mod_id` 是你的模组 ID): + +```json +{ + "values": [ + "minecraft:gold_block", + "minecraft:raw_gold_block", + "minecraft:gold_ore", + "minecraft:deepslate_gold_ore", + "minecraft:redstone_ore", + "minecraft:deepslate_redstone_ore" + ] +} +``` + +最后,我们可以像上面看到的那样将我们的标签传递给我们的等级创建。 + +### `TierSortingRegistry` + +为了使游戏真正选择你的等级位于另外两个等级之间,你必须将其注册到 `TierSortingRegistry`。这必须在物品注册之前发生,将 `static` 初始化器放在与你的等级定义相同的类中是一个不错的选择。如果你不将你的等级添加到注册表中,它将退回到原版所做的操作。 + +```java +public static final Tier COPPER_TIER = new SimpleTier(...); + +static { + TierSortingRegistry.registerTier( + COPPER_TIER, + // 用于内部解析的名称。如果适用,可以使用 Minecraft 命名空间。 + new ResourceLocation("minecraft", "copper"), + // 被认为低于正在添加的类型的一系列等级。例如,石头低于铜。 + // 我们不需要在这里添加木头和金,因为这些已经低于石头了。 + List.of(Tiers.STONE), + // 被认为高于正在添加的类型的一系列等级。例如,铁高于铜。 + // 我们不需要在这里添加钻石和下界合金,因为这些已经高于铁了。 + List.of(Tiers.IRON) + ); +} +``` + +可以将其他等级的 ID 传递到这些列表中,作为 `Tier` 的替代或补充。例如,假设我们想要使我们的材料被认为比铁和 [Mekanism 工具][mektools] 的钨更弱,我们可以这样做: + +```java +public static final Tier COPPER_TIER = new SimpleTier(...); + +static { + TierSortingRegistry.registerTier( + COPPER_TIER, + new ResourceLocation("minecraft", "copper"), + List.of(Tiers.STONE), + // 我们可以在这里混合和匹配 Tiers 和 ResourceLocations。 + List.of(Tiers.IRON, new ResourceLocation("mekanism", "osmium")) + ); +} +``` + +## 工具与护甲 + +工具是主要用于破坏[方块][block]的[物品][item]。许多模组添加了新的工具套装(例如铜工具)或新的工具类型(例如锤子)。 + +### 自定义工具套装 + +工具套装通常由五种物品组成:镐、斧、铲、锄和剑。(剑在传统意义上不是工具,但为了保持一致性,也包括在内。)所有这些物品都有各自对应的类:`PickaxeItem`、`AxeItem`、`ShovelItem`、`HoeItem` 和 `SwordItem`。工具的类层次结构如下所示: + +``` +Item +- TieredItem + - DiggerItem + - AxeItem + - HoeItem + - PickaxeItem + - ShovelItem + - SwordItem +``` + +`TieredItem` 是一个包含某个 `Tier` 的辅助类(详见下文)。`DiggerItem` 包含了用于破坏方块的物品的辅助方法。请注意,其他通常被视为工具的物品(如剪刀)不包括在这个层次结构中。相反,它们直接扩展 `Item` 并自行处理破坏逻辑。 + +要创建标准的工具套装,首先必须定义一个 `Tier`。参考 Minecraft 的 `Tiers` 枚举获取参考值。以下示例使用铜工具,你可以在此处使用你自己的材料并根据需要调整值。 + +```java +// 我们将铜放在石头和铁之间。 +public static final Tier COPPER_TIER = new SimpleTier( + // 确定此工具的等级。由于这是一个整数,因此没有好的方法将我们的工具放置在石头和铁之间。 + // NeoForge 引入了 TierSortingRegistry 来解决这个问题,有关更多信息,请参见下文。在此处尽力估计。 + // 石头为 1,铁为 2。 + 1, + // 确定等级的耐久度。 + // 石头为 131,铁为 250。 + 200, + // 确定等级的挖掘速度。斧头不使用此项。 + // 石头使用 4,铁使用 6。 + 5f, + // 确定攻击伤害奖励。不同的工具使用方式不同。例如,剑会造成 (getAttackDamageBonus() + 4) 的伤害。 + // 石头使用 1,铁使用 2,对应于剑的伤害分别为 5 和 6;我们的剑现在造成 5.5 的伤害。 + 1.5f, + // 确定等级的附魔性。这代表了这个工具上附魔的好坏程度。 + // 金使用 22,我们稍微低于这个值。 + 20, + // 决定这个工具可以破坏哪些方块的标签。更多信息请参见下文。 + MyBlockTags.NEEDS_COPPER_TOOL, + // 确定等级的修复材料。使用供应商进行延迟初始化。 + () -> Ingredient.of(Tags.Items.INGOTS_COPPER) +); +``` + +现在我们有了我们的 `Tier`,我们可以在注册工具时使用它。所有工具构造函数都具有相同的四个参数。 + +```java +// ITEMS 是一个 DeferredRegister +public static final Supplier COPPER_SWORD = ITEMS.register("copper_sword", () -> new SwordItem( + // 要使用的等级。 + COPPER_TIER, + // 类型特定的攻击伤害奖励。剑为 3,铲子为 1.5,镐子为 1,斧头和锄头的值各不相同。 + 3, + // 类型特定的攻击速度修饰符。玩家的默认攻击速度为 4,所以要达到期望的值 1.6f,我们使用 -2.4f。对于剑,值为 -2.4f,铲子为 -3f,镐子为 -2.8f,斧头和锄头的值各不相同。 + -2.4f, + // 物品属性。我们不需要在此设置耐久度,因为 TieredItem 会为我们处理。 + new Item.Properties() +)); +public static final Supplier COPPER_AXE = ITEMS.register("copper_axe", () -> new AxeItem(...)); +public static final Supplier COPPER_PICKAXE = ITEMS.register("copper_pickaxe", () -> new PickaxeItem(...)); +public static final Supplier COPPER + +_SHOVEL = ITEMS.register("copper_shovel", () -> new ShovelItem(...)); +``` + +### 工具动作 + +工具动作是工具能够执行和不能执行的操作的抽象。这包括左键和右键行为。NeoForge 在 `ToolActions` 类中提供了默认的 `ToolAction`: + +- 挖掘动作。这些适用于上文提到的所有四种 `DiggerItem` 类型,以及剑和剪刀挖掘。 +- 斧头右键动作用于去皮(原木)、刮(氧化铜)和去蜡(蜡质铜)。 +- 剪刀动作用于收获(蜜蜂巢)、雕刻(南瓜)和解除武装(绊线)。 +- 铲子平整(土径)、剑扫射、锄头耕作、盾牌阻挡和钓鱼竿抛出的动作。 + +要创建自己的 `ToolAction`,请使用 `ToolAction#get` - 它会在需要时创建一个新的 `ToolAction`。然后,在自定义工具类型中根据需要覆盖 `IItemExtension#canPerformAction`。 + +要查询一个 `ItemStack` 是否可以执行某个 `ToolAction`,请调用 `IItemStackExtension#canPerformAction`。请注意,这适用于任何 `Item`,而不仅仅是工具。 + +### 护甲 + +与工具类似,护甲也使用一个等级系统(尽管不同)。工具中称为 `Tier` 的东西在护甲中称为 `ArmorMaterial`。就像上面一样,这个例子展示了如何添加铜护甲;这可以根据需要进行调整。有关原始数值,请参见 `ArmorMaterials` 枚举。 + +```java +// 我们将铜放在锁链甲和铁之间。 +public static final ArmorMaterial COPPER_ARMOR_MATERIAL = new ArmorMaterial() { + // 护甲材料的名称。主要用于确定护甲纹理的位置。应包含一个前导的模组标识符以确保唯一性,否则当两个模组尝试添加相同的护甲材料时可能会出现问题。(如果省略模组标识符,则将使用 "minecraft" 命名空间。) + @Override + public String getName() { + return "modid:copper"; + } + + // StringRepresentable 的重写。通常应与 getName() 返回相同的值。 + @Override + public String getSerializedName() { + return getName(); + } + + // 确定此护甲材料的耐久度,具体取决于护甲部件是什么。 + // ArmorItem.Type 是四个值的枚举:HELMET、CHESTPLATE、LEGGINGS 和 BOOTS。 + // Vanilla 护甲材料通过使用一个基础值并将其与类型特定的常量相乘来确定这一点。 + // 这些常量是 13(BOOTS)、15(LEGGINGS)、16(CHESTPLATE)和 11(HELMET)。 + // 锁链甲和铁都使用 15 作为基础值,所以我们也使用它。 + @Override + public int getDurabilityForType(ArmorItem.Type type) { + return switch (type) { + case HELMET -> 11 * 15; + case CHESTPLATE -> 16 * 15; + case LEGGINGS -> 15 * 15; + case BOOTS -> 13 * 15; + }; + } + + // 确定此护甲材料的防御值,具体取决于护甲部件是什么。 + @Override + public int getDurabilityForType(ArmorItem.Type type) { + return switch (type) { + case HELMET -> 2; + case CHESTPLATE -> 4; + case LEGGINGS -> 6; + case BOOTS -> 2; + }; + } + + // 返回护甲的坚韧度值。坚韧度值是包含在伤害计算中的额外值,有关更多信息,请参见 Minecraft Wiki 上的护甲机制文章:https://minecraft.wiki/w/Armor#Armor_toughness + // 只有钻石和下界合金在这里的值大于 0,所以我们只返回 0。 + @Override + public float getToughness() { + return 0; + } + + // 返回护甲的抗击退值。穿戴这种护甲时,玩家对击退具有一定程度的免疫。如果玩家从所有护甲部件中获得的总击退抗性值大于或等于 1,则它们将根本不受到任何击退。 + // 只有下界合金在这里的值大于 0,所以我们只返回 0。 + @Override + public float getKnockbackResistance() { + return 0; + } + + // 确定等级的附魔性。这代表了这个护甲上的附魔有多好。 + // 金使用 25,我们将铜放在稍低的位置。 + @Override + public int getEnchantmentValue(ArmorItem.Type type) { + return 20; + } + + // 确定装备这件护甲时播放的声音。 + @Override + public SoundEvent getEquipSound() { + return SoundEvents.ARMOR_EQUIP_GENERIC; + } + + // 确定这件护甲的修复物品。 + @Override + public Ingredient getRepairIngredient() { + return Ingredient.of(Tags.Items.INGOTS_COPPER); + } + + // 可选地,您还可以在这里重写 #getArmorTexture。此方法返回一个 ResourceLocation,用于确定存储护甲位置的位置,以防您希望将其存储在非默认位置。 + +有关示例,请参见 Tier 中的默认实现。 +} +``` + +然后,在物品注册中使用该护甲材料。 + +```java +// ITEMS 是一个 DeferredRegister +public static final Supplier COPPER_HELMET = ITEMS.register("copper_helmet", () -> new ArmorItem( + // 要使用的护甲材料。 + COPPER_ARMOR_MATERIAL, + // 要使用的护甲类型。 + ArmorItem.Type.HELMET, + // 物品属性。我们不需要在此设置耐久度,因为 ArmorItem 会为我们处理。 + new Item.Properties() +)); +public static final Supplier COPPER_CHESTPLATE = ITEMS.register("copper_chestplate", () -> new ArmorItem(...)); +public static final Supplier COPPER_LEGGINGS = ITEMS.register("copper_leggings", () -> new ArmorItem(...)); +public static final Supplier COPPER_BOOTS = ITEMS.register("copper_boots", () -> new ArmorItem(...)); +``` + +除了通常的资源外,护甲还需要一个穿戴时的护甲纹理,它将在装备护甲时渲染在玩家模型上。该纹理必须位于 `src/main/resources/assets//textures/models/armor/_layer_1.png`(头盔、胸甲和靴子的纹理),以及相同目录中的 `_layer_2.png`(护腿的纹理)。 + +创建护甲纹理时,最好在基于标准护甲纹理的基础上进行工作,以确定每个部分的位置。 + +[block]: ../blocks/index.md +[farmersdelight]: https://www.curseforge.com/minecraft/mc-mods/farmers-delight +[item]: index.md +[mektools]: https://www.curseforge.com/minecraft/mc-mods/mekanism-tools +[tags]: ../resources/server/tags.md diff --git a/i18n/zh-Hans/docusaurus-plugin-content-docs/current/legacy/_category_.json b/i18n/zh-Hans/docusaurus-plugin-content-docs/current/legacy/_category_.json new file mode 100644 index 000000000..da5b88dbc --- /dev/null +++ b/i18n/zh-Hans/docusaurus-plugin-content-docs/current/legacy/_category_.json @@ -0,0 +1,3 @@ +{ + "label": "Legacy" +} \ No newline at end of file diff --git a/i18n/zh-Hans/docusaurus-plugin-content-docs/current/legacy/porting.md b/i18n/zh-Hans/docusaurus-plugin-content-docs/current/legacy/porting.md new file mode 100644 index 000000000..9e50a0119 --- /dev/null +++ b/i18n/zh-Hans/docusaurus-plugin-content-docs/current/legacy/porting.md @@ -0,0 +1,22 @@ +移植到 Minecraft 1.20 +========================= + +在这里,您可以找到有关如何从旧版本移植到当前版本的入门指南。某些版本被合并在一起,因为该特定版本从未被广泛使用。 + +| 从 -> 到 | 入门指南 | +|:-----------------:|:----------------------------------------| +| 1.12 -> 1.13/1.14 | [williewillus 的入门指南][112to114] | +| 1.14 -> 1.15 | [williewillus 的入门指南][114to115] | +| 1.15 -> 1.16 | [50ap5ud5 的入门指南][115to116] | +| 1.16 -> 1.17 | [50ap5ud5 的入门指南][116to117] | +| 1.19.2 -> 1.19.3 | [ChampionAsh5357 的入门指南][1192to1193] | +| 1.19.3 -> 1.19.4 | [ChampionAsh5357 的入门指南][1193to1194] | +| 1.19.4 -> 1.20.0 | [ChampionAsh5357 的入门指南][1194to120] | + +[112to114]: https://gist.github.com/williewillus/353c872bcf1a6ace9921189f6100d09a +[114to115]: https://gist.github.com/williewillus/30d7e3f775fe93c503bddf054ef3f93e +[115to116]: https://gist.github.com/50ap5ud5/f4e70f0e8faeddcfde6b4b1df70f83b8 +[116to117]: https://gist.github.com/50ap5ud5/beebcf056cbdd3c922cc8993689428f4 +[1192to1193]: https://gist.github.com/ChampionAsh5357/c21724bafbc630da2ed8899fe0c1d226 +[1193to1194]: https://gist.github.com/ChampionAsh5357/163a75e87599d19ee6b4b879821953e8 +[1194to120]: https://gist.github.com/ChampionAsh5357/cf818acc53ffea6f4387fe28c2977d56 diff --git a/i18n/zh-Hans/docusaurus-plugin-content-docs/current/misc/_category_.json b/i18n/zh-Hans/docusaurus-plugin-content-docs/current/misc/_category_.json new file mode 100644 index 000000000..26fda970d --- /dev/null +++ b/i18n/zh-Hans/docusaurus-plugin-content-docs/current/misc/_category_.json @@ -0,0 +1,3 @@ +{ + "label": "Miscellaneous" +} \ No newline at end of file diff --git a/i18n/zh-Hans/docusaurus-plugin-content-docs/current/misc/config.md b/i18n/zh-Hans/docusaurus-plugin-content-docs/current/misc/config.md new file mode 100644 index 000000000..e6f94f3eb --- /dev/null +++ b/i18n/zh-Hans/docusaurus-plugin-content-docs/current/misc/config.md @@ -0,0 +1,139 @@ +配置 +============= + +配置定义了可应用于模组实例的设置和用户偏好。NeoForge 使用 [TOML][toml] 文件并使用 [NightConfig][nightconfig] 进行读取的配置系统。 + +创建配置 +------------------------ + +可以使用 `IConfigSpec` 的子类型来创建配置。NeoForge 通过 `ModConfigSpec` 实现了该类型,并通过 `ModConfigSpec.Builder` 启用其构建。该构建器可以通过 `Builder#push` 将配置值分成部分以创建一个部分,通过 `Builder#pop` 离开一个部分。然后,可以使用以下两种方法之一构建配置: + + 方法 | 描述 + :--- | :--- +`build` | 创建 `ModConfigSpec`。 +`configure` | 创建持有配置值的类和 `ModConfigSpec` 的一对。 + +:::note +`ModConfigSpec.Builder#configure` 通常与 `static` 块和一个类一起使用,该类作为其构造函数的一部分接受 `ModConfigSpec.Builder` 来附加和保存值: + +```java +// 在某个配置类中 +ExampleConfig(ModConfigSpec.Builder builder) { + // 在此定义值的最终字段 +} + +// 某处可以访问构造函数 +static { + Pair pair = new ModConfigSpec.Builder() + .configure(ExampleConfig::new); + // 将配对值存储在某个常量字段中 +} +``` +::: + +每个配置值可以提供额外的上下文以提供附加行为。必须在完全构建配置值之前定义上下文: + +| 方法 | 描述 | +|:---------------|:------------------------------------------------------------------------------------------------------------| +| `comment` | 提供配置值功能的描述。可以为多行注释提供多个字符串。 | +| `translation` | 为配置值的名称提供翻译键。 | +| `worldRestart` | 必须在更改配置值之前重新启动世界。 | + +### ConfigValue + +可以使用提供的上下文(如果已定义)使用任何 `#define` 方法构建配置值。 + +所有配置值方法至少接受两个组件: + +* 表示变量名称的路径:一个 `.` 分隔的字符串,表示配置值所在的部分 +* 当没有有效配置时的默认值 + +`ConfigValue` 特定的方法接受两个额外的组件: + +* 验证器,以确保反序列化的对象有效 +* 表示配置值的数据类型的类 + +```java +// 对于某个 ModConfigSpec.Builder builder +ConfigValue value = builder.comment("Comment") + .define("config_value_name", defaultValue); +``` + +还可以使用 `ConfigValue#get` 获取值。值还被缓存以防止从文件中进行多次读取。 + +#### 额外的配置值类型 + +* **范围值** + * 描述:值必须在定义的边界之间 + * 类型:`Comparable` + * 方法名:`#defineInRange` + * 额外组件: + * 配置值可能的最小值和最大值 + * 表示配置值的数据类型的类 + +:::note +`DoubleValue`、`IntValue` 和 `LongValue` 是范围值,它们将类指定为 `Double`、`Integer` 和 `Long`,分别。 +::: + +* **白名单值** + * 描述:值必须在提供的集合中 + * 类型:`T` + * 方法名:`#defineInList` + * 额外组件: + * 配置可以是哪些值的集合 + +* **列表值** + * 描述:值是一系列条目 + * 类型:`List` + * 方法名:`#defineList`,如果列表可以为空,则为 `#defineListAllowEmpty` + * 额外组件: + * 验证器,以确保从列表中反序列化的元素有效 + +* **枚举值** + * 描述:在提供的集合中的枚举值 + * 类型:`Enum` + * 方法名:`#defineEnum` + * 额外组件: + * 一个 getter,将字符串或整数转换为枚举 + * 配置可以是哪些值的集合 + +* **布尔值** + * 描述:一个 `boolean` 值 + * 类型:`Boolean` + * 方法名:`#define` + +注册配置 +--------------------------- + +一旦构建了 `ModConfigSpec`,就必须注册它以允许 NeoForge 加载、跟踪和根据需要同步配置设置。配置应该在模组构造函数中通过 `ModLoadingContext#registerConfig` 注册。可以使用给定的类型(表示配置所属的一侧)、`ModConfigSpec` 和可选的特定文件名为配置注册。 + +```java +// 在具有 ModConfigSpec CONFIG 的模组构造函数中 +ModLoadingContext.get().registerConfig(Type.COMMON, CONFIG); +``` + +以下是可用的配置类型列表: + +| 类型 | 加载 | 同步到客户端 | 客户端位置 | 服务器位置 | 默认文件后缀 | +|:------:|:----------------:|:----------------:|:--------------------------------------------:|:------------------------------------:|:--------------------| +| CLIENT | 仅客户端 | 否 | `.minecraft/config` | N/A | `-client` | +| COMMON | 两边都有 | 否 | `.minecraft/config` | `/config` | `-common` | +| SERVER | 仅服务器 | 是 | `.minecraft/saves//serverconfig` | `/world/serverconfig` | `-server` | + +:::tip +NeoForge 在其代码库中记录了[配置类型][type]。 +::: + +配置事件 +-------------------- + +可以使用 `ModConfigEvent$Loading` 和 `ModConfigEvent$Reloading` 事件在加载或重新加载配置时执行的操作。必须将这些事件[注册][events]到模组事件总线上。 + +:::caution +这些事件适用于模组的所有配置;应使用提供的 `ModConfig` 对象来指示正在加载或重新加载的配置。 +::: + +[toml]: https://toml.io/ +[nightconfig]: https://github.com/TheElectronWill/night-config +[type]: https://github.com/neoforged/FancyModLoader/blob/19d6326b810233e683f1beb3d28e41372e1e89d1/core/src/main/java/net/neoforged/fml/config/ModConfig.java#L83-L111 +[events]: ../concepts/events.md#registering-an-event-handler diff --git a/i18n/zh-Hans/docusaurus-plugin-content-docs/current/misc/debugprofiler.md b/i18n/zh-Hans/docusaurus-plugin-content-docs/current/misc/debugprofiler.md new file mode 100644 index 000000000..15e821aed --- /dev/null +++ b/i18n/zh-Hans/docusaurus-plugin-content-docs/current/misc/debugprofiler.md @@ -0,0 +1,45 @@ +# 调试性能分析器 + +Minecraft 提供了一个调试性能分析器,它提供系统数据、当前游戏设置、JVM 数据、级别数据和边界刻信息,以找到耗时的代码。考虑到诸如 `TickEvent` 和刻动 `BlockEntities` 等因素,这对于想要找到卡顿来源的模组开发者和服务器所有者非常有用。 + +## 使用调试性能分析器 + +调试性能分析器非常简单易用。它需要使用调试按键组合 `F3 + L` 来启动分析器。10 秒后,它将自动停止;但是,您也可以通过再次按下该组合键来提前停止。 + +:::note +自然而然,您只能分析实际被执行的代码路径。您想要分析的 `实体` 和 `BlockEntities` 必须存在于级别中才会出现在结果中。 +::: + +在停止调试器后,它将在运行目录的 `debug/profiling` 子目录中创建一个新的 zip 文件。文件名将以日期和时间格式化为 `yyyy-mm-dd_hh_mi_ss-WorldName-VersionNumber.zip` + +## 阅读性能分析结果 + +在每个边界文件夹 (`client` 和 `server`) 中,您会找到一个包含结果数据的 `profiling.txt` 文件。在顶部,它首先告诉您在运行的毫秒数以及在此期间运行了多少个刻。 + +在此之下,您会发现类似于以下片段的信息: +``` +[00] levels - 96.70%/96.70% +[01] | Level Name - 99.76%/96.47% +[02] | | tick - 99.31%/95.81% +[03] | | | entities - 47.72%/45.72% +[04] | | | | regular - 98.32%/44.95% +[04] | | | | blockEntities - 0.90%/0.41% +[05] | | | | | unspecified - 64.26%/0.26% +[05] | | | | | minecraft:furnace - 33.35%/0.14% +[05] | | | | | minecraft:chest - 2.39%/0.01% +``` +这里是每个部分的简要解释 + +| [02] | tick | 99.31% | 95.81% | +| :----------------------- | :---------------------- | :----------- | :----------- | +| 该部分的深度 | 该部分的名称 | 它花费的时间与其父部分的百分比。对于层级 0,它是一次刻所花费时间的百分比。对于层级 1,它是其父部分所花费时间的百分比。 | 第二个百分比告诉您它从整个刻中花费了多少时间。 + +## 对自己的代码进行性能分析 + +调试性能分析器对 `Entity` 和 `BlockEntity` 有基本支持。如果您想分析其他内容,您可能需要手动创建您的部分,如下所示: +```java +ProfilerFiller#push(yourSectionName : String); +//您想要分析的代码 +ProfilerFiller#pop(); +``` +您可以从 `Level`、`MinecraftServer` 或 `Minecraft` 实例获取 `ProfilerFiller` 实例。现在,您只需要在结果文件中搜索您的部分名称即可。 diff --git a/i18n/zh-Hans/docusaurus-plugin-content-docs/current/misc/gametest.mdx b/i18n/zh-Hans/docusaurus-plugin-content-docs/current/misc/gametest.mdx new file mode 100644 index 000000000..981b1c40c --- /dev/null +++ b/i18n/zh-Hans/docusaurus-plugin-content-docs/current/misc/gametest.mdx @@ -0,0 +1,290 @@ +# 游戏测试 + +游戏测试是一种运行游戏内单元测试的方法。该系统被设计为可扩展并且并行运行,以有效地运行大量不同的测试。测试对象的交互和行为只是该框架的众多应用之一。 + +创建游戏测试 +------------ + +标准的游戏测试遵循三个基本步骤: + +1. 加载一个结构或模板,其中包含要测试的交互或行为所在的场景。 +2. 一个方法执行逻辑来执行场景上的操作。 +3. 方法逻辑执行。如果达到了成功的状态,则测试成功。否则,测试失败,并将结果存储在场景旁边的讲台上。 + +因此,要创建一个游戏测试,必须有一个存在的模板,其中包含场景的初始起始状态,以及一个提供执行逻辑的方法。 + +### 测试方法 + +游戏测试方法是一个 `Consumer` 引用,意味着它接受一个 `GameTestHelper` 并且不返回任何内容。为了使游戏测试方法被识别,它必须有一个 `@GameTest` 注解: + +```java +public class ExampleGameTests { + @GameTest + public static void exampleTest(GameTestHelper helper) { + // Do stuff + } +} +``` + +`@GameTest` 注解还包含配置游戏测试运行方式的成员。 + +```java +// 在某个类中 +@GameTest( + setupTicks = 20L, // 测试花费 20 刻来设置执行环境 + required = false // 失败会被记录,但不会影响批次的执行 +) +public static void exampleConfiguredTest(GameTestHelper helper) { + // Do stuff +} +``` + +#### 相对定位 + +所有 `GameTestHelper` 方法都将结构模板场景中的相对坐标转换为其绝对坐标,使用结构方块的当前位置。为了方便相对和绝对定位之间的转换,可以分别使用 `GameTestHelper#absolutePos` 和 `GameTestHelper#relativePos`。 + +通过在游戏中加载结构并使用 [测试命令][test] 将玩家放置在所需位置,最后运行 `/test pos` 命令,可以获取结构模板的相对位置。该命令将以可复制的文本组件的形式在聊天中导出相对于玩家所在位置的最近结构的坐标。这个导出的文本可以作为最终的局部变量使用。 + +:::tip +`/test pos` 生成的本地变量可以通过将其附加到命令的末尾来指定其引用名称: + +```bash +/test pos # 导出 'final BlockPos = new BlockPos(...);' +``` +::: + +#### 成功完成 + +游戏测试方法负责一件事情:在有效完成时标记测试为成功。如果在达到超时之前(由 `GameTest#timeoutTicks` 定义)没有达到成功状态,则测试会自动失败。 + +在 `GameTestHelper` 中有许多抽象方法可以用来定义成功状态;然而,有四个是非常重要的需要注意的。 + +方法 | 描述 +:---: | :--- +`#succeed` | 将测试标记为成功。 +`#succeedIf` | 立即测试提供的 `Runnable`,如果没有抛出 `GameTestAssertException`,则成功。如果在当前刻没有成功,则标记为失败。 +`#succeedWhen` | 每刻测试提供的 `Runnable`,直到超时,如果在其中一刻检查不会抛出 `GameTestAssertException` 则成功。 +`#succeedOnTickWhen` | 在指定的刻上测试提供的 `Runnable`,如果没有抛出 `GameTestAssertException`,则成功。如果在任何其他刻上成功,则标记为失败。 + +:::caution +游戏测试会每刻执行一次,直到测试被标记为成功为止。因此,为了成功标记某一特定刻上的成功,必须在之前的任何刻上都失败。 +::: + +#### 安排操作 + +并不是所有的操作在测试开始时都会发生。可以安排在特定时间或间隔发生的操作: + +方法 | 描述 +:---: | :--- +`#runAtTickTime` | 在指定的刻上运行操作。 +`#runAfterDelay` | 在当前刻之后的 `x` 刻运行操作。 +`#onEachTick` | 每一刻都运行操作。 + +#### 断言 + +在游戏测试的任何时候,都可以进行断言来检查给定条件是否为真。在 `GameTestHelper` 中有许多断言方法;然而,简化为在适当状态未满足时抛出 `GameTestAssertException`。 + +### 生成的测试方法 + +如果游戏测试方法需要动态生成,可以创建一个测试方法生成器。这些方法不接受任何参数,并返回一个 `TestFunction` 集合。为了使测试方法生成器被识别,它必须有一个 `@GameTestGenerator` 注解: + +```java +public class ExampleGameTests { + @GameTestGenerator + public static Collection exampleTests() { + // 返回一个 TestFunction 集合 + } +} +``` + +#### TestFunction + +`TestFunction` 是由 `@GameTest` 注解和运行测试的方法所包装的信息。 + +:::tip +使用 `@GameTest` 注解的任何方法都会使用 `GameTestRegistry#turnMethodIntoTestFunction` 将其转换为 `TestFunction`。该方法可用作创建 `TestFunction` 的参考,而不需要使用注解。 +::: + +### 批次处理 + +游戏测试可以以批次方式而不是按照注册顺序执行。可以通过具有相同的提供的 `GameTest#batch` 字符串将测试添加到 + +批次中。 + +仅有批处理本身并不提供任何有用的功能。然而,批处理可以用于在测试运行的当前级别上执行设置和拆卸状态。通过将方法标记为 `@BeforeBatch` 进行设置,或标记为 `@AfterBatch` 进行拆卸。`#batch` 方法必须与游戏测试中提供的字符串匹配。 + +批处理方法是 `Consumer` 引用,意味着它们接受一个 `ServerLevel` 并且不返回任何内容: + +```java +public class ExampleGameTests { + @BeforeBatch(batch = "firstBatch") + public static void beforeTest(ServerLevel level) { + // 执行设置 + } + + @GameTest(batch = "firstBatch") + public static void exampleTest2(GameTestHelper helper) { + // Do stuff + } +} +``` + +注册游戏测试 +---------- + +游戏测试必须注册才能在游戏中运行。有两种方法可以实现:通过 `@GameTestHolder` 注解或 `RegisterGameTestsEvent`。这两种注册方法仍然要求测试方法被注解为 `@GameTest`、`@GameTestGenerator`、`@BeforeBatch` 或 `@AfterBatch` 中的一种。 + +### GameTestHolder + +`@GameTestHolder` 注解注册类型(类、接口、枚举或记录)中的任何测试方法。`@GameTestHolder` 包含一个方法,具有多种用途。在此示例中,提供的 `#value` 必须是 mod 的 id;否则,测试将不会在默认配置下运行。 + +```java +@GameTestHolder(MODID) +public class ExampleGameTests { + // ... +} +``` + +### RegisterGameTestsEvent + +`RegisterGameTestsEvent` 也可以通过 `#register` 注册类或方法。事件监听器必须 [添加][event] 到 mod 事件总线上。以这种方式注册的测试方法必须在每个标注了 `@GameTest` 的方法中提供它们的 mod id 给 `GameTest#templateNamespace`。 + +```java +// 在某个类中 +public void registerTests(RegisterGameTestsEvent event) { + event.register(ExampleGameTests.class); +} + +// 在 ExampleGameTests 中 +@GameTest(templateNamespace = MODID) +public static void exampleTest3(GameTestHelper helper) { + // 执行设置 +} +``` + +:::note +提供给 `GameTestHolder#value` 和 `GameTest#templateNamespace` 的值可以与当前 mod id 不同。在 [buildscript][namespaces] 中的配置需要更改。 +::: + +结构模板 +----------- + +游戏测试在由结构或模板加载的场景中执行。所有模板都定义了场景的尺寸以及将要加载的初始数据(方块和实体)。模板必须以 `.nbt` 文件的形式存储在 `data//structures` 目录中。 + +:::tip +可以使用结构方块创建并保存结构模板。 +::: + +模板的位置由以下几个因素确定: + +- 如果指定了模板的命名空间。 +- 如果类应该作为模板名称的前缀。 +- 如果指定了模板的名称。 + +模板的命名空间由 `GameTest#templateNamespace` 确定,如果未指定,则由 `GameTestHolder#value` 确定,如果两者都未指定,则由 `minecraft` 确定。 + +如果将 `@PrefixGameTestTemplate` 应用于带有测试注解的类或方法,并且设置为 `false`,则不会将简单类名添加到模板名称的前面。否则,简单类名将被转换为小写并添加到模板名称的前后,中间用点分隔。 + +模板的名称由 `GameTest#template` 确定。如果未指定,则使用方法的小写名称。 + +```java +// 所有结构的 modid 将为 MODID +@GameTestHolder(MODID) +public class ExampleGameTests { + + // 类名作为前缀,未指定模板名称 + // 模板位置为 'modid:examplegametests.exampletest' + @GameTest + public static void exampleTest(GameTestHelper helper) { /*...*/ } + + // 类名不作为前缀,未指定模板名称 + // 模板位置为 'modid:exampletest2' + @PrefixGameTestTemplate(false) + @GameTest + public static void exampleTest2(GameTestHelper helper) { /*...*/ } + + // 类名作为前缀,指定了模板名称 + // 模板位置为 'modid:examplegametests.test_template' + @GameTest(template = "test_template") + public static void exampleTest3(GameTestHelper helper) { /*...*/ } + + // 类名不作为前缀,指定了模板名称 + // 模板位置为 'modid:test_template2' + @PrefixGameTestTemplate(false) + @GameTest(template = "test_template2") + public static void exampleTest4(GameTestHelper helper) { /*...*/ } +} +``` + +运行游戏测试 +------------------ + +可以使用 `/test` 命令运行游戏测试。`test` 命令是高度可配置的;然而,只有几个对于运行测试至关重要: + +子命令 | 描述 +:---: | :--- +`run` | 运行指定的测试:`run `。 +`runall` | 运行所有可用的测试。 +`runthis` | 在玩家附近 15 格内运行最近的测试。 +`runthese` | 运行玩家 200 格内的测试。 +`runfailed` | 运行上次运行失败的所有测试。 + +:::note +子命令遵循测试命令:`/test `。 +::: + +构建脚本配置 +------------------ + +游戏测试提供了在构建脚本(`build.gradle` 文件)中运行和集成到不同设置中的额外配置设置。 + +### 启用其他命名空间 + +如果构建脚本已按照[推荐方式设置][buildscript],那么只有当前 mod id 下的游戏测试将被启用。要启用其他命名空间加载游戏测试,运行配置必须将属性 `forge.enabledGameTestNamespaces` 设置为一个字符串,其中指定每个命名空间,用逗号分隔。如果属性为空或未设置,则将加载所有命名空间。 + +```gradle +// 在运行配置中 +property 'forge.enabledGameTestNamespaces', 'modid1,modid2,modid3' +``` + +:::caution +命名空间之间不能有空格;否则,命名空间将无法正确加载。 +::: + +### 游戏测试服务器运行配置 + +游戏测试服务器是一个特殊配置,运行一个构建服务器。构建服务器返回需要的失败游戏测试数的退出代码。所有失败的测试,无论是必需的还是可选的,都将被记录。可以使用 `gradlew runGameTestServer` 来运行此服务器。 + +
+ FG5 的重要信息 + +:::caution +由于 Gradle 的工作方式的一个怪异之处,如果一个任务强制系统退出,默认情况下 Gradle 守护进程将被终止,导致 Gradle 运行器报告构建失败。ForgeGradle 默认设置了对运行任务的强制退出,以便任何子项目都不会按顺序执行。然而,这样一来,游戏测试服务器将总是失败。 + +可以通过在运行配置中禁用强制退出来解决此问题,使用 `#setForceExit` 方法: + +```gradle +// 游戏测试服务器运行配置 +gameTestServer { + // ... + setForceExit false +} +``` +::: +
+ +### 在其他运行配置中启用游戏测试 + +默认情况下,只有 `client`、`server` 和 `gameTestServer` 运行配置中启用了游戏测试。如果另一个运行配置应该运行游戏测试,那么必须将 `forge.enableGameTest` 属性设置为 `true`。 + +```gradle +// 在运行配置中 +property + + 'forge.enableGameTest', 'true' +``` + +[test]: #running-game-tests +[namespaces]: #enabling-other-namespaces +[event]: ../concepts/events.md#registering-an-event-handler +[buildscript]: ../gettingstarted/index.md#simple-buildgradle-customizations diff --git a/i18n/zh-Hans/docusaurus-plugin-content-docs/current/misc/keymappings.md b/i18n/zh-Hans/docusaurus-plugin-content-docs/current/misc/keymappings.md new file mode 100644 index 000000000..20d66d57f --- /dev/null +++ b/i18n/zh-Hans/docusaurus-plugin-content-docs/current/misc/keymappings.md @@ -0,0 +1,159 @@ +# Key Mappings + +一个按键映射或键绑定定义了应与输入相关联的特定操作:鼠标单击、按键等。每当客户端可以接受输入时,都可以检查键映射定义的每个操作。 此外,每个按键映射都可以通过[控制选项菜单][控制]分配给任何输入。 + +## 注册一个`KeyMapping` + +可以通过仅在物理客户端上监听 [**mod 事件总线**][modbus] 上的 `RegisterKeyMappingsEvent` 并调用 `#register` 来注册 `KeyMapping`。 + +```java +// 在某个仅限物理客户端的类中 + +// KeyMapping 是惰性初始化的,因此在注册之前它不存在 +public static final Lazy EXAMPLE_MAPPING = Lazy.of(() -> /*...*/); + +// 事件仅在物理客户端上的 mod 事件总线上 +@SubscribeEvent +public void registerBindings(RegisterKeyMappingsEvent event) { + event.register(EXAMPLE_MAPPING.get()); +} +``` + +## 创建 `KeyMapping` + +可以使用其构造函数创建 `KeyMapping`。`KeyMapping` 接受一个[翻译键][tk],用于定义映射的名称,映射的默认输入以及在[控制选项菜单][controls]中将映射放置在其中的类别的[翻译键][tk]。 + +:::tip +可以通过提供未由原版提供的自定义类别 [翻译键][tk] 来将 `KeyMapping` 添加到自定义类别中。自定义类别翻译键应包含 mod id(例如 `key.categories.examplemod.examplecategory`)。 +::: + +### 默认输入 + +每个键映射都与一个默认输入关联。这是通过 `InputConstants$Key` 提供的。每个输入由一个 `InputConstants$Type` 组成,用于定义提供输入的设备,以及一个整数,用于定义设备上关联的标识符。 + +原版提供了三种输入类型:`KEYSYM`,它使用提供的 `GLFW` 键令定义键盘,`SCANCODE`,它使用平台特定的扫描码定义键盘,以及 `MOUSE`,它定义了鼠标。 + +:::note +强烈建议使用 `KEYSYM` 而不是 `SCANCODE` 用于键盘,因为 `GLFW` 键令与任何特定的系统无关。您可以在 [GLFW 文档][keyinput] 上阅读更多信息。 +::: + +整数取决于所提供的类型。所有输入代码都在 `GLFW` 中定义:`KEYSYM` 令牌以 `GLFW_KEY_*` 为前缀,而 `MOUSE` 代码以 `GLFW_MOUSE_*` 为前缀。 + +```java +new KeyMapping( + "key.examplemod.example1", // 将使用此翻译键进行本地化 + InputConstants.Type.KEYSYM, // 默认映射在键盘上 + GLFW.GLFW_KEY_P, // 默认键为 P + "key.categories.misc" // 映射将位于杂项类别中 +) +``` + +:::note +如果键映射不应映射到默认键,则应将输入设置为 `InputConstants#UNKNOWN`。原版构造函数将要求您通过 `InputConstants$Key#getValue` 提取输入代码,而 Forge 构造函数可以提供原始输入字段。 +::: + +### `IKeyConflictContext` + +并非所有映射都在每个上下文中使用。某些映射仅在 GUI 中使用,而其他映射则仅在游戏中使用。为了避免在不同上下文中使用相同键的映射相互冲突,可以分配一个 `IKeyConflictContext`。 + +每个冲突上下文都包含两种方法:`#isActive`,定义映射是否可以在当前游戏状态下使用,以及 `#conflicts`,定义映射是否与同一冲突上下文中的键或不同冲突上下文中的键冲突。 + +目前,Forge 通过 `KeyConflictContext` 定义了三个基本上下文:`UNIVERSAL`,默认为意味着键可以在每个上下文中使用,`GUI`,意味着映射只能在打开 `Screen` 时使用,以及 `IN_GAME`,意味着映射只能在未打开 `Screen` 时使用。可以通过实现 `IKeyConflictContext` 来创建新的冲突上下文。 + +```java +new KeyMapping( + "key.examplemod.example2", + KeyConflictContext.GUI, // 只能在打开屏幕时使用映射 + InputConstants.Type.MOUSE, // 默认映射在鼠标上 + GLFW.GLFW_MOUSE_BUTTON_LEFT, // 默认鼠标输入为左键 + "key.categories.examplemod.examplecategory" // 映射将位于新示例类别中 +) +``` + +### `KeyModifier` + +模组可能不希望映射在按下修饰键时具有相同的行为(例如 `G` vs `CTRL + G`)。为解决这个问题,Forge 在构造函数中添加了一个额外的参数,以接受一个 `KeyModifier`,该修饰符可以应用控制(`KeyModifier#CONTROL`)、shift(`KeyModifier#SHIFT`)或 alt(`KeyModifier#ALT`)到任何输入。`KeyModifier#NONE` 是默认值,不会应用任何修饰符。 + +可以通过按住修饰键和相关输入来将修饰符添加到[控制选项菜单][controls]中。 + +```java +new KeyMapping( + "key.examplemod.example3", + KeyConflictContext.UNIVERSAL, + KeyModifier.SHIFT, // 默认映射需要按住 shift + InputConstants.Type.KEYSYM, // 默认映射在键盘上 + GLFW.GLFW_KEY_G, // 默认键为 G + "key.categories.misc" +) +``` + +## 检查 `KeyMapping` + +可以检查 `KeyMapping` 来查看是否已单击它。根据何时,映射可以在条件中用于应用相关逻辑。 + +### 在游戏中 + +在游戏中,应通过监听[**Forge 事件总线**][forgebus]上的 `ClientTickEvent` 并在 while 循环中检查 `KeyMapping#consumeClick` 来检查映射是否已被单击。`#consumeClick` 仅在执行输入的次数且尚未处理之前返回 `true`,因此不会 + +无限制地阻止游戏。 + +```java +// 事件仅在 Forge 事件总线上的物理客户端上 +public void onClientTick(ClientTickEvent event) { + if (event.phase == TickEvent.Phase.END) { // 由于每个刻度事件都调用两次,因此只调用一次代码 + while (EXAMPLE_MAPPING.get().consumeClick()) { + // 在此处执行单击时要执行的逻辑 + } + } +} +``` + +:::caution +不要将 `InputEvent` 用作 `ClientTickEvent` 的替代方案。键盘和鼠标输入各有单独的事件,因此它们不会处理任何额外的输入。 +::: + +### 在 GUI 内 + +在 GUI 中,可以在 `GuiEventListener` 方法之一中使用 `IForgeKeyMapping#isActiveAndMatches` 检查映射。可以检查的最常见方法是 `#keyPressed` 和 `#mouseClicked`。 + +`#keyPressed` 接受 `GLFW` 键令、平台特定的扫描码以及按下的修饰键的位字段。可以通过使用 `InputConstants#getKey` 创建输入来将键与映射进行检查。修饰符已经在映射方法本身中进行了检查。 + +```java +// 在某个 Screen 子类中 +@Override +public boolean keyPressed(int key, int scancode, int mods) { + if (EXAMPLE_MAPPING.get().isActiveAndMatches(InputConstants.getKey(key, scancode))) { + // 在此处执行按键按下时要执行的逻辑 + return true; + } + return super.keyPressed(x, y, button); +} +``` + +:::note +如果您不拥有要检查 **键** 的屏幕,则可以监听[**Forge 事件总线**][forgebus]上的 `ScreenEvent$KeyPressed` 的 `Pre` 或 `Post` 事件。 +::: + +`#mouseClicked` 接受鼠标的 x 位置、y 位置和单击的按钮。鼠标按钮可以使用 `InputConstants$Type#getOrCreate` 与 `MOUSE` 输入创建输入进行与映射的检查。 + +```java +// 在某个 Screen 子类中 +@Override +public boolean mouseClicked(double x, double y, int button) { + if (EXAMPLE_MAPPING.get().isActiveAndMatches(InputConstants.TYPE.MOUSE.getOrCreate(button))) { + // 在此处执行鼠标单击时要执行的逻辑 + return true; + } + return super.mouseClicked(x, y, button); +} +``` + +:::note +如果您不拥有要检查 **鼠标** 的屏幕,则可以监听[**Forge 事件总线**][forgebus]上的 `ScreenEvent$MouseButtonPressed` 的 `Pre` 或 `Post` 事件。 +::: + +[modbus]: ../concepts/events.md#event-buses +[controls]: https://minecraft.fandom.com/wiki/Controls +[tk]: ../resources/client/i18n.md#components +[keyinput]: https://www.glfw.org/docs/3.3/input_guide.html#input_key +[forgebus]: ../concepts/events.md#registering-an-event-handler diff --git a/i18n/zh-Hans/docusaurus-plugin-content-docs/current/misc/resourcelocation.md b/i18n/zh-Hans/docusaurus-plugin-content-docs/current/misc/resourcelocation.md new file mode 100644 index 000000000..ee7682cd0 --- /dev/null +++ b/i18n/zh-Hans/docusaurus-plugin-content-docs/current/misc/resourcelocation.md @@ -0,0 +1,48 @@ +# 资源位置 + +`ResourceLocation` 是 Minecraft 中最重要的内容之一。它们用作[注册表][registries]中的键,作为数据或资源文件的标识符,作为代码中模型的引用,以及许多其他地方。`ResourceLocation` 由两部分组成:命名空间和路径,由 `:` 分隔。 + +命名空间表示资源位置所指的 mod、资源包或数据包。例如,具有模组 ID `examplemod` 的模组将使用 `examplemod` 命名空间。Minecraft 使用 `minecraft` 命名空间。可以根据需要定义额外的命名空间,只需创建相应的数据文件夹,这通常是由数据包执行的,以将其逻辑与 Vanilla 分开。 + +路径是指你的命名空间内的任何对象的引用。例如,`minecraft:cow` 是指 `minecraft` 命名空间中名为 `cow` 的对象 - 通常此位置将用于从实体注册表中获取 cow 实体。另一个示例是 `examplemod:example_item`,它可能用于从项注册表中获取模组的 `example_item`。 + +`ResourceLocation` 只能包含小写字母、数字、下划线、点和连字符。路径可能还包含斜杠。请注意,由于 Java 模块的限制,模组 ID 不得包含连字符,这意味着模组命名空间也不得包含连字符(路径仍然允许包含)。 + +:::info +`ResourceLocation` 本身并不表示我们要使用它的对象的类型。例如,名为 `minecraft:dirt` 的对象存在于多个位置。由接收 `ResourceLocation` 的对象决定将对象与其关联。 +::: + +可以通过调用 `new ResourceLocation("examplemod", "example_item")` 或 `new ResourceLocation("examplemod:example_item")` 来创建新的 `ResourceLocation` 实例。如果使用后者,并且字符串不包含 `:`,则整个字符串将用作路径,而 `minecraft` 将用作命名空间。因此,例如 `new ResourceLocation("example_item")` 将导致 `minecraft:example_item`。 + +可以使用 `ResourceLocation#getNamespace()` 和 `#getPath()` 分别检索 `ResourceLocation` 的命名空间和路径,并通过 `ResourceLocation#toString()` 检索组合形式。 + +`ResourceLocation` 是不可变的。`ResourceLocation` 上的所有实用方法,例如 `withPrefix` 或 `withSuffix`,都返回一个新的 `ResourceLocation`。 + +## 解析 `ResourceLocation` + +某些位置,例如注册表,直接使用 `ResourceLocation`。然而,其他一些位置将根据需要解析 `ResourceLocation`。例如: + +- `ResourceLocation` 用作 GUI 背景的标识符。例如,熔炉 GUI 使用资源位置 `minecraft:textures/gui/container/furnace.png`。这映射到磁盘上的文件 `assets/minecraft/textures/gui/container/furnace.png`。请注意,在此资源位置中需要 `.png` 后缀。 +- `ResourceLocation` 用作方块模型的标识符。例如,泥土的方块模型使用资源位置 `minecraft:block/dirt`。这映射到磁盘上的文件 `assets/minecraft/models/block/dirt.json`。请注意,在此资源位置中不需要 `.json` 后缀。还请注意,此资源位置自动映射到 `models` 子文件夹。 +- `ResourceLocation` 用作配方的标识符。例如,铁块的合成配方使用资源位置 `minecraft:iron_block`。这映射到磁盘上的文件 `data/minecraft/recipes/iron_block.json`。请注意,在此资源位置中不需要 `.json` 后缀。还请注意,此资源位置自动映射到 `recipes` 子文件夹。 + +`ResourceLocation` 是否需要文件后缀,以及资源位置解析为什么内容,取决于使用情况。 + +## `ModelResourceLocation` + +`ModelResourceLocation` 是一种特殊类型的资源位置,包含第三部分,称为变体。Minecraft 主要用于区分模型的不同变体,在不同的显示上下文中使用不同的变体(例如三叉戟,在第一人称、第三人称和库存中有不同的模型)。对于项,变体始终为 `inventory`,对于块状态,变体是由属性-值对的逗号分隔字符串组成的(例如 `facing=north,waterlogged=false`),对于没有块状态属性的块为空。 + +变体附加到常规资源位置,以及 `#`。例如,钻石剑的项模型的完整名称是 `minecraft:diamond_sword#inventory`。然而,在大多数情况下,`inventory` 变体可以省略。 + +`ModelResourceLocation` 是一个[仅客户端][sides]的类。这意味着引用该类的服务器将因为 `NoClassDefFoundError` 而崩溃。 + +## `ResourceKey` + +`ResourceKey` 将注册表 ID 与注册表名称结合在一起。一个示例是具有注册表 ID `minecraft:item` 和注册表名称 `minecraft:diamond_sword` 的注册表键。与 `ResourceLocation` 不同,`ResourceKey` 实际上指代一个唯一的元素,因此能够清楚地识别一个元素。它们通常用于许多不同的注册表相互接触的情况。一个常见的用例是数据包,特别是世界生成。 + +可以通过静态方法 `ResourceKey#create(ResourceKey>, ResourceLocation)` 创建新的 `ResourceKey`。这里的第二个参数是注册表名称,而第一个参数是所谓的注册表键。注册表键是一种特殊的 `ResourceKey`,其注册表是根注册表(即所有其他注册表的注册表)。可以通过 `ResourceKey#createRegistryKey(ResourceLocation)` 创建所需注册表的注册表键。 + +`ResourceKey` 在创建时进行了内部化。这意味着可以并且鼓励通过引用相等性(`==`)进行比较,但它们的创建相对较昂贵。 + +[registries]: ../concepts/registries.md +[sides]: ../concepts/sides.md diff --git a/i18n/zh-Hans/docusaurus-plugin-content-docs/current/misc/updatechecker.md b/i18n/zh-Hans/docusaurus-plugin-content-docs/current/misc/updatechecker.md new file mode 100644 index 000000000..3f6194d43 --- /dev/null +++ b/i18n/zh-Hans/docusaurus-plugin-content-docs/current/misc/updatechecker.md @@ -0,0 +1,63 @@ +Forge更新检查器 +==================== + +Forge提供了一个非常轻量级的、可选择的更新检查框架。如果任何mod有可用的更新,它将在主菜单的“Mods”按钮和mod列表上显示一个闪烁的图标,以及相应的更新日志。它*不会*自动下载更新。 + +入门指南 +--------------- + +首先,您需要在您的 `mods.toml` 文件中指定 `updateJSONURL` 参数。该参数的值应该是一个指向更新JSON文件的有效URL。此文件可以托管在您自己的Web服务器、GitHub或任何您想要的地方,只要所有使用您的mod的用户都可以可靠地访问它。 + +更新JSON格式 +------------------ + +JSON本身具有相对简单的格式,如下所示: + +```js +{ + "homepage": "<您的mod的主页/下载页面>", + "": { + "": "<此版本的更新日志>", + // 列出给定Minecraft版本的所有版本的您的mod,以及它们的更新日志 + // ... + }, + "promos": { + "-latest": "", + // 声明给定Minecraft版本的最新的“最新”版本的您的mod + "-recommended": "", + // 声明给定Minecraft版本的最新的“稳定”版本的您的mod + // ... + } +} +``` + +这相当容易理解,但一些注意事项: + +* `homepage` 下的链接是当mod过期时将向用户显示的链接。 +* Forge使用内部算法来确定您的mod的一个版本字符串是否比另一个版本字符串“更新”。大多数版本方案都应该是兼容的,但如果您担心您的方案是否受支持,请参阅 `ComparableVersion` 类。强烈建议遵循[Maven版本规范][mvnver]。 +* 更新日志字符串可以使用 `\n` 分隔成行。一些人喜欢包含一个简短的更新日志,然后链接到一个提供完整更改列表的外部网站。 +* 手动输入数据可能会很烦琐。您可以将您的 `build.gradle` 配置为在构建发布时自动更新此文件,因为 Groovy 具有原生的JSON解析支持。将此操作留给读者作为练习。 + +- 这里有一些示例,[nocubes][], [Corail Tombstone][corail] 和 [Chisels & Bits 2][chisel]。 + +检索更新检查结果 +------------------------------- + +您可以使用 `VersionChecker#getResult(IModInfo)` 检索 Forge 更新检查器的结果。您可以通过 `ModContainer#getModInfo` 获取您的 `IModInfo`。您可以在构造函数中使用 `ModLoadingContext.get().getActiveContainer()`,`ModList.get().getModContainerById()` 或 `ModList.get().getModContainerByObject()` 获取您的 `ModContainer`。您可以使用 `ModList.get().getModContainerById()` 获取任何其他 mod 的 `ModContainer`。返回的对象具有一个 `#status` 方法,该方法指示版本检查的状态。 + +| 状态 | 描述 | +|----------------:|:------------| +| `FAILED` | 版本检查器无法连接到提供的URL。 | +| `UP_TO_DATE` | 当前版本等于推荐版本。 | +| `AHEAD` | 如果没有最新版本,当前版本比推荐版本新。 | +| `OUTDATED` | 有新的推荐或最新版本。 | +| `BETA_OUTDATED` | 有新的最新版本。 | +| `BETA` | 当前版本等于或比最新版本更新。 | +| `PENDING` | 请求的结果尚未完成,因此您应该稍后重试。 | + +返回的对象还将具有目标版本和任何在 `update.json` 中指定的更新日志行。 + +[mvnver]: ../gettingstarted/versioning.md +[nocubes]: https://cadiboo.github.io/projects/nocubes/update.json +[corail]: https://github.com/Corail31/tombstone_lite/blob/master/update.json +[chisel]: https://github.com/Aeltumn/Chisels-and-Bits-2/blob/master/update.json diff --git a/i18n/zh-Hans/docusaurus-plugin-content-docs/current/networking/configuration-tasks.md b/i18n/zh-Hans/docusaurus-plugin-content-docs/current/networking/configuration-tasks.md new file mode 100644 index 000000000..e8f40c032 --- /dev/null +++ b/i18n/zh-Hans/docusaurus-plugin-content-docs/current/networking/configuration-tasks.md @@ -0,0 +1,122 @@ +# 使用配置任务 + +客户端和服务器的网络协议有一个特定的阶段,服务器可以在玩家实际加入游戏之前配置客户端。 +这个阶段称为配置阶段,例如,原版服务器用它来向客户端发送资源包信息。 + +这个阶段也可以被 mod 用来在玩家加入游戏之前配置客户端。 + +## 注册配置任务 +使用配置阶段的第一步是注册一个配置任务。 +这可以通过在 `OnGameConfigurationEvent` 事件中注册新的配置任务来完成。 +```java +@SubscribeEvent +public static void register(final OnGameConfigurationEvent event) { + event.register(new MyConfigurationTask()); +} +``` +`OnGameConfigurationEvent` 事件在 mod 总线上触发,并暴露了服务器用来配置相关客户端的当前监听器。 +Modder 可以使用暴露的监听器来判断客户端是否运行了 mod,并在是这样的情况下注册一个配置任务。 + +## 实现配置任务 +配置任务是一个简单的接口:`ICustomConfigurationTask`。 +这个接口有两个方法:`void run(Consumer sender);`,和 `ConfigurationTask.Type type();` 返回配置任务的类型。 +类型用于标识配置任务。 +下面是一个配置任务的示例: +```java +public record MyConfigurationTask implements ICustomConfigurationTask { + public static final ConfigurationTask.Type TYPE = new ConfigurationTask.Type(new ResourceLocation("mymod:my_task")); + + @Override + public void run(final Consumer sender) { + final MyData payload = new MyData(); + sender.accept(payload); + } + + @Override + public ConfigurationTask.Type type() { + return TYPE; + } +} +``` + +## 确认配置任务 +您的配置在服务器上执行,服务器需要知道何时可以执行下一个配置任务。 +这可以通过确认所述配置任务的执行来完成。 + +有两种主要方式可以实现这一点: + +### 捕获监听器 +当客户端不需要确认配置任务时,可以捕获监听器,并可以直接在服务器端确认配置任务。 +```java +public record MyConfigurationTask(ServerConfigurationListener listener) implements ICustomConfigurationTask { + public static final ConfigurationTask.Type TYPE = new ConfigurationTask.Type(new ResourceLocation("mymod:my_task")); + + @Override + public void run(final Consumer sender) { + final MyData payload = new MyData(); + sender.accept(payload); + listener.finishCurrentTask(type()); + } + + @Override + public ConfigurationTask.Type type() { + return TYPE; + } +} +``` +要使用这样的配置任务,需要在 `OnGameConfigurationEvent` 事件中捕获监听器。 +```java +@SubscribeEvent +public static void register(final OnGameConfigurationEvent event) { + event.register(new MyConfigurationTask(event.listener())); +} +``` +然后,在当前配置任务完成后,下一个配置任务将立即执行,客户端不需要确认配置任务。 +此外,服务器将不会等待客户端正确处理发送的载荷。 + +### 确认配置任务 +当客户端需要确认配置任务时,您将需要向客户端发送自己的载荷: +```java +public record AckPayload() implements CustomPacketPayload { + public static final ResourceLocation ID = new ResourceLocation("mymod:ack"); + + @Override + public void write(final FriendlyByteBuf buffer) { + // 无需写入数据 + } + + @Override + public ResourceLocation id() { + return ID; + } +} +``` +当服务器端配置任务发送的有效载荷被正确处理时,您可以向服务器发送此载荷以确认配置任务。 +```java +public void onMyData(MyData data, ConfigurationPayloadContext context) { + context.submitAsync(() -> { + blah(data.name()); + }) + .exceptionally(e -> { + // 处理异常 + context.packetHandler().disconnect(Component.translatable("my_mod.configuration.failed", e.getMessage())); + return null; + }) + .thenAccept(v -> { + context.replyHandler().send(new AckPayload()); + }); +} +``` +其中 `onMyData` 是处理由服务器端配置任务发送的载荷的处理程序。 + +当服务器接收到此载荷时,将确认配置任务,并将执行下一个配置任务: +```java +public void onAck(AckPayload payload, ConfigurationPayloadContext context) { + context.taskCompletedHandler().onTaskCompleted(MyConfigurationTask.TYPE); +} +``` +其中 `onAck` 是处理由客户端发送的载荷的处理程序。 + +## 阻塞登录过程 +当配置未被确认时,服务器将永远等待,客户端将永远无法加入游戏。 +因此,始终确认配置任务非常重要,除非配置任务失败,然后您可以断开客户端的连接。 diff --git a/i18n/zh-Hans/docusaurus-plugin-content-docs/current/networking/entities.md b/i18n/zh-Hans/docusaurus-plugin-content-docs/current/networking/entities.md new file mode 100644 index 000000000..07af05890 --- /dev/null +++ b/i18n/zh-Hans/docusaurus-plugin-content-docs/current/networking/entities.md @@ -0,0 +1,29 @@ +# 实体 + +除了常规的网络消息外,还提供了各种其他系统来处理实体数据的同步。 + +## 生成数据 +自 1.20.2 版本以来,Mojang 引入了 Bundle 数据包的概念,用于将实体生成数据包一起发送。 +这允许更多的数据与生成数据包一起发送,并且使得数据的发送更有效率。 + +您可以通过实现以下接口向 Forge 发送的生成数据包添加额外数据。 + +### IEntityWithComplexSpawn +如果您的实体具有在客户端上需要但随时间不变的数据,则可以使用此接口将其添加到实体生成数据包中。`#writeSpawnData` 和 `#readSpawnData` 控制如何将数据编码到/从网络缓冲区中解码。 +或者,您可以重写 `sendPairingData(...)` 方法,该方法在实体与客户端配对时调用。此方法在服务器上调用,可用于在生成数据包的同一捆绑包中向客户端发送附加负载。 + +## 动态数据 +### 数据参数 + +这是将实体数据从服务器同步到客户端的主要原始系统。因此,有许多可用于参考的原始示例。 + +首先,您需要为要保持同步的数据获取一个 `EntityDataAccessor`。这应该作为您的实体类中的 `static final` 字段存储,通过调用 `SynchedEntityData#defineId` 并传递实体类和该类型数据的序列化程序来获得。可用的序列化程序实现可以在 `EntityDataSerializers` 类的静态常量中找到。 + +:::caution +您应该 __仅__ 为您自己的实体创建数据参数,在该实体类内部。 +为您无法控制的实体添加参数可能会导致用于通过网络发送该数据的 ID 不同步,导致难以调试的崩溃。 +::: + +然后,重写 `Entity#defineSynchedData` 并为每个数据参数调用 `this.entityData.define(...)`,传递参数和要使用的初始值。记得始终先调用 `super` 方法! + +然后,您可以通过实体的 `entityData` 实例获取和设置这些值。所做的更改将自动同步到客户端。 diff --git a/i18n/zh-Hans/docusaurus-plugin-content-docs/current/networking/index.md b/i18n/zh-Hans/docusaurus-plugin-content-docs/current/networking/index.md new file mode 100644 index 000000000..6a0cb4b8c --- /dev/null +++ b/i18n/zh-Hans/docusaurus-plugin-content-docs/current/networking/index.md @@ -0,0 +1,18 @@ +# 网络通信 + +服务器和客户端之间的通信是成功实现模组的基础。 + +网络通信有两个主要目标: + +1. 确保客户端视图与服务器视图“同步” + - 在坐标 (X, Y, Z) 处的花刚刚生长了 +2. 让客户端告诉服务器有关玩家状态变化的信息 + - 玩家按下了一个键 + +实现这些目标最常见的方式是在客户端和服务器之间传递消息。这些消息通常会被结构化,按特定的排列方式包含数据,以便于发送和接收。 + +NeoForge 提供了一种技术来促进通信,主要建立在 [netty][] 之上。 +通过监听 `RegisterPayloadHandlerEvent` 事件,可以注册特定类型的 [负载][payloads]、其读取器和处理函数到注册器中。 + +[netty]: https://netty.io "Netty Website" +[payloads]: ./payload.md "Registering custom Payloads" diff --git a/i18n/zh-Hans/docusaurus-plugin-content-docs/current/networking/payload.md b/i18n/zh-Hans/docusaurus-plugin-content-docs/current/networking/payload.md new file mode 100644 index 000000000..b549ec2e4 --- /dev/null +++ b/i18n/zh-Hans/docusaurus-plugin-content-docs/current/networking/payload.md @@ -0,0 +1,98 @@ +# 注册负载 + +负载是在客户端和服务器之间发送任意数据的一种方法。它们使用从 `RegisterPayloadHandlerEvent` 事件中获取的 `IPayloadRegistrar` 进行注册,该事件可以为给定的命名空间检索到。 +```java +@SubscribeEvent +public static void register(final RegisterPayloadHandlerEvent event) { + final IPayloadRegistrar registrar = event.registrar("mymod"); +} +``` + +假设我们想要发送以下数据: +```java +public record MyData(String name, int age) {} +``` + +然后,我们可以实现 `CustomPacketPayload` 接口来创建一个可用于发送和接收此数据的负载。 +```java +public record MyData(String name, int age) implements CustomPacketPayload { + + public static final ResourceLocation ID = new ResourceLocation("mymod", "my_data"); + + public MyData(final FriendlyByteBuf buffer) { + this(buffer.readUtf(), buffer.readInt()); + } + + @Override + public void write(final FriendlyByteBuf buffer) { + buffer.writeUtf(name()); + buffer.writeInt(age()); + } + + @Override + public ResourceLocation id() { + return ID; + } +} +``` +从上面的示例中可以看出,`CustomPacketPayload` 接口要求我们实现 `write` 和 `id` 方法。`write` 方法负责将数据写入缓冲区,而 `id` 方法负责返回此负载的唯一标识符。 +然后,我们还需要一个读取器来稍后进行注册,在这里我们可以使用自定义构造函数从缓冲区中读取数据。 + +最后,我们可以使用注册器注册此负载: +```java +@SubscribeEvent +public static void register(final RegisterPayloadHandlerEvent event) { + final IPayloadRegistrar registrar = event.registrar("mymod"); + registrar.play(MyData.ID, MyData::new, handler -> handler + .client(ClientPayloadHandler.getInstance()::handleData) + .server(ServerPayloadHandler.getInstance()::handleData)); +} +``` +分解上面的代码,我们可以注意到几件事情: +- 注册器有一个 `play` 方法,可用于注册在游戏播放阶段发送的负载。 + - 此代码中未显示的方法还有 `configuration` 和 `common`,但它们也可以用于为配置阶段注册负载。`common` 方法可用于同时为配置和游戏播放阶段注册负载。 +- `MyData` 的构造函数被用作方法引用,以创建负载的读取器。 +- 注册方法的第三个参数是一个回调,用于注册负载到达客户端或服务器端时的处理程序。 + - `client` 方法用于在负载到达客户端时注册处理程序。 + - `server` 方法用于在负载到达服务器端时注册处理程序。 + - 在注册器本身上还有一个次要的注册方法 `play`,它接受客户端和服务器端的处理程序,可以用于同时为两端注册处理程序。 + +现在我们已经注册了负载,我们需要实现一个处理程序。 +在此示例中,我们将特别关注客户端端处理程序,但服务器端处理程序非常相似。 +```java +public class ClientPayloadHandler { + + private static final ClientPayloadHandler INSTANCE = new ClientPayloadHandler(); + + public static ClientPayloadHandler getInstance() { + return INSTANCE; + } + + public void handleData(final MyData data, final PlayPayloadContext context) { + // 处理数据,在网络线程上 + blah(data.name()); + + // 在主游戏线程上处理数据 + context.workHandler().submitAsync(() -> { + blah(data.age()); + }) + .exceptionally(e -> { + // 处理异常 + context.packetHandler().disconnect(Component.translatable("my_mod.networking.failed", e.getMessage())); + return null; + }); + } +} +``` +这里需要注意几件事情: +- 此处处理方法获取负载和上下文对象。上下文对象对于播放和配置阶段是不同的,如果注册了一个通用负载,则需要接受两个上下文的超类型。 +- 负载方法的处理程序在网络线程上调用,因此重要的是在此处进行所有繁重的工作,而不是阻塞主游戏线程。 +- 如果要在主游戏线程上运行代码,可以使用上下文的 `workHandler` 提交任务到主线程。 + - `workHandler` 将返回一个在主线程上完成的 `CompletableFuture`,可以用于提交任务到主线程。 + - 注意:返回的是 `CompletableFuture`,这意味着您可以将多个任务链接在一起,并在单个位置处理异常。 + - 如果不在 `CompletableFuture` 中处理异常,则它将被忽略,**您将不会收到任何通知**。 + +现在您知道了如何为您的模组促进客户端和服务器之间的通信,您可以开始实现自己的负载。 +有了自己的负载,您就可以使用它们来配置客户端和服务器,使用[配置任务][]。 + +[配置任务]: ./configuration-tasks.md diff --git a/i18n/zh-Hans/docusaurus-plugin-content-docs/current/resources/client/_category_.json b/i18n/zh-Hans/docusaurus-plugin-content-docs/current/resources/client/_category_.json new file mode 100644 index 000000000..f785d798d --- /dev/null +++ b/i18n/zh-Hans/docusaurus-plugin-content-docs/current/resources/client/_category_.json @@ -0,0 +1,3 @@ +{ + "label": "Client" +} \ No newline at end of file diff --git a/i18n/zh-Hans/docusaurus-plugin-content-docs/current/resources/client/i18n.md b/i18n/zh-Hans/docusaurus-plugin-content-docs/current/resources/client/i18n.md new file mode 100644 index 000000000..54bb13b3c --- /dev/null +++ b/i18n/zh-Hans/docusaurus-plugin-content-docs/current/resources/client/i18n.md @@ -0,0 +1,181 @@ +# I18n 与 L10n + +I18n(国际化的简称)是一种设计程序以适应多种语言的方法。L10n(本地化的简称)是将文本翻译成用户语言的过程。Minecraft使用`Component`来实现这些功能。 + +## `Component`组件 + +`Component`是有元数据的文本片段,元数据包括如文本格式化等内容。它可以通过以下方式之一创建(以下都是`Component`接口中的静态方法): + +| 方法 | 描述 | +|-----------------|--------------------------------------------------------------------------------------------------| +| `empty` | 创建一个空组件。 | +| `literal` | 创建一个具有给定文本的组件,并直接显示该文本,而不进行翻译。 | +| `nullToEmpty` | 对于给定的null创建一个空组件,否则创建一个文字组件。 | +| `translatable` | 创建一个可翻译的组件。给定的字符串随后会被解析为翻译键(见下文)。 | +| `keybind` | 创建一个包含给定按键绑定的(翻译后的)显示名称的组件。 | +| `nbt` | 创建一个表示给定路径上的[NBT][nbt]的组件。 | +| `score` | 创建一个包含记分板目标值的组件。 | +| `selector` | 创建一个组件,包含给定[实体选择器][selector]的实体名称列表。 | + +`Component.translatable()`还有一个可变参数,接受字符串插值元素。这与Java的`String#format`类似,但总是使用`%s`代替`%i`、`%d`、`%f`和任何其他格式说明符,在需要时调用`#toString()`。 + +每个`Component`都可以使用`#getString()`来解析。解析通常是惰性的,这意味着服务器可以指定一个`Component`,将其发送给客户端,然后客户端会各自解析`Component`(不同语言可能导致不同的文本)。Minecraft中的许多地方也会直接接受`Component`并为你解决解析问题。 + +:::caution +永远不要让服务器翻译`Component`。总是将`Component`发送到客户端并在那里解析它们。 +::: + +### 文本格式化 + +`Component`可以使用`Style`进行格式化。`Style`是不可变的,修改时会创建一个新的`Style`对象,因此允许一次创建,然后根据需要重复使用。 + +`Style.EMPTY`通常可以用作工作的基础。可以通过`Style#applyTo(Style other)`合并多个`Style`,该方法返回一个新的`Style`,它从被`applyTo()`方法调用的`Style`中取得设置,除非相应的设置不存在,在这种情况下,则使用作为参数传入的`Style`中的设置。然后可以这样将`Style`应用到组件上: + +```java +Component text = Component.literal("Hello World!"); + +// Create a new style. +Style blue = Style.EMPTY.withColor(0x0000FF); +// Styles use a builder-like pattern. +Style blueItalic = Style.EMPTY.withColor(0x0000FF).withItalic(true); +// Besides italic, we can also make styles bold, underlined, strikethrough, or obfuscated. +Style bold = Style.EMPTY.withBold(true); +Style underlined = Style.EMPTY.withUnderlined(true); +Style strikethrough = Style.EMPTY.withStrikethrough(true); +Style obfuscated = Style.EMPTY.withObfuscated(true); +// Let's merge some styles together! +Style merged = blueItalic.applyTo(bold).applyTo(strikethrough); + +// Set a style on a component. +text.setStyle(merged); +// Merge a new style into it. +text.withStyle(Style.EMPTY.withColor(0xFF0000)); +``` + +另一个更复杂的格式化选项是使用点击和悬停事件: + +```java +// We have a total of 6 options for a click event, and a total of 3 options for a hover event. +ClickEvent clickEvent; +HoverEvent hoverEvent; + +// Opens the given URL in your default browser when clicked. +clickEvent = new ClickEvent(ClickEvent.Action.OPEN_URL, "http://example.com/"); +// Opens the given file when clicked. For security reasons, this cannot be sent from a server. +clickEvent = new ClickEvent(ClickEvent.Action.OPEN_FILE, "C:/example.txt"); +// Runs the given command when clicked. +clickEvent = new ClickEvent(ClickEvent.Action.RUN_COMMAND, "/gamemode creative"); +// Suggests the given command in the chat when clicked. +clickEvent = new ClickEvent(ClickEvent.Action.SUGGEST_COMMAND, "/gamemode creative"); +// Changes a book page when clicked. Irrelevant outside of a book screen context. +clickEvent = new ClickEvent(ClickEvent.Action.CHANGE_PAGE, "1"); +// Copies the given text to the clipboard. +clickEvent = new ClickEvent(ClickEvent.Action.COPY_TO_CLIPBOARD, "Hello World!"); + +// Shows the given component when hovered. May be formatted as well. +// Keep in mind that click or hover events won't work in a hover tooltip for obvious reasons. +hoverEvent = new HoverEvent(HoverEvent.Action.SHOW_TEXT, Component.literal("Hello World!")); +// Shows a complete tooltip of the given item stack when hovered. +// See the possible constructors of ItemStackInfo. +hoverEvent = new HoverEvent(HoverEvent.Action.SHOW_ITEM, new HoverEvent.ItemStackInfo(...)); +// Shows a complete tooltip of the given entity when hovered. +// See the possible constructors of EntityTooltipInfo. +hoverEvent = new HoverEvent(HoverEvent.Action.SHOW_ENTITY, new HoverEvent.EntityTooltipInfo(...)); + +// Apply the events to a style. +Style clickable = Style.EMPTY.withClickEvent(clickEvent); +Style hoverable = Style.EMPTY.withHoverEvent(hoverEvent); +``` + +## 语言文件 + +语言文件是包含从翻译键(见下文)到实际名称的映射的JSON文件。它们位于`assets//lang/language_name.json`。例如,对于一个id为`examplemod`的mod,US English的翻译将位于`assets/examplemod/lang/en_us.json`。可以在[这里][mcwikilang]找到Minecraft支持的所有语言的完整列表。 + +一个语言文件通常看起来是这样的: + +```json +{ + "translation.key.1": "Translation 1", + "translation.key.2": "Translation 2" +} +``` + +### 翻译键 + +翻译键是在翻译中使用的键。在许多情况下,它们遵循格式`registry.modid.name`。例如,一个id为`examplemod`的mod提供了一个名为`example_block`的方块,可能会希望为键`block.examplemod.example_block`提供翻译。然而,你基本上可以使用任何字符串作为翻译键。 + +如果选定语言中没有与翻译键相关联的翻译,游戏将回退到US English(`en_us`),除非已经选择了US English。如果US English也没有翻译,翻译将静默失败,并代之以显示原始翻译键。 + +Minecraft的一些地方为你提供了获取翻译键的辅助方法。例如,方块和物品都提供了`#getDescriptionId`方法。这些不仅可以被查询,而且在需要时也可以被覆盖。一个常见的用例是,根据它们的[NBT][nbt]值有不同名称的物品。这些通常会覆盖带有[`ItemStack`][itemstack]参数的`#getDescriptionId`变体,并根据堆栈的NBT返回不同的值。另一个常见的用例是`BlockItem`,它覆盖该方法以使用关联方块的翻译键。 + +:::tip +翻译键的唯一目的是用于本地化。不要用它们来处理游戏逻辑,游戏逻辑应该使用[注册名称][regname]。 +::: + +### 翻译Mod元数据 + +从NeoForge 20.4.179版本开始,翻译文件可以使用以下键(其中`modid`需替换为实际的mod id)覆盖[mod信息][modstoml]的某些部分: + +| | 翻译键 | 覆盖内容 | +|--------------|--------------------------------------------|-------------------------------------------------------------------------------| +| 显示名称 | `fml.menu.mods.info.displayname.modid` | 可在`[[mods]]`部分中放置一个名为`displayName`的字段代替。 | +| 描述 | `fml.menu.mods.info.description.modid` | 可在`[[mods]]`部分中放置一个名为`description`的字段代替。 | + +### 数据生成 + +语言文件可以[通过数据生成][datagen]来创建。要这样做,请扩展`LanguageProvider`类并在`addTranslations()`方法中添加你的翻译: + +```java +public class MyLanguageProvider extends LanguageProvider { + public MyLanguageProvider(PackOutput output) { + super( + // Provided by the GatherDataEvent. + output, + // Your mod id. + "examplemod", + // The locale to use. You may use multiple language providers for different locales. + "en_us" + ); + } + + @Override + protected void addTranslations() { + // Adds a translation with the given key and the given value. + add("translation.key.1", "Translation 1"); + + // Helpers are available for various common object types. Every helper has two variants: an add() variant + // for the object itself, and an addTypeHere() variant that accepts a supplier for the object. + // The different names for the supplier variants are required due to generic type erasure. + // All following examples assume the existence of the values as suppliers of the needed type. + + // Adds a block translation. + add(MyBlocks.EXAMPLE_BLOCK.get(), "Example Block"); + addBlock(MyBlocks.EXAMPLE_BLOCK, "Example Block"); + // Adds an item translation. + add(MyItems.EXAMPLE_ITEM.get(), "Example Item"); + addItem(MyItems.EXAMPLE_ITEM, "Example Item"); + // Adds an item stack translation. This is mainly for items that have NBT-specific names. + add(MyItems.EXAMPLE_ITEM_STACK.get(), "Example Item"); + addItemStack(MyItems.EXAMPLE_ITEM_STACK, "Example Item"); + // Adds an entity type translation. + add(MyEntityTypes.EXAMPLE_ENTITY_TYPE.get(), "Example Entity"); + addEntityType(MyEntityTypes.EXAMPLE_ENTITY_TYPE, "Example Entity"); + // Adds an enchantment translation. + add(MyEnchantments.EXAMPLE_ENCHANTMENT.get(), "Example Enchantment"); + addEnchantment(MyEnchantments.EXAMPLE_ENCHANTMENT, "Example Enchantment"); + // Adds a mob effect translation. + add(MyMobEffects.EXAMPLE_MOB_EFFECT.get(), "Example Effect"); + addEffect(MyMobEffects.EXAMPLE_MOB_EFFECT, "Example Effect"); + } +} +``` + +然后,在`GatherDataEvent`中像注册其他提供者一样注册这个提供者。 + +[datagen]: ../index.md#data-generation +[itemstack]: ../../items/index.md#itemstacks +[mcwikilang]: https://minecraft.wiki/w/Language +[modstoml]: ../../gettingstarted/modfiles.md#modstoml +[nbt]: ../../datastorage/nbt.md +[regname]: ../../concepts/registries.md +[selector]: https://minecraft.wiki/w/Target_selectors diff --git a/i18n/zh-Hans/docusaurus-plugin-content-docs/current/resources/client/models/bakedmodel.md b/i18n/zh-Hans/docusaurus-plugin-content-docs/current/resources/client/models/bakedmodel.md new file mode 100644 index 000000000..214e08d5f --- /dev/null +++ b/i18n/zh-Hans/docusaurus-plugin-content-docs/current/resources/client/models/bakedmodel.md @@ -0,0 +1,138 @@ +# 烘焙模型 + +`BakedModel` 是带有纹理的形状在代码中的表示形式。它们可以来自多个来源,例如通过 `UnbakedModel#bake`(默认模型加载器)或 `IUnbakedGeometry#bake`([自定义模型加载器][modelloader])的调用生成。一些[方块实体渲染器][ber]也使用烘焙模型。模型的复杂度没有限制。 + +模型存储在 `ModelManager` 中,可以通过 `Minecraft.getInstance().modelManager` 访问。然后,您可以调用 `ModelManager#getModel` 通过其 [`ResourceLocation`][rl] 或 [`ModelResourceLocation`][mrl] 获取特定模型。模组基本上总是重用之前自动加载和烘焙的模型。 + +## `BakedModel` 的方法 + +### `getQuads` + +烘焙模型最重要的方法是 `getQuads`。此方法负责返回 `BakedQuad` 的列表,这些列表随后可以发送到 GPU。在建模程序中(以及大多数其他游戏中),四边形类似于三角形,然而由于 Minecraft 通常关注于方形,开发者选择使用四边形(4个顶点)而非三角形(3个顶点)进行渲染。`getQuads` 有五个参数可用: + +- `BlockState`:正在渲染的[方块状态][blockstate]。可能为空,表示正在渲染物品。 +- `Direction`:正在剔除的面的方向。可能为空,这意味着应该返回不可遮挡的四边形。 +- `RandomSource`:客户端绑定的随机源,您可以用于随机化。 +- `ModelData`:使用的额外模型数据。这可能包含方块实体渲染所需的额外数据。由 `BakedModel#getModelData` 提供。 +- `RenderType`:用于渲染方块的[渲染类型][rendertype]。可能为空,表示应返回此模型使用的所有渲染类型的四边形。否则,它是 `BakedModel#getRenderTypes` 返回的渲染类型之一(见下文)。 + +模型应该大量缓存。这是因为即使区块只有在其中一个方块更改时才重建,但此方法中的计算仍需尽可能快速,并且理想情况下由于该方法将被每个区块部分调用多次(每个给定模型使用的渲染类型的次数 * 每个模型使用的渲染类型的数量 * 每个区块部分 4096 个方块),因此应该进行大量缓存。此外,[方块实体渲染器][ber]或实体渲染器实际上可能每帧调用此方法几次。 + +### `applyTransform` 和 `getTransforms` + +`applyTransform` 允许在应用模型的透视变换时应用自定义逻辑,包括返回完全不同的模型。此方法由 NeoForge 添加,替代了 vanilla 的 `getTransforms()` 方法,后者只允许您自定义变换本身,而不能自定义应用方式。然而,`applyTransform` 的默认实现遵循 `getTransforms`,所以如果您只需要自定义变换,也可以重写 `getTransforms` 并完成它。`applyTransforms` 提供三个参数: + +- `ItemDisplayContext`:模型正在转换到的[透视][perspective]。 +- `PoseStack`:用于渲染的姿势堆栈。 +- `boolean`:是否使用修改后的值进行左手渲染,而不是默认的右手渲染;如果渲染的手是左手(副手,或在选项中启用 + +左手模式的主手),则为 `true`。 + +:::note +`applyTransform` 和 `getTransforms` 仅适用于物品模型。 +::: + +### 其他 + +您可能会重写和/或查询的其他 `BakedModel` 方法包括: + +| 签名 | 效果 | +|---|---| +| `TriState useAmbientOcclusion()` | 是否使用[环境光遮蔽][ao]。接受 `BlockState`、`RenderType` 和 `ModelData` 参数,并返回 `TriState`,这不仅允许强制禁用 AO,还允许强制启用 AO。有两个重载,每个都返回一个 `boolean` 参数,并只接受 `BlockState` 或无参数;这两个都已弃用,将被第一个变体取代。 | +| `boolean isGui3d()` | 此模型在 GUI 插槽中是否渲染为 3D 或平面。 | +| `boolean usesBlockLight()` | 在照亮模型时是否使用 3D 照明(`true`)或正面的平面照明(`false`)。 | +| `boolean isCustomRenderer()` | 如果为真,跳过正常渲染并调用关联的 [`BlockEntityWithoutLevelRenderer`][bewlr] 的 `renderByItem` 方法。如果为假,则通过默认渲染器渲染。 | +| `ItemOverrides getOverrides()` | 返回与此模型相关的 [`ItemOverrides`][itemoverrides]。这仅在物品模型中相关。 | +| `ModelData getModelData(BlockAndTintGetter, BlockPos, BlockState, ModelData)` | 返回用于模型的模型数据。此方法传递一个现有的 `ModelData`,如果方块有关联的方块实体,则为 `BlockEntity#getModelData()` 的结果,如果不是这种情况,则为 `ModelData.EMPTY`。此方法可用于需要模型数据但没有方块实体的方块,例如具有连接纹理的方块。 | +| `TextureAtlasSprite getParticleIcon(ModelData)` | 返回用于模型的粒子精灵。可以使用模型数据为不同的模型数据值使用不同的粒子精灵。NeoForge 添加,替换了没有参数的 vanilla `getParticleIcon()` 重载。 | +| `ChunkRenderTypeSet getRenderTypes(BlockState, RandomSource, ModelData)` | 返回包含用于渲染方块模型的渲染类型的 `ChunkRenderTypeSet`。`ChunkRenderTypeSet` 是一个支持集合的有序 `Iterable`。默认回退到从模型 JSON [获取渲染类型][rendertype]。仅用于方块模型,物品模型使用下面的重载。 | +| `List getRenderTypes(ItemStack, boolean)` | 返回包含用于渲染物品模型的渲染类型的 `List`。默认回退到正常的模型绑定渲染类型查找,这总是产生一个元素的列表。仅用于物品模型,方块模型使用上面的重载。 | + +## 透视 + +Minecraft 的渲染引擎总共识别 8 种透视类型(如果包括代码中的回退,则为 9 种)用于物品渲染。这些在模型 JSON 的 `display` 块中使用,并在代码中通过 `ItemDisplayContext` 枚举表示。 + +| 枚举值 | JSON 键 | 用途 | +|---|---|---| +| `THIRD_PERSON_RIGHT_HAND` | `"thirdperson_righthand"` | 第三人称右手(F5 视图,或其他玩家上) | +| `THIRD_PERSON_LEFT_HAND` | `"thirdperson_lefthand"` | 第三人称左手(F5 视图,或其他玩家上) | +| `FIRST_PERSON_RIGHT_HAND` | `"firstperson_righthand"` | 第一人称右手 | +| `FIRST_PERSON_LEFT_HAND` | `"firstperson_lefthand"` | 第一人 + +称左手 | +| `HEAD` | `"head"` | 玩家头部装备槽中(通常只能通过命令实现) | +| `GUI` | `"gui"` | 背包,玩家快捷栏 | +| `GROUND` | `"ground"` | 掉落物;注意,掉落物的旋转是由掉落物渲染器处理的,而非模型 | +| `FIXED` | `"fixed"` | 物品框 | +| `NONE` | `"none"` | 代码中的回退用途,不应在 JSON 中使用 | + +## `ItemOverrides` + +`ItemOverrides` 是一个类,提供了一种方式让烘焙模型处理 [`ItemStack`][itemstack] 的状态并通过 `#resolve` 方法返回新的烘焙模型。`#resolve` 有五个参数: + +- `BakedModel`:原始模型。 +- `ItemStack`:正在渲染的物品堆栈。 +- `ClientLevel`:模型正在其中渲染的级别。这应该只用于查询级别,不得以任何方式修改。可能为空。 +- `LivingEntity`:模型渲染在其上的实体。可能为空,例如在[方块实体渲染器][ber]中渲染时。 +- `int`:用于随机化的种子。 + +`ItemOverrides` 还持有模型的覆盖选项作为 `BakedOverride`。`BakedOverride` 的对象是模型的 [`overrides`][overrides] 块在代码中的表示形式。它可以由烘焙模型使用,根据其内容返回不同的模型。可以通过 `ItemOverrides#getOverrides()` 检索 `ItemOverrides` 实例的所有 `BakedOverride` 列表。 + +## `BakedModelWrapper` + +`BakedModelWrapper` 可用于修改已存在的 `BakedModel`。`BakedModelWrapper` 是 `BakedModel` 的一个子类,它在构造函数中接受另一个 `BakedModel`(“原始”模型),并默认将所有方法重定向到原始模型。然后,您的实现可以选择只覆盖某些方法,如下所示: + +```java +// 泛型参数可以是 BakedModel 的更具体的子类。 +// 如果是这样,构造参数必须匹配该类型。 +public class MyBakedModelWrapper extends BakedModelWrapper { + // 将原始模型传递给 super。 + public MyBakedModelWrapper(BakedModel originalModel) { + super(originalModel); + } + + // 在这里覆盖您想要的方法。如果需要,也可以访问 originalModel。 +} +``` + +编写您的模型包装类后,必须将包装器应用于它应影响的模型。在[客户端][sides]的[事件处理器][event]中为 `ModelEvent.ModifyBakingResult` 这样做: + +```java +@SubscribeEvent +public static void modifyBakingResult(ModelEvent.ModifyBakingResult event) { + // 对于方块模型 + event.getModels().computeIfPresent( + // 要修改的模型的模型资源位置。从 + // BlockModelShaper#stateToModelLocation 获取,参数为受影响的方块状态。 + BlockModelShaper.stateToModelLocation(MyBlocksClass.EXAMPLE_BLOCK.defaultBlockState()), + // 一个带有位置和原始模型为参数的 BiFunction,返回新模型。 + (location, model) -> new MyBakedModelWrapper(model); + ); + // 对于物品模型 + event.getModels().computeIfPresent( + // 要修改的模型的模型资源位置。 + new ModelResourceLocation("examplemod", "example_item", "inventory"), + // 一个带有位置和原始模型为参数的 BiFunction,返回新模型。 + (location, model) -> new MyBakedModelWrapper(model); + ); +} +``` + +:::warning +通常建议在可能的情况下使用[自定义模型加载器][modelloader + +]而不是在 `ModelEvent.ModifyBakingResult` 中包装烘焙模型。如果需要,自定义模型加载器也可以使用 `BakedModelWrapper`。 +::: + +[ao]: https://zh.wikipedia.org/wiki/环境光遮蔽 +[ber]: ../../../blockentities/ber.md +[bewlr]: ../../../items/bewlr.md +[blockstate]: ../../../blocks/states.md +[itemoverrides]: #itemoverrides +[itemstack]: ../../../items/index.md#itemstacks +[modelloader]: modelloaders.md +[mrl]: ../../../misc/resourcelocation.md#modelresourcelocations +[overrides]: index.md#overrides +[perspective]: #perspectives +[rendertype]: index.md#render-types +[rl]: ../../../misc/resourcelocation.md diff --git a/i18n/zh-Hans/docusaurus-plugin-content-docs/current/resources/client/models/datagen.md b/i18n/zh-Hans/docusaurus-plugin-content-docs/current/resources/client/models/datagen.md new file mode 100644 index 000000000..b84b30b07 --- /dev/null +++ b/i18n/zh-Hans/docusaurus-plugin-content-docs/current/resources/client/models/datagen.md @@ -0,0 +1,289 @@ +# 模型数据生成 + +与大多数JSON数据一样,方块和物品模型可以通过[数据生成][datagen]创建。由于物品和方块模型之间有一些共同之处,因此部分数据生成代码也是相同的。 + +## 模型数据生成类 + +### `ModelBuilder` + +每个模型都以某种形式的`ModelBuilder`开始 - 通常是`BlockModelBuilder`或`ItemModelBuilder`,具体取决于您要生成的内容。它包含模型的所有属性:其父级、纹理、元素、变换、加载器等。每个属性都可以通过一个方法设置: + +| 方法 | 效果 | +|----------------------------------------------------|----------------------------------------------------------------------------------------------------------| +| `#texture(String key, ResourceLocation texture)` | 添加具有给定键和给定纹理位置的纹理变量。有一个重载,其中第二个参数是`String`。 | +| `#renderType(ResourceLocation renderType)` | 设置渲染类型。有一个参数为`String`的重载。有效值的列表见`RenderType`类。 | +| `#ao(boolean ao)` | 设置是否使用[环境光遮蔽][ao]。 | +| `#guiLight(GuiLight light)` | 设置GUI光源。可以是`GuiLight.FRONT`或`GuiLight.SIDE`。 | +| `#element()` | 添加一个新的`ElementBuilder`(相当于向模型添加一个新[element][elements])。返回该`ElementBuilder`以便进一步修改。| +| `#transforms()` | 返回构建器的`TransformVecBuilder`,用于设置模型的`display`。 | +| `#customLoader(BiFunction customLoaderFactory)` | 使用给定的工厂使该模型使用[自定义加载器][custommodelloader],因此使用自定义加载器构建器。这会改变构建器类型,因此可能会使用不同的方法,这取决于加载器的实现。NeoForge提供了一些开箱即用的自定义加载器,详见链接文章(包括数据生成)。| + +:::tip +虽然可以通过数据生成创建复杂和详细的模型,但建议使用如[Blockbench][blockbench]之类的建模软件创建更复杂的模型,然后直接使用导出的模型或作为其他模型的父级。 +::: + +### `ModelProvider` + +方块和物品模型数据生成都利用了`ModelProvider`的子类,分别命名为`BlockModelProvider`和`ItemModelProvider`。虽然物品模型数据生成直接扩展`ItemModelProvider`,但方块模型数据生成使用`BlockStateProvider`基类,它内部有一个可以通过`BlockStateProvider#models()`访问的`BlockModelProvider`。此外,`BlockStateProvider`还有自己的内部`ItemModelProvider`,可通过`BlockStateProvider#itemModels()`访问。`ModelProvider`最重要的部分是`getBuilder(String path)`方法,它返回给定位置的`BlockModelBuilder`(或`ItemModelBuilder`)。 + +然而,`ModelProvider`还包含各种辅助方法。可能最重要的辅助方法是`withExistingParent(String name, ResourceLocation parent)`,它返回一个新的构建器(通过`getBuilder(name)`)并将给定的`ResourceLocation`设置为模型父级。另外两个非常常见的辅助器是`mcLoc(String name)`,返回带有命名空间`minecraft`和给定路径的`ResourceLocation`,以及`modLoc(String name)`,做同样的事情但使用提供者的mod id(通常是您的mod id)而不是`minecraft`。此外,它还提供了各种辅助方法,这些方法是`#withExistingParent`的快捷方式,用于常见事物如板条、楼梯、栅栏、门等。 + +### `ModelFile` + +最后一个重要的类是`ModelFile`。`ModelFile`是 + +磁盘上模型JSON的代码表示形式。`ModelFile`是一个抽象类,有两个内部子类`ExistingModelFile`和`UncheckedModelFile`。使用`ExistingFileHelper`验证`ExistingModelFile`的存在,而`UncheckedModelFile`被假定为存在而无需进一步检查。此外,`ModelBuilder`也被视为`ModelFile`。 + +## 方块模型数据生成 + +现在,要实际生成方块状态和方块模型文件,请扩展`BlockStateProvider`并重写`registerStatesAndModels()`方法。请注意,方块模型总是放置在`models/block`子文件夹中,但引用相对于`models`(即它们必须总是以`block/`为前缀)。在大多数情况下,选择众多预定义辅助方法之一是有意义的: + +```java +public class MyBlockStateProvider extends BlockStateProvider { + // 参数值由GatherDataEvent提供。 + public MyBlockStateProvider(PackOutput output, ExistingFileHelper existingFileHelper) { + // 用您自己的mod id替换"examplemod"。 + super(output, "examplemod", existingFileHelper); + } + + @Override + protected void registerStatesAndModels() { + // 占位符,其用法应替换为实际值。请参阅上文了解如何使用模型构建器, + // 以及下文了解模型构建器提供的辅助方法。 + ModelFile exampleModel = models().withExistingParent("minecraft:block/cobblestone"); + Block block = MyBlocksClass.EXAMPLE_BLOCK.get(); + ResourceLocation exampleTexture = modLoc("block/example_texture"); + ResourceLocation bottomTexture = modLoc("block/example_texture_bottom"); + ResourceLocation topTexture = modLoc("block/example_texture_top"); + ResourceLocation sideTexture = modLoc("block/example_texture_front"); + ResourceLocation frontTexture = modLoc("block/example_texture_front"); + + // 创建一个简单的方块模型,每个面都使用相同的纹理。 + // 纹理必须位于assets//textures/block/.png, + // 其中分别是方块的注册名的命名空间和路径。 + // 用于大多数(完整)方块,如木板、圆石或砖块。 + simpleBlock(block); + // 接受要使用的模型文件的重载。 + simpleBlock(block, exampleModel); + // 接受一个或多个(变量参数)ConfiguredModel对象的重载。 + // 有关ConfiguredModel的更多信息,请参见下文。 + simpleBlock(block, ConfiguredModel.builder().build()); + // 添加一个带有方块名称的物品模型文件,以给定的模型文件为父级,供方块物品使用。 + simpleBlockItem(block, exampleModel); + // 调用#simpleBlock()(模型文件重载)和#simpleBlockItem的简写。 + simpleBlockWithItem(block, exampleModel); + + // 添加一个木材方块模型。需要两个纹理,位于assets//textures/block/.png和 + // assets//textures/block/_top.png,分别引用侧面和顶部纹理。 + // 请注意,这里的方块输入仅限于RotatedPillarBlock,这是原版木材使用的类。 + logBlock(block); + // 类似于#logBlock,但纹理命名为_side.png和_end.png而不是 + // .png和_top.png。由石英柱和类似方块使用。 + // 有一个重载允许您指定不同的纹理基本名称,然后根据需要后缀为_side和_end, + // 一个重载允许您指定两个资源位置 + // 为侧面和端部纹理,以及一个重载允许指定侧面和端部模型文件。 + axisBlock(block); + // #logBlock和#axisBlock的变体,另外允许指定渲染类型。 + // 有字符串和资源位置变体用于渲染类型, + // 与#logBlock和#axisBlock的所有变体结合使用。 + logBlockWithRenderType(block, "minecraft:cutout"); + axisBlockWithRenderType(block, mcLoc("cutout_mipped")); + + // 指定一个具有侧面纹理、前面纹理和顶部纹理的水平可旋转方块模型。 + // 底部将使用侧面纹理。如果不需要前面或顶部纹理, + // 只需传入侧面纹理两次。例如,用于熔炉和类似方块。 + horizontalBlock(block, sideTexture, frontTexture, topTexture); + // 指定一个将根据需要旋转的模型文件的水平可旋转方块模型。 + // 有一个重载,而不是模型文件接受一个Function, + // 允许不同的旋转使用不同的模型。例如,用于石切机。 + horizontalBlock(block, exampleModel); + // 指定一个附着在面上的水平可旋转方块模型,例如按钮或拉杆。 + // 考虑到在地面和天花板上放置方块,并相应旋转它们。 + // 像#horizontalBlock一样,有一个重载接受一个Function。 + horizontalFaceBlock(block, exampleModel); + // 类似于#horizontalBlock,但用于可向上和向下旋转的方块。 + // 同样,有一个重载接受一个Function。 + directionalBlock(block, exampleModel); + } +} +``` + +另外,`BlockStateProvider`中存在以下常见方块模型的辅助方法: + +- 楼梯 +- 板条 +- 按钮 +- 压力板 +- 标志 +- 栅栏 +- 栅栏门 +- 墙 +- 窗格 +- 门 +- 活板门 + +在某些情况下,方块状态不需要特殊处理,但模型需要。在这种情况下,可通过`BlockStateProvider#models()`访问的`BlockModelProvider`提供了一些额外的辅助方法,所有这些方法都接受第一个参数为名称,并且大多数与完整立方体有关。它们通常用作例如`simpleBlock`的模型文件参数。辅助方法包括支持`BlockStateProvider`中的方法,以及: + +- `withExistingParent`: 前面已经提到,此方法返回一个带有给定父级的新模型构建器。父级必须已经存在或在模型之前创建。 +- `getExistingFile`: 在模型提供者的`ExistingFileHelper`中执行查找,如果存在则返回相应的`ModelFile`,否则抛出`IllegalStateException`。 +- `singleTexture`: 接受一个父级和一个纹理位置,返回一个带有给定父级的模型,并将纹理变量`texture`设置为给定的纹理位置。 +- `sideBottomTop`: 接受一个父级和三个纹理位置,返回一个模型,其侧面、底部和顶部纹理设置为三个纹理位置。 +- `cube`: 接受六个纹理资源位置,分别用于六个面,返回一个完整立方体模型,其六个面设置为六个纹理。 +- `cubeAll`: 接受一个纹理位置,返回一个完整立方体模型,将给定纹理应用于所有六个面。如果愿意,可以将其视为`singleTexture`和`cube`的混合体。 +- `cubeTop`: 接受两个纹理位置,返回一个完整立方体模型,第一个纹理 + +应用于侧面和底部,第二个纹理应用于顶部。 +- `cubeBottomTop`: 接受三个纹理位置,返回一个完整立方体模型,其侧面、底部和顶部纹理设置为三个纹理位置。如果愿意,可以将其视为`cube`和`sideBottomTop`的混合体。 +- `cubeColumn`和`cubeColumnHorizontal`: 接受两个纹理位置,返回一个“立立”或“横卧”的柱状立方体模型,其侧面和端部纹理设置为两个纹理位置。由`BlockStateProvider#logBlock`、`BlockStateProvider#axisBlock`及其变体使用。 +- `orientable`: 接受三个纹理位置,返回一个带有“前面”纹理的立方体。这三个纹理位置分别是侧面、前面和顶部纹理。 +- `orientableVertical`: `orientable`的变体,省略了顶部参数,改为使用侧面参数。 +- `orientableWithBottom`: `orientable`的变体,其具有一个在前面和顶部参数之间的底部纹理的第四参数。 +- `crop`: 接受一个纹理位置,返回一个带有给定纹理的类似作物的模型,如四种原版作物所使用的。 +- `cross`: 接受一个纹理位置,返回一个带有给定纹理的十字模型,如花、树苗和许多其他植被方块所使用的。 +- `torch`: 接受一个纹理位置,返回一个带有给定纹理的火把模型。 +- `wall_torch`: 接受一个纹理位置,返回一个带有给定纹理的壁挂火把模型(壁挂火把是与立火把不同的方块)。 +- `carpet`: 接受一个纹理位置,返回一个带有给定纹理的地毯模型。 + +最后,别忘了在事件中注册您的方块状态提供者: + +```java +@SubscribeEvent +public static void gatherData(GatherDataEvent event) { + DataGenerator generator = event.getGenerator(); + PackOutput output = generator.getPackOutput(); + ExistingFileHelper existingFileHelper = event.getExistingFileHelper(); + + // 其他提供者在这里 + generator.addProvider( + event.includeClient(), + new MyBlockStateProvider(output, existingFileHelper) + ); +} +``` + +### `ConfiguredModel.Builder` + +如果默认辅助工具不能满足您的需求,您也可以使用`ConfiguredModel.Builder`直接构建模型对象,然后在`VariantBlockStateBuilder`中使用它们构建`variants`块状态文件,或在`MultiPartBlockStateBuilder`中构建`multipart`块状态文件: + +```java +// 创建一个ConfiguredModel.Builder。或者,您可以使用下面演示的方式之一 +// (VariantBlockStateBuilder.PartialBlockstate#modelForState或MultiPartBlockStateBuilder#part)在适用的情况下。 +ConfiguredModel.Builder builder = ConfiguredModel.builder() +// 使用一个模型文件。如前所述,可以是ExistingModelFile、UncheckedModelFile, +// 或某种类型的ModelBuilder。参见上文了解如何使用ModelBuilder。 + .modelFile(models().withExistingParent("minecraft:block/cobblestone")) + // 设置绕x轴和y轴的旋转。 + .rotationX(90) + .rotationY(180) + // 设置uv锁定。 + .uvlock(true) + // 设置权重。 + .weight(5); +// 构建配置模型。返回类型是一个数组 +// 以考虑同一块状态中可能有多个模型。 +ConfiguredModel[] model = builder.build(); + +// 获取一个变体块状态构建器。 +VariantBlockStateBuilder variantBuilder = getVariantBuilder(MyBlocksClass.EXAMPLE_BLOCK.get()); +// 创建一个部分状态并设置属性。 +VariantBlockStateBuilder.PartialBlockstate partialState = variantBuilder.partialState + +(); +// 为部分块状态添加一个或多个模型。模型是变量参数。 +variantBuilder.addModels(partialState, + // 至少指定一个ConfiguredModel.Builder,如上所见。通过#modelForState创建。 + partialState.modelForState() + .modelFile(models().withExistingParent("minecraft:block/cobblestone")) + .uvlock(true) +); +// 或者,forAllStates(Function)为每个可能的状态创建一个模型。 +// 传递的函数将为每个可能的状态调用一次。 +variantBuilder.forAllStates(state -> { + // 根据状态的属性返回一个ConfiguredModel。 + // 例如,以下代码将根据方块的水平旋转旋转模型。 + return ConfiguredModel.builder() + .modelFile(models().withExistingParent("minecraft:block/cobblestone")) + .rotationY((int) state.getValue(BlockStateProperties.HORIZONTAL_FACING).toYRot()) + .build(); +}); + +// 获取一个多部分块状态构建器。 +MultiPartBlockStateBuilder multipartBuilder = getMultipartBuilder(MyBlocksClass.EXAMPLE_BLOCK.get()); +// 添加一个新部分。从.part()开始,以.end()结束。 +multipartBuilder.addPart(multipartBuilder.part() + // 步骤一:构建模型。multipartBuilder.part()返回一个ConfiguredModel.Builder, + // 意味着上面看到的所有方法都可以在这里使用。 + .modelFile("minecraft:block/cobblestone") + // 调用.addModel()。现在模型已构建,我们可以进入步骤二:添加部分数据。 + .addModel() + // 为部分添加条件。需要一个属性 + // 和至少一个属性值;属性值是变量参数。 + .condition(BlockStateProperties.FACING, Direction.NORTH, Direction.SOUTH) + // 将多部分条件设置为或运算而不是默认的与运算。 + .useOr() + // 创建一个嵌套条件组。 + .nestedGroup() + // 向嵌套组添加一个条件。 + .condition(BlockStateProperties.FACING, Direction.NORTH) + // 仅将这个条件组设置为或运算而不是与运算。 + .useOr() + // 创建另一个嵌套条件组。嵌套组的数量没有限制。 + .nestedGroup() + // 结束嵌套条件组,返回到拥有的部分构建器或条件组级别。 + // 这里调用两次,因为我们当前有两个嵌套组。 + .endNestedGroup() + .endNestedGroup() + // 结束部分构建器并将生成的部分添加到多部分构建器中。 + .end() +); +``` + +## 物品模型数据生成 + +生成物品模型相对简单得多,这主要是因为我们直接在`ItemModelProvider`上操作,而不是使用像`BlockStateProvider`这样的中间类,这当然是因为物品模型没有与方块状态文件等价的文件,而是直接使用。 + +与上面类似,我们创建一个类并让它扩展基础提供者,在这种情况下是`ItemModelProvider`。由于我们直接在`ModelProvider`的子类中,所有的`models()`调用都变成了`this`(或被省略)。 + +```java +public class MyItemModelProvider extends ItemModelProvider { + public MyItemModelProvider(PackOutput output, ExistingFileHelper existingFileHelper) { + super(output, "examplemod", existingFileHelper); + } + + @Override + protected void registerModels() { + // 方块物品通常使用其相应的方块模型作为父级。 + withExistingParent(MyItemsClass.EXAMPLE_BLOCK_ITEM.get(), modLoc("block/example_block")); + // 物品通常使用一个简单的父级和一个纹理。最常见的父级是item/generated和item/handheld。 + // 在这个例子中,物品纹理位于assets/examplemod/textures/item/example_item.png。 + // 如果您想要一个更复杂的模型,您可以使用 getBuilder(),然后从中进行工作,就像使用块模型一样。 + withExistingParent(MyItemsClass.EXAMPLE_ITEM.get(), mcLoc("item/ generated")).texture("layer0", "item/example_item"); + // 上面的行很常见,因此有一个快捷方式。 请注意项目注册表名称和 + // 相对于纹理/项目的纹理路径必须匹配。 + basicItem(MyItemsClass.EXAMPLE_ITEM.get()); + } +} +``` + +与所有数据提供者一样,不要忘记将您的提供者注册到该事件: + +```java +@SubscribeEvent +public static void gatherData(GatherDataEvent event) { + DataGenerator generator = event.getGenerator(); + PackOutput output = generator.getPackOutput(); + ExistingFileHelper existingFileHelper = event.getExistingFileHelper(); + + // other providers here + generator.addProvider( + event.includeClient(), + new MyItemModelProvider(output, existingFileHelper) + ); +} +``` + +[ao]: https://en.wikipedia.org/wiki/Ambient_occlusion +[blockbench]: https://www.blockbench.net +[custommodelloader]: modelloaders.md#datagen +[datagen]: ../../index.md#data-generation +[elements]: index.md#elements diff --git a/i18n/zh-Hans/docusaurus-plugin-content-docs/current/resources/client/models/index.md b/i18n/zh-Hans/docusaurus-plugin-content-docs/current/resources/client/models/index.md new file mode 100644 index 000000000..04f3b3324 --- /dev/null +++ b/i18n/zh-Hans/docusaurus-plugin-content-docs/current/resources/client/models/index.md @@ -0,0 +1,305 @@ +模型是JSON文件,确定方块或物品的视觉形状和纹理。模型由立方体元素组成,每个元素都有自己的大小,然后每个面都被分配一个纹理。 + +每个物品通过其注册名称被分配一个物品模型。例如,注册名称为 `examplemod:example_item` 的物品将被分配到 `assets/examplemod/models/item/example_item.json` 中的模型。对于方块来说,情况稍微复杂一些,因为它们首先被分配一个方块状态文件。更多信息请参见[下文][bsfile]。 + +## 规范 + +_另请参阅:[Minecraft Wiki][mcwiki]上的[模型][mcwikimodel]_ + +模型是一个具有以下可选属性的JSON文件: + +- `loader`:NeoForge添加的。设置自定义模型加载器。有关更多信息,请参阅[自定义模型加载器][custommodelloader]。 +- `parent`:设置父模型,格式为相对于 `models` 文件夹的[资源位置][rl]。所有父属性将首先应用,然后被声明模型中设置的属性覆盖。常见的父模型包括: + - `minecraft:block/block`:所有方块模型的通用父模型。 + - `minecraft:block/cube`:所有使用1x1x1立方体模型的模型的父模型。 + - `minecraft:block/cube_all`:使用相同纹理在所有六个面上的立方体模型变种,例如圆石或木板。 + - `minecraft:block/cube_bottom_top`:使用相同纹理在所有四个水平面上,并在顶部和底部使用单独的纹理的立方体模型变种。常见示例包括砂岩或镶嵌石英。 + - `minecraft:block/cube_column`:具有侧面纹理和底部和顶部纹理的立方体模型变种。示例包括木头原木,以及石英和紫珀柱。 + - `minecraft:block/cross`:使用两个具有相同纹理的平面,一个顺时针旋转45°,另一个逆时针旋转45°,从上方看形成X形(因此得名)。示例包括大多数植物,例如草、树苗和花朵。 + - `minecraft:item/generated`:经典的2D平面物品模型的父模型。大多数物品都使用此模型。由于其四边形是从纹理生成的,因此会忽略 `elements` 块。 + - `minecraft:item/handheld`:用于看起来实际由玩家持有的2D平面物品模型的父模型。主要由工具使用。作为 `item/generated` 的子模型,因此它也会忽略 `elements` 块。 + - `minecraft:builtin/entity`:指定除 `particle` 外没有其他纹理。如果这是父模型,则[`BakedModel#isCustomRenderer()`][iscustomrenderer]将返回 `true`,以允许使用 [`BlockEntityWithoutLevelRenderer`][bewlr]。 + - 方块物品通常(但不总是)使用其对应的方块模型作为父模型。例如,圆石物品模型使用父模型 `minecraft:block/cobblestone`。 +- `ambientocclusion`:是否启用[环境光遮蔽][ao]。仅在方块模型上有效。默认为 `true`。如果您的自定义方块模型具有奇怪的阴影,请尝试将其设置为 `false`。 +- `render_type`:参见[渲染类型][rendertype]。 +- `gui_light`:可以是 `"front"` 或 `"side"`。如果是 `"front"`,光将来自前方,对于平面2D模型很有用。如果是 `"side"`,光将来自侧面,对于3D模型(尤其是方块模型)很有用。默认为 `"side"`。仅在物品模型上有效。 +- `textures`:一个子对象,将名称(称为纹理变量)映射到[纹理位置][textures]。然后可以在[elements]中使用纹理变量。它们也可以在元素中指定,但在子模型中保留未指定。 + - 方块模型还应指定一个 `particle` 纹理。当坠落在、穿越或破坏方块时,将使用此纹理。 + - 物品模型还可以使用层纹理,命名为 `layer0`、`layer1` 等,其中具有较高索引的层会呈现在具有较低索引的层上方(例如 `layer1` 将呈现在 `layer0` 上方)。仅在父模型为 `item/generated` 时有效,最多支持5层(`layer0` 到 `layer4`)。 +- `elements`:立方体[元素]的列表。 +- `overrides`:[覆盖模型][overrides]的列表。仅在物品模型上有效。 +- `display`:包含不同[视角]的不同显示选项的子对象,请参见链接的文章以获取可能的键。仅在物品模型上有效,但通常在方块模型中指定,以便物品模型可以继承显示选项。每个视角都是一个可选的子对象,可能包含以下选项,按顺序应用: + - `translation`:模型的平移,指定为 `[x, y, z]`。 + - `rotation`:模型的旋转,指定为 `[x, y, z]`。 + - `scale`:模型的 + +缩放,指定为 `[x, y, z]`。 + - `right_rotation`:NeoForge添加的。在缩放后应用的第二个旋转,指定为 `[x, y, z]`。 +- `transform`:参见[根变换][roottransforms]。 + +:::tip +如果您在确定如何精确指定某些内容方面遇到困难,请查看执行类似操作的原版模型。 +::: + +### 渲染类型 + +使用可选的 NeoForge 添加的 `render_type` 字段,您可以为模型设置渲染类型。如果未设置(如所有原版模型),游戏将退回到 `ItemBlockRenderTypes` 中硬编码的渲染类型。如果 `ItemBlockRenderTypes` 中也不存在该方块的渲染类型,它将退回到 `minecraft:solid`。原版提供以下默认渲染类型: + +- `minecraft:solid`:用于完全实心的方块,例如石头。 +- `minecraft:cutout`:用于任何像素完全实心或完全透明的方块,即具有完全不透明或完全透明的像素,例如玻璃。 +- `minecraft:cutout_mipped`:`minecraft:cutout` 的变体,将在较大距离上缩小纹理以避免视觉伪影([mipmapping])。由于通常不希望物品上使用mipmapping并且可能会导致伪影,因此不会对物品渲染应用mipmapping。例如,用于树叶。 +- `minecraft:cutout_mipped_all`:`minecraft:cutout_mipped` 的变体,将mipmapping应用于物品模型。 +- `minecraft:translucent`:用于任何像素可能部分透明的方块,例如有色玻璃。 +- `minecraft:tripwire`:用于具有被渲染到天气目标的特殊要求的方块,即绊线。 + +选择正确的渲染类型在某种程度上是一个性能问题。实心渲染比切割渲染快,切割渲染比半透明渲染快。因此,您应该为您的用例指定最严格的适用渲染类型,因为它也将是最快的。 + +如果愿意,您也可以添加自己的渲染类型。要这样做,请订阅 [mod 总线][modbus] [事件] `RegisterNamedRenderTypesEvent` 并 `#register` 您的渲染类型。`#register` 具有三个或四个参数: + +- 渲染类型的名称。将以您的mod id作为前缀。例如,在此处使用 `"my_cutout"` 将为您提供 `examplemod:my_cutout` 作为新的可供您使用的渲染类型(前提是您的mod id为 `examplemod`)。 +- 分块渲染类型。可以使用 `RenderType.chunkBufferLayers()` 返回的列表中的任何类型。 +- 实体渲染类型。必须是具有 `DefaultVertexFormat.NEW_ENTITY` 顶点格式的渲染类型。 +- 可选项:神奇的渲染类型。必须是具有 `DefaultVertexFormat.NEW_ENTITY` 顶点格式的渲染类型。如果将图形模式设置为 _Fabulous!_,则将使用此渲染类型而不是常规实体渲染类型。如果省略,将回退到常规渲染类型。通常建议在渲染类型在某种程度上使用透明度时设置。 + +### 元素 + +元素是立方体对象的JSON表示。它具有以下属性: + +- `from`:立方体起始角的坐标,指定为 `[x, y, z]`。以1/16方块单位指定。例如,`[0, 0, 0]` 将是“左下”角,`[8, 8, 8]` 将是中心,`[16, 16, 16]` 将是“右上”角。 +- `to`:立方体结束角的坐标,指定为 `[x, y, z]`。与 `from` 一样,这是以1/16方块单位指定的。 + +:::tip +Minecraft中的值在范围 `[-16, 32]` 内。但是,强烈不建议超过 `[0, 16]`,因为这将导致光照和/或剔除问题。 +::: + +- `neoforge_data`:请参见[额外的面数据][extrafacedata]。 +- `faces`:包含最多6个面的数据的对象,分别命名为 `north`、`south`、`east`、`west`、`up` 和 `down`。每个面都具有以下数据: + - `uv`:面的uv,指定为 `[u1, v1, u2, v2]`,其中 `u1, v1` 是左上角uv坐标,`u2, v2` 是右下角uv坐标。 + - `texture`:面使用的纹理。必须是以 `#` 为前缀的纹理变量。例如,如果您的模型有一个名为 `wood` 的纹理,则可以使用 `#wood` 引用该纹理。在技术上是可选的,如果缺少将使用缺失的纹理。 + - `rotation`:可选。以顺时针90、180或270度旋转纹理。 + - `cullface`:可选。告诉渲染引擎在指定方向上有一个完整方块触碰时跳过渲染面。方向可以是 `north`、`south`、`east`、`west`、`up` 或 `down`。 + - `tint + +index`:可选。指定颜色处理程序可能使用的染色索引,有关更多信息,请参见[着色][tinting]。默认为-1,表示不染色。 + - `neoforge_data`:请参见[额外的面数据][extrafacedata]。 + +此外,它还可以指定以下可选属性: + +- `shade`:仅适用于方块模型。可选。此元素的面是否应该有方向相关的阴影。默认为 true。 +- `rotation`:对象的旋转,指定为包含以下数据的子对象: + - `angle`:旋转角度,以度为单位。可以是 -45 到 45,步长为22.5度。 + - `axis`:围绕旋转的轴。目前无法围绕多个轴旋转对象。 + - `origin`:可选。旋转的原点,指定为 `[x, y, z]`。请注意,这些是绝对值,即它们不是相对于立方体位置的。如果未指定,将使用 `[0, 0, 0]`。 + +#### 额外的面数据 + +额外的面数据(`neoforge_data`)可以应用于元素和元素的单个面。在所有可用的上下文中,它都是可选的。如果同时指定了元素级和面级额外面数据,则面级数据将覆盖元素级数据。额外的数据可以指定以下数据: + +- `color`:使用给定颜色对面进行染色。必须是ARGB值。可以指定为字符串或十进制整数(JSON不支持十六进制文字)。默认为 `0xFFFFFFFF`。如果颜色值是恒定的,可以用作对染色的替代。 +- `block_light`:覆盖用于此面的块光照值。默认为0。 +- `sky_light`:覆盖用于此面的天空光照值。默认为0。 +- `ambient_occlusion`:为此面禁用或启用环境光遮蔽。默认为模型中设置的值。 + +使用自定义的 `neoforge:item_layers` 加载器,还可以指定要应用于 `item/generated` 模型中所有几何图形的额外面数据。在以下示例中,第1层将以红色染色并以完全亮度发光: + +```json5 +{ + "loader": "neoforge:item_layers", + "parent": "minecraft:item/generated", + "textures": { + "layer0": "minecraft:item/stick", + "layer1": "minecraft:item/glowstone_dust" + }, + "neoforge_data": { + "1": { + "color": "0xFFFF0000", + "block_light": 15, + "sky_light": 15, + "ambient_occlusion": false + } + } +} +``` + +### 覆盖模型 + +物品覆盖可以根据浮点值(称为覆盖值)为物品分配不同的模型。例如,弓和十字弓使用此功能根据它们已经拉开的时间来更改纹理。覆盖模型有模型和代码两个方面。 + +模型可以指定一个或多个覆盖模型,当覆盖值等于或大于给定的阈值时应使用。例如,弓使用两个不同的属性 `pulling` 和 `pull`。 `pulling` 被视为布尔值,其中1被解释为正在拉动,0被解释为未拉动,而 `pull` 表示弓当前拉伸的程度。然后,它使用这些属性来指定在充能至低于65%时(`pulling` 1,没有 `pull` 值),65%时(`pulling` 1,`pull` 0.65)和90%时(`pulling` 1,`pull` 0.9)使用三种不同的替代模型。如果多个模型适用(因为值不断变大),则匹配列表的最后一个元素,因此请确保您的顺序是正确的。覆盖模型如下所示: +```json5 +{ + // other stuff here + "overrides": [ + { + // pulling = 1 + "predicate": { + "pulling": 1 + }, + "model": "item/bow_pulling_0" + }, + { + // pulling = 1, pull >= 0.65 + "predicate": { + "pulling": 1, + "pull": 0.65 + }, + "model": "item/bow_pulling_1" + }, + // pulling = 1, pull >= 0.9 + { + "predicate": { + "pulling": 1, + "pull": 0.9 + }, + "model": "item/bow_pulling_2" + } + ] +} +``` + +事情的代码方面相当简单。假设我们想要向我们的物品添加一个名为 `examplemod:property` 的属性,我们会在[客户端][side]的[event handler][eventhandler]中使用以下代码: + +```java +@SubscribeEvent +public static void onClientSetup(FMLClientSetupEvent event) { + event.enqueueWork(() -> { // ItemProperties#register is not threadsafe, so we need to call it on the main thread + ItemProperties.register( + // The item to apply the property to. + ExampleItems.EXAMPLE_ITEM, + // The id of the property. + new ResourceLocation("examplemod", "property"), + // A reference to a method that calculates the override value. + // Parameters are the used item stack, the level context, the player using the item, + // and a random seed you can use. + (stack, level, player, seed) -> someMethodThatReturnsAFloat() + ); + }); +} +``` + +:::info +原版 Minecraft 仅允许 0 到 1 之间的浮点值。NeoForge 对此进行了补充,以允许任意的浮点值。 +::: + +### 根变换 + +在模型的顶层添加 `transform` 属性会告诉加载器在应用 [blockstate 文件][bsfile](用于方块模型)中的旋转或 `display` 块中的变换(用于物品模型)之前,应该对所有几何图形应用一个变换。这是由 NeoForge 添加的。 + +根变换可以通过两种方式指定。第一种方式是作为一个名为 `matrix` 的单个属性,其中包含一个 3x4 的变换矩阵(行主序,最后一行被省略),以嵌套的 JSON 数组形式表示。矩阵是按照平移、左旋转、缩放、右旋转和变换原点的顺序组合而成。示例如下: + +```json5 +{ + // ... + "transform": { + "matrix": [ + [0, 0, 0, 0], + [0, 0, 0, 0], + [0, 0, 0, 0] + ] + } +} +``` + +根据Minecraft译名标准化,以下是翻译后的文档: + +第二种方式是指定一个包含以下条目的JSON对象,按以下顺序应用: + +- `translation`:相对位移。指定为一个三维向量(`[x, y, z]`),如果缺失默认为`[0, 0, 0]`。 +- `rotation` 或 `left_rotation`:在缩放之前应用于平移原点的旋转。默认不旋转。可以用以下方式指定: + - 一个带有单一轴到旋转映射的JSON对象,例如 `{"x": 90}` + - 一个包含单一轴到旋转映射的JSON对象的数组,按照指定的顺序应用,例如 `[{"x": 90}, {"y": 45}, {"x": -22.5}]` + - 一个包含三个值的数组,每个值分别指定每个轴的旋转,例如 `[90, 45, -22.5]` + - 一个包含四个值的数组,直接指定一个四元数,例如 `[0.38268346, 0, 0, 0.9238795]`(= X轴45度旋转) +- `scale`:相对于平移原点的缩放。指定为一个三维向量(`[x, y, z]`),如果缺失默认为`[1, 1, 1]`。 +- `post_rotation` 或 `right_rotation`:在缩放之后应用于平移原点的旋转。默认不旋转。指定方式与`rotation`相同。 +- `origin`:用于旋转和缩放的原点。转换也作为最后一步移到这里。指定为一个三维向量(`[x, y, z]`)或使用三个内置值之一 `"corner"`(=`[0, 0, 0]`),`"center"`(=`[0.5, 0.5, 0.5]`)或 `"opposing-corner"`(=`[1, 1, 1]`,默认值)。 + +## 方块状态文件 + +参见:[Minecraft Wiki][mcwiki]上的[方块状态文件][mcwikiblockstate] + +方块状态文件由游戏用于为不同的[方块状态]分配不同的模型。每个注册到游戏的方块必须有一个确切的方块状态文件。指定方块模型到方块状态有两种相互排斥的方式:通过变体或者多部件。 + +在`variants`块内,每个方块状态都有一个元素。这是将方块状态与模型相关联的主要方式,被绝大多数方块使用。 +- 键是没有方块名的方块状态的字符串表示,例如对于非含水的台阶是`"type=top,waterlogged=false"`,或者对于没有属性的方块是`""`。值得注意的是,未使用的属性可以省略。例如,如果`waterlogged`属性对所选模型无影响,则两个对象`type=top,waterlogged=false`和`type=top,waterlogged=true`可以被合并为一个`type=top`对象。这也意味着对于每个方块,空字符串都是有效的。 +- 值要么是单一的模型对象,要么是模型对象的数组。如果使用了模型对象的数组,将从中随机选择一个模型。一个模型对象包含以下数据: + - `model`:模型文件位置的路径,相对于命名空间的`models`文件夹,例如`minecraft:block/cobblestone`。 + - `x`和`y`:模型在x轴/y轴的旋转。限制为90度的步进。每个都是可选的,默认为0。 + - `uvlock`:旋转模型时是否锁定UV。可选的,默认为false。 + - `weight`:仅在模型对象数组中有用。给对象一个权重,用于选择随机模型对象。可选的,默认为1。 + +相反,在`multipart`块内,元素根据方块状态的属性组合。这种方法主要被栅栏和围墙使用,它们根据布尔属性启用四个方向的部分。一个多部分元素由两个部分组成:`when`块和`apply`块。 + +- `when`块指定了一个方块状态的字符串表示,或者一个必须满足元素应用的属性列表。这些列表可以被命名为`"OR"`或`"AND"`,对其内容执行相应的逻辑操作。单个方块状态和列表值都可以通过用`|`分隔它们来指定多个实际值(例如 `facing=east|facing=west`)。 +- `apply`块指定了要使用的模型对象或模型对象数组。这与`variants`块的工作方式完全相同。 + +## 着色 + +有些方块,如草或树叶,会根据它们的位置和/或属性改变它们的纹理。[模型元素][elements]可以在它们的面上指定一个染色指数,这将允许颜色处理器处理相应的面。代码方面通过两个事件来处理,一个是方块颜色处理器,另一个是物品颜色处理器。它们的工作方式非常相似,让我们先看一下方块处理器: + +```java +@SubscribeEvent +public static void registerBlockColorHandlers(RegisterColorHandlersEvent.Block event) { + // Parameters are the block's state, the level the block is in, the block's position, and the tint index. + // The level and position may be null. + event.register((state, level, pos, tintIndex) -> { + // Replace with your own calculation. See the BlockColors class for vanilla references. + // All vanilla uses assume alpha 255 (= 1f), but modded consumers may also account + // for alpha values specified here. Generally, if the tint index is -1, + // it means that no tinting should take place and a default value should be used instead. + return 0xFFFFFF; + }); +} +``` + +物品处理器的工作方式几乎相同,只是命名和lambda参数有所不同: + +```java +@SubscribeEvent +public static void registerItemColorHandlers(RegisterColorHandlersEvent.Item event) { + // Parameters are the item stack and the tint index. + event.register((stack, tintIndex) -> { + // Like above, replace with your own calculation. Vanilla values are in the ItemColors class. + // Also like above, tint index -1 means no tint and should use a default value instead. + return 0xFFFFFF; + }); +} +``` + +请注意,`item/generated`模型为其各个层指定了染色指数 - `layer0`有染色指数0,`layer1`有染色指数1,等等。另外,记住方块物品是物品,而不是方块,需要物品颜色处理器来着色。 + +## 注册额外的模型 + +一些并未与某个方块或物品有所关联,但在其他上下文(例如[方块实体渲染器][ber])中仍然需要的模型,可以通过`ModelEvent.RegisterAdditional`来注册: + +```java +// Client-side mod bus event handler +@SubscribeEvent +public static void registerAdditional(ModelEvent.RegisterAdditional event) { + event.register(new ResourceLocation("examplemod", "block/example_unused_model")); +} +``` + +[ao]: https://en.wikipedia.org/wiki/Ambient_occlusion +[ber]: ../../../blockentities/ber.md +[bewlr]: ../../../items/bewlr.md +[bsfile]: #blockstate-files +[custommodelloader]: modelloaders.md +[elements]: #elements +[event]: ../../../concepts/events.md +[eventhandler]: ../../../concepts/events.md#registering-an-event-handler +[extrafacedata]: #extra-face-data +[iscustomrenderer]: bakedmodel.md#others +[mcwiki]: https://minecraft.wiki +[mcwikiblockstate]: https://minecraft.wiki/w/Tutorials/Models#Block_states +[mcwikimodel]: https://minecraft.wiki/w/Model +[mipmapping]: https://en.wikipedia.org/wiki/Mipmap +[modbus]: ../../../concepts/events.md#event-buses +[overrides]: #overrides +[perspectives]: bakedmodel.md#perspectives +[rendertype]: #render-types +[roottransforms]: #root-transforms +[rl]: ../../../misc/resourcelocation.md +[side]: ../../../concepts/sides.md +[textures]: ../textures.md +[tinting]: #tinting diff --git a/i18n/zh-Hans/docusaurus-plugin-content-docs/current/resources/client/models/modelloaders.md b/i18n/zh-Hans/docusaurus-plugin-content-docs/current/resources/client/models/modelloaders.md new file mode 100644 index 000000000..3852f1fb8 --- /dev/null +++ b/i18n/zh-Hans/docusaurus-plugin-content-docs/current/resources/client/models/modelloaders.md @@ -0,0 +1,469 @@ +# 自定义模型加载器 + +一个模型就是一个形状。它可以是一个立方体、一组立方体、一组三角形,或者任何其他的几何形状(或几何形状的组合)。在大多数上下文中,模型是如何定义的并不重要,因为最终一切都会在内存中变成`BakedModel`。因此,NeoForge增加了能够注册自定义模型加载器的功能,这些加载器可以将任何你想要的模型转换成游戏使用的`BakedModel`。 + +一个方块模型的入口点仍然是模型JSON文件。然而,你可以在JSON的根部指定一个`loader`字段,它会将默认加载器替换为你自己的加载器。一个自定义模型加载器可能会忽略默认加载器所需的所有字段。 + +## 内建模型加载器 + +除了默认模型加载器,NeoForge还提供了几个内建的加载器,每个加载器都有不同的用途。 + +### 组合模型 + +组合模型可以用来在父模型中指定不同的模型部分,并且只在子模型中应用其中的一些。以下面的例子最能说明这一点。考虑以下位于`examplemod:example_composite_model`的父模型: + +```json5 +{ + "loader": "neoforge:composite", + // Specify model parts. + "children": { + "part_1": { + "parent": "examplemod:some_model_1" + }, + "part_2": { + "parent": "examplemod:some_model_2" + } + }, + "visibility": { + // Disable part 2 by default. + "part_2": false + } +} +``` + +然后,我们可以在`examplemod:example_composite_model`的子模型中禁用和启用单独的部分: + +```json5 +{ + "parent": "examplemod:example_composite_model", + // Override visibility. If a part is missing, it will use the parent model's visibility value. + "visibility": { + "part_1": false, + "part_2": true + } +} +``` + +要[datagen][modeldatagen]这个模型,使用自定义加载器类`CompositeModelBuilder`。 + +### 动态流体容器模型 + +动态流体容器模型,也称为动态桶模型,它最常见的使用场景是用于表示流体容器的物品(如桶或罐)并想在模型中显示流体。这只有在流体量是固定的(例如只有岩浆和细雪)的情况下才可行,如果流体是任意的,那么请使用[`BlockEntityWithoutLevelRenderer`][bewlr]。 + +```json5 +{ + "loader": "neoforge:fluid_container", + // Required. Must be a valid fluid id. + "fluid": "minecraft:water", + // The loader generally expects two textures: base and fluid. + "textures": { + // The base container texture, i.e. the equivalent of an empty bucket. + "base": "examplemod:item/custom_container", + // The fluid texture, i.e. the equivalent of water in a bucket. + "fluid": "examplemod:item/custom_container_fluid" + }, + // Optional, defaults to false. Whether to flip the model upside down, for gaseous fluids. + "flip_gas": true, + // Optional, defaults to true. Whether to have the cover act as the mask. + "cover_is_mask": false, + // Optional, defaults to true. Whether to apply the fluid's luminosity to the item model. + "apply_fluid_luminosity": false, +} +``` + +很多时候,动态流体容器模型会直接使用桶模型。这是通过指定`neoforge:item_bucket`父模型来实现的,如下所示: + +```json5 +{ + "loader": "neoforge:fluid_container", + "parent": "neoforge:item/bucket", + // Replace with your own fluid. + "fluid": "minecraft:water" + // Optional properties here. Note that the textures are handled by the parent. +} +``` + +要[datagen][modeldatagen]这个模型,使用自定义加载器类`DynamicFluidContainerModelBuilder`。请注意,出于对旧版本支持的考虑,这个类还提供了一个设置`apply_tint`属性的方法,这个属性现在已不再使用。 + +### 元素模型 + +一个元素模型由方块模型[elements][elements]和一个可选的[根变换][transform]组成。主要用于常规模型渲染之外的场景,例如在[BER][ber]中。 + +```json5 +{ + "loader": "neoforge:elements", + "elements": [...], + "transform": {...} +} +``` + +### 空模型 + +一个空模型什么都不渲染。 + +```json5 +{ + "loader": "neoforge:empty" +} +``` + +### 物品层模型 + +物品层模型是标准`item/generated`模型的一个变种,提供了以下额外的功能: + +- 无限数量的层(而不是默认的5层) +- 每一层的[渲染类型][rendertype] + +```json5 +{ + "loader": "neoforge:item_layers", + "textures": { + "layer0": "...", + "layer1": "...", + "layer2": "...", + "layer3": "...", + "layer4": "...", + "layer5": "...", + }, + "render_types": { + // Map render types to layer numbers. For example, layers 0, 2 and 4 will use cutout. + "minecraft:cutout": [0, 2, 4], + "minecraft:cutout_mipped": [1, 3], + "minecraft:translucent": [5] + }, + // other stuff the default loader allows here +} +``` + +要[datagen][modeldatagen]这个模型,使用自定义加载器类`ItemLayerModelBuilder`。 + +### OBJ模型 + +OBJ模型加载器允许您在游戏中使用Wavefront `.obj` 3D模型,允许在模型中包含任意形状(包括三角形、圆形等)。`.obj`模型必须放在`models`文件夹(或其子文件夹)中,并且必须提供一个同名的`.mtl`文件(或手动设置),所以例如,位于`models/block/example.obj`的OBJ模型必须有一个对应的MTL文件位于`models/block/example.mtl`。 + +```json5 +{ + "loader": "neoforge:obj", + // Required. Reference to the model file. Note that this is relative to the namespace root, not the model folder. + "model": "examplemod:models/example.obj", + // Normally, .mtl files must be put into the same location as the .obj file, with only the file ending differing. + // This will cause the loader to automatically pick them up. However, you can also set the location + // of the .mtl file manually if needed. + "mtl_override": "examplemod:models/example_other_name.mtl", + // These textures can be referenced in the .mtl file as #texture0, #particle, etc. + // This usually requires manual editing of the .mtl file. + "textures": { + "texture0": "minecraft:block/cobblestone", + "particle": "minecraft:block/stone" + }, + // Enable or disable automatic culling of the model. Optional, defaults to true. + "automatic_culling": false, + // Whether to shade the model or not. Optional, defaults to true. + "shade_quads": false, + // Some modeling programs will assume V=0 to be bottom instead of the top. This property flips the Vs upside-down. + // Optional, defaults to false. + "flip_v": true, + // Whether to enable emissivity or not. Optional, defaults to true. + "emissive_ambient": false +} +``` + +要[datagen][modeldatagen]这个模型,使用自定义加载器类`ObjModelBuilder`。 + +### 独立变换模型 + +独立变换模型可用于根据视角切换不同的模型。视角与[normal model][model]中的`display`块相同。这通过指定一个基础模型(作为后备)然后为每个视角指定覆盖模型来实现。注意,如果您愿意,每个这样的模型都可以是完整的模型,但通常最简单的方法是使用那个模型的子模型来引用另一个模型,如下所示: + +```json5 +{ + "loader": "neoforge:separate_transforms", + // Use the cobblestone model normally. + "base": { + "parent": "minecraft:block/cobblestone" + }, + // Use the stone model only when dropped. + "perspectives": { + "ground": { + "parent": "minecraft:block/stone" + } + } +} +``` + +要[datagen][modeldatagen]这个模型,使用自定义加载器类`SeparateTransformsModelBuilder`。 + +## 创建自定义模型加载器 + +要创建自己的模型加载器,您需要三个类加上一个事件处理程序: + +- 一个几何体加载器类 +- 一个几何体类 +- 一个动态的[baked model][bakedmodel]类 +- 一个用于`ModelEvent.RegisterGeometryLoaders`的[客户端][sides] [事件处理程序][event],用于注册几何体加载器 + +为了说明这些类是如何连接的,我们将跟随一个模型的加载过程: + +- 在模型加载期间,带有`loader`属性设置为您的加载器的模型JSON被传递给您的几何体加载器。然后,几何体加载器读取模型JSON并使用模型JSON的属性返回一个几何体对象。 +- 在模型烘焙期间,几何体被烘焙,返回一个动态烘焙模型。 +- 在模型渲染期间,动态烘焙模型用于渲染。 + +让我们通过一个基本的类设置进一步说明。几何体加载器类命名为`MyGeometryLoader`,几何体类命名为`MyGeometry`,动态烘焙模型类命名为`MyDynamicModel`: + +```java +public class MyGeometryLoader implements IGeometryLoader { + // It is highly recommended to use a singleton pattern for geometry loaders, as all models can be loaded through one loader. + public static final MyGeometryLoader INSTANCE = new MyGeometryLoader(); + // The id we will use to register this loader. Also used in the loader datagen class. + public static final ResourceLocation ID = new ResourceLocation("examplemod", "my_custom_loader"); + + // In accordance with the singleton pattern, make the constructor private. + private MyGeometryLoader() {} + + @Override + public MyGeometry read(JsonObject jsonObject, JsonDeserializationContext context) throws JsonParseException { + // Use the given JsonObject and, if needed, the JsonDeserializationContext to get properties from the model JSON. + // The MyGeometry constructor may have constructor parameters (see below). + return new MyGeometry(); + } +} + +public class MyGeometry implements IUnbakedGeometry { + // The constructor may have any parameters you need, and store them in fields for further usage below. + // If the constructor has parameters, the constructor call in MyGeometryLoader#read must match them. + public MyGeometry() {} + + // Method responsible for model baking, returning our dynamic model. Parameters in this method are: + // - The geometry baking context. Contains many properties that we will pass into the model, e.g. light and ao values. + // - The model baker. Can be used for baking sub-models. + // - The sprite getter. Maps materials (= texture variables) to TextureAtlasSprites. Materials can be obtained from the context. + // For example, to get a model's particle texture, call spriteGetter.apply(context.getMaterial("particle")); + // - The model state. This holds the properties from the blockstate file, e.g. rotations and the uvlock boolean. + // - The item overrides. This is the code representation of an "overrides" block in an item model. + // - The resource location of the model. + @Override + public BakedModel bake(IGeometryBakingContext context, ModelBaker baker, Function spriteGetter, ModelState modelState, ItemOverrides overrides, ResourceLocation modelLocation) { + // See info on the parameters below. + return new MyDynamicModel(context.useAmbientOcclusion(), context.isGui3d(), context.useBlockLight(), + spriteGetter.apply(context.getMaterial("particle")), overrides); + } + + // Method responsible for correctly resolving parent properties. Required if this model loads any nested models or reuses the vanilla loader on itself (see below). + @Override + public void resolveParents(Function modelGetter, IGeometryBakingContext context) { + base.resolveParents(modelGetter); + } +} + +// BakedModelWrapper can be used as well to return default values for most methods, allowing you to only override what actually needs to be overridden. +public class MyDynamicModel implements IDynamicBakedModel { + // Material of the missing texture. Its sprite can be used as a fallback when needed. + private static final Material MISSING_TEXTURE = + new Material(TextureAtlas.LOCATION_BLOCKS, MissingTextureAtlasSprite.getLocation()); + + // Attributes for use in the methods below. Optional, the methods may also use constant values if applicable. + private final boolean useAmbientOcclusion; + private final boolean isGui3d; + private final boolean usesBlockLight; + private final TextureAtlasSprite particle; + private final ItemOverrides overrides; + + // The constructor does not require any parameters other than the ones for instantiating the final fields. + // It may specify any additional parameters to store in fields you deem necessary for your model to work. + public MyDynamicModel(boolean useAmbientOcclusion, boolean isGui3d, boolean usesBlockLight, TextureAtlasSprite particle, ItemOverrides overrides) { + this.useAmbientOcclusion = useAmbientOcclusion; + this.isGui3d = isGui3d; + this.usesBlockLight = usesBlockLight; + this.particle = particle; + this.overrides = overrides; + } + + // Use our attributes. Refer to the article on baked models for more information on the method's effects. + @Override + public boolean useAmbientOcclusion() { + return useAmbientOcclusion; + } + + @Override + public boolean isGui3d() { + return isGui3d; + } + + @Override + public boolean usesBlockLight() { + return usesBlockLight; + } + + @Override + public TextureAtlasSprite getParticleIcon() { + // Return MISSING_TEXTURE.sprite() if you don't need a particle, e.g. when in an item model context. + return particle; + } + + @Override + public ItemOverrides getOverrides() { + // Return ItemOverrides.EMPTY when in a block model context. + return overrides; + } + + // Override this to true if you want to use a custom block entity renderer instead of the default renderer. + @Override + public boolean isCustomRenderer() { + return false; + } + + // This is where the magic happens. Return a list of the quads to render here. Parameters are: + // - The blockstate being rendered. May be null if rendering an item. + // - The side being culled against. May be null, which means quads that cannot be occluded should be returned. + // - A client-bound random source you can use for randomizing stuff. + // - The extra data to use. Originates from a block entity (if present), or from BakedModel#getModelData(). + // - The render type for which quads are being requested. + // NOTE: This may be called many times in quick succession, up to several times per block. + // This should be as fast as possible and use caching wherever applicable. + @Override + public List getQuads(@Nullable BlockState state, @Nullable Direction side, RandomSource rand, ModelData extraData, @Nullable RenderType renderType) { + List quads = new ArrayList<>(); + // add elements to the quads list as needed here + return quads; + } +} +``` + +在所有操作完成后,不要忘记实际注册您的加载器,否则所有的工作都将白费: + +```java +// Client-side mod bus event handler +@SubscribeEvent +public static void registerGeometryLoaders(ModelEvent.RegisterGeometryLoaders event) { + event.register(MyGeometryLoader.ID, MyGeometryLoader.INSTANCE); +} +``` + +### 数据生成 + +当然,我们也可以对我们的模型进行[数据生成]。为此,我们需要一个扩展`CustomLoaderBuilder`的类: + +```java +// This assumes a block model. Use ItemModelBuilder as the generic parameter instead +// if you're making a custom item model. +public class MyLoaderBuilder extends CustomLoaderBuilder { + public MyLoaderBuilder(BlockModelBuilder parent, ExistingFileHelper existingFileHelper) { + super( + // Your model loader's id. + MyGeometryLoader.ID, + // The parent builder we use. This is always the first constructor parameter. + parent, + // The existing file helper we use. This is always the second constructor parameter. + existingFileHelper, + // Whether the loader allows inline vanilla elements as a fallback if the loader is absent. + false + ); + } + + // Add fields and setters for the fields here. The fields can then be used below. + + // Serialize the model to JSON. + @Override + public JsonObject toJson(JsonObject json) { + // Add your fields to the given JsonObject. + // Then call super, which adds the loader property and some other things. + return super.toJson(json); + } +} +``` + +要使用这个加载器构建器,在块(或物品)[模型数据生成][modeldatagen]期间执行以下操作: + +```java +// This assumes a BlockStateProvider. Use getBuilder("my_cool_block") directly in an ItemModelProvider. +// The parameter for customLoader() is a BiFunction. The parameters of the BiFunction +// are the result of the getBuilder() call and the provider's ExistingFileHelper. +MyLoaderBuilder loaderBuilder = models().getBuilder("my_cool_block").customLoader(MyLoaderBuilder::new); +``` + +然后,在`loaderBuilder`上调用你的字段设置器。 + +#### 可见性 + +`CustomLoaderBuilder`的默认实现有应用可见性的方法。你可以选择在你的模型加载器中使用或忽视`visibility`属性。目前,只有[复合模型加载器][composite]使用了这个属性。 + +### 重用默认模型加载器 + +在某些情况下,重用 Vanilla 模型加载器并在其基础上构建你的模型逻辑,而不是直接替换它,是有意义的。我们可以使用一个巧妙的技巧来实现这个目标:在模型加载器中,我们只需移除`loader`属性,然后将其发送回模型解析器,让其误以为现在是一个常规模型。然后我们将它传给几何体,在那里烘焙模型几何体(就像默认的几何体处理器那样),并将其传递给动态模型,在那里我们可以以我们想要的方式使用模型的quads: + +```java +public class MyGeometryLoader implements IGeometryLoader { + public static final MyGeometryLoader INSTANCE = new MyGeometryLoader(); + public static final ResourceLocation ID = new ResourceLocation(...); + + private MyGeometryLoader() {} + + @Override + public MyGeometry read(JsonObject jsonObject, JsonDeserializationContext context) throws JsonParseException { + // Trick the deserializer into thinking this is a normal model by removing the loader field and then passing it back into the deserializer. + jsonObject.remove("loader"); + BlockModel base = context.deserialize(jsonObject, BlockModel.class); + // other stuff here if needed + return new MyGeometry(base); + } +} + +public class MyGeometry implements IUnbakedGeometry { + private final BlockModel base; + + // Store the block model for usage below. + public MyGeometry(BlockModel base) { + this.base = base; + } + + @Override + public BakedModel bake(IGeometryBakingContext context, ModelBaker baker, Function spriteGetter, ModelState modelState, ItemOverrides overrides, ResourceLocation modelLocation) { + BakedModel bakedBase = new ElementsModel(base.getElements()).bake(context, baker, spriteGetter, modelState, overrides, modelLocation); + return new MyDynamicModel(bakedBase, /* other parameters here */); + } + + @Override + public void resolveParents(Function modelGetter, IGeometryBakingContext context) { + base.resolveParents(modelGetter); + } +} + +public class MyDynamicModel implements IDynamicBakedModel { + private final BakedModel base; + // other fields here + + public MyDynamicModel(BakedModel base, /* other parameters here */) { + this.base = base; + // set other fields here + } + + // other override methods here + + @Override + public List getQuads(@Nullable BlockState state, @Nullable Direction side, RandomSource rand, ModelData extraData, @Nullable RenderType renderType) { + List quads = new ArrayList<>(); + // Add the base model's quads. Can also do something different with the quads here, depending on what you need. + quads.add(base.getQuads(state, side, rand, extraData, renderType)); + // add other elements to the quads list as needed here + return quads; + } + + // Apply the base model's transforms to our model as well. + @Override + public BakedModel applyTransform(ItemDisplayContext transformType, PoseStack poseStack, boolean applyLeftHandTransform) { + return base.applyTransform(transformType, poseStack, applyLeftHandTransform); + } +} +``` + +[bakedmodel]: bakedmodel.md +[ber]: ../../../blockentities/ber.md +[bewlr]: ../../../items/bewlr.md +[composite]: #composite-model +[datagen]: ../../index.md#data-generation +[elements]: index.md#elements +[event]: ../../../concepts/events.md#registering-an-event-handler +[model]: index.md#specification +[modeldatagen]: datagen.md +[rendertype]: index.md#render-types +[sides]: ../../../concepts/sides.md +[transform]: index.md#root-transforms diff --git a/i18n/zh-Hans/docusaurus-plugin-content-docs/current/resources/client/particles.md b/i18n/zh-Hans/docusaurus-plugin-content-docs/current/resources/client/particles.md new file mode 100644 index 000000000..e7f3e9bf6 --- /dev/null +++ b/i18n/zh-Hans/docusaurus-plugin-content-docs/current/resources/client/particles.md @@ -0,0 +1,260 @@ +# 粒子效果 + +粒子效果是能够美化游戏并增加沉浸感的2D效果。它们可以在客户端和服务器端[side]产生,但由于它们主要是视觉效果,关键部分只存在于物理(和逻辑)客户端。 + +## 注册粒子效果 + +### `ParticleType` + +粒子效果是使用`ParticleType`注册的。这与`EntityType`或`BlockEntityType`类似,有一个`Particle`类 - 每个产生的粒子都是该类的一个实例 -,然后有`ParticleType`类,保存一些共有信息,用于注册。`ParticleType`是一个[注册表],这意味着我们想要使用`DeferredRegister`来注册它们,就像所有其他注册的对象一样: + +```java +public class MyParticleTypes { + // Assuming that your mod id is examplemod + public static final DeferredRegister> PARTICLE_TYPES = + DeferredRegister.create(BuiltInRegistries.PARTICLE_TYPE, "examplemod"); + + // The easiest way to add new particle types is reusing vanilla's SimpleParticleType. + // Implementing a custom ParticleType is also possible, see below. + public static final Supplier MY_PARTICLE = PARTICLE_TYPES.register( + // The name of the particle type. + "my_particle", + // The supplier. The boolean parameter denotes whether setting the Particles option in the + // video settings to Minimal will affect this particle type or not; this is false for + // most vanilla particles, but true for e.g. explosions, campfire smoke, or squid ink. + () -> new SimpleParticleType(false) + ); +} +``` + +:::info +如果您需要在服务器端处理粒子效果,那么`ParticleType`是必需的。客户端也可以直接使用`Particle`。 +::: + +### `Particle` + +`Particle`是稍后被生成到世界中并显示给玩家的实体。虽然你可以扩展`Particle`并自己实现一些功能,但在许多情况下,扩展`TextureSheetParticle`可能会更好,因为这个类为你提供了如动画和缩放等功能的助手,而且还为你实现了实际的渲染(如果直接扩展`Particle`,你需要自己实现这些功能)。 + +`Particle`的大多数属性是由如`gravity`,`lifetime`,`hasPhysics`,`friction`等字段控制的。唯一有意义的自我实现方法是`tick`和`move`,这两个方法都正如你所期望的那样进行操作。因此,自定义的粒子类通常很简短,例如,只包括一个构造函数,设置一些字段并让超类处理剩下的事情。一个基本的实现可能看起来像这样: + +```java +public class MyParticle extends TextureSheetParticle { + private final SpriteSet spriteSet; + + // First four parameters are self-explanatory. The SpriteSet parameter is provided by the + // ParticleProvider, see below. You may also add additional parameters as needed, e.g. xSpeed/ySpeed/zSpeed. + public MyParticle(ClientLevel level, double x, double y, double z, SpriteSet spriteSet) { + super(level, x, y, z); + this.spriteSet = spriteSet; + this.gravity = 0; // Our particle floats in midair now, because why not. + } + + @Override + public void tick() { + // Set the sprite for the current particle age, i.e. advance the animation. + setSpriteFromAge(spriteSet); + // Let super handle further movement. You may replace this with your own movement if needed. + // You may also override move() if you only want to modify the built-in movement. + super.tick(); + } +} +``` + +### `ParticleProvider` + +接下来,粒子类型必须注册一个`ParticleProvider`。`ParticleProvider`是一个仅在客户端的类,负责通过`createParticle`方法实际创建我们的`Particle`。虽然这里可以包含更复杂的代码,但许多粒子提供器的实现可能非常简单,如下所示: + +```java +// The generic type of ParticleProvider must match the type of the particle type this provider is for. +public class MyParticleProvider implements ParticleProvider { + // A set of particle sprites. + private final SpriteSet spriteSet; + + // The registration function passes a SpriteSet, so we accept that and store it for further use. + public MyParticleProvider(SpriteSet spriteSet) { + this.spriteSet = spriteSet; + } + + // This is where the magic happens. We return a new particle each time this method is called! + // The type of the first parameter matches the generic type passed to the super interface. + @Override + public Particle createParticle(SimpleParticleType type, ClientLevel level, + double x, double y, double z, double xSpeed, double ySpeed, double zSpeed) { + // We don't use the type and speed, and pass in everything else. You may of course use them if needed. + return new MyParticle(level, x, y, z, spriteSet); + } +} +``` + +然后,您的粒子提供器必须在[客户端][side] [mod bus][modbus] [event] `RegisterParticleProvidersEvent`中与粒子类型关联: + +```java +@SubscribeEvent +public static void registerParticleProviders(RegisterParticleProvidersEvent event) { + // There are multiple ways to register providers, all differing in the functional type they provide in the + // second parameter. For example, #registerSpriteSet represents a Function>: + event.registerSpriteSet(MyParticleTypes.MY_PARTICLE.get(), MyParticleProvider::new); + // Other methods include #registerSprite, which is essentially a Supplier, + // and #registerSpecial, which maps to a Supplier. See the source code of the event for further info. +} +``` + +### 粒子定义 + +最后,我们必须将我们的粒子类型与一个纹理关联起来。与物品被关联到一个物品模型相似,我们将我们的粒子类型与所谓的粒子定义(或粒子描述)关联起来。粒子定义是`assets//particles`目录中的一个JSON文件,它的名称与粒子类型相同(例如,对于上述示例是`my_particle.json`)。粒子定义JSON的格式如下: + +```json5 +{ + // A list of textures that will be played in order. Will loop if necessary. + // Texture locations are relative to the textures/particle folder. + "textures": [ + "examplemod:my_particle_0", + "examplemod:my_particle_1", + "examplemod:my_particle_2", + "examplemod:my_particle_3" + ] +} +``` + +请注意,仅当使用精灵集粒子时才需要粒子定义文件。单精灵粒子直接映射到`assets//textures/particle/.png`的纹理文件,特殊粒子提供器可以做任何你想做的事情。 + +:::danger +不匹配的精灵集粒子工厂列表和粒子定义文件,即没有相应粒子工厂的粒子描述,或者反之亦然,将会抛出异常! +::: + +### 数据生成 + +粒子定义文件也可以通过扩展`ParticleDescriptionProvider`并覆写`#addDescriptions()`方法来进行[数据生成][datagen]: + +```java +public class MyParticleDescriptionProvider extends ParticleDescriptionProvider { + // Get the parameters from GatherDataEvent. + public AMParticleDefinitionsProvider(PackOutput output, ExistingFileHelper existingFileHelper) { + super(output, existingFileHelper); + } + + // Assumes that all the referenced particles actually exists. Replace "examplemod" with your mod id. + @Override + protected void addDescriptions() { + // Adds a single sprite particle definition with the file at + // assets/examplemod/textures/particle/my_single_particle.png. + sprite(MyParticleTypes.MY_SINGLE_PARTICLE.get(), new ResourceLocation("examplemod", "my_single_particle")); + // Adds a multi sprite particle definition, with a vararg parameter. Alternatively accepts a list. + spriteSet(MyParticleTypes.MY_MULTI_PARTICLE.get(), + new ResourceLocation("examplemod", "my_multi_particle_0"), + new ResourceLocation("examplemod", "my_multi_particle_1"), + new ResourceLocation("examplemod", "my_multi_particle_2") + ); + // Alternative for the above, appends "_" to the base name given, for the given amount of textures. + spriteSet(MyParticleTypes.MY_ALT_MULTI_PARTICLE.get(), + // The base name. + new ResourceLocation("examplemod", "my_multi_particle"), + // The amount of textures. + 3, + // Whether to reverse the list, i.e. start at the last element instead of the first. + false + ); + } +} +``` + +不要忘了向 `GatherDataEvent` 添加提供器: + +```java +@SubscribeEvent +public static void gatherData(GatherDataEvent event) { + DataGenerator generator = event.getGenerator(); + PackOutput output = generator.getPackOutput(); + ExistingFileHelper existingFileHelper = event.getExistingFileHelper(); + + // other providers here + generator.addProvider( + event.includeClient(), + new MyParticleDescriptionProvider(output, existingFileHelper) + ); +} +``` + +### 自定义 `ParticleType` + +虽然在大多数情况下,`SimpleParticleType`就足够了,但有时需要在服务器端为粒子附加额外的数据。这就需要一个自定义的`ParticleType`和一个关联的自定义`ParticleOptions`。让我们从`ParticleOptions`开始,因为这是实际存储信息的地方: + +```java +public class MyParticleOptions implements ParticleOptions { + // Does not need any parameters, but may define any fields necessary for the particle to work. + public MyParticleOptions() {} + + @Override + public void writeToNetwork(FriendlyByteBuf buf) { + // Write your custom info to the given buffer. + } + + @Override + public String writeToString() { + // Return a stringified version of your custom info, for use in commands. + // We don't have any info in this type, so we return the empty string. + return ""; + } + + // The deserializer object to use. We will discuss how to use this in a moment. + public static final ParticleOptions.Deserializer DESERIALIZER = + new ParticleOptions.Deserializer() { + public MyParticleOptions fromCommand(ParticleType type, StringReader reader) + throws CommandSyntaxException { + // You may deserialize things using the given StringReader and pass them to your + // particle options object if needed. + return new MyParticleOptions(); + } + + public MyParticleOptions fromNetwork(ParticleType type, FriendlyByteBuf buf) { + // Similar to above, deserialize any needed info from the given buffer. + return new MyParticleOptions(); + } + }; +} +``` + +然后我们在我们的自定义`ParticleType`中使用这个`ParticleOptions`实现... + +```java +public class MyParticleType extends ParticleType { + // The boolean parameter again determines whether to limit particles at lower particle settings. + // See implementation of the MyParticleTypes class near the top of the article for more information. + public MyParticleType(boolean overrideLimiter) { + // Pass the deserializer to super. + super(overrideLimiter, MyParticleOptions.DESERIALIZER); + } + + // Mojang is moving towards codecs for particle types, so expect the old deserializer approach to vanish soon. + // We define our codec and then return it in the codec() method. Since our example uses no parameters + // for serialization, we use an empty unit codec. Refer to the Codecs article for more information. + public static final Codec CODEC = Codec.unit(new MyParticleOptions()); + + @Override + public Codec codec() { + return CODEC; + } +} +``` + +... 并在注册过程中引用它: + +```java +public static final Supplier MY_CUSTOM_PARTICLE = PARTICLE_TYPES.register( + "my_custom_particle", + () -> new MyParticleType(false)); +``` + +## 生成粒子 + +作为之前的提醒,服务器只知道`ParticleType`和`ParticleOption`,而客户端直接使用与`ParticleType`关联的`ParticleProvider`提供的`Particle`。因此,生成粒子的方式根据你所在的方面有很大的不同。 + +- **通用代码**:调用`Level#addParticle`或`Level#addAlwaysVisibleParticle`。这是创建对所有人都可见的粒子的首选方式。 +- **客户端代码**:使用通用代码方式。或者,选择你喜欢的粒子类创建一个`new Particle()`,并用那个粒子调用`Minecraft.getInstance().particleEngine#add(Particle)`。注意,这种方式添加的粒子只会显示给客户端,因此其他玩家看不到。 +- **服务器代码**:调用`ServerLevel#sendParticles`。在原版中被`/particle`命令使用。 + +[datagen]: ../index.md#data-generation +[event]: ../../concepts/events.md +[modbus]: ../../concepts/events.md#event-buses +[registry]: ../../concepts/registries.md +[side]: ../../concepts/sides.md diff --git a/i18n/zh-Hans/docusaurus-plugin-content-docs/current/resources/client/sounds.md b/i18n/zh-Hans/docusaurus-plugin-content-docs/current/resources/client/sounds.md new file mode 100644 index 000000000..48c46f082 --- /dev/null +++ b/i18n/zh-Hans/docusaurus-plugin-content-docs/current/resources/client/sounds.md @@ -0,0 +1,311 @@ +# 声音 + +虽然声音对于任何事情都不是必需的,但它们可以使模组感觉更加细腻和生动。Minecraft为你提供了各种注册和播放声音的方式,本文将对此进行说明。 + +## 术语 + +Minecraft的声音引擎使用多种术语来指代不同的事物: + +- **声音事件**:声音事件是一个在代码中的触发器,它告诉声音引擎播放某个特定的声音。`SoundEvent`也是你需要注册到游戏中的东西。 +- **声音类别**或**声音源**:声音类别是声音的粗略分组,可以单独切换。声音选项GUI中的滑块代表这些类别:`master`(主音量)、`block`(方块)、`player`(玩家)等等。在代码中,它们可以在`SoundSource`枚举中找到。 +- **声音定义**:将声音事件映射到一个或多个声音对象的映射,加上一些可选的元数据。声音定义位于命名空间的[`sounds.json`文件][soundsjson]中。 +- **声音对象**:由声音文件位置加上一些可选的元数据组成的JSON对象。 +- **声音文件**:磁盘上的声音文件。Minecraft仅支持`.ogg`格式的声音文件。 + +:::danger +由于OpenAL(Minecraft的音频库)的实现方式,为了让你的声音具有衰减效果——即根据玩家与声源的距离声音变小或变大——你的声音文件必须是单声道(单通道)。立体声(多通道)声音文件不会受到衰减的影响,并且总是在玩家的位置播放,这使它们成为环境声音和背景音乐的理想选择。也看看[MC-146721][bug]。 +::: + +## 创建`SoundEvent` + +`SoundEvent`是[注册对象][registration],意味着它们必须通过`DeferredRegister`注册到游戏中,并且是单例的: + +```java +public class MySoundsClass { + // Assuming that your mod id is examplemod + public static final DeferredRegister SOUND_EVENTS = + DeferredRegister.create(BuiltInRegistries.SOUND_EVENT, "examplemod"); + + // All vanilla sounds use variable range events. + public static final Supplier MY_SOUND = SOUND_EVENTS.register( + "my_sound", // must match the resource location on the next line + () -> SoundEvent.createVariableRangeEvent(new ResourceLocation("examplemod", "my_sound")) + ); + + // There is a currently unused method to register fixed range (= non-attenuating) events as well: + public static final Supplier MY_FIXED_SOUND = SOUND_EVENTS.register("my_fixed_sound", + // 16 is the default range of sounds. Be aware that due to OpenAL limitations, + // values above 16 have no effect and will be capped to 16. + () -> SoundEvent.createFixedRangeEvent(new ResourceLocation("examplemod", "my_fixed_sound"), 16) + ); +} +``` + +当然,不要忘记在[模组构造器][modctor]中将你的注册表添加到[模组事件总线][modbus]中: + +```java +public ExampleMod(IEventBus modBus) { + MySoundsClass.SOUND_EVENTS.register(modBus); + // other things here +} +``` + +然后,你就有了一个声音事件! + +## `sounds.json` + +_另见:[Minecraft Wiki][mcwiki]上的[sounds.json][mcwikisounds]_ + +现在,为了将你的声音事件连接到实际的声音文件,我们需要创建声音定义。一个命名空间的所有声音定义都存储在一个名为`sounds.json`的文件中,也就是声音定义文件,直接放在命名空间的根目录下。每个声音定义都是声音事件id(如`my_sound`)到JSON声音对象的映射。注意,声音事件id不指定命名空间,因为这已经由声音定义文件所在的命名空间确定。一个示例的`sounds.json`看起来像这样: + +```json5 +{ + // Sound definition for the sound event "examplemod:my_sound" + "my_sound": { + // List of sound objects. If this contains more than one element, an element will be chosen randomly. + "sounds": [ + // Only name is required, all other properties are optional. + { + // Location of the sound file, relative to the namespace's sounds folder. + // This example references a sound at assets/examplemod/sounds/sound_1.ogg. + "name": "examplemod:sound_1", + // May be "sound" or "event". "sound" causes the name to refer to a sound file. + // "event" causes the name to refer to another sound event. Defaults to "sound". + "type": "sound", + // The volume this sound will be played at. Must be between 0.0 and 1.0 (default). + "volume": 0.8, + // The pitch value the sound will be played at. + // Must be between 0.0 and 2.0. Defaults to 1.0. + "pitch": 1.1, + // Weight of this sound when choosing a sound from the sounds list. Defaults to 1. + "weight": 3, + // If true, the sound will be streamed from the file instead of loaded all at once. + // Recommended for sound files that are more than a few seconds long. Defaults to false. + "stream": true, + // Manual override for the attenuation distance. Defaults to 16. Ignored by fixed range sound events. + "attenuation_distance": 8, + // If true, the sound will be loaded into memory on pack load, instead of when the sound is played. + // Vanilla uses this for underwater ambience sounds. Defaults to false. + "preload": true + }, + // Shortcut for { "name": "examplemod:sound_2" } + "examplemod:sound_2" + ] + }, + "my_fixed_sound": { + // Optional. If true, replaces sounds from other resource packs instead of adding to them. + // See the Merging chapter below for more information. + "replace": true, + // The translation key of the subtitle displayed when this sound event is triggered. + "subtitle": "examplemod.my_fixed_sound", + "sounds": [ + "examplemod:sound_1", + "examplemod:sound_2" + ] + } +} +``` + +### 合并 + +与大多数其他资源文件不同,`sounds.json`文件不会覆盖它们下面的包中的值。相反,它们被合并在一起,然后解释为一个组合的`sounds.json`文件。考虑在两个不同资源包RP1和RP2中的两个`sounds.json`文件里定义了声音`sound_1`、`sound_2`、`sound_3`和`sound_4`,其中RP2位于RP1下面: + +RP1中的`sounds.json`: + +```json5 +{ + "sound_1": { + "sounds": [ + "sound_1" + ] + }, + "sound_2": { + "replace": true, + "sounds": [ + "sound_2" + ] + }, + "sound_3": { + "sounds": [ + "sound_3" + ] + }, + "sound_4": { + "replace": true, + "sounds": [ + "sound_4" + ] + } +} +``` + +RP2中的`sounds.json`: + +```json5 +{ + "sound_1": { + "sounds": [ + "sound_5" + ] + }, + "sound_2": { + "sounds": [ + "sound_6" + ] + }, + "sound_3": { + "replace": true, + "sounds": [ + "sound_7" + ] + }, + "sound_4": { + "replace": true, + "sounds": [ + "sound_8" + ] + } +} +``` + +游戏最终会使用的组合(合并)的`sounds.json`文件,在内存中看起来会像这样(这个文件从不会被写在任何地方): + +```json5 +{ + "sound_1": { + // replace false and false: add from lower pack, then from upper pack + "sounds": [ + "sound_5", + "sound_1" + ] + }, + "sound_2": { + // replace true in upper pack and false in lower pack: add from upper pack only + "sounds": [ + "sound_2" + ] + }, + "sound_3": { + // replace false in upper pack and true in lower pack: add from lower pack, then from upper pack + // Would still discard values from a third resource pack sitting below RP2 + "sounds": [ + "sound_7", + "sound_3" + ] + }, + "sound_4": { + // replace true and true: add from upper pack only + "sounds": [ + "sound_8" + ] + } +} +``` + +## 播放声音 + +Minecraft提供了各种播放声音的方法,有时不清楚应该使用哪一个。所有的方法都接受一个`SoundEvent`,可以是你自己的,也可以是原版的(原版声音事件可以在`SoundEvents`类中找到)。以下的方法描述,客户端和服务器分别指的是[逻辑客户端和逻辑服务器][sides]。 + +### `Level` + +- `playSound(Player player, double x, double y, double z, SoundEvent soundEvent, SoundSource soundSource, float volume, float pitch)` + - 客户端行为:如果传入的玩家是本地玩家,则在给定位置为玩家播放声音事件,否则无操作。 + - 服务器行为:向所有除传入的玩家以外的玩家发送一个数据包,指示客户端在给定位置为玩家播放声音事件。 + - 用法:从将在两侧运行的客户端启动的代码中调用。服务器不会对发起播放的玩家播放声音,以防止对他们播放两次声音事件。或者,从服务器启动的代码(如[block entity][be])中调用,并使用`null`作为玩家,对所有人播放声音。 +- `playSound(Player player, BlockPos pos, SoundEvent soundEvent, SoundSource soundSource, float volume, float pitch)` + - 转发到第一个方法,其中`x`、`y`和`z`分别取`pos.getX() + 0.5`、`pos.getY() + 0.5`和`pos.getZ() + 0.5`的值。 +- `playLocalSound(double x, double y, double z, SoundEvent soundEvent, SoundSource soundSource, float volume, float pitch, boolean distanceDelay)` + - 客户端行为:在给定位置为玩家播放声音。不向服务器发送任何内容。如果`distanceDelay`是`true`,则根据距离玩家的距离延迟声音。 + - 服务器行为:无操作。 + - 用法:从服务器发送的自定义数据包中调用。原版用这个方法播放雷声。 + +### `ClientLevel` + +- `playLocalSound(BlockPos pos, SoundEvent soundEvent, SoundSource soundSource, float volume, float pitch, boolean distanceDelay)` + - 转发到`Level#playLocalSound`,其中`x`、`y`和`z`分别取`pos.getX() + 0.5`、`pos.getY() + 0.5`和`pos.getZ() + 0.5`的值。 + +### `Entity` + +- `playSound(SoundEvent soundEvent, float volume, float pitch)` + - 转发到`Level#playSound`,其中玩家为`null`,声音源为`SoundSource.ENTITY`,实体的位置为x/y/z,其他参数为传入的参数。 + +### `Player` + +- `playSound(SoundEvent soundEvent, float volume, float pitch)` (覆盖`Entity`中的方法) + - 转发到`Level#playSound`,其中玩家为`this`,声音源为`SoundSource.PLAYER`,玩家的位置为x/y/z,其他参数为传入的参数。因此,客户端/服务器的行为模仿`Level#playSound`: + - 客户端行为:在给定位置为客户端玩家播放声音事件。 + - 服务器行为:除了调用此方法的玩家,对给定位置附近的所有人播放声音事件。 + +## 数据生成 + +声音文件本身当然不能被[数据生成][datagen],但是`sounds.json`文件可以。为了做到这一点,我们扩展`SoundDefinitionsProvider`并覆盖`registerSounds()`方法: + +```java +public class MySoundDefinitionsProvider extends SoundDefinitionsProvider { + // Parameters can be obtained from GatherDataEvent. + public MySoundDefinitionsProvider(PackOutput output, ExistingFileHelper existingFileHelper) { + // Use your actual mod id instead of "examplemod". + super(output, "examplemod", existingFileHelper); + } + + @Override + public void registerSounds() { + // Accepts a Supplier, a SoundEvent, or a ResourceLocation as the first parameter. + add(MySoundsClass.MY_SOUND, SoundDefinition.definition() + // Add sound objects to the sound definition. Parameter is a vararg. + .with( + // Accepts either a string or a ResourceLocation as the first parameter. + // The second parameter can be either SOUND or EVENT, and can be omitted if the former. + sound("examplemod:sound_1", SoundDefinition.SoundType.SOUND) + // Sets the volume. Also has a double counterpart. + .volume(0.8f) + // Sets the pitch. Also has a double counterpart. + .pitch(1.2f) + // Sets the weight. + .weight(2) + // Sets the attenuation distance. + .attenuationDistance(8) + // Enables streaming. + // Also has a parameterless overload that defers to stream(true). + .stream(true) + // Enables preloading. + // Also has a parameterless overload that defers to preload(true). + .preload(true), + // The shortest we can get. + sound("examplemod:sound_2") + ) + // Sets the subtitle. + .subtitle("sound.examplemod.sound_1") + // Enables replacing. + .replace(true) + ); + } +} +``` + +与所有数据提供器一样,不要忘记将提供者注册到事件中: + +```java +@SubscribeEvent +public static void gatherData(GatherDataEvent event) { + DataGenerator generator = event.getGenerator(); + PackOutput output = generator.getPackOutput(); + ExistingFileHelper existingFileHelper = event.getExistingFileHelper(); + + // other providers here + generator.addProvider( + event.includeClient(), + new MySoundDefinitionsProvider(output, existingFileHelper) + ); +} +``` + +[bug]: https://bugs.mojang.com/browse/MC-146721 +[datagen]: ../index.md#data-generation +[mcwiki]: https://minecraft.wiki +[mcwikisounds]: https://minecraft.wiki/w/Sounds.json +[modbus]: ../../concepts/events.md#event-buses +[modctor]: ../../gettingstarted/modfiles.md#javafml-and-mod +[registration]: ../../concepts/registries.md +[sides]: ../../concepts/sides.md#the-logical-side +[soundsjson]: #soundsjson diff --git a/i18n/zh-Hans/docusaurus-plugin-content-docs/current/resources/client/textures.md b/i18n/zh-Hans/docusaurus-plugin-content-docs/current/resources/client/textures.md new file mode 100644 index 000000000..896eb020c --- /dev/null +++ b/i18n/zh-Hans/docusaurus-plugin-content-docs/current/resources/client/textures.md @@ -0,0 +1,70 @@ +# 纹理 + +Minecraft中的所有纹理都是PNG文件,位于命名空间的`textures`文件夹内。不支持JPG、GIF和其他图像格式。指向纹理的[资源位置][rl]的路径通常相对于`textures`文件夹,所以例如,资源位置`examplemod:block/example_block`指的是`assets/examplemod/textures/block/example_block.png`路径的纹理文件。 + +纹理的大小通常应该是2的幂,例如16x16或32x32。与旧版本不同,现代Minecraft本身就支持大于16x16的方块和物品纹理大小。对于那些你自己渲染出来的不是2的幂的纹理(例如GUI背景),可以在下一可用的2的幂大小(通常是256x256)创建一个空文件,然后在该文件的左上角添加你的纹理,让文件的其余部分保持空白。然后,可以在使用该纹理的代码中设置实际的纹理大小。 + +## 纹理元数据 + +纹理元数据可以在一个与纹理完全同名的文件中指定,但需要添加一个`.mcmeta`后缀。例如,位于`textures/block/example.png`的动画纹理需要一个伴随的`textures/block/example.png.mcmeta`文件。`.mcmeta`文件有以下格式(所有的都是可选的): + +```json5 +{ + // Whether the texture will be blurred if needed. Defaults to false. + // Currently specified by the codec, but unused otherwise both in the files and in code. + "blur": true, + // Whether the texture will be clamped if needed. Defaults to false. + // Currently specified by the codec, but unused otherwise both in the files and in code. + "clamp": true, + "gui": { + // Specifies how the texture will be scaled if needed. Can be one of these three: + "scaling": "stretch", // default + "scaling": { + "tile": { + "width": 16, + "height": 16 + } + }, + "scaling": { + // Like "tile", but allows specifying the border offsets. + "nine_slice": { + "width": 16, + "height": 16, + // May also be a single int that is used as the value for all four sides. + "border": { + "left": 0, + "top": 0, + "right": 0, + "bottom": 0 + } + } + } + }, + // See below. + "animation": {} +} +``` + +## 动画纹理 + +Minecraft本身支持方块和物品的动画纹理。动画纹理由一个纹理文件组成,不同的动画阶段位于彼此的下方(例如,一个带有8个阶段的动画16x16纹理将通过一个16x128的PNG文件表示)。 + +为了确实被动画化而不仅仅是显示为扭曲的纹理,纹理元数据中必须有一个`animation`对象。子对象可以是空的,但可以包含以下可选条目: + +```json5 +{ + "animation": { + // A custom order in which the frames are played. If omitted, the frames are played top to bottom. + "frames": [1, 0], + // How long one frame stays before switching to the next animation stage, in frames. Defaults to 1. + "frametime": 5, + // Whether to interpolate between animation stages. Defaults to false. + "interpolate": true, + // Width and height of one animation stage. If omitted, uses the texture width for both of these. + "width": 12, + "height": 12 + } +} +``` + +[rl]: ../../misc/resourcelocation.md diff --git a/i18n/zh-Hans/docusaurus-plugin-content-docs/current/resources/index.md b/i18n/zh-Hans/docusaurus-plugin-content-docs/current/resources/index.md new file mode 100644 index 000000000..57215a819 --- /dev/null +++ b/i18n/zh-Hans/docusaurus-plugin-content-docs/current/resources/index.md @@ -0,0 +1,219 @@ +# 资源 + +资源是游戏使用的外部文件,但不包括代码。在 Minecraft 生态系统中,最常见的资源类型是纹理,但还有许多其他类型的资源。当然,所有这些资源都需要代码端的消费系统,因此本节也将对这些系统进行分组。 + +Minecraft 通常有两种资源:客户端资源,称为资产,以及服务器端资源,称为数据。资产主要是显示信息,例如纹理、显示模型、翻译或声音,而数据包括影响游戏玩法的各种内容,如战利品表、配方或世界生成信息。它们分别从资源包和数据包中加载。NeoForge 为每个模组生成内置的资源包和数据包。 + +无论资源包还是数据包,通常都需要一个 [`pack.mcmeta` 文件][packmcmeta],在过去的 Forge 版本中也是如此。然而,NeoForge 为您在运行时生成了这些文件,因此您无需再担心。 + +如果您对某个格式感到困惑,请查看原版资源。您的 NeoForge 开发环境不仅包含原版代码,还包含原版资源。它们可以在 External Resources 部分(IntelliJ)/Project Libraries 部分(Eclipse)中找到,名称为 `ng_dummy_ng.net.minecraft:client:client-extra:`(对于 Minecraft 资源)或 `ng_dummy_ng.net.neoforged:neoforge:`(对于 NeoForge 资源)。 + +## 资产 + +_另见:[Minecraft Wiki 上的资源包][mcwikiresourcepacks]_ + +资产,或客户端资源,是所有仅在[客户端][sides]上相关的资源。它们从资源包中加载,有时也被称为旧术语纹理包(源自旧版本,当时它们只能影响纹理)。资源包基本上是一个 `assets` 文件夹。`assets` 文件夹包含资源包包含的各种命名空间的子文件夹;每个命名空间是一个子文件夹。例如,一个模组的资源包可能包含 `coolmod` 命名空间,但可能还包括其他命名空间,例如 `minecraft`。 + +NeoForge 自动将所有模组资源包收集到 `Mod resources` 包中,该包位于资源包菜单中的 Selected Packs 边的底部。当前无法禁用 `Mod resources` 包。但是,位于 `Mod resources` 包上方的资源包可以覆盖位于其下的资源包中定义的资源。这种机制允许资源包制作者覆盖您的模组资源,并允许模组开发者覆盖 Minecraft 资源。 + +资源包可以包含 [模型][models]、[方块状态文件][bsfile]、[纹理][textures]、[声音][sounds]、[粒子定义][particles] 和 [翻译文件][translations]。 + +## 数据 + +_另见:[Minecraft Wiki 上的数据包][mcwikidatapacks]_ + +与资产不同,数据是所有[服务器][sides]资源的术语。与资源包类似,数据通过数据包加载。像资源包一样,数据包由 [`pack.mcmeta` 文件][packmcmeta] 和一个名为 `data` 的根文件夹组成。然后,同样像资源包一样,`data` 文件夹包含数据包包含的各种命名空间的子文件夹;每个命名空间是一个子文件夹。例如,一个模组的数据包可能包含 `coolmod` 命名空间,但可能还包括其他命名空间,例如 `minecraft`。 + +NeoForge 在创建新世界时自动应用所有模组数据包。当前无法禁用模组数据包。但是,大多数数据文件可以通过具有更高优先级的数据包覆 + +盖(因此可以通过替换为空文件来删除)。通过将数据包放置在世界的 `datapacks` 子文件夹中,然后通过 [`/datapack`][datapackcmd] 命令启用或禁用它们,可以启用或禁用额外的数据包。 + +:::info +目前没有内置的方法将一组自定义数据包应用到每个世界。然而,有许多模组可以实现这一点。 +::: + +数据包可能包含影响以下事物的文件夹: + +| 文件夹名称 | 内容 | +|---------|------| +| `advancements` | [进度][advancements] | +| `damage_type` | 伤害类型 | +| `loot_tables` | [战利品表][loottables] | +| `recipes` | [配方][recipes] | +| `structures` | 结构 | +| `tags` | [标签][tags] | +| `dimension`, `dimension_type`, `worldgen`, `neoforge/biome_modifiers` | 世界生成文件 | +| `neoforge/global_loot_modifiers` | [全局战利品修饰器][glm] | + +此外,它们还可能包含一些与命令集成的系统的子文件夹。这些系统很少与模组一起使用,但无论如何都值得一提: + +| 文件夹名称 | 内容 | +|---------|------| +| `chat_type` | [聊天类型][chattype] | +| `functions` | [功能][function] | +| `item_modifiers` | [物品修饰器][itemmodifier] | +| `predicates` | [条件判断][predicate] | + +## `pack.mcmeta` + +_另见:[Minecraft Wiki 上的 `pack.mcmeta` (资源包)][packmcmetaresourcepack] 和 `pack.mcmeta` (数据包)][packmcmetadatapack]_ + +`pack.mcmeta` 文件保存资源包或数据包的元数据。对于模组来说,NeoForge 让这个文件变得多余,因为 `pack.mcmeta` 是合成生成的。如果您仍需要一个 `pack.mcmeta` 文件,完整的规范可以在链接的 Minecraft Wiki 文章中找到。 + +## 数据生成 + +数据生成,俗称 datagen,是一种以编程方式生成 JSON 资源文件的方式,以避免手动编写它们时的繁琐和容易出错的过程。这个名字有点误导,因为它适用于资产和数据。 + +Datagen 通过为您生成的客户端和服务器运行配置旁的数据运行配置来运行。数据运行配置遵循[模组生命周期][lifecycle],直到注册事件触发之后。然后触发 [`GatherDataEvent`][event],在该事件中,您可以注册您要生成的对象,以数据提供者的形式,将所述对象写入磁盘,并结束过程。 + +所有数据提供者都扩展了 `DataProvider` 接口,通常需要重写一个方法。以下是 Minecraft 和 NeoForge 提供的一些值得注意的数据生成器(链接文章提供了更多信息,如辅助方法): + +| 类 | 方法 | 生成 | 方面 | 备注 | +|---|----|-----|----|----| +| [`BlockStateProvider`][blockstateprovider] | `registerStatesAndModels()` | 方块状态文件,方块模型 | 客户端 | | +| [`ItemModelProvider`][itemmodelprovider] | `registerModels()` | 物品模型 | 客户端 | | +| [`LanguageProvider`][langprovider] | `addTranslations()` | 翻译 | 客户端 | 构造函数还需要传递语言。 | +| [`ParticleDescriptionProvider`][particleprovider] | `addDescriptions()` | 粒子定义 | 客户端 | | +| [`SoundDefinitionsProvider`][soundprovider] | `registerSounds()` | 声音定义 | 客户端 | | +| [`AdvancementProvider`][advancementprovider] | `generate()` | 进度 | 服务器 | 确保使用 NeoForge 变体,而不是 Minecraft 本身 + +。 | +| [`LootTableProvider`][loottableprovider] | `generate()` | 战利品表 | 服务器 | 需要额外的方法和类才能正常工作,详情请参阅链接文章。 | +| [`RecipeProvider`][recipeprovider] | `buildRecipes(RecipeOutput)` | 配方 | 服务器 | | +| [多个 `TagsProvider` 的子类][tagsprovider] | `addTags(HolderLookup.Provider)` | 标签 | 服务器 | 存在几个专门的子类,例如 `BlockTagsProvider`。如果您需要的不存在,请扩展 `TagsProvider`(或适用时扩展 `IntrinsicHolderTagsProvider`),将您的标签类型作为泛型参数。 | +| [`DatapackBuiltinEntriesProvider`][datapackprovider] | N/A | 数据包内置条目,例如世界生成 | 服务器 | 详情请参阅链接文章。 | +| [`DataMapProvider`][datamapprovider] | `gather()` | 数据映射条目 | 服务器 | | +| [`GlobalLootModifierProvider`][glmprovider] | `start()` | 全局战利品修饰器 | 服务器 | | + +所有这些提供者都遵循相同的模式。首先,创建一个子类并添加您自己要生成的资源。然后,在[事件处理器][eventhandler]中添加提供者。使用 `RecipeProvider` 的一个示例: + +```java +public class MyRecipeProvider extends RecipeProvider { + public MyRecipeProvider(PackOutput output) { + super(output); + } + + @Override + protected void buildRecipes(RecipeOutput output) { + // 在这里注册您的配方。 + } +} + +@Mod.EventBusSubscriber(bus = Mod.EventBusSubscriber.Bus.MOD, modid = "examplemod") +public class MyDatagenHandler { + @SubscribeEvent + public static void gatherData(GatherDataEvent event) { + // 数据生成器可能需要这些作为构造函数参数。 + // 详情请参阅下文每个部分。 + DataGenerator generator = event.getGenerator(); + PackOutput output = generator.getPackOutput(); + ExistingFileHelper existingFileHelper = event.getExistingFileHelper(); + CompletableFuture lookupProvider = event.getLookupProvider(); + + // 注册提供者。 + generator.addProvider( + // 一个布尔值,确定是否实际生成数据。 + // 事件提供了确定这一点的方法: + // event.includeClient(), event.includeServer(), + // event.includeDev() 和 event.includeReports()。 + // 由于配方是服务器数据,我们只在服务器数据生成中运行它们。 + event.includeServer(), + // 我们的提供者。 + new MyRecipeProvider(output) + ); + // 在这里注册其他数据提供者。 + } +} +``` + +事件提供了一些上下文供您使用: + +- `event.getGenerator()` 返回您注册提供者的 `DataGenerator`。 +- `event.getPackOutput()` 返回 `PackOutput`,一些提供者用它来确定文件输出位置。 +- `event.getExistingFileHelper()` 返回 `ExistingFileHelper`,用于提供者需要引用其他文件的事物(例如,可以指定父文件的方块模型)。 +- `event.getLookupProvider()` 返回 `CompletableFuture`,主要用于标签和数据生成注册表引用其他尚未存在的元素。 +- `event.includeClient()`、`event.includeServer()`、`event.includeDev()` 和 `event.includeReports()` 是 `boolean` 方法,允许您检查是否启用了特定的命令行参数。 + +### 命令行参数 + +数据生成器可以接受几个命令行参数: + +- `--mod examplemod`: 告诉数据生成器为此模组运行数据生成。NeoGradle 为所属模组 ID 自动添加此项,如果您有多个模组在一个项目中,请添加此项。 +- `--output path/to/folder`: 告诉数据生成器输出到给定文件夹。建议使用 Gradle 的 `file(...).getAbsolutePath()` 为您生成绝对路径(相对于项目根目录的路径)。默认为 `file('src/generated/resources').getAbsolutePath()`。 +- `--existing path/to/folder`: 告诉数据生成器 + +在检查现有文件时考虑给定文件夹。与输出一样,建议使用 Gradle 的 `file(...).getAbsolutePath()`。 +- `--existing-mod examplemod`: 告诉数据生成器在检查现有文件时考虑给定模组的 JAR 文件中的资源。 +- 生成器模式(所有这些都是布尔参数,不需要任何额外的参数): + - `--includeClient`: 是否生成客户端资源(资产)。在运行时检查 `GatherDataEvent#includeClient()`。 + - `--includeServer`: 是否生成服务器资源(数据)。在运行时检查 `GatherDataEvent#includeServer()`。 + - `--includeDev`: 是否运行开发工具。通常不应由模组使用。在运行时检查 `GatherDataEvent#includeDev()`。 + - `--includeReports`: 是否转储注册对象列表。在运行时检查 `GatherDataEvent#includeReports()`。 + - `--all`: 启用所有生成器模式。 + +所有参数可以通过在 `build.gradle` 中添加以下内容来添加到运行配置中: + +```groovy +runs { + // 这里有其他运行配置 + + data { + programArguments.addAll '--arg1', 'value1', '--arg2', 'value2', '--all' // 布尔参数没有值 + } +} +``` + +例如,要复制默认参数,您可以指定以下内容: + +```groovy +runs { + // 这里有其他运行配置 + + data { + programArguments.addAll '--mod', 'examplemod', // 插入您自己的模组 ID + '--output', file('src/generated/resources').getAbsolutePath(), + '--includeClient', + '--includeServer' + } +} +``` + +[advancementprovider]: ../datagen/advancements.md +[advancements]: server/advancements.md +[blockstateprovider]: client/models/datagen.md#block-model-datagen +[bsfile]: client/models/index.md#blockstate-files +[chattype]: https://minecraft.wiki/w/Chat_type +[datamap]: ../datamaps/index.md +[datamapprovider]: ../datamaps/index.md#datagen +[datapackcmd]: https://minecraft.wiki/w/Commands/datapack +[datapackprovider]: ../concepts/registries.md#data-generation-for-datapack-registries +[event]: ../concepts/events.md +[eventhandler]: ../concepts/events.md#registering-an-event-handler +[function]: https://minecraft.wiki/w/Function_(Java_Edition) +[glm]: server/glm.md +[glmprovider]: ../datagen/glm.md +[itemmodelprovider]: client/models/datagen.md#item-model-datagen +[itemmodifier]: https://minecraft.wiki/w/Item_modifier +[langprovider]: client/i18n.md#datagen +[lifecycle]: ../concepts/events.md#the-mod-lifecycle +[loottableprovider]: ../datagen/loottables.md +[loottables]: server/loottables.md +[mcwiki]: https://minecraft.wiki +[mcwikidatapacks]: https://minecraft.wiki/w/Data_pack +[mcwikiresourcepacks]: https://minecraft.wiki/w/Resource_pack +[models]: client/models/index.md +[packmcmeta]: #pack.mcmeta +[packmcmetadatapack]: https://minecraft.wiki/w/Data_pack#pack.mcmeta +[packmcmetaresourcepack]: https://minecraft.wiki/w/Resource_pack#Contents +[particleprovider]: client/particles.md#datagen +[particles]: client/particles.md +[predicate]: https://minecraft.wiki/w/Predicate +[recipeprovider]: ../datagen/recipes.md +[recipes]: server/recipes/index.md +[sides]: ../concepts/sides.md +[soundprovider]: client/sounds.md#datagen +[sounds]: client/sounds.md +[tags]: server/tags.md +[tagsprovider]: ../datagen/tags.md +[textures]: client/textures.md +[translations]: client/i18n.md#language-files diff --git a/i18n/zh-Hans/docusaurus-plugin-content-docs/current/resources/server/advancements.md b/i18n/zh-Hans/docusaurus-plugin-content-docs/current/resources/server/advancements.md new file mode 100644 index 000000000..b5e4d59e5 --- /dev/null +++ b/i18n/zh-Hans/docusaurus-plugin-content-docs/current/resources/server/advancements.md @@ -0,0 +1,167 @@ +# 成就 + +成就是玩家可以完成的任务,可能会推进游戏的进程。玩家可能直接参与的任何动作都可以触发成就。 + +所有原版内的成就实现都通过JSON进行数据驱动。这意味着创建新的成就不需要mod,只需要一个[data pack][datapack]。关于如何创建并将这些成就放入mod的`resources`的完整列表,可以在[Minecraft Wiki][wiki]上找到。此外,根据存在的信息(加载的模组,存在的项目等),成就可以有条件地[加载和默认][conditional]。和其他数据驱动的功能一样,成就可以通过[data generators][datagen]来生成。 + +## 成就条件 + +为了解锁一个成就,必须满足指定的条件。条件通过触发器进行跟踪,当执行某个动作时触发:杀死实体,改变库存,繁殖动物等。每当一个成就被加载到游戏中,定义的条件就会被读取并添加为触发器的监听器。然后调用一个触发器函数(通常命名为`#trigger`),检查所有监听器是否当前状态满足成就条件。只有在完成所有要求并获得成就后,成就的条件监听器才会被移除。 + +要求被定义为一个字符串数组的数组,表示在成就上指定的条件的名称。一旦满足一个条件的字符串数组,成就就完成了: + +```js +// In some advancement JSON + +// List of defined criteria to meet +"criteria": { + "example_criterion1": { /*...*/ }, + "example_criterion2": { /*...*/ }, + "example_criterion3": { /*...*/ }, + "example_criterion4": { /*...*/ } +}, + +// This advancement is only unlocked once +// - Criteria 1 AND 2 have been met +// OR +// - Criteria 3 and 4 have been met +"requirements": [ + [ + "example_criterion1", + "example_criterion2" + ], + [ + "example_criterion3", + "example_criterion4" + ] +] +``` + +原版定义的条件触发器列表可以在`CriteriaTriggers`中找到。此外,JSON格式在[Minecraft Wiki][triggers]上有定义。 + +### 自定义条件触发器 + +自定义条件触发器由两部分组成:触发器,它在你指定的某个时候在代码中被激活,通过调用`#trigger`,和实例,它定义了触发器应在何种条件下授予条件。触发器扩展了`SimpleCriterionTrigger`,而实例实现了`SimpleCriterionTrigger.SimpleInstance`。泛型值`T`代表触发器实例类型。 + +### The SimpleCriterionTrigger.SimpleInstance实现 + +`SimpleCriterionTrigger.SimpleInstance`代表在`criteria`对象中定义的单个条件。触发器实例负责保存定义的条件,并返回输入是否匹配条件。 + +条件通常通过构造函数传入。`SimpleCriterionTrigger.SimpleInstance`接口只需要一个函数,名为`#player`,它返回玩家必须满足的条件,作为一个`Optional`。如果子类是一个Java记录,有一个这种类型的`player`参数(如下所示),那么自动生成的`#player`方法就足够了。 + +```java +public record ExampleTriggerInstance(Optional player, ItemPredicate item) implements SimpleCriterionTrigger.SimpleInstance { + // extra methods here +} +``` + +:::note +通常,触发器实例具有静态辅助方法,这些方法可以根据实例的参数构造完整的`Criterion`对象。这使得这些实例可以在数据生成期间轻松创建,但这是可选的。 + +```java +// In this example, EXAMPLE_TRIGGER is a DeferredHolder> +public static Criterion instance(ContextAwarePredicate player, ItemPredicate item) { + return EXAMPLE_TRIGGER.get().createCriterion(new ExampleTriggerInstance(Optional.of(player), item)); +} +``` +::: + +最后,应该添加一个方法,该方法输入当前的数据状态,并返回用户是否满足了必要的条件。玩家的条件已经通过`SimpleCriterionTrigger#trigger(ServerPlayer, Predicate)`进行了检查。大多数触发器实例将这个方法称为`#matches`。 + +```java +// This method is unique for each instance and is as such not overridden +public boolean matches(ItemStack stack) { + // Since ItemPredicate matches a stack, a stack is the input + return this.item.matches(stack); +} +``` + +### SimpleCriterionTrigger + +`SimpleCriterionTrigger`子类负责指定一个编解码器来[序列化]触发器实例`T`,并提供一个方法来检查触发器实例并在成功时运行已附加的监听器。 + +后者是通过定义一个方法来检查所有的触发器实例,并在满足条件时运行监听器来完成的。该方法接收`ServerPlayer`和`SimpleCriterionTrigger.SimpleInstance`子类中匹配方法定义的其他数据。该方法应内部调用`SimpleCriterionTrigger#trigger`以正确处理检查所有监听器。大多数触发器实例将这个方法称为`#trigger`。 + +```java +// This method is unique for each trigger and is as such not a method to override +public void trigger(ServerPlayer player, ItemStack stack) { + this.trigger(player, + // The condition checker method within the SimpleCriterionTrigger.SimpleInstance subclass + triggerInstance -> triggerInstance.matches(stack) + ); +} +``` + +最后,实例必须在`Registries.TRIGGER_TYPE`注册表上注册。关于如何进行注册的技巧可以在[Registries][registration]下找到。 + +### 序列化 + +必须定义一个[编解码器]来序列化和反序列化触发器实例。原版通常在实例实现中创建这个编解码器作为一个常量,然后通过触发器的`#codec`方法返回。 + + +```java +class ExampleTrigger extends SimpleCriterionTrigger { + @Override + public Codec codec() { + return ExampleTriggerInstance.CODEC; + } + // ... + public class ExampleTriggerInstance implements SimpleCriterionTrigger.SimpleInstance { + public static final Codec CODEC = ...; + // ... + } +} +``` + +对于之前提到的带有`ContextAwarePredicate`和`ItemPredicate`的记录示例,编解码器可以是: +```java +RecordCodecBuilder.create(instance -> instance.group( + ExtraCodecs.strictOptionalField(EntityPredicate.ADVANCEMENT_CODEC, "player").forGetter(ExampleTriggerInstance::player), + ItemPredicate.CODEC.fieldOf("item").forGetter(ExampleTriggerInstance::item) +).apply(instance, ExampleTriggerInstance::new)); +`````` + +### 调用触发器 + +每当执行正在检查的动作时,应该调用由`SimpleCriterionTrigger`子类定义的`#trigger`方法。 + +```java +// In some piece of code where the action is being performed +// Again, EXAMPLE_TRIGGER is a supplier for the registered instance of the custom criteria trigger +public void performExampleAction(ServerPlayer player, ItemStack stack) { + // Run code to perform action + EXAMPLE_TRIGGER.get().trigger(player, stack); +} +``` + +## 成就奖励 + +当一个成就完成时,可能会给予奖励。这些奖励可以是经验点数、战利品表、食谱书中的食谱,或者作为创造者玩家执行的[函数]的组合。 + +```js +// In some advancement JSON +"rewards": { + "experience": 10, + "loot": [ + "minecraft:example_loot_table", + "minecraft:example_loot_table2" + // ... + ], + "recipes": [ + "minecraft:example_recipe", + "minecraft:example_recipe2" + // ... + ], + "function": "minecraft:example_function" +} +``` + +[datapack]: https://minecraft.wiki/w/Data_pack +[wiki]: https://minecraft.wiki/w/Advancement/JSON_format +[conditional]: ./conditional.md#implementations +[function]: https://minecraft.wiki/w/Function_(Java_Edition) +[triggers]: https://minecraft.wiki/w/Advancement/JSON_format#List_of_triggers +[datagen]: ../../datagen/server/advancements.md#advancement-generation +[codec]: ../../datastorage/codecs.md +[registration]: ../../concepts/registries.md#methods-for-registering +[serialize]: #serialization diff --git a/i18n/zh-Hans/docusaurus-plugin-content-docs/current/resources/server/conditional.md b/i18n/zh-Hans/docusaurus-plugin-content-docs/current/resources/server/conditional.md new file mode 100644 index 000000000..35628fae5 --- /dev/null +++ b/i18n/zh-Hans/docusaurus-plugin-content-docs/current/resources/server/conditional.md @@ -0,0 +1,176 @@ +# Conditionally-Loaded Data + +There are times when modders may want to include data-driven objects using information from another mod without having to explicitly make that mod a dependency. Other cases may be to swap out certain objects with other modded entries when they are present. This can be done through the conditional subsystem. + +## Implementations + +Conditions are loaded from a top-level `neoforge:conditions` array of objects that represent the conditions to check. If all conditions specified are met, the JSON file will be loaded, otherwise it will be discarded. + +```js +{ + "neoforge:conditions": [ + // Condition 1 + { + + }, + // Condition 2 + { + + } + ], + + // The rest of the object + ... +} +``` + +Conditionally-loaded recipes additionally have wrappers for [data generation][datagen] through `RecipeOutput#withConditions`. Other generators (like the data map one) will usually have a vararg `ICondition...` array in their methods for adding conditions to objects. + +Currently, conditions are supported by the following Vanilla objects: +- recipes +- advancements +- dynamic registries (i.e. biomes) +- loot tables - individual pools can also have their own conditions + +:::note +Loot tables that do not meet their loading conditions will not be ignored, but rather replaced with an empty loot table. +::: + +```js title="Example recipe that will only be loaded if the examplemod mod is loaded" +{ + // highlight-start + "neoforge:conditions": [ + { + "type": "neoforge:mod_loaded", + "modid": "examplemod" + } + ], + // highlight-end + + "type": "minecraft:crafting_shaped", + "category": "redstone", + "key": { + "#": { + "item": "examplemod:example_planks" + } + }, + "pattern": [ + "##", + "##", + "##" + ], + "result": { + "count": 3, + "item": "mymod:compat_door" + } +} +``` + +## Conditions + +### True and False + +Boolean conditions consist of no data and return the expected value of the condition. They are represented by `neoforge:true` and `neoforge:false`. + +```js +// For some condition +{ + // Will always return true (or false for 'neoforge:false') + "type": "neoforge:true" +} +``` + +### Not, And, and Or + +Boolean operator conditions consist of the condition(s) being operated upon and apply the following logic. They are represented by `neoforge:not`, `neoforge:and`, and `neoforge:or`. + + +```js +// For some condition +{ + // Inverts the result of the stored condition + "type": "neoforge:not", + "value": { + // A condition + } +} +``` + +```js +// For some condition +{ + // ANDs the stored conditions together (or ORs for 'neoforge:or') + "type": "neoforge:and", + "values": [ + { + // First condition + }, + { + // Second condition to be ANDed (or ORed for 'neoforge:or') + } + ] +} +``` + +### Mod Loaded + +`ModLoadedCondition` returns true whenever the specified mod with the given id is loaded in the current application. This is represented by `neoforge:mod_loaded`. + +```js +// For some condition +{ + "type": "neoforge:mod_loaded", + // Returns true if 'examplemod' is loaded + "modid": "examplemod" +} +``` + +### Item Exists + +`ItemExistsCondition` returns true whenever the given item has been registered in the current application. This is represented by `neoforge:item_exists`. + +```js +// For some condition +{ + "type": "neoforge:item_exists", + // Returns true if 'examplemod:example_item' has been registered + "item": "examplemod:example_item" +} +``` + +### Tag Empty + +`TagEmptyCondition` returns true whenever the given item tag has no items within it. This is represented by `neoforge:tag_empty`. + +```js +// For some condition +{ + "type": "neoforge:tag_empty", + // Returns true if 'examplemod:example_tag' is an item tag with no entries + "tag": "examplemod:example_tag" +} +``` + +## Creating Custom Conditions + +Custom conditions can be created by implementing `ICondition` and creating a [Codec] for it. + +### ICondition + +A condition needs to implement the `ICondition#test(IContext)` method. This method will return `true` if the object should be loaded, and `false` otherwise. + +:::note +Every `#test` has access to some `IContext` representing the state of the game. Currently, this only allows obtaining tags from a registry. +::: + +:::info +Some objects may be loaded earlier than tags. In those cases, the condition context will be `IContext.EMPTY`. +::: + +The `ICondition#codec` method should return the codec used to encode and decode the condition. This codec **must** be [registered] to the `NeoForgeRegistries#CONDITION_SERIALIZERS` registry. The name the codec is registered under will be the name used to refer to that condition in the `type` field. + + +[datagen]: ../../datagen/server/recipes.md +[condition]: #icondition +[Codec]: ../../datastorage/codecs +[registered]: ../../concepts/registries \ No newline at end of file diff --git a/i18n/zh-Hans/docusaurus-plugin-content-docs/current/resources/server/glm.md b/i18n/zh-Hans/docusaurus-plugin-content-docs/current/resources/server/glm.md new file mode 100644 index 000000000..91058726f --- /dev/null +++ b/i18n/zh-Hans/docusaurus-plugin-content-docs/current/resources/server/glm.md @@ -0,0 +1,148 @@ +全局战利品修改器 (Global Loot Modifiers) +=========== + +全局战利品修改器是一种数据驱动的方法,用于处理收获物品的修改,无需覆盖数十到数百个原版战利品表,或处理可能需要与其他模组的战利品表互动而不知道哪些模组可能已加载的效果。全局战利品修改器也是叠加的,而不是最后加载胜出的方式,类似于标签。 + +注册全局战利品修改器 +------------------------------- + +您将需要做4件事: + +1. 创建一个`global_loot_modifiers.json`。 + * 这将告诉Forge有关您的修改器,并且其工作方式类似于[标签]。 +2. 表示您的修改器的序列化json。 + * 这将包含有关您的修改的所有数据,并允许数据包调整您的效果。 +3. 一个扩展了`IGlobalLootModifier`的类。 + * 使您的修改器工作的操作代码。大多数模组开发者可以扩展`LootModifier`,因为它提供了基本功能。 +4. 最后,一个编解码器来编码和解码您的操作类。 + * 这被[注册]为任何其他`IForgeRegistryEntry`。 + +`global_loot_modifiers.json` +------------------------------- + +`global_loot_modifiers.json`代表要加载到游戏中的所有战利品修改器。这个文件**必须**被放置在`data/forge/loot_modifiers/global_loot_modifiers.json`内。 + +:::danger +`global_loot_modifiers.json`将只在`forge`命名空间下读取。如果文件位于模组的命名空间下,则会被忽略。 +::: + +`entries`是将要加载的修改器的*有序列表*。指定的[ResourceLocation][resloc]指向`data//loot_modifiers/.json`内的相关条目。这主要与数据包制作者相关,用于解决来自不同模组的修改器之间的冲突。 + +`replace`为`true`时,会将从在全局列表中追加战利品修改器的行为改为完全替换全局列表条目。模组开发者会想使用`false`来与其他模组的实现兼容。数据包制作者可能会想使用`true`来指定他们的覆盖项。 + +```js +{ + "replace": false, // Must be present + "entries": [ + // Represents a loot modifier in 'data/examplemod/loot_modifiers/example_glm.json' + "examplemod:example_glm", + "examplemod:example_glm2" + // ... + ] +} +``` + +序列化的JSON +------------------------------- + +此文件包含与您的修改器相关的所有可能变量,包括在修改任何战利品之前必须满足的条件。应尽可能避免硬编码的值,以便数据包制作者可以在需要时调整平衡。 + +`type`代表用来读取关联的JSON文件的[编解码器]的注册名称。这必须始终存在。 + +`conditions`应该代表此修改器激活的战利品表条件。条件应尽量避免硬编码,以便数据包创建者可以尽可能灵活地调整条件。这也必须始终存在。 + +:::caution +虽然`conditions`应该代表激活修改器所需的条件,但只有在使用捆绑的Forge类时才是这种情况。如果使用`LootModifier`作为子类,所有的条件将被**并在一起**,并检查是否应该应用修改器。 +::: + +也可以指定由修改器定义并由序列化器读取的任何其他属性。 + +```js +// Within data/examplemod/loot_modifiers/example_glm.json +{ + "type": "examplemod:example_loot_modifier", + "conditions": [ + // Normal loot table conditions + // ... + ], + "prop1": "val1", + "prop2": 10, + "prop3": "minecraft:dirt" +} +``` + +`IGlobalLootModifier` +--------------------- + +为了提供全局战利品修改器指定的功能,必须指定一个`IGlobalLootModifier`的实现。这些是每次序列化器从JSON解码信息并将其提供到此对象时生成的实例。 + +为了创建一个新的修改器,需要定义两个方法:`#apply`和`#codec`。`#apply`接收将要生成的当前战利品以及上下文信息,如当前级别或额外定义的参数。它返回要生成的掉落物列表。 + +:::info +从任一修改器返回的掉落物列表将按照它们注册的顺序提供给其他修改器。因此,修改过的战利品可以被其他战利品修改器修改。 +::: + +`#codec`返回已注册的[编解码器],用于将修改器编码并解码为JSON。 + +### `LootModifier`子类 + +`LootModifier`是`IGlobalLootModifier`的一个抽象实现,它提供了大多数模组开发者可以轻松扩展和实现的基本功能。这在现有的接口上进行了扩展,通过定义`#apply`方法来检查条件,以确定是否要修改生成的战利品。 + +在子类实现中有两件事需要注意:一个是必须接受一个`LootItemCondition`数组的构造器,另一个是`#doApply`方法。 + +`LootItemCondition`数组定义了在战利品可以被修改之前必须为真的条件列表。提供的条件将被**并在一起**,这意味着所有的条件都必须为真。 + +`#doApply`方法的工作方式与`#apply`方法相同,只是它只在所有条件返回真时才执行。 + +```java +public class ExampleModifier extends LootModifier { + + public ExampleModifier(LootItemCondition[] conditionsIn, String prop1, int prop2, Item prop3) { + super(conditionsIn); + // Store the rest of the parameters + } + + @NotNull + @Override + protected ObjectArrayList doApply(ObjectArrayList generatedLoot, LootContext context) { + // Modify the loot and return the new drops + } + + @Override + public Codec codec() { + // Return the codec used to encode and decode this modifier + } +} +``` + +战利品修改器编解码器 +----------------------- + +将JSON与`IGlobalLootModifier`实例连接起来的是一个[`Codec`][codecdef],其中`T`代表要使用的`IGlobalLootModifier`的类型。 + +为了方便起见,已经提供了一个战利品条件编解码器,可以通过`LootModifier#codecStart`轻松地添加到类似记录的编解码器中。这用于关联战利品修改器的[数据生成][datagen]。 + +```java +// For some DeferredRegister> REGISTRAR +public static final RegistryObject> = REGISTRAR.register("example_codec", () -> + RecordCodecBuilder.create( + inst -> LootModifier.codecStart(inst).and( + inst.group( + Codec.STRING.fieldOf("prop1").forGetter(m -> m.prop1), + Codec.INT.fieldOf("prop2").forGetter(m -> m.prop2), + ForgeRegistries.ITEMS.getCodec().fieldOf("prop3").forGetter(m -> m.prop3) + ) + ).apply(inst, ExampleModifier::new) + ) +); +``` + +[示例][examples]可以在Forge Git仓库中找到,包括精准采集和烧炼效果的示例。 + +[tags]: ./tags.md +[resloc]: ../../misc/resourcelocation.md +[codec]: #the-loot-modifier-codec +[registered]: ../../concepts/registries.md#methods-for-registering +[codecdef]: ../../datastorage/codecs.md +[datagen]: ../../datagen/server/glm.md +[examples]: https://github.com/neoforged/NeoForge/blob/1.20.x/tests/src/main/java/net/neoforged/neoforge/debug/loot/GlobalLootModifiersTest.java diff --git a/i18n/zh-Hans/docusaurus-plugin-content-docs/current/resources/server/index.md b/i18n/zh-Hans/docusaurus-plugin-content-docs/current/resources/server/index.md new file mode 100644 index 000000000..6c50b1c47 --- /dev/null +++ b/i18n/zh-Hans/docusaurus-plugin-content-docs/current/resources/server/index.md @@ -0,0 +1,14 @@ +数据包 (Datapacks) +========= +在1.13版本中,Mojang向基础游戏中添加了[数据包][datapack]。它们允许通过`data`目录修改逻辑服务器的文件。这包括进度、战利品表、结构、配方、标签等。Forge和您的模组也可以有数据包。因此,任何用户都可以修改在此目录内定义的所有配方、战利品表和其他数据。 + +### 创建一个数据包 +数据包存储在项目资源中的`data`目录内。 +您的模组可以有多个数据域,因为您可以添加或修改已存在的数据包,比如原版的、Forge的或其他模组的。 +然后,您可以按照[这里][createdatapack]找到的步骤创建任何数据包。 + +额外阅读资料:[资源位置][resourcelocation] + +[datapack]: https://minecraft.wiki/w/Data_pack +[createdatapack]: https://minecraft.wiki/w/Tutorials/Creating_a_data_pack +[resourcelocation]: ../../misc/resourcelocation.md diff --git a/i18n/zh-Hans/docusaurus-plugin-content-docs/current/resources/server/loottables.md b/i18n/zh-Hans/docusaurus-plugin-content-docs/current/resources/server/loottables.md new file mode 100644 index 000000000..99c785315 --- /dev/null +++ b/i18n/zh-Hans/docusaurus-plugin-content-docs/current/resources/server/loottables.md @@ -0,0 +1,110 @@ +战利品表 (Loot Tables) +=========== + +战利品表是控制在不同动作或场景发生时应该发生什么的逻辑文件。虽然原版系统纯粹与物品生成有关,但这个系统可以扩展以执行任意数量的定义动作。 + +数据驱动的表 +------------------ + +原版中的大多数战利品表都是通过JSON数据驱动的。这意味着创建一个新的战利品表不需要模组,只需要一个[数据包][datapack]。有关如何创建这些战利品表并将它们放入模组的`resources`文件夹的完整列表可以在[Minecraft Wiki][wiki]上找到。 + +使用战利品表 +------------------ + +战利品表通过其`ResourceLocation`引用,它指向`data//loot_tables/.json`。可以使用`LootDataResolver#getLootTable`获取与引用相关联的`LootTable`,其中`LootDataResolver`可以通过`MinecraftServer#getLootData`获得。 + +战利品表总是带有给定的参数生成的。`LootParams`包含生成表的等级、用于更好生成的幸运值、定义场景上下文的`LootContextParam`,以及在激活时应进行的任何动态信息。`LootParams`可以使用`LootParams$Builder`构造器的构造函数创建,并通过向`LootParams$Builder#create`传入`LootContextParamSet`构建。 + +战利品表也可能有一些上下文。`LootContext`接受构建的`LootParams`,并可以设置一些随机种子实例。上下文是通过构建器`LootContext$Builder`创建的,并通过向`LootContext$Builder#create`传入一个可为空的`ResourceLocation`来构建,代表要使用的随机实例。 + +`LootTable`可以使用可用的方法之一生成`ItemStack`,这些方法可能接受`LootParams`或`LootContext`: + +方法 | 描述 +:---: | :--- +`getRandomItemsRaw` | 消费战利品表生成的物品。 +`getRandomItems` | 返回战利品表生成的物品。 +`fill` | 用生成的战利品填充一个容器。 + +:::note +战利品表是为生成物品而构建的,所以这些方法期望对`ItemStack`进行一些处理。 +::: + +额外功能 +------------------- + +Forge为战利品表提供了一些额外的行为,以更好地控制系统。 + +### `LootTableLoadEvent` + +`LootTableLoadEvent`是在Forge事件总线上触发的一个[事件],每当一个战利品表被加载时就会触发。如果事件被取消,则将加载一个空的战利品表。 + +:::info +不要通过这个事件修改战利品表的掉落。这些修改应该使用[全局战利品修改器][glm]来完成。 +::: + +### 战利品池名称 + +可以使用`name`键命名战利品池。任何未命名的战利品池将以`custom#`为前缀,后面跟着池的哈希码。 + +```js +// For some loot pool +{ + "name": "example_pool", // Pool will be named 'example_pool' + "rolls": { + // ... + }, + "entries": { + // ... + } +} +``` + +### 掠夺修饰符 + +现在,战利品表除了掠夺附魔以外,还受到Forge事件总线上的`LootingLevelEvent`的影响。 + +### 额外的上下文参数 + +Forge扩展了某些参数集来考虑可能适用的缺失上下文。`LootContextParamSets#CHEST`现在允许使用`LootContextParams#KILLER_ENTITY`,因为宝藏矿车是可以被破坏(或“杀死”)的实体。`LootContextParamSets#FISHING`也允许使用`LootContextParams#KILLER_ENTITY`,因为钓鱼钩也是一个实体,当玩家收回它时,它被收回(或“杀死”)。 + +### 多项物品熔炼 + +使用`SmeltItemFunction`时,熔炼的配方现在会返回结果中的实际物品数量,而不是单个熔炼物品(例如,如果熔炼配方返回3个物品,而且有3个掉落,那么结果将是9个熔炼物品,而不是3个)。 + +### 战利品表ID条件 + +Forge添加了一个额外的`LootItemCondition`,它允许某些物品为特定表生成。这通常在[全局战利品修改器][glm]中使用。 +```js +// In some loot pool or pool entry +{ + "conditions": [ + { + "condition": "forge:loot_table_id", + // Will apply when the loot table is for dirt + "loot_table_id": "minecraft:blocks/dirt" + } + ] +} +``` + +### 工具是否能执行动作条件 + +Forge添加了一个额外的`LootItemCondition`,用于检查给定的`LootContextParams#TOOL`是否能执行指定的`ToolAction`。这允许战利品表更精确地根据玩家使用的工具来调整掉落物,不仅限于工具的类型,还包括其能够执行的动作,例如挖掘、砍伐等。 + +```js +// In some loot pool or pool entry +{ + "conditions": [ + { + "condition": "forge:can_tool_perform_action", + // Will apply when the tool can strip a log like an axe + "action": "axe_strip" + } + ] +} +``` + +[datapack]: https://minecraft.wiki/w/Data_pack +[wiki]: https://minecraft.wiki/w/Loot_table +[event]: ../../concepts/events.md#registering-an-event-handler +[glm]: ./glm.md diff --git a/i18n/zh-Hans/docusaurus-plugin-content-docs/current/resources/server/recipes/custom.md b/i18n/zh-Hans/docusaurus-plugin-content-docs/current/resources/server/recipes/custom.md new file mode 100644 index 000000000..fdf8a7fdd --- /dev/null +++ b/i18n/zh-Hans/docusaurus-plugin-content-docs/current/resources/server/recipes/custom.md @@ -0,0 +1,131 @@ +自定义配方 +============== + +每个配方定义都由三个组件组成:持有数据和处理提供的输入的执行逻辑的`Recipe`实现,表示配方将在其中使用的类别或上下文的`RecipeType`,以及处理配方数据的解码和网络通信的`RecipeSerializer`。如何选择使用配方取决于实现者。 + +配方 +------ + +`Recipe`接口描述了配方数据和执行逻辑。这包括匹配输入和提供相关的结果。由于配方子系统默认执行物品转换,因此通过`Container`子类型提供输入。 + +:::note +应该将传入配方的`Container`视为其内容是不可变的。任何可变操作都应该在输入的副本上执行,通过`ItemStack#copy`。 +::: + +要能够从管理器获取配方实例,`#matches`必须返回true。此方法检查提供的容器,看看相关的输入是否有效。可以通过调用`Ingredient#test`来使用`Ingredient`进行验证。 + +如果选择了配方,那么就使用`#assemble`进行构建,这可能使用来自输入的数据来创建结果。 + +:::note +`#assemble`应该总是产生一个唯一的`ItemStack`。如果不确定`#assemble`是否这样做,那么在返回之前在结果上调用`ItemStack#copy`。 +::: + +大多数其他的方法纯粹是为了与配方书的集成。 + +```java +public record ExampleRecipe(Ingredient input, int data, ItemStack output) implements Recipe { + // Implement methods here +} +``` + +:::note +虽然在上面的例子中使用了记录(record),但在您自己的实现中不要求这样做。 +::: + +RecipeType +---------- + +`RecipeType`负责定义配方将在其中使用的类别或上下文。例如,如果一个配方要在熔炉中熔炼,它会有一个`RecipeType#SMELTING`的类型。在高炉中爆炸熔炼将会有一个`RecipeType#BLASTING`的类型。 + +如果没有现有的类型与配方将要使用的上下文匹配,那么必须[注册][forge]一个新的`RecipeType`。 + +然后,新的配方子类型必须通过`Recipe#getType`返回`RecipeType`实例。 + +```java +// For some RegistryObject EXAMPLE_TYPE +// In ExampleRecipe +@Override +public RecipeType getType() { + return EXAMPLE_TYPE.get(); +} +``` + +RecipeSerializer +---------------- + +`RecipeSerializer`负责解码JSON文件,并且负责与网络上相关的`Recipe`子类型进行通信。每个由序列化器解码的配方被保存为`RecipeManager`内的一个唯一实例。必须[注册][forge]一个`RecipeSerializer`。 + +对于`RecipeSerializer`,只需要实现三个方法: + + 方法 | 描述 + :---: | :--- +fromJson | 将JSON解码为`Recipe`子类型。 +toNetwork | 将`Recipe`编码到缓冲区以发送给客户端。配方标识符不需要被编码。 +fromNetwork | 从服务器发送的缓冲区解码`Recipe`。配方标识符不需要被解码。 + +然后,`RecipeSerializer`实例必须通过新配方子类型的`Recipe#getSerializer`返回。 + +```java +// For some RegistryObject EXAMPLE_SERIALIZER +// In ExampleRecipe +@Override +public RecipeSerializer getSerializer() { + return EXAMPLE_SERIALIZER.get(); +} +``` + +:::tip +有一些有用的方法可以使阅读和写入配方数据更加容易。`Ingredient`可以使用`#fromJson`、`#toNetwork`和`#fromNetwork`,而`ItemStack`可以使用`CraftingHelper#getItemStack`、`FriendlyByteBuf#writeItem`和`FriendlyByteBuf#readItem`。 +::: + +构建JSON +----------------- + +自定义配方JSON存储在与其他[配方][json]相同的地方。指定的`type`应表示**配方序列化器**的注册名。在解码过程中,序列化器指定任何额外的数据。 +```js +{ + // The custom serializer registry name + "type": "examplemod:example_serializer", + "input": { + // Some ingredient input + }, + "data": 0, // Some data wanted for the recipe + "output": { + // Some stack output + } +} +``` + +非物品逻辑 +-------------- + +如果物品不被用作配方的输入或结果的一部分,那么[`RecipeManager`][manager]提供的正常方法将不会有用。相反,应该向自定义`Recipe`实例添加一个额外的方法,用于测试配方的有效性和/或提供结果。从那里,可以通过`RecipeManager#getAllRecipesFor`获取特定`RecipeType`的所有配方,然后使用新实现的方法检查和/或提供结果。 + +```java +// In some Recipe subimplementation ExampleRecipe + +// Checks the block at the position to see if it matches the stored data +boolean matches(Level level, BlockPos pos); + +// Creates the block state to set the block at the specified position to +BlockState assemble(RegistryAccess access); + +// In some manager class +public Optional getRecipeFor(Level level, BlockPos pos) { + return level.getRecipeManager() + .getAllRecipesFor(exampleRecipeType) // Gets all recipes + .stream() // Looks through all recipes for types + .filter(recipe -> recipe.matches(level, pos)) // Checks if the recipe inputs are valid + .findFirst(); // Finds the first recipe whose inputs match +} +``` + +数据生成 +--------------- + +所有自定义配方,无论输入或输出数据如何,都可以使用`RecipeProvider`将其创建为用于[数据生成][datagen]的`FinishedRecipe`。 + +[forge]: ../../../concepts/registries.md#methods-for-registering +[json]: https://minecraft.wiki/w/Recipe#JSON_format +[manager]: ./index.md#recipe-manager +[datagen]: ../../../datagen/server/recipes.md#custom-recipe-serializers diff --git a/i18n/zh-Hans/docusaurus-plugin-content-docs/current/resources/server/recipes/incode.md b/i18n/zh-Hans/docusaurus-plugin-content-docs/current/resources/server/recipes/incode.md new file mode 100644 index 000000000..907a03d39 --- /dev/null +++ b/i18n/zh-Hans/docusaurus-plugin-content-docs/current/resources/server/recipes/incode.md @@ -0,0 +1,65 @@ +非数据包配方 +==================== + +并非所有配方都足够简单或已迁移到使用数据驱动的配方。一些子系统仍需要在代码库中进行修补,以支持添加新的配方。 + +酿造配方 +--------------- + +酿造是仍然存在于代码中的少数配方之一。酿造配方作为`PotionBrewing`内的引导程序的一部分添加,用于其容器、容器配方和药水混合。为了扩展现有系统,Forge允许通过在`FMLCommonSetupEvent`中调用`BrewingRecipeRegistry#addRecipe`来添加酿造配方。 + +:::caution +`BrewingRecipeRegistry#addRecipe`必须在同步工作队列中通过`#enqueueWork`调用,因为该方法不是线程安全的。 +::: + +默认实现接受一个输入成分,一个催化剂成分,和一个堆叠输出以进行标准实现。此外,也可以提供一个`IBrewingRecipe`实例来执行转换。 + +### IBrewingRecipe + +`IBrewingRecipe`是一种伪[`Recipe`][recipe]接口,它检查输入和催化剂是否有效,并在满足条件时提供相关的输出。这通过`#isInput`、`#isIngredient`和`#getOutput`实现。输出方法可以访问输入和催化剂堆叠以构造结果。 + +:::caution +在`ItemStack`或`CompoundTag`之间复制数据时,确保使用它们各自的`#copy`方法创建唯一的实例。 +::: + +没有类似于原版的添加额外药水容器或药水混合的包装器。需要添加一个新的`IBrewingRecipe`实现来复制这种行为。 + +铁砧配方 +------------- + +铁砧负责接受一个受损的输入,给出一些材料或类似的输入,减少输入结果上的一些损伤。因此,其系统不容易被数据驱动。然而,因为铁砧配方是一个输入物品加上一些数量的材料等于一些输出物品,当用户有足够的经验等级时,它可以通过`AnvilUpdateEvent`修改为创建一个伪配方系统。这取决于输入和材料,并允许开发者指定输出、经验等级成本,以及用于输出的材料数量。事件还可以通过[取消][cancel]来阻止任何输出。 + +```java +// Checks whether the left and right items are correct +// When true, sets the output, level experience cost, and material amount +public void updateAnvil(AnvilUpdateEvent event) { + if (event.getLeft().is(...) && event.getRight().is(...)) { + event.setOutput(...); + event.setCost(...); + event.setMaterialCost(...); + } +} +``` + +更新事件必须[附加]到Forge事件总线上。 + +织布机配方 +------------ + +织布机负责将染料和图案(来自织布机或来自物品)应用于旗帜。虽然旗帜和染料必须分别是`BannerItem`或`DyeItem`,但可以在织布机中创建和应用自定义图案。可以通过[注册]一个`BannerPattern`来创建旗帜图案。 + +:::caution +在`minecraft:no_item_required`标签中的`BannerPattern`会作为一个选项出现在织布机中。不在此标签中的图案必须有一个相应的`BannerPatternItem`,并且有一个相关联的标签,才能使用。 +::: + +```java +private static final DeferredRegister REGISTER = DeferredRegister.create(Registries.BANNER_PATTERN, "examplemod"); + +// Takes in the pattern name to send over the network +public static final BannerPattern EXAMPLE_PATTERN = REGISTER.register("example_pattern", () -> new BannerPattern("examplemod:ep")); +``` + +[recipe]: ./custom.md#recipe +[cancel]: ../../../concepts/events.md#cancellable-events +[attached]: ../../../concepts/events.md#registering-an-event-handler +[registering]: ../../../concepts/registries.md diff --git a/i18n/zh-Hans/docusaurus-plugin-content-docs/current/resources/server/recipes/index.md b/i18n/zh-Hans/docusaurus-plugin-content-docs/current/resources/server/recipes/index.md new file mode 100644 index 000000000..d64b89d30 --- /dev/null +++ b/i18n/zh-Hans/docusaurus-plugin-content-docs/current/resources/server/recipes/index.md @@ -0,0 +1,104 @@ +配方 +======= + +配方是一种在Minecraft世界中将一些对象转换为其他对象的方式。虽然原版系统纯粹处理的是物品转换,但整个系统可以扩展,以使用程序员创建的任何对象。 + +基于数据的配方 +------------------- + +在原版中,大多数配方的实现都是通过JSON进行数据驱动的。这意味着不需要一个mod就可以创建一个新的配方,只需要一个[数据包][datapack]。如何在mod的`resources`文件夹中创建这些配方并放入其中的完整列表可以在[Minecraft Wiki][wiki]上找到。 + +可以在配方书中作为完成[进度][advancement]的奖励获取配方。配方进度总是以`minecraft:recipes/root`作为其父级,以避免在进度界面上显示。获得配方进度的默认条件是检查用户是否已经通过一次使用或通过如`/recipe`命令接收它,从而解锁了配方: + +```js +// 在某个配方进度json中 +"has_the_recipe": { // 条件标签 + // 如果使用了examplemod:example_recipe则成功 + "trigger": "minecraft:recipe_unlocked", + "conditions": { + "recipe": "examplemod:example_recipe" + } +} +//... +"requirements": [ + [ + "has_the_recipe" + // ... 解锁配方的其它要求标签对OR运算 + ] +] +``` + +基于数据的配方及其解锁的进度可以通过`RecipeProvider`[生成][datagen]。 + +配方管理器 +-------------- + +配方是通过`RecipeManager`加载和存储的。任何与获取可用配方相关的操作都由此管理器处理。有两个重要的方法需要了解: + + 方法 | 描述 + :---: | :--- +`getRecipeFor` | 获取与当前输入匹配的第一个配方。 +`getRecipesFor` | 获取与当前输入匹配的所有配方。 + +每种方法都需要一个`RecipeType`,它表示正在采用什么方法使用配方(合成,熔炼等),一个`Container`,它保存了输入的配置,以及当前的等级,这个等级与容器一起传递给了`Recipe#matches`。 + +:::tip +Forge提供了`RecipeWrapper`实用类,它扩展了`Container`,用于包装围绕`IItemHandler`的对象,并将它们传递给需要`Container`参数的方法。 + +```java +// 在具有IItemHandlerModifiable handler的某个方法中 +recipeManger.getRecipeFor(RecipeType.CRAFTING, new RecipeWrapper(handler), level); +``` +::: + +额外特性 +------------------- + +Forge对配方模式及其实现提供了一些额外的行为,以更好地控制系统。 + +### 配方的ItemStack结果 + +除了`minecraft:stonecutting`配方外,所有的原版配方序列化器都将`result`标签扩展为一个完整的`ItemStack`作为`JsonObject`,而不仅仅是在某些情况下的物品名称和数量。 + +```js +// 在某个配方JSON中 +"result": { + // 给出结果的注册物品的名称 + "item": "examplemod:example_item", + // 返回的物品数量 + "count": 4, + // 堆叠的标签数据,也可以是一个字符串 + "nbt": { + // 在此添加标签数据 + } +} +``` + +:::note +`nbt`标签也可以是一个包含字符串化NBT(或SNBT)的字符串,用于无法正确表示为JSON对象的数据(如`IntArrayTag`)。 +::: + +### 条件配方 + +配方及其解锁的进度可以根据存在的信息(已加载的mod,存在的物品等)[有条件地加载和默认][conditional]。 + +### 更大的合成网格 + +默认情况下,原版声明了合成网格的最大宽度和高度为3x3的正方形。这可以通过在`FMLCommonSetupEvent`中调用`ShapedRecipe#setCraftingSize`并设定新的宽度和高度来扩展。 + +:::caution +`ShapedRecipe#setCraftingSize`是**不**线程安全的。因此,它应该通过`FMLCommonSetupEvent#enqueueWork`被加入到同步工作队列中。 +::: + +大的合成网格在配方中可以[生成数据][datagen]。 + +### 配料类型 + +添加了一些额外的[配料类型][ingredients],以允许配方具有检查标签数据或将多个配料合并到一个输入检查器中。 + +[datapack]: https://minecraft.wiki/w/Data_pack +[wiki]: https://minecraft.wiki/w/Recipe +[advancement]: ../advancements.md +[datagen]: ../../../datagen/server/recipes.md +[conditional]: ../conditional.md#implementations +[ingredients]: ./ingredients.md#forge-types \ No newline at end of file diff --git a/i18n/zh-Hans/docusaurus-plugin-content-docs/current/resources/server/recipes/ingredients.md b/i18n/zh-Hans/docusaurus-plugin-content-docs/current/resources/server/recipes/ingredients.md new file mode 100644 index 000000000..7c5e661e6 --- /dev/null +++ b/i18n/zh-Hans/docusaurus-plugin-content-docs/current/resources/server/recipes/ingredients.md @@ -0,0 +1,177 @@ +配料 +=========== + +`Ingredient`是针对基于物品的输入的谓词处理器,它检查某个`ItemStack`是否满足作为配方有效输入的条件。所有采用输入的[原版配方][recipes]都使用一个`Ingredient`或一系列`Ingredient`,然后合并为一个单一的`Ingredient`。 + +自定义配料 +------------------ + +可以通过将`type`设置为[配料序列化器][serializer]的名称来指定自定义配料,[复合配料][compound]除外。当未指定类型时,`type`默认为原版配料`minecraft:item`。自定义配料也可以轻松地用于[数据生成][datagen]。 + +### Forge 类型 + +Forge为程序员提供了一些额外的`Ingredient`类型。 + +#### CompoundIngredient + +虽然它们在功能上是相同的,但复合配料替换了在配方中实现配料列表的方式。它们作为一个集合OR操作,传入的堆叠必须至少在提供的配料中的一个中。这个更改是为了让自定义配料能在列表中正常工作。因此,**无需**指定类型。 + +```js +// For some input +[ + // At least one of these ingredients must match to succeed + { + // Ingredient + }, + { + // Custom ingredient + "type": "examplemod:example_ingredient" + } +] +``` + +#### StrictNBTIngredient + +`StrictNBTIngredient`会比较`ItemStack`上的物品、损坏和共享标签(由`IForgeItem#getShareTag`定义)以确保完全等价。这可以通过将`type`指定为`forge:nbt`来使用。 + +```js +// For some input +{ + "type": "forge:nbt", + "item": "examplemod:example_item", + "nbt": { + // Add nbt data (must match exactly what is on the stack) + } +} +``` + +### PartialNBTIngredient + +`PartialNBTIngredient`是[`StrictNBTIngredient`][nbt]的一个更宽松的版本,因为它们只比较一个或一组物品,并且只比较在共享标签(由`IForgeItem#getShareTag`定义)中指定的键。这可以通过将`type`指定为`forge:partial_nbt`来使用。 + +```js +// For some input +{ + "type": "forge:partial_nbt", + + // Either 'item' or 'items' must be specified + // If both are specified, only 'item' will be read + "item": "examplemod:example_item", + "items": [ + "examplemod:example_item", + "examplemod:example_item2" + // ... + ], + + "nbt": { + // Checks only for equivalency on 'key1' and 'key2' + // All other keys in the stack will not be checked + "key1": "data1", + "key2": { + // Data 2 + } + } +} +``` + +### IntersectionIngredient + +`IntersectionIngredient`作为一个集合AND操作,传入的堆叠必须匹配所有提供的配料。至少必须向此提供两种配料。这可以通过将`type`指定为`forge:intersection`来使用。 + +```js +// For some input +{ + "type": "forge:intersection", + + // All of these ingredients must return true to succeed + "children": [ + { + // Ingredient 1 + }, + { + // Ingredient 2 + } + // ... + ] +} +``` + +### DifferenceIngredient + +`DifferenceIngredient`作为一个集合减法(SUB)操作,传入的堆叠必须匹配第一个配料,但不得匹配第二个配料。这可以通过将`type`指定为`forge:difference`来使用。 + +```js +// For some input +{ + "type": "forge:difference", + "base": { + // Ingredient the stack is in + }, + "subtracted": { + // Ingredient the stack is NOT in + } +} +``` + +创建自定义配料 +--------------------------- + +可以通过为创建的`Ingredient`子类实现`IIngredientSerializer`来创建自定义配料。 + +:::提示 +自定义配料应该子类化`AbstractIngredient`,因为它为实施提供了一些有用的抽象。 +::: + +### Ingredient子类 + +每个配料子类要实施三个重要的方法: + + 方法 | 描述 + :---: | :--- +getSerializer | 返回用于读写配料的[序列化器]。 +test | 如果输入对于这个配料是有效的,返回true。 +isSimple | 如果配料匹配堆栈的标签,则返回false。`AbstractIngredient`子类需要定义这种行为,而`Ingredient`子类默认返回`true`。 + +所有其他定义的方法留给读者根据需要使用配料子类。 + +### IIngredientSerializer + +`IIngredientSerializer`子类型必须实现三个方法: + + 方法 | 描述 + :---: | :--- +parse (JSON) | 将`JsonObject`转化为`Ingredient`。 +parse (Network) | 读取网络缓冲区以解码一个`Ingredient`。 +write | 将一个`Ingredient`写入网络缓冲区。 + +此外,`Ingredient`子类应该实现`Ingredient#toJson`以用于[data generation][datagen]。`AbstractIngredient`子类将`#toJson`设置为抽象方法,要求实现该方法。 + +然后,应声明一个静态实例来保存初始化的序列化器,然后使用`CraftingHelper#register`在`RecipeSerializer`的`RegisterEvent`或`FMLCommonSetupEvent`期间注册。`Ingredient`子类在`Ingredient#getSerializer`中返回序列化器的静态实例。 + +```java +// In some serializer class +public static final ExampleIngredientSerializer INSTANCE = new ExampleIngredientSerializer(); + +// In some handler class +public void registerSerializers(RegisterEvent event) { + event.register(ForgeRegistries.Keys.RECIPE_SERIALIZERS, + helper -> CraftingHelper.register(registryName, INSTANCE) + ); +} + +// In some ingredient subclass +@Override +public IIngredientSerializer getSerializer() { + return INSTANCE; +} +``` + +:::tip +如果使用`FMLCommonSetupEvent`来注册配料序列化器,必须通过`FMLCommonSetupEvent#enqueueWork`将其加入到同步工作队列,因为`CraftingHelper#register`不是线程安全的。 +::: + +[recipes]: https://minecraft.wiki/w/Recipe#List_of_recipe_types +[nbt]: #strictnbtingredient +[serializer]: #iingredientserializer +[compound]: #compoundingredient +[datagen]: ../../../datagen/server/recipes.md diff --git a/i18n/zh-Hans/docusaurus-plugin-content-docs/current/resources/server/tags.md b/i18n/zh-Hans/docusaurus-plugin-content-docs/current/resources/server/tags.md new file mode 100644 index 000000000..b5f88eeb3 --- /dev/null +++ b/i18n/zh-Hans/docusaurus-plugin-content-docs/current/resources/server/tags.md @@ -0,0 +1,119 @@ +标签 (Tags) +==== + +标签是游戏中对象的广义集合,用于将相关事物分组在一起并提供快速的成员检查。 + +声明您自己的分组 +---------------------------- +标签在您的模组的[数据包][datapack]中声明。例如,一个给定标识符为`modid:foo/tagname`的`TagKey`将引用在`/data//tags/blocks/foo/tagname.json`的标签。`Block`、`Item`、`EntityType`、`Fluid`和`GameEvent`的标签使用它们的文件夹位置的复数形式,而所有其他注册表使用单数版本(`EntityType`使用文件夹`entity_types`,而`Potion`则使用文件夹`potion`)。 +同样,您可以通过声明自己的JSON来附加或覆盖在其他域中声明的标签,比如Vanilla。 +例如,要将您自己模组的树苗添加到Vanilla的树苗标签,您需要在`/data/minecraft/tags/blocks/saplings.json`中指定,如果`replace`选项为false,那么Vanilla将在重新加载时将所有内容合并到一个标签中。 +如果`replace`为true,则指定`replace`的json之前的所有条目将被删除。 +列出的不存在的值将导致标签出错,除非该值使用`id`字符串和`required`布尔值列出且设置为false,如下例: + +```js +{ + "replace": false, + "values": [ + "minecraft:gold_ingot", + "mymod:my_ingot", + { + "id": "othermod:ingot_other", + "required": false + } + ] +} +``` + +请参阅[Vanilla wiki][tags]了解基本语法的描述。 + +此外,Forge在Vanilla语法上进行了扩展。 +您可以声明一个与`values`数组格式相同的`remove`数组。列在这里的任何值都将从标签中删除。这作为Vanilla `replace`选项的更细粒度版本。 + +在代码中使用标签 +------------------ +所有注册表的标签都会在登录和重新加载时自动从服务器发送到任何远程客户端。`Block`、`Item`、`EntityType`、`Fluid`和`GameEvent`是特殊情况,因为它们有`Holder`,允许通过对象本身访问可用的标签。 + +:::note +未来版本的Minecraft中可能会删除侵入式的`Holder`。如果它们被删除,下面的方法可以用来查询相关的`Holder`。 +::: + +### ITagManager + +Forge包装的注册表提供了一个额外的帮助器,通过`ITagManager`来创建和管理标签,可以通过`IForgeRegistry#tags`获得。标签可以使用`#createTagKey`或`#createOptionalTagKey`创建。也可以分别使用`#getTag`或`#getReverseTag`检查标签或注册对象。 + +#### 自定义注册表 + +自定义注册表可以在构建它们的`DeferredRegister`时通过`#createTagKey`或`#createOptionalTagKey`创建标签。然后可以通过调用`DeferredRegister#makeRegistry`获得的`IForgeRegistry`来检查它们的标签或注册对象。 + +### 引用标签 + +有四种创建标签包装的方法: + +方法 | 适用于 +:---: | :--- +`*Tags#create` | `BannerPattern`、`Biome`、`Block`、`CatVariant`、`DamageType`、`EntityType`、`FlatLevelGeneratorPreset`、`Fluid`、`GameEvent`、`Instrument`、`Item`、`PaintingVariant`、`PoiType`、`Structure`和`WorldPreset`,其中`*`代表这些类型之一。 +`ITagManager#createTagKey` | Forge包装的vanilla注册表,注册表可以从`ForgeRegistries`获得。 +`DeferredRegister#createTagKey` | 自定义forge注册表。 +`TagKey#create` | 没有forge包装的vanilla注册表,注册表可以从`Registry`获得。 + +注册对象可以通过它们的`Holder`或对于vanilla或forge注册表对象分别通过`ITag`/`IReverseTag`检查它们的标签或注册对象。 + +Vanilla注册表对象可以使用`Registry#getHolder`或`Registry#getHolderOrThrow`抓取它们关联的holder,然后使用`Holder#is`比较注册表对象是否有标签。 + +Forge注册表对象可以使用`ITagManager#getTag`或`ITagManager#getReverseTag`抓取它们的标签定义,然后分别使用`ITag#contains`或`IReverseTag#containsTag`比较注册表对象是否有标签。 + +包含标签的注册表对象包含一个称为`#is`的方法,在它们的注册表对象或状态感知类中,用以检查对象是否属于某个标签。 + +作为一个示例: + +```java +public static final TagKey myItemTag = ItemTags.create(new ResourceLocation("mymod", "myitemgroup")); + +public static final TagKey myPotionTag = ForgeRegistries.POTIONS.tags().createTagKey(new ResourceLocation("mymod", "mypotiongroup")); + +public static final TagKey myVillagerTypeTag = TagKey.create(Registries.VILLAGER_TYPE, new ResourceLocation("mymod", "myvillagertypegroup")); + +// In some method: + +ItemStack stack = /*...*/; +boolean isInItemGroup = stack.is(myItemTag); + +Potion potion = /*...*/; +boolean isInPotionGroup = ForgeRegistries.POTIONS.tags().getTag(myPotionTag).contains(potion); + +ResourceKey villagerTypeKey = /*...*/; +boolean isInVillagerTypeGroup = BuiltInRegistries.VILLAGER_TYPE.getHolder(villagerTypeKey).map(holder -> holder.is(myVillagerTypeTag)).orElse(false); +``` + +约定 +----------- + +有几个约定将有助于在生态系统中促进兼容性: + +* 如果有Vanilla标签适合您的方块或物品,请将其添加到该标签中。参见[Vanilla标签列表][taglist]。 +* 如果有Forge标签适合您的方块或物品,请将其添加到该标签中。可以在[GitHub][forgetags]上查看Forge声明的标签列表。 +* 如果您觉得有一组东西应该被社区共享,请使用`forge`命名空间而不是您的mod id。 +* 标签命名约定应遵循Vanilla约定。特别是,物品和方块分组应使用复数而不是单数(例如,`minecraft:logs`,`minecraft:saplings`)。 +* 物品标签应按照它们的类型排序到子目录中(例如,`forge:ingots/iron`,`forge:nuggets/brass`等)。 + +从OreDictionary迁移 +---------------------------- + +* 对于配方,可以直接在vanilla配方格式中使用标签(见下文)。 +* 要在代码中匹配物品,请参阅上述部分。 +* 如果您正在声明一种新类型的物品分组,请遵循一些命名约定: + * 使用`domain:type/material`。当名称是所有modders都应采用的常见名称时,使用`forge`域。 + * 例如,铜锭应在`forge:ingots/brass`标签下注册,钴粒则应在`forge:nuggets/cobalt`标签下注册。 + +在配方和成就中使用标签 +-------------------------------------- + +Vanilla直接支持标签。有关使用详细信息,请参阅相应的Vanilla wiki页面,包括[配方]和[成就]。 + +[datapack]: ./index.md +[tags]: https://minecraft.wiki/w/Tag#JSON_format +[taglist]: https://minecraft.wiki/w/Tag#List_of_tags +[forgetags]: https://github.com/neoforged/NeoForge/tree/1.20.x/src/generated/resources/data/forge/tags +[recipes]: https://minecraft.wiki/w/Recipe#JSON_format +[advancements]: https://minecraft.wiki/w/Advancement diff --git a/i18n/zh-Hans/docusaurus-theme-classic/footer.json b/i18n/zh-Hans/docusaurus-theme-classic/footer.json new file mode 100644 index 000000000..e5da5c703 --- /dev/null +++ b/i18n/zh-Hans/docusaurus-theme-classic/footer.json @@ -0,0 +1,38 @@ +{ + "link.title.Docs": { + "message": "Docs", + "description": "The title of the footer links column with title=Docs in the footer" + }, + "link.title.Links": { + "message": "Links", + "description": "The title of the footer links column with title=Links in the footer" + }, + "link.item.label.NeoForge Documentation": { + "message": "NeoForge Documentation", + "description": "The label of footer link with label=NeoForge Documentation linking to /docs/gettingstarted/" + }, + "link.item.label.NeoGradle Documentation": { + "message": "NeoGradle Documentation", + "description": "The label of footer link with label=NeoGradle Documentation linking to /neogradle/docs/" + }, + "link.item.label.Contributing to the Documentation": { + "message": "Contributing to the Documentation", + "description": "The label of footer link with label=Contributing to the Documentation linking to /contributing" + }, + "link.item.label.Discord": { + "message": "Discord", + "description": "The label of footer link with label=Discord linking to https://discord.neoforged.net/" + }, + "link.item.label.Main Website": { + "message": "Main Website", + "description": "The label of footer link with label=Main Website linking to https://neoforged.net/" + }, + "link.item.label.GitHub": { + "message": "GitHub", + "description": "The label of footer link with label=GitHub linking to https://github.com/neoforged/documentation" + }, + "copyright": { + "message": "Copyright © 2016, under the MIT license. Built with Docusaurus.", + "description": "The footer copyright" + } +} diff --git a/i18n/zh-Hans/docusaurus-theme-classic/navbar.json b/i18n/zh-Hans/docusaurus-theme-classic/navbar.json new file mode 100644 index 000000000..0816cf3cb --- /dev/null +++ b/i18n/zh-Hans/docusaurus-theme-classic/navbar.json @@ -0,0 +1,26 @@ +{ + "title": { + "message": "Homepage", + "description": "The title in the navbar" + }, + "logo.alt": { + "message": "NeoForged Logo", + "description": "The alt text of navbar logo" + }, + "item.label.NeoForge Documentation": { + "message": "NeoForge Documentation", + "description": "Navbar item with label NeoForge Documentation" + }, + "item.label.NeoGradle Documentation": { + "message": "NeoGradle Documentation", + "description": "Navbar item with label NeoGradle Documentation" + }, + "item.label.Contributing": { + "message": "Contributing", + "description": "Navbar item with label Contributing" + }, + "item.label.GitHub": { + "message": "GitHub", + "description": "Navbar item with label GitHub" + } +} diff --git a/neogradle/docs/5.x_to_6.0.md b/neogradle/docs/5.x_to_6.0.md index dfb47d272..c727abdb1 100644 --- a/neogradle/docs/5.x_to_6.0.md +++ b/neogradle/docs/5.x_to_6.0.md @@ -1,92 +1,92 @@ -# ForgeGradle 5到6迁移指南 +# ForgeGradle 5 -> 6 Migration Primer -这是一个从ForgeGradle 5迁移到6的高层次、非详尽的概览。 +This is a high level, non-exhaustive overview on how to migrate your buildscript from ForgeGradle 5 to 6. -## 添加 Foojay 工具链插件 +## Add Foojay Toolchains Plugin -Gradle 现在使用 [Foojay 工具链插件](https://github.com/gradle/foojay-toolchains) 来管理 Java 工具链。该插件必须添加到 `settings.gradle[.kts]` 中: +Gradle now uses the [Foojay Toolchains Plugin](https://github.com/gradle/foojay-toolchains) to manage the Java toolchain. The plugin must be added to the `settings.gradle[.kts]`: -使用 Gradle DSL: +With Gradle DSL: ```gradle -// 在 settings.gradle 中 +// In settings.gradle plugins { id 'org.gradle.toolchains.foojay-resolver-convention' version '0.5.0' } ``` -使用 Kotlin DSL: +With Kotlin DSL: ```kotlin -// 在 settings.gradle.kts 中 +// In settings.gradle.kts plugins { id("org.gradle.toolchains.foojay-resolver-convention") version "0.5.0" } ``` -## 更新 Gradle 至 8.1.1 +## Update Gradle to 8.1.1 -首先,你需要更新你的 Gradle Wrapper 使用 8.1.1 版本。你可以在 [Gradle迁移指南](https://docs.gradle.org/current/userguide/upgrading_version_7.html) 上找到有关从 7 到 8 版本迁移的更多信息。 +First, you need to update your Gradle Wrapper to use 8.1.1. You can find additional information regarding the migration process from 7 to 8 on [Gradle's migration guide](https://docs.gradle.org/current/userguide/upgrading_version_7.html). -这可以通过以下两种方式之一完成: +This can be done in one of two ways: ### `gradle wrapper` -首选的选项是使用 `gradle wrapper` 命令与 `--gradle-version` 选项。 +The preferred option is to use the `gradle wrapper` command with the `--gradle-version` option. ```bash -# 在gradle项目的根目录内 +# Inside root directory of the gradle project ./gradlew wrapper --gradle-version=8.1.1 ``` ### gradle-wrapper.properties -这种方法不推荐,仅当上面的 `gradlew wrapper` 命令失败时使用。 +This method is not recommended, and it should only be used if the `gradlew wrapper` command above fails. -更新 `gradle/wrapper/gradle-wrapper.properties` 内的 `distributionUrl` 属性中的版本。之后运行 `./gradlew wrapper`。 +Update the version in the `distributionUrl` property within `gradle/wrapper/gradle-wrapper.properties`. Afterwards run `./gradlew wrapper`. ```properties distributionUrl=https\://services.gradle.org/distributions/gradle-8.1.1-bin.zip ``` -## 更新 ForgeGradle 插件版本 +## Update ForgeGradle Plugin Version -更新 Gradle 后,在你的 `build.gradle[.kts]` 中更新 `ForgeGradle` 版本到 `[6.0,6.2)`: +After updating Gradle, update the `ForgeGradle` version in your `build.gradle[.kts]` to `[6.0,6.2)`: -使用 Gradle DSL: +With Gradle DSL: ```gradle -// 在 build.gradle 中 +// In build.gradle plugins { id 'net.minecraftforge.gradle' version '[6.0,6.2)' } ``` -使用 Kotlin DSL: +With Kotlin DSL: ```kotlin -// 在 build.gradle.kts 中 +// In build.gradle.kts plugins { id("net.minecraftforge.gradle") version "[6.0,6.2)" } ``` -## 从运行配置中移除 `forceExit` +## Remove `forceExit` from Run Configurations -ForgeGradle 现在已经移除了运行配置中的 `forceExit` 属性。对运行任务的更改仅适用于当前项目而不适用于任何子项目。 +ForgeGradle has now removed the `forceExit` property in run configurations. Changes to run tasks only apply to the current project and not any subprojects. -### 小的添加、更改、移除 +### Minor Additions, Changes, Removals -### 重混淆类路径继承 +### Reobfuscated Classpath Inheritance -如果你在 `reobf*` 任务配置中使用了 `classpath.from` 来声明额外库,必须要考虑的 `reobf*` 任务;它应该迁移至 `libraries.from` 属性而不是。 +If you were using `classpath.from` within a `reobf*` task configuration to declare extra libraries, the `reobf*` task needed to take into account; it should be migrated to the `libraries.from` property instead. -### Eclipse 启动器组 +### Eclipse Launcher Groups -如果设置了 ForgeGradle 使 Eclipse 在开始游戏之前运行 Gradle 任务,你必须从 `Launch Group` 文件夹执行运行配置。如果在 `minecraft` 块中启用了新属性,这只是正确的。 +If ForgeGradle is setup such that Eclipse should run the Gradle tasks before starting the game, you must execute the run configuration from the `Launch Group` folder. This is only true if new properties are enabled within the `minecraft` block. -![Eclipse 启动器组演示](/img/eclipse_launch_groups.png) +![Eclipse launch groups demo](/img/eclipse_launch_groups.png) -### 在 ModConfig 中移除 `resources` 和 `classes` +### Removal of `resources` and `classes` in ModConfig -在 ForgeGradle 6中,运行配置的 `mods` 块内的 `resources` 和 `classes` 属性已被移除。 +The properties `resources` and `classes` within the `mods` block of a run configuration have been removed from ForgeGradle 6. diff --git a/neogradle/docs/configuration/advanced.md b/neogradle/docs/configuration/advanced.md index 3c15496d9..d5537bb96 100644 --- a/neogradle/docs/configuration/advanced.md +++ b/neogradle/docs/configuration/advanced.md @@ -1,15 +1,15 @@ -高级配置 +Advanced Configurations ======================= -ForgeGradle 包含了一些特定的或细微的配置技术,这些技术取决于构建项目的复杂性。 +ForgeGradle contains a few specific or nuanced configuration techniques depending on the complexity of your build project. -重混淆源集 +Reobfuscating Source Sets ------------------------- -默认情况下,`reobf*` 和 `rename*` 任务只包含主源集类路径上的文件。要重混淆不同类路径上的文件,需要将它们添加到任务中的 `libraries` 属性。 +By default, the `reobf*` abd `rename*` tasks only contain files on the main source set's classpath. To reobfuscate files on a different classpath, they need to be added to the `libraries` property within the task. ```gradle -// 将另一个源集的类路径添加到 'reobf' 任务。 +// Adds another source set's classpath to 'reobf' task. tasks.withType('reobfJar') { libraries.from sourceSets.api.classpath } diff --git a/neogradle/docs/configuration/index.mdx b/neogradle/docs/configuration/index.mdx index c7d6b933b..66c25d9fe 100644 --- a/neogradle/docs/configuration/index.mdx +++ b/neogradle/docs/configuration/index.mdx @@ -1,35 +1,36 @@ import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; -ForgeGradle 配置 +ForgeGradle Configurations ========================== -ForgeGradle 提供了众多配置选项,以便改变开发环境的配置方式。大部分的配置通过 `minecraft` 代码块设置,但一些其他配置可以在 `dependencies` 代码块内指定,或修改构建的 `jar`,例如 `reobfJar`。 +ForgeGradle has numerous configurations that can change how the development environment is configured. Most configurations are set using the `minecraft` block; however, some others can be specified within the `dependencies` block or modify the built `jar`, such as `reobfJar`. -启用 Access Transformer +Enabling Access Transformers ---------------------------- -[Access Transformer][at]可以扩大 Minecraft 类、方法和字段的可见性或修改它们的 `final` 标志。 + +[Access Transformers][at] can widen the visibility or modify the `final` flag of Minecraft classes, methods, and fields. - -要在开发环境中启用 Access Transformer ,您可以在 `minecraft` 代码块中设置 `accessTransformers` 到相关的配置文件: + +To enable Access Transformers in the production environment, you can set `accessTransformers` to the configuration file in question: ```gradle minecraft { // ... - // 添加一个AT文件,相对于项目目录 + // Add an Access Transformer file relative to the project's directory accessTransformers { file('src/main/resources/META-INF/accesstransformer.cfg') - // 可以指定多个文件,按顺序应用 + // Multiple files can be specified and are applied in order file('src/main/resources/accesstransformer_extras.cfg') } } ``` -在开发环境中,NeoForge 会根据 `mods.toml` 中指定的搜索AT文件,或者在没有指定的情况下搜索 `META-INF/accesstransformer.cfg`: +In production, NeoForge will search for Access Transformer files as specified in `mods.toml`, or at `META-INF/accesstransformer.cfg` if none are specified: ```toml [[accessTransformers]] @@ -42,48 +43,48 @@ file="accesstransformer_extras.cfg" - -要在开发环境中启用AT,您可以将 `accessTransformer` 设置到相关配置文件: + +To enable Access Transformers in the production environment, you can set `accessTransformer` to the configuration file in question: ```gradle minecraft { // ... - // 添加一个AT文件,相对于项目目录 + // Add an access transformer file relative to the project's directory accessTransformer = project.file('src/main/resources/META-INF/accesstransformer.cfg') } ``` :::caution -虽然在开发环境中的AT可以从用户指定的任何位置读取,在开发环境中,文件只能从 `META-INF/accesstransformer.cfg` 读取。 +While the Access Transformer in the development environment can be read from anywhere the user specifies, in production, the file will only be read from `META-INF/accesstransformer.cfg`. ::: -人类可读映射 +Human-Readable Mappings ----------------------- -Minecraft 的源代码是混淆的。因此,所有的类、方法和字段都有机器生成的名称,没有包结构。与此同时,函数本地变量名称被转换为雪人(`☃`),因为本地变量表的存储方式。使用这些混淆名称创建模组非常困难,因为给它们做逆向工程既繁琐,又可能在版本之间发生变化,且可能在语言本身中无效,但在字节码中有效。 +Minecraft's source code is obfuscated. As such, all classes, methods, and fields have machine-generated names with no package structures. Function-local variable names, meanwhile, are turned into a snowman (`☃`) due to how the Local Variable Table is stored. It is difficult to create mods using obfuscated names as reverse engineering them is tedious, may change between versions, and may be invalid in the language itself but not in the bytecode. -为了解决这最后两个问题,Forge 通过 [ForgeAutoRenamingTool][fart] (Forge 自动重命名工具),模糊地将每个类、方法、字段和参数映射到一个唯一的标识符,即 SRG 名称。当用户客户端运行游戏时,会使用 SRG 映射。 +To address the last two issues, Forge fuzzily maps each class, method, field, and parameter to a unique identifier, known as the SRG name, via the [ForgeAutoRenamingTool][fart]. SRG mappings are used in production when the game is being run by the user client. -为了简化开发,ForgeGradle 允许用户选择一个映射集来应用在 SRG 映射之上,我们将其称为人类可读映射。ForgeGradle 知道如何通过 `reobf*` 任务将模组 jar 转换为 SRG 映射,以便在生产中使用。 +To allow easier development, ForgeGradle allows the user to choose a mapping set to apply on top of SRG mappings, which we will refer to as human-readable mappings. ForgeGradle knows how to convert the mod jar to SRG mappings for use in production via the `reobf*` task. -可以通过在 `minecraft` 块中设置 `mappings` 字段来指定所使用的映射集。`mappings` 字段接受两个参数:`channel` 是映射集的类型,而 `version` 是要应用的映射集的版本。 +The mapping set used can be specified by setting the `mappings` field in the `minecraft` block. The `mappings` field takes in two arguments: `channel` which is the type of the mapping set, and `version` which is the version of the mapping set to apply. -目前,ForgeGradle 内置了三套默认的映射集: +Currently, there are three default mapping sets built into ForgeGradle: -* `official` - 使用 Mojang 提供的映射集。这些映射没有参数名称,只从 1.14 版本开始存在。 -* `stable` - 使用由 MCP 生成的映射集。这些通常不完整,并且从 1.17 版本开始就不再存在。 -* `snapshot` - 这也是 MCP 生成的映射集,类似于程序的每日版构建。这些也通常不完整,并从 1.17 版本开始就不再存在。 +* `official` - This uses the mapping set provided by Mojang. These mappings do not have parameter names and only exist from 1.14 onward. +* `stable` - This uses a mapping set generated by MCP. These are typically incomplete and no longer exist as of 1.17. +* `snapshot` - This also is a mapping set generated by MCP, similar to a nightly build of a program. These are also typically incomplete and no longer exist as of 1.17. :::info -生产中使用的类名称来自 1.17 版本之前的 `stable` 映射集,从 1.17 版本开始则来自 `official` 映射集。 +The class names used in production are from `stable` prior to 1.17 and from `official` from 1.17 onwards. ::: ```gradle mappings { - // 设置映射 使用 Mojang 提供的 Minecraft 1.19.4 版本映射。 + // Sets the mappings to use those from Mojang for Minecraft 1.19.4. mappings channel: 'official', version: '1.19.4' // ... @@ -92,49 +93,50 @@ mappings { ### Parchment -Parchment 是 ParchmentMC 维护的官方项目,它在 `official` 映射集之上提供了开放的、社区来源的参数名称和 javadocs。你可以在[它们的网站][parchment]上了解如何设置和使用 Parchment 映射集。 +Parchment is an official project maintained by ParchmentMC which provides open, community-sourced parameter names and javadocs on top of the `official` mapping set. You can learn how setup and use the parchment mapping set on [their website][parchment]. -准备运行任务 +Preparing Run Tasks ------------------- -运行任务(`run*`)有两个独立的处理流程,这取决于它们是通过 `gradlew` 还是通过运行配置执行的。默认情况下,有两个任务负责准备工作区以执行游戏: +Run tasks (`run*`) have two separate pipelines depending on whether they are executed through `gradlew` or a run configuration. By default, there are two tasks that prepare the workspace for execution: -首先,有 `prepare*` 任务,这些任务在 `run*` 任务之前执行,以确保游戏的映射文件已准备好。`prepare*Compile` 任务通常只作为 `run*` 任务的依赖项执行,以确保在运行游戏之前先将游戏编译好。 +First, there are `prepare*` tasks which are executed before `run*` tasks and ensure that mapping files are prepared for the game. The `prepare*Compile` task is typically only executed as a dependency of `run*` tasks to make sure that the game is compiled before it is run. -如果你使用的 IDE 是 Eclipse 或 IntelliJ IDEA,可以通过分别将 `enableEclipsePrepareRuns` 或 `enableIdeaPrepareRuns` 设置为 `true` 来配置运行配置,在启动游戏之前执行 `prepare*` 任务。这将允许你在 IDE 启动游戏之前调用自定义的 Gradle 任务。 +If your IDE is either Eclipse or IntelliJ IDEA, the run configuration can be configured to execute the `prepare*` tasks before starting the game by setting `enableEclipsePrepareRuns` or `enableIdeaPrepareRuns`, respectively, to `true`. This will allow you to invoke custom Gradle tasks before your IDE launches the game. ```gradle minecraft { // ... - // 启用运行配置的 'prepare*' 任务 + // Enable the 'prepare*' task for run configurations enableEclipsePrepareRuns true enableIdeaPrepareRuns true } ``` -### 复制 IDE 资源 +### Copy IDE Resources -`copyIdeResources` 属性可以用来将 `processResources` 任务配置的资源复制到 IDE 的资源输出目录。这允许不调用 Gradle 的 IDE 运行配置(配置为使用 IDEA 运行器的 IntelliJ 或 Eclipse)使用由构建脚本配置的资源。通常,在你需要在像 `mods.toml` 这样的文件中替换值时,你需要启用这个属性。 -这只适用于通过 `copyEclipseResources` 任务和 `copyIntellijResources` 任务,分别应用于 Eclipse 和 IntelliJ IDEA。 +The `copyIdeResources` property can be used to copy resources configured by the `processResources` task to the IDE's resource output directories. This allows IDE run configurations that do not invoke Gradle (IntelliJ configured to use the IDEA runner or Eclipse) to use buildscript configurable resources. Usually, you need to enable this property when you are replacing values in files like the `mods.toml`. +This only applies to Eclipse and IntelliJ IDEA via the `copyEclipseResources` and `copyIntellijResources` tasks, respectively. ```gradle minecraft { // ... - // 从 'processResources' 任务复制文件到 IDE 的资源输出目录 + // Copies the files from 'processResources' to the IDE's resource output directories copyIdeResources true } ``` -### 运行配置文件夹 +### Run Configuration Folders + +Run configurations can be sorted into folders if the `generateRunFolders` is set to `true`. This reads the `folderName` property set in the specific [run configuration][run] to determine the organizational structure. -如果将 `generateRunFolders` 设置为 `true`,则可以将运行配置排序到文件夹中。这会读取特定 [运行配置][run] 中设置的 `folderName` 属性来确定组织结构 ```gradle minecraft { // ... - // 当设置为 true 时,运行配置将根据它们的 'folderName' 被分组到文件夹中 + // When true, run configurations will be grouped into folders by their 'folderName' generateRunFolders true } ``` diff --git a/neogradle/docs/configuration/runs.md b/neogradle/docs/configuration/runs.md index 6c797928e..f06f90a04 100644 --- a/neogradle/docs/configuration/runs.md +++ b/neogradle/docs/configuration/runs.md @@ -1,128 +1,126 @@ -运行配置 +Run Configurations ================== -运行配置定义了如何运行游戏实例。这包括参数、工作目录、任务名称等。运行配置在 `minecraft.runs` 块内定义。虽然默认情况下没有配置任何运行配置,但 [Forge][userdev] 提供了 `client`、`server`、`data` 或 `gameTestServer` 等配置。 +Run configurations define how an instance of the game is going to run. This includes arguments, working directories, task names, etc. Run configurations are defined within the `minecraft.runs` block. While no runs are configured by default, [Forge][userdev] does provide the configurations `client`, `server`, `data`, or `gameTestServer`. ```gradle minecraft { // ... runs { - // 在此配置运行 + // Configure runs here } } ``` -可以使用闭包类似于任何 `NamedDomainObjectContainer` 来添加运行配置。 +Run configurations can be added similar to any `NamedDomainObjectContainer` using closures. ```gradle -// 在 minecraft 块内 +// Inside the minecraft block runs { - // 创建或配置名为 'client' 的运行配置 + // Creates or configures the run configuration named 'client' client { - // 配置运行 + // Configure run } } ``` -以下配置属性是可用的: +The following configurations properties are available: ```gradle -// 在 runs 块内 +// Inside the runs block client { - // Gradle 运行任务的名称, - // 默认为 'runX',其中 X 是容器名称 + // The name of the Gradle run tasks, + // Defaults to 'runX' where X is the container name taskName 'runThing' - // 设置要启动的程序的入口点 - // Forge 设置 userdev 主要为 'cpw.mods.bootstraplauncher.BootstrapLauncher' + // Sets the entrypoint of the program to launch + // Forge sets userdev main to be 'cpw.mods.bootstraplauncher.BootstrapLauncher' main 'com.example.Main' - // 设置配置的工作目录 - // 默认为 './run' + // Sets the working directory of the config + // Defaults to './run' workingDirectory 'run' - // 设置 IntelliJ IDEA 的模块名称,用于配置其运行 - // 默认为 '.main' + // Sets the name of the module for IntelliJ IDEA to configure for its runs + // Defaults to '.main' ideaModule 'example.main' - // 设置将运行配置添加到的文件夹名称 - // 默认为项目的名称 + // Sets the name of the folder that the run configuration should be added to + // Defaults to the name of the project folderName 'example' - // 设置是否运行 Minecraft 客户端 - // 如果未指定,检查以下内容: - // - 是否存在包含 'client' 的环境属性 'thing' - // - 配置名称是否包含 'client' - // - main 是否设置为 'mcp.client.Start' - // - main 是否设置为 'net.minecraft.client.main.Main' + // Sets whether this should run a Minecraft client + // If not specified, checks the following + // - Is there an environment property 'thing' that contains 'client' + // - Does the configuration name contain 'client' + // - Is main set to 'mcp.client.Start' + // - Is main set to 'net.minecraft.client.main.Main' client true - // 设置此配置的父配置以继承 + // Set the parent of this configuration to inherit from parent runs.example - // 设置此配置的子配置 + // Sets the children of this configuration children runs.child - // 合并此配置并指定是否覆盖现有属性 + // Merges this configuration and specifies whether to overwrite existing properties merge runs.server, true - // 如果不为 false,将合并父配置的参数到此配置 + // If not false, will merge the arguments of the parent with this configuration inheritArgs false - // 如果不为 false,将合并父配置的 JVM 参数到此配置 + // If not false, will merge the JVM arguments of the parent with this configuration inheritJvmArgs false - // 添加一个源集到类路径 - // 如果未指定,默认添加 sourceSet.main + // Adds a sourceset to the classpath + // If none is specified, adds sourceSet.main source sourceSets.api - // 为运行设置一个环境属性 - // 值将被解释为文件或字符串 + // Sets an environment property for the run + // Value will be interpreted as a file or a string environment 'envKey', 'value' - // 设置一个系统属性 - // 值将被解释为文件或字符串 + // Sets a system property + // Value will be interpreted as a file or a string property 'propKey', 'value' - // 设置传递给应用程序的参数 - // 可以使用 'args' 指定多个 + // Sets an argument to be passed into the application + // Can specify multiple with 'args' arg 'hello' - // 设置一个 JVM 参数 - // 可以使用 'jvmArgs' 指定多个 + // Sets a JVM argument + // Can specify multiple with 'jvmArgs' jvmArg '-Xmx2G' - // 设置一个令牌 - // 目前,正在使用以下令牌: + // Sets a token + // Currently, the following tokens are being used: // - runtime_classpath // - minecraft_classpath token 'tokenKey', 'value' - // 设置一个延迟初始化的令牌 - // 通常应该使用 'token' 替代,例如当令牌解析 Gradle 配置时 + // Sets a token that's lazily initialized + // Should usually be used instead of 'token', for example when the token resolves Gradle configurations lazyToken('lazyTokenKey') { 'value' } - // 如果为 true,则编译所有项目而不仅仅是 - ```gradle -// 如果为 true,则编译所有项目,而不是仅当前任务 -// 这仅被 IntelliJ IDEA 使用 -buildAllProjects false + // If true, compile all projects instead of for the current task + // This is only used by IntelliJ IDEA + buildAllProjects false } ``` :::tip -你可以在 [MinecraftForge 构建脚本][buildscript] 中查看所有配置的 userdev 属性的列表。 +You can see a list of all configured userdev properties within the [MinecraftForge buildscript][buildscript]. ::: -Mod 配置 --------- +Mod Configurations +------------------ -在当前环境中可以使用 Run 配置内的 `mods` 块添加mod。Mod 块也是 `NamedDomainObjectContainer`。 +A mod in the current environment can be added using the `mods` block within a Run configuration. Mod blocks are also `NamedDomainObjectContainer`s. ```gradle -// 在 runs 块内 +// Inside the runs block client { // ... @@ -131,12 +129,12 @@ client { // ... } - // 配置 'example' mod + // Configures the 'example' mod example { - // 向 mod 的源添加源集 + // Add a source set to a mod's sources source sourceSets.main - // 合并此配置并指定是否覆盖现有属性 + // Merges this configuration and specifies whether to overwrite existing properties merge mods.other_mod, true } } diff --git a/neogradle/docs/dependencies/index.md b/neogradle/docs/dependencies/index.md index 6bee91df2..35dfdee7b 100644 --- a/neogradle/docs/dependencies/index.md +++ b/neogradle/docs/dependencies/index.md @@ -1,51 +1,48 @@ -依赖项 -==== +Dependencies +============ -依赖项不仅用于开发模组间的互操作性或向游戏添加额外的库,而且还决定了要为哪个版本的Minecraft进行开发。这将提供如何修改 `repositories` 和 `dependencies` 块以将依赖项添加到您的开发环境的快速概述。 +Dependencies are not only used to develop interoperability between mods or add additional libraries to the game, but it also determines what version of Minecraft to develop for. This will provide a quick overview on how to modify the `repositories` and `dependencies` block to add dependencies to your development environment. -> 这将不会深入解释 Gradle 概念。强烈建议在继续之前阅读 [Gradle 依赖管理指南][guide]。 +> This will not explain Gradle concepts in depth. It is highly recommended to read the [Gradle Dependency Management guide][guide] before continuing. `minecraft` ----------- -`minecraft` 依赖项指定了要使用的 Minecraft 版本,并且必须包含在 `dependencies` 块中。任何非 `net.minecraft` 组的工件都将应用随依赖项提供的任何补丁。这通常只指定 `net.minecraftforge:forge` 工件。 +The `minecraft` dependency specifies the version of Minecraft to use and must be included in the `dependencies` block. Any artifact, except artifacts which have the group `net.minecraft`, will apply any patches provided with the dependency. This typically only specifies the `net.minecraftforge:forge` artifact. ```gradle dependencies { - // Forge 工件的版本形式为 '-' - // 'mc_version' 是要加载的 Minecraft 版本(例如,1.19.4) - // 'forge_version' 是该 Minecraft 版本所需的 Forge 版本(例如,45.0.23) - // Vanilla 可以使用 'net.minecraft:joined:' 来编译 + // Version of Forge artifact is in the form '-' + // 'mc_version' is the version of Minecraft to load (e.g., 1.19.4) + // 'forge_version' is the version of Forge wanted for that Minecraft version (e.g., 45.0.23) + // Vanilla can be compiled against using 'net.minecraft:joined:' instead minecraft 'net.minecraftforge:forge:1.19.4-45.0.23' } ``` -Mod 依赖项 ----------- +Mod Dependencies +---------------- -在典型的开发环境中,Minecraft 被反混淆到中间映射中,用于生产,然后转换为模组制作者指定的任何[人类可读映射][mappings]。构建的 Mod 工件被混淆到生产映射(SRG)中,因此不能直接用作 Gradle 依赖项。 +In a typical development environment, Minecraft is deobfuscated to intermediate mappings, used in production, and then transformed into whatever [human-readable mappings][mappings] the modder specified. Mod artifacts, when built, are obfuscated to production mappings (SRG), and as such, are unable to be used directly as a Gradle dependency. -因此,所有 Mod 依赖项在添加到预期配置之前都需要用 `fg.deobf` 包装。 - -[guide]: https://docs.gradle.org/current/userguide/dependency_management.html -[mappings]: https://github.com/MinecraftForge/MCPConfig +As such, all mod dependencies need to be wrapped with `fg.deobf` before being added to the intended configuration. ```gradle dependencies { - // 假设我们已经指定了 'minecraft' 依赖项 + // Assume we have already specified the 'minecraft' dependency - // 假设我们有一些可以从指定仓库获得的工件 'examplemod' + // Assume we have some artifact 'examplemod' that can be obtained from a specified repository implementation fg.deobf('com.example:examplemod:1.0') } ``` -### 本地 Mod 依赖项 +### Local Mod Dependencies -如果你试图依赖的 mod 不在 maven 仓库中可用(例如,[Maven Central][central]、[CurseMaven]、[Modrinth]),你可以使用 [flat directory] 来添加 mod 依赖项: +If the mod you are trying to depend on is not available on a maven repository (e.g., [Maven Central][central], [CurseMaven], [Modrinth]), you can add a mod dependency using a [flat directory] instead: ```gradle repositories { - // 将项目目录中的 'libs' 文件夹添加为扁平目录 + // Adds the 'libs' folder in the project directory as a flat directory flatDir { dir 'libs' } @@ -54,16 +51,16 @@ repositories { dependencies { // ... - // 给定某些 ::: - // 带有扩展名 - // 扁平目录中的工件将按以下顺序解析: + // Given some ::: + // with an extension + // Artifacts in flat directories will be resolved in the following order: // - -. // - --. // - . // - -. - // 如果明确指定了分类器 - // 带有分类器的工件将优先: + // If a classifier is explicitly specified + // artifacts with the classifier will take priority: // - examplemod-1.0-api.jar // - examplemod-api.jar // - examplemod-1.0.jar @@ -73,28 +70,24 @@ dependencies { ``` :::note -组名可以是任何东西,但对于扁平目录条目必须非空,因为在解析工件文件时不会检查它们。 +The group name can be anything but must not be empty for flat directory entries as they are not checked when resolving the artifact file. ::: -非 Minecraft 依赖项 -------------------- +Non-Minecraft Dependencies +-------------------------- -Forge 在开发环境中默认不加载非 Minecraft 的依赖项。要让 Forge 识别非 Minecraft 依赖项,它们必须被添加到 `minecraftLibrary` 配置中。`minecraftLibrary` 的工作方式与 Gradle 中的 `implementation` 配置类似,在编译时间和运行时间都会应用。 +Non-Minecraft dependencies are not loaded by Forge by default in the development environment. To get Forge to recognize the non-Minecraft dependency, they must be added to the `minecraftLibrary` configuration. `minecraftLibrary` works similarly to the `implementation` configuration within Gradle, being applied during compile time and runtime. ```gradle dependencies { // ... - // 假设有一些非 Minecraft 库 'dummy-lib' + // Assume there is some non-Minecraft library 'dummy-lib' minecraftLibrary 'com.dummy:dummy-lib:1.0' } ``` -> 默认情况下,添加到开发环境中的非 Minecraft 依赖项不会包含在构建的工件中!你必须使用 [Jar-In-Jar][jij] 在构建时将依赖项包含在工件内。 - -:::note -你创建的 Mod 在分发时,必须确保所有的依赖项都遵循其相应的许可协议,并且你在你的模组中包含它们时也符合这些许可。 -::: +> Non-Minecraft dependencies added to the development environment will not be included in built artifact by default! You must use [Jar-In-Jar][jij] to include the dependencies within the artifact on build. [guide]: https://docs.gradle.org/8.1.1/userguide/dependency_management.html [mappings]: ../configuration/index.md#human-readable-mappings diff --git a/neogradle/docs/dependencies/jarinjar.md b/neogradle/docs/dependencies/jarinjar.md index eba23ac9b..6222a5fe0 100644 --- a/neogradle/docs/dependencies/jarinjar.md +++ b/neogradle/docs/dependencies/jarinjar.md @@ -1,102 +1,102 @@ Jar-in-Jar ========== -Jar-in-Jar 是一种从 Mod 的 jar 内部加载依赖项的方法。为了实现这一点,Jar-in-Jar 在构建时会在 `META-INF/jarjar/metadata.json` 中生成一个包含将从 jar 内部加载的工件的元数据 json 文件。 +Jar-in-Jar is a way to load dependencies for mods from within the jars of the mods. To accomplish this, Jar-in-Jar generates a metadata json within `META-INF/jarjar/metadata.json` on build containing the artifacts to load from within the jar. -Jar-in-Jar 是一个完全可选的系统,可以使用 `jarJar#enable` 在 `minecraft` 块之前启用。这将包括所有来自 `jarJar` 配置的依赖项进入 `jarJar` 任务。您可以类似于其他 jar 任务来配置该任务: +Jar-in-Jar is a completely optional system which can be enabled using `jarJar#enable` before the `minecraft` block. This will include all dependencies from the `jarJar` configuration into the `jarJar` task. You can configure the task similarly to other jar tasks: ```gradle -// 在 build.gradle 中 +// In build.gradle -// 为您的 mod 启用 Jar-in-Jar 系统 +// Enable the Jar-in-Jar system for your mod jarJar.enable() -// 配置 'jarJar' 任务 -// 'all' 是默认的分类器 +// Configure the 'jarJar' task +// 'all' is the default classifier tasks.named('jarJar') { // ... } ``` -添加依赖项 -------- +Adding Dependencies +------------------- -您可以使用 `jarJar` 配置将依赖项添加到您的 jar 内部。由于 Jar-in-Jar 是一个协商系统,所有版本都应提供一个支持的范围。 +You can add dependencies to be included inside your jar using the `jarJar` configuration. As Jar-in-Jar is a negotiation system, all versions should supply a supported range. ```gradle -// 在 build.gradle 中 +// In build.gradle dependencies { - // 编译时使用 examplelib 的最高支持版本 - // 区间在 2.0(含)和 3.0(不含)之间 + // Compiles against and includes the highest supported version of examplelib + // between 2.0 (inclusive) and 3.0 (exclusive) jarJar(group: 'com.example', name: 'examplelib', version: '[2.0,3.0)') } ``` -如果您需要指定一个确切的版本来包含,而不是范围内的最高支持版本,您可以在依赖项闭包内使用 `jarJar#pin`。在这些情况下,编译时将使用工件版本,而固定版本将被打包在 mod jar 内部。 +If you need to specify an exact version to include rather than the highest supported version in the range, you can use `jarJar#pin` within the dependency closure. In these instances, the artifact version will be used during compile time while the pinned version will be bundled inside the mod jar. ```gradle -// 在 build.gradle 中 +// In build.gradle dependencies { - // 编译时使用 examplelib 的最高支持版本 - // 区间在 2.0(含)和 3.0(不含)之间 + // Compiles against the highest supported version of examplelib + // between 2.0 (inclusive) and 3.0 (exclusive) jarJar(group: 'com.example', name: 'examplelib', version: '[2.0,3.0)') { - // 包含 examplelib 2.8.0 + // Includes examplelib 2.8.0 jarJar.pin(it, '2.8.0') } } ``` -您还可以在编译时针对特定版本,同时固定一个版本范围: +You can additionally pin a version range while compiling against a specific version instead: ```gradle -// 在 build.gradle 中 +// In build.gradle dependencies { - // 针对 examplelib 2.8.0 编译 + // Compiles against examplelib 2.8.0 jarJar(group: 'com.example', name: 'examplelib', version: '2.8.0') { - // 包含 examplelib 的最高支持版本 - // 在 2.0(含)到 3.0(不含)之间 + // Includes the highest supported version of examplelib + // between 2.0 (inclusive) and 3.0 (exclusive) jarJar.pin(it, '[2.0,3.0)') } } ``` -### 使用运行时依赖项 +### Using Runtime Dependencies -如果您想将您的模组的运行时依赖项包含在您的 jar 中,可以在您的构建脚本中调用 `jarJar#fromRuntimeConfiguration`。如果您决定使用此选项,强烈建议包含依赖项过滤器;否则,每一个依赖项 —— 包括 Minecraft 和 Forge —— 也将被打包到 jar 中。为了支持更灵活的声明,`dependency` 配置已被添加到 `jarJar` 扩展和任务中。使用它,您可以指定要从配置中包含或排除的模式: +If you would like to include the runtime dependencies of your mod inside your jar, you can invoke `jarJar#fromRuntimeConfiguration` within your buildscript. If you decide to use this option, it is highly suggested to include dependency filters; otherwise, every single dependency -- including Minecraft and Forge -- will be bundled in the jar as well. To support more flexible statements, the `dependency` configuration has been added to the `jarJar` extension and task. Using this, you can specify patterns to include or exclude from the configuration: ```gradle -// 在 build.gradle 中 +// In build.gradle -// 添加运行时依赖项到 jar +// Add runtime dependencies to jar jarJar.fromRuntimeConfiguration() // ... jarJar { - // 在此处从运行时配置中包括或排除依赖项 + // Include or exclude dependencies here from runtime configuration dependencies { - // 排除任何以 'com.google.gson.' 开头的依赖项 + // Exclude any dependency which begins with 'com.google.gson.' exclude(dependency('com.google.gson.*')) } } ``` :::tip -通常建议在使用 `#fromRuntimeConfiguration` 时设置至少一个 `include` 过滤器。 +It is generally recommended to set at least one `include` filter when using `#fromRuntimeConfiguration`. ::: -发布 Jar-in-Jar 到 Maven ------------------------- +Publishing a Jar-in-Jar to Maven +-------------------------------- -出于存档的原因,ForgeGradle 支持将 Jar-in-Jar 工件发布到选择的 Maven,类似于 [Shadow 插件][shadow] 的处理方式。实际上,这并不常用也不推荐。 +For archival reasons, ForgeGradle supports publishing Jar-in-Jar artifacts to a maven of choice, similar to how the [Shadow plugin][shadow] handles it. In practices, this is not useful or recommended. ```gradle -// 在 build.gradle 中(已安装 'maven-publish' 插件) +// In build.gradle (has 'maven-publish' plugin) publications { mavenJava(MavenPublication) { - // 添加标准的 java 组件和 Jar-in-Jar 工件 + // Add standard java components and Jar-in-Jar artifact from components.java jarJar.component(it) diff --git a/neogradle/docs/gettingstarted/index.md b/neogradle/docs/gettingstarted/index.md index 83d63d1db..f2a88690e 100644 --- a/neogradle/docs/gettingstarted/index.md +++ b/neogradle/docs/gettingstarted/index.md @@ -1,42 +1,42 @@ -开始使用ForgeGradle -============================ +Getting Started with ForgeGradle +================================ -如果你之前从未使用过ForgeGradle,这里是建立开发环境所需的最少的信息。 +If you have never used ForgeGradle before, here is the minimum amount of information needed to get a development environment setup. -#### 前提条件 +#### Prerequisites -* Java开发工具包(JDK)的安装 +* An installation of the Java Development Kit (JDK) -Minecraft版本 | Java开发工具包版本 -:---: | :---: -1.12 - 1.16 | [JDK 8][jdk8] -1.17 | [JDK 16][jdk16] -1.18 - 1.19 | [JDK 17][jdk17] +Minecraft Versions | Java Development Kit Version +:---: | :---: +1.12 - 1.16 | [JDK 8][jdk8] +1.17 | [JDK 16][jdk16] +1.18 - 1.19 | [JDK 17][jdk17] -* 熟悉集成开发环境(IDE) - * 最好使用具有某种形式的Gradle集成的IDE +* Familiarity with an Integrated Development Environment (IDE) + * It is preferable to use one with some form of Gradle integration -## 设置ForgeGradle +## Setting Up ForgeGradle -1. 首先从MinecraftForge下载[模组开发套件(MDK)][mdk],将zip文件解压到一个空的目录中。 -1. 使用你选择的IDE打开MDK解压的目录。如果你的IDE有Gradle集成,将其作为Gradle项目导入。 -1. 自定义你的Gradle构建脚本以适应你的模组: - 1. 将`archivesBaseName`设置为所需的模组ID。此外,替换所有出现的`examplemod`为你的模组ID。 - 1. 更改`group`为你想要的包名称。确保遵循现有的[命名约定][group]。 - 1. 更改`version`号以反映你模组的当前版本。强烈推荐使用[Forge关于语义版本控制的扩展][semver]。 +1. First download a copy of the [Modder Development Kit (MDK)][mdk] from MinecraftForge and extract the zip to an empty directory. +1. Open the directory you extracted the MDK to within your IDE of choice. If your IDE integrates with Gradle, import it as a Gradle project. +1. Customize your Gradle buildscript for your mod: + 1. Set `archivesBaseName` to the desired mod id. Additionally, replace all occurrences of `examplemod` with the mod id as well. + 1. Change the `group` to your desired package name. Make sure to follow existing [naming conventions][group]. + 1. Change the `version` number to reflect the current version of your mod. It is highly recommended to use [Forge's extension on semantic versioning][semver]. -:::警告 -确保对模组ID的任何更改都反映在mods.toml和主要模组类中。有关更多信息,请查看Forge文档上的[构建你的模组][structuring]。 +:::caution +Make sure that any changes to the mod id are reflected in the mods.toml and main mod class. See [Structuring Your Mod][structuring] on the Forge docs for more information. ::: -2. 使用IDE重新加载或刷新你的Gradle项目。如果你的IDE没有Gradle集成,运行以下命令在项目目录中的shell中: +2. Reload or refresh your Gradle project using your IDE. If your IDE does not have Gradle integration, run the following from a shell in your project's directory: ```sh ./gradlew build --refresh-dependencies ``` -5. 如果你的IDE是Eclipse、IntelliJ IDEA或Visual Studio Code,你可以使用以下命令生成运行配置,分别是: +5. If your IDE is either Eclipse, IntelliJ IDEA, or Visual Studio Code, you can generate run configurations using one of the following commands, respectively: #### Eclipse @@ -56,13 +56,14 @@ Minecraft版本 | Java开发工具包版本 ./gradlew genVSCodeRuns ``` -你可以使用生成的运行配置来运行客户端、服务器等。 +You can the run the client, server, etc. using one of the generated run configurations. -:::小贴士 -如果你的IDE未列出,你仍然可以使用`./gradlew run*` (例如,`runClient`, `runServer`, `runData`)来运行配置。这些命令也可以在支持的IDE中使用。 +:::tip +If your IDE is not listed, you can still run the configurations using `./gradlew run*` (e.g., `runClient`, `runServer`, `runData`). You can use these commands with the supported IDEs as well. ::: -恭喜你,现在你已经建立了一个开发环境! +Congratulations, now you have a development environment set up! + [jdk8]: https://adoptium.net/temurin/releases/?version=8 [jdk16]: https://adoptium.net/temurin/releases/?version=16 @@ -70,4 +71,5 @@ Minecraft版本 | Java开发工具包版本 [mdk]: https://files.minecraftforge.net/ [group]: https://docs.oracle.com/javase/tutorial/java/package/namingpkgs.html -[semver]: https:// +[semver]: https://docs.minecraftforge.net/en/latest/gettingstarted/versioning/ +[structuring]: https://docs.minecraftforge.net/en/latest/gettingstarted/structuring/ diff --git a/neogradle/docs/index.md b/neogradle/docs/index.md index 8bf750c2b..85e36cf2e 100644 --- a/neogradle/docs/index.md +++ b/neogradle/docs/index.md @@ -1,44 +1,48 @@ -NeoGradle 文档 +--- +sidebar_position: 0 +--- + +NeoGradle Documentation ========================= -:::注意 -请注意,鉴于NeoForged的最近创建,本文档可能不是最新的。 +:::caution +Please note that this documentation may not be up to date considering the recent creation of NeoForged. -在NeoGradle发布其首个版本之前,您应该参考ForgeGradle的文档。版本6和5的ForgeGradle文档已存在这里。 +Until NeoGradle releases its first version, you should refer to ForgeGradle documentation. The ForgeGradle documentation for versions 6 and 5 has been archived here. ::: -这是[ForgeGradle]官方文档,一个用于使用MinecraftForge开发[MinecraftForge]和mods的[Gradle]插件。 +This is the official documentation for [ForgeGradle], a [Gradle] plugin for developing [MinecraftForge] and mods using MinecraftForge. -这份文档 _仅_ 适用于ForgeGradle,**这不是Java、Groovy或Gradle教程**。 +This documentation is _only_ for ForgeGradle, **this is not a Java, Groovy, or Gradle tutorial**. -如果您想为文档贡献内容,请阅读[为文档做贡献][contributing]。 +If you would like to contribute to the docs, read [Contributing to the Docs][contributing]. -添加插件 +Adding the Plugin ----------------- -ForgeGradle 使用 Gradle 8;它可以通过在 `build.gradle` 的 `plugins` 区块中添加以下信息到 `settings.gradle` 来添加: +ForgeGradle uses Gradle 8; it can be added using the `plugins` block in the `build.gradle` by adding the following information to the `settings.gradle`: ```gradle -// 在 settings.gradle 中 +// In settings.gradle pluginManagement { repositories { // ... - // 添加 MinecraftForge maven + // Add the MinecraftForge maven maven { url = 'https://maven.minecraftforge.net/' } } } plugins { - // 添加工具链解析器 + // Add toolchain resolver id 'org.gradle.toolchains.foojay-resolver-convention' version '0.5.0' } ``` ```gradle -// 在 build.gradle 中 +// In build.gradle plugins { - // 添加 ForgeGradle 插件 + // Add the ForgeGradle plugin id 'net.minecraftforge.gradle' version '[6.0,6.2)' // ... diff --git a/neogradle_versioned_docs/version-5.x/docs/configuration/index.md b/neogradle_versioned_docs/version-5.x/docs/configuration/index.md index 7c1443add..785e001f3 100644 --- a/neogradle_versioned_docs/version-5.x/docs/configuration/index.md +++ b/neogradle_versioned_docs/version-5.x/docs/configuration/index.md @@ -1,50 +1,50 @@ -ForgeGradle配置 +ForgeGradle Configurations ========================== -ForgeGradle具有许多配置,可以改变开发环境的设置。大多数配置使用`minecraft`代码块设置;然而,还有一些可以在`dependencies`代码块中指定,或者修改构建好的`jar`文件,例如`reobfJar`。 +ForgeGradle has numerous configurations that can change how the development environment is configured. Most configurations are set using the `minecraft` block; however, some others can be specified within the `dependencies` block or modify the built `jar`, such as `reobfJar`. -启用访问转换器 +Enabling Access Transformers ---------------------------- -[访问转换器][at]可以扩大Minecraft类、方法和字段的可见性或修改`final`标志。要在开发环境中启用访问转换器,可以设置`accessTransformer`到相关的配置文件: +[Access Transformers][at] can widen the visibility or modify the `final` flag of Minecraft classes, methods, and fields. To enable access transformers in the production environment, you can set `accessTransformer` to configuration file in question: ```gradle minecraft { // ... - // 添加一个相对于项目目录的访问转换器文件 + // Add an access transformer file relative to the project's directory accessTransformer = project.file('src/main/resources/META-INF/accesstransformer.cfg') } ``` -:::警告 -虽然开发环境中的访问转换器可以从用户指定的任何地方读取,在开发环境中,文件只能从`META-INF/accesstransformer.cfg`读取。 +:::caution +While the access transformer in the development environment can be read from anywhere the user specifies, in production, the file will only be read from `META-INF/accesstransformer.cfg`. ::: -可读的映射 +Human-Readable Mappings ----------------------- -Minecraft的源代码是混淆的。因此,所有的类、方法和字段都有机器生成的名称,没有包结构。与此同时,函数局部变量名称由于Local Variable Table的存储方式,被转化为雪人(☃)。使用混淆名创建模组是困难的,因为给它们做逆向工程是乏味的,可能在版本之间变化,并且可能导致在语言本身中是无效的,但在字节码中不是。 +Minecraft's source code is obfuscated. As such, all classes, methods, and fields have machine-generated names with no package structures. Function-local variable names, meanwhile, are turned into a snowman (`☃`) due to how the Local Variable Table is stored. It is difficult to create mods using obfuscated names as reverse engineering them is tedious, may change between versions, and may be invalid in the language itself but not in the bytecode. -为了解决后两个问题,Forge通过[ForgeAutoRenamingTool][fart]将每个类、方法、字段和参数模糊地映射到一个唯一的标识符,即SRG名称。SRG映射在用户客户端运行游戏时用于开发。 +To address the last two issues, Forge fuzzily maps each class, method, field, and parameter to a unique identifier, known as the SRG name, via the [ForgeAutoRenamingTool][fart]. SRG mappings are used in production when the game is being run by the user client. -为了便于开发,ForgeGradle允许用户选择一个映射集,在SRG映射的基础上应用,这里我们称之为可读的映射。ForgeGradle知道如何将模组jar转换为用于开发的SRG映射,通过`reobf*`任务。 +To allow easier development, ForgeGradle allows the user to choose a mapping set to apply on top of SRG mappings, which we will refer to as human-readable mappings. ForgeGradle knows how to convert the mod jar to SRG mappings for use in production via the `reobf*` task. -可以通过在`minecraft`代码块中设置`mappings`字段来指定使用的映射集。`mappings`字段接受两个参数:`channel`,它是映射集的类型,以及`version`,它是要应用的映射集的版本。 +The mapping set used can be specified by setting the `mappings` field in the `minecraft` block. The `mappings` field takes in two arguments: `channel` which is the type of the mapping set, and `version` which is the version of the mapping set to apply. -目前,默认内置到ForgeGradle中有三个映射集: +Currently, there are three default mapping sets built into ForgeGradle: -* `official` - 使用由Mojang提供的映射集。这些映射没有参数名称,且只存在于1.14及更高版本。 -* `stable` - 使用由MCP生成的映射集。这些通常是不完整的,从1.17版本开始就不再存在了。 -* `snapshot` - 也是由MCP生成的映射集,类似于程序的每日版本。这些也通常是不完整的,从1.17版本开始就不再存在了。 +* `official` - This uses the mapping set provided by Mojang. These mappings do not have parameter names and only exist from 1.14 onward. +* `stable` - This uses a mapping set generated by MCP. These are typically incomplete and no longer exist as of 1.17. +* `snapshot` - This also is a mapping set generated by MCP, similar to a nightly build of a program. These are also typically incomplete and no longer exist as of 1.17. -:::注意 -开发中使用的类名来自1.17之前的`stable`,从1.17开始来自`official`。 +:::note +The class names used in production are from `stable` prior to 1.17 and from `official` from 1.17 onwards. ::: ```gradle mappings { - // 设置映射,使用Mojang为Minecraft 1.19.4版本提供的映射。 + // Sets the mappings to use those from Mojang for Minecraft 1.19.4. mappings channel: 'official', version: '1.19.4' // ... @@ -53,7 +53,7 @@ mappings { ### Parchment -Parchment是由ParchmentMC维护的官方项目,在`official`映射集的基础上提供开放的、社区来源的参数名称和javadocs。你可以在[他们的网站][parchment]上了解如何设置和使用parchment映射集。 +Parchment is an official project maintained by ParchmentMC which provides open, community-sourced parameter names and javadocs on top of the `official` mapping set. You can learn how setup and use the parchment mapping set on [their website][parchment]. [at]: https://docs.minecraftforge.net/en/latest/advanced/accesstransformers/ [fart]: https://github.com/MinecraftForge/ForgeAutoRenamingTool diff --git a/neogradle_versioned_docs/version-5.x/docs/configuration/runs.md b/neogradle_versioned_docs/version-5.x/docs/configuration/runs.md index 74f79cef9..083e8813f 100644 --- a/neogradle_versioned_docs/version-5.x/docs/configuration/runs.md +++ b/neogradle_versioned_docs/version-5.x/docs/configuration/runs.md @@ -1,139 +1,139 @@ -运行配置 +Run Configurations ================== -运行配置定义了一个游戏实例如何运行。这包括参数、工作目录、任务名等。运行配置在`minecraft.runs`块中定义。虽然默认情况下没有配置运行,但[Forge][userdev]确实提供了`client`、`server`、`data`、或`gameTestServer`的配置。 +Run configurations define how an instance of the game is going to run. This includes arguments, working directories, task names, etc. Run configurations are defined within the `minecraft.runs` block. While no runs are configured by default, [Forge][userdev] does provide the configurations `client`, `server`, `data`, or `gameTestServer`. ```gradle minecraft { // ... runs { - // 在此处配置运行 + // Configure runs here } } ``` -运行配置可以使用闭包类似于任何`NamedDomainObjectContainer`进行添加。 +Run configurations can be added similar to any `NamedDomainObjectContainer` using closures. ```gradle -// 在minecraft代码块内部 +// Inside the minecraft block runs { - // 创建或配置名为'client'的运行配置 + // Creates or configures the run configuration named 'client' client { - // 配置运行 + // Configure run } } ``` -以下是可用的配置属性: +The following configurations properties are available: ```gradle -// 在runs块内部 +// Inside the runs block client { - // Gradle运行任务的名称, - // 默认为`runX`,其中X是容器名称 + // The name of the Gradle run tasks, + // Defaults to 'runX' where X is the container name taskName 'runThing' - // 设置要启动程序的入口点 - // Forge将userdev main设置为'cpw.mods.bootstraplauncher.BootstrapLauncher' + // Sets the entrypoint of the program to launch + // Forge sets userdev main to be 'cpw.mods.bootstraplauncher.BootstrapLauncher' main 'com.example.Main' - // 设置配置的工作目录 - // 默认为'./run' + // Sets the working directory of the config + // Defaults to './run' workingDirectory 'run' - // 设置IntelliJ IDEA配置其运行的模块的名称 - // 默认为'.main' + // Sets the name of the module for IntelliJ IDEA to configure for its runs + // Defaults to '.main' ideaModule 'example.main' - // 设置是否应运行Minecraft客户端 - // 如果未指定,检查以下内容 - // - 是否存在一个环境属性'thing',包含'client' - // - 配置名称中是否包含'client' - // - main是否设置为'mcp.client.Start' - // - main是否设置为'net.minecraft.client.main.Main' + // Sets whether this should run a Minecraft client + // If not specified, checks the following + // - Is there an environment property 'thing' that contains 'client' + // - Does the configuration name contain 'client' + // - Is main set to 'mcp.client.Start' + // - Is main set to 'net.minecraft.client.main.Main' client true - // 设置此配置的父级以便继承 + // Set the parent of this configuration to inherit from parent runs.example - // 设置此配置的子配置 + // Sets the children of this configuration children runs.child - // 合并此配置并指定是否覆盖现有属性 + // Merges this configuration and specifies whether to overwrite existing properties merge runs.server, true - // 如果不为false,将父配置的参数合并到此配置中 + // If not false, will merge the arguments of the parent with this configuration inheritArgs false - // 如果不为false,将父配置的JVM参数合并到此配置中 + // If not false, will merge the JVM arguments of the parent with this configuration inheritJvmArgs false - // 将源集添加到类路径中 - // 如果未指定,添加sourceSet.main + // Adds a sourceset to the classpath + // If none is specified, adds sourceSet.main source sourceSets.api - // 设置运行的环境属性 - // 值将被解释为文件或字符串 + // Sets an environment property for the run + // Value will be interpreted as a file or a string environment 'envKey', 'value' - // 设置系统属性 - // 值将被解释为文件或字符串 + // Sets a system property + // Value will be interpreted as a file or a string property 'propKey', 'value' - // 设置传入应用程序的参数 - // 可以使用'args'指定多个 + // Sets an argument to be passed into the application + // Can specify multiple with 'args' arg 'hello' - // 设置JVM参数 - // 可以使用'jvmArgs'指定多个 + // Sets a JVM argument + // Can specify multiple with 'jvmArgs' jvmArg '-Xmx2G' - // 设置标记 - // 目前,正在使用以下的标记: + // Sets a token + // Currently, the following tokens are being used: // - runtime_classpath // - minecraft_classpath token 'tokenKey', 'value' - //设置延迟初始化的标记 - // 应该通常用于替代'token',例如当标记解析Gradle配置时 + // Sets a token that's lazily initialized + // Should usually be used instead of 'token', for example when the token resolves Gradle configurations lazyToken('lazyTokenKey') { 'value' } - // 如果不为false,Gradle将在进程结束后停止 + // If not false, Gradle will stop once the process has finished forceExit true - // 如果为true,编译所有项目而不是当前任务 - // 此选项仅由IntelliJ IDEA使用 + // If true, compile all projects instead of for the current task + // This is only used by IntelliJ IDEA buildAllProjects false } ``` -:::提示 -你可以在[MinecraftForge构建脚本][buildscript]内看到所有配置的userdev属性的列表。 +:::tip +You can see a list of all configured userdev properties within the [MinecraftForge buildscript][buildscript]. ::: -模组配置 +Mod Configurations ------------------ -可以使用Run配置中的`mods`块添加当前环境中的模组。模组块也是`NamedDomainObjectContainer`。 +A mod in the current environment can be added using the `mods` block within a Run configuration. Mod blocks are also `NamedDomainObjectContainer`s. ```gradle -// 在runs块内部 +// Inside the runs block client { // ... mods { - // 配置'example'模块 + // Configures the 'example' mod example { - // 将源集添加到模组的源中 - // 这比手动添加类和资源更为推荐 + // Add a source set to a mod's sources + // This is recommended over manually adding classes and resources source sourceSets.main - // 设置模组类的位置 + // Sets the location of the mod's classes classes sourceSets.api.output - // 设置模组资源的位置 + // Sets the location of the mod's resources resources files('./my_resources') } } diff --git a/neogradle_versioned_docs/version-5.x/docs/dependencies/index.md b/neogradle_versioned_docs/version-5.x/docs/dependencies/index.md index 9cb878bec..1576ec66a 100644 --- a/neogradle_versioned_docs/version-5.x/docs/dependencies/index.md +++ b/neogradle_versioned_docs/version-5.x/docs/dependencies/index.md @@ -1,48 +1,48 @@ -依赖关系 +Dependencies ============ -依赖关系不仅用于开发模组之间的互操作性或向游戏添加额外的库,而且还决定了要为哪个版本的Minecraft开发。这里会简要介绍如何修改`repositories`和`dependencies`块来向你的开发环境添加依赖。 +Dependencies are not only used to develop interoperability between mods or add additional libraries to the game, but it also determines what version of Minecraft to develop for. This will provide a quick overview on how to modify the `repositories` and `dependencies` block to add dependencies to your development environment. -> 这不会深入解释Gradle概念。在继续之前,强烈推荐阅读[Gradle依赖管理指南][guide]。 +> This will not explain Gradle concepts in depth. It is highly recommended to read the [Gradle Dependency Management guide][guide] before continuing. `minecraft` ----------- -`minecraft`依赖项指定要使用的Minecraft版本,必须包含在`dependencies`块中。任何非`net.minecraft`组的工件都将应用随依赖项提供的任何补丁。这通常仅指定`net.minecraftforge:forge`工件。 +The `minecraft` dependency specifies the version of Minecraft to use and must be included in the `dependencies` block. Any artifact, except artifacts which have the group `net.minecraft`, will apply any patches provided with the dependency. This typically only specifies the `net.minecraftforge:forge` artifact. ```gradle dependencies { - // Forge工件的版本格式为'-' - // 'mc_version' 是要加载的Minecraft版本(例如,1.19.4) - // 'forge_version' 是该Minecraft版本所需的Forge版本(例如,45.0.23) - // 正版可以使用'net.minecraft:joined:'来编译 + // Version of Forge artifact is in the form '-' + // 'mc_version' is the version of Minecraft to load (e.g., 1.19.4) + // 'forge_version' is the version of Forge wanted for that Minecraft version (e.g., 45.0.23) + // Vanilla can be compiled against using 'net.minecraft:joined:' instead minecraft 'net.minecraftforge:forge:1.19.4-45.0.23' } ``` -模组依赖项 +Mod Dependencies ---------------- -在典型的开发环境中,Minecraft的代码会去混淆到中间映射,这些映射用于生产,然后转换成模组开发者指定的任何[可读的映射][mappings]。构建的模组工件在生产中被混淆为SRG映射,因此不能直接用作Gradle依赖。 +In a typical development environment, Minecraft is deobfuscated to intermediate mappings, used in production, and then transformed into whatever [human-readable mappings][mappings] the modder specified. Mod artifacts, when built, are obfuscated to production mappings (SRG), and as such, are unable to be used directly as a Gradle dependency. -因此,所有模组依赖项在添加到预定配置之前都需要用`fg.deobf`进行包装。 +As such, all mod dependencies need to be wrapped with `fg.deobf` before being added to the intended configuration. ```gradle dependencies { - // 假设我们已经指定了`minecraft`依赖项 + // Assume we have already specified the 'minecraft' dependency - // 假设我们有一些可以从指定仓库获得的`examplemod`工件 + // Assume we have some artifact 'examplemod' that can be obtained from a specified repository implementation fg.deobf('com.example:examplemod:1.0') } ``` -### 本地模组依赖项 +### Local Mod Dependencies -如果你试图依赖的模组没有在maven仓库(例如,[Maven Central][central]、[CurseMaven]、[Modrinth])上可用,你可以使用[平面目录][flat]添加模组依赖项: +If the mod you are trying to depend on is not available on a maven repository (e.g., [Maven Central][central], [CurseMaven], [Modrinth]), you can add a mod dependency using a [flat directory] instead: ```gradle repositories { - // 将项目目录中的`libs`文件夹添加为平面目录 + // Adds the 'libs' folder in the project directory as a flat directory flatDir { dir 'libs' } @@ -51,16 +51,16 @@ repositories { dependencies { // ... - // 假定有某个::: - // 且扩展名为 - // 平面目录中的工件将按以下顺序解析: + // Given some ::: + // with an extension + // Artifacts in flat directories will be resolved in the following order: // - -. // - --. // - . // - -. - // 如果显式指定了分类器 - // 带有分类器的工件将获得优先权: + // If a classifier is explicitly specified + // artifacts with the classifier will take priority: // - examplemod-1.0-api.jar // - examplemod-api.jar // - examplemod-1.0.jar @@ -69,30 +69,33 @@ dependencies { } ``` -:::注意 -组名可以是任何东西,但对于平面目录项来说不能是空的,因为在解析工件文件时不会检查它。 +:::note +The group name can be anything but must not be empty for flat directory entries as they are not checked when resolving the artifact file. ::: -非Minecraft依赖项 +Non-Minecraft Dependencies -------------------------- -Forge默认情况下不会在开发环境中加载非Minecraft依赖项。要让Forge识别非Minecraft依赖项,它们必须被添加到`minecraftLibrary`配置中。`minecraftLibrary`的工作方式类似于Gradle中的`implementation`配置,在编译时和运行时被应用。 +Non-Minecraft dependencies are not loaded by Forge by default in the development environment. To get Forge to recognize the non-Minecraft dependency, they must be added to the `minecraftLibrary` configuration. `minecraftLibrary` works similarly to the `implementation` configuration within Gradle, being applied during compile time and runtime. ```gradle dependencies { // ... - // 假设有一些非Minecraft库'dummy-lib' + // Assume there is some non-Minecraft library 'dummy-lib' minecraftLibrary 'com.dummy:dummy-lib:1.0' } ``` -> 默认情况下,添加到开发环境中的非Minecraft依赖项将不会包含在构建的工件中!你必须使用[Jar-In-Jar][jij]在构建时,在工件中包含这些依赖项。 +> Non-Minecraft dependencies added to the development environment will not be included in built artifact by default! You must use [Jar-In-Jar][jij] to include the dependencies within the artifact on build. [guide]: https://docs.gradle.org/7.6/userguide/dependency_management.html [mappings]: ../configuration/index.md#human-readable-mappings + [central]: https://central.sonatype.com/ [CurseMaven]: https://cursemaven.com/ [Modrinth]: https://docs.modrinth.com/docs/tutorials/maven/ + [flat]: https://docs.gradle.org/7.6/userguide/declaring_repositories.html#sub:flat_dir_resolver + [jij]: ./jarinjar.md diff --git a/neogradle_versioned_docs/version-5.x/docs/dependencies/jarinjar.md b/neogradle_versioned_docs/version-5.x/docs/dependencies/jarinjar.md index 3436bf0f2..6222a5fe0 100644 --- a/neogradle_versioned_docs/version-5.x/docs/dependencies/jarinjar.md +++ b/neogradle_versioned_docs/version-5.x/docs/dependencies/jarinjar.md @@ -1,78 +1,109 @@ Jar-in-Jar ========== -Jar-in-Jar 是一种从模组的 jar 文件内加载依赖的方式。为了实现这一点,在构建时 Jar-in-Jar 在 `META-INF/jarjar/metadata.json` 中生成一个元数据 json 文件,其中包含要从 jar 内加载的工件。 +Jar-in-Jar is a way to load dependencies for mods from within the jars of the mods. To accomplish this, Jar-in-Jar generates a metadata json within `META-INF/jarjar/metadata.json` on build containing the artifacts to load from within the jar. -Jar-in-Jar 是一个完全可选的系统,可以使用 `jarJar#enable` 在 `minecraft` 代码块之前启用。这将包括所有来自 `jarJar` 配置的依赖到 `jarJar` 任务中。你可以像其它 jar 任务一样配置这个任务: +Jar-in-Jar is a completely optional system which can be enabled using `jarJar#enable` before the `minecraft` block. This will include all dependencies from the `jarJar` configuration into the `jarJar` task. You can configure the task similarly to other jar tasks: ```gradle -// 在 build.gradle 中 +// In build.gradle -// 为你的模组启用 Jar-in-Jar 系统 +// Enable the Jar-in-Jar system for your mod jarJar.enable() -// 配置 'jarJar' 任务 -// 'all' 是默认的分类器 + +// Configure the 'jarJar' task +// 'all' is the default classifier tasks.named('jarJar') { // ... } ``` -添加依赖 +Adding Dependencies ------------------- -你可以使用 `jarJar` 配置在你的 jar 文件中包含要添加的依赖。由于 Jar-in-Jar 是一个协商系统,所有的版本都应提供一个支持的版本范围。 +You can add dependencies to be included inside your jar using the `jarJar` configuration. As Jar-in-Jar is a negotiation system, all versions should supply a supported range. ```gradle -// 在 build.gradle 中 +// In build.gradle dependencies { - // 编译并包含 examplelib 的最高支持版本 - // 从 2.0(包含)到 3.0(不包含) + // Compiles against and includes the highest supported version of examplelib + // between 2.0 (inclusive) and 3.0 (exclusive) jarJar(group: 'com.example', name: 'examplelib', version: '[2.0,3.0)') } ``` -如果你需要在编译时指定要包含的确切版本,而不是范围内的最高支持版本,你可以在依赖闭包内使用 `jarJar#pin`。在这些情况下,将在编译时使用工件版本,而固定的版本将被打包在模组 jar 内。 +If you need to specify an exact version to include rather than the highest supported version in the range, you can use `jarJar#pin` within the dependency closure. In these instances, the artifact version will be used during compile time while the pinned version will be bundled inside the mod jar. ```gradle -// 在 build.gradle 中 +// In build.gradle dependencies { - // 编译时使用 examplelib 的最高支持版本 - // 从 2.0(包含)到 3.0(不包含) + // Compiles against the highest supported version of examplelib + // between 2.0 (inclusive) and 3.0 (exclusive) jarJar(group: 'com.example', name: 'examplelib', version: '[2.0,3.0)') { - // 包含 examplelib 2.8.0 + // Includes examplelib 2.8.0 jarJar.pin(it, '2.8.0') } } ``` -你也可以在编译对特定版本编译的同时,固定一个版本范围: +You can additionally pin a version range while compiling against a specific version instead: ```gradle -// 在 build.gradle 中 +// In build.gradle dependencies { - // 针对 examplelib 2.8.0 编译 + // Compiles against examplelib 2.8.0 jarJar(group: 'com.example', name: 'examplelib', version: '2.8.0') { - // 包含 examplelib 的最高支持版本 - // 从 2.0(包含)到 3.0(不包含) + // Includes the highest supported version of examplelib + // between 2.0 (inclusive) and 3.0 (exclusive) jarJar.pin(it, '[2.0,3.0)') } } ``` -### 使用运行时依赖 +### Using Runtime Dependencies -如果你希望在你的 jar 中包含你的模组的运行时依赖,你可以在构建脚本中调用 `jarJar#fromRuntimeConfiguration`。如果你决定使用这个选项,强烈建议包含依赖过滤器;否则,包括 Minecraft 和 Forge 在内的每一个依赖项都会被打包到jar文件中。为了支持更灵活的声明,`dependency` 配置已添加到 `jarJar` 扩展和任务中。使用它,你可以指定模式来包括或排除来自配置的内容: +If you would like to include the runtime dependencies of your mod inside your jar, you can invoke `jarJar#fromRuntimeConfiguration` within your buildscript. If you decide to use this option, it is highly suggested to include dependency filters; otherwise, every single dependency -- including Minecraft and Forge -- will be bundled in the jar as well. To support more flexible statements, the `dependency` configuration has been added to the `jarJar` extension and task. Using this, you can specify patterns to include or exclude from the configuration: ```gradle -// 在 build.gradle 中 +// In build.gradle -// 向 jar 添加运行时依赖 +// Add runtime dependencies to jar jarJar.fromRuntimeConfiguration() // ... jarJar { - // 在这里包含或排除运行时配置中的依赖 + // Include or exclude dependencies here from runtime configuration dependencies { - // 排除任何以 'com.google.gson.' 开头的 + // Exclude any dependency which begins with 'com.google.gson.' + exclude(dependency('com.google.gson.*')) + } +} +``` + +:::tip +It is generally recommended to set at least one `include` filter when using `#fromRuntimeConfiguration`. +::: + +Publishing a Jar-in-Jar to Maven +-------------------------------- + +For archival reasons, ForgeGradle supports publishing Jar-in-Jar artifacts to a maven of choice, similar to how the [Shadow plugin][shadow] handles it. In practices, this is not useful or recommended. + +```gradle +// In build.gradle (has 'maven-publish' plugin) + +publications { + mavenJava(MavenPublication) { + // Add standard java components and Jar-in-Jar artifact + from components.java + jarJar.component(it) + + // ... + } +} +``` + + +[shadow]: https://imperceptiblethoughts.com/shadow/getting-started/ diff --git a/neogradle_versioned_docs/version-5.x/docs/gettingstarted/index.md b/neogradle_versioned_docs/version-5.x/docs/gettingstarted/index.md index beb5ba9eb..248d1175b 100644 --- a/neogradle_versioned_docs/version-5.x/docs/gettingstarted/index.md +++ b/neogradle_versioned_docs/version-5.x/docs/gettingstarted/index.md @@ -1,41 +1,42 @@ -ForgeGradle入门 +Getting Started with ForgeGradle ================================ -如果你以前从未使用过ForgeGradle,这里有一些设置开发环境所需的最少内容。 +If you have never used ForgeGradle before, here is the minimum amount of information needed to get a development environment setup. -#### 先决条件 +#### Prerequisites -* Java开发工具包(JDK)的安装 +* An installation of the Java Development Kit (JDK) -Minecraft版本 | Java开发工具包版本 -:---: | :---: -1.12 - 1.16 | [JDK 8][jdk8] -1.17 | [JDK 16][jdk16] -1.18 - 1.19 | [JDK 17][jdk17] +Minecraft Versions | Java Development Kit Version +:---: | :---: +1.12 - 1.16 | [JDK 8][jdk8] +1.17 | [JDK 16][jdk16] +1.18 - 1.19 | [JDK 17][jdk17] -* 熟悉一个集成开发环境(IDE) - * 最好使用带有某种形式的Gradle集成的IDE +* Familiarity with an Integrated Development Environment (IDE) + * It is preferable to use one with some form of Gradle integration -## 设置ForgeGradle +## Setting Up ForgeGradle -1. 首先从MinecraftForge下载[模组开发套件(MDK)][mdk]的副本,并将zip解压到一个空目录中。 -1. 在你选择的IDE中打开你解压MDK的目录。如果你的IDE与Gradle集成,将其作为一个Gradle项目导入。 -1. 自定义你的Gradle构建脚本以适合你的模组: - 1. 将`archivesBaseName`设置为所需的模组ID。此外,将所有出现的`examplemod`替换为模组ID。 - 1. 更改`group`为你想要的包名。确保遵循现有的[命名惯例][group]。 - 1. 将`version`号更改为反映你的模组的当前版本。强烈建议使用[Forge对语义版本控制的扩展][semver]。 +1. First download a copy of the [Modder Development Kit (MDK)][mdk] from MinecraftForge and extract the zip to an empty directory. +1. Open the directory you extracted the MDK to within your IDE of choice. If your IDE integrates with Gradle, import it as a Gradle project. +1. Customize your Gradle buildscript for your mod: + 1. Set `archivesBaseName` to the desired mod id. Additionally, replace all occurrences of `examplemod` with the mod id as well. + 1. Change the `group` to your desired package name. Make sure to follow existing [naming conventions][group]. + 1. Change the `version` number to reflect the current version of your mod. It is highly recommended to use [Forge's extension on semantic versioning][semver]. -:::警告 -确保对模组ID的任何更改都反映在mods.toml和主模组类中。有关更多信息,请参阅Forge文档上的[结构化你的模组][structuring]。 + +:::caution +Make sure that any changes to the mod id are reflected in the mods.toml and main mod class. See [Structuring Your Mod][structuring] on the Forge docs for more information. ::: -4. 使用你的IDE重新加载或刷新你的Gradle项目。如果你的IDE没有Gradle集成,以上一个shell在你项目的目录下运行以下命令: +4. Reload or refresh your Gradle project using your IDE. If your IDE does not have Gradle integration, run the following from a shell in your project's directory: ```sh ./gradlew build --refresh-dependencies ``` -5. 如果你的IDE是Eclipse、IntelliJ IDEA或Visual Studio Code,你可以使用下列命令之一生成运行配置: +5. If your IDE is either Eclipse, IntelliJ IDEA, or Visual Studio Code, you can generate run configurations using one of the following commands, respectively: #### Eclipse @@ -55,13 +56,14 @@ Minecraft版本 | Java开发工具包版本 ./gradlew genVSCodeRuns ``` -你可以使用生成的运行配置来运行客户端、服务器等。 +You can the run the client, server, etc. using one of the generated run configurations. -:::提示 -如果你的IDE未被列出,你仍然可以使用`./gradlew run*`(例如,`runClient`, `runServer`, `runData`)运行配置。你也可以在支持的IDE中使用这些命令。 +:::tip +If your IDE is not listed, you can still run the configurations using `./gradlew run*` (e.g., `runClient`, `runServer`, `runData`). You can use these commands with the supported IDEs as well. ::: -恭喜你,现在你已经设置了一个开发环境! +Congratulations, now you have a development environment set up! + [jdk8]: https://adoptium.net/temurin/releases/?version=8 [jdk16]: https://adoptium.net/temurin/releases/?version=16 diff --git a/neogradle_versioned_docs/version-5.x/docs/index.md b/neogradle_versioned_docs/version-5.x/docs/index.md index 5b01576ab..2fdc85cba 100644 --- a/neogradle_versioned_docs/version-5.x/docs/index.md +++ b/neogradle_versioned_docs/version-5.x/docs/index.md @@ -1,27 +1,38 @@ -ForgeGradle文档 +ForgeGradle Documentation ========================= -这是[ForgeGradle]的官方文档,ForgeGradle是一个用于使用MinecraftForge开发[MinecraftForge]和模组的[Gradle]插件。 +This is the official documentation for [ForgeGradle], a [Gradle] plugin for developing [MinecraftForge] and mods using MinecraftForge. -这份文档 _仅_ 针对ForgeGradle,**这不是Java、Groovy或Gradle教程**。 +This documentation is _only_ for ForgeGradle, **this is not a Java, Groovy, or Gradle tutorial**. -如果你想为文档做出贡献,请阅读[为文档做贡献][contributing]。 +If you would like to contribute to the docs, read [Contributing to the Docs][contributing]. -添加插件 +Adding the Plugin ----------------- -通过在可用的插件仓库中添加MinecraftForge maven,可以使用`plugins`代码块添加ForgeGradle: +ForgeGradle can be added using the `plugins` block by adding the MinecraftForge maven to the available plugin repositories: ```gradle -// 在settings.gradle文件中 +// In settings.gradle pluginManagement { repositories { // ... - // 添加MinecraftForge maven + // Add the MinecraftForge maven maven { url = 'https://maven.minecraftforge.net/' } } } +``` + +```gradle +// In build.gradle +plugins { + // Add the ForgeGradle plugin + id 'net.minecraftforge.gradle' version '5.1.+' + + // ... +} +``` [ForgeGradle]: https://github.com/MinecraftForge/ForgeGradle [Gradle]: https://gradle.org/ diff --git a/package-lock.json b/package-lock.json index 83284ddfe..a3e0d0a55 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4116,12 +4116,13 @@ } }, "node_modules/axios": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/axios/-/axios-0.27.2.tgz", - "integrity": "sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ==", + "version": "1.6.8", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.8.tgz", + "integrity": "sha512-v/ZHtJDU39mDpyBoFVkETcd/uNdxrWRrg3bKpOKzXFA6Bvqopts6ALSMU3y6ijYxbw2B+wPrIv46egTzJXCLGQ==", "dependencies": { - "follow-redirects": "^1.14.9", - "form-data": "^4.0.0" + "follow-redirects": "^1.15.6", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" } }, "node_modules/babel-loader": { @@ -4233,12 +4234,12 @@ } }, "node_modules/body-parser": { - "version": "1.20.1", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz", - "integrity": "sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==", + "version": "1.20.2", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz", + "integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==", "dependencies": { "bytes": "3.1.2", - "content-type": "~1.0.4", + "content-type": "~1.0.5", "debug": "2.6.9", "depd": "2.0.0", "destroy": "1.2.0", @@ -4246,7 +4247,7 @@ "iconv-lite": "0.4.24", "on-finished": "2.4.1", "qs": "6.11.0", - "raw-body": "2.5.1", + "raw-body": "2.5.2", "type-is": "~1.6.18", "unpipe": "1.0.0" }, @@ -4414,13 +4415,18 @@ } }, "node_modules/call-bind": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.5.tgz", - "integrity": "sha512-C3nQxfFZxFRVoJoGKKI8y3MOEo129NQ+FgQ08iye+Mk4zNZZGdjfs06bVTr+DBSlA66Q2VEcMki/cUCP4SercQ==", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", + "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.1", - "set-function-length": "^1.1.1" + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -4935,9 +4941,9 @@ "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==" }, "node_modules/cookie": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", - "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==", + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", + "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", "engines": { "node": ">= 0.6" } @@ -5460,16 +5466,19 @@ } }, "node_modules/define-data-property": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.1.tgz", - "integrity": "sha512-E7uGkTzkk1d0ByLeSc6ZsFS79Axg+m1P/VsgYsxHgiuc3tFSj+MjMIwe90FC4lOAZzNBdY7kkO2P2wKdsQ1vgQ==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", "dependencies": { - "get-intrinsic": "^1.2.1", - "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.0" + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" }, "engines": { "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/define-lazy-prop": { @@ -5812,6 +5821,25 @@ "is-arrayish": "^0.2.1" } }, + "node_modules/es-define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", + "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", + "dependencies": { + "get-intrinsic": "^1.2.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "engines": { + "node": ">= 0.4" + } + }, "node_modules/es-module-lexer": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.3.1.tgz", @@ -6064,16 +6092,16 @@ } }, "node_modules/express": { - "version": "4.18.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz", - "integrity": "sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==", + "version": "4.19.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz", + "integrity": "sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==", "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", - "body-parser": "1.20.1", + "body-parser": "1.20.2", "content-disposition": "0.5.4", "content-type": "~1.0.4", - "cookie": "0.5.0", + "cookie": "0.6.0", "cookie-signature": "1.0.6", "debug": "2.6.9", "depd": "2.0.0", @@ -6416,9 +6444,9 @@ } }, "node_modules/follow-redirects": { - "version": "1.15.3", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.3.tgz", - "integrity": "sha512-1VzOtuEM8pC9SFU1E+8KfTjZyMztRsgEfwQl44z8A25uy13jSzTj6dyK2Df52iV0vgHCfBwLhDWevLn95w5v6Q==", + "version": "1.15.6", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz", + "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==", "funding": [ { "type": "individual", @@ -6664,15 +6692,19 @@ } }, "node_modules/get-intrinsic": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.2.tgz", - "integrity": "sha512-0gSo4ml/0j98Y3lngkFEot/zhiCeWsbYIlZ+uZOVgzLyLaUw7wxUL+nCTP0XJvJg1AXulJRI3UJi8GsbDuxdGA==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", + "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", "dependencies": { + "es-errors": "^1.3.0", "function-bind": "^1.1.2", "has-proto": "^1.0.1", "has-symbols": "^1.0.3", "hasown": "^2.0.0" }, + "engines": { + "node": ">= 0.4" + }, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -6930,11 +6962,11 @@ } }, "node_modules/has-property-descriptors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.1.tgz", - "integrity": "sha512-VsX8eaIewvas0xnvinAe9bw4WfIeODpGYikiWYLH+dma0Jw6KHYqWiWfhQlgOVK8D6PvjubK5Uc4P0iIhIcNVg==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", "dependencies": { - "get-intrinsic": "^1.2.2" + "es-define-property": "^1.0.0" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -11734,6 +11766,11 @@ "node": ">= 0.10" } }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" + }, "node_modules/punycode": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", @@ -11827,9 +11864,9 @@ } }, "node_modules/raw-body": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", - "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", + "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", "dependencies": { "bytes": "3.1.2", "http-errors": "2.0.0", @@ -12973,14 +13010,16 @@ } }, "node_modules/set-function-length": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.1.1.tgz", - "integrity": "sha512-VoaqjbBJKiWtg4yRcKBQ7g7wnGnLV3M8oLvVWwOk2PdYY6PEFegR1vezXR0tw6fZGF9csVakIRjrJiy2veSBFQ==", + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", "dependencies": { - "define-data-property": "^1.1.1", - "get-intrinsic": "^1.2.1", + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.0" + "has-property-descriptors": "^1.0.2" }, "engines": { "node": ">= 0.4" @@ -13056,13 +13095,17 @@ } }, "node_modules/side-channel": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", - "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", + "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", "dependencies": { - "call-bind": "^1.0.0", - "get-intrinsic": "^1.0.2", - "object-inspect": "^1.9.0" + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4", + "object-inspect": "^1.13.1" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -14328,15 +14371,15 @@ } }, "node_modules/wait-on": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/wait-on/-/wait-on-7.0.1.tgz", - "integrity": "sha512-9AnJE9qTjRQOlTZIldAaf/da2eW0eSRSgcqq85mXQja/DW3MriHxkpODDSUEg+Gri/rKEcXUZHe+cevvYItaog==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/wait-on/-/wait-on-7.2.0.tgz", + "integrity": "sha512-wCQcHkRazgjG5XoAq9jbTMLpNIjoSlZslrJ2+N9MxDsGEv1HnFoVjOCexL0ESva7Y9cu350j+DWADdk54s4AFQ==", "dependencies": { - "axios": "^0.27.2", - "joi": "^17.7.0", + "axios": "^1.6.1", + "joi": "^17.11.0", "lodash": "^4.17.21", - "minimist": "^1.2.7", - "rxjs": "^7.8.0" + "minimist": "^1.2.8", + "rxjs": "^7.8.1" }, "bin": { "wait-on": "bin/wait-on" @@ -14464,9 +14507,9 @@ } }, "node_modules/webpack-dev-middleware": { - "version": "5.3.3", - "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-5.3.3.tgz", - "integrity": "sha512-hj5CYrY0bZLB+eTO+x/j67Pkrquiy7kWepMHmUMoPsmcUaeEnQJqFzHJOyxgWlq746/wUuA64p9ta34Kyb01pA==", + "version": "5.3.4", + "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-5.3.4.tgz", + "integrity": "sha512-BVdTqhhs+0IfoeAf7EoH5WE+exCmqGerHfDM0IL096Px60Tq2Mn9MAbnaGUe6HiMa41KMCYF19gyzZmBcq/o4Q==", "dependencies": { "colorette": "^2.0.10", "memfs": "^3.4.3", @@ -17916,12 +17959,13 @@ } }, "axios": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/axios/-/axios-0.27.2.tgz", - "integrity": "sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ==", + "version": "1.6.8", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.8.tgz", + "integrity": "sha512-v/ZHtJDU39mDpyBoFVkETcd/uNdxrWRrg3bKpOKzXFA6Bvqopts6ALSMU3y6ijYxbw2B+wPrIv46egTzJXCLGQ==", "requires": { - "follow-redirects": "^1.14.9", - "form-data": "^4.0.0" + "follow-redirects": "^1.15.6", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" } }, "babel-loader": { @@ -18006,12 +18050,12 @@ "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==" }, "body-parser": { - "version": "1.20.1", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz", - "integrity": "sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==", + "version": "1.20.2", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz", + "integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==", "requires": { "bytes": "3.1.2", - "content-type": "~1.0.4", + "content-type": "~1.0.5", "debug": "2.6.9", "depd": "2.0.0", "destroy": "1.2.0", @@ -18019,7 +18063,7 @@ "iconv-lite": "0.4.24", "on-finished": "2.4.1", "qs": "6.11.0", - "raw-body": "2.5.1", + "raw-body": "2.5.2", "type-is": "~1.6.18", "unpipe": "1.0.0" }, @@ -18140,13 +18184,15 @@ } }, "call-bind": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.5.tgz", - "integrity": "sha512-C3nQxfFZxFRVoJoGKKI8y3MOEo129NQ+FgQ08iye+Mk4zNZZGdjfs06bVTr+DBSlA66Q2VEcMki/cUCP4SercQ==", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", + "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", "requires": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.1", - "set-function-length": "^1.1.1" + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.1" } }, "callsites": { @@ -18508,9 +18554,9 @@ "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==" }, "cookie": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", - "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==" + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", + "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==" }, "cookie-signature": { "version": "1.0.6", @@ -18842,13 +18888,13 @@ "integrity": "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==" }, "define-data-property": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.1.tgz", - "integrity": "sha512-E7uGkTzkk1d0ByLeSc6ZsFS79Axg+m1P/VsgYsxHgiuc3tFSj+MjMIwe90FC4lOAZzNBdY7kkO2P2wKdsQ1vgQ==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", "requires": { - "get-intrinsic": "^1.2.1", - "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.0" + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" } }, "define-lazy-prop": { @@ -19100,6 +19146,19 @@ "is-arrayish": "^0.2.1" } }, + "es-define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", + "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", + "requires": { + "get-intrinsic": "^1.2.4" + } + }, + "es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==" + }, "es-module-lexer": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.3.1.tgz", @@ -19270,16 +19329,16 @@ } }, "express": { - "version": "4.18.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz", - "integrity": "sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==", + "version": "4.19.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz", + "integrity": "sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==", "requires": { "accepts": "~1.3.8", "array-flatten": "1.1.1", - "body-parser": "1.20.1", + "body-parser": "1.20.2", "content-disposition": "0.5.4", "content-type": "~1.0.4", - "cookie": "0.5.0", + "cookie": "0.6.0", "cookie-signature": "1.0.6", "debug": "2.6.9", "depd": "2.0.0", @@ -19556,9 +19615,9 @@ "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==" }, "follow-redirects": { - "version": "1.15.3", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.3.tgz", - "integrity": "sha512-1VzOtuEM8pC9SFU1E+8KfTjZyMztRsgEfwQl44z8A25uy13jSzTj6dyK2Df52iV0vgHCfBwLhDWevLn95w5v6Q==" + "version": "1.15.6", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz", + "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==" }, "fork-ts-checker-webpack-plugin": { "version": "6.5.3", @@ -19714,10 +19773,11 @@ "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==" }, "get-intrinsic": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.2.tgz", - "integrity": "sha512-0gSo4ml/0j98Y3lngkFEot/zhiCeWsbYIlZ+uZOVgzLyLaUw7wxUL+nCTP0XJvJg1AXulJRI3UJi8GsbDuxdGA==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", + "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", "requires": { + "es-errors": "^1.3.0", "function-bind": "^1.1.2", "has-proto": "^1.0.1", "has-symbols": "^1.0.3", @@ -19913,11 +19973,11 @@ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" }, "has-property-descriptors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.1.tgz", - "integrity": "sha512-VsX8eaIewvas0xnvinAe9bw4WfIeODpGYikiWYLH+dma0Jw6KHYqWiWfhQlgOVK8D6PvjubK5Uc4P0iIhIcNVg==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", "requires": { - "get-intrinsic": "^1.2.2" + "es-define-property": "^1.0.0" } }, "has-proto": { @@ -22944,6 +23004,11 @@ } } }, + "proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" + }, "punycode": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", @@ -23002,9 +23067,9 @@ "integrity": "sha512-kA5WQoNVo4t9lNx2kQNFCxKeBl5IbbSNBl1M/tLkw9WCn+hxNBAW5Qh8gdhs63CJnhjJ2zQWFoqPJP2sK1AV5A==" }, "raw-body": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", - "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", + "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", "requires": { "bytes": "3.1.2", "http-errors": "2.0.0", @@ -23867,14 +23932,16 @@ } }, "set-function-length": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.1.1.tgz", - "integrity": "sha512-VoaqjbBJKiWtg4yRcKBQ7g7wnGnLV3M8oLvVWwOk2PdYY6PEFegR1vezXR0tw6fZGF9csVakIRjrJiy2veSBFQ==", + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", "requires": { - "define-data-property": "^1.1.1", - "get-intrinsic": "^1.2.1", + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.0" + "has-property-descriptors": "^1.0.2" } }, "setimmediate": { @@ -23929,13 +23996,14 @@ } }, "side-channel": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", - "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", + "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", "requires": { - "call-bind": "^1.0.0", - "get-intrinsic": "^1.0.2", - "object-inspect": "^1.9.0" + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4", + "object-inspect": "^1.13.1" } }, "signal-exit": { @@ -24798,15 +24866,15 @@ } }, "wait-on": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/wait-on/-/wait-on-7.0.1.tgz", - "integrity": "sha512-9AnJE9qTjRQOlTZIldAaf/da2eW0eSRSgcqq85mXQja/DW3MriHxkpODDSUEg+Gri/rKEcXUZHe+cevvYItaog==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/wait-on/-/wait-on-7.2.0.tgz", + "integrity": "sha512-wCQcHkRazgjG5XoAq9jbTMLpNIjoSlZslrJ2+N9MxDsGEv1HnFoVjOCexL0ESva7Y9cu350j+DWADdk54s4AFQ==", "requires": { - "axios": "^0.27.2", - "joi": "^17.7.0", + "axios": "^1.6.1", + "joi": "^17.11.0", "lodash": "^4.17.21", - "minimist": "^1.2.7", - "rxjs": "^7.8.0" + "minimist": "^1.2.8", + "rxjs": "^7.8.1" } }, "watchpack": { @@ -24946,9 +25014,9 @@ } }, "webpack-dev-middleware": { - "version": "5.3.3", - "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-5.3.3.tgz", - "integrity": "sha512-hj5CYrY0bZLB+eTO+x/j67Pkrquiy7kWepMHmUMoPsmcUaeEnQJqFzHJOyxgWlq746/wUuA64p9ta34Kyb01pA==", + "version": "5.3.4", + "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-5.3.4.tgz", + "integrity": "sha512-BVdTqhhs+0IfoeAf7EoH5WE+exCmqGerHfDM0IL096Px60Tq2Mn9MAbnaGUe6HiMa41KMCYF19gyzZmBcq/o4Q==", "requires": { "colorette": "^2.0.10", "memfs": "^3.4.3", diff --git a/src/pages/contributing.mdx b/src/pages/contributing.mdx index 0cc345717..6cee77db6 100644 --- a/src/pages/contributing.mdx +++ b/src/pages/contributing.mdx @@ -1,78 +1,78 @@ -# 为文档做贡献 +# Contributing to the Documentation -这是一个对[NeoForged文档][docs]仓库做出贡献的非详尽指南。贡献可以通过Fork和Clone仓库然后通过在[GitHub][docs]上添加一个拉取请求(PR)来实现。 +This is a non-exhaustive guideline for making contributions to the [NeoForged Documentation][docs] repository. Contributions can be made by forking and cloning the repository and then added via a pull request, or PR, on the [GitHub][docs]. -你可以使用[npm]在本地运行网站。建议使用Node版本管理器,如[nvm](Mac,Linux)或[nvs](Windows),来设置并安装npm和Node。从那里,你可以运行以下命令: +You can run the website locally using [npm]. It is recommended to use a Node Version Manager like [nvm] (Mac, Linux) or [nvs] (Windows) to setup and install npm and Node. From there, you can run the following commands: ```bash -nvm use # 或在Windows上使用 nvs use +nvm use # or nvs use on Windows npm install npm run start ``` -## 原则 +## Principles -这份文档旨在帮助模组开发者理解并实现来自Minecraft或NeoForged的概念。 +This documentation is a guide to help a modder understand and implement a given concept from Minecraft or NeoForged. -这份文档**不**是一个教程,不允许模组开发者复制粘贴示例。如果你正在寻找一个教程,有许多视频和页面可以使用和跟随,这里没有链接。 +This documentation is **not** meant as a tutorial, allowing a modder to copy-paste the examples. If you are looking for a tutorial, there are plenty of videos and pages, which are not linked here, that you can use and follow along with. -这份文档也**不**旨在作为一个类的文档。编写指南时提供元素的描述是不可避免的;但是,如果你想要记录一个类,你应该为[Parchment for Minecraft][parchment]或[NeoForge for NeoForged][neo]做贡献。 +This documentation is also **not** meant as documentation for a class. Providing a description of an element is unavoidable when writing a guide; however, if you would like to document a class, you should contribute to [Parchment for Minecraft][parchment] or [NeoForge for NeoForged][neo]. -最后,这份文档**不**旨在解释Java概念。这份文档是为那些已经具有扎实Java基础的人准备的。如果需要解释Java概念以更好地理解概念(如为访问转换器的JVM描述符),应提供指向原始资源的链接。否则,如果你不熟悉Java,有许多在线资源可以学习: +Finally, this documentation is **not** meant to explain Java concepts. This documentation is intended for people who already have a solid basis in Java. If a Java concept needs to be explained to better understand the concept (such as JVM Descriptors for Access Transformers), a link should be provided to the original resource. Otherwise, if you are unfamiliar with Java, there are plenty of online resources to learn from: * [JetBrains Academy][jetbrains] * [Codeacademy][codeacademy] -* [赫尔辛基大学][helsinki] +* [University of Helsinki][helsinki] * [Oracle][oracle] -* [David J. Eck的《使用Java进行编程入门》][eck] +* [Introduction to Programming using Java by David J. Eck][eck] -## 概念 +## Concepts -每页应指导模组开发者了解特定概念。如果概念范围太大,应将概念分割成单独的子概念,每个子概念都有自己的页面。例如,如果你在写一个食谱,可以为每个食谱设置一个页面,而不是一个页面包含所有食谱。 +Each page should guide a modder on a particular concept. If the concept is too large in scope, the concept should be split into separate sub-concepts, each within its own page. For example, if you are writing a cookbook, there can be a page for each recipe, rather than a single page containing all the recipes. -描述概念时,你应该首先介绍概念是什么,它在Minecraft中的用途,为什么应该使用它,以及如何使用它。概念中的每个部分都应该有一个标题。如果需要,一个部分也可以被分解成子部分。例如,每个食谱中的食谱可以有配料和食谱本身的子节。 +When describing a concept, you should first introduce what the concept is, where it is used in Minecraft, why it should be used, and how to use it. Each section within a concept should have a header. A section can also be broken into sub-sections if necessary. For example, each recipe within a cookbook can have a sub-section for ingredients and the recipe itself. -如果需要引用其他概念,应链接相关页面,并附上摘要和/或一些例子来理解应用。 +If you need to refer to other concepts, the relevant page should be linked along with a summary and/or some example to understand the application. -## 示例 +## Examples -代码示例通常应该是伪代码对象,旨在增强模组开发者的理解。对于这份文档来说,伪代码是指以所需语言的结构和语法编写的代码块,使用注释作为模组开发者可能选择自己实现的特定逻辑的占位符。代码块不一定需要编译,但每行都应该有所需语言的有效语法和结构。 +Code examples should generally be pseudocode-like objects meant to enhance the understanding of a modder. For this documentation, pseudocode-like refers to code blocks written in the structure and syntax of the desired language with comments used as placeholders for specific logic that the modder may choose to implement themselves. The code blocks do not necessarily need to be compilable, but each line should have valid syntax and structure of the desired language. -实现一个方法通常特定于模组开发者试图实现的目标。作为一个指南,这份文档旨在在某种程度上对模组开发者的特定目标保持中立,而是覆盖普遍的用例。 +When implementing a method, it is usually specific to the desired goal a modder is trying to achieve. As a guide, this documentation aims to be somewhat agnostic to a modder's specific goal, instead covering the general use case. -例如,假设我们使用一个叫做`#applyDiscount`的方法从当前价格中减去一些值。不是每个人都会在方法内实现相同的逻辑。因此,伪代码可以留下一个评论说明代替做什么: +Let's say we are using a method called `#applyDiscount` to take some value off the current price. Not everyone will implement the same logic within the method. So, the pseudocode can leave a comment mentioning what to do instead: ```java -// 在某个类中 +// In some class public float applyDiscount(float price) { float newPrice = price; - // 对newPrice应用折扣 + // Apply discount to newPrice // ... return newPrice; } ``` :::tip -如果伪代码不足以解释概念,那么可以使用完整的代码示例代替。完整的代码示例应提供虚拟值,并解释它们代表什么。 +If the pseudocode is not explanatory enough to understand the concept, then a full code example can be used instead. A full code example should supply dummy values and explain what they represent. ::: -## 小变更和补丁变更 +## Minor and Patch Changes -如果 NeoForge 在小版本或补丁版本之间发生变更,则文档中的相关变更应分割成单独的部分或放入标签中。这样可以根据模组开发者当前开发的版本保持信息的准确性。 +If a change occurs between a minor or patch versions of NeoForge, then relevant changes in the documentation should be split into separate sections or put into tabs. This maintains the accuracy of the information depending on the version the modder is currently developing for. -标签必须在`.mdx`文件中,而不是`.md`文件中,以获得适当的 IDE 支持。 +Tabs must be in an `.mdx` file, not an `.md` file, for proper IDE support. ````md import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; - - - + + + - + ```java public void latestMethod() { // ... @@ -80,10 +80,10 @@ public void latestMethod() { ``` - + - + ```java public void previousMethod() { // ... @@ -142,135 +142,135 @@ public void firstMethod() { -## 编撰文档的风格指南 +## Style Guide -此文档采用 [Docusaurus][docusaurus] 为基础,此框架内部使用 [MDX][mdx] 生成页面。您可以在他们的页面上找到更多可用功能的详细信息。此风格指南将更专注于我们在 Markdown 文件中使用的常见功能和格式。 +This documentation uses [Docusaurus][docusaurus], which internally uses [MDX][mdx], to generate the pages. You can find more detailed information about available features on their pages. This style guide will be more focused towards common features and formatting we use in the Markdown files. -### 前言 +### Front Matter -前言定义了可以影响页面渲染方式的元数据字段。这使用 `---` 表示,类似于代码块。通常应该定义的唯一前言是 `sidebar_position`,它决定页面在侧边栏中的渲染位置。 +Front matter defines metadata fields which can affect how the page is rendered. This is denoted using `---`, similar to a code block. The only front matter that should usually be defined is `sidebar_position`, which determines where the page should be rendered on the sidebar. -还有其他元数据字段如 `title` 和 `description`,但这些通常从页面本身解析。 +There are other metadata fields like `title` and `description`, but those are typically parsed from the page itself. ```md - + --- sidebar_position: 2 --- ``` -#### 类别 +#### Categories -类别是文档内的文件夹。他们从 `index.md` 继承标题和位置数据。如果子文件夹内没有 `index.md`,应该创建一个 `_category_.json` 文件,指定代表部分名称的 `label` 和代表它应该在侧边栏上去哪里的 `position`。 +Categories are folders within the documentation. They inherit titles and positional data from `index.md`. If an `index.md` is not within a subfolder, a `_category_.json` file should be created, specifying the `label` representing the name of the section, and `position` representing where on the sidebar it should go. ```json5 { - // 要显示的类别名称 - "label": "示例标题", + // Name of the category to display + "label": "Example Title", - // 这将在当前级别的侧边栏中作为第三个元素渲染。 + // This will be rendered as the third element on the sidebar at the current level. "position": 3 } ``` -### 标题 +### Titles -使用多达六个井号(`#`)定义每个部分的标题。标题应该将所有东西都首字母大写,但不重要的单词除外。 +Titles are defined using up to six hashtags (`#`) to define each section. Titles should capitalize everything but unimportant words. ```md - -# 编撰此文档的指南 + +# Guide For Contributing to This Documentation -### 构建和测试你的Mod +### Building and Testing Your Mod ``` -### 措辞 +### Diction -拼写、语法和句法应遵循美式英语。避免使用缩写词;使用两个单词('is not' 而不是 'isn't')。另外,尽可能避免使用代词(例如 I, me, you),除非您需要直接提及读者。指示代词(例如 this, that, its)应谨慎使用,以避免使读者感到困惑。更喜欢使用所指的实际对象或名词。 +Spelling, grammar, and syntax should follow those in American English. Avoid using contractions in sentences; use two separate words ('is not' instead of 'isn't'). Additionally, avoid using pronouns (e.g. I, me, you) when possible, unless you need to directly refer to the reader. Demonstratives (e.g. this, that, its) should be used sparingly to avoid confusing the reader. Prefer using the actual object or noun being referred to. -### 段落 +### Paragraphs -段落应该是一个连续的块,由一个换行符分隔。段落**不**应该每个句子都在新行上。 +Paragraphs should be a continuous block, separated by a newline. Paragraphs should **not** have each sentence be on a new line. ```md -这是我的第一段。看到下一句是如何在同一行上的吗?您可以在编辑器中使用单词换行来阻止行条从屏幕上滚动。 +This is my first paragraph. See how the next sentence is on the same line? You can use word wrapping in your editor to stop the line from going off the screen. -这是我的下一段。它通过一个新行分隔。 +This is my next paragraph. It is separated by a new line. ``` -### 缩进 +### Indentation -缩进行时,使用四个空格而不是制表符。大多数 markdown 功能要求四个空格来识别缩进,因此它允许整个文档保持一致性。 +When indenting lines, use four spaces instead of tabs. Most markdown features require four spaces to recognize indentation, so it allows consistency across the document. ```md -* 你好,世界 - * 四个空格缩进 +* Hello World + * Four Spaces In ``` -### 重要性 +### Importance -强调单词应使用 **粗体** 或 _斜体_。请使用两个星号(`**`)进行粗体和下划线(`_`)进行斜体,以使 Markdown 中的区分更加明显。 +Emphasizing words should be done using **bold** or _italics_. Please use two asterisks (`**`) for bold and an underscore (`_`) for italics to make the separation in Markdown more distinct. ```md -这是一个**加粗**的单词。 +This is a **bolded** word. -这是一个_倾斜_的单词。 +This is an _italicized_ word. ``` -### 代码引用 +### Code References -当引用代码块外的元素时,它们应该用反引号(`` ` ``)包围。类应使用它们的简单名称。方法和字段应在 `#` 之前指定类名。如果类名是隐含的,则方法或字段可以简单地以 `#` 作为前缀。内部类应在 `.` 之后指定外部类的名称。 +When referencing elements outside of code blocks, they should be surrounded with backticks (`` ` ``). Classes should use their simple name. Methods and fields should specify the class name followed by a `#`. If the class name is implied, the method or field can simply be prefixed with `#`. Inner classes should specify the name of the outer class followed by a `.`. ```md - + `MyClass` `MyClass.InnerClass` - + `MyClass#foo` `MyClass.InnerClass#bar` `#SOME_CONSTANT` ``` -代码块应在三连反引号(`` ``` ``)之后指定语言。编写 JSON 块时,应使用 JSON5(`json5`)语法高亮显示器,以允许注释。 +Code blocks should specify the language after the triple backtick (`` ``` ``). When writing a JSON block, the JSON5 (`json5`) syntax highlighter should be used to allow comments. ````md - + ```java public void run() { //... } ``` - + ```json5 { - // 这里允许注释 - "text": "嗨" + // Comments are allowed here + "text": "Hiya" } ``` ```` -### 链接 +### Links -所有链接应使用括号 (`[]`) 引用页面底部指定的链接。如果第一对括号中的名称被使用,则可以省略第二对括号。 +All links should use brackets (`[]`) to refer to a link specified on the bottom of the markdown page. The second pair of brackets can be omitted if the name between the first pair of brackets is used. ```md - -这里有两种不同类型的[链接引用][linkref]。 + +There are [two] different types of [link references][linkref]. - + [two]: https://linkrefwithoutref.donotclick [linkref]: https://linkref.donotclick ``` -### 警示 +### Admonitions -可以使用三个冒号(`:::`)和指定其类型在页面上指定警示。警示的格式可以在 [Docusaurus wiki][admonition] 上找到。 +Admonitions can be specified on the page using three colons (`:::`) and by specifying its type. Admonition formatting can be found on the [Docusaurus wiki][admonition]. ```md :::note -我在一个警示框内部! +I'm within an admonition! ::: ``` @@ -292,5 +292,4 @@ public void run() { [docusaurus]: https://docusaurus.io/docs/markdown-features [mdx]: https://mdxjs.com/guides/ - [admonition]: https://docusaurus.io/docs/markdown-features/admonitions diff --git a/src/pages/index.mdx b/src/pages/index.mdx index 23d6994bf..a2740f0f3 100644 --- a/src/pages/index.mdx +++ b/src/pages/index.mdx @@ -1,37 +1,37 @@ import Card from "../theme/Card.tsx"; -# NeoForged 文档 +# NeoForged Documentation :::caution -请注意,鉴于NeoForged最近的创建,本文档可能未能完全跟上最新的情况。 +Please note that this documentation may not be up to date considering the recent creation of NeoForged. ::: -这是[Minecraft modding API NeoForged]的官方文档。 +This is the official documentation for [NeoForged], the Minecraft modding API. -此文档 _仅限于_ NeoForged,**这不是Java教程**。 +This documentation is _only_ for NeoForged, **this is not a Java tutorial**. -如果您想向文档贡献内容,请阅读[如何为文档贡献][贡献]。 +If you would like to contribute to the docs, read [Contributing to the Docs][contributing].
[NeoForged]: https://neoforged.net -[贡献]: ./contributing +[contributing]: ./contributing