From 5305e7eac37ad9c1e041d6746ab458db13a44d35 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=ED=95=9C=ED=98=84=EB=B9=88?= <41675181+tmvkrpxl0@users.noreply.github.com> Date: Thu, 5 Oct 2023 05:58:37 +0900 Subject: [PATCH 01/15] Fix markdown link in components.md (#17) --- docs/misc/components.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/misc/components.md b/docs/misc/components.md index 556a41579..296ea9db9 100644 --- a/docs/misc/components.md +++ b/docs/misc/components.md @@ -14,7 +14,7 @@ A `Component` is a holder for text which can be formatted and chained with other | `score` | it creates a component for representing the `objective`'s score of an entity specified by the [entity selector][selectors] `name`. | | `selector` | it creates a component for displaying the list of names of entities selected by the [entity selector][selectors] `pattern`. | -A component's text contents are represented by `ComponentContents`. Notably, the subtype `TranslatableContents` not only supports [localization][internalization] but also [text formatting][formatting]. +A component's text contents are represented by `ComponentContents`. Notably, the subtype `TranslatableContents` not only supports [localization][internationalization] but also [text formatting][formatting]. Applying Style -------------- @@ -101,10 +101,10 @@ Text formatting is the process of inserting data as text into predefined larger Any `Component` element within `args` will be transformed into a formatted text string. -[internalization]: ../concepts/internationalization.md +[internationalization]: ../concepts/internationalization.md [selectors]: https://minecraft.wiki/w/Target_selectors [red_hello]: /img/component_red_hello.png [style_annotated]: /img/component_style_annotated.png [formatting]: #text-formatting [tree]: /img/component_graph.png -[keymapping]: ./keymappings.md \ No newline at end of file +[keymapping]: ./keymappings.md From 88087436835ced5622b4e38725e4829fa3290a72 Mon Sep 17 00:00:00 2001 From: src_resources Date: Tue, 10 Oct 2023 11:45:27 +0800 Subject: [PATCH 02/15] Initialize the source structure for Chinese translations Signed-off-by: src_resources --- docusaurus.config.js | 2 +- .../current/advanced/_category_.json | 3 + .../current/advanced/accesstransformers.md | 114 +++++ .../current/blockentities/ber.md | 28 ++ .../current/blockentities/index.md | 146 ++++++ .../current/blocks/index.md | 51 ++ .../current/blocks/states.md | 96 ++++ .../current/concepts/_category_.json | 3 + .../current/concepts/events.md | 144 ++++++ .../current/concepts/internationalization.md | 71 +++ .../current/concepts/lifecycle.md | 76 +++ .../current/concepts/registries.md | 200 ++++++++ .../current/concepts/resources.md | 21 + .../current/concepts/sides.md | 121 +++++ .../current/datagen/client/_category_.json | 3 + .../current/datagen/client/localization.md | 41 ++ .../current/datagen/client/modelproviders.md | 427 +++++++++++++++++ .../current/datagen/client/sounds.md | 84 ++++ .../current/datagen/index.md | 86 ++++ .../current/datagen/server/_category_.json | 3 + .../current/datagen/server/advancements.md | 67 +++ .../datagen/server/datapackregistries.md | 128 +++++ .../current/datagen/server/glm.md | 30 ++ .../current/datagen/server/loottables.md | 144 ++++++ .../current/datagen/server/recipes.md | 195 ++++++++ .../current/datagen/server/tags.md | 121 +++++ .../current/datastorage/_category_.json | 3 + .../current/datastorage/capabilities.md | 177 +++++++ .../current/datastorage/codecs.md | 441 ++++++++++++++++++ .../current/datastorage/saveddata.md | 41 ++ .../current/gameeffects/_category_.json | 3 + .../current/gameeffects/particles.md | 129 +++++ .../current/gameeffects/sounds.md | 111 +++++ .../current/gettingstarted/_category_.json | 4 + .../current/gettingstarted/index.md | 127 +++++ .../current/gettingstarted/modfiles.md | 167 +++++++ .../current/gettingstarted/structuring.md | 83 ++++ .../current/gettingstarted/versioning.md | 57 +++ .../current/gui/_category_.json | 3 + .../current/gui/menus.md | 347 ++++++++++++++ .../current/gui/screens.md | 354 ++++++++++++++ .../current/items/bewlr.md | 36 ++ .../current/items/index.md | 72 +++ .../current/legacy/_category_.json | 3 + .../current/legacy/porting.md | 22 + .../current/misc/_category_.json | 3 + .../current/misc/components.md | 110 +++++ .../current/misc/config.md | 139 ++++++ .../current/misc/debugprofiler.md | 47 ++ .../current/misc/gametest.mdx | 288 ++++++++++++ .../current/misc/keymappings.md | 157 +++++++ .../current/misc/updatechecker.md | 63 +++ .../current/networking/entities.md | 37 ++ .../current/networking/index.md | 20 + .../current/networking/simpleimpl.md | 119 +++++ .../current/rendering/_category_.json | 3 + .../rendering/modelextensions/_category_.json | 3 + .../rendering/modelextensions/facedata.md | 117 +++++ .../rendering/modelextensions/rendertypes.md | 81 ++++ .../rendering/modelextensions/transforms.md | 76 +++ .../rendering/modelextensions/visibility.md | 52 +++ .../rendering/modelloaders/bakedmodel.md | 58 +++ .../current/rendering/modelloaders/index.md | 27 ++ .../rendering/modelloaders/itemoverrides.md | 49 ++ .../rendering/modelloaders/transform.md | 37 ++ .../current/resources/_category_.json | 3 + .../current/resources/client/index.md | 15 + .../current/resources/client/models/index.md | 25 + .../resources/client/models/itemproperties.md | 65 +++ .../resources/client/models/tinting.md | 32 ++ .../current/resources/server/advancements.md | 167 +++++++ .../current/resources/server/conditional.md | 182 ++++++++ .../current/resources/server/glm.md | 148 ++++++ .../current/resources/server/index.md | 14 + .../current/resources/server/loottables.md | 111 +++++ .../resources/server/recipes/custom.md | 132 ++++++ .../resources/server/recipes/incode.md | 65 +++ .../current/resources/server/recipes/index.md | 105 +++++ .../resources/server/recipes/ingredients.md | 177 +++++++ .../current/resources/server/tags.md | 121 +++++ 80 files changed, 7432 insertions(+), 1 deletion(-) create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/advanced/_category_.json create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/advanced/accesstransformers.md create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/blockentities/ber.md create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/blockentities/index.md create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/blocks/index.md create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/blocks/states.md create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/concepts/_category_.json create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/concepts/events.md create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/concepts/internationalization.md create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/concepts/lifecycle.md create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/concepts/registries.md create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/concepts/resources.md create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/concepts/sides.md create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/datagen/client/_category_.json create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/datagen/client/localization.md create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/datagen/client/modelproviders.md create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/datagen/client/sounds.md create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/datagen/index.md create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/datagen/server/_category_.json create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/datagen/server/advancements.md create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/datagen/server/datapackregistries.md create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/datagen/server/glm.md create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/datagen/server/loottables.md create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/datagen/server/recipes.md create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/datagen/server/tags.md create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/datastorage/_category_.json create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/datastorage/capabilities.md create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/datastorage/codecs.md create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/datastorage/saveddata.md create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/gameeffects/_category_.json create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/gameeffects/particles.md create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/gameeffects/sounds.md create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/gettingstarted/_category_.json create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/gettingstarted/index.md create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/gettingstarted/modfiles.md create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/gettingstarted/structuring.md create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/gettingstarted/versioning.md create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/gui/_category_.json create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/gui/menus.md create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/gui/screens.md create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/items/bewlr.md create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/items/index.md create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/legacy/_category_.json create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/legacy/porting.md create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/misc/_category_.json create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/misc/components.md create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/misc/config.md create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/misc/debugprofiler.md create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/misc/gametest.mdx create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/misc/keymappings.md create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/misc/updatechecker.md create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/networking/entities.md create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/networking/index.md create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/networking/simpleimpl.md create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/rendering/_category_.json create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/rendering/modelextensions/_category_.json create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/rendering/modelextensions/facedata.md create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/rendering/modelextensions/rendertypes.md create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/rendering/modelextensions/transforms.md create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/rendering/modelextensions/visibility.md create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/rendering/modelloaders/bakedmodel.md create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/rendering/modelloaders/index.md create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/rendering/modelloaders/itemoverrides.md create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/rendering/modelloaders/transform.md create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/resources/_category_.json create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/resources/client/index.md create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/resources/client/models/index.md create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/resources/client/models/itemproperties.md create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/resources/client/models/tinting.md create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/resources/server/advancements.md create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/resources/server/conditional.md create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/resources/server/glm.md create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/resources/server/index.md create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/resources/server/loottables.md create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/resources/server/recipes/custom.md create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/resources/server/recipes/incode.md create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/resources/server/recipes/index.md create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/resources/server/recipes/ingredients.md create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/resources/server/tags.md diff --git a/docusaurus.config.js b/docusaurus.config.js index f9029dc1d..96293cebe 100644 --- a/docusaurus.config.js +++ b/docusaurus.config.js @@ -29,7 +29,7 @@ const config = { // to replace "en" with "zh-Hans". i18n: { defaultLocale: "en", - locales: ["en"], + locales: ["en", "zh-CN"], }, presets: [ diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/advanced/_category_.json b/i18n/zh-CN/docusaurus-plugin-content-docs/current/advanced/_category_.json new file mode 100644 index 000000000..c392eef49 --- /dev/null +++ b/i18n/zh-CN/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-CN/docusaurus-plugin-content-docs/current/advanced/accesstransformers.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/advanced/accesstransformers.md new file mode 100644 index 000000000..707e3ce9c --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/advanced/accesstransformers.md @@ -0,0 +1,114 @@ +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. + +The [specification document][specs] can be viewed on the NeoForged GitHub. + +Adding ATs +---------- + +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') +} +``` + +After adding or modifying the Access Transformer, the gradle project must be refreshed for the transformations to take effect. + +During development, the AT file can be anywhere specified by the line above. However, when loading in a non-development environment, Forge will only search for the exact path of `META-INF/accesstransformer.cfg` in your JAR file. + +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 + +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 +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. + +Examples of methods that can be safely transformed are `private` methods, `final` methods (or methods in `final` classes), and `static` methods. +::: + +Targets and Directives +---------------------- + +:::caution +When using Access Transformers on Minecraft classes, the SRG name must be used for fields and methods. +::: + +### 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 +-------- + +``` +# Makes public the ByteArrayToKeyFunction interface in Crypt +public net.minecraft.util.Crypt$ByteArrayToKeyFunction + +# Makes protected and removes the final modifier from 'random' in MinecraftServer +protected-f net.minecraft.server.MinecraftServer f_129758_ #random + +# Makes public the 'makeExecutor' method in Util, +# accepting a String and returns an ExecutorService +public net.minecraft.Util m_137477_(Ljava/lang/String;)Ljava/util/concurrent/ExecutorService; #makeExecutor + +# Makes public the 'leastMostToIntArray' method in UUIDUtil, +# accepting two longs and returning an int[] +public net.minecraft.core.UUIDUtil m_235872_(JJ)[I #leastMostToIntArray +``` + +[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-CN/docusaurus-plugin-content-docs/current/blockentities/ber.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/blockentities/ber.md new file mode 100644 index 000000000..0196f965d --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/blockentities/ber.md @@ -0,0 +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`. + +Creating a 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. + +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. + +#### 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. + +Registering a BER +----------------- + +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/i18n/zh-CN/docusaurus-plugin-content-docs/current/blockentities/index.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/blockentities/index.md new file mode 100644 index 000000000..89ef2b872 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/blockentities/index.md @@ -0,0 +1,146 @@ +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. + +:::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 +// For some DeferredRegister> REGISTER +public static final RegistryObject> MY_BE = REGISTER.register("mybe", () -> BlockEntityType.Builder.of(MyBE::new, validBlocks).build(null)); + +// In MyBE, a BlockEntity subclass +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`. + +## 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. + +:::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. +::: + +:::danger +It is important that you call the `super` methods! + +The tag names `id`, `x`, `y`, `z`, `ForgeData` and `ForgeCaps` are reserved by the `super` methods. +::: + +## Ticking `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: + +```java +// Inside some Block subclass +@Nullable +@Override +public BlockEntityTicker getTicker(Level level, BlockState state, BlockEntityType type) { + return type == MyBlockEntityTypes.MYBE.get() ? MyBlockEntity::tick : null; +} + +// Inside 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) +::: + +## 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. + +### Synchronizing on LevelChunk Load + +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. + +:::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]. +::: + +### 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 + return ClientboundBlockEntityDataPacket.create(this); +} + +// Can override IForgeBlockEntity#onDataPacket. By default, this will defer to the #load. +``` +The static constructors `ClientboundBlockEntityDataPacket#create` takes: + +* 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) +``` +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 + +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 +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 +[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-CN/docusaurus-plugin-content-docs/current/blocks/index.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/blocks/index.md new file mode 100644 index 000000000..f083f104d --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/blocks/index.md @@ -0,0 +1,51 @@ +Blocks +====== + +Blocks are, obviously, essential to the Minecraft world. They make up all of 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. + +Creating a Block +---------------- + +### Basic Blocks + +For simple blocks, which need no special functionality (think cobblestone, wooden planks, etc.), a custom class is not necessary. You can create a block by instantiating the `Block` class with a `BlockBehaviour$Properties` object. This `BlockBehaviour$Properties` object can be made using `BlockBehaviour$Properties#of`, and it can be customized by calling its methods. For instance: + +- `strength` - The hardness controls the time it takes to break the block. It is an arbitrary value. For reference, stone has a hardness of 1.5, and dirt 0.5. If the block should be unbreakable a hardness of -1.0 should be used, see the definition of `Blocks#BEDROCK` as an example. The resistance controls the explosion resistance of the block. For reference, stone has a resistance of 6.0, and dirt 0.5. +- `sound` - Controls the sound the block makes when it is punched, broken, or placed. Requires a `SoundType` argument, see the [sounds] page for more details. +- `lightLevel` - Controls the light emission of the block. Takes a function with a `BlockState` parameter that returns a value from zero to fifteen. +- `friction` - Controls how slippery the block is. For reference, ice has a slipperiness of 0.98. + +All these methods are *chainable* which means you can call them in series. See the `Blocks` class for examples of this. + +:::note +Blocks have no setter for their `CreativeModeTab`. This is handled by the [`BuildCreativeModeTabContentsEvent`][creativetabs] if the block has an associated item (e.g. `BlockItem`). Furthermore, there is no setter for translation key of the block as it is generated from the registry name via `Block#getDescriptionId`. +::: + +### Advanced Blocks + +Of course, the above only allows for extremely basic blocks. If you want to add functionality, like player interaction, a custom class is required. However, the `Block` class has many methods and unfortunately not every single one can be documented here. See the rest of the pages in this section for things you can do with blocks. + +Registering a Block +------------------- + +Blocks must be [registered][registering] to function. + +:::caution +A block in the level and a "block" in an inventory are very different things. A block in the level is represented by an `BlockState`, and its behavior defined by an instance of `Block`. Meanwhile, an item in an inventory is an `ItemStack`, controlled by an `Item`. As a bridge between the different worlds of `Block` and `Item`, there exists the class `BlockItem`. `BlockItem` is a subclass of `Item` that has a field `block` that holds a reference to the `Block` it represents. `BlockItem` defines some of the behavior of a "block" as an item, like how a right click places the block. It's possible to have a `Block` without an `BlockItem`. (E.g. `minecraft:water` exists a block, but not an item. It is therefore impossible to hold it in an inventory as one.) + +When a block is registered, *only* a block is registered. The block does not automatically have an `BlockItem`. To create a basic `BlockItem` for a block, one should set the registry name of the `BlockItem` to that of its `Block`. Custom subclasses of `BlockItem` may be used as well. Once an `BlockItem` has been registered for a block, `Block#asItem` can be used to retrieve it. `Block#asItem` will return `Items#AIR` if there is no `BlockItem` for the `Block`, so if you are not certain that there is an `BlockItem` for the `Block` you are using, check for if `Block#asItem` returns `Items#AIR`. +::: + +#### Optionally Registering Blocks + +In the past there have been several mods that have allowed users to disable blocks/items in a configuration file. However, you shouldn't do this. There is no limit on the amount of blocks that can be register, so register all blocks in your mod! If you want a block to be disabled through a configuration file, you should disable the crafting recipe. If you would like to disable the block in the creative tab, use a `FeatureFlag` when building the contents within [`BuildCreativeModeTabContentsEvent`][creativetabs]. + +Further Reading +--------------- + +For information about block properties, such as those used for vanilla blocks like fences, walls, and many more, see the section on [blockstates]. + +[sounds]: ../gameeffects/sounds.md +[creativetabs]: ../items/index.md#creative-tabs +[registering]: ../concepts/registries.md#methods-for-registering +[blockstates]: states.md diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/blocks/states.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/blocks/states.md new file mode 100644 index 000000000..f18bc385e --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/blocks/states.md @@ -0,0 +1,96 @@ +Block States +============ + +Legacy Behavior +--------------------------------------- + +In Minecraft 1.7 and previous versions, blocks which need to store placement or state data that did not have BlockEntities used **metadata**. Metadata was an extra number stored with the block, allowing different rotations, facings, or even completely separate behaviors within a block. + +However, the metadata system was confusing and limited, since it was stored as only a number alongside the block ID, and had no meaning except what was commented in the code. For example, to implement a block that can face a direction and be on either the upper or lower half of a block space (such as a stair): + +```java +switch (meta) { + case 0: { ... } // south and on the lower half of the block + case 1: { ... } // south on the upper side of the block + case 2: { ... } // north and on the lower half of the block + case 3: { ... } // north and on the upper half of the block + // ... etc. ... +} +``` + +Because the numbers carry no meaning by themselves, no one could know what they represent unless they had access to the source code and comments. + +Introduction of States +--------------------------------------- + +In Minecraft 1.8 and above, the metadata system, along with the block ID system, was deprecated and eventually replaced with the **block state system**. The block state system abstracts out the details of the block's properties from the other behaviors of the block. + +Each *property* of a block is described by an instance of `Property`. Examples of block properties include instruments (`EnumProperty`), facing (`DirectionProperty`), poweredness (`Property`), etc. Each property has the value of the type `T` parametrized by `Property`. + +A unique pair can be constructed from the `Block` and a map of the `Property` to their associated values. This unique pair is called a `BlockState`. + +The previous system of meaningless metadata values were replaced by a system of block properties, which are easier to interpret and deal with. Previously, a stone button which is facing east and is powered or held down is represented by "`minecraft:stone_button` with metadata `9`. Now, this is represented by "`minecraft:stone_button[facing=east,powered=true]`". + +Proper Usage of Block States +--------------------------------------- + +The `BlockState` system is a flexible and powerful system, but it also has limitations. `BlockState`s are immutable, and all combinations of their properties are generated on startup of the game. This means that having a `BlockState` with many properties and possible values will slow down the loading of the game, and befuddle anyone trying to make sense of your block logic. + +Not all blocks and situations require the usage of `BlockState`; only the most basic properties of a block should be put into a `BlockState`, and any other situation is better off with having a `BlockEntity` or being a separate `Block`. Always consider if you actually need to use blockstates for your purposes. + +:::note +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. +An "Oak Chair" facing east (`oak_chair[facing=east]`) is different from a "Spruce Chair" facing west (`spruce_chair[facing=west]`). + +Implementing Block States +--------------------------------------- + +In your Block class, create or reference `static final` `Property` objects for every property that your Block has. You are free to make your own `Property` implementations, but the means to do that are not covered in this article. The vanilla code provides several convenience implementations: + +* `IntegerProperty` + * Implements `Property`. Defines a property that holds an integer value. + * Created by calling `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)`. +* `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`. +* `DirectionProperty` + * This is a convenience implementation of `EnumProperty` + * Several convenience predicates are also 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)`. + +The class `BlockStateProperties` contains shared vanilla properties which should be used or referenced whenever possible, in place of creating your own properties. + +When you have your desired `Property<>` objects, override `Block#createBlockStateDefinition(StateDefinition$Builder)` in your Block class. In that method, call `StateDefinition$Builder#add(...);` with the parameters as every `Property` you wish the block to have. + +Every block will also have a "default" state that is automatically chosen for you. You can change this "default" state by calling the `Block#registerDefaultState(BlockState)` method from your constructor. When your block is placed it will become this "default" state. An example from `DoorBlock`: + +```java +this.registerDefaultState( + this.stateDefinition.any() + .setValue(FACING, Direction.NORTH) + .setValue(OPEN, false) + .setValue(HINGE, DoorHingeSide.LEFT) + .setValue(POWERED, false) + .setValue(HALF, DoubleBlockHalf.LOWER) +); +``` + +If you wish to change what `BlockState` is used when placing your block, you can overwrite `Block#getStateForPlacement(BlockPlaceContext)`. This can be used to, for example, set the direction of your block depending on where the player is standing when they place it. + +Because `BlockState`s are immutable, and all combinations of their properties are generated on startup of the game, calling `BlockState#setValue(Property, T)` will simply go to the `Block`'s `StateHolder` and request the `BlockState` with the set of values you want. + +Because all possible `BlockState`s are generated at startup, you are free and encouraged to use the reference equality operator (`==`) to check if two `BlockState`s are equal. + +Using `BlockState`'s +--------------------- + +You can get the value of a property by calling `BlockState#getValue(Property)`, passing it the property you want to get the value of. +If you want to get a `BlockState` with a different set of values, simply call `BlockState#setValue(Property, T)` with the property and its value. + +You can get and place `BlockState`'s in the level using `Level#setBlockAndUpdate(BlockPos, BlockState)` and `Level#getBlockState(BlockPos)`. If you are placing a `Block`, call `Block#defaultBlockState()` to get the "default" state, and use subsequent calls to `BlockState#setValue(Property, T)` as stated above to achieve the desired state. diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/concepts/_category_.json b/i18n/zh-CN/docusaurus-plugin-content-docs/current/concepts/_category_.json new file mode 100644 index 000000000..5e1f44f64 --- /dev/null +++ b/i18n/zh-CN/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-CN/docusaurus-plugin-content-docs/current/concepts/events.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/concepts/events.md new file mode 100644 index 000000000..d3982b36e --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/concepts/events.md @@ -0,0 +1,144 @@ +Events +====== + +Forge uses an event bus that allows mods to intercept events from various Vanilla and mod behaviors. + +Example: An event can be used to perform an action when a Vanilla stick is right clicked. + +The main event bus used for most events is located at `MinecraftForge#EVENT_BUS`. There is another event bus for mod specific events located at `FMLJavaModLoadingContext#getModEventBus` that you should only use in specific cases. More information about this bus can be found below. + +Every event is fired on one of these busses: most events are fired on the main forge event bus, but some are fired on the mod specific event buses. + +An event handler is some method that has been registered to an event bus. + +Creating an Event Handler +------------------------- + +Event handlers methods have a single parameter and do not return a result. The method could be static or instance depending on implementation. + +Event handlers can be directly registered using `IEventBus#addListener` for or `IEventBus#addGenericListener` for generic events (as denoted by subclassing `GenericEvent`). Either listener adder takes in a consumer representing the method reference. Generic event handlers need to specify the class of the generic as well. Event handlers must be registered within the constructor of the main mod class. + +```java +// In the main mod class ExampleMod + +// This event is on the mod bus +private void modEventHandler(RegisterEvent event) { + // Do things here +} + +// This event is on the forge bus +private static void forgeEventHandler(AttachCapabilitiesEvent event) { + // ... +} + +// In the mod constructor +modEventBus.addListener(this::modEventHandler); +forgeEventBus.addGenericListener(Entity.class, ExampleMod::forgeEventHandler); +``` + +### Instance Annotated Event Handlers + +This event handler listens for the `EntityItemPickupEvent`, which is, as the name states, posted to the event bus whenever an `Entity` picks up an item. + +```java +public class MyForgeEventHandler { + @SubscribeEvent + public void pickupItem(EntityItemPickupEvent event) { + System.out.println("Item picked up!"); + } +} +``` + +To register this event handler, use `MinecraftForge.EVENT_BUS.register(...)` and pass it an instance of the class the event handler is within. If you want to register this handler to the mod specific event bus, you should use `FMLJavaModLoadingContext.get().getModEventBus().register(...)` instead. + +### Static Annotated Event Handlers + +An event handler may also be static. The handling method is still annotated with `@SubscribeEvent`. The only difference from an instance handler is that it is also marked `static`. In order to register a static event handler, an instance of the class won't do. The `Class` itself has to be passed in. An example: + +```java +public class MyStaticForgeEventHandler { + @SubscribeEvent + public static void arrowNocked(ArrowNockEvent event) { + System.out.println("Arrow nocked!"); + } +} +``` + +which must be registered like this: `MinecraftForge.EVENT_BUS.register(MyStaticForgeEventHandler.class)`. + +### Automatically Registering Static Event Handlers + +A class may be annotated with the `@Mod$EventBusSubscriber` annotation. Such a class is automatically registered to `MinecraftForge#EVENT_BUS` when the `@Mod` class itself is constructed. This is essentially equivalent to adding `MinecraftForge.EVENT_BUS.register(AnnotatedClass.class);` at the end of the `@Mod` class's constructor. + +You can pass the bus you want to listen to the `@Mod$EventBusSubscriber` annotation. It is recommended you also specify the mod id, since the annotation process may not be able to figure it out, and the bus you are registering to, since it serves as a reminder to make sure you are on the correct one. You can also specify the `Dist`s or physical sides to load this event subscriber on. This can be used to not load client specific event subscribers on the dedicated server. + +An example for a static event listener listening to `RenderLevelStageEvent` which will only be called on the client: + +```java +@Mod.EventBusSubscriber(modid = "mymod", bus = Bus.FORGE, value = Dist.CLIENT) +public class MyStaticClientOnlyEventHandler { + @SubscribeEvent + public static void drawLast(RenderLevelStageEvent event) { + System.out.println("Drawing!"); + } +} +``` + +:::note +This does not register an instance of the class; it registers the class itself (i.e. the event handling methods must be static). +::: + +Canceling +--------- + +If an event can be canceled, it will be marked with the `@Cancelable` annotation, and the method `Event#isCancelable()` will return `true`. The cancel state of a cancelable event may be modified by calling `Event#setCanceled(boolean canceled)`, wherein passing the boolean value `true` is interpreted as canceling the event, and passing the boolean value `false` is interpreted as "un-canceling" the event. However, if the event cannot be canceled (as defined by `Event#isCancelable()`), an `UnsupportedOperationException` will be thrown regardless of the passed boolean value, since the cancel state of a non-cancelable event event is considered immutable. + +:::danger +Not all events can be canceled! Attempting to cancel an event that is not cancelable will result in an unchecked `UnsupportedOperationException` being thrown, which is expected to result in the game crashing! Always check that an event can be canceled using `Event#isCancelable()` before attempting to cancel it! +::: + +Results +------- + +Some events have an `Event$Result`. A result can be one of three things: `DENY` which stops the event, `DEFAULT` which uses the Vanilla behavior, and `ALLOW` which forces the action to take place, regardless if it would have originally. The result of an event can be set by calling `#setResult` with an `Event$Result` on the event. Not all events have results; an event with a result will be annotated with `@HasResult`. + +:::caution +Different events may use results in different ways, refer to the event's JavaDoc before using the result. +::: + +Priority +-------- + +Event handler methods (marked with `@SubscribeEvent`) have a priority. You can set the priority of an event handler method by setting the `priority` value of the annotation. The priority can be any value of the `EventPriority` enum (`HIGHEST`, `HIGH`, `NORMAL`, `LOW`, and `LOWEST`). Event handlers with priority `HIGHEST` are executed first and from there in descending order until `LOWEST` events which are executed last. + +Sub Events +---------- + +Many events have different variations of themselves. These can be different but all based around one common factor (e.g. `PlayerEvent`) or can be an event that has multiple phases (e.g. `PotionBrewEvent`). Take note that if you listen to the parent event class, you will receive calls to your method for *all* subclasses. + +Mod Event Bus +------------- + +The mod event bus is primarily used for listening to lifecycle events in which mods should initialize. Each event on the mod bus is required to implement `IModBusEvent`. Many of these events are also ran in parallel so mods can be initialized at the same time. This does mean you can't directly execute code from other mods in these events. Use the `InterModComms` system for that. + +These are the four most commonly used lifecycle events that are called during mod initialization on the mod event bus: + +* `FMLCommonSetupEvent` +* `FMLClientSetupEvent` & `FMLDedicatedServerSetupEvent` +* `InterModEnqueueEvent` +* `InterModProcessEvent` + +:::note +The `FMLClientSetupEvent` and `FMLDedicatedServerSetupEvent` are only called on their respective distribution. +::: + +These four lifecycle events are all ran in parallel since they all are a subclass of `ParallelDispatchEvent`. If you want to run run code on the main thread during any `ParallelDispatchEvent`, you can use the `#enqueueWork` to do so. + +Next to the lifecycle events, there are a few miscellaneous events that are fired on the mod event bus 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` +* `RegisterEvent` + +A good rule of thumb: events are fired on the mod event bus when they should be handled during initialization of a mod. diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/concepts/internationalization.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/concepts/internationalization.md new file mode 100644 index 000000000..69d127283 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/concepts/internationalization.md @@ -0,0 +1,71 @@ +Internationalization and Localization +===================================== + +Internationalization, i18n for short, is a way of designing code so that it requires no changes to be adapted for various languages. Localization is the process of adapting displayed text to the user's language. + +I18n is implemented using _translation keys_. A translation key is a string that identifies a piece of displayable text in no specific language. For example, `block.minecraft.dirt` is the translation key referring to the name of the Dirt block. This way, displayable text may be referenced with no concern for a specific language. The code requires no changes to be adapted in a new language. + +Localization will happen in the game's locale. In a Minecraft client the locale is specified by the language settings. On a dedicated server, the only supported locale is `en_us`. A list of available locales can be found on the [Minecraft Wiki][langs]. + +Language files +-------------- + +Language files are located by `assets/[namespace]/lang/[locale].json` (e.g. all US English translations provided by `examplemod` would be within `assets/examplemod/lang/en_us.json`). The file format is simply a json map from translation keys to values. The file must be encoded in UTF-8. Old .lang files can be converted to json using a [converter][converter]. + +```js +{ + "item.examplemod.example_item": "Example Item Name", + "block.examplemod.example_block": "Example Block Name", + "commands.examplemod.examplecommand.error": "Example Command Errored!" +} +``` + +Usage with Blocks and Items +--------------------------- + +Block, Item and a few other Minecraft classes have built-in translation keys used to display their names. These translation keys are specified by overriding `#getDescriptionId`. Item also has `#getDescriptionId(ItemStack)` which can be overridden to provide different translation keys depending on ItemStack NBT. + +By default, `#getDescriptionId` will return `block.` or `item.` prepended to the registry name of the block or item, with the colon replaced by a dot. `BlockItem`s override this method to take their corresponding `Block`'s translation key by default. For example, an item with ID `examplemod:example_item` effectively requires the following line in a language file: + +```js +{ + "item.examplemod.example_item": "Example Item Name" +} +``` + +:::note +The only purpose of a translation key is internationalization. Do not use them for logic. Use registry names instead. +::: + +Localization methods +-------------------- + +:::caution +A common issue is having the server localize for clients. The server can only localize in its own locale, which does not necessarily match the locale of connected clients. + +To respect the language settings of clients, the server should have clients localize text in their own locale using `TranslatableContents` or other methods preserving the language neutral translation keys. +::: + +### `net.minecraft.client.resources.language.I18n` (client only) + +**This I18n class can only be found on a Minecraft client!** It is intended to be used by code that only runs on the client. Attempts to use this on a server will throw exceptions and crash. + +- `get(String, Object...)` localizes in the client's locale with formatting. The first parameter is a translation key, and the rest are formatting arguments for `String.format(String, Object...)`. + +### `TranslatableContents` + +`TranslatableContents` is a `ComponentContents` that is localized and formatted lazily. It is very useful when sending messages to players because it will be automatically localized in their own locale. + +The first parameter of the `TranslatableContents(String, Object...)` constructor is a translation key, and the rest are used for [formatting]. + +A `MutableComponent` can be created using `Component#translatable` by passing in the `TranslatableContents`'s parameters. It can also be created using `MutableComponent#create` by passing in the `ComponentContents` itself. +Read [components] for more details. + +### `TextComponentHelper` + +- `createComponentTranslation(CommandSource, String, Object...)` is useful for sending messages between clients and the server. If the receiver is a vanilla client, the method will eagerly localize and format the provided translation key in sender's locale, or American English if no locale is loaded; the modded server may allow vanilla clients to join, and they will lack localization data required to localize the message itself. Otherwise, the method will create the component with `TranslatableContents`. + +[langs]: https://minecraft.wiki/w/Language#Languages +[converter]: https://tterrag.com/lang2json/ +[formatting]: ../misc/components.md#text-formatting +[components]: ../misc/components.md diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/concepts/lifecycle.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/concepts/lifecycle.md new file mode 100644 index 000000000..99e66f2fd --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/concepts/lifecycle.md @@ -0,0 +1,76 @@ +Mod Lifecycle +============== + +During the mod loading process, the various lifecycle events are fired on the mod-specific event bus. Many actions are performed during these events, such as [registering objects][registering], preparing for [data generation][datagen], or [communicating with other mods][imc]. + +Event listeners should be registered either using `@EventBusSubscriber(bus = Bus.MOD)` or in the mod constructor: + +```java +@Mod.EventBusSubscriber(modid = "mymod", bus = Mod.EventBusSubscriber.Bus.MOD) +public class MyModEventSubscriber { + @SubscribeEvent + static void onCommonSetup(FMLCommonSetupEvent event) { ... } +} + +@Mod("mymod") +public class MyMod { + public MyMod() { + FMLModLoadingContext.get().getModEventBus().addListener(this::onCommonSetup); + } + + private void onCommonSetup(FMLCommonSetupEvent event) { ... } +} +``` + +:::caution +Most of the lifecycle events are fired in parallel: all mods will concurrently receive the same event. + +Mods *must* take care to be thread-safe, like when calling other mods' APIs or accessing vanilla systems. Defer code for later execution via `ParallelDispatchEvent#enqueueWork`. +::: + +Registry Events +--------------- + +The registry events are fired after the mod instance construction. There are three: `NewRegistryEvent`, `DataPackRegistryEvent$NewRegistry` and `RegisterEvent`. These events are fired synchronously during mod loading. + +`NewRegistryEvent` allows modders to register their own custom registries, using the `RegistryBuilder` class. + +`DataPackRegistryEvent$NewRegistry` allows modders to register custom datapack registries by providing a `Codec` to encode and decode the object from JSON. + +`RegisterEvent` is for [registering objects][registering] into the registries. The event is fired for each registry. + +Data Generation +--------------- + +If the game is setup to run [data generators][datagen], then the `GatherDataEvent` will be the last event to fire. This event is for registering mods' data providers to their associated data generator. This event is also fired synchronously. + +Common Setup +------------ + +`FMLCommonSetupEvent` is for actions that are common to both physical client and server, such as registering [capabilities][capabilities]. + +Sided Setup +----------- + +The sided-setup events are fired on their respective [physical sides][sides]: `FMLClientSetupEvent` on the physical client, and `FMLDedicatedServerSetupEvent` for the dedicated server. This is where physical side-specific initialization should occur, such as registering client-side key bindings. + +InterModComms +------------- + +This is where messages can be sent to mods for cross-mod compatibility. There are two events: `InterModEnqueueEvent` and `InterModProcessEvent`. + +`InterModComms` is the class responsible for holding messages for mods. The methods are safe to call during the lifecycle events, as it is backed by a `ConcurrentMap`. + +During the `InterModEnqueueEvent`, use `InterModComms#sendTo` to send messages to different mods. These methods take in the mod id that will be sent the message, the key associated with the message data, and a supplier holding the message data. Additionally, the sender of the message can also be specified, but by default it will be the mod id of the caller. + +Then during the `InterModProcessEvent`, use `InterModComms#getMessages` to get a stream of all received messages. The mod id supplied will almost always be the mod id of the mod the method is called on. Additionally, a predicate can be specified to filter out the message keys. This will return a stream of `IMCMessage`s which hold the sender of the data, the receiver of the data, the data key, and the supplied data itself. + +:::tip +There are two other lifecycle events: `FMLConstructModEvent`, fired directly after mod instance construction but before the `RegisterEvent`, and `FMLLoadCompleteEvent`, fired after the `InterModComms` events, for when the mod loading process is complete. +::: + +[registering]: ./registries.md#methods-for-registering +[capabilities]: ../datastorage/capabilities.md +[datagen]: ../datagen/index.md +[imc]: ./lifecycle.md#intermodcomms +[sides]: ./sides.md diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/concepts/registries.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/concepts/registries.md new file mode 100644 index 000000000..22325c439 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/concepts/registries.md @@ -0,0 +1,200 @@ +Registries +========== + +Registration is the process of taking the objects of a mod (such as items, blocks, sounds, 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. + +Most things that require registration in the game are handled by the Forge registries. A registry is an object similar to a map that assigns values to keys. Forge uses registries with [`ResourceLocation`][ResourceLocation] keys to register objects. This allows the `ResourceLocation` to act as the "registry name" for objects. + +Every type of registrable object has its own registry. To see all registries wrapped by Forge, see the `ForgeRegistries` class. All registry names within a registry must be unique. However, names in different registries will not collide. For example, there's a `Block` registry, and an `Item` registry. A `Block` and an `Item` may be registered with the same name `example:thing` without colliding; however, if two different `Block`s or `Item`s were registered with the same exact name, the second object will override the first. + +Methods for Registering +------------------ + +There are two proper ways to register objects: the `DeferredRegister` class, and the `RegisterEvent` lifecycle event. + +### DeferredRegister + +`DeferredRegister` is the recommended way to register objects. It allows the use and convenience of static initializers while avoiding the issues associated with it. It simply maintains a list of suppliers for entries and registers the objects from those suppliers during `RegisterEvent`. + +An example of a mod registering a custom block: + +```java +private static final DeferredRegister BLOCKS = DeferredRegister.create(ForgeRegistries.BLOCKS, MODID); + +public static final RegistryObject ROCK_BLOCK = BLOCKS.register("rock", () -> new Block(BlockBehaviour.Properties.of().mapColor(MapColor.STONE))); + +public ExampleMod() { + BLOCKS.register(FMLJavaModLoadingContext.get().getModEventBus()); +} +``` + +### `RegisterEvent` + +`RegisterEvent` is the second way to register objects. This [event] is fired for each registry after the mod constructors and before the loading of configs. Objects are registered using `#register` by passing in the registry key, the name of the registry object, and the object itself. There is an additional `#register` overload which takes in a consumed helper to register an object with a given name. It is recommended to use this method to avoid unnecessary object creation. + +Here is an example: (the event handler is registered on the *mod event bus*) + +```java +@SubscribeEvent +public void register(RegisterEvent event) { + event.register(ForgeRegistries.Keys.BLOCKS, + helper -> { + helper.register(new ResourceLocation(MODID, "example_block_1"), new Block(...)); + helper.register(new ResourceLocation(MODID, "example_block_2"), new Block(...)); + helper.register(new ResourceLocation(MODID, "example_block_3"), new Block(...)); + // ... + } + ); +} +``` + +### Registries that aren't Forge Registries + +Not all registries are wrapped by Forge. These can be static registries, like `LootItemConditionType`, which are safe to use. There are also dynamic registries, like `ConfiguredFeature` and some other worldgen registries, which are typically represented in JSON. `DeferredRegister#create` has an overload which allows modders to specify the registry key of which vanilla registry to create a `RegistryObject` for. The registry method and attaching to the mod event bus is the same as other `DeferredRegister`s. + +:::danger +Dynamic registry objects can **only** be registered through data files (e.g. JSON). They **cannot** be registered in-code. +::: + +```java +private static final DeferredRegister REGISTER = DeferredRegister.create(Registries.LOOT_CONDITION_TYPE, "examplemod"); + +public static final RegistryObject EXAMPLE_LOOT_ITEM_CONDITION_TYPE = REGISTER.register("example_loot_item_condition_type", () -> new LootItemConditionType(...)); +``` + +:::note +Some classes cannot by themselves be registered. Instead, `*Type` classes are registered, and used in the formers' constructors. For example, [`BlockEntity`][blockentity] has `BlockEntityType`, and `Entity` has `EntityType`. These `*Type` classes are factories that simply create the containing type on demand. + +These factories are created through the use of their `*Type$Builder` classes. An example: (`REGISTER` refers to a `DeferredRegister`) +```java +public static final RegistryObject> EXAMPLE_BLOCK_ENTITY = REGISTER.register( + "example_block_entity", () -> BlockEntityType.Builder.of(ExampleBlockEntity::new, EXAMPLE_BLOCK.get()).build(null) +); +``` +::: + +Referencing Registered Objects +------------------------------ + +Registered objects should not be stored in fields when they are created and registered. They are to be always newly created and registered whenever `RegisterEvent` is fired for that registry. This is to allow dynamic loading and unloading of mods in a future version of Forge. + +Registered objects must always be referenced through a `RegistryObject` or a field with `@ObjectHolder`. + +### Using RegistryObjects + +`RegistryObject`s can be used to retrieve references to registered objects once they are available. These are used by `DeferredRegister` to return a reference to the registered objects. Their references are updated after `RegisterEvent` is called for their registry, along with the `@ObjectHolder` annotations. + +To get a `RegistryObject`, call `RegistryObject#create` with a `ResourceLocation` and the `IForgeRegistry` of the registrable object. Custom registries can also be used by supplying the registry name instead. Store the `RegistryObject` in a `public static final` field, and call `#get` whenever you need the registered object. + +An example of using `RegistryObject`: + +```java +public static final RegistryObject BOW = RegistryObject.create(new ResourceLocation("minecraft:bow"), ForgeRegistries.ITEMS); + +// assume that 'neomagicae:mana_type' is a valid registry, and 'neomagicae:coffeinum' is a valid object within that registry +public static final RegistryObject COFFEINUM = RegistryObject.create(new ResourceLocation("neomagicae", "coffeinum"), new ResourceLocation("neomagicae", "mana_type"), "neomagicae"); +``` + +### Using @ObjectHolder + +Registered objects from registries can be injected into the `public static` fields by annotating classes or fields with `@ObjectHolder` and supplying enough information to construct a `ResourceLocation` to identify a specific object in a specific registry. + +The rules for `@ObjectHolder` are as follows: + +* If the class is annotated with `@ObjectHolder`, its value will be the default namespace for all fields within if not explicitly defined +* If the class is annotated with `@Mod`, the modid will be the default namespace for all annotated fields within if not explicitly defined +* A field is considered for injection if: + * it has at least the modifiers `public static`; + * the **field** is annotated with `@ObjectHolder`, and: + * the name value is explicitly defined; and + * the registry name value is explicitly defined + * _A compile-time exception is thrown if a field does not have a corresponding registry or name._ +* _An exception is thrown if the resulting `ResourceLocation` is incomplete or invalid (non-valid characters in path)_ +* If no other errors or exceptions occur, the field will be injected +* If all of the above rules do not apply, no action will be taken (and a message may be logged) + +`@ObjectHolder`-annotated fields are injected with their values after `RegisterEvent` is fired for their registry, along with the `RegistryObject`s. + +:::note +If the object does not exist in the registry when it is to be injected, a debug message will be logged and no value will be injected. +::: + +As these rules are rather complicated, here are some examples: + +```java +class Holder { + @ObjectHolder(registryName = "minecraft:enchantment", value = "minecraft:flame") + public static final Enchantment flame = null; // Annotation present. [public static] is required. [final] is optional. + // Registry name is explicitly defined: "minecraft:enchantment" + // Resource location is explicitly defined: "minecraft:flame" + // To inject: "minecraft:flame" from the [Enchantment] registry + + public static final Biome ice_flat = null; // No annotation on the field. + // Therefore, the field is ignored. + + @ObjectHolder("minecraft:creeper") + public static Entity creeper = null; // Annotation present. [public static] is required. + // The registry has not been specified on the field. + // Therefore, THIS WILL PRODUCE A COMPILE-TIME EXCEPTION. + + @ObjectHolder(registryName = "potion") + public static final Potion levitation = null; // Annotation present. [public static] is required. [final] is optional. + // Registry name is explicitly defined: "minecraft:potion" + // Resource location is not specified on the field + // Therefore, THIS WILL PRODUCE A COMPILE-TIME EXCEPTION. +} +``` + +Creating Custom Forge Registries +-------------------------------- + +Custom registries can usually just be a simple map of key to value. This is a common style; however, it forces a hard dependency on the registry being present. It also requires that any data that needs to be synced between sides must be done manually. Custom Forge Registries provide a simple alternative for creating soft dependents along with better management and automatic syncing between sides (unless told otherwise). Since the objects also use a Forge registry, registration becomes standardized in the same way. + +Custom Forge Registries are created with the help of a `RegistryBuilder`, through either `NewRegistryEvent` or the `DeferredRegister`. The `RegistryBuilder` class takes various parameters (such as the registry's name, id range, and various callbacks for different events happening on the registry). New registries are registered to the `RegistryManager` after `NewRegistryEvent` finishes firing. + +Any newly created registry should use its associated [registration method][registration] to register the associated objects. + +### Using NewRegistryEvent + +When using `NewRegistryEvent`, calling `#create` with a `RegistryBuilder` will return a supplier-wrapped registry. The supplied registry can be accessed after `NewRegistryEvent` has finished posting to the mod event bus. Getting the custom registry from the supplier before `NewRegistryEvent` finishes firing will result in a `null` value. + +#### New Datapack Registries + +New datapack registries can be added using the `DataPackRegistryEvent$NewRegistry` event on the mod event bus. The registry is created via `#dataPackRegistry` by passing in the `ResourceKey` representing the registry name and the `Codec` used to encode and decode the data from JSON. An optional `Codec` can be provided to sync the datapack registry to the client. + +:::note +Datapack Registries cannot be created with `DeferredRegister`. They can only be created through the event. +::: + +### With DeferredRegister + +The `DeferredRegister` method is once again another wrapper around the above event. Once a `DeferredRegister` is created in a constant field using the `#create` overload which takes in the registry name and the mod id, the registry can be constructed via `DeferredRegister#makeRegistry`. This takes in a supplied `RegistryBuilder` containing any additional configurations. The method already populates `#setName` by default. Since this method can be returned at any time, a supplied version of an `IForgeRegistry` is returned instead. Getting the custom registry from the supplier before `NewRegistryEvent` is fired will result in a `null` value. + +:::caution +`DeferredRegister#makeRegistry` must be called before the `DeferredRegister` is added to the mod event bus via `#register`. `#makeRegistry` also uses the `#register` method to create the registry during `NewRegistryEvent`. +::: + +Handling Missing Entries +------------------------ + +There are cases where certain registry objects will cease to exist whenever a mod is updated or, more likely, removed. It is possible to specify actions to handle the missing mapping through the third of the registry events: `MissingMappingsEvent`. Within this event, a list of missing mappings can be obtained either by `#getMappings` given a registry key and mod id or all mappings via `#getAllMappings` given a registry key. + +:::caution +`MissingMappingsEvent` is fired on the **Forge** event bus. +::: + +For each `Mapping`, one of four mapping types can be selected to handle the missing entry: + +| Action | Description | +| :---: | :--- | +| IGNORE | Ignores the missing entry and abandons the mapping. | +| WARN | Generates a warning in the log. | +| FAIL | Prevents the world from loading. | +| REMAP | Remaps the entry to an already registered, non-null object. | + +If no action is specified, then the default action will occur by notifying the user about the missing entry and whether they still would like to load the world. All actions besides remapping will prevent any other registry object from taking the place of the existing id in case the associated entry ever gets added back into the game. + +[ResourceLocation]: ./resources.md#resourcelocation +[registration]: #methods-for-registering +[event]: ./events.md +[blockentity]: ../blockentities/index.md diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/concepts/resources.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/concepts/resources.md new file mode 100644 index 000000000..d0d0b2074 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/concepts/resources.md @@ -0,0 +1,21 @@ +Resources +========= + +A resource is extra data used by the game, and is stored in a data file, instead of being in the code. +Minecraft has two primary resource systems active: one on the logical client used for visuals such as models, textures, and localization called `assets`, and one on the logical server used for gameplay such as recipes and loot tables called `data`. +[Resource packs][respack] control the former, while [Datapacks][datapack] control the latter. + +In the default mod development kit, assets and data directories are located under the `src/main/resources` directory of the project. + +When multiple resource packs or data packs are enabled, they are merged. Generally, files from packs at the top of the stack override those below; however, for certain files, such as localization files and tags, data is actually merged contentwise. Mods define resource and data packs in their `resources` directories, but they are seen as subsets of the "Mod Resources" pack. Mod resource packs cannot be disabled, but they can be overridden by other resource packs. Mod datapacks can be disabled with the vanilla `/datapack` command. + +All resources should have snake case paths and filenames (lowercase, using "_" for word boundaries), which is enforced in 1.11 and above. + +`ResourceLocation` +------------------ + +Minecraft identifies resources using `ResourceLocation`s. A `ResourceLocation` contains two parts: a namespace and a path. It generally points to the resource at `assets///`, where `ctx` is a context-specific path fragment that depends on how the `ResourceLocation` is being used. When a `ResourceLocation` is written/read as from a string, it is seen as `:`. If the namespace and the colon are left out, then when the string is read into an `ResourceLocation` the namespace will always default to `"minecraft"`. A mod should put its resources into a namespace with the same name as its mod id (e.g. a mod with the id `examplemod` should place its resources in `assets/examplemod` and `data/examplemod` respectively, and `ResourceLocation`s pointing to those files would look like `examplemod:`.). This is not a requirement, and in some cases it can be desirable to use a different (or even more than one) namespace. `ResourceLocation`s are used outside the resource system, too, as they happen to be a great way to uniquely identify objects (e.g. [registries][]). + +[respack]: ../resources/client/index.md +[datapack]: ../resources/server/index.md +[registries]: ./registries.md diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/concepts/sides.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/concepts/sides.md new file mode 100644 index 000000000..f3e66ae15 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/concepts/sides.md @@ -0,0 +1,121 @@ +Sides in Minecraft +=================== + +A very important concept to understand when modding Minecraft are the two sides: *client* and *server*. There are many, many common misconceptions and mistakes regarding siding, which can lead to bugs that might not crash the game, but can rather have unintended and often unpredictable effects. + +Different Kinds of Sides +------------------------ + +When we say "client" or "server", it usually follows with a fairly intuitive understanding of what part of the game we are talking about. After all, a client is what the user interacts with, and a server is where the user connects for a multiplayer game. Easy, right? + +As it turns out, there can be some ambiguity even with two such terms. Here we disambiguate the four possible meanings of "client" and "server": + +* Physical client - The *physical client* is the entire program that runs whenever you launch Minecraft from the launcher. All threads, processes, and services that run during the game's graphical, interactable lifetime are part of the physical client. +* Physical server - Often known as the dedicated server, the *physical server* is the entire program that runs whenever you launch any sort of `minecraft_server.jar` that does not bring up a playable GUI. +* Logical server - The *logical server* is what runs game logic: mob spawning, weather, updating inventories, health, AI, and all other game mechanics. The logical server is present within a physical server, but it also can run inside a physical client together with a logical client, as a single player world. The logical server always runs in a thread named the `Server Thread`. +* Logical client - The *logical client* is what accepts input from the player and relays it to the logical server. In addition, it also receives information from the logical server and makes it available graphically to the player. The logical client runs in the `Render Thread`, though often several other threads are spawned to handle things like audio and chunk render batching. + +In the MinecraftForge codebase, the physical side is represented by an enum called `Dist`, while the logical side is represented by an enum called `LogicalSide`. + +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. That is, if this field is `true`, the level is currently 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 (in other words, a single player world). + +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. + +This check should be used as your go-to default. Aside from `DistExecutor`, rarely will you need the other ways of determining side and adjusting behavior. + +### `DistExecutor` + +Considering the use of a single "universal" jar for client and server mods, and the separation of the physical sides into two jars, an important question comes to mind: How do we use code that is only present on one physical side? All code in `net.minecraft.client` is only present on the physical client. If any class you write references those names in any way, they will crash the game when that respective class is loaded in an environment where those names do not exist. A very common mistake in beginners is to call `Minecraft.getInstance().()` in block or block entity classes, which will crash any physical server as soon as the class is loaded. + +How do we resolve this? Luckily, FML has `DistExecutor`, which provides various methods to run different methods on different physical sides, or a single method only on one side. + +:::note +It is important to understand that FML checks based on the **physical** side. A single player world (logical server + logical client within a physical client) will always use `Dist.CLIENT`! +::: + +`DistExecutor` works by taking in a supplied supplier executing a method, effectively preventing classloading by taking advantage of the [`invokedynamic` JVM instruction][invokedynamic]. The executed method should be static and within a different class. Additionally, if no parameters are present for the static method, a method reference should be used instead of a supplier executing a method. + +There are two main methods within `DistExecutor`: `#runWhenOn` and `#callWhenOn`. The methods take in the physical side the executing method should run on and the supplied executing method which either runs or returns a result respectively. + +These two methods are subdivided further into `#safe*` and `#unsafe*` variants. Safe and unsafe variants are misnomers for their purposes. The main difference is that when in a development environment, the `#safe*` methods will validate that the supplied executing method is a lambda returning a method reference to another class with an error being thrown otherwise. Within the production environment, `#safe*` and `#unsafe*` are functionally the same. + +```java +// In a client class: ExampleClass +public static void unsafeRunMethodExample(Object param1, Object param2) { + // ... +} + +public static Object safeCallMethodExample() { + // ... +} + +// In some common class +DistExecutor.unsafeRunWhenOn(Dist.CLIENT, () -> ExampleClass.unsafeRunMethodExample(var1, var2)); + +DistExecutor.safeCallWhenOn(Dist.CLIENT, () -> ExampleClass::safeCallMethodExample); + +``` + +:::caution +Due to a change in how `invokedynamic` works in Java 9+, all `#safe*` variants of the `DistExecutor` methods throw the original exception wrapped within a `BootstrapMethodError` in the development environment. `#unsafe*` variants or a check to [`FMLEnvironment#dist`][dist] should be used instead. +::: + +### Thread Groups + +If `Thread.currentThread().getThreadGroup() == SidedThreadGroups.SERVER` is true, it is likely the current thread is on the logical server. Otherwise, it is likely on the logical client. This is useful to retrieve the **logical** side when you do not have access to a `Level` object to check `isClientSide`. It *guesses* which logical side you are on by looking at the group of the currently running thread. Because it is a guess, this method should only be used when other options have been exhausted. In nearly every case, you should prefer checking `Level#isClientSide`. + +### `FMLEnvironment#dist` and `@OnlyIn` + +`FMLEnvironment#dist` holds the **physical** side your code is running on. Since it is determined at startup, it does not rely on guessing to return its result. The number of use cases for this is limited, however. + +Annotating a method or field with the `@OnlyIn(Dist)` annotation indicates to the loader that the respective member should be completely stripped out of the definition not on the specified **physical** side. Usually, these are only seen when browsing through the decompiled Minecraft code, indicating methods that the Mojang obfuscator stripped out. There is **NO** reason for using this annotation directly. Use `DistExecutor` or a check on `FMLEnvironment#dist` instead. + +Common Mistakes +--------------- + +### Reaching Across Logical Sides + +Whenever you want to send information from one logical side to another, you must **always** use network packets. It is incredibly tempting, when in a single player scenario, to directly transfer data from the logical server to the logical client. + +This is actually very commonly inadvertently done through static fields. Since the logical client and logical server share the same JVM in a single player scenario, both threads writing to and reading from static fields will cause all sorts of race conditions and the classical issues associated with threading. + +This mistake can also be made explicitly by accessing physical client-only classes such as `Minecraft` from common code that runs or can run on the logical server. This mistake is easy to miss for beginners who debug in a physical client. The code will work there, but it will immediately crash on a physical server. + + +Writing One-Sided Mods +---------------------- + +In recent versions, Minecraft Forge has removed a "sidedness" attribute from the mods.toml. This means that your mods are expected to work whether they are loaded on the physical client or the physical server. So for one-sided mods, you would typically register your event handlers inside a `DistExecutor#safeRunWhenOn` or `DistExecutor#unsafeRunWhenOn` instead of directly calling the relevant registration methods in your mod constructor. Basically, if your mod is loaded on the wrong side, it should simply do nothing, listen to no events, and so on. A one-sided mod by nature should not register blocks, items, ... since they would need to be available on the other side, too. + +Additionally, if your mod is one-sided, it typically does not forbid the user from joining a server that is lacking that mod. Therefore, you should set the `displayTest` property in your [mods.toml][structuring] to whatever value is necessary. + +```toml +[[mods]] + # ... + + # MATCH_VERSION means that your mod will cause a red X if the versions on client and server differ. This is the default behaviour and should be what you choose if you have server and client elements to your mod. + # IGNORE_SERVER_VERSION means that your mod will not cause a red X if it's present on the server but not on the client. This is what you should use if you're a server only mod. + # IGNORE_ALL_VERSION means that your mod will not cause a red X if it's present on the client or the server. This is a special case and should only be used if your mod has no server component. + # NONE means that no display test is set on your mod. You need to do this yourself, see IExtensionPoint.DisplayTest for more information. You can define any scheme you wish with this value. + # IMPORTANT NOTE: this is NOT an instruction as to which environments (CLIENT or DEDICATED SERVER) your mod loads on. Your mod should load (and maybe do nothing!) whereever it finds itself. + displayTest="IGNORE_ALL_VERSION" # MATCH_VERSION is the default if nothing is specified (#optional) +``` + +If a custom display test is to be used, then the `displayTest` option should be set to `NONE`, and an `IExtensionPoint$DisplayTest` extension should be registered: + +```java +//Make sure the mod being absent on the other network side does not cause the client to display the server as incompatible +ModLoadingContext.get().registerExtensionPoint(IExtensionPoint.DisplayTest.class, () -> new IExtensionPoint.DisplayTest(() -> NetworkConstants.IGNORESERVERONLY, (a, b) -> true)); +``` + +This tells the client that it should ignore the server version being absent, and the server that it should not tell the client this mod should be present. So this snippet works both for client- and server-only-sided mods. + + +[invokedynamic]: https://docs.oracle.com/javase/specs/jvms/se17/html/jvms-6.html#jvms-6.5.invokedynamic +[dist]: #fmlenvironmentdist-and-onlyin +[structuring]: ../gettingstarted/modfiles.md#modstoml diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/datagen/client/_category_.json b/i18n/zh-CN/docusaurus-plugin-content-docs/current/datagen/client/_category_.json new file mode 100644 index 000000000..c1b8c6d90 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/datagen/client/_category_.json @@ -0,0 +1,3 @@ +{ + "label": "Client" +} \ No newline at end of file diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/datagen/client/localization.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/datagen/client/localization.md new file mode 100644 index 000000000..c497b3387 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/datagen/client/localization.md @@ -0,0 +1,41 @@ +Language Generation +=================== + +[Language files][lang] can be generated for a mod by subclassing `LanguageProvider` and implementing `#addTranslations`. Each `LanguageProvider` subclass created represents a separate [locale] (`en_us` represents American English, `es_es` represents Spanish, etc.). After implementation, the provider must be [added][datagen] to the `DataGenerator`. + +```java +// On the MOD event bus +@SubscribeEvent +public void gatherData(GatherDataEvent event) { + event.getGenerator().addProvider( + // Tell generator to run only when client assets are generating + event.includeClient(), + // Localizations for American English + output -> new MyLanguageProvider(output, MOD_ID, "en_us") + ); +} +``` + +`LanguageProvider` +------------------ + +Each language provider is simple a map of strings where each translation key is mapped to a localized name. A translation key mapping can be added using `#add`. Additionally, there are methods which use the translation key of a `Block`, `Item`, `ItemStack`, `Enchantment`, `MobEffect`, and `EntityType`. + +```java +// In LanguageProvider#addTranslations +this.addBlock(EXAMPLE_BLOCK, "Example Block"); +this.add("object.examplemod.example_object", "Example Object"); +``` + +:::tip +Localized names which contain alphanumeric values not in American English can be supplied as is. The provider automatically translates the characters into their unicode equivalents to be read by the game. + +```java +// Encdoded as 'Example with a d\u00EDacritic' +this.addItem("example.diacritic", "Example with a díacritic"); +``` +::: + +[lang]: ../../concepts/internationalization.md +[locale]: https://minecraft.wiki/w/Language#Languages +[datagen]: ../index.md#data-providers diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/datagen/client/modelproviders.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/datagen/client/modelproviders.md new file mode 100644 index 000000000..5075164c7 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/datagen/client/modelproviders.md @@ -0,0 +1,427 @@ +Model Generation +================ + +[Models] can be generated for models or block states by default. Each provides a method of generating the necessary JSONs (`ModelBuilder#toJson` for models and `IGeneratedBlockState#toJson` for block states). After implementation, the [associated providers][provider] must be [added][datagen] to the `DataGenerator`. + +```java +// On the MOD event bus +@SubscribeEvent +public void gatherData(GatherDataEvent event) { + DataGenerator gen = event.getGenerator(); + ExistingFileHelper efh = event.getExistingFileHelper(); + + gen.addProvider( + // Tell generator to run only when client assets are generating + event.includeClient(), + output -> new MyItemModelProvider(output, MOD_ID, efh) + ); + gen.addProvider( + event.includeClient(), + output -> new MyBlockStateProvider(output, MOD_ID, efh) + ); +} +``` + +Model Files +----------- + +A `ModelFile` acts as the base for all models referenced or generated by a provider. Each model file stores the location relative to the `models` subdirectory and can assert whether the file exists. + +### Existing Model Files + +`ExistingModelFile` is a subclass of `ModelFile` which checks via [`ExistingFileHelper#exists`][efh] whether the model already exists within the `models` subdirectory. All non-generated models are usually referenced through `ExistingModelFile`s. + +### Unchecked Model Files + +`UncheckedModelFile` is a subclass of `ModelFile` which assumes the specified model exists in some location. + +:::note +There should be no cases where an `UncheckedModelFile` is used to reference a model. If there is, then the associated resources are not properly being tracked by `ExistingFileHelper`. +::: + +Model Builders +-------------- + +A `ModelBuilder` represents a to-be-generated `ModelFile`. It contains all the data about a model: its parent, faces, textures, transformations, lighting, and [loader]. + +:::tip +While a complex model can be generated, it is recommended that those models be constructed using a modeling software beforehand. Then, the data provider can generate the children models with specific textures applied through the defined references in the parent complex model. +::: + +The parent (via `ModelBuilder#parent`) of the builder can be any `ModelFile`: generated or existing. Generated files are added to `ModelProvider`s as soon as the builder is created. The builder itself can be passed in as a parent, or the `ResourceLocation` can supplied alternatively. + +:::caution +If the parent is not generated before the child model when passing in a `ResourceLocation`, then an exception will be thrown. +::: + +Each element (via `ModelBuilder#element`) within a model is defined as cube using two three-dimensional points (`ElementBuilder#from` and `#to` respectively) where each axis is limited to the values `[-16,32]` (between -16 and 32 inclusive). Each face (`ElementBuilder#face`) of the cube can specify when the face is culled (`FaceBuilder#cullface`), [tint index][color] (`FaceBuilder#tintindex`), texture reference from the `textures` keys (`FaceBuilder#texture`), UV coordinate on the texture (`FaceBuilder#uvs`), and rotation in 90 degree intervals (`FaceBuilder#rotation`). + +:::note +It recommended for block models which have elements that exceed a bound of `[0,16]` on any axis to separate into multiple blocks, such as for a multiblock structure, to avoid lighting and culling issues. +::: + +Each cube can additionally be rotated (`ElementBuilder#rotation`) around a specified point (`RotationBuilder#origin`) for a given axis (`RotationBuilder#axis`) in 22.5 degree intervals (`RotationBuilder#angle`). The cube can scale all faces in relation to the entire model as well (`RotationBuilder#rescale`). The cube can also determine whether its shadows should be rendered (`ElementBuilder#shade`). + +Each model defines a list of texture keys (`ModelBuilder#texture`) which points to either a location or a reference. Each key can then be referenced in any element by prefixing using a `#` (a texture key of `example` can be referenced in an element using `#example`). A location specifies where a texture is in `assets//textures/.png`. A reference is used by any models parenting the current model as keys to define textures for later. + +The model can additionally be transformed (`ModelBuilder#transforms`) for any defined perspective (in the left hand in first person, in the gui, on the ground, etc.). For any perspective (`TransformsBuilder#transform`), the rotation (`TransformVecBuilder#rotation`), translation (`TransformVecBuilder#translation`), and scale (`TransformVecBuilder#scale`) can be set. + +Finally, the model can set whether to use ambient occlusion in a level (`ModelBuilder#ao`) and from what location to light and shade the model from `ModelBuilder#guiLight`. + +### `BlockModelBuilder` + +A `BlockModelBuilder` represents a block model to-be-generated. In addition to the `ModelBuilder`, a transform to the entire model (`BlockModelBuilder#rootTransform`) can be generated. The root can be translated (`RootTransformBuilder#transform`), rotated (`RootTransformBuilder#rotation`, `RootTransformBuilder#postRotation`), and scaled (`RootTransformBuilder#scale`) either individually or all in one transformation (`RootTransformBuilder#transform`) around some origin (`RootTransformBuilder#origin`). + +### `ItemModelBuilder` + +An `ItemModelBuilder` represents an item model to-be-generated. In addition to the `ModelBuilder`, [overrides] (`OverrideBuilder#override`) can be generated. Each override applied to a model can apply conditions which represent for a given property that must be above the specified value (`OverrideBuilder#predicate`). If the conditions are met, then the specified model (`OverrideBuilder#model`) will be rendered instead of this model. + +Model Providers +--------------- + +The `ModelProvider` subclasses are responsible for generating the constructed `ModelBuilder`s. The provider takes in the generator, mod id, subdirectory in the `models` folder to generate within, a `ModelBuilder` factory, and the existing file helper. Each provider subclass must implement `#registerModels`. + +The provider contains basic methods which either create the `ModelBuilder` or provides convenience for getting texture or model references: + +Method | Description +:---: | :--- +`getBuilder` | Creates a new `ModelBuilder` within the provider's subdirectory for the given mod id. +`withExistingParent` | Creates a new `ModelBuilder` for the given parent. Should be used when the parent is not generated by the builder. +`mcLoc` | Creates a `ResourceLocation` for the path in the `minecraft` namespace. +`modLoc` | Creates a `ResourceLocation` for the path in the given mod id's namespace. + +Additionally, there are several helpers for easily generating common models using vanilla templates. Most are for block models with only a few being universal. + +:::note +Although the models are within a specific subdirectory, that does **not** mean that the model cannot be referenced by a model in another subdirectory. Usually, it is indicative of that model being used for that type of object. +::: + +### `BlockModelProvider` + +The `BlockModelProvider` is used for generating block models via `BlockModelBuilder` in the `block` folder. Block models should typically parent `minecraft:block/block` or one of its children models for use with item models. + +:::note +Block models and its item model counterpart are typically not generated through a direct subclass of `BlockModelProvider` and `ItemModelProvider` but through [`BlockStateProvider`][blockstateprovider]. +::: + +### `ItemModelProvider` + +The `ItemModelProvider` is used for generating block models via `ItemModelBuilder` in the `item` folder. Most item models parent `item/generated` and use `layer0` to specify their texture, which can be done using `#singleTexture`. + +:::note +`item/generated` can support five texture layers stacked on top of each other: `layer0`, `layer1`, `layer2`, `layer3`, and `layer4`. +::: + +```java +// In some ItemModelProvider#registerModels + +// Will generate 'assets//models/item/example_item.json' +// Parent will be 'minecraft:item/generated' +// For the texture key 'layer0' +// It will be at 'assets//textures/item/example_item.png' +this.basicItem(EXAMPLE_ITEM.get()); +``` + +:::note +Item models for blocks should typically parent an existing block model instead of generating a separate model for an item. +::: + +Block State Provider +-------------------- + +A `BlockStateProvider` is responsible for generating [block state JSONs][blockstate] in `blockstates`, block models in `models/block`, and item models in `models/item` for said blocks. The provider takes in the data generator, mod id, and existing file helper. Each `BlockStateProvider` subclass must implement `#registerStatesAndModels`. + +The provider contains basic methods for generating block state JSONs and block models. Item models must be generated separately as a block state JSON may define multiple models to use in different contexts. There are a number of common methods, however, that that the modder should be aware of when dealing with more complex tasks: + +Method | Description +:---: | :--- +`models` | Gets the [`BlockModelProvider`][blockmodels] used to generate the item block models. +`itemModels` | Gets the [`ItemModelProvider`][itemmodels] used to generate the item block models. +`modLoc` | Creates a `ResourceLocation` for the path in the given mod id's namespace. +`mcLoc` | Creates a `ResourceLocation` for the path in the `minecraft` namespace. +`blockTexture` | References a texture within `textures/block` which has the same name as the block. +`simpleBlockItem` | Creates an item model for a block given the associated model file. +`simpleBlockWithItem` | Creates a single block state for a block model and an item model using the block model as its parent. + +A block state JSON is made up of variants or conditions. Each variant or condition references a `ConfiguredModelList`: a list of `ConfiguredModel`s. Each configured model contains the model file (via `ConfiguredModel$Builder#modelFile`), the X and Y rotation in 90 degree intervals (via `#rotationX` and `rotationY` respectively), whether the texture can rotate when the model is rotated by the block state JSON (via `#uvLock`), and the weight of the model appearing compared to other models in the list (via `#weight`). + +The builder (`ConfiguredModel#builder`) can also create an array of `ConfiguredModel`s by creating the next model using `#nextModel` and repeating the settings until `#build` is called. + +### `VariantBlockStateBuilder` + +Variants can be generated using `BlockStateProvider#getVariantBuilder`. Each variant specifies a list of [properties] (`PartialBlockstate`) which when matches a `BlockState` in a level, will display a model chosen from the corresponding model list. An exception is thrown if there is a `BlockState` which is not covered by any variant defined. Only one variant can be true for any `BlockState`. + +A `PartialBlockstate` is typically defined using one of three methods: + +Method | Description +:---: | :--- +`partialState` | Creates a `PartialBlockstate` to be defined. +`forAllStates` | Defines a function where a given `BlockState` can be represented by an array of `ConfiguredModel`s. +`forAllStatesExcept` | Defines a function similar to `#forAllStates`; however, it also specifies which properties do not affect the models rendered. + +For a `PartialBlockstate`, the properties defined can be specified (`#with`). The configured models can be set (`#setModels`), appended to the existing models (`#addModels`), or built (`#modelForState` and then `ConfiguredModel$Builder#addModel` once finished instead of `#ConfiguredModel$Builder#build`). + +```java +// In some BlockStateProvider#registerStatesAndModels + +// EXAMPLE_BLOCK_1: Has Property BlockStateProperties#AXIS +this.getVariantBuilder(EXAMPLE_BLOCK_1) // Get variant builder + .partialState() // Construct partial state + .with(AXIS, Axis.Y) // When BlockState AXIS = Y + .modelForState() // Set models when AXIS = Y + .modelFile(yModelFile1) // Can show 'yModelFile1' + .nextModel() // Adds another model when AXIS = Y + .modelFile(yModelFile2) // Can show 'yModelFile2' + .weight(2) // Will show 'yModelFile2' 2/3 of the time + .addModel() // Finalizes models when AXIS = Y + .with(AXIS, Axis.Z) // When BlockState AXIS = Z + .modelForState() // Set models when AXIS = Z + .modelFile(hModelFile) // Can show 'hModelFile' + .addModel() // Finalizes models when AXIS = Z + .with(AXIS, Axis.X) // When BlockState AXIS = X + .modelForState() // Set models when AXIS = X + .modelFile(hModelFile) // Can show 'hModelFile' + .rotationY(90) // Rotates 'hModelFile' 90 degrees on the Y axis + .addModel(); // Finalizes models when AXIS = X + +// EXAMPLE_BLOCK_2: Has Property BlockStateProperties#HORIZONTAL_FACING +this.getVariantBuilder(EXAMPLE_BLOCK_2) // Get variant builder + .forAllStates(state -> // For all possible states + ConfiguredModel.builder() // Creates configured model builder + .modelFile(modelFile) // Can show 'modelFile' + .rotationY((int) state.getValue(HORIZONTAL_FACING).toYRot()) // Rotates 'modelFile' on the Y axis depending on the property + .build() // Creates the array of configured models + ); + +// EXAMPLE_BLOCK_3: Has Properties BlockStateProperties#HORIZONTAL_FACING, BlockStateProperties#WATERLOGGED +this.getVariantBuilder(EXAMPLE_BLOCK_3) // Get variant builder + .forAllStatesExcept(state -> // For all HORIZONTAL_FACING states + ConfiguredModel.builder() // Creates configured model builder + .modelFile(modelFile) // Can show 'modelFile' + .rotationY((int) state.getValue(HORIZONTAL_FACING).toYRot()) // Rotates 'modelFile' on the Y axis depending on the property + .build(), // Creates the array of configured models + WATERLOGGED); // Ignores WATERLOGGED property +``` + +### `MultiPartBlockStateBuilder` + +Multiparts can be generated using `BlockStateProvider#getMultipartBuilder`. Each part (`MultiPartBlockStateBuilder#part`) specifies a group of conditions of properties which when matches a `BlockState` in a level, will display a model from the model list. All condition groups that match the `BlockState` will display their chosen model overlaid on each other. + +For any part (obtained via `ConfiguredModel$Builder#addModel`), a condition can be added (via `#condition`) when a property is one of the specified values. Conditions must all succeed or, when `#useOr` is set, at least one must succeed. Conditions can be grouped (via `#nestedGroup`) as long as the current grouping only contains other groups and no single conditions. Groups of conditions can be left using `#endNestedGroup` and a given part can be finished via `#end`. + +```java +// In some BlockStateProvider#registerStatesAndModels + +// Redstone Wire +this.getMultipartBuilder(REDSTONE) // Get multipart builder + .part() // Create part + .modelFile(redstoneDot) // Can show 'redstoneDot' + .addModel() // 'redstoneDot' is displayed when... + .useOr() // At least one of these conditions are true + .nestedGroup() // true when all grouped conditions are true + .condition(WEST_REDSTONE, NONE) // true when WEST_REDSTONE is NONE + .condition(EAST_REDSTONE, NONE) // true when EAST_REDSTONE is NONE + .condition(SOUTH_REDSTONE, NONE) // true when SOUTH_REDSTONE is NONE + .condition(NORTH_REDSTONE, NONE) // true when NORTH_REDSTONE is NONE + .endNestedGroup() // End group + .nestedGroup() // true when all grouped conditions are true + .condition(EAST_REDSTONE, SIDE, UP) // true when EAST_REDSTONE is SIDE or UP + .condition(NORTH_REDSTONE, SIDE, UP) // true when NORTH_REDSTONE is SIDE or UP + .endNestedGroup() // End group + .nestedGroup() // true when all grouped conditions are true + .condition(EAST_REDSTONE, SIDE, UP) // true when EAST_REDSTONE is SIDE or UP + .condition(SOUTH_REDSTONE, SIDE, UP) // true when SOUTH_REDSTONE is SIDE or UP + .endNestedGroup() // End group + .nestedGroup() // true when all grouped conditions are true + .condition(WEST_REDSTONE, SIDE, UP) // true when WEST_REDSTONE is SIDE or UP + .condition(SOUTH_REDSTONE, SIDE, UP) // true when SOUTH_REDSTONE is SIDE or UP + .endNestedGroup() // End group + .nestedGroup() // true when all grouped conditions are true + .condition(WEST_REDSTONE, SIDE, UP) // true when WEST_REDSTONE is SIDE or UP + .condition(NORTH_REDSTONE, SIDE, UP) // true when NORTH_REDSTONE is SIDE or UP + .endNestedGroup() // End group + .end() // Finish part + .part() // Create part + .modelFile(redstoneSide0) // Can show 'redstoneSide0' + .addModel() // 'redstoneSide0' is displayed when... + .condition(NORTH_REDSTONE, SIDE, UP) // NORTH_REDSTONE is SIDE or UP + .end() // Finish part + .part() // Create part + .modelFile(redstoneSideAlt0) // Can show 'redstoneSideAlt0' + .addModel() // 'redstoneSideAlt0' is displayed when... + .condition(SOUTH_REDSTONE, SIDE, UP) // SOUTH_REDSTONE is SIDE or UP + .end() // Finish part + .part() // Create part + .modelFile(redstoneSideAlt1) // Can show 'redstoneSideAlt1' + .rotationY(270) // Rotates 'redstoneSideAlt1' 270 degrees on the Y axis + .addModel() // 'redstoneSideAlt1' is displayed when... + .condition(EAST_REDSTONE, SIDE, UP) // EAST_REDSTONE is SIDE or UP + .end() // Finish part + .part() // Create part + .modelFile(redstoneSide1) // Can show 'redstoneSide1' + .rotationY(270) // Rotates 'redstoneSide1' 270 degrees on the Y axis + .addModel() // 'redstoneSide1' is displayed when... + .condition(WEST_REDSTONE, SIDE, UP) // WEST_REDSTONE is SIDE or UP + .end() // Finish part + .part() // Create part + .modelFile(redstoneUp) // Can show 'redstoneUp' + .addModel() // 'redstoneUp' is displayed when... + .condition(NORTH_REDSTONE, UP) // NORTH_REDSTONE is UP + .end() // Finish part + .part() // Create part + .modelFile(redstoneUp) // Can show 'redstoneUp' + .rotationY(90) // Rotates 'redstoneUp' 90 degrees on the Y axis + .addModel() // 'redstoneUp' is displayed when... + .condition(EAST_REDSTONE, UP) // EAST_REDSTONE is UP + .end() // Finish part + .part() // Create part + .modelFile(redstoneUp) // Can show 'redstoneUp' + .rotationY(180) // Rotates 'redstoneUp' 180 degrees on the Y axis + .addModel() // 'redstoneUp' is displayed when... + .condition(SOUTH_REDSTONE, UP) // SOUTH_REDSTONE is UP + .end() // Finish part + .part() // Create part + .modelFile(redstoneUp) // Can show 'redstoneUp' + .rotationY(270) // Rotates 'redstoneUp' 270 degrees on the Y axis + .addModel() // 'redstoneUp' is displayed when... + .condition(WEST_REDSTONE, UP) // WEST_REDSTONE is UP + .end(); // Finish part +``` + +Model Loader Builders +--------------------- + +Custom model loaders can also be generated for a given `ModelBuilder`. Custom model loaders subclass `CustomLoaderBuilder` and can be applied to a `ModelBuilder` via `#customLoader`. The factory method passed in creates a new loader builder to which configurations can be made. After all the changes have been finished, the custom loader can return back to the `ModelBuilder` via `CustomLoaderBuilder#end`. + +Model Builder | Factory Method | Description +:---: | :---: | :--- +`DynamicFluidContainerModelBuilder` | `#begin` | Generates a bucket model for the specified fluid. +`CompositeModelBuilder` | `#begin` | Generates a model composed of models. +`ItemLayersModelBuilder` | `#begin` | Generates a Forge implementation of an `item/generated` model. +`SeparateTransformsModelBuilder` | `#begin` | Generates a model which changes based on the specified [transform]. +`ObjModelBuilder` | `#begin` | Generates an [OBJ model][obj]. + +```java +// For some BlockModelBuilder builder +builder.customLoader(ObjModelBuilder::begin) // Custom loader 'forge:obj' + .modelLocation(modLoc("models/block/model.obj")) // Set the OBJ model location + .flipV(true) // Flips the V coordinate in the supplied .mtl texture + .end() // Finish custom loader configuration +.texture("particle", mcLoc("block/dirt")) // Set particle texture to dirt +.texture("texture0", mcLoc("block/dirt")); // Set 'texture0' texture to dirt +``` + +Custom Model Loader Builders +---------------------------- + +Custom loader builders can be created by extending `CustomLoaderBuilder`. The constructor can still have a `protected` visibility with the `ResourceLocation` hardcoded to the loader id registered via `ModelEvent$RegisterGeometryLoaders#register`. The builder can then be initialized via a static factory method or the constructor if made `public`. + +```java +public class ExampleLoaderBuilder> extends CustomLoaderBuilder { + public static > ExampleLoaderBuilder begin(T parent, ExistingFileHelper existingFileHelper) { + return new ExampleLoaderBuilder<>(parent, existingFileHelper); + } + + protected ExampleLoaderBuilder(T parent, ExistingFileHelper existingFileHelper) { + super(new ResourceLocation(MOD_ID, "example_loader"), parent, existingFileHelper); + } +} +``` + +Afterwards, any configurations specified by the loader should be added as chainable methods. + +```java +// In ExampleLoaderBuilder +public ExampleLoaderBuilder exampleInt(int example) { + // Set int + return this; +} + +public ExampleLoaderBuilder exampleString(String example) { + // Set string + return this; +} +``` + +If any additional configuration is specified, `#toJson` should be overridden to write the additional properties. + +```java +// In ExampleLoaderBuilder +@Override +public JsonObject toJson(JsonObject json) { + json = super.toJson(json); // Handle base loader properties + // Encode custom loader properties + return json; +} +``` + +Custom Model Providers +---------------------- + +Custom model providers require a `ModelBuilder` subclass, which defines the base of the model to generate, and a `ModelProvider` subclass, which generates the models. + +The `ModelBuilder` subclass contains any special properties to which can be applied specifically to those types of models (item models can have overrides). If any additional properties are added, `#toJson` needs to be overridden to write the additional information. + +```java +public class ExampleModelBuilder extends ModelBuilder { + // ... +} +``` + +The `ModelProvider` subclass requires no special logic. The constructor should hardcode the subdirectory within the `models` folder and the `ModelBuilder` to represent the to-be-generated models. + +```java +public class ExampleModelProvider extends ModelProvider { + + public ExampleModelProvider(PackOutput output, String modid, ExistingFileHelper existingFileHelper) { + // Models will be generated to 'assets//models/example' if no 'modid' is specified in '#getBuilder' + super(output, modid, "example", ExampleModelBuilder::new, existingFileHelper); + } +} +``` + +Custom Model Consumers +---------------------- + +Custom model consumers like [`BlockStateProvider`][blockstateprovider] can be created by manually generating the models themselves. The `ModelProvider` subclass used to generate the models should be specified and made available. + +```java +public class ExampleModelConsumerProvider implements IDataProvider { + + public ExampleModelConsumerProvider(PackOutput output, String modid, ExistingFileHelper existingFileHelper) { + this.example = new ExampleModelProvider(output, modid, existingFileHelper); + } +} +``` + +Once the data provider is running, the models within the `ModelProvider` subclass can be generated using `ModelProvider#generateAll`. + +```java +// In ExampleModelConsumerProvider +@Override +public CompletableFuture run(CachedOutput cache) { + // Populate the model provider + CompletableFuture exampleFutures = this.example.generateAll(cache); // Generate the models + + // Run logic and create CompletableFuture(s) for writing files + // ... + + // Assume we have a new CompletableFuture providerFuture + return CompletableFuture.allOf(exampleFutures, providerFuture); +} +``` + +[provider]: #model-providers +[models]: ../../resources/client/models/index.md +[datagen]: ../index.md#data-providers +[efh]: ../index.md#existing-files +[loader]: #custom-model-loader-builders +[color]: ../../resources/client/models/tinting.md#blockcoloritemcolor +[overrides]: ../../resources/client/models/itemproperties.md +[blockstateprovider]: #block-state-provider +[blockstate]: https://minecraft.wiki/w/Tutorials/Models#Block_states +[blockmodels]: #blockmodelprovider +[itemmodels]: #itemmodelprovider +[properties]: ../../blocks/states.md#implementing-block-states +[transform]: ../../rendering/modelloaders/transform.md +[obj]: ../../rendering/modelloaders/index.md#wavefront-obj-models diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/datagen/client/sounds.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/datagen/client/sounds.md new file mode 100644 index 000000000..6f0ec9136 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/datagen/client/sounds.md @@ -0,0 +1,84 @@ +Sound Definition Generation +=========================== + +The `sounds.json` file can be generated for a mod by subclassing `SoundDefinitionsProvider` and implementing `#registerSounds`. After implementation, the provider must be [added][datagen] to the `DataGenerator`. + +```java +// On the MOD event bus +@SubscribeEvent +public void gatherData(GatherDataEvent event) { + event.getGenerator().addProvider( + // Tell generator to run only when client assets are generating + event.includeClient(), + output -> new MySoundDefinitionsProvider(output, MOD_ID, event.getExistingFileHelper()) + ); +} +``` + +Adding a Sound +-------------- + +A sound definition can be generated by specifying the sound name and definition via `#add`. The sound name can either be provided from a [`SoundEvent`][soundevent], `ResourceLocation`, or string. + +:::caution +The sound name supplied will always assume the namespace is the mod id supplied to the constructor of the provider. There is no validation performed on the namespace of the sound name! +::: + +### `SoundDefinition` + +The `SoundDefinition` can be created using `#definition`. The definition contains the data to define a sound instance. + +A definition specifies a few methods: + +Method | Description +:---: | :--- +`with` | Adds a sound(s) which may be played when the definition is selected. +`subtitle` | Sets the translation key of the definition. +`replace` | When `true`, removes the sounds already defined by other `sounds.json` for this definition instead of appending to it. + +### `SoundDefinition$Sound` + +A sound supplied to the `SoundDefinition` can be specified using `SoundDefinitionsProvider#sound`. These methods take in the reference of the sound and a `SoundType` if specified. + +The `SoundType` can be one of two values: + +Sound Type | Definition +:---: | :--- +`SOUND` | Specifies a reference to the sound located at `assets//sounds/.ogg`. +`EVENT` | Specifies a reference to the name of another sound defined by the `sounds.json`. + +Each `Sound` created from `SoundDefinitionsProvider#sound` can specify additional configurations on how to load and play the sound provided: + +Method | Description +:---: | :--- +`volume` | Sets the volume scale of the sound, must be greater than `0`. +`pitch` | Sets the pitch scale of the sound, must be greater than `0`. +`weight` | Sets the likelihood of the sound getting played when the sound is selected. +`stream` | When `true`, reads the sound from file instead of loading the sound into memory. Recommended for long sounds: background music, music discs, etc. +`attenuationDistance` | Sets the number of blocks the sound can be heard from. +`preload` | When `true`, immediately loads the sound into memory as soon as the resource pack is loaded. + +```java +// In some SoundDefinitionsProvider#registerSounds +this.add(EXAMPLE_SOUND_EVENT, definition() + .subtitle("sound.examplemod.example_sound") // Set translation key + .with( + sound(new ResourceLocation(MODID, "example_sound_1")) // Set first sound + .weight(4) // Has a 4 / 5 = 80% chance of playing + .volume(0.5), // Scales all volumes called on this sound by half + sound(new ResourceLocation(MODID, "example_sound_2")) // Set second sound + .stream() // Streams the sound + ) +); + +this.add(EXAMPLE_SOUND_EVENT_2, definition() + .subtitle("sound.examplemod.example_sound") // Set translation key + .with( + sound(EXAMPLE_SOUND_EVENT.getLocation(), SoundType.EVENT) // Adds sounds from 'EXAMPLE_SOUND_EVENT' + .pitch(0.5) // Scales all pitches called on this sound by half + ) +); +``` + +[datagen]: ../index.md#data-providers +[soundevent]: ../../gameeffects/sounds.md#creating-sound-events diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/datagen/index.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/datagen/index.md new file mode 100644 index 000000000..eaf6e05ba --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/datagen/index.md @@ -0,0 +1,86 @@ +Data Generators +=============== + +Data generators are a way to programmatically generate the assets and data of mods. It allows the definition of the contents of these files in the code and their automatic generation, without worrying about the specifics. + +The data generator system is loaded by the main class `net.minecraft.data.Main`. Different command-line arguments can be passed to customize which mods' data are gathered, what existing files are considered, etc. The class responsible for data generation is `net.minecraft.data.DataGenerator`. + +The default configurations in the MDK `build.gradle` adds the `runData` task for running the data generators. + +Existing Files +-------------- +All references to textures or other data files not generated for data generation must reference existing files on the system. This is to ensure that all referenced textures are in the correct places, so typos can be found and corrected. + +`ExistingFileHelper` is the class responsible for validating the existence of those data files. An instance can be retrieved from `GatherDataEvent#getExistingFileHelper`. + +The `--existing ` argument allows the specified folder and its subfolders to be used when validating the existence of files. Additionally, the `--existing-mod ` argument allows the resources of a loaded mod to be used for validation. By default, only the vanilla datapack and resources are available to the `ExistingFileHelper`. + +Generator Modes +--------------- + +The data generator can be configured to run 4 different data generations, which are configured from the command-line parameters, and can be checked from `GatherDataEvent#include***` methods. + +* __Client Assets__ + * Generates client-only files in `assets`: block/item models, blockstate JSONs, language files, etc. + * __`--client`__, `#includeClient` +* __Server Data__ + * Generates server-only files in `data`: recipes, advancements, tags, etc. + * __`--server`__, `#includeServer` +* __Development Tools__ + * Runs some development tools: converting SNBT to NBT and vice-versa, etc. + * __`--dev`__, `#includeDev` +* __Reports__ + * Dumps all registered blocks, items, commands, etc. + * __`--reports`__, `#includeReports` + +All of the generators can be included using `--all`. + +Data Providers +-------------- + +Data providers are the classes that actually define what data will be generated and provided. All data providers implement `DataProvider`. Minecraft has abstract implementations for most assets and data, so modders need only to extend and override the specified method. + +The `GatherDataEvent` is fired on the mod event bus when the data generator is being created, and the `DataGenerator` can be obtained from the event. Create and register data providers using `DataGenerator#addProvider`. + +### Client Assets +* [`net.minecraftforge.common.data.LanguageProvider`][langgen] - for [language strings][lang]; implement `#addTranslations` +* [`net.minecraftforge.common.data.SoundDefinitionsProvider`][soundgen] - for [`sounds.json`][sounds]; implement `#registerSounds` +* [`net.minecraftforge.client.model.generators.ModelProvider`][modelgen] - for [models]; implement `#registerModels` + * [`ItemModelProvider`][itemmodelgen] - for item models + * [`BlockModelProvider`][blockmodelgen] - for block models +* [`net.minecraftforge.client.model.generators.BlockStateProvider`][blockstategen] - for blockstate JSONs and their block and item models; implement `#registerStatesAndModels` + +### Server Data + +**These classes are under the `net.minecraftforge.common.data` package**: + +* [`GlobalLootModifierProvider`][glmgen] - for [global loot modifiers][glm]; implement `#start` +* [`DatapackBuiltinEntriesProvider`][datapackregistriesgen] for datapack registry objects; pass in `RegistrySetBuilder` to the constructor + +**These classes are under the `net.minecraft.data` package**: + +* [`loot.LootTableProvider`][loottablegen] - for [loot tables][loottable]; pass in `LootTableProvider$SubProviderEntry`s to the constructor +* [`recipes.RecipeProvider`][recipegen] - for [recipes] and their unlocking advancements; implement `#buildRecipes` +* [`tags.TagsProvider`][taggen] - for [tags]; implement `#addTags` +* [`advancements.AdvancementProvider`][advgen] - for [advancements]; pass in `AdvancementSubProvider`s to the constructor + +[langgen]: ./client/localization.md +[lang]: https://minecraft.wiki/w/Language +[soundgen]: ./client/sounds.md +[sounds]: https://minecraft.wiki/w/Sounds.json +[modelgen]: ./client/modelproviders.md +[models]: ../resources/client/models/index.md +[itemmodelgen]: ./client/modelproviders.md#itemmodelprovider +[blockmodelgen]: ./client/modelproviders.md#blockmodelprovider +[blockstategen]: ./client/modelproviders.md#block-state-provider +[glmgen]: ./server/glm.md +[glm]: ../resources/server/glm.md +[datapackregistriesgen]: ./server/datapackregistries.md +[loottablegen]: ./server/loottables.md +[loottable]: ../resources/server/loottables.md +[recipegen]: ./server/recipes.md +[recipes]: ../resources/server/recipes/index.md +[taggen]: ./server/tags.md +[tags]: ../resources/server/tags.md +[advgen]: ./server/advancements.md +[advancements]: ../resources/server/advancements.md diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/datagen/server/_category_.json b/i18n/zh-CN/docusaurus-plugin-content-docs/current/datagen/server/_category_.json new file mode 100644 index 000000000..8de78b39d --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/datagen/server/_category_.json @@ -0,0 +1,3 @@ +{ + "label": "Server" +} \ No newline at end of file diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/datagen/server/advancements.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/datagen/server/advancements.md new file mode 100644 index 000000000..4b27b85d1 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/datagen/server/advancements.md @@ -0,0 +1,67 @@ +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`. + +!!! 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`. + +```java +// 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, /*...*/) + ) + ); +} +``` + +`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.. + +```java +// 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` 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` | 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. + +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 +// In some 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 +``` + +[advancements]: ../../resources/server/advancements.md +[datagen]: ../index.md#data-providers +[conditional]: ../../resources/server/conditional.md diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/datagen/server/datapackregistries.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/datagen/server/datapackregistries.md new file mode 100644 index 000000000..3f105921e --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/datagen/server/datapackregistries.md @@ -0,0 +1,128 @@ +Datapack Registry Object Generation +================================== + +Datapack registry objects can be generated for a mod by constructing a new `DatapackBuiltinEntriesProvider` and providing a `RegistrySetBuilder` with the new objects to register. The provider must be [added][datagen] to the `DataGenerator`. + +!!! note + `DatapackBuiltinEntriesProvider` is a Forge extension on top of `RegistriesDatapackGenerator` which properly handles referencing existing datapack registry objects without exploding the entry. So, this documentation will use `DatapackBuiltinEntriesProvider`. + +```java +// 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 DatapackBuiltinEntriesProvider( + output, + event.getLookupProvider(), + // The builder containing the datapack registry objects to generate + new RegistrySetBuilder().add(/* ... */), + // Set of mod ids to generate the datapack registry objects of + Set.of(MOD_ID) + ) + ); +} +``` + +`RegistrySetBuilder` +-------------------- + +A `RegistrySetBuilder` is responsible for building all datapack registry objects to be used within the game. The builder can add a new entry for a registry, which can then register objects to that registry. + +First, a new instance of a `RegistrySetBuilder` can be initialized by calling the constructor. Then, the `#add` method (which takes in the `ResourceKey` of the registry, a `RegistryBootstrap` consumer containing the `BootstapContext` to register the objects, and an optional `Lifecycle` argument to indicate the registry's current lifecycle status) can be called to handle a specific registry for registration. + +```java +new RegistrySetBuilder() + // Create configured features + .add(Registries.CONFIGURED_FEATURE, bootstrap -> { + // Register configured features here + }) + // Create placed features + .add(Registries.PLACED_FEATURE, bootstrap -> { + // Register placed features here + }); +``` + +!!! note + Datapack registries created through Forge can also generate their objects using this builder by also passing in the associated `ResourceKey`. + +Registering with `BootstapContext` +---------------------------------- + +The `#register` method in the `BootstapContext` provided by the builder can be used to register objects. It takes in the `ResourceKey` representing the registry name of the object, the object to register, and an optional `Lifecycle` argument to indicate the registry object's current lifecycle status. + +```java +public static final ResourceKey> EXAMPLE_CONFIGURED_FEATURE = ResourceKey.create( + Registries.CONFIGURED_FEATURE, + new ResourceLocation(MOD_ID, "example_configured_feature") +); + +// In some constant location or argument +new RegistrySetBuilder() + // Create configured features + .add(Registries.CONFIGURED_FEATURE, bootstrap -> { + // Register configured features here + bootstrap.register( + // The resource key for the configured feature + EXAMPLE_CONFIGURED_FEATURE, + new ConfiguredFeature<>( + Feature.ORE, // Create an ore feature + new OreConfiguration( + List.of(), // Does nothing + 8 // in veins of at most 8 + ) + ) + ); + }) + // Create placed features + .add(Registries.PLACED_FEATURE, bootstrap -> { + // Register placed features here + }); +``` + +### Datapack Registry Object Lookup + +Sometimes datapack registry objects may want to use other datapack registry objects or tags containing datapack registry objects. In those cases, you can look up another datapack registry using `BootstapContext#lookup` to get a `HolderGetter`. From there, you can get a `Holder$Reference` to the datapack registry object or a `HolderSet$Named` for the tag via `#getOrThrow` by passing in the associated key. + +```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") +); + +// In some constant location or argument +new RegistrySetBuilder() + // Create configured features + .add(Registries.CONFIGURED_FEATURE, bootstrap -> { + // Register configured features here + bootstrap.register( + // The resource key for the configured feature + EXAMPLE_CONFIGURED_FEATURE, + new ConfiguredFeature(/* ... */) + ); + }) + // Create placed features + .add(Registries.PLACED_FEATURE, bootstrap -> { + // Register placed features here + + // Get configured feature registry + HolderGetter> configured = bootstrap.lookup(Registries.CONFIGURED_FEATURE); + + bootstrap.register( + // The resource key for the placed feature + EXAMPLE_PLACED_FEATURE, + new PlacedFeature( + configured.getOrThrow(EXAMPLE_CONFIGURED_FEATURE), // Get the configured feature + List.of() // and do nothing to the placement location + ) + ) + }); +``` + +[datagen]: ../index.md#data-providers \ No newline at end of file diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/datagen/server/glm.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/datagen/server/glm.md new file mode 100644 index 000000000..1192b2377 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/datagen/server/glm.md @@ -0,0 +1,30 @@ +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`. + +```java +// 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) + ); +} + +// In some GlobalLootModifierProvider#start +this.add("example_modifier", new ExampleModifier( + new LootItemCondition[] { + WeatherCheck.weather().setRaining(true).build() // Executes when raining + }, + "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-CN/docusaurus-plugin-content-docs/current/datagen/server/loottables.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/datagen/server/loottables.md new file mode 100644 index 000000000..2436a2d2b --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/datagen/server/loottables.md @@ -0,0 +1,144 @@ +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`. + +```java +// 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, /*...*/) + ) + ); +} +``` + +`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. + +```java +public class ExampleSubProvider implements LootTableSubProvider { + + // 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) { + // Generate loot tables here by calling writer#accept + } +} +``` + +The table can then be added to `LootTableProvider#getTables` for any available `LootContextParamSet`: + +```java +// In the list passed into the LootTableProvider constructor +new LootTableProvider.SubProviderEntry( + ExampleSubProvider::new, + // Loot table generator for the 'empty' param set + LootContextParamSets.EMPTY +) +``` + +### `BlockLootSubProvider` and `EntityLootSubProvider` Subclasses + +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. + +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 +// In some BlockLootSubProvider subclass +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. + +```java +// In some EntityLootSubProvider subclass +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. + +!!! tip + If `DeferredRegister` is being used to register a mod's objects, then the `#getKnown*` methods can be supplied the entries via `DeferredRegister#getEntries`: + + ```java + // In some BlockLootSubProvider subclass for some 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 + } + ``` + +The loot tables themselves can be added by implementing the `#generate` method. + +```java +// In some BlockLootSubProvider subclass +@Override +public void generate() { + // Add loot tables here +} +``` + +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. + +!!! 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. + +### 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. + +### 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. + +### 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`). + +### 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`). + +### 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`. + +#### 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`. + +### NumberProvider + +Number providers determine how many times a loot pool executes. Each provider has an associated, [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`. + +[loottable]: ../../resources/server/loottables.md +[datagen]: ../index.md#data-providers +[registered]: ../../concepts/registries.md#registries-that-arent-forge-registries diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/datagen/server/recipes.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/datagen/server/recipes.md new file mode 100644 index 000000000..b74e98b96 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/datagen/server/recipes.md @@ -0,0 +1,195 @@ +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`. + +After implementation, the provider must be [added][datagen] to the `DataGenerator`. + +```java +// 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 + ); +} +``` + +`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. + +!!! 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. + +!!! warning + The item results being generated must have a valid `RecipeCategory` specified; otherwise, a `NullPointerException` will be thrown. + +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 + Recipe criteria commonly use `InventoryChangeTrigger` to unlock their recipe when certain items are present in the user's inventory. + +### 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. + +```java +// In 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 +``` + +#### 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. + +```java +// In 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 +``` + +### 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. + +```java +// In 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 +``` + +### 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. + +```java +// In 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 +``` + +Non-`RecipeBuilder` Builders +---------------------------- + +Some recipe builders do not implement `RecipeBuilder` due to lacking features used by all previously mentioned recipes. + +### 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. + +```java +// In 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 +``` + +### 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. + +```java +// In 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 +``` + +### 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`. + +```java +// In RecipeProvider#buildRecipes(writer) +SpecialRecipeBuilder.special(dynamicRecipeSerializer) + .save(writer, name); // Add data to builder +``` + +Conditional Recipes +------------------- + +[Conditional recipes][conditional] can also be data generated via `ConditionalRecipe$Builder`. The builder can be obtained using `#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. + +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 +// 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 + +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 +// In ConditionalRecipe$Builder#addCondition +( + // If either 'examplemod:example_item' + // OR 'examplemod:example_item2' exists + // AND + // NOT FALSE + + // Methods are defined by IConditionBuilder + and( + or( + itemExists("examplemod", "example_item"), + itemExists("examplemod", "example_item2") + ), + not( + FALSE() + ) + ) +) +``` + +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`. + +!!! tip + `FinishedRecipe`s are flexible enough that any object transformation can be data generated, not just items. + +[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-CN/docusaurus-plugin-content-docs/current/datagen/server/tags.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/datagen/server/tags.md new file mode 100644 index 000000000..ce3db346b --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/datagen/server/tags.md @@ -0,0 +1,121 @@ +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`. + +```java +// 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(), + // Extends net.minecraftforge.common.data.BlockTagsProvider + output -> new MyBlockTagsProvider( + output, + event.getLookupProvider(), + MOD_ID, + event.getExistingFileHelper() + ) + ); +} +``` + +`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`. + +!!! 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). + +When `#tag` is called, a `TagAppender` is created which acts as a chainable consumer of elements to add to the tag: + +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 +// In some 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 + +this.tag(EXAMPLE_TAG_2) + .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 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 +:---: | :--- +`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` + +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 +//In 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. + +```java +public RecipeTypeTagsProvider(PackOutput output, CompletableFuture registries, ExistingFileHelper fileHelper) { + super(output, Registries.RECIPE_TYPE, registries, MOD_ID, fileHelper); +} +``` + +### Intrinsic Holder Tags Providers + +One special type of `TagProvider`s are `IntrinsicHolderTagsProvider`s. When creating a tag using this provider via `#tag`, the object itself can be used to add itself to the tag via `#add`. To do so, a function is provided within the constructor to turn an object into its `ResourceKey`. + +```java +// Subtype of `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#creating-custom-forge-registries diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/datastorage/_category_.json b/i18n/zh-CN/docusaurus-plugin-content-docs/current/datastorage/_category_.json new file mode 100644 index 000000000..7b4467f7c --- /dev/null +++ b/i18n/zh-CN/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-CN/docusaurus-plugin-content-docs/current/datastorage/capabilities.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/datastorage/capabilities.md new file mode 100644 index 000000000..9204511d5 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/datastorage/capabilities.md @@ -0,0 +1,177 @@ +The Capability System +===================== + +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. + +Forge adds capability support to BlockEntities, Entities, ItemStacks, Levels, and LevelChunks, which can be exposed either by attaching them through an event or by overriding the capability methods in your own implementations of the objects. This will be explained in more detail in the following sections. + +Forge-provided Capabilities +--------------------------- + +Forge provides three capabilities: `IItemHandler`, `IFluidHandler` and `IEnergyStorage` + +`IItemHandler` exposes an interface for handling inventory slots. It can be applied to BlockEntities (chests, machines, etc.), Entities (extra player slots, mob/creature inventories/bags), or ItemStacks (portable backpacks and such). It replaces the old `Container` and `WorldlyContainer` with an automation-friendly system. + +`IFluidHandler` exposes an interface for handling fluid inventories. It can also be applied to BlockEntities, Entities, or ItemStacks. + +`IEnergyStorage` exposes an interface for handling energy containers. It can be applied to BlockEntities, Entities, or ItemStacks. It is based on the RedstoneFlux API by TeamCoFH. + +Using an Existing Capability +---------------------------- + +As mentioned earlier, BlockEntities, Entities, and ItemStacks implement the capability provider feature through the `ICapabilityProvider` interface. This interface adds the method `#getCapability`, which can be used to query the capabilities present in the associated provider objects. + +In order to obtain a capability, you will need to refer it by its unique instance. In the case of the `IItemHandler`, this capability is primarily stored in `ForgeCapabilities#ITEM_HANDLER`, but it is possible to get other instance references by using `CapabilityManager#get` + +```java +public static final Capability ITEM_HANDLER = CapabilityManager.get(new CapabilityToken<>(){}); +``` + +When called, `CapabilityManager#get` provides a non-null capability for your associated type. The anonymous `CapabilityToken` allows Forge to keep a soft dependency system while still having the necessary generic information to get the correct capability. + +:::danger +Even if you have a non-null capability available to you at all times, it does not mean the capability itself is usable or registered yet. This can be checked via `Capability#isRegistered`. +::: + +The `#getCapability` method has a second parameter, of type `Direction`, which can be used to request the specific instance for that one face. If passed `null`, it can be assumed that the request comes either from within the block or from some place where the side has no meaning, such as a different dimension. In this case a general capability instance that does not care about sides will be requested instead. The return type of `#getCapability` will correspond to a `LazyOptional` of the type declared in the capability passed to the method. For the Item Handler capability, this is `LazyOptional`. If the capability is not available for a particular provider, it will return an empty `LazyOptional` instead. + +Exposing a Capability +--------------------- + +In order to expose a capability, you will first need an instance of the underlying capability type. Note that you should assign a separate instance to each object that keeps the capability, since the capability will most probably be tied to the containing object. + +In the case of `IItemHandler`, the default implementation uses the `ItemStackHandler` class, which has an optional argument in the constructor, to specify a number of slots. However, relying on the existence of these default implementations should be avoided, as the purpose of the capability system is to prevent loading errors in contexts where the capability is not present, so instantiation should be protected behind a check testing if the capability has been registered (see the remarks about `CapabilityManager#get` in the previous section). + +Once you have your own instance of the capability interface, you will want to notify users of the capability system that you expose this capability and provide a `LazyOptional` of the interface reference. This is done by overriding the `#getCapability` method, and comparing the capability instance with the capability you are exposing. If your machine has different slots based on which side is being queried, you can test this with the `side` parameter. For Entities and ItemStacks, this parameter can be ignored, but it is still possible to have side as a context, such as different armor slots on a player (`Direction#UP` exposing the player's helmet slot), or about the surrounding blocks in the inventory (`Direction#WEST` exposing the input slot of a furnace). Do not forget to fall back to `super`, otherwise existing attached capabilities will stop working. + +Capabilities must be invalidated at the end of the provider's lifecycle via `LazyOptional#invalidate`. For owned BlockEntities and Entities, the `LazyOptional` can be invalidated within `#invalidateCaps`. For non-owned providers, a runnable supplying the invalidation should be passed into `AttachCapabilitiesEvent#addListener`. + +```java +// Somewhere in your BlockEntity subclass +LazyOptional inventoryHandlerLazyOptional; + +// Supplied instance (e.g. () -> inventoryHandler) +// Ensure laziness as initialization should only happen when needed +inventoryHandlerLazyOptional = LazyOptional.of(inventoryHandlerSupplier); + +@Override +public LazyOptional getCapability(Capability cap, Direction side) { + if (cap == ForgeCapabilities.ITEM_HANDLER) { + return inventoryHandlerLazyOptional.cast(); + } + return super.getCapability(cap, side); +} + +@Override +public void invalidateCaps() { + super.invalidateCaps(); + inventoryHandlerLazyOptional.invalidate(); +} +``` + +:::tip +If only one capability is exposed on a given object, you can use `Capability#orEmpty` as an alternative to the if/else statement. + +```java +@Override +public LazyOptional getCapability(Capability cap, Direction side) { + return ForgeCapabilities.ITEM_HANDLER.orEmpty(cap, inventoryHandlerLazyOptional); +} +``` +::: + +`Item`s are a special case since their capability providers are stored on an `ItemStack`. Instead, a provider should be attached through `Item#initCapabilities`. This should hold your capabilities for the lifecycle of the stack. + +It is strongly suggested that direct checks in code are used to test for capabilities instead of attempting to rely on maps or other data structures, since capability tests can be done by many objects every tick, and they need to be as fast as possible in order to avoid slowing down the game. + +Attaching Capabilities +---------------------- + +As mentioned, attaching capabilities to existing providers, `Level`s, and `LevelChunk`s can be done using `AttachCapabilitiesEvent`. The same event is used for all objects that can provide capabilities. `AttachCapabilitiesEvent` has 5 valid generic types providing the following events: + +* `AttachCapabilitiesEvent`: Fires only for entities. +* `AttachCapabilitiesEvent`: Fires only for block entities. +* `AttachCapabilitiesEvent`: Fires only for item stacks. +* `AttachCapabilitiesEvent`: Fires only for levels. +* `AttachCapabilitiesEvent`: Fires only for level chunks. + +The generic type cannot be more specific than the above types. For example: If you want to attach capabilities to `Player`, you have to subscribe to the `AttachCapabilitiesEvent`, and then determine that the provided object is an `Player` before attaching the capability. + +In all cases, the event has a method `#addCapability` which can be used to attach capabilities to the target object. Instead of adding capabilities themselves to the list, you add capability providers, which have the chance to return capabilities only from certain sides. While the provider only needs to implement `ICapabilityProvider`, if the capability needs to store data persistently, it is possible to implement `ICapabilitySerializable` which, on top of returning the capabilities, will provide tag save/load functions. + +For information on how to implement `ICapabilityProvider`, refer to the [Exposing a Capability][expose] section. + +Creating Your Own Capability +---------------------------- + +A capability can be registered using one of two ways: `RegisterCapabilitiesEvent` or `@AutoRegisterCapability`. + +### RegisterCapabilitiesEvent + +A capability can be registered using `RegisterCapabilitiesEvent` by supplying the class of the capability type to the `#register` method. The event is [handled] on the mod event bus. + +```java +@SubscribeEvent +public void registerCaps(RegisterCapabilitiesEvent event) { + event.register(IExampleCapability.class); +} +``` + +### @AutoRegisterCapability + +A capability is registered using `@AutoRegisterCapability` by annotating the capability type. + +```java +@AutoRegisterCapability +public interface IExampleCapability { + // ... +} +``` + +Persisting LevelChunk and BlockEntity capabilities +-------------------------------------------- + +Unlike Levels, Entities, and ItemStacks, LevelChunks and BlockEntities are only written to disk when they have been marked as dirty. A capability implementation with persistent state for a LevelChunk or a BlockEntity should therefore ensure that whenever its state changes, its owner is marked as dirty. + +`ItemStackHandler`, commonly used for inventories in BlockEntities, has an overridable method `void onContentsChanged(int slot)` designed to be used to mark the BlockEntity as dirty. + +```java +public class MyBlockEntity extends BlockEntity { + + private final IItemHandler inventory = new ItemStackHandler(...) { + @Override + protected void onContentsChanged(int slot) { + super.onContentsChanged(slot); + setChanged(); + } + } + + // ... +} +``` + +Synchronizing Data with Clients +------------------------------- + +By default, capability data is not sent to clients. In order to change this, the mods have to manage their own synchronization code using packets. + +There are three different situations in which you may want to send synchronization packets, all of them optional: + +1. When the entity spawns in the level, or the block is placed, you may want to share the initialization-assigned values with the clients. +2. When the stored data changes, you may want to notify some or all of the watching clients. +3. When a new client starts viewing the entity or block, you may want to notify it of the existing data. + +Refer to the [Networking][network] page for more information on implementing network packets. + +Persisting across Player Deaths +------------------------------- + +By default, the capability data does not persist on death. In order to change this, the data has to be manually copied when the player entity is cloned during the respawn process. + +This can be done 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. + +[expose]: #exposing-a-capability +[handled]: ../concepts/events.md#creating-an-event-handler +[network]: ../networking/index.md diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/datastorage/codecs.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/datastorage/codecs.md new file mode 100644 index 000000000..0d08b3dd6 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/datastorage/codecs.md @@ -0,0 +1,441 @@ +# 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. + +## 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. + +### 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. + +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 +// Let exampleCodec represent a Codec +// Let exampleObject be a ExampleJavaObject +// Let exampleJson be a JsonElement + +// Encode Java object to regular JsonElement +exampleCodec.encodeStart(JsonOps.INSTANCE, exampleObject); + +// Encode Java object to compressed JsonElement +exampleCodec.encodeStart(JsonOps.COMPRESSED, exampleObject); + +// Decode JsonElement into Java object +// Assume JsonElement was parsed normally +exampleCodec.parse(JsonOps.INSTANCE, exampleJson); +``` + +Minecraft also provides `NbtOps` to codec NBT data stored in `Tag` instances. This can be referenced using `NbtOps#INSTANCE`. + +```java +// Let exampleCodec represent a Codec +// Let exampleObject be a ExampleJavaObject +// Let exampleNbt be a Tag + +// Encode Java object to Tag +exampleCodec.encodeStart(JsonOps.INSTANCE, exampleObject); + +// Decode Tag into Java object +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. + +```java +// Convert Tag to JsonElement +// Let exampleTag be a 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. + +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 +// Let exampleCodec represent a Codec +// Let exampleJson be a JsonElement + +// Decode JsonElement into Java object +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 */); +``` + +## 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`\*\* + +\* `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. + +\*\* `Unit` is an object used to represent `null` objects. + +### Vanilla and 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. + +:::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. +::: + +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 + +Codecs can be created for encoding and decoding any object. For understanding purposes, the equivalent encoded JSON will be shown. + +### 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` 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) { /* ... */ } + + public String s() { /* ... */ } + + public int i() { /* ... */ } + + public boolean b() { /* ... */ } +} +``` + +#### Fields + +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. + +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 -> // 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 +// Encoded SomeObject +{ + "s": "value", + "i": 5, + "b": false +} + +// Another encoded SomeObject +{ + "s": "value2", + // i is omitted, defaults to 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. + +```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 +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. + +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 +// 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 -> { // Return data result containing error on failure + try { + return DataResult.success(Integer.valueOf(s)); + } catch (NumberFormatException e) { + return DataResult.error(s + " is not an integer."); + } + }, + Integer::toString // Regular function +); +``` + +```js +// Will return 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. + +```java +public static final Codec RANGE_CODEC = Codec.intRange(0, 4); +``` + +```js +// Will be valid, inside [0, 4] +4 + +// Will error, outside [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`. + +```java +public static final Codec DEFAULT_CODEC = Codec.INT.orElse(0); // Can also be a supplied value via #orElseGet +``` + +```js +// Not an integer, defaults to 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. + +```java +public static final Codec> UNIT_CODEC = Codec.unit( + () -> ForgeRegistries.BLOCKS // Can also be a raw value +); +``` + +```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`. + +```java +// BlockPos#CODEC is a Codec +public static final Codec> LIST_CODEC = BlockPos.CODEC.listOf(); +``` + +```js +// 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 + +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 is a Codec +public static final Codec> MAP_CODEC = Codec.unboundedMap(Codec.STRING, BlockPos.CODEC); +``` + +```js +// Encoded 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) +} +``` + +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`. + +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( + Codec.INT.fieldOf("left").codec(), + Codec.STRING.fieldOf("right").codec() +); +``` + +```js +// Encoded Pair +{ + "left": 5, // fieldOf looks up 'left' key for left object + "right": "value" // fieldOf looks up 'right' key for right object +} +``` + +:::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 + +A codec for two different methods of encoding/decoding some object data can be generated from two codecs via `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. + +```java +public static final Codec> EITHER_CODEC = Codec.either( + Codec.INT, + Codec.STRING +); +``` + +```js +// Encoded Either$Left +5 + +// Encoded Either$Right +"value" +``` + +:::tip +This can be used in conjunction with a [transformer] to get a specific object from two different methods of encoding. +::: + +### 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. + +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) { /* ... */ } + + 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) { /* ... */ } + + public String s() { /* ... */ } + + public int i() { /* ... */ } + + public Codec type() { + // A registered registry object + // "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(); + } +} + +// Assume there is an IForgeRegistry> DISPATCH +public static final Codec = DISPATCH.getCodec() // Gets Codec> + .dispatch( + ExampleObject::type, // Get the codec from the specific object + Function.identity() // Get the codec from the registry + ); +``` + +```js +// Simple object +{ + "type": "string", // For StringObject + "value": "value" // Codec type is not augmented from MapCodec, needs field +} + +// Complex object +{ + "type": "complex", // For ComplexObject + + // Codec type is augmented from MapCodec, can be inlined + "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-CN/docusaurus-plugin-content-docs/current/datastorage/saveddata.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/datastorage/saveddata.md new file mode 100644 index 000000000..7ab85faca --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/datastorage/saveddata.md @@ -0,0 +1,41 @@ +Saved Data +========== + +The Saved Data (SD) system is an alternative to level capabilities that can attach data per level. + +Declaration +----------- + +Each SD implementation must subtype the `SavedData` class. There are two important methods to be aware of: + +* `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 existing data will persist. + +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`s are created and loaded from the `DimensionDataStorage`, which can be accessed by 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` takes in three arguments: a function to load NBT data into a SD and return it, a supplier to construct a new instance of the SD, and the name of the `.dat` file stored within the `data` folder for the implemented level. + +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(this::load, this::create, "example"); +``` + +To persist a SD across levels, a 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/i18n/zh-CN/docusaurus-plugin-content-docs/current/gameeffects/_category_.json b/i18n/zh-CN/docusaurus-plugin-content-docs/current/gameeffects/_category_.json new file mode 100644 index 000000000..818267c27 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/gameeffects/_category_.json @@ -0,0 +1,3 @@ +{ + "label": "Game Effects" +} \ No newline at end of file diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/gameeffects/particles.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/gameeffects/particles.md new file mode 100644 index 000000000..91a216bdc --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/gameeffects/particles.md @@ -0,0 +1,129 @@ +Particles +========= + +Particles are an effect within the game used as polish to better improve immersion. Their usefulness also requires great caution because of their methods of creation and reference. + +Creating a Particle +------------------- + +Particles are broken up between its [**client only**][sides] implementation to display the particle and its common implementation to reference the particle or sync data from the server. + +| Class | Side | Description | +| :--- | :---: | :--- | +| ParticleType | BOTH | The registry object of a particle's type definition used to reference the particle on either side | +| ParticleOptions | BOTH | A data holder used to sync information from the network or a command to the associated client(s) | +| ParticleProvider | CLIENT | A factory registered by the `ParticleType` used to construct a `Particle` from the associated `ParticleOptions`. +| Particle | CLIENT | The renderable logic to display on the associated client(s) | + +### ParticleType + +A `ParticleType` is the registry object defining what a particular particle type is and provides an available reference to the specific particle on both sides. As such, every `ParticleType` must be [registered][registration]. + +Each `ParticleType` takes in two parameters: an `overrideLimiter` which determines whether the particle renders regardless of distance, and a `ParticleOptions$Deserializer` which is used to read the sent `ParticleOptions` on the client. As the base `ParticleType` is abstract, a single method needs to be implemented: `#codec`. This represents how to encode and decode the associated `ParticleOptions` of the type. + +:::note +`ParticleType#codec` is only used within the biome codec for vanilla implementations. +::: + +In most cases, there is no need to have any particle data sent to the client. For these instances, it is easier to create a new instance of `SimpleParticleType`: an implementation of `ParticleType` and `ParticleOptions` which does not send any custom data to the client besides the type. Most vanilla implementations use `SimpleParticleType` besides redstone dust for coloring and block/item dependent particles. + +:::info +A `ParticleType` is not needed to make a particle spawn if only referenced on the client. However, it is necessary to use any of the prebuilt logic within `ParticleEngine` or spawn a particle from the server. +::: + +### ParticleOptions + +An `ParticleOptions` represents the data that each particle takes in. It is also used to send data from particles spawned via the server. All particle spawning methods take in a `ParticleOptions` such that it knows the type of the particle and the data associated with spawning one. + +`ParticleOptions` is broken down into three methods: + +| Method | Description | +| :--- | :--- | +| getType | Gets the type definition of the particle, or the `ParticleType` +| writeToNetwork | Writes the particle data to a buffer on the server to send to the client +| writeToString | Writes the particle data to a string + +These objects are either constructed on the fly as needed, or they are singletons as a result of being a `SimpleParticleType`. + +#### ParticleOptions$Deserializer + +To receive the `ParticleOptions` on the client, or to reference the data within a command, the particle data must be deserialized via `ParticleOptions$Deserializer`. Each method within `ParticleOptions$Deserializer` has a parity encoding method within `ParticleOptions`: + +| Method | ParticleOptions Encoder | Description | +| :--- | :---: | :--- | +| fromCommand | writeToString | Decodes a particle data from a string, usually from a command. | +| fromNetwork | writeToNetwork | Decodes a particle data from a buffer on the client. | + +This object, when needing to send custom particle data, is passed into the constructor of the `ParticleType`. + +### Particle + +A `Particle` provides the rendering logic needed to draw said data onto the screen. To create any `Particle`, two methods must be implemented: + +| Method | Description | +| :--- | :--- | +| render | Renders the particle onto the screen. | +| getRenderType | Gets the render type of the particle. | + +A common subclass of `Particle` to render textures is `TextureSheetParticle`. While `#getRenderType` needs to be implemented, whatever the texture sprite is set will be rendered at the particle's location. + +#### ParticleRenderType + +`ParticleRenderType` is a variation on `RenderType` which constructs the startup and teardown phase for every particle of that type and then renders them all at once via the `Tesselator`. There are six different render types a particle can be in. + +| Render Type | Description | +| :--- | :--- | +| TERRAIN_SHEET | Renders a particle whose texture is located within the available blocks. | +| PARTICLE_SHEET_OPAQUE | Renders a particle whose texture is opaque and located within the available particles. | +| PARTICLE_SHEET_TRANSLUCENT | Renders a particle whose texture is translucent and located within the available particles. | +| PARTICLE_SHEET_LIT | Same as `PARTICLE_SHEET_OPAQUE` except without using the particle shader. | +| CUSTOM | Provides setup for blending and depth mask but provides no rendering functionality as that would be implemented within `Particle#render`. | +| NO_RENDER | The particle will never render. | + +Implementing a custom render type will be left as an exercise to the reader. + +### ParticleProvider + +Finally, a particle is usually created via an `ParticleProvider`. A factory has a single method `#createParticle` which is used to create a particle given the particle data, client level, position, and movement delta. Since a `Particle` is not beholden to any particular `ParticleType`, it can be reused in different factories as necessary. + +An `ParticleProvider` must be registered by subscribing to the `RegisterParticleProvidersEvent` on the **mod event bus**. Within the event, the factory can be registered via `#registerSpecial` by supplying an instance of the factory to the method. + +:::danger +`RegisterParticleProvidersEvent` should only be called on the client and thus sided off in some isolated client class, referenced by either `DistExecutor` or `@EventBusSubscriber`. +::: + +#### ParticleDescription, SpriteSet, and SpriteParticleRegistration + +There are three particle render types that cannot use the above method of registration: `PARTICLE_SHEET_OPAQUE`, `PARTICLE_SHEET_TRANSLUCENT`, and `PARTICLE_SHEET_LIT`. This is because all three of these particle render types use a sprite set that is loaded by the `ParticleEngine` directly. As such, the textures supplied must be obtained and registered through a different method. This will assume your particle is a subtype of `TextureSheetParticle` as that is the only vanilla implementation for this logic. + +To add a texture to a particle, a new JSON file must be added to `assets//particles`. This is known as the `ParticleDescription`. The name of this file will represent the registry name of the `ParticleType` the factory is being attached to. Each particle JSON is an object. The object stores a single key `textures` which holds an array of `ResourceLocation`s. Any `:` texture represented here will point to a texture at `assets//textures/particle/.png`. + +```js +{ + "textures": [ + // Will point to a texture located in + // assets/mymod/textures/particle/particle_texture.png + "mymod:particle_texture", + // Textures should by ordered by drawing order + // e.g. particle_texture will render first, then particle_texture2 + // after some time + "mymod:particle_texture2" + ] +} +``` + +To reference a particle texture, the subtype of `TextureSheetParticle` should either take in an `SpriteSet` or a `TextureAtlasSprite` obtained from `SpriteSet`. `SpriteSet` holds a list of textures which refer to the sprites as defined by our `ParticleDescription`. `SpriteSet` has two methods, both of which grab a `TextureAtlasSprite` in different methods. The first method takes in two integers. The backing implementation allows the sprite to have a texture change as it ages. The second method takes in a `Random` instance to get a random texture from the sprite set. The sprite can be set within `TextureSheetParticle` by using one of the helper methods that takes in the `SpriteSet`: `#pickSprite` which uses the random method of picking a texture, and `#setSpriteFromAge` which uses the percentage method of two integers to pick the texture. + +To register these particle textures, a `SpriteParticleRegistration` needs to be supplied to the `RegisterParticleProvidersEvent#registerSpriteSet` method. This method takes in an `SpriteSet` holding the associated sprite set for the particle and creates an `ParticleProvider` to create the particle. The simplest method of implementation can be done by implementing `ParticleProvider` on some class and having the constructor take in an `SpriteSet`. Then the `SpriteSet` can be passed to the particle as normal. + +:::note +If you are registering a `TextureSheetParticle` subtype which only contains one texture, then you can supply a `ParticleProvider$Sprite` instead to the `#registerSprite` method, which has essentially the same functional interface method as `ParticleProvider`. +::: + +Spawning a Particle +------------------- + +Particles can be spawned from either level instance. However, each side has a specific way to spawn a particle. If on the `ClientLevel`, `#addParticle` can be called to spawn a particle or `#addAlwaysVisibleParticle` can be called to spawn a particle that is visible from any distance. If on the `ServerLevel`, `#sendParticles` can be called to send a packet to the client to spawn the particle. Calling the two `ClientLevel` methods on the server will result in nothing. + +[sides]: ../concepts/sides.md +[registration]: ../concepts/registries.md#methods-for-registering diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/gameeffects/sounds.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/gameeffects/sounds.md new file mode 100644 index 000000000..070f108b9 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/gameeffects/sounds.md @@ -0,0 +1,111 @@ +Sounds +====== + +Terminology +----------- + +| Term | Description | +|----------------|----------------| +| Sound Events | Something that triggers a sound effect. Examples include `minecraft:block.anvil.hit` or `botania:spreader_fire`. | +| Sound Category | The category of the sound, for example `player`, `block` or simply `master`. The sliders in the sound settings GUI represent these categories. | +| Sound File | The literal file on disk that is played: an .ogg file. | + +`sounds.json` +------------- + +This JSON defines sound events, and defines which sound files they play, the subtitle, etc. Sound events are identified with [`ResourceLocation`][loc]s. `sounds.json` should be located at the root of a resource namespace (`assets//sounds.json`), and it defines sound events in that namespace (`assets//sounds.json` defines sound events in the namespace `namespace`.). + +A full specification is available on the vanilla [wiki][], but this example highlights the important parts: + +```js +{ + "open_chest": { + "subtitle": "mymod.subtitle.open_chest", + "sounds": [ "mymod:open_chest_sound_file" ] + }, + "epic_music": { + "sounds": [ + { + "name": "mymod:music/epic_music", + "stream": true + } + ] + } +} +``` + +Underneath the top-level object, each key corresponds to a sound event. Note that the namespace is not given, as it is taken from the namespace of the JSON itself. Each event specifies a localization key to be shown when subtitles are enabled. Finally, the actual sound files to be played are specified. Note that the value is an array; if multiple sound files are specified, the game will randomly choose one to play whenever the sound event is triggered. + +The two examples represent two different ways to specify a sound file. The [wiki] has precise details, but generally, long sound files such as background music or music discs should use the second form, because the "stream" argument tells Minecraft to not load the entire sound file into memory but to stream it from disk. The second form can also specify the volume, pitch, and weight of a sound file. + +In all cases, the path to a sound file for namespace `namespace` and path `path` is `assets//sounds/.ogg`. Therefore `mymod:open_chest_sound_file` points to `assets/mymod/sounds/open_chest_sound_file.ogg`, and `mymod:music/epic_music` points to `assets/mymod/sounds/music/epic_music.ogg`. + +A `sounds.json` can be [data generated][datagen]. + +Creating Sound Events +--------------------- + +In order to reference sounds on the server, a `SoundEvent` holding a corresponding entry in `sounds.json` must be created. This `SoundEvent` must then be [registered][registration]. Normally, the location used to create a sound event should be set as it's registry name. + +The `SoundEvent` acts as a reference to the sound and is passed around to play them. If a mod has an API, it should expose its `SoundEvent`s in the API. + +:::note +As long as a sound is registered within the `sounds.json`, it can still be referenced on the logical client regardless of whether there is a referencing `SoundEvent`. +::: + +Playing Sounds +-------------- + +Vanilla has lots of methods for playing sounds, and it is unclear which to use at times. + +Note that each takes a `SoundEvent`, the ones registered above. Additionally, the terms *"Server Behavior"* and *"Client Behavior"* refer to the respective [**logical** side][sides]. + +### `Level` + +1. `playSound(Player, BlockPos, SoundEvent, SoundSource, volume, pitch)` + - Simply forwards to [overload (2)](#level-playsound-pxyzecvp), adding 0.5 to each coordinate of the `BlockPos` given. + +2. `playSound(Player, double x, double y, double z, SoundEvent, SoundSource, volume, pitch)` + - **Client Behavior**: If the passed in player is *the* client player, plays the sound event to the client player. + - **Server Behavior**: Plays the sound event to everyone nearby **except** the passed in player. Player can be `null`. + - **Usage**: The correspondence between the behaviors implies that these two methods are to be called from some player-initiated code that will be run on both logical sides at the same time: the logical client handles playing it to the user, and the logical server handles everyone else hearing it without re-playing it to the original user. They can also be used to play any sound in general at any position server-side by calling it on the logical server and passing in a `null` player, thus letting everyone hear it. + +3. `playLocalSound(double x, double y, double z, SoundEvent, SoundSource, volume, pitch, distanceDelay)` + - **Client Behavior**: Just plays the sound event in the client level. If `distanceDelay` is `true`, then delays the sound based on how far it is from the player. + - **Server Behavior**: Does nothing. + - **Usage**: This method only works client-side, and thus is useful for sounds sent in custom packets, or other client-only effect-type sounds. Used for thunder. + +### `ClientLevel` + +1. `playLocalSound(BlockPos, SoundEvent, SoundSource, volume, pitch, distanceDelay)` + - Simply forwards to `Level`'s [overload (3)](#level-playsound-xyzecvpd), adding 0.5 to each coordinate of the `BlockPos` given. + +### `Entity` + +1. `playSound(SoundEvent, volume, pitch)` + - Forwards to `Level`'s [overload (2)](#level-playsound-pxyzecvp), passing in `null` as the player. + - **Client Behavior**: Does nothing. + - **Server Behavior**: Plays the sound event to everyone at this entity's position. + - **Usage**: Emitting any sound from any non-player entity server-side. + +### `Player` + +1. `playSound(SoundEvent, volume, pitch)` (overriding the one in [`Entity`](#entity-playsound-evp)) + - Forwards to `Level`'s [overload (2)](#level-playsound-pxyzecvp), passing in `this` as the player. + - **Client Behavior**: Does nothing, see override in [`LocalPlayer`](#localplayer-playsound-evp). + - **Server Behavior**: Plays the sound to everyone nearby *except* this player. + - **Usage**: See [`LocalPlayer`](#localplayer-playsound-evp). + +### `LocalPlayer` + +1. `playSound(SoundEvent, volume, pitch)` (overriding the one in [`Player`](#player-playsound-evp)) + - Forwards to `Level`'s [overload (2)](#level-playsound-pxyzecvp), passing in `this` as the player. + - **Client Behavior**: Just plays the Sound Event. + - **Server Behavior**: Method is client-only. + - **Usage**: Just like the ones in `Level`, these two overrides in the player classes seem to be for code that runs together on both sides. The client handles playing the sound to the user, while the server handles everyone else hearing it without re-playing to the original user. + +[loc]: ../concepts/resources.md#resourcelocation +[wiki]: https://minecraft.wiki/w/Sounds.json +[datagen]: ../datagen/client/sounds.md +[registration]: ../concepts/registries.md#methods-for-registering +[sides]: ../concepts/sides.md diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/gettingstarted/_category_.json b/i18n/zh-CN/docusaurus-plugin-content-docs/current/gettingstarted/_category_.json new file mode 100644 index 000000000..da8a48332 --- /dev/null +++ b/i18n/zh-CN/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-CN/docusaurus-plugin-content-docs/current/gettingstarted/index.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/gettingstarted/index.md new file mode 100644 index 000000000..e3f8a5dac --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/gettingstarted/index.md @@ -0,0 +1,127 @@ +# Getting Started with Forge + +:::caution +Please note that this documentation may not be up to date considering the recent creation of NeoForged. + +Until NeoForged releases its first version, you should refer to Forge documentation. The Forge documentation for 1.20 has been archived here. +::: + +If you have never made a Forge mod before, this section will provide the minimum amount of information needed to setup a Forge development environment. The rest of the documentation is about where to go from here. + +## Prerequisites + +- An installation of the Java 17 Development Kit (JDK) and 64-bit Java Virtual Machine (JVM). Forge recommends and officially supports [Eclipse Temurin][jdk]. + +:::caution + +Make sure you are using a 64-bit JVM. One way of checking is to run `java -version` in a terminal. Using a 32-bit JVM will cause some problems when using [ForgeGradle]. + +::: + +- Familiarity with an Integrated Development Environment (IDE). + - It is recommended to use an IDE with Gradle integration. + +## From Zero to Modding + +1. Download the Mod Developer Kit (MDK) from the [Forge file site][files] by clicking 'Mdk' followed by the 'Skip' button in the top right after waiting for a period of time. It is recommended to download the latest version of Forge whenever possible. +2. Extract the downloaded MDK into an empty directory. This will be your mod's directory, which should now contain some gradle files and a `src` subdirectory containing the example mod. + +:::info + +A number of files can be reused across different mods. These files are: + +- the `gradle` subdirectory +- `build.gradle` +- `gradlew` +- `gradlew.bat` +- `settings.gradle` + +The `src` subdirectory does not need to be copied across workspaces; however, you may need to refresh the Gradle project if the java (`src/main/java`) and resource (`src/main/resources`) are created later. +::: + +3. Open your selected IDE: + - Forge only explicitly supports development on Eclipse and IntelliJ IDEA, but there are additional run configurations for Visual Studio Code. Regardless, any environment, from Apache NetBeans to Vim / Emacs, can be used. + - Eclipse and IntelliJ IDEA's Gradle integration, both installed and enabled by default, will handle the rest of the initial workspace setup on import or open. This includes downloading the necessary packages from Mojang, MinecraftForge, etc. The 'Gradle for Java' plugin is needed for Visual Studio Code to do the same. + - Gradle will need to be invoked to re-evaluate the project for almost all changes to its associated files (e.g., `build.gradle`, `settings.gradle`, etc.). Some IDEs come with 'Refresh' buttons to do this; however, it can be done through the terminal via `gradlew`. +4. Generate run configurations for your selected IDE: + - **Eclipse**: Run the `genEclipseRuns` task. + - **IntelliJ IDEA**: Run the `genIntellijRuns` task. If a "module not specified" error occurs, set the [`ideaModule` property][config] to your 'main' module (typically `${project.name}.main`). + - **Visual Studio Code**: Run the `getVSCodeRuns` task. + - **Other IDEs**: You can run the configurations directly using `gradle run*` (e.g., `runClient`, `runServer`, `runData`, `runGameTestServer`). These can also be used with the supported IDEs. + +## Customizing Your Mod Information + +Edit the `build.gradle` file to customize how your mod is built (e.g., file name, artifact version, etc.). + +:::danger +Do **not** edit the `settings.gradle` unless you know what you are doing. The file specifies the repository that [ForgeGradle] is uploaded to. +::: + +### Recommended `build.gradle` Customizations + +#### Mod Id Replacement + +Replace all occurrences of `examplemod`, including [`mods.toml` and the main mod file][modfiles] with the mod id of your mod. This also includes changing the name of the file you build by setting `base.archivesName` (this is typically set to your mod id). + +```gradle +// In some build.gradle +base.archivesName = 'mymod' +``` + +#### Group Id + +The `group` property should be set to your [top-level package][packaging], which should either be a domain you own or your email address: + +| Type | Value | Top-Level Package | +| :-------: | :---------------: | :------------------ | +| Domain | example.com | `com.example` | +| Subdomain | example.github.io | `io.github.example` | +| Email | example@gmail.com | `com.gmail.example` | + +```gradle +// In some build.gradle +group = '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: + +```text +com +- example (top-level package specified in group property) + - mymod (the mod id) + - MyMod.java (renamed ExampleMod.java) +``` + +#### Version + +Set the `version` property to the current version of your mod. We recommend using a [variation of Maven versioning][mvnver]. + +```gradle +// In some build.gradle +version = '1.20-1.0.0.0' +``` + +### Additional Configurations + +Additional configurations can be found on the [ForgeGradle] docs. + +## Building and Testing Your Mod + +1. To build your mod, run `gradlew build`. This will output a file in `build/libs` with the name `[archivesBaseName]-[version].jar`, by default. This file can be placed in the `mods` folder of a Forge-enabled Minecraft setup or distributed. +1. 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 run directory (default 'run') 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. +1. If you are running a dedicated server, whether through the run configuration or `gradlew runServer`, the server will initially 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, which can then be accessed via a direct connect to `localhost`. + +:::tip + +You should always test your mod in a dedicated server environment. This includes [client-only mods][client] as they should not do anything when loaded on the server. + +::: + +[jdk]: https://adoptium.net/temurin/releases?version=17 "Eclipse Temurin 17 Prebuilt Binaries" +[ForgeGradle]: https://docs.neoforged.net/neogradle/docs/ +[files]: https://files.minecraftforge.net "Forge Files distribution site" +[config]: https://docs.neoforged.net/neogradle/docs/configuration/runs +[modfiles]: ./modfiles.md +[packaging]: ./structuring.md#packaging +[mvnver]: ./versioning.md +[client]: ../concepts/sides.md#writing-one-sided-mods diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/gettingstarted/modfiles.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/gettingstarted/modfiles.md new file mode 100644 index 000000000..aabf06118 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/gettingstarted/modfiles.md @@ -0,0 +1,167 @@ +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. + +mods.toml +--------- + +The `mods.toml` file defines the metadata of your mod(s). It also contains additional information that is displayed within the 'Mods' menu and how your mod(s) should be loaded into the game. + +The file uses the [Tom's Obvious Minimal Language, or TOML][toml], format. The file must be stored under the `META-INF` folder in the resource directory of the source set you are using (`src/main/resources/META-INF/mods.toml` for the `main` source set). A `mods.toml` file may look something like this: + +```toml +modLoader="javafml" +loaderVersion="[46,)" + +license="All Rights Reserved" +issueTrackerURL="https://github.com/MinecraftForge/MinecraftForge/issues" +showAsResourcePack=false + +[[mods]] + modId="examplemod" + version="1.0.0.0" + displayName="Example Mod" + updateJSONURL="https://files.minecraftforge.net/net/minecraftforge/forge/promotions_slim.json" + displayURL="https://minecraftforge.net" + logoFile="logo.png" + credits="I'd like to thank my mother and father." + authors="Author" + description=''' + Lets you craft dirt into diamonds. This is a traditional mod that has existed for eons. It is ancient. The holy Notch created it. Jeb rainbowfied it. Dinnerbone made it upside down. Etc. + ''' + displayTest="MATCH_VERSION" + +[[dependencies.examplemod]] + modId="forge" + mandatory=true + versionRange="[46,)" + ordering="NONE" + side="BOTH" + +[[dependencies.examplemod]] + modId="minecraft" + mandatory=true + versionRange="[1.20]" + ordering="NONE" + side="BOTH" +``` + +`mods.toml` is broken 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. Each of the properties associated with the `mods.toml` file will be explained below, where `required` means that a value must be specified or an exception will be thrown. + +### 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. Forge provides the Java loader `"javafml"` and low/no code loader `"lowcodefml"`. | `"javafml"` +`loaderVersion` | string | **mandatory** | The acceptable version range of the language loader, expressed as a [Maven Version Range][mvr]. For `javafml` and `lowcodefml`, the version is the major version of the Forge version. | `"[46,)"` +`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. | `"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. | `true` +`services` | array | `[]` | An array of services your mod **uses**. This is consumed as part of the created module for the mod from Forge's implementation of the Java Platform Module System. | `["net.minecraftforge.forgespi.language.IModLanguageProvider"]` +`properties` | table | `{}` | A table of substitution properties. This is used by `StringSubstitutor` to replace `${file.}` with its corresponding value. This is currently only used to replace the `version` in the [mod-specific properties][modsp]. | `{ "example" = "1.2.3" }` referenced by `${file.example}` +`issueTrackerURL` | string | *nothing* | A URL representing the place to report and track issues with the mod(s). | `"https://forums.minecraftforge.net/"` + +:::note +The `services` property is functionally equivalent to specifying the [`uses` directive in a module][uses], which allows [*loading*][serviceload] a service of a given type. +::: + +### 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). | `"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. | `"example"` +`version` | string | `"1"` | The version of the mod, preferably in a [variation of Maven versioning][mvnver]. 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). | `"1.20-1.0.0.0"` +`displayName` | string | value of `modId` | The pretty name of the mod. Used when representing the mod on a screen (e.g., mod list, mod mismatch). | `"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]. | `"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). | `"example_logo.png"` +`logoBlur` | boolean | `true` | Whether to use `GL_LINEAR*` (true) or `GL_NEAREST*` (false) to render the `logoFile`. | `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. | `"https://files.minecraftforge.net/net/minecraftforge/forge/promotions_slim.json"` +`features` | table | `{}` | See '[features]'. | `{ java_version = "17" }` +`modproperties` | table | `{}` | A table of key/values associated with this mod. Currently unused by Forge, but is mainly for use by mods. | `{ example = "value" }` +`modUrl` | string | *nothing* | A URL to the download page of the mod. Currently unused. | `"https://files.minecraftforge.net/"` +`credits` | string | *nothing* | Credits and acknowledges for the mod shown on the mod list screen. | `"The person over here and there."` +`authors` | string | *nothing* | The authors of the mod shown on the mod list screen. | `"Example Person"` +`displayURL` | string | *nothing* | A URL to the display page of the mod shown on the mod list screen. | `"https://minecraftforge.net/"` +`displayTest` | string | `"MATCH_VERSION"` | See '[sides]'. | `"NONE"` + +#### 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, Forge 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. | `"[17,)"` + +### Dependency Configurations + +Mods can specify their dependencies, which are checked by Forge before loading the mods. These configurations are created using the [array of tables][array] `[[dependencies.]]` where `modid` is the identifier of the mod the dependency is for. + +Property | Type | Default | Description | Example +:--- | :---: | :---: | :---: | :--- +`modId` | string | **mandatory** | The identifier of the mod added as a dependency. | `"example_library"` +`mandatory` | boolean | **mandatory** | Whether the game should crash when this dependency is not met. | `true` +`versionRange` | string | `""` | The acceptable version range of the language loader, expressed as a [Maven Version Range][mvr]. An empty string matches any version. | `"[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"` | `"AFTER"` +`side` | string | `"BOTH"` | The [physical side][dist] the dependency must be present on: `"CLIENT"`, `"SERVER"`, or `"BOTH"`.| `"CLIENT"` +`referralUrl` | string | *nothing* | A URL to the download page of the dependency. Currently unused. | `"https://library.example.com/"` + +:::danger +The `ordering` of two mods may cause a crash due to a cyclic dependency: for example, mod A must load `"BEFORE"` mod B and mod B `"BEFORE"` mod A. +::: + +Mod Entrypoints +--------------- + +Now that the `mods.toml` is filled out, we need to provide an entrypoint to being programming 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` and `@Mod` + +`javafml` is a language loader provided by Forge 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], [adding `DeferredRegister`s][registration]) can be specified within the constructor of the class. The mod bus can be obtained from `FMLJavaModLoadingContext`. + +```java +@Mod("examplemod") // Must match mods.toml +public class Example { + + public Example() { + // Initialize logic here + var modBus = FMLJavaModLoadingContext.get().getModEventBus(); + + // ... + } +} +``` + +### `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. + +[toml]: https://toml.io/ +[mvr]: https://maven.apache.org/enforcer/enforcer-rules/versionRanges.html +[spdx]: https://spdx.org/licenses/ +[modsp]: #mod-specific-properties +[uses]: https://docs.oracle.com/javase/specs/jls/se17/html/jls-7.html#jls-7.7.3 +[serviceload]: https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/ServiceLoader.html#load(java.lang.Class) +[array]: https://toml.io/en/v1.0.0#array-of-tables +[mvnver]: ./versioning.md +[multiline]: https://toml.io/en/v1.0.0#string +[update]: ../misc/updatechecker.md +[features]: #features +[sides]: ../concepts/sides.md#writing-one-sided-mods +[dist]: ../concepts/sides.md#different-kinds-of-sides +[events]: ../concepts/events.md +[registration]: ../concepts/registries.md#deferredregister diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/gettingstarted/structuring.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/gettingstarted/structuring.md new file mode 100644 index 000000000..641015478 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/gettingstarted/structuring.md @@ -0,0 +1,83 @@ +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 Forge 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 +--------- + +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 // 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. + +``` +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 +``` + +As such, your top level package should be something that you own: a domain, email address, a subdomain of where your 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. + +Type | Value | Top-Level Package +:---: | :---: | :--- +Domain | example.com | `com.example` +Subdomain | example.github.io | `io.github.example` +Email | 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. + +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: + +* **Group By Function**: Make subpackages for classes with a common purpose. For example, blocks can be under `block` or `blocks`, entities under `entity` or `entities`, etc. Mojang uses this structure with the singular version of the word. +* **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 + +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 while code only on the dedicated server should go in a `server` package. + +However, 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. As such, having a dedicated package would provide a decent sanity check to verify you are not reaching across sides within your mod. + +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: + +* An `Item` called `PowerRing` -> `PowerRingItem`. +* A `Block` called `NotDirt` -> `NotDirtBlock`. +* A menu for an `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.). +::: + +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. While this does improve code formatting, it also avoid any weird interactions or redundancies that may occur (e.g. your event listener executing twice). + +[naming]: https://docs.oracle.com/javase/tutorial/java/package/namingpkgs.html +[datagen]: ../datagen/index.md +[sides]: ../concepts/sides.md diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/gettingstarted/versioning.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/gettingstarted/versioning.md new file mode 100644 index 000000000..2db561eb4 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/gettingstarted/versioning.md @@ -0,0 +1,57 @@ +Versioning +========== + +In general projects, [semantic versioning][semver] is often used (which has the format `MAJOR.MINOR.PATCH`). However, in the case of modding it may be more beneficial to use the format `MCVERSION-MAJORMOD.MAJORAPI.MINOR.PATCH` to be able to differentiate between world-breaking and API-breaking changes of a mod. + +:::caution +Forge uses [Maven version ranges][cmpver] to compare version strings, which is not fully compatible with the Semantic Versioning 2.0.0 spec, such as the 'prerelease' tag. +::: + +Examples +-------- + +Here is a list of examples that can increment the various variables. + +* `MCVERSION` + * Always matches the Minecraft version the mod is for. +* `MAJORMOD` + * Removing items, blocks, block entities, etc. + * Changing or removing previously existing mechanics. + * Updating to a new Minecraft version. +* `MAJORAPI` + * Changing the order or variables of enums. + * Changing return types of methods. + * Removing public methods altogether. +* `MINOR` + * Adding items, blocks, block entities, etc. + * Adding new mechanics. + * Deprecating public methods. (This is not a `MAJORAPI` increment since it doesn't break an API.) +* `PATCH` + * Bugfixes. + +When incrementing any variable, all lesser variables should reset to `0`. For instance, if `MINOR` would increment, `PATCH` would become `0`. If `MAJORMOD` would increment, all other variables would become `0`. + +### Work In Progress + +If you are in the initial development stage of your mod (before any official releases), the `MAJORMOD` and `MAJORAPI` should always be `0`. Only `MINOR` and `PATCH` should be updated every time you build your mod. Once you build an official release (most of the time with a stable API), you should increment `MAJORMOD` to version `1.0.0.0`. For any further development stages, refer to the [Prereleases][pre] and [Release candidates][rc] section of this document. + +### Multiple Minecraft Versions + +If the mod upgrades to a new version of Minecraft, and the old version will only receive bug fixes, the `PATCH` variable should be updated based on the version before the upgrade. If the mod is still in active development in both the old and the new version of Minecraft, it is advised to append the version to **both** build numbers. For example, if the mod is upgraded to version `3.0.0.0` due to a Minecraft version change, the old mod should also be updated to `3.0.0.0`. The old version will become, for example, version `1.7.10-3.0.0.0`, while the new version will become `1.8-3.0.0.0`. If there are no changes at all when building for a newer Minecraft version, all variables except for the Minecraft version should stay the same. + +### Final Release + +When dropping support for a Minecraft version, the last build for that version should get the `-final` suffix. This denotes that the mod will no longer be supported for the denoted `MCVERSION` and that players should upgrade to a newer version of the mod to continue receiving updates and bug fixes. + +### Pre-releases + +It is also possible to prerelease work-in-progress features, which means new features are released that are not quite done yet. These can be seen as a sort of "beta". These versions should be appended with `-betaX`, where `X` is the number of the prerelease. (This guide does not use `-pre` since, at the time of writing, it is not a valid alias for `-beta`.) Note that already released versions and versions before the initial release can not go into prerelease; variables (mostly `MINOR`, but `MAJORAPI` and `MAJORMOD` can also prerelease) should be updated accordingly before adding the `-beta` suffix. Versions before the initial release are simply work-in-progress builds. + +### Release Candidates + +Release candidates act as prereleases before an actual version change. These versions should be appended with `-rcX`, where `X` is the number of the release candidate which should, in theory, only be increased for bugfixes. Already released versions can not receive release candidates; variables (mostly `MINOR`, but `MAJORAPI` and `MAJORMOD` can also prerelease) should be updated accordingly before adding the `-rc` suffix. When releasing a release candidate as stable build, it can either be exactly the same as the last release candidate or have a few more bug fixes. + +[semver]: https://semver.org/ +[cmpver]: https://maven.apache.org/ref/3.5.2/maven-artifact/apidocs/org/apache/maven/artifact/versioning/ComparableVersion.html +[pre]: #pre-releases +[rc]: #release-candidates diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/gui/_category_.json b/i18n/zh-CN/docusaurus-plugin-content-docs/current/gui/_category_.json new file mode 100644 index 000000000..59cfbca67 --- /dev/null +++ b/i18n/zh-CN/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-CN/docusaurus-plugin-content-docs/current/gui/menus.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/gui/menus.md new file mode 100644 index 000000000..3a5f63c25 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/gui/menus.md @@ -0,0 +1,347 @@ +# Menus + +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` + +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`s must be [registered]. + +### `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]. + +```java +// For some DeferredRegister> REGISTER +public static final RegistryObject> MY_MENU = REGISTER.register("my_menu", () -> new MenuType(MyMenu::new, FeatureFlags.DEFAULT_FLAGS)); + +// In MyMenu, an AbstractContainerMenu subclass +public MyMenu(int containerId, Inventory playerInv) { + super(MY_MENU.get(), containerId); + // ... +} +``` + +:::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. +::: + +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` + +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 +// For some DeferredRegister> REGISTER +public static final RegistryObject> MY_MENU_EXTRA = REGISTER.register("my_menu_extra", () -> IForgeMenuType.create(MyMenu::new)); + +// 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` + +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 +The player can only have 100 unique menus open at once. +::: + +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) { + // ... +} +``` + +Each menu implementation must implement two methods: `#stillValid` and [`#quickMoveStack`][qms]. + +### `#stillValid` and `ContainerLevelAccess` + +`#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. + +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) { + // ... +} + +// 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 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 +Since `Container`s used by `Slot`s are deprecated by Forge in favor of using the [`IItemHandler` capability][cap], the rest of the explanation will revolve around using the capability variant: `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. + +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. + +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 +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 +// 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); + + // ... +} +``` + +#### `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`. + +```java +// 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) { + // Check if the ContainerData size is some fixed value + checkContainerDataCount(dataMultiple, 3); + + // Add data slots for handled integers + this.addDataSlots(dataMultiple); + + // ... +} +``` + +:::caution +As `ContainerData` delegates to `DataSlot`s, these are also limited to a **short** (-32768 to 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. + +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 +// 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(); + } + + /* + 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 the slot stack +} +``` + +## 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. + +:::note +`NetworkHooks#openScreen` with the `FriendlyByteBuf` parameter should only be used if a menu type was created using an [`IContainerFactory`][icf]. +::: + +#### `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. + +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 + +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`. + +The `MenuProvider` should be implemented by overriding `BlockBehaviour#getMenuProvider`. Vanilla methods use this to view the menu in spectator mode. + +```java +// In some Block subclass +@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 +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 + +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 { + // ... + + @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 +Once again, this is the simplest way to implement the logic, not the only way. +::: + +[registered]: ../concepts/registries.md#methods-for-registering +[acm]: #abstractcontainermenu +[mt]: #menutype +[qms]: #quickmovestack +[cap]: ../datastorage/capabilities.md#forge-provided-capabilities +[screen]: ./screens.md +[icf]: #icontainerfactory diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/gui/screens.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/gui/screens.md new file mode 100644 index 000000000..b1591033f --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/gui/screens.md @@ -0,0 +1,354 @@ +# 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. + +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 + +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 +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 Graphics + +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. + +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. + +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. + +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 + +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 +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 + +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. + +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 +The size of the PNG file must be specified so that the coordinates can be normalized to obtain the associated UV values. +::: + +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 Offset + +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 +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`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). + +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. + +## 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 + +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. + +Dragging an element with the mouse, implemented via `#mouseClicked` and `#mouseReleased`, provides more precisely executed logic. + +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 +Screens implement `ContainerEventHandler` through `AbstractContainerEventHandler`, which adds in the setter and getter logic for dragging and focusing children. +::: + +## 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`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 +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. + +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 + +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: + +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. + +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 + +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(); + + // Add ticking logic for EditBox in 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]. + +### 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. + +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. + +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 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 + +When a screen is closed, two methods handle the teardown: `#onClose` and `#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. + +`#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` + +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` | 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 +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. + +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. + +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 +// In some AbstractContainerScreen subclass +public MyContainerScreen(MyMenu menu, Inventory playerInventory, Component title) { + super(menu, playerInventory, title); + + this.titleLabelX = 10; + this.inventoryLabelX = 10; + + /* + * If the 'imageHeight' is changed, 'inventoryLabelY' must also be + * changed as the value depends on the 'imageHeight' value. + */ +} +``` + +### 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. + +### 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. + +```java +// In some AbstractContainerScreen subclass +@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. + +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 +// 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); +} +``` + +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 +// In some AbstractContainerScreen subclass + +// 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) { + /* + * 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); + + /* + * 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); +} +``` + +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 +// In some AbstractContainerScreen subclass +@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' + 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. +::: + +## Registering an AbstractContainerScreen + +To use an `AbstractContainerScreen` with a menu, it needs to be registered. This can be done by calling `MenuScreens#register` within the `FMLClientSetupEvent` on the [**mod event bus**][modbus]. + +```java +// Event is listened to on the mod event bus +private void clientSetup(FMLClientSetupEvent event) { + event.enqueueWork( + // Assume RegistryObject> MY_MENU + // Assume MyContainerScreen which takes in three parameters + () -> MenuScreens.register(MY_MENU.get(), MyContainerScreen::new) + ); +} +``` + +:::danger +`MenuScreens#register` is not thread-safe, so it needs to be called inside `#enqueueWork` provided by the parallel dispatch event. +::: + +[menus]: ./menus.md +[network]: ../networking/index.md +[screen]: #the-screen-subtype +[argb]: https://en.wikipedia.org/wiki/RGBA_color_model#ARGB32 +[component]: ../concepts/internationalization.md#translatablecontents +[keymapping]: ../misc/keymappings.md#inside-a-gui +[modbus]: ../concepts/events.md#mod-event-bus diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/items/bewlr.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/items/bewlr.md new file mode 100644 index 000000000..fcabedad8 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/items/bewlr.md @@ -0,0 +1,36 @@ +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`. + +Using BlockEntityWithoutLevelRenderer +-------------------------- + +BlockEntityWithoutLevelRenderer allows you to render your item using `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. + +:::note +`Block`s also render using a BEWLR if `Block#getRenderShape` is set to `RenderShape#ENTITYBLOCK_ANIMATED`. +::: + +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() { + + @Override + public BlockEntityWithoutLevelRenderer getCustomRenderer() { + return myBEWLRInstance; + } + }); +} +``` + +:::caution +Each mod should only have one instance of a custom BEWLR. +::: + +That is it, no additional setup is necessary to use a BEWLR. diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/items/index.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/items/index.md new file mode 100644 index 000000000..2b592394d --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/items/index.md @@ -0,0 +1,72 @@ +Items +===== + +Along with blocks, items are a key component of most mods. While blocks make up the level around you, items exist within inventories. + +Creating an Item +---------------- + +### Basic Items + +Basic items that need no special functionality (think sticks or sugar) do not need custom classes. You can create an item by instantiating the `Item` class with an `Item$Properties` object. This `Item$Properties` object can be made via the constructor and customized by calling its methods. For instance: + +| Method | Description | +|:------------------:|:----------------------------------------------| +| `requiredFeatures` | Sets the required `FeatureFlag`s needed to see this item in the `CreativeModeTab` it is added to. | +| `durability` | Sets the maximum damage value for this item. If it is over `0`, two item properties "damaged" and "damage" are added. | +| `stacksTo` | Sets the maximum stack size. You cannot have an item that is both damageable and stackable. | +| `setNoRepair` | Makes this item impossible to repair, even if it is damageable. | +| `craftRemainder` | Sets this item's container item, the way that lava buckets give you back an empty bucket when they are used. | + +The above methods are chainable, meaning they `return this` to facilitate calling them in series. + +### Advanced Items + +Setting the properties of an item as above only works for simple items. If you want more complicated items, you should subclass `Item` and override its methods. + +## Creative Tabs + +An item can be added to a `CreativeModeTab` via `BuildCreativeModeTabContentsEvent` on the [mod event bus][modbus]. An item(s) can be added without any additional configurations via `#accept`. + +```java +// Registered on the MOD event bus +// Assume we have RegistryObject and RegistryObject called ITEM and BLOCK +@SubscribeEvent +public void buildContents(BuildCreativeModeTabContentsEvent event) { + // Add to ingredients tab + if (event.getTabKey() == CreativeModeTabs.INGREDIENTS) { + event.accept(ITEM); + event.accept(BLOCK); // Takes in an ItemLike, assumes block has registered item + } +} +``` + +You can also enable or disable items being added through a `FeatureFlag` in the `FeatureFlagSet` or a boolean determining whether the player has permissions to see operator creative tabs. + +### Custom Creative Tabs + +A custom `CreativeModeTab` must be [registered][registering]. The builder can be created via `CreativeModeTab#builder`. The tab can set the title, icon, default items, and a number of other properties. In addition, Forge provides additional methods to customize the tab's image, label and slot colors, where the tab should be ordered, etc. + +```java +// Assume we have a DeferredRegister called REGISTRAR +public static final RegistryObject EXAMPLE_TAB = REGISTRAR.register("example", () -> CreativeModeTab.builder() + // Set name of tab to display + .title(Component.translatable("item_group." + MOD_ID + ".example")) + // Set icon of creative tab + .icon(() -> new ItemStack(ITEM.get())) + // Add default items to tab + .displayItems((params, output) -> { + output.accept(ITEM.get()); + output.accept(BLOCK.get()); + }) + .build() +); +``` + +Registering an Item +------------------- + +Items must be [registered][registering] to function. + +[modbus]: ../concepts/events.md#mod-event-bus +[registering]: ../concepts/registries.md#methods-for-registering diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/legacy/_category_.json b/i18n/zh-CN/docusaurus-plugin-content-docs/current/legacy/_category_.json new file mode 100644 index 000000000..da5b88dbc --- /dev/null +++ b/i18n/zh-CN/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-CN/docusaurus-plugin-content-docs/current/legacy/porting.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/legacy/porting.md new file mode 100644 index 000000000..cc7c0480b --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/legacy/porting.md @@ -0,0 +1,22 @@ +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 | [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 +[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-CN/docusaurus-plugin-content-docs/current/misc/_category_.json b/i18n/zh-CN/docusaurus-plugin-content-docs/current/misc/_category_.json new file mode 100644 index 000000000..26fda970d --- /dev/null +++ b/i18n/zh-CN/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-CN/docusaurus-plugin-content-docs/current/misc/components.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/misc/components.md new file mode 100644 index 000000000..296ea9db9 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/misc/components.md @@ -0,0 +1,110 @@ +Text Components +================== + +A `Component` is a holder for text which can be formatted and chained with other components via its subtype `MutableComponent`. A component can be created using one of the available static helpers: + +| Method Name | Description | +|----------------|------------------------------------------------------------------------------------------------------------------------------------| +| `literal` | it creates a component which simply wraps the passed in text. | +| `nullToEmpty` | it's the same as `#literal` except it creates an empty component if null has been passed | +| `translatable` | it creates a component which is represented as localized text to user, read [internationalization] for more details. | +| `empty` | it creates an empty component | +| `keybind` | it creates a component which is represented as the name of current keyboard key of the passed [key mapping][keymapping]. | +| `nbt` | it creates a component for representing nbt data specified by `path` inside `dataSource` | +| `score` | it creates a component for representing the `objective`'s score of an entity specified by the [entity selector][selectors] `name`. | +| `selector` | it creates a component for displaying the list of names of entities selected by the [entity selector][selectors] `pattern`. | + +A component's text contents are represented by `ComponentContents`. Notably, the subtype `TranslatableContents` not only supports [localization][internationalization] but also [text formatting][formatting]. + +Applying Style +-------------- + +Components can be formatted (e.g., bold, click actions, color) via `Style`s. `Style`s are immutable, creating a new `Style` each time when modified. The empty style `Style#EMPTY` can be used as a base for configuration. + +Multiple styles can be merged together with `#applyTo(Style other)`; `other` will override all non-configured of the current object. + +After configuring a style, it can be applied to a component with either `MutableComponent#setStyle` for overwriting, or `#withStyle` for merging: +```java +// Creates MutableComponent wrapping literal "Hello!" +MutableComponent text = Component.literal("Hello!"); + +// Copies empty style and sets color to blue and makes it italic +Style blueItalic = Style.EMPTY + .withColor(0x0000FF) + .withItalic(true); + +// Copies empty style and sets color to red +Style red = Style.EMPTY + .withColor(0xFF0000); + +// Copies empty style and makes it bold +Style bold = Style.EMPTY + .withBold(true); + +// Copies empty style and makes it both underlined and strikethrough +Style doubleLines = Style.EMPTY + .withUnderlined(true) + .withStrikethrough(true); + +// Sets style of the text to be blue and italic +text.setStyle(blueItalic); + +// Overwrites blue and italic style to be red, bold, underlined, and strikethrough +text.withStyle(red).withStyle(bold).withStyle(doubleLines); +``` +This creates a red, bold text with two lines: +![red_hello] + +Chaining Components +------------------- + +`MutableComponent#append` can chain multiple components together. Chained components can be retrieved with `MutableComponent#getSiblings`. + +`Component` stores its siblings like a tree and is traversed in preorder; the parent style is merged with those of its siblings. +![tree] + +The code below will create a component with the same structure in the above example: +```java +// Create text only components +MutableComponent first = Component.literal("first "); +MutableComponent second = Component.literal("second "); +MutableComponent third = Component.literal("third "); +MutableComponent fourth = Component.literal("fourth "); +MutableComponent fifth = Component.literal("fifth "); +MutableComponent sixth = Component.literal("sixth "); +MutableComponent seventh = Component.literal("seventh "); + +// Create components with style +MutableComponent red = Component.litearl("red ").withStyle(Style.EMPTY.withColor(0xFF0000)); +MutableComponent blue = Component.literal("blue ").withStyle(Style.EMPTY.withColor(0x0000FF)); +MutableComponent bold = Component.literal("bold ").withStyle(Style.EMPTY.withBold(true)); + +// Structure created components in the same way as the image +red.append(first).append(blue).append(seventh); +blue.append(second).append(third).append(bold); +bold.append(fourth).append(fifth).append(sixth); +``` +![style_annotated] + +Text Formatting +--------------- + +Text formatting is the process of inserting data as text into predefined larger text. It can be used for displaying coordinates, showing unit measurements, etc. **Format specifiers** are used for indicating where a text can be inserted. + +`TranslatableContents` allows two types of format specifiers: `%s` and `%n$s`. The component uses the second parameter onwards, denoted as `args` , for holding what object to insert in place of a format specifier. + +`%s` is replaced with elements of `args` in order they appear, i.e., the first `%s` is replaced with the first element of `args`, and so on. +`%n$s` is positional specifier; each positional specifier can denote which element in `args` will replace the specifier via the number `n`. +* Formatting `x:%s y:%s z:%s` with `[1, 2, 3]` as `args` results in `x:1 y:2 z:3` +* Formatting `Time: %1$s ms` with `17` as `args` results in `Time: 17 ms` +* Formatting `Player name: %2$s, HP: %1$s` with `[10.2, Dev]` as `args` results in `Player name: Dev, HP: 10.2` + +Any `Component` element within `args` will be transformed into a formatted text string. + +[internationalization]: ../concepts/internationalization.md +[selectors]: https://minecraft.wiki/w/Target_selectors +[red_hello]: /img/component_red_hello.png +[style_annotated]: /img/component_style_annotated.png +[formatting]: #text-formatting +[tree]: /img/component_graph.png +[keymapping]: ./keymappings.md diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/misc/config.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/misc/config.md new file mode 100644 index 000000000..e940fdb93 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/misc/config.md @@ -0,0 +1,139 @@ +Configuration +============= + +Configurations define settings and consumer preferences that can be applied to a mod instance. Forge uses a configuration system using [TOML][toml] files and read with [NightConfig][nightconfig]. + +Creating a Configuration +------------------------ + +A configuration can be created using a subtype of `IConfigSpec`. Forge implements the type via `ForgeConfigSpec` and enables its construction through `ForgeConfigSpec$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` | Creates the `ForgeConfigSpec`. +`configure` | Creates a pair of the class holding the config values and the `ForgeConfigSpec`. + +:::note +`ForgeConfigSpec$Builder#configure` is typically used with a `static` block and a class that takes in `ForgeConfigSpec$Builder` as part of its constructor to attach and hold the values: + +```java +// In some config class +ExampleConfig(ForgeConfigSpec.Builder builder) { + // Define values here in final fields +} + +// Somewhere the constructor is accessible +static { + Pair pair = new ForgeConfigSpec.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. + +### ConfigValue + +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 + +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 +// For some ForgeConfigSpec$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. + +#### 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 + +:::note +`DoubleValue`s, `IntValue`s, and `LongValue`s are range values which specify the class as `Double`, `Integer`, and `Long` respectively. +::: + +* **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 +--------------------------- + +Once a `ForgeConfigSpec` has been built, it must be registered to allow Forge 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 `ForgeConfigSpec`, and optionally a specific file name for the configuration. + +```java +// In the mod constructor with a ForgeConfigSpec 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` + +:::tip +Forge documents the [config types][type] within their codebase. +::: + +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. + +:::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. +::: + +[toml]: https://toml.io/ +[nightconfig]: https://github.com/TheElectronWill/night-config +[type]: https://github.com/MinecraftForge/MinecraftForge/blob/c3e0b071a268b02537f9d79ef8e7cd9b100db416/fmlcore/src/main/java/net/minecraftforge/fml/config/ModConfig.java#L108-L136 +[events]: ../concepts/events.md#creating-an-event-handler diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/misc/debugprofiler.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/misc/debugprofiler.md new file mode 100644 index 000000000..f59e33b56 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/misc/debugprofiler.md @@ -0,0 +1,47 @@ +# 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. + +## 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. + +:::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. +::: + +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 + +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% +[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% +``` +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. + +## 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: +```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. diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/misc/gametest.mdx b/i18n/zh-CN/docusaurus-plugin-content-docs/current/misc/gametest.mdx new file mode 100644 index 000000000..90b1f0470 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/misc/gametest.mdx @@ -0,0 +1,288 @@ +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. + +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: + +```java +public class ExampleGameTests { + @GameTest + public static void exampleTest(GameTestHelper helper) { + // Do stuff + } +} +``` + +The `@GameTest` annotation also contains members which configure how the game test should run. + +```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 +) +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. + +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 +The local variable generated by `/test pos` can specify its reference name by appending it to the end of the command: + +```bash +/test pos # Exports '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. + +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. + +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` | 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 + +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 + +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() { + // Return a collection of TestFunctions + } +} +``` + +#### TestFunction + +A `TestFunction` is the boxed information held by the `@GameTest` annotation and the method running the test. + +:::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. +::: + +### 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. + +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: + +```java +public class ExampleGameTests { + @BeforeBatch(batch = "firstBatch") + public static void beforeTest(ServerLevel level) { + // Perform setup + } + + @GameTest(batch = "firstBatch") + public static void exampleTest2(GameTestHelper helper) { + // Do stuff + } +} +``` + +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 + +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) +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`. + +```java +// In some class +public void registerTests(RegisterGameTestsEvent event) { + event.register(ExampleGameTests.class); +} + +// In 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. +::: + +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`. + +:::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. + +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. + +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 for all structures will be MODID +@GameTestHolder(MODID) +public class ExampleGameTests { + + // Class name is prepended, template name is not specified + // Template Location at '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' + @PrefixGameTestTemplate(false) + @GameTest + public static void exampleTest2(GameTestHelper helper) { /*...*/ } + + // 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) { /*...*/ } + + // 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 +------------------ + +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` | 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 +Subcommands follow the test command: `/test `. +::: + +Buildscript Configurations +-------------------------- + +Game Tests provide additional configuration settings within a buildscript (the `build.gradle` file) to run and integrate into different settings. + +### 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. + +```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`. + +
+ Important infromation on 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. + +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 +} +``` +::: +
+ + +### 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`. + +```gradle +// Inside a run configuration +property 'forge.enableGameTest', 'true' +``` + +[test]: #running-game-tests +[namespaces]: #enabling-other-namespaces +[event]: ../concepts/events.md#creating-an-event-handler +[buildscript]: ../gettingstarted/index.md#simple-buildgradle-customizations diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/misc/keymappings.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/misc/keymappings.md new file mode 100644 index 000000000..5d72a8cfe --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/misc/keymappings.md @@ -0,0 +1,157 @@ +# 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` + +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 + +// Key mapping is lazily initialized so it doesn't exist until it is registered +public static final Lazy EXAMPLE_MAPPING = Lazy.of(() -> /*...*/); + +// Event is on the mod event bus only on the physical client +@SubscribeEvent +public void registerBindings(RegisterKeyMappingsEvent event) { + event.register(EXAMPLE_MAPPING.get()); +} +``` + +## Creating a `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]. + +:::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`). +::: + +### 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. + +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 +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]. +::: + +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", // 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 +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` + +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. + +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. + +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, // 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` + +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. + +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, // 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" +) +``` + +## Checking a `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 + +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 +// Event is on the Forge event bus only on the physical client +public void onClientTick(ClientTickEvent event) { + 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 +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. +::: + +### Inside a 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`. + +`#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 +// 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); +} +``` + +:::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. +::: + +`#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 +// 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); +} +``` + +:::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. +::: + +[modbus]: ../concepts/events.md#mod-event-bus +[controls]: https://minecraft.wiki/w/Options#Controls +[tk]: ../concepts/internationalization.md#translatablecontents +[keyinput]: https://www.glfw.org/docs/3.3/input_guide.html#input_key +[forgebus]: ../concepts/events.md#creating-an-event-handler diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/misc/updatechecker.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/misc/updatechecker.md new file mode 100644 index 000000000..85d7960ce --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/misc/updatechecker.md @@ -0,0 +1,63 @@ +Forge Update Checker +==================== + +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 +--------------- + +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. + +Update JSON format +------------------ + +The JSON itself has a relatively simple format as follows: + +```js +{ + "homepage": "", + "": { + "": "", + // List all versions of your mod for the given Minecraft version, along with their changelogs + // ... + }, + "promos": { + "-latest": "", + // Declare the latest "bleeding-edge" version of your mod for the given Minecraft version + "-recommended": "", + // 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. + +- Some examples can be found here for [nocubes][], [Corail Tombstone][corail] and [Chisels & Bits 2][chisel]. + +Retrieving Update Check Results +------------------------------- + +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` | 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 +[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-CN/docusaurus-plugin-content-docs/current/networking/entities.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/networking/entities.md new file mode 100644 index 000000000..466b6cfcb --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/networking/entities.md @@ -0,0 +1,37 @@ +Entities +======== + +In addition to regular network messages, there are various other systems provided to handle synchronizing entity data. + +Spawn Data +---------- + +In general, the spawning of modded entities is handled separately, by Forge. + +:::note +This means that simply extending a vanilla entity class may not inherit all its behavior. You may need to implement certain vanilla behaviors yourself. +::: + +You can add extra data to the spawn packet Forge sends by implementing the following interface. + +### IEntityAdditionalSpawnData + +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. + +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. + +:::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. +::: + +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! + +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/i18n/zh-CN/docusaurus-plugin-content-docs/current/networking/index.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/networking/index.md new file mode 100644 index 000000000..9d88ae644 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/networking/index.md @@ -0,0 +1,20 @@ +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 + +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 are a variety of techniques provided by Forge to facilitate communication mostly built on top of [netty][]. + +The simplest, for a new mod, would be [SimpleImpl][channel], where most of the complexity of the netty system is abstracted away. It uses a message and handler style system. + +[netty]: https://netty.io "Netty Website" +[channel]: ./simpleimpl.md "SimpleImpl in Detail" diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/networking/simpleimpl.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/networking/simpleimpl.md new file mode 100644 index 000000000..88380f089 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/networking/simpleimpl.md @@ -0,0 +1,119 @@ +SimpleImpl +========== + +SimpleImpl is the name given to the packet system that revolves around the `SimpleChannel` class. Using this system is by far the easiest way to send custom data between clients and the server. + +Getting Started +--------------- + +First you need to create your `SimpleChannel` object. We recommend that you do this in a separate class, possibly something like `ModidPacketHandler`. Create your `SimpleChannel` as a static field in this class, like so: + +```java +private static final String PROTOCOL_VERSION = "1"; +public static final SimpleChannel INSTANCE = NetworkRegistry.newSimpleChannel( + new ResourceLocation("mymodid", "main"), + () -> PROTOCOL_VERSION, + PROTOCOL_VERSION::equals, + PROTOCOL_VERSION::equals +); +``` + +The first argument is a name for the channel. The second argument is a `Supplier` returning the current network protocol version. The third and fourth arguments respectively are `Predicate` checking whether an incoming connection protocol version is network-compatible with the client or server, respectively. +Here, we simply compare with the `PROTOCOL_VERSION` field directly, meaning that the client and server `PROTOCOL_VERSION`s must always match or FML will deny login. + +The Version Checker +------------------- + +If your mod does not require the other side to have a specific network channel, or to be a Forge instance at all, you should take care that you properly define your version compatibility checkers (the `Predicate` parameters) to handle additional "meta-versions" (defined in `NetworkRegistry`) that can be received by the version checker. These are: + +* `ABSENT` - if this channel is missing on the other endpoint. Note that in this case, the endpoint is still a Forge endpoint, and may have other mods. +* `ACCEPTVANILLA` - if the endpoint is a vanilla (or non-Forge) endpoint. + +Returning `false` for both means that this channel must be present on the other endpoint. If you just copy the code above, this is what it does. Note that these values are also used during the list ping compatibility check, which is responsible for showing the green check / red cross in the multiplayer server select screen. + +Registering Packets +------------------- + +Next, we must declare the types of messages that we would like to send and receive. This is done using `INSTANCE#registerMessage`, which takes 5 parameters: + +- The first parameter is the discriminator for the packet. This is a per-channel unique ID for the packet. We recommend you use a local variable to hold the ID, and then call registerMessage using `id++`. This will guarantee 100% unique IDs. +- The second parameter is the actual packet class `MSG`. +- The third parameter is a `BiConsumer` responsible for encoding the message into the provided `FriendlyByteBuf`. +- The fourth parameter is a `Function` responsible for decoding the message from the provided `FriendlyByteBuf`. +- The final parameter is a `BiConsumer>` responsible for handling the message itself. + +The last three parameters can be method references to either static or instance methods in Java. Remember that an instance method `MSG#encode(FriendlyByteBuf)` still satisfies `BiConsumer`; the `MSG` simply becomes the implicit first argument. + +Handling Packets +---------------- + +There are a couple things to highlight in a packet handler. A packet handler has both the message object and the network context available to it. The context allows access to the player that sent the packet (if on the server), and a way to enqueue thread-safe work. + +```java +public static void handle(MyMessage msg, Supplier ctx) { + ctx.get().enqueueWork(() -> { + // Work that needs to be thread-safe (most work) + ServerPlayer sender = ctx.get().getSender(); // the client that sent this packet + // Do stuff + }); + ctx.get().setPacketHandled(true); +} +``` + +Packets sent from the server to the client should be handled in another class and wrapped via `DistExecutor#unsafeRunWhenOn`. + +```java +// In Packet class +public static void handle(MyClientMessage msg, Supplier ctx) { + ctx.get().enqueueWork(() -> + // Make sure it's only executed on the physical client + DistExecutor.unsafeRunWhenOn(Dist.CLIENT, () -> () -> ClientPacketHandlerClass.handlePacket(msg, ctx)) + ); + ctx.get().setPacketHandled(true); +} + +// In ClientPacketHandlerClass +public static void handlePacket(MyClientMessage msg, Supplier ctx) { + // Do stuff +} +``` + +Note the presence of `#setPacketHandled`, which is used to tell the network system that the packet has successfully completed handling. + +:::caution +As of Minecraft 1.8 packets are by default handled on the network thread. + +That means that your handler can _not_ interact with most game objects directly. Forge provides a convenient way to make your code execute on the main thread instead through the supplied `NetworkEvent$Context`. Simply call `NetworkEvent$Context#enqueueWork(Runnable)`, which will call the given `Runnable` on the main thread at the next opportunity. +::: + +:::caution +Be defensive when handling packets on the server. A client could attempt to exploit the packet handling by sending unexpected data. + +A common problem is vulnerability to **arbitrary chunk generation**. This typically happens when the server is trusting a block position sent by a client to access blocks and block entities. When accessing blocks and block entities in unloaded areas of the level, the server will either generate or load this area from disk, then promptly write it to disk. This can be exploited to cause **catastrophic damage** to a server's performance and storage space without leaving a trace. + +To avoid this problem, a general rule of thumb is to only access blocks and block entities if `Level#hasChunkAt` is true. +::: + +Sending Packets +--------------- + +### Sending to the Server + +There is but one way to send a packet to the server. This is because there is only ever *one* server the client can be connected to at once. To do so, we must again use that `SimpleChannel` that was defined earlier. Simply call `INSTANCE.sendToServer(new MyMessage())`. The message will be sent to the handler for its type, if one exists. + +### Sending to Clients + +Packets can be sent directly to a client using the `SimpleChannel`: `HANDLER.sendTo(new MyClientMessage(), serverPlayer.connection.getConnection(), NetworkDirection.PLAY_TO_CLIENT)`. However, this can be quite inconvenient. Forge has some convenience functions that can be used: + +```java +// Send to one player +INSTANCE.send(PacketDistributor.PLAYER.with(serverPlayer), new MyMessage()); + +// Send to all players tracking this level chunk +INSTANCE.send(PacketDistributor.TRACKING_CHUNK.with(levelChunk), new MyMessage()); + +// Send to all connected players +INSTANCE.send(PacketDistributor.ALL.noArg(), new MyMessage()); +``` + +There are additional `PacketDistributor` types available; check the documentation on the `PacketDistributor` class for more details. diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/rendering/_category_.json b/i18n/zh-CN/docusaurus-plugin-content-docs/current/rendering/_category_.json new file mode 100644 index 000000000..461293ccb --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/rendering/_category_.json @@ -0,0 +1,3 @@ +{ + "label": "Rendering" +} \ No newline at end of file diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/rendering/modelextensions/_category_.json b/i18n/zh-CN/docusaurus-plugin-content-docs/current/rendering/modelextensions/_category_.json new file mode 100644 index 000000000..528a320b6 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/rendering/modelextensions/_category_.json @@ -0,0 +1,3 @@ +{ + "label": "Model Extensions" +} \ No newline at end of file diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/rendering/modelextensions/facedata.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/rendering/modelextensions/facedata.md new file mode 100644 index 000000000..c7d01f001 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/rendering/modelextensions/facedata.md @@ -0,0 +1,117 @@ +Face Data +========= + +In a vanilla "elements" model, additional data about an element's faces can be specified at either the element level or the face level. Faces which do not specify their own face data will fall back to the element's face data or a default if no face data is specified at the element level. + +To use this extension for a generated item model, the model must be loaded through the `forge:item_layers` model loader due to the vanilla item model generator not being extended to read this additional data. + +All values of the face data are optional. + +Elements Model +-------------- + +In vanilla "elements" models, the face data applies to the face it is specified in or all faces of the element it is specified in which don't have their own face data. + +:::note +If `forge_data` is specified on a face, it will not inherit any parameters from the element-level `forge_data` declaration. +::: + +The additional data can be specified in the two ways shown in this example: +```js +{ + "elements": [ + { + "forge_data": { + "color": "0xFFFF0000", + "block_light": 15, + "sky_light": 15, + "ambient_occlusion": false + }, + "faces": { + "north": { + "forge_data": { + "color": "0xFFFF0000", + "block_light": 15, + "sky_light": 15, + "ambient_occlusion": false + }, + // ... + }, + // ... + }, + // ... + } + ] +} +``` + +Generated Item Model +-------------------- + +In item models generated using the `forge:item_layers` loader, face data is specified for each texture layer and applies to all of the geometry (front/back facing quads and edge quads). + +The `forge_data` field must be located at the top level of the model JSON, with each key-value pair associating a face data object to a layer index. + +In the following example, layer 1 will be tinted red and glow at full brightness: +```js +{ + "textures": { + "layer0": "minecraft:item/stick", + "layer1": "minecraft:item/glowstone_dust" + }, + "forge_data": { + "1": { + "color": "0xFFFF0000", + "block_light": 15, + "sky_light": 15, + "ambient_occlusion": false + } + } +} +``` + +Parameters +---------- + +### Color + +Specifying a color value with the `color` entry will apply that color as a tint to the quads. Defaults to `0xFFFFFFFF` (white, fully opaque). The color must be in the `ARGB` format packed into a 32-bit integer and can be specified as either a hexadecimal string (`"0xAARRGGBB"`) or as a decimal integer literal (JSON does not support hexadecimal integer literals). + +:::caution +The four color components are multiplied with the texture's pixels. Omitting the alpha component is equivalent to making it 0, which will make the geometry fully transparent. +::: + +This can be used as a replacement for tinting with [`BlockColor` and `ItemColor`][tinting] if the color values are constant. + +### Block and Sky Light + +Specifying a block and/or sky light value with the `block_light` and `sky_light` entry respectively will override the respective light value of the quads. Both values default to 0. The values must be in the range 0-15 (inclusive) and are treated as a minimum value for the respective light type when the face is rendered, meaning that a higher in-world value of the respective light type will override the specified value. + +The specified light values are purely client-side and affect neither the server's light level nor the brightness of surrounding blocks. + +### Ambient Occlusion + +Specifying the `ambient_occlusion` flag will configure [AO] for the quads. Defaults to `true`. The behaviour of this flag is equivalent to the top-level `ambientocclusion` flag of the vanilla format. + +![Ambient occlusion in action][ao_img] +*Ambient occlusion enabled on the left and disabled on the right, demonstrated with the Smooth Lighting graphics setting* + +:::note +If the top-level AO flag is set to false, specifying this flag as true on an element or face won't be able to override the top-level flag. +```js +{ + "ambientocclusion": false, + "elements": [ + { + "forge_data": { + "ambient_occlusion": true // Has no effect + } + } + ] +} +``` +::: + +[tinting]: ../../resources/client/models/tinting.md +[AO]: https://en.wikipedia.org/wiki/Ambient_occlusion +[ao_img]: /img/ambientocclusion_annotated.png \ No newline at end of file diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/rendering/modelextensions/rendertypes.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/rendering/modelextensions/rendertypes.md new file mode 100644 index 000000000..ad6af4173 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/rendering/modelextensions/rendertypes.md @@ -0,0 +1,81 @@ +Render Types +============ + +Adding the `render_type` entry at the top level of the JSON suggests to the loader what render type the model should use. If not specified, the loader gets to pick the render type(s) used, often falling back to the render types returned by `ItemBlockRenderTypes#getRenderLayers()`. + +Custom model loaders may ignore this field entirely. + +:::note +Since 1.19 this is preferred over the deprecated method of setting the applicable render type(s) via `ItemBlockRenderTypes#setRenderLayer()` for blocks. +::: + +Example of a model for a cutout block with the glass texture + +```js +{ + "render_type": "minecraft:cutout", + "parent": "block/cube_all", + "textures": { + "all": "block/glass" + } +} +``` + +Vanilla Values +-------------- + +The following options with the respective chunk and entity render type are supplied by Forge (`NamedRenderTypeManager#preRegisterVanillaRenderTypes()`): + +* `minecraft:solid` + * Chunk render type: `RenderType#solid()` + * Entity render type: `ForgeRenderTypes#ITEM_LAYERED_SOLID` + * Used for fully solid blocks (i.e. Stone) +* `minecraft:cutout` + * Chunk render type: `RenderType#cutout()` + * Entity render type: `ForgeRenderTypes#ITEM_LAYERED_CUTOUT` + * Used for blocks where any given pixel is either fully transparent or fully opaque (i.e. Glass Block) +* `minecraft:cutout_mipped` + * Chunk render type: `RenderType#cutoutMipped()` + * Entity render type: `ForgeRenderTypes#ITEM_LAYERED_CUTOUT` + * Chunk and entity render type differ due to mipmapping on the entity render type making items look weird + * Used for blocks where any given pixel is either fully transparent or fully opaque and the texture should be scaled down at larger distances ([mipmapping]) to avoid visual artifacts (i.e. Leaves) +* `minecraft:cutout_mipped_all` + * Chunk render type: `RenderType#cutoutMipped()` + * Entity render type: `ForgeRenderTypes#ITEM_LAYERED_CUTOUT_MIPPED` + * Used in similar cases as `minecraft:cutout_mipped` when the item representation should also have mipmapping applied +* `minecraft:translucent` + * Chunk render type: `RenderType#translucent()` + * Entity render type: `ForgeRenderTypes#ITEM_LAYERED_TRANSLUCENT` + * Used for blocks where any given pixel may be partially transparent (i.e. Stained Glass) +* `minecraft:tripwire` + * Chunk render type: `RenderType#tripwire()` + * Entity render type: `ForgeRenderTypes#ITEM_LAYERED_TRANSLUCENT` + * Chunk and entity render type differ due to the tripwire render type not being feasible as an entity render type + * Used for blocks with the special requirement of being rendered to the weather render target (i.e. Tripwire) + +Custom Values +------------- + +Custom named render types to be specified in a model can be registered in the `RegisterNamedRenderTypesEvent`. This event is fired on the mod event bus. + +A custom named render type consists of two or three components: + +* A chunk render type - any of the types in the list returned by `RenderType.chunkBufferLayers()` can be used +* A render type with the `DefaultVertexFormat.NEW_ENTITY` vertex format ("entity render type") +* A render type with the `DefaultVertexFormat.NEW_ENTITY` vertex format for use when the *Fabulous!* graphics mode is selected (optional) + +The chunk render type is used when a block using this named render type is rendered as part of the chunk geometry. +The required entity render type is used when an item using this named render type is rendered in the Fast and Fancy graphics modes (inventory, ground, item frame, etc.). +The optional entity render type is used the same way as the required entity render type when the *Fabulous!* graphics mode is selected. This render type is needed in cases where the required entity render type does not work in the *Fabulous!* graphics mode (typically only applies to translucent render types). + +```java +public static void onRegisterNamedRenderTypes(RegisterNamedRenderTypesEvent event) +{ + event.register("special_cutout", RenderType.cutout(), Sheets.cutoutBlockSheet()); + event.register("special_translucent", RenderType.translucent(), Sheets.translucentCullBlockSheet(), Sheets.translucentItemSheet()); +} +``` + +These can then be addressed in JSON as `:special_cutout` and `:special_translucent`. + +[mipmapping]: https://en.wikipedia.org/wiki/Mipmap \ No newline at end of file diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/rendering/modelextensions/transforms.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/rendering/modelextensions/transforms.md new file mode 100644 index 000000000..100ee4670 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/rendering/modelextensions/transforms.md @@ -0,0 +1,76 @@ +Root Transforms +=============== + +Adding the `transform` entry at the top level of a model JSON suggests to the loader that a transformation should be applied to all geometry right before the rotations in the [blockstate] file in the case of a block model, and before the [display transforms][displaytransform] in the case of an item model. The transformation is available through `IGeometryBakingContext#getRootTransform()` in `IUnbakedGeometry#bake()`. + +Custom model loaders may ignore this field entirely. + +The root transforms can be specified in two formats: + +1. A JSON object containing a singular `matrix` entry containing a raw transformation matrix in the form of a nested JSON array with the last row omitted (3*4 matrix, row major order). The matrix is the composition of the translation, left rotation, scale, right rotation and the transformation origin in that order. Example demonstrating the structure: + ```js + "transform": { + "matrix": [ + [ 0, 0, 0, 0 ], + [ 0, 0, 0, 0 ], + [ 0, 0, 0, 0 ] + ] + } + ``` +2. A JSON object containing any combination of the following optional entries: + * `origin`: origin point used for the rotations and scaling + * `translation`: relative translation + * `rotation` or `left_rotation`: rotation around the translated origin to be applied before scaling + * `scale`: scale relative to the translated origin + * `right_rotation` or `post_rotation`: rotation around the translated origin to be applied after scaling + +Element-wise specification +------------------------- + +If the transformation is specified as a combination of the entries mentioned in option 4, these entries will be applied in the order of `translation`, `left_rotation`, `scale`, `right_rotation`. +The transformation is moved to the specified origin as a last step. + +```js +{ + "transform": { + "origin": "center", + "translation": [ 0, 0.5, 0 ], + "rotation": { "y": 45 } + }, + // ... +} +``` + +The elements are expected to be defined as follows: + +### Origin + +The origin can be specified either as an array of 3 floating point values representing a three-dimensional vector: `[ x, y, z ]` or as one of the three default values: + +* `"corner"` (0, 0, 0) +* `"center"` (.5, .5, .5) +* `"opposing-corner"` (1, 1, 1) + +If the origin is not specified, it defaults to `"opposing-corner"`. + +### Translation + +The translation must be specified as an array of 3 floating point values representing a three-dimensional vector: `[ x, y, z ]` and defaults to (0, 0, 0) if not present. + +### Left and Right Rotation + +The rotations can be specified in any one of the following four ways: + +* Single JSON object with a single axis => rotation degree mapping: `{ "x": 90 }` +* Array of an arbitrary amount of JSON objects with the above format (applied in the order they are specified in): `[ { "x": 90 }, { "y": 45 }, { "x": -22.5 } ]` +* Array of 3 floating point values specifying the rotation in degrees around each axis: `[ 90, 180, 45 ]` +* Array of 4 floating point values specifying a quaternion directly: `[ 0.38268346, 0, 0, 0.9238795 ]` (example equals 45 degrees around the X axis) + +If the respective rotation is not specified, it will default to no rotation. + +### Scale + +The scale must be specified as an array of 3 floating point values representing a three-dimensional vector: `[ x, y, z ]` and defaults to (1, 1, 1) if not present. + +[blockstate]: https://minecraft.wiki/w/Tutorials/Models#Block_states +[displaytransform]: ../modelloaders/transform.md \ No newline at end of file diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/rendering/modelextensions/visibility.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/rendering/modelextensions/visibility.md new file mode 100644 index 000000000..d9a1f71b5 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/rendering/modelextensions/visibility.md @@ -0,0 +1,52 @@ +Part Visibility +=============== + +Adding the `visibility` entry at the top level of a model JSON allows control over the visibility of different parts of the model to decide whether they should be baked into the final [`BakedModel`][bakedmodel]. The definition of a "part" is dependent on the model loader loading this model and custom model loaders are free to ignore this entry completely. Out of the model loaders provided by Forge only the [composite model loader][composite] and the [OBJ model loader][obj] make use of this functionality. The visibility entries are specified as `"part name": boolean` entries. + +Example of a composite model with two parts, the second of which will not be baked into the final model, and two child models overriding this visibility to have only the first part and both parts visible respectively: +```js +// mycompositemodel.json +{ + "loader": "forge:composite", + "children": { + "part_one": { + "parent": "mymod:mypartmodel_one" + }, + "part_two": { + "parent": "mymod:mypartmodel_two" + } + }, + "visibility": { + "part_two": false + } +} + +// mycompositechild_one.json +{ + "parent": "mymod:mycompositemodel", + "visibility": { + "part_one": false, + "part_two": true + } +} + +// mycompositechild_two.json +{ + "parent": "mymod:mycompositemodel", + "visibility": { + "part_two": true + } +} +``` + +The visibility of a given part is determined by checking whether the model specifies a visibility for this part and, if not present, recursively checking the model's parent until either an entry is found or there is no further parent to check, in which case it defaults to true. + +This allows setups like the following where multiple models use different parts of a single composite model: + +1. A composite model specifies multiple components +2. Multiple models specify this composite model as their parent +3. These child models individually specify different visibilities for the parts + +[bakedmodel]: ../modelloaders/bakedmodel.md +[composite]: ../modelloaders/index.md/#composite-models +[obj]: ../modelloaders/index.md/#wavefront-obj-models \ No newline at end of file diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/rendering/modelloaders/bakedmodel.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/rendering/modelloaders/bakedmodel.md new file mode 100644 index 000000000..91be720fe --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/rendering/modelloaders/bakedmodel.md @@ -0,0 +1,58 @@ +`BakedModel` +============= + +`BakedModel` is the result of calling `UnbakedModel#bake` for the vanilla model loader or `IUnbakedGeometry#bake` for custom model loaders. Unlike `UnbakedModel` or `IUnbakedGeometry`, which purely represents a shape without any concept of items or blocks, `BakedModel` is not as abstract. It represents geometry that has been optimized and reduced to a form where it is (almost) ready to go to the GPU. It can also process the state of an item or block to change the model. + +In a majority of cases, it is not really necessary to implement this interface manually. One can instead use one of the existing implementations. + +### `getOverrides` + +Returns the [`ItemOverrides`][overrides] to use for this model. This is only used if this model is being rendered as an item. + +### `useAmbientOcclusion` + +If the model is rendered as a block in the level, the block in question does not emit any light, and ambient occlusion is enabled. This causes the model to be rendered with [ambient occlusion](ambocc). + +### `isGui3d` + +If the model is rendered as an item in an inventory, on the ground as an entity, on an item frame, etc., this makes the model look "flat." In GUIs, this also disables the lighting. + +### `isCustomRenderer` + +:::caution +Unless you know what you're doing, just `return false` from this and continue on. +::: + +When rendering this as an item, returning `true` causes the model to not be rendered, instead falling back to `BlockEntityWithoutLevelRenderer#renderByItem`. For certain vanilla items such as chests and banners, this method is hardcoded to copy data from the item into a `BlockEntity`, before using a `BlockEntityRenderer` to render that BE in place of the item. For all other items, it will use the `BlockEntityWithoutLevelRenderer` instance provided by `IClientItemExtensions#getCustomRenderer`. Refer to [BlockEntityWithoutLevelRenderer][bewlr] page for more information. + +### `getParticleIcon` + +Whatever texture should be used for the particles. For blocks, this shows when an entity falls on it, when it breaks, etc. For items, this shows when it breaks or when it's eaten. + +!!! important + The vanilla method with no parameters has been deprecated in favor of `#getParticleIcon(ModelData)` since model data can have an effect on how a particular model might be rendered. + +### `getTransforms` + +Deprecated in favor of implementing `#applyTransform`. The default implementation is fine if `#applyTransform` is implemented. See [Transform][transform]. + +### `applyTransform` + +See [Transform][transform]. + +### `getQuads` + +This is the main method of `BakedModel`. It returns a list of `BakedQuad`s: objects which contain the low-level vertex data that will be used to render the model. If the model is being rendered as a block, then the `BlockState` passed in is non-null. If the model is being rendered as an item, the `ItemOverrides` returned from `#getOverrides` is responsible for handling the state of the item, and the `BlockState` parameter will be `null`. + +The `Direction` passed in is used for face culling. If the block against the given side of another block being rendered is opaque, then the faces associated with that side are not rendered. If that parameter is `null`, all faces not associated with a side are returned (that will never be culled). + +The `rand` parameter is an instance of Random. + +It also takes in a non null `ModelData` instance. This can be used to define extra data when rendering the specific model via `ModelProperty`s. For example, one such property is `CompositeModel$Data`, which is used to store any additional submodel data for a model using the `forge:composite` model loader. + +Note that this method is called very often: once for every combination of non-culled face and supported block render layer (anywhere between 0 to 28 times) *per block in a level*. This method should be as fast as possible, and should probably cache heavily. + +[overrides]: ./itemoverrides.md +[ambocc]: https://en.wikipedia.org/wiki/Ambient_occlusion +[bewlr]: ../../items/bewlr.md +[transform]: ./transform.md diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/rendering/modelloaders/index.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/rendering/modelloaders/index.md new file mode 100644 index 000000000..9856d688a --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/rendering/modelloaders/index.md @@ -0,0 +1,27 @@ +Custom Model Loaders +==================== + +A "model" is simply a shape. It can be a simple cube, it can be several cubes, it can be a truncated icosidodecahedron, or anything in between. Most models you'll see will be in the vanilla JSON format. Models in other formats are loaded into `IUnbakedGeometry`s by an `IGeometryLoader` at runtime. Forge provides default implementations for WaveFront OBJ files, buckets, composite models, models in different render layers, and a reimplementation of Vanilla's `builtin/generated` item model. Most things do not care about what loaded the model or what format it's in as they are all eventually represented by an `BakedModel` in code. + +:::caution +Specifying a custom model loader through the top-level `loader` entry in a model JSON will cause the `elements` entry to be ignored unless it is consumed by the custom loader. All other vanilla entries will still be loaded and available in the unbaked `BlockModel` representation and may be consumed outside of the custom loader. +::: + +WaveFront OBJ Models +-------------------- + +Forge adds a loader for the `.obj` file format. To use these models, the JSON must reference the `forge:obj` loader. This loader accepts any model location that is in a registered namespace and whose path ends in `.obj`. The `.mtl` file should be placed in the same location with the same name as the `.obj` to be used automatically. The `.mtl` file will probably have to be manually edited to change the paths pointing to textures defined within the JSON. Additionally, the V axis for textures may be flipped depending on the external program that created the model (i.e. V = 0 may be the bottom edge, not the top). This may be rectified in the modelling program itself or done in the model JSON like so: + +```js +{ + // Add the following line on the same level as a 'model' declaration + "loader": "forge:obj", + "flip_v": true, + "model": "examplemod:models/block/model.obj", + "textures": { + // Can refer to in .mtl using #texture0 + "texture0": "minecraft:block/dirt", + "particle": "minecraft:block/dirt" + } +} +``` diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/rendering/modelloaders/itemoverrides.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/rendering/modelloaders/itemoverrides.md new file mode 100644 index 000000000..63ec2d882 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/rendering/modelloaders/itemoverrides.md @@ -0,0 +1,49 @@ +`ItemOverrides` +================== + +`ItemOverrides` provides a way for an [`BakedModel`][baked] to process the state of an `ItemStack` and return a new `BakedModel`; thereafter, the returned model replaces the old one. `ItemOverrides` represents an arbitrary function `(BakedModel, ItemStack, ClientLevel, LivingEntity, int)` → `BakedModel`, making it useful for dynamic models. In vanilla, it is used to implement item property overrides. + +### `ItemOverrides()` + +Given a list of `ItemOverride`s, the constructor copies and bakes the list. The baked overrides may be accessed with `#getOverrides`. + +### `resolve` + +This takes an `BakedModel`, an `ItemStack`, a `ClientLevel`, a `LivingEntity`, and an `int` to produce another `BakedModel` to use for rendering. This is where models can handle the state of their items. + +This should not mutate the level. + +### `getOverrides` + +Returns an immutable list containing all the [`BakedOverride`][override]s used by this `ItemOverrides`. If none are applicable, this returns the empty list. + +## `BakedOverride` + +This class represents a vanilla item override, which holds several `ItemOverrides$PropertyMatcher` for the properties on an item and a model to use in case those matchers are satisfied. They are the objects in the `overrides` array of a vanilla item JSON model: + +```js +{ + // Inside a vanilla JSON item model + "overrides": [ + { + // This is an ItemOverride + "predicate": { + // This is the Map, containing the names of properties and their minimum values + "example1:prop": 0.5 + }, + // This is the 'location', or target model, of the override, which is used if the predicate above matches + "model": "example1:item/model" + }, + { + // This is another ItemOverride + "predicate": { + "example2:prop": 1 + }, + "model": "example2:item/model" + } + ] +} +``` + +[baked]: ./bakedmodel.md +[override]: #bakedoverride diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/rendering/modelloaders/transform.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/rendering/modelloaders/transform.md new file mode 100644 index 000000000..58a55a5f5 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/rendering/modelloaders/transform.md @@ -0,0 +1,37 @@ +Transform +========== + +When an [`BakedModel`][bakedmodel] is being rendered as an item, it can apply special handling depending on which transform it is being rendered in. "Transform" means in what context the model is being rendered. The possible transforms are represented in code by the `ItemDisplayContext` enum. There are two systems for handling transform: the deprecated vanilla system, constituted by `BakedModel#getTransforms`, `ItemTransforms`, and `ItemTransform`, and the Forge system, embodied by the method `IForgeBakedModel#applyTransform`. The vanilla code is patched to favor using `applyTransform` over the vanilla system whenever possible. + +`ItemDisplayContext` +--------------- + +`NONE` - Used for the display entity by default when no context is set and by Forge when a `Block`'s `RenderShape` is set to `#ENTITYBLOCK_ANIMATED`. + +`THIRD_PERSON_LEFT_HAND`/`THIRD_PERSON_RIGHT_HAND`/`FIRST_PERSON_LEFT_HAND`/`FIRST_PERSON_RIGHT_HAND` - The first person values represent when the player is holding the item in their own hand. The third person values represent when another player is holding the item and the client is looking at them in the 3rd person. Hands are self-explanatory. + +`HEAD` - Represents when any player is wearing the item in the helmet slot (e.g. pumpkins). + +`GUI` - Represents when the item is being rendered in a `Screen`. + +`GROUND` - Represents when the item is being rendered in the level as an `ItemEntity`. + +`FIXED` - Used for item frames. + +The Vanilla Way +--------------- + +The vanilla way of handling transform is through `BakedModel#getTransforms`. This method returns an `ItemTransforms`, which is a simple object that contains various `ItemTransform`s as `public final` fields. An `ItemTransform` represents a rotation, a translation, and a scale to be applied to the model. The `ItemTransforms` is a container for these, holding one for each of the `ItemDisplayContext`s except `NONE`. In the vanilla implementation, calling `#getTransform` for `NONE` results in the default transform, `ItemTransform#NO_TRANSFORM`. + +The entire vanilla system for handling transforms is deprecated by Forge, and most implementations of `BakedModel` should simply `return ItemTransforms#NO_TRANSFORMS` (which is the default implementation) from `BakedModel#getTransforms`. Instead, they should implement `#applyTransform`. + +The Forge Way +------------- + +The Forge way of handling transforms is `#applyTransform`, a method patched into `BakedModel`. It supersedes the `#getTransforms` method. + +#### `BakedModel#applyTransform` + +Given a `ItemDisplayContext`, `PoseStack`, and a boolean to determine whether to apply the transform for the left hand, this method produces an `BakedModel` to be rendered. Because the returned `BakedModel` can be a totally new model, this method is more flexible than the vanilla method (e.g. a piece of paper that looks flat in hand but crumpled on the ground). + +[bakedmodel]: ./bakedmodel.md diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/resources/_category_.json b/i18n/zh-CN/docusaurus-plugin-content-docs/current/resources/_category_.json new file mode 100644 index 000000000..678d589f3 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/resources/_category_.json @@ -0,0 +1,3 @@ +{ + "label": "Resources" +} \ No newline at end of file diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/resources/client/index.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/resources/client/index.md new file mode 100644 index 000000000..f8ea1ed7c --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/resources/client/index.md @@ -0,0 +1,15 @@ +Resource Packs +============== + +[Resource Packs][respack] allow for the customization of client resources through the `assets` directory. This includes textures, models, sounds, localizations, and others. Your mod (as well as Forge itself) can also have resource packs. Any user can therefore modify all the textures, models, and other assets defined within this directory. + +### Creating a Resource Pack +Resource Packs are stored within your project's resources. The `assets` directory contains the contents of the pack, while the pack itself is defined by the `pack.mcmeta` alongside the `assets` folder. +Your mod can have multiple asset domains, since you can add or modify already existing resource packs, like vanilla's, Forge's, or another mod's. +You can then follow the steps found [at the Minecraft Wiki][createrespack] to create any resource pack. + +Additional reading: [Resource Locations][resourcelocation] + +[respack]: https://minecraft.wiki/w/Resource_Pack +[createrespack]: https://minecraft.wiki/w/Tutorials/Creating_a_resource_pack +[resourcelocation]: ../../concepts/resources.md#ResourceLocation diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/resources/client/models/index.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/resources/client/models/index.md new file mode 100644 index 000000000..a999c5622 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/resources/client/models/index.md @@ -0,0 +1,25 @@ +Models +====== + +The [model system][models] is Minecraft's way of giving blocks and items their shapes. Through the model system, blocks and items are mapped to their models, which define how they look. One of the main goals of the model system is to allow not only textures but the entire shape of a block/item to be changed by resource packs. Indeed, any mod that adds items or blocks also contains a mini-resource pack for their blocks and items. + +Model Files +----------- + +Models and textures are linked through [`ResourceLocation`][resloc]s but are stored in the `ModelManager` using `ModelResourceLocation`s. Models are referenced in different locations through the block or item's registry name depending on whether they are referencing [block states][statemodel] or [item models][itemmodels]. Blocks will have their `ModelResourceLocation` represent their registry name along with a stringified version of its current [`BlockState`][state] while items will use their registry name followed by `inventory`. + +:::note +JSON models only support cuboid elements; there is no way to express a triangular wedge or anything like it. To have more complicated models, another format must be used. +::: + +### Textures + +Textures, like models, are contained within resource packs and are referred to with `ResourceLocation`s. In Minecraft, the [UV coordinates][uv] (0,0) are taken to mean the **top-left** corner. UVs are *always* from 0 to 16. If a texture is larger or smaller, the coordinates are scaled to fit. A texture should also be square, and the side length of a texture should be a power of two, as doing otherwise breaks mipmapping (e.g. 1x1, 2x2, 8x8, 16x16, and 128x128 are good. 5x5 and 30x30 are not recommended because they are not powers of 2. 5x10 and 4x8 are completely broken as they are not square.). Textures should only ever be not a square if it is [animated][animated]. + +[models]: https://minecraft.wiki/w/Tutorials/Models#File_path +[resloc]: ../../../concepts/resources.md#resourcelocation +[statemodel]: https://minecraft.wiki/w/Tutorials/Models#Block_states +[itemmodels]: https://minecraft.wiki/w/Tutorials/Models#Item_models +[state]: ../../../blocks/states.md +[uv]: https://en.wikipedia.org/wiki/UV_mapping +[animated]: https://minecraft.wiki/w/Resource_Pack?so=search#Animation diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/resources/client/models/itemproperties.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/resources/client/models/itemproperties.md new file mode 100644 index 000000000..bf0a43ca5 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/resources/client/models/itemproperties.md @@ -0,0 +1,65 @@ +Item Properties +=============== + +Item properties are a way for the "properties" of items to be exposed to the model system. An example is the bow, where the most important property is how far the bow has been pulled. This information is then used to choose a model for the bow, creating an animation for pulling it. + +An item property assigns a certain `float` value to every `ItemStack` it is registered for, and vanilla item model definitions can use these values to define "overrides", where an item defaults to a certain model, but if an override matches, it overrides the model and uses another. They are useful mainly because they are continuous. For example, bows use item properties to define their pull animation. The item models are decided by the 'float' number predicates, it is not limited but generally between `0.0F` and `1.0F`. This allows resource packs to add as many models as they want for the bow pulling animation along that spectrum, instead of being stuck with four "slots" for their models in the animation. The same is true of the compass and clock. + +Adding Properties to Items +-------------------------- + +`ItemProperties#register` is used to add a property to a certain item. The `Item` parameter is the item the property is being attached to (e.g. `ExampleItems#APPLE`). The `ResourceLocation` parameter is the name given to the property (e.g. `new ResourceLocation("pull")`). The `ItemPropertyFunction` is a functional interface that takes the `ItemStack`, the `ClientLevel` it is in (may be null), the `LivingEntity` that holds it (may be null), and the `int` containing the id of the holding entity (may be `0`), returning the `float` value for the property. For modded item properties, it is recommended that the mod id of the mod is used as the namespace (e.g. `examplemod:property` and not just `property`, as that really means `minecraft:property`). These should be done in `FMLClientSetupEvent`. +There's also another method `ItemProperties#registerGeneric` that is used to add properties to all items, and it does not take `Item` as its parameter since all items will apply this property. + +:::caution +Use `FMLClientSetupEvent#enqueueWork` to proceed with the tasks, since the data structures in `ItemProperties` are not thread-safe. +::: + +:::note +`ItemPropertyFunction` is deprecated by Mojang in favor of using the subinterface `ClampedItemPropertyFunction` which clamps the result between `0` and `1`. +::: + +Using Overrides +--------------- + +The format of an override can be seen on the [wiki][format], and a good example can be found in `model/item/bow.json`. For reference, here is a hypothetical example of an item with an `examplemod:power` property. If the values have no match, the default is the current model, but if there are multiple matches, the last match in the list will be selected. + +:::caution +A predicate applies to all values *greater than or equal to* the given value. +::: + +```js +{ + "parent": "item/generated", + "textures": { + // Default + "layer0": "examplemod:items/example_partial" + }, + "overrides": [ + { + // power >= .75 + "predicate": { + "examplemod:power": 0.75 + }, + "model": "examplemod:item/example_powered" + } + ] +} +``` + +And here is a hypothetical snippet from the supporting code. Unlike the older versions (lower than 1.16.x), this needs to be done on client side only because `ItemProperties` does not exist on server. + +```java +private void setup(final FMLClientSetupEvent event) +{ + event.enqueueWork(() -> + { + ItemProperties.register(ExampleItems.APPLE, + new ResourceLocation(ExampleMod.MODID, "pulling"), (stack, level, living, id) -> { + return living != null && living.isUsingItem() && living.getUseItem() == stack ? 1.0F : 0.0F; + }); + }); +} +``` + +[format]: https://minecraft.wiki/w/Tutorials/Models#Item_models diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/resources/client/models/tinting.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/resources/client/models/tinting.md new file mode 100644 index 000000000..d5347239c --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/resources/client/models/tinting.md @@ -0,0 +1,32 @@ +Coloring Textures +================= + +Many blocks and items in vanilla change their texture color depending on where they are or what properties they have, such as grass. Models support specifying "tint indices" on faces, which are integers that can then be handled by `BlockColor`s and `ItemColor`s. See the [wiki][] for information on how tint indices are defined in vanilla models. + +### `BlockColor`/`ItemColor` + +Both of these are single-method interfaces. `BlockColor` takes a `BlockState`, a (nullable) `BlockAndTintGetter`, and a (nullable) `BlockPos`. `ItemColor` takes an `ItemStack`. Both of them take an `int` parameter `tintIndex`, which is the tint index of the face being colored. Both of them return an `int`, a color multiplier. This `int` is treated as 4 unsigned bytes, alpha, red, green, and blue, in that order, from most significant byte to least. For each pixel in the tinted face, the value of each color channel is `(int)((float) base * multiplier / 255.0)`, where `base` is the original value for the channel, and `multiplier` is the associated byte from the color multiplier. Note that blocks do not use the alpha channel. For example, the grass texture, untinted, looks white and gray. The `BlockColor` and `ItemColor` for grass return color multipliers with low red and blue components, but high alpha and green components, (at least in warm biomes) so when the multiplication is performed, the green is brought out and the red/blue diminished. + +If an item inherits from the `builtin/generated` model, each layer ("layer0", "layer1", etc.) has a tint index corresponding to its layer index. + +### Creating Color Handlers + +`BlockColor`s need to be registered to the `BlockColors` instance of the game. `BlockColors` can be acquired through `RegisterColorHandlersEvent$Block`, and an `BlockColor` can be registered by `#register`. Note that this does not cause the `BlockItem` for the given block to be colored. `BlockItem`s are items and need to be colored with an `ItemColor`. + +```java +@SubscribeEvent +public void registerBlockColors(RegisterColorHandlersEvent.Block event){ + event.register(myBlockColor, coloredBlock1, coloredBlock2, ...); +} +``` + +`ItemColor`s need to be registered to the `ItemColors` instance of the game. `ItemColors` can be acquired through `RegisterColorHandlersEvent$Item`, and an `ItemColor` can be registered by `#register`. This method is overloaded to also take `Block`s, which simply registers the color handler for the item `Block#asItem` (i.e. the block's `BlockItem`). + +```java +@SubscribeEvent +public void registerItemColors(RegisterColorHandlersEvent.Item event){ + event.register(myItemColor, coloredItem1, coloredItem2, ...); +} +``` + +[wiki]: https://minecraft.wiki/w/Tutorials/Models#Block_models diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/resources/server/advancements.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/resources/server/advancements.md new file mode 100644 index 000000000..79d412d35 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/resources/server/advancements.md @@ -0,0 +1,167 @@ +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.). + +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. + +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 + +// 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" + ] +] +``` + +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 + +Custom criteria triggers can be created by implementing `SimpleCriterionTrigger` for the created `AbstractCriterionTriggerInstance` subclass. + +### AbstractCriterionTriggerInstance Subclass + +The `AbstractCriterionTriggerInstance` represents a single criteria defined in the `criteria` object. Trigger instances are responsible for holding the defined conditions, returning whether the inputs match the condition, and writing the instance to JSON for data generation. + +Conditions are usually passed in through the constructor. The `AbstractCriterionTriggerInstance` super constructor requires the instance to define the registry name of the trigger and the conditions the player must meet as an `ContextAwarePredicate`. The registry name of the trigger should be supplied to the super directly while the conditions of the player should be a constructor parameter. + +```java +// Where ID is the registry name of the trigger +public ExampleTriggerInstance(ContextAwarePredicate player, ItemPredicate item) { + super(ID, player); + // Store the item condition that must be met +} +``` + +:::note +Typically, trigger instances have a static constructor which allow these instances to be easily created for data generation. These static factory methods can also be statically imported instead of the class itself. + +```java +public static ExampleTriggerInstance instance(ContextAwarePredicate player, ItemPredicate item) { + return new ExampleTriggerInstance(player, item); +} +``` +::: + +Additionally, the `#serializeToJson` method should be overridden. The method should add the conditions of the instance to the other JSON data. + +```java +@Override +public JsonObject serializeToJson(SerializationContext context) { + JsonObject obj = super.serializeToJson(context); + // Write conditions to json + return obj; +} +``` + +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 +public boolean matches(ItemStack stack) { + // Since ItemPredicate matches a stack, a stack is the input + return this.item.matches(stack); +} +``` + +### SimpleCriterionTrigger + +The `SimpleCriterionTrigger` subclass, where `T` is the type of the trigger instance, is responsible for specifying the registry name of the trigger, creating a trigger instance, and a method to check trigger instances and run attached listeners on success. + +The registry name of the trigger is supplied to `#getId`. This should match the registry name supplied to the trigger instance. + +A trigger instance is created via `#createInstance`. This method reads a criteria from JSON. + +```java +@Override +public ExampleTriggerInstance createInstance(JsonObject json, ContextAwarePredicate player, DeserializationContext context) { + // Read conditions from JSON: item + return new ExampleTriggerInstance(player, item); +} +``` + +Finally, a method is defined 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 `AbstractCriterionTriggerInstance` 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 overridden +public void trigger(ServerPlayer player, ItemStack stack) { + this.trigger(player, + // The condition checker method within the AbstractCriterionTriggerInstance subclass + triggerInstance -> triggerInstance.matches(stack) + ); +} +``` + +Afterwards, an instance should be registered using `CriteriaTriggers#register` during `FMLCommonSetupEvent`. + +:::danger +`CriteriaTriggers#register` must be enqueued to the synchronous work queue via `FMLCommonSetupEvent#enqueueWork` as the method is not thread-safe. +::: + +### Calling the 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 +// Where EXAMPLE_CRITERIA_TRIGGER is the custom criteria trigger +public void performExampleAction(ServerPlayer player, ItemStack stack) { + // Run code to perform action + EXAMPLE_CRITERIA_TRIGGER.trigger(player, 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 +"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 diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/resources/server/conditional.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/resources/server/conditional.md new file mode 100644 index 000000000..89b8562f0 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/resources/server/conditional.md @@ -0,0 +1,182 @@ +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 +--------------- + +Currently, conditional loading is implemented for recipes and advancements. For any conditional recipe or advancement, a list of conditions to datum pair is loaded. If the conditions specified for a datum in the list is true, then that datum is returned. Otherwise, the datum is discarded. + +```js +{ + // The type needs to be specified for recipes as they can have custom serializers + // Advancements do not need this type + "type": "forge:conditional", + + "recipes": [ // Or 'advancements' for Advancements + { + // The conditions to check + "conditions": [ + // Conditions in the list are ANDed together + { + // Condition 1 + }, + { + // Condition 2 + } + ], + "recipe": { // Or 'advancement' for Advancements + // The recipe to use if all conditions succeed + } + }, + { + // Next condition to check if the previous fails + }, + ] +} +``` + +Conditionally-loaded data additionally have wrappers for [data generation][datagen] through `ConditionalRecipe$Builder` and `ConditionalAdvancement$Builder`. + +Conditions +---------- + +Conditions are specified by setting `type` to the name of the condition as specified by [`IConditionSerializer#getID`][serializer]. + +### True and False + +Boolean conditions consist of no data and return the expected value of the condition. They are represented by `forge:true` and `forge:false`. + +```js +// For some condition +{ + // Will always return true (or false for 'forge:false') + "type": "forge: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 `forge:not`, `forge:and`, and `forge:or`. + + +```js +// For some condition +{ + // Inverts the result of the stored condition + "type": "forge:not", + "value": { + // A condition + } +} +``` + +```js +// For some condition +{ + // ANDs the stored conditions together (or ORs for 'forge:or') + "type": "forge:and", + "values": [ + { + // First condition + }, + { + // Second condition to be ANDed (or ORed for 'forge:or') + } + ] +} +``` + +### Mod Loaded + +`ModLoadedCondition` returns true whenever the specified mod with the given id is loaded in the current application. This is represented by `forge:mod_loaded`. + +```js +// For some condition +{ + "type": "forge: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 `forge:item_exists`. + +```js +// For some condition +{ + "type": "forge: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 `forge:tag_empty`. + +```js +// For some condition +{ + "type": "forge: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 its associated `IConditionSerializer`. + +### ICondition + +Any condition only need to implement two methods: + +Method | Description +:---: | :--- +getID | The registry name of the condition. Must be equivalent to [`IConditionSerializer#getID`][serializer]. Used only for [data generation][datagen]. +test | Returns true if the condition has been satisfied. + +:::note +Every `#test` has access to some `IContext` representing the state of the game. Currently, only tags can be obtained from a registry. +::: + +### IConditionSerializer + +Serializers need to implement three methods: + +Method | Description +:---: | :--- +getID | The registry name of the condition. Must be equivalent to [`ICondition#getID`][condition]. +read | Reads the condition data from JSON. +write | Writes the given condition data to JSON. + +:::note +Condition serializers are not responsible for writing or reading the type of the serializer, similar to other serializer implementations in Minecraft. +::: + +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`. + +```java +// In some serializer class +public static final ExampleConditionSerializer INSTANCE = new ExampleConditionSerializer(); + +// In some handler class +public void registerSerializers(RegisterEvent event) { + event.register(ForgeRegistries.Keys.RECIPE_SERIALIZERS, + helper -> CraftingHelper.register(INSTANCE) + ); +} +``` + +:::danger +If using `FMLCommonSetupEvent` to register a condition serializer, it must be enqueued to the synchronous work queue via `FMLCommonSetupEvent#enqueueWork` as `CraftingHelper#register` is not thread-safe. +::: + +[datagen]: ../../datagen/server/recipes.md +[serializer]: #iconditionserializer +[condition]: #icondition diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/resources/server/glm.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/resources/server/glm.md new file mode 100644 index 000000000..71fae565a --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/resources/server/glm.md @@ -0,0 +1,148 @@ +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: + +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`. + +The `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` will only be read in the `forge` namespace. The file will be neglected if it is under the mod's namespace. +::: + +`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`, 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 +{ + "replace": false, // Must be present + "entries": [ + // Represents a loot modifier in 'data/examplemod/loot_modifiers/example_glm.json' + "examplemod:example_glm", + "examplemod:example_glm2" + // ... + ] +} +``` + +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` represents the registry name of the [codec] used to read the associated JSON file. This must always be present. + +`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 +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 +{ + "type": "examplemod:example_loot_modifier", + "conditions": [ + // Normal loot table conditions + // ... + ], + "prop1": "val1", + "prop2": 10, + "prop3": "minecraft:dirt" +} +``` + +`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. + +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` returns the registered [codec] used to encode and decode the modifier to/from JSON. + +### The `LootModifier` Subclass + +`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. + +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. + +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. + +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 { + + 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 + } +} +``` + +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. + +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 +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][examples] can be found on the Forge Git repository, including silk touch and smelting effects. + +[tags]: ./tags.md +[resloc]: ../../concepts/resources.md#ResourceLocation +[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/MinecraftForge/MinecraftForge/blob/1.20.x/src/test/java/net/minecraftforge/debug/gameplay/loot/GlobalLootModifiersTest.java diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/resources/server/index.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/resources/server/index.md new file mode 100644 index 000000000..bc5b93139 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/resources/server/index.md @@ -0,0 +1,14 @@ +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. + +### 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. + +Additional reading: [Resource Locations][resourcelocation] + +[datapack]: https://minecraft.wiki/w/Data_pack +[createdatapack]: https://minecraft.wiki/w/Tutorials/Creating_a_data_pack +[resourcelocation]: ../../concepts/resources.md#ResourceLocation diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/resources/server/loottables.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/resources/server/loottables.md new file mode 100644 index 000000000..52d7eed62 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/resources/server/loottables.md @@ -0,0 +1,111 @@ +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]. + +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`. + +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`. + +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. + +A `LootTable` can be used to generate `ItemStack`s using one of the available methods which may take in a `LootParams` or a `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. + +:::note +Loot tables were built for generating items, so the methods expect some handling for the `ItemStack`s. +::: + +Additional Features +------------------- + +Forge provides some additional behavior to loot tables for greater control of the system. + +### `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. + +:::info +Do **not** modify a loot table's drops through this event. Those modifications should be done using [global loot modifiers][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#`. + +```js +// For some loot pool +{ + "name": "example_pool", // Pool will be named 'example_pool' + "rolls": { + // ... + }, + "entries": { + // ... + } +} +``` + +### Looting Modifiers + +Loot tables are now affected by the `LootingLevelEvent`, on the Forge event bus, in addition to the looting enchantment. + +### 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. + +### 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). + +### 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]. + +```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" + } + ] +} +``` + +### Can Tool Perform Action Condition + +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 +{ + "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#creating-an-event-handler +[glm]: ./glm.md diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/resources/server/recipes/custom.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/resources/server/recipes/custom.md new file mode 100644 index 000000000..1c77d2109 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/resources/server/recipes/custom.md @@ -0,0 +1,132 @@ +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 +------ + +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. + +:::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`. +::: + +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`. + +If the recipe has been chosen, it is then built using `#assemble` which may use data from the inputs to create the result. + +:::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 { + // Implement methods here +} +``` + +:::note +While a record is used in the above example, it is not required to do so in your own implementation. +::: + +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`. + +If none of the existing types match what context the recipe will be used within, then a new `RecipeType` must be [registered][forge]. + +The `RecipeType` instance must then be returned by `Recipe#getType` in the new recipe subtype. + +```java +// For some RegistryObject EXAMPLE_TYPE +// In ExampleRecipe +@Override +public RecipeType getType() { + return EXAMPLE_TYPE.get(); +} +``` + +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]. + +Only three methods need to be implemented for a `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. + +The `RecipeSerializer` instance must then be returned by `Recipe#getSerializer` in the new recipe subtype. + +```java +// For some RegistryObject EXAMPLE_SERIALIZER +// In ExampleRecipe +@Override +public RecipeSerializer getSerializer() { + return EXAMPLE_SERIALIZER.get(); +} +``` + +:::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`. +::: + +Building the 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. + +```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 + } +} +``` + +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. + +```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 +} +``` + +Data Generation +--------------- + +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 +[manager]: ./index.md#recipe-manager +[datagen]: ../../../datagen/server/recipes.md#custom-recipe-serializers diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/resources/server/recipes/incode.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/resources/server/recipes/incode.md new file mode 100644 index 000000000..26d1f7ce4 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/resources/server/recipes/incode.md @@ -0,0 +1,65 @@ +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`. + +:::caution +`BrewingRecipeRegistry#addRecipe` must be called within the synchronous work queue via `#enqueueWork` as the method is not thread-safe. +::: + +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` 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 +When copying data between `ItemStack`s or `CompoundTag`s, make sure to use their respective `#copy` methods to create unique instances. +::: + +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 +------------- + +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 +// 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(...); + } +} +``` + +The update event must be [attached] to the Forge event bus. + +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`. + +:::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. +::: + +```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#canceling +[attached]: ../../../concepts/events.md#creating-an-event-handler +[registering]: ../../../concepts/registries.md#registries-that-arent-forge-registries diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/resources/server/recipes/index.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/resources/server/recipes/index.md new file mode 100644 index 000000000..d41b60501 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/resources/server/recipes/index.md @@ -0,0 +1,105 @@ +Recipes +======= + +Recipes are a way to transform some number of objects into other objects within a Minecraft world. Although the vanilla system deals purely with item transformations, the system as a whole can be expanded to use any object the programmer creates. + +Data-Driven Recipes +------------------- + +Most recipe implementations within vanilla are data driven via JSON. This means that a mod is not necessary to create a new recipe, only a [Data pack][datapack]. A full list on how to create and put these recipes within the mod's `resources` folder can be found on the [Minecraft Wiki][wiki]. + +A recipe can be obtained within the Recipe Book as a reward for completing an [advancement][advancement]. Recipe advancements always have `minecraft:recipes/root` as their parent, to not to appear on the advancement screen. The default criteria to gain the recipe advancement is a check if the user has unlocked the recipe from using it once or receiving it through a command like `/recipe`: + +```js +// Within some recipe advancement json +"has_the_recipe": { // Criteria label + // Succeeds if examplemod:example_recipe is used + "trigger": "minecraft:recipe_unlocked", + "conditions": { + "recipe": "examplemod:example_recipe" + } +} +//... +"requirements": [ + [ + "has_the_recipe" + // ... Other criteria labels to be ORed against to unlock recipe + ] +] +``` + +Data-driven recipes and their unlocking advancement can be [generated][datagen] via `RecipeProvider`. + +Recipe Manager +-------------- + +Recipes are loaded and stored via the `RecipeManager`. Any operations relating to getting available recipe(s) are handled by this manager. There are two important methods to know of: + + Method | Description + :---: | :--- +`getRecipeFor` | Gets the first recipe that matches the current input. +`getRecipesFor` | Gets all recipes that match the current input. + +Each method takes in a `RecipeType`, which denotes what method is being applied to use the recipe (crafting, smelting, etc.), a `Container` which holds the configuration of the inputs, and the current level which is passed to `Recipe#matches` along with the container. + +:::tip +Forge provides the `RecipeWrapper` utility class which extends `Container` for wrapping around `IItemHandler`s and passing them to methods which requires a `Container` parameter. + +```java +// Within some method with IItemHandlerModifiable handler +recipeManger.getRecipeFor(RecipeType.CRAFTING, new RecipeWrapper(handler), level); +``` +::: + +Additional Features +------------------- + +Forge provides some additional behavior to the recipe schema and its implementations for greater control of the system. + +### Recipe ItemStack Result + +Except for `minecraft:stonecutting` recipes, all vanilla recipe serializers expand the `result` tag to take in a full `ItemStack` as a `JsonObject` instead of just the item name and amount in some cases. + +```js +// In some recipe JSON +"result": { + // The name of the registry item to give as a result + "item": "examplemod:example_item", + // The number of items to return + "count": 4, + // The tag data of the stack, can also be a string + "nbt": { + // Add tag data here + } +} +``` + +:::note +The `nbt` tag can alternatively be a string containing a stringified NBT (or SNBT) for data which cannot be properly represented as a JSON object (such as `IntArrayTag`s). +::: + +### Conditional Recipes + +Recipes and their unlocking advancement can be [loaded conditionally and defaulted][conditional] depending on what information is present (mod loaded, item exists, etc.). + +### Larger Crafting Grids + +By default, vanilla declares a maximum width and height for a crafting grid to be a 3x3 square. This can be expanded by calling `ShapedRecipe#setCraftingSize` with the new width and height in `FMLCommonSetupEvent`. + +:::caution +`ShapedRecipe#setCraftingSize` is **NOT** thread-safe. As such, it should be enqueued to the synchronous work queue via `FMLCommonSetupEvent#enqueueWork`. +::: + +Larger crafting grids in recipes can be [data generated][datagen]. + +### Ingredient Types + +A few additional [ingredient types][ingredients] are added to allow recipes to have inputs which check tag data or combine multiple ingredients into a single input checker. + +[datapack]: https://minecraft.wiki/w/Data_pack +[wiki]: https://minecraft.wiki/w/Recipe +[advancement]: ../advancements.md +[datagen]: ../../../datagen/server/recipes.md +[cap]: ../../../datastorage/capabilities.md +[conditional]: ../conditional.md#implementations +[ingredients]: ./ingredients.md#forge-types diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/resources/server/recipes/ingredients.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/resources/server/recipes/ingredients.md new file mode 100644 index 000000000..7d5d3629d --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/resources/server/recipes/ingredients.md @@ -0,0 +1,177 @@ +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`. + +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]. + +### Forge Types + +Forge provides a few additional `Ingredient` types for programmers to implement. + +#### 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. + +```js +// For some input +[ + // At least one of these ingredients must match to succeed + { + // Ingredient + }, + { + // Custom ingredient + "type": "examplemod:example_ingredient" + } +] +``` + +#### 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`. + +```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`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 +{ + "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`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 +{ + "type": "forge:intersection", + + // All of these ingredients must return true to succeed + "children": [ + { + // Ingredient 1 + }, + { + // Ingredient 2 + } + // ... + ] +} +``` + +### 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`. + +```js +// For some input +{ + "type": "forge:difference", + "base": { + // Ingredient the stack is in + }, + "subtracted": { + // Ingredient the stack is NOT in + } +} +``` + +Creating Custom Ingredients +--------------------------- + +Custom ingredients can be created by implementing `IIngredientSerializer` for the created `Ingredient` subclass. + +:::tip +Custom ingredients should subclass `AbstractIngredient` as it provides some useful abstractions for ease of implementation. +::: + +### Ingredient Subclass + +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. + +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: + + 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. + +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. + +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 +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 +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 +[nbt]: #strictnbtingredient +[serializer]: #iingredientserializer +[compound]: #compoundingredient +[datagen]: ../../../datagen/server/recipes.md diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/resources/server/tags.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/resources/server/tags.md new file mode 100644 index 000000000..4a5883d9a --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/resources/server/tags.md @@ -0,0 +1,121 @@ +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: + +```js +{ + "replace": false, + "values": [ + "minecraft:gold_ingot", + "mymod:my_ingot", + { + "id": "othermod:ingot_other", + "required": false + } + ] +} +``` + +See the [Vanilla wiki][tags] for a description of the base syntax. + +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 +------------------ +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 +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 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. + +#### 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 + +There are four methods of creating a tag wrapper: + +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`. + +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 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 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")); + +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); +``` + +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.). + + +Migration from 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. + + +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. + +[datapack]: ./index.md +[tags]: https://minecraft.wiki/w/Tag#JSON_format +[taglist]: https://minecraft.wiki/w/Tag#List_of_tags +[forgetags]: https://github.com/MinecraftForge/MinecraftForge/tree/1.19.x/src/generated/resources/data/forge/tags +[recipes]: https://minecraft.wiki/w/Recipe#JSON_format +[advancements]: https://minecraft.wiki/w/Advancement From 151ff29c9b10505f08a51695f622a4c78efe4a8d Mon Sep 17 00:00:00 2001 From: src_resources Date: Tue, 10 Oct 2023 12:01:22 +0800 Subject: [PATCH 03/15] Import existing Chinese translations of NeoForge Documentation Signed-off-by: src_resources --- .../current/advanced/accesstransformers.md | 134 +++-- .../current/blockentities/ber.md | 34 +- .../current/blockentities/index.md | 123 +++-- .../current/blocks/index.md | 56 +-- .../current/blocks/states.md | 98 ++-- .../current/concepts/events.md | 108 ++-- .../current/concepts/internationalization.md | 60 ++- .../current/concepts/lifecycle.md | 62 ++- .../current/concepts/registries.md | 190 ++++--- .../current/concepts/resources.md | 16 +- .../current/concepts/sides.md | 106 ++-- .../current/datagen/client/localization.md | 31 +- .../current/datagen/client/modelproviders.md | 466 +++++++++--------- .../current/datagen/client/sounds.md | 79 ++- .../current/datagen/index.md | 80 +-- .../current/datagen/server/advancements.md | 46 +- .../datagen/server/datapackregistries.md | 79 +-- .../current/datagen/server/glm.md | 14 +- .../current/datagen/server/loottables.md | 86 ++-- .../current/datagen/server/recipes.md | 151 +++--- .../current/datagen/server/tags.md | 68 +-- .../current/datastorage/capabilities.md | 142 +++--- .../current/datastorage/codecs.md | 265 +++++----- .../current/datastorage/saveddata.md | 31 +- .../current/gameeffects/particles.md | 120 +++-- .../current/gameeffects/sounds.md | 91 ++-- .../current/gettingstarted/index.md | 144 +++--- .../current/gettingstarted/modfiles.md | 130 +++-- .../current/gettingstarted/structuring.md | 78 ++- .../current/gettingstarted/versioning.md | 61 ++- .../current/gui/menus.md | 230 +++++---- .../current/gui/screens.md | 274 +++++----- .../current/items/bewlr.md | 28 +- .../current/items/index.md | 65 +-- .../current/legacy/index.md | 29 ++ .../current/legacy/porting.md | 10 +- .../current/misc/config.md | 186 ++++--- .../current/misc/debugprofiler.md | 37 +- .../current/misc/gametest.mdx | 246 +++++---- .../current/misc/keymappings.md | 118 +++-- .../current/misc/updatechecker.md | 64 +-- .../current/networking/entities.md | 42 +- .../current/networking/index.md | 22 +- .../current/networking/simpleimpl.md | 103 ++-- .../ambientocclusion_annotated.png | Bin 0 -> 98157 bytes .../rendering/modelextensions/facedata.md | 89 ++-- .../rendering/modelextensions/rendertypes.md | 83 ++-- .../rendering/modelextensions/transforms.md | 64 +-- .../rendering/modelextensions/visibility.md | 18 +- .../rendering/modelloaders/bakedmodel.md | 41 +- .../current/rendering/modelloaders/index.md | 21 +- .../rendering/modelloaders/itemoverrides.md | 26 +- .../rendering/modelloaders/transform.md | 36 +- .../current/resources/client/index.md | 20 +- .../current/resources/client/models/index.md | 29 +- .../resources/client/models/itemproperties.md | 41 +- .../resources/client/models/tinting.md | 18 +- .../current/resources/server/advancements.md | 114 +++-- .../current/resources/server/conditional.md | 135 +++-- .../current/resources/server/glm.md | 107 ++-- .../current/resources/server/index.md | 20 +- .../current/resources/server/loottables.md | 89 ++-- .../resources/server/recipes/custom.md | 112 ++--- .../resources/server/recipes/incode.md | 53 +- .../current/resources/server/recipes/index.md | 99 ++-- .../resources/server/recipes/ingredients.md | 116 +++-- .../current/resources/server/tags.md | 111 +++-- 67 files changed, 2951 insertions(+), 3094 deletions(-) create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/legacy/index.md create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/rendering/modelextensions/ambientocclusion_annotated.png diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/advanced/accesstransformers.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/advanced/accesstransformers.md index 707e3ce9c..49bbd10f8 100644 --- a/i18n/zh-CN/docusaurus-plugin-content-docs/current/advanced/accesstransformers.md +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/advanced/accesstransformers.md @@ -1,114 +1,112 @@ -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. +访问转换器(简称AT)允许扩大可见性并修改类、方法和字段的`final`标志。它们允许模组开发者访问和修改其控制之外的类中不可访问的成员。 -The [specification document][specs] can be viewed on the NeoForged GitHub. +[规范文档][specs]可以在Minecraft Forge GitHub上查看。 -Adding ATs ----------- +添加AT +------ -Adding an Access Transformer to your mod project is as simple as adding a single line into your `build.gradle`: +在你的模组项目中添加一个访问转换器就像在`build.gradle`中添加一行一样简单: ```groovy -// This block is where your mappings version is also specified +// 此代码块也是指定映射版本的位置 minecraft { accessTransformer = file('src/main/resources/META-INF/accesstransformer.cfg') } ``` -After adding or modifying the Access Transformer, the gradle project must be refreshed for the transformations to take effect. +添加或修改访问转换器后,必须刷新Gradle项目才能使转换生效。 -During development, the AT file can be anywhere specified by the line above. However, when loading in a non-development environment, Forge will only search for the exact path of `META-INF/accesstransformer.cfg` in your JAR file. +在开发过程中,AT文件可以位于上面一行指定的任何位置。然而,当在非开发环境中加载时,Forge只会在JAR文件中搜索`META-INF/accesstransformer.cfg`的确切路径。 -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抛出错误(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 ----------------------- +!!! 重要 + 在Minecraft类上使用访问转换器时,字段和方法必须使用SRG名称。 -:::caution -When using Access Transformers on Minecraft classes, the SRG name must be used for fields and methods. -::: - -### 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节][jvmdescriptors]。 + +* `B` - `byte`,有符号字节 +* `C` - `char`,UTF-16 Unicode字符 +* `D` - `double`,双精度浮点值 +* `F` - `float`,单精度浮点值 +* `I` - `integer`,32位整数 +* `J` - `long`,64位整数 +* `S` - `short`,有符号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 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 f_129758_ #random -# Makes public the 'makeExecutor' method in Util, -# accepting a String and returns an ExecutorService +# 将Util中的'makeExecutor'方法转换为public, +# 接受一个String并返回一个ExecutorService public net.minecraft.Util m_137477_(Ljava/lang/String;)Ljava/util/concurrent/ExecutorService; #makeExecutor -# Makes public the 'leastMostToIntArray' method in UUIDUtil, -# accepting two longs and returning an int[] +# 将UUIDUtil中的'leastMostToIntArray'方法转换为public +# 接受两个long参数并返回一个int[] public net.minecraft.core.UUIDUtil m_235872_(JJ)[I #leastMostToIntArray ``` -[specs]: https://github.com/NeoForged/AccessTransformers/blob/main/FMLAT.md +[specs]: https://github.com/MinecraftForge/AccessTransformers/blob/master/FMLAT.md [jvmdescriptors]: https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.3.2 diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/blockentities/ber.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/blockentities/ber.md index 0196f965d..90e4a722b 100644 --- a/i18n/zh-CN/docusaurus-plugin-content-docs/current/blockentities/ber.md +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/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`: 在帧的摩擦过程中,从上一次完整刻度开始经过的时间量。 +* `poseStack`: 一个栈,包含偏移到方块实体当前位置的四维矩阵条目。 +* `bufferSource`: 能够访问顶点Consumer的渲染缓冲区。 +* `combinedLight`: 方块实体上当前亮度值的整数。 +* `combinedOverlay`: 设置为方块实体的当前overlay的整数,通常为`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`。 diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/blockentities/index.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/blockentities/index.md index 89ef2b872..5b1b32a8c 100644 --- a/i18n/zh-CN/docusaurus-plugin-content-docs/current/blockentities/index.md +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/blockentities/index.md @@ -1,143 +1,132 @@ -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. +`BlockEntities`类似于绑定到某一方块的简化的`Entities`。 +它们能用于存储动态数据、执行基于游戏刻的任务和动态渲染。 +原版Minecraft中的一些例子是处理箱子的物品栏、熔炉的熔炼逻辑或信标的区域效果。 +模组中存在更高级的示例,例如采石场(如BC)、分拣机(如IC2)、管道(如BC)和显示器(如OC)。(括号内容为译者注。) -:::note -`BlockEntities` aren't a solution for everything and they can cause lag when used wrongly. -When possible, try to avoid them. -::: +!!! 注意 + `BlockEntities`并不是万能的解决方案,如果使用错误,它们可能会导致游戏卡顿。 + 如果可能的话,尽量避免使用。 -## 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. +为了创建`BlockEntity`,你需要继承`BlockEntity`类。这样,另一个对象被替代性地注册以方便创建和引用动态对象的*类型*。对于`BlockEntity`,这些对象被称为`BlockEntityType`。 -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`,可以通过`BlockEntityType$Builder#of`使用其Builder形式。这需要两个参数:`BlockEntityType$BlockEntitySupplier`,它接受`BlockPos`和`BlockState`来创建关联`BlockEntity`的新实例,以及该`BlockEntity`可以附加到的`Block`的可变参数。构建该`BlockEntityType`是通过调用`BlockEntityType$Builder#build`来完成的。其接受一个`Type`,表示用于引用某个`DataFixer`中的此注册表对象的类型安全引用。由于`DataFixer`是用于模组的可选系统,因此其也可用`null`代替。 ```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` +## 创建一个`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`,`EntityBlock`接口必须在你的`Block`子类上实现。方法`EntityBlock#newBlockEntity(BlockPos, BlockState)`必须实现并返回一个你的`BlockEntity`的新实例。 -## Storing Data within your `BlockEntity` +## 将数据存储到你的`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` +## 计时的`BlockEntity` -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: +如果你需要一个计时的`BlockEntity`,例如为了跟踪冶炼过程中的进度,则必须在`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) -::: +!!! 注意 + 这个方法在每个游戏刻都会调用;因此,你应该避免在这里进行复杂的计算。如果可能的话,你应该每X个游戏刻进行更复杂的计算。(一秒钟内的游戏刻数量可能低于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. +有三种方法可以将数据同步到客户端:在区块加载时同步、在方块更新时同步以及使用自定义网络消息同步。 -### 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`不包含太多数据,你可以使用[将数据存储到你的`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`的位掩码(bitmask),其将向客户端同步数据。有关更多信息以及flags的其余信息,参见`Block`。flag `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 [storing-data]: #storing-data-within-your-blockentity diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/blocks/index.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/blocks/index.md index f083f104d..5239cae04 100644 --- a/i18n/zh-CN/docusaurus-plugin-content-docs/current/blocks/index.md +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/blocks/index.md @@ -1,49 +1,47 @@ -Blocks -====== +方块 +==== -Blocks are, obviously, essential to the Minecraft world. They make up all of 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. +显然,方块是Minecraft世界的关键。它们构成了所有的地形、结构和机器。如果你有兴趣制作一个模组,那么你必然可能会想添加一些方块。本页将指导你创建方块,以及你可以使用它们做的一些事情。 -Creating a Block ----------------- +创建一个方块 +----------- -### Basic Blocks +### 基础方块 -For simple blocks, which need no special functionality (think cobblestone, wooden planks, etc.), a custom class is not necessary. You can create a block by instantiating the `Block` class with a `BlockBehaviour$Properties` object. This `BlockBehaviour$Properties` object can be made using `BlockBehaviour$Properties#of`, and it can be customized by calling its methods. For instance: +对于不需要特殊功能的简单方块(比如圆石、木板等),不必自定义一个类。你可以通过使用`BlockBehaviour$Properties`对象实例化`Block`类来创建一个方块。该`BlockBehaviour$Properties`对象可以调用`BlockBehaviour$Properties#of`创建,并且可以通过调用其方法进行自定义。例如: -- `strength` - The hardness controls the time it takes to break the block. It is an arbitrary value. For reference, stone has a hardness of 1.5, and dirt 0.5. If the block should be unbreakable a hardness of -1.0 should be used, see the definition of `Blocks#BEDROCK` as an example. The resistance controls the explosion resistance of the block. For reference, stone has a resistance of 6.0, and dirt 0.5. -- `sound` - Controls the sound the block makes when it is punched, broken, or placed. Requires a `SoundType` argument, see the [sounds] page for more details. -- `lightLevel` - Controls the light emission of the block. Takes a function with a `BlockState` parameter that returns a value from zero to fifteen. -- `friction` - Controls how slippery the block is. For reference, ice has a slipperiness of 0.98. +- `strength` - 硬度控制着断块所需的时间。它是一个任意值。作为参考,石头的硬度为1.5,泥土的硬度为0.5。如果该方块不能被破坏,则应使用-1.0的硬度,`Blocks#BEDROCK`的定义是一个例子。抗性控制块的防爆性。作为参考,石头的抗性为6.0,泥土的抗性为0.5。 +- `sound` - 控制方块在点击、破坏或放置时发出的音效。其需要一个`SoundType`参数,请参阅[音效][sounds]页面了解更多详细信息。 +- `lightLevel` - 控制方块的亮度。其接受一个带有`BlockState`参数的函数,该函数返回从0到15的某一个值。 +- `friction` - 控制方块的动摩擦系数。作为参考,冰的动摩擦系数为0.98。 -All these methods are *chainable* which means you can call them in series. See the `Blocks` class for examples of this. +所有这些方法都是*可链接*的,这意味着你可以串联地调用它们。有关此方面的示例,请参见`Blocks`类。 -:::note -Blocks have no setter for their `CreativeModeTab`. This is handled by the [`BuildCreativeModeTabContentsEvent`][creativetabs] if the block has an associated item (e.g. `BlockItem`). Furthermore, there is no setter for translation key of the block as it is generated from the registry name via `Block#getDescriptionId`. -::: +!!! 注意 + `CreativeModeTab`未针对方块定义setter。如果方块有与之关联的物品(例如`BlockItem`),则由[`BuildCreativeModeTabContentsEvent`][creativetabs]处理。此外,也没有针对翻译键的setter,因为它是通过`Block#getDescriptionId`从注册表名称生成的。 -### Advanced Blocks +### 进阶方块 -Of course, the above only allows for extremely basic blocks. If you want to add functionality, like player interaction, a custom class is required. However, the `Block` class has many methods and unfortunately not every single one can be documented here. See the rest of the pages in this section for things you can do with blocks. +当然,上面只允许创建非常基本的方块。如果你想添加一些功能,比如玩家交互,那么需要一个自定义的方块类。然而,`Block`类有很多方法,并且不幸的是,并不是每一个方法都能在这里用文档完全表述。请参阅本节中的其余页面,以了解你可以对方块进行的操作。 -Registering a Block -------------------- +注册一个方块 +----------- -Blocks must be [registered][registering] to function. +方块必须经过[注册][registering]后才能发挥作用。 -:::caution -A block in the level and a "block" in an inventory are very different things. A block in the level is represented by an `BlockState`, and its behavior defined by an instance of `Block`. Meanwhile, an item in an inventory is an `ItemStack`, controlled by an `Item`. As a bridge between the different worlds of `Block` and `Item`, there exists the class `BlockItem`. `BlockItem` is a subclass of `Item` that has a field `block` that holds a reference to the `Block` it represents. `BlockItem` defines some of the behavior of a "block" as an item, like how a right click places the block. It's possible to have a `Block` without an `BlockItem`. (E.g. `minecraft:water` exists a block, but not an item. It is therefore impossible to hold it in an inventory as one.) +!!! 重要 + 存档中的方块和物品栏中的“方块”是非常不同的东西。存档中的方块由`BlockState`表示,其行为由一个`Block`类的实例定义。同时,物品栏中的物品是由`Item`控制的`ItemStack`。作为`Block`和`Item`二者之间的桥梁,有一个`BlockItem`类。`BlockItem`是`Item`的一个子类,它有一个字段`block`,其中包含对它所代表的`Block`的引用。`BlockItem`将“方块”的一些行为定义为物品,例如右键单击如何放置方块。存在一个没有其`BlockItem`的`Block`也是可能的。(例如`minecraft:water`是一个方块,但不是一个物品。因此,不可能将其作为一个物品保存在物品栏中。) -When a block is registered, *only* a block is registered. The block does not automatically have an `BlockItem`. To create a basic `BlockItem` for a block, one should set the registry name of the `BlockItem` to that of its `Block`. Custom subclasses of `BlockItem` may be used as well. Once an `BlockItem` has been registered for a block, `Block#asItem` can be used to retrieve it. `Block#asItem` will return `Items#AIR` if there is no `BlockItem` for the `Block`, so if you are not certain that there is an `BlockItem` for the `Block` you are using, check for if `Block#asItem` returns `Items#AIR`. -::: + 当一个方块被注册时,也*仅仅*意味着一个方块被注册了。该方块不会自动具有`BlockItem`。要为块创建基本的`BlockItem`,应该将`BlockItem`的注册表名称设置为其`Block`的注册表名称。`BlockItem`的自定义子类也可以使用。一旦为方块注册了`BlockItem`,就可以使用`Block#asItem`来获取它。如果该方块没有`BlockItem`,`Block#asItem`将返回`Items#AIR`,因此,如果你不确定你正在使用的方块是否有`BlockItem`,请检查其`Block#asItem`是否返回`Items#AIR`。 -#### Optionally Registering Blocks +#### 选择性地注册方块 -In the past there have been several mods that have allowed users to disable blocks/items in a configuration file. However, you shouldn't do this. There is no limit on the amount of blocks that can be register, so register all blocks in your mod! If you want a block to be disabled through a configuration file, you should disable the crafting recipe. If you would like to disable the block in the creative tab, use a `FeatureFlag` when building the contents within [`BuildCreativeModeTabContentsEvent`][creativetabs]. +在过去,有一些模组允许用户在配置文件中禁用方块/物品。但是,你不应该这样做。允许注册的方块数量没有限制,所以请在你的模组中注册所有方块!如果你想通过配置文件禁用一个方块,你应该禁用其配方。如果要禁用创造模式物品栏中的方块,请在[`BuildCreativeModeTabContentsEvent`][creativetabs]中构建内容时使用`FeatureFlag`。 -Further Reading ---------------- +延伸阅读 +------- -For information about block properties, such as those used for vanilla blocks like fences, walls, and many more, see the section on [blockstates]. +有关方块属性的信息,例如用于栅栏、墙等原版方块的属性,请参见[方块状态][blockstates]部分。 [sounds]: ../gameeffects/sounds.md [creativetabs]: ../items/index.md#creative-tabs diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/blocks/states.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/blocks/states.md index f18bc385e..472a761ea 100644 --- a/i18n/zh-CN/docusaurus-plugin-content-docs/current/blocks/states.md +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/blocks/states.md @@ -1,76 +1,75 @@ -Block States -============ +方块状态 +======= -Legacy Behavior ---------------------------------------- +旧版本的行为 +----------- -In Minecraft 1.7 and previous versions, blocks which need to store placement or state data that did not have BlockEntities used **metadata**. Metadata was an extra number stored with the block, allowing different rotations, facings, or even completely separate behaviors within a block. +在Minecraft 1.7及以前的版本中,需要存储没有BlockEntity的位置或状态数据的方块使用**元数据(metadata)**。元数据是与方块一起存储的额外数字,允许方块不同的旋转、朝向,甚至完全独立的行为。 -However, the metadata system was confusing and limited, since it was stored as only a number alongside the block ID, and had no meaning except what was commented in the code. For example, to implement a block that can face a direction and be on either the upper or lower half of a block space (such as a stair): +然而,元数据系统是令人费解且有限度的,因为它只存储为方块ID旁边的一个数字,离开了代码中注释的内容后便没有任何意义。例如,要实现可以面向某个方向并且位于方块空间(例如楼梯)的上半部分或下半部分的方块,下列操作被执行: -```java +```Java switch (meta) { - case 0: { ... } // south and on the lower half of the block - case 1: { ... } // south on the upper side of the block - case 2: { ... } // north and on the lower half of the block - case 3: { ... } // north and on the upper half of the block + case 0: { ... } // 面向南方且位于方块的下半部分 + case 1: { ... } // 面向南方且位于方块的上半部分 + case 2: { ... } // 面向北方且位于方块的下半部分 + case 3: { ... } // 面向北方且位于方块的上半部分 // ... etc. ... } ``` -Because the numbers carry no meaning by themselves, no one could know what they represent unless they had access to the source code and comments. +因为这些数字本身没有任何意义,所以除非能够访问源代码和注释,否则没有人能够知道它们代表什么。 -Introduction of States ---------------------------------------- +状态简介 +------- -In Minecraft 1.8 and above, the metadata system, along with the block ID system, was deprecated and eventually replaced with the **block state system**. The block state system abstracts out the details of the block's properties from the other behaviors of the block. +在Minecraft 1.8及以上版本中,元数据系统和方块ID系统被弃用,最终被**方块状态系统**取代。方块状态系统从方块的其他行为中抽象出方块属性的细节。 -Each *property* of a block is described by an instance of `Property`. Examples of block properties include instruments (`EnumProperty`), facing (`DirectionProperty`), poweredness (`Property`), etc. Each property has the value of the type `T` parametrized by `Property`. +方块的每个*属性*都由`Property`的一个实例来描述。方块属性的示例包括乐器(`EnumProperty`)、朝向(`DirectionProperty`)、充能状态(`Property`)等。每个属性都具有由`Property`参数化后的类型`T`的值。 -A unique pair can be constructed from the `Block` and a map of the `Property` to their associated values. This unique pair is called a `BlockState`. +可以构建从`Block`和`Property`的Map到与它们相关联的值的唯一的对。这个唯一的对被称为`BlockState`。 -The previous system of meaningless metadata values were replaced by a system of block properties, which are easier to interpret and deal with. Previously, a stone button which is facing east and is powered or held down is represented by "`minecraft:stone_button` with metadata `9`. Now, this is represented by "`minecraft:stone_button[facing=east,powered=true]`". +以前无意义的元数据值系统被更容易解释和处理的方块属性系统所取代。以前,朝向东方并充能或按下的石头按钮由“带有元数据`9`的`minecraft:stone_button`”表示。现在,其用“`minecraft:stone_button[facing=east,powered=true]`”来表示。 -Proper Usage of Block States ---------------------------------------- +方块状态系统的正确用法 +-------------------- -The `BlockState` system is a flexible and powerful system, but it also has limitations. `BlockState`s are immutable, and all combinations of their properties are generated on startup of the game. This means that having a `BlockState` with many properties and possible values will slow down the loading of the game, and befuddle anyone trying to make sense of your block logic. +`BlockState`系统是一个灵活而强大的系统,但它也有局限性。`BlockState`是不可变的,其属性的所有组合都是在游戏启动时生成的。这意味着拥有一个具有许多属性和可能值的`BlockState`会减慢游戏的加载速度,并让任何试图理解你的方块逻辑的人感到困惑。 -Not all blocks and situations require the usage of `BlockState`; only the most basic properties of a block should be put into a `BlockState`, and any other situation is better off with having a `BlockEntity` or being a separate `Block`. Always consider if you actually need to use blockstates for your purposes. +并非所有方块和情况都需要使用`BlockState`;只有方块的最基本属性才应该被放入`BlockState`,而任何其他情况都最好有一个`BlockEntity`或是一个区分开的`Block`。要始终考虑是否确实需要出于你的目的而使用方块状态。 -:::note -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. -An "Oak Chair" facing east (`oak_chair[facing=east]`) is different from a "Spruce Chair" facing west (`spruce_chair[facing=west]`). +一个案例是制作椅子方块:椅子的*朝向*应该是一个*属性*,而*不同类型的木材*应该被分成不同的块。朝向东方的“橡木椅子”(`oak_chair[facing=east]`)与朝向西方的“云杉椅子”(`oak_chair[facing=east]`)不同。 -Implementing Block States ---------------------------------------- +实现方块状态 +----------- -In your Block class, create or reference `static final` `Property` objects for every property that your Block has. You are free to make your own `Property` implementations, but the means to do that are not covered in this article. The vanilla code provides several convenience implementations: +在你的Block类中,为你的方块所拥有的所有属性创建或引用`static final` `Property`对象。你尽可以创建自己的`Property`实现,但本文没有介绍实现的方法。原版代码提供了几个方便的实现: * `IntegerProperty` - * Implements `Property`. Defines a property that holds an integer value. - * 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`中的4个)。请参阅`EnumProperty#create`的重载。 * `DirectionProperty` - * This is a convenience implementation of `EnumProperty` + * 这是`EnumProperty`的一个便利实现。 * Several convenience predicates are also 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)`. + * 也提供了一些便利的Predicate。例如,要获得一个表示基本方向的属性,请调用`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`包含共有的原版属性,应该尽可能先使用或引用这些属性,而不是创建自己的属性。 -When you have your desired `Property<>` objects, override `Block#createBlockStateDefinition(StateDefinition$Builder)` in your Block class. In that method, call `StateDefinition$Builder#add(...);` with the parameters as every `Property` you wish the block to have. +当你拥有所需的`Property<>`对象时,请重写你的Block类中的`Block#createBlockStateDefinition(StateDefinition$Builder)`。在该方法中,使用你希望这个方块具有的每个`Property`作为参数调用`StateDefinition$Builder#add(...);`。 -Every block will also have a "default" state that is automatically chosen for you. You can change this "default" state by calling the `Block#registerDefaultState(BlockState)` method from your constructor. When your block is placed it will become this "default" state. An example from `DoorBlock`: +每个方块也将有一个自动为你选择的“默认”状态。你可以通过从构造函数中调用`Block#registerDefaultState(BlockState)`方法来更改此“默认”状态。放置方块后,它将变为此“默认”状态。如`DoorBlock`的一个案例: -```java +```Java this.registerDefaultState( this.stateDefinition.any() .setValue(FACING, Direction.NORTH) @@ -81,16 +80,15 @@ this.registerDefaultState( ); ``` -If you wish to change what `BlockState` is used when placing your block, you can overwrite `Block#getStateForPlacement(BlockPlaceContext)`. This can be used to, for example, set the direction of your block depending on where the player is standing when they place it. +如果你希望更改放置方块时使用的`BlockState`,可以重写`Block#getStateForPlacement(BlockPlaceContext)`。例如,这可以用来根据玩家放置方块时所站的位置来设置方块的方向。 -Because `BlockState`s are immutable, and all combinations of their properties are generated on startup of the game, calling `BlockState#setValue(Property, T)` will simply go to the `Block`'s `StateHolder` and request the `BlockState` with the set of values you want. +由于`BlockState`是不可变的,并且其属性的所有组合都是在游戏启动时生成的,因此调用`BlockState#setValue(Property, T)`将只需转到`Block`的`StateHolder`并请求具有你所需的一系列值的`BlockState`。 -Because all possible `BlockState`s are generated at startup, you are free and encouraged to use the reference equality operator (`==`) to check if two `BlockState`s are equal. +因为所有可能的`BlockState`都是在启动时生成的,所以你可以自由地使用引用相等运算符(`==`)来检查两个`BlockState`是否相等。 -Using `BlockState`'s ---------------------- +使用`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. -If you want to get a `BlockState` with a different set of values, simply call `BlockState#setValue(Property, T)` with the property and its value. +你可以通过调用`BlockState#getValue(Property)`来获取某个属性的值,并将要获取值的属性传递给它。如果你想获得一个具有不同的一系列值的`BlockState`,只需使用该属性及其值调用`BlockState#setValue(Property, T)`。 -You can get and place `BlockState`'s in the level using `Level#setBlockAndUpdate(BlockPos, BlockState)` and `Level#getBlockState(BlockPos)`. If you are placing a `Block`, call `Block#defaultBlockState()` to get the "default" state, and use subsequent calls to `BlockState#setValue(Property, T)` as stated above to achieve the desired state. +你可以使用`Level#setBlockAndUpdate(BlockPos, BlockState)`和`Level#getBlockState(BlockPos)`在存档中获取并放置`BlockState`。如果你正在放置一个`Block`,请调用`Block#defaultBlockState()`以获得“默认”状态,并使用对`BlockState#setValue(Property, T)`的后续调用,如上所述,以保存所需状态。 diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/concepts/events.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/concepts/events.md index d3982b36e..8f4c93013 100644 --- a/i18n/zh-CN/docusaurus-plugin-content-docs/current/concepts/events.md +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/concepts/events.md @@ -1,44 +1,44 @@ -Events -====== +事件 +==== -Forge uses an event bus that allows mods to intercept events from various Vanilla and mod behaviors. +Forge使用事件总线以允许模组拦截来自各种原版和模组行为的事件。 -Example: An event can be used to perform an action when a Vanilla stick is right clicked. +例如:右键单击原版的木棍时,一个事件可被触发以用于执行操作。 -The main event bus used for most events is located at `MinecraftForge#EVENT_BUS`. There is another event bus for mod specific events located at `FMLJavaModLoadingContext#getModEventBus` that you should only use in specific cases. More information about this bus can be found below. +用于大多数事件的主事件总线位于`MinecraftForge#EVENT_BUS`。在`FMLJavaModLoadingContext#getModEventBus`中还有另一个用于特定于模组事件的事件总线,你应该只在特定情况下使用它。关于该事件总线的更多信息可以在下面找到。 -Every event is fired on one of these busses: most events are fired on the main forge event bus, but some are fired on the mod specific event buses. +每个事件都在其中一条总线上触发:大多数事件在主要的Forge事件总线上触发,但也有一些在特定于模组的事件总线上触发。 -An event handler is some method that has been registered to an event bus. +事件处理器是某个已注册到事件总线的方法。 -Creating an Event Handler -------------------------- +创建一个事件处理器 +---------------- -Event handlers methods have a single parameter and do not return a result. The method could be static or instance depending on implementation. +事件处理器方法只有一个参数,不返回结果。该方法可以是静态的,也可以是实例化的,具体取决于实现。 -Event handlers can be directly registered using `IEventBus#addListener` for or `IEventBus#addGenericListener` for generic events (as denoted by subclassing `GenericEvent`). Either listener adder takes in a consumer representing the method reference. Generic event handlers need to specify the class of the generic as well. Event handlers must be registered within the constructor of the main mod class. +事件处理器可以使用`IEventBus#addListener`直接注册,或对于泛型事件(`GenericEvent`的子类)使用`IEventBus#addGenericListener`直接注册。任一监听器注册方法接收表示方法引用的Consumer。泛型事件处理器还需要指定泛型的具体类型。事件处理器必须在模组主类的构造函数中注册。 ```java -// In the main mod class ExampleMod +// 在模组主类ExampleMod中 -// This event is on the mod bus +// 该事件位于模组事件总线上 private void modEventHandler(RegisterEvent event) { // Do things here } -// This event is on the forge bus +// 该事件位于Forge事件总线上 private static void forgeEventHandler(AttachCapabilitiesEvent event) { // ... } -// In the mod constructor +// 在模组构造函数内 modEventBus.addListener(this::modEventHandler); forgeEventBus.addGenericListener(Entity.class, ExampleMod::forgeEventHandler); ``` -### Instance Annotated Event Handlers +### 实例化的已注释的事件处理器 -This event handler listens for the `EntityItemPickupEvent`, which is, as the name states, posted to the event bus whenever an `Entity` picks up an item. +该事件处理器监听`EntityItemPickupEvent`,正如名称所述,每当`Entity`拾取一件物品时,该事件就会被发布到事件总线。 ```java public class MyForgeEventHandler { @@ -49,11 +49,11 @@ public class MyForgeEventHandler { } ``` -To register this event handler, use `MinecraftForge.EVENT_BUS.register(...)` and pass it an instance of the class the event handler is within. If you want to register this handler to the mod specific event bus, you should use `FMLJavaModLoadingContext.get().getModEventBus().register(...)` instead. +要注册这个事件处理器,请使用`MinecraftForge.EVENT_BUS.register(...)`并向其传递事件处理器所在类的一个实例。如果要将此处理器注册到特定于模组的事件总线,则应使用`FMLJavaModLoadingContext.get().getModEventBus().register(...)`。 -### Static Annotated Event Handlers +### 静态的已注释的事件处理器 -An event handler may also be static. The handling method is still annotated with `@SubscribeEvent`. The only difference from an instance handler is that it is also marked `static`. In order to register a static event handler, an instance of the class won't do. The `Class` itself has to be passed in. An example: +事件处理器也可以是静态的。处理事件的方法仍然使用`@SubscribeEvent`进行注释。与实例化的事件处理器的唯一区别是它也被标记为`static`。要注册静态的事件处理器,传入类的实例是不行的。必须传入类本身。例如: ```java public class MyStaticForgeEventHandler { @@ -64,15 +64,15 @@ public class MyStaticForgeEventHandler { } ``` -which must be registered like this: `MinecraftForge.EVENT_BUS.register(MyStaticForgeEventHandler.class)`. +其必须像这样注册:`MinecraftForge.EVENT_BUS.register(MyStaticForgeEventHandler.class)`。 -### Automatically Registering Static Event Handlers +### 自动注册静态的事件处理器 -A class may be annotated with the `@Mod$EventBusSubscriber` annotation. Such a class is automatically registered to `MinecraftForge#EVENT_BUS` when the `@Mod` class itself is constructed. This is essentially equivalent to adding `MinecraftForge.EVENT_BUS.register(AnnotatedClass.class);` at the end of the `@Mod` class's constructor. +类可以使用`@Mod$EventBusSubscriber`进行注释。当`@Mod`类本身被构造时,这样的类会自动注册到`MinecraftForge#EVENT_BUS`。这实质上相当于在`@Mod`类的构造函数的末尾添加`MinecraftForge.EVENT_BUS.register(AnnotatedClass.class);`。 -You can pass the bus you want to listen to the `@Mod$EventBusSubscriber` annotation. It is recommended you also specify the mod id, since the annotation process may not be able to figure it out, and the bus you are registering to, since it serves as a reminder to make sure you are on the correct one. You can also specify the `Dist`s or physical sides to load this event subscriber on. This can be used to not load client specific event subscribers on the dedicated server. +你可以向`@Mod$EventBusSubscriber`注释指明所要监听的总线。建议你也指定mod id,因为注释在处理的过程中可能无法确定它,以及你所注册的总线,因为它作为一个保障可以确保你所注册的是正确的总线。你还可以指定要加载此事件处理器的`Dist`或物理端。这可用于保证不在dedicated服务器上加载客户端特定的事件处理器。 -An example for a static event listener listening to `RenderLevelStageEvent` which will only be called on the client: +下面是静态事件处理器监听`RenderLevelStageEvent`的示例,该处理器将仅在客户端上调用: ```java @Mod.EventBusSubscriber(modid = "mymod", bus = Bus.FORGE, value = Dist.CLIENT) @@ -84,61 +84,57 @@ public class MyStaticClientOnlyEventHandler { } ``` -:::note -This does not register an instance of the class; it registers the class itself (i.e. the event handling methods must be static). -::: +!!! 注意 + 这不会注册类的实例;它注册类本身(即事件处理方法必须是静态的)。 -Canceling +事件的取消 --------- -If an event can be canceled, it will be marked with the `@Cancelable` annotation, and the method `Event#isCancelable()` will return `true`. The cancel state of a cancelable event may be modified by calling `Event#setCanceled(boolean canceled)`, wherein passing the boolean value `true` is interpreted as canceling the event, and passing the boolean value `false` is interpreted as "un-canceling" the event. However, if the event cannot be canceled (as defined by `Event#isCancelable()`), an `UnsupportedOperationException` will be thrown regardless of the passed boolean value, since the cancel state of a non-cancelable event event is considered immutable. +如果一个事件可以被取消,它将带有`@Cancelable`注释,并且方法`Event#isCancelable()`将返回`true`。可取消事件的取消状态可以通过调用`Event#setCanceled(boolean canceled)`来修改,其中传递布尔值`true`意为取消事件,传递布尔值`false`被解释为“不取消”事件。但是,如果无法取消事件(如`Event#isCancelable()`所定义),则无论传递的布尔值如何,都将抛出`UnsupportedOperationException`,因为不可取消事件事件的取消状态被认为是不可变的。 -:::danger -Not all events can be canceled! Attempting to cancel an event that is not cancelable will result in an unchecked `UnsupportedOperationException` being thrown, which is expected to result in the game crashing! Always check that an event can be canceled using `Event#isCancelable()` before attempting to cancel it! -::: +!!! 重要 + 并非所有事件都可以取消!试图取消不可取消的事件将导致抛出未经检查的`UnsupportedOperationException`,可能将导致游戏崩溃!在尝试取消某个事件之前,请始终使用`Event#isCancelable()`检查该事件是否可以取消! -Results -------- +事件的结果 +--------- -Some events have an `Event$Result`. A result can be one of three things: `DENY` which stops the event, `DEFAULT` which uses the Vanilla behavior, and `ALLOW` which forces the action to take place, regardless if it would have originally. The result of an event can be set by calling `#setResult` with an `Event$Result` on the event. Not all events have results; an event with a result will be annotated with `@HasResult`. +某些事件具有`Event$Result`。结果可以是以下三种情况之一:`DENY`(停止事件)、`DEFAULT`(使用默认行为)和`ALLOW`(强制执行操作,而不管最初是否执行)。事件的结果可以通过调用`#setResult`并用一个`Event$Result`来设置。并非所有事件都有结果;带有结果的事件将用`@HasResult`进行注释。 -:::caution -Different events may use results in different ways, refer to the event's JavaDoc before using the result. -::: +!!! 重要 + 不同的事件可能以不同的方式处理结果,在使用事件的结果之前请参阅事件的JavaDoc。 -Priority --------- +事件处理优先级 +------------- -Event handler methods (marked with `@SubscribeEvent`) have a priority. You can set the priority of an event handler method by setting the `priority` value of the annotation. The priority can be any value of the `EventPriority` enum (`HIGHEST`, `HIGH`, `NORMAL`, `LOW`, and `LOWEST`). Event handlers with priority `HIGHEST` are executed first and from there in descending order until `LOWEST` events which are executed last. +事件处理方法(用`@SubscribeEvent`标记)具有优先级。你可以通过设置注释的`priority`值来安排事件处理方法的优先级。优先级可以是`EventPriority`枚举的任何值(`HIGHEST`、`HIGH`、`NORMAL`、`LOW`和`LOWEST`)。优先级为`HIGHEST`的事件处理器首先执行,然后按降序执行,直到最后执行的`LOWEST`为止。 -Sub Events ----------- +子事件 +------ -Many events have different variations of themselves. These can be different but all based around one common factor (e.g. `PlayerEvent`) or can be an event that has multiple phases (e.g. `PotionBrewEvent`). Take note that if you listen to the parent event class, you will receive calls to your method for *all* subclasses. +许多事件本身都有不同的变体。这些变体事件可以不尽相同,但都基于一个共同的因素(例如`PlayerEvent`),也可以是具有多个阶段的事件(例如`PotionBrewEvent`)。请注意,如果你监听父类事件,你的事件处理方法也将收到其*所有*子类事件。 -Mod Event Bus -------------- +模组事件总线 +----------- -The mod event bus is primarily used for listening to lifecycle events in which mods should initialize. Each event on the mod bus is required to implement `IModBusEvent`. Many of these events are also ran in parallel so mods can be initialized at the same time. This does mean you can't directly execute code from other mods in these events. Use the `InterModComms` system for that. +模组事件总线主要用于监听模组应该初始化的生命周期事件。模组总线上的每个事件类型都需要实现`IModBusEvent`。其中许多事件也是并行运行的(多线程——译者注),因此多个模组可以同时被初始化。这意味着你不能在这些事件中直接执行来自其他模组的代码。为此,请使用`InterModComms`系统。 -These are the four most commonly used lifecycle events that are called during mod initialization on the mod event bus: +以下是在模组事件总线上的模组初始化期间调用的四个最常用的生命周期事件: * `FMLCommonSetupEvent` -* `FMLClientSetupEvent` & `FMLDedicatedServerSetupEvent` +* `FMLClientSetupEvent`和`FMLDedicatedServerSetupEvent` * `InterModEnqueueEvent` * `InterModProcessEvent` -:::note -The `FMLClientSetupEvent` and `FMLDedicatedServerSetupEvent` are only called on their respective distribution. -::: +!!! 注意 + `FMLClientSetupEvent`和`FMLDedicatedServerSetupEvent`仅在各自的分发版本(物理端——译者注)上调用。 -These four lifecycle events are all ran in parallel since they all are a subclass of `ParallelDispatchEvent`. If you want to run run code on the main thread during any `ParallelDispatchEvent`, you can use the `#enqueueWork` to do so. +这四个生命周期事件都是并行运行的,因为它们都是`ParallelDispatchEvent`的子类。如果你想在任何`ParallelDispatchEvent`期间在主线程上运行运行代码,可以使用`#enqueueWork`来执行此操作。 -Next to the lifecycle events, there are a few miscellaneous events that are fired on the mod event bus 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` * `RegisterEvent` -A good rule of thumb: events are fired on the mod event bus when they should be handled during initialization of a mod. +一个很好的经验法则是:当事件应该在模组初始化期间处理时,就在模组事件总线上触发事件。 diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/concepts/internationalization.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/concepts/internationalization.md index 69d127283..3e8272030 100644 --- a/i18n/zh-CN/docusaurus-plugin-content-docs/current/concepts/internationalization.md +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/concepts/internationalization.md @@ -1,16 +1,16 @@ -Internationalization and Localization -===================================== +国际化与本地化 +============= -Internationalization, i18n for short, is a way of designing code so that it requires no changes to be adapted for various languages. Localization is the process of adapting displayed text to the user's language. +国际化(Internationalization),简称I18n,是一种设计代码的方式,以便不需要进行任何更改即可适应各种语言。本地化(Localization)是使显示的文本适应用户语言的过程。 -I18n is implemented using _translation keys_. A translation key is a string that identifies a piece of displayable text in no specific language. For example, `block.minecraft.dirt` is the translation key referring to the name of the Dirt block. This way, displayable text may be referenced with no concern for a specific language. The code requires no changes to be adapted in a new language. +I18n是使用 _翻译键_ 来实现的。翻译键是一个字符串,用于指定一段不使用特定语言的可显示文本。例如,`block.minecraft.dirt`是引用泥土方块名称的翻译键。这样,可显示文本可被引用,而不必考虑特定的语言。这些代码不需要任何更改即可适应新的语言。 -Localization will happen in the game's locale. In a Minecraft client the locale is specified by the language settings. On a dedicated server, the only supported locale is `en_us`. A list of available locales can be found on the [Minecraft Wiki][langs]. +本地化将在游戏的语言设置中进行。在Minecraft客户端中,语言环境由语言设置指定。在dedicated服务端上,唯一支持的语言设置是`en_us`。可用语言地区的列表可以在[Minecraft Wiki][langs]上找到。 -Language files --------------- +语言文件 +------- -Language files are located by `assets/[namespace]/lang/[locale].json` (e.g. all US English translations provided by `examplemod` would be within `assets/examplemod/lang/en_us.json`). The file format is simply a json map from translation keys to values. The file must be encoded in UTF-8. Old .lang files can be converted to json using a [converter][converter]. +语言文件由`assets/[namespace]/lang/[locale].json`定位(例如,`examplemod`提供的所有美国英语翻译都在`assets/examplemod/lang/en_us.json`中)。文件格式只是从翻译键到值的json映射。文件必须使用UTF-8编码。可以使用[转换器][converter]将旧的.lang文件转换为json。 ```js { @@ -20,12 +20,12 @@ Language files are located by `assets/[namespace]/lang/[locale].json` (e.g. all } ``` -Usage with Blocks and Items ---------------------------- +对方块和物品的用法 +----------------- -Block, Item and a few other Minecraft classes have built-in translation keys used to display their names. These translation keys are specified by overriding `#getDescriptionId`. Item also has `#getDescriptionId(ItemStack)` which can be overridden to provide different translation keys depending on ItemStack NBT. +Block、Item和其他一些Minecraft类都内置了用于显示其名称的翻译键。这些转换键是通过重写`#getDescriptionId`指定的。Item还具有`#getDescriptionId(ItemStack)`,重写该方法后可以根据所给ItemStack NBT提供不同的翻译键。 -By default, `#getDescriptionId` will return `block.` or `item.` prepended to the registry name of the block or item, with the colon replaced by a dot. `BlockItem`s override this method to take their corresponding `Block`'s translation key by default. For example, an item with ID `examplemod:example_item` effectively requires the following line in a language file: +默认情况下,`#getDescriptionId`将返回以`block.`或`item.`为前缀的方块或物品的注册表名称,冒号由句点代替。默认情况下,`BlockItem`覆盖此方法以获取其对应的`Block`的翻译密钥。例如,ID为`examplemod:example_item`的物品实际上需要语言文件中的以下行: ```js { @@ -33,39 +33,35 @@ By default, `#getDescriptionId` will return `block.` or `item.` prepended to the } ``` -:::note -The only purpose of a translation key is internationalization. Do not use them for logic. Use registry names instead. -::: +!!! 注意 + 翻译键的唯一目的是国际化。不要把它们用于代码的逻辑处理部分。请改用注册表名称。 -Localization methods --------------------- -:::caution -A common issue is having the server localize for clients. The server can only localize in its own locale, which does not necessarily match the locale of connected clients. +本地化相关方法 +------------- -To respect the language settings of clients, the server should have clients localize text in their own locale using `TranslatableContents` or other methods preserving the language neutral translation keys. -::: +!!! 警告 + 一个常见的问题是让服务端为客户端进行本地化。服务端只能在自己的语言设置中进行本地化,这不一定与所连接的客户端的语言设置相匹配。 + + 为了尊重客户端的语言设置,服务端应该让客户端使用`TranslatableComponent`或其他保留语言中性翻译键的方法在自己的语言设置中本地化文本。 -### `net.minecraft.client.resources.language.I18n` (client only) +### `net.minecraft.client.resources.language.I18n` (仅客户端) -**This I18n class can only be found on a Minecraft client!** It is intended to be used by code that only runs on the client. Attempts to use this on a server will throw exceptions and crash. +**这个I18n类仅在Minecraft客户端上有效!**它旨在由仅在客户端上运行的代码使用。尝试在服务端上使用它会引发异常并崩溃。 -- `get(String, Object...)` localizes in the client's locale with formatting. The first parameter is a translation key, and the rest are formatting arguments for `String.format(String, Object...)`. +- `get(String, Object...)`使用格式采取客户端的语言设置进行本地化。第一个参数是翻译键,其余的是`String.format(String, Object...)`的格式化参数。 ### `TranslatableContents` -`TranslatableContents` is a `ComponentContents` that is localized and formatted lazily. It is very useful when sending messages to players because it will be automatically localized in their own locale. +`TranslatableContents`是一个经过惰性的本地化和格式化的`ComponentContents`。它在向玩家发送消息时非常有用,因为它将在玩家自己的语言设置中自动本地化。 -The first parameter of the `TranslatableContents(String, Object...)` constructor is a translation key, and the rest are used for [formatting]. +`TranslatableContents(String, Object...)`构造函数的第一个参数是翻译键,其余参数用于格式化。唯一支持的格式说明符是`%s`和`%1$s`、`%2$s`、`%3$s`等。格式化参数可能是将插入到格式化结果文本中并保留其所有属性的`Component`。 -A `MutableComponent` can be created using `Component#translatable` by passing in the `TranslatableContents`'s parameters. It can also be created using `MutableComponent#create` by passing in the `ComponentContents` itself. -Read [components] for more details. +通过传入`TranslatableContents`的参数,可以使用`Component#translatable`创建`MutableComponent`。它也可以使用`MutableComponent#create`通过传入`ComponentContents`本身来创建。 ### `TextComponentHelper` -- `createComponentTranslation(CommandSource, String, Object...)` is useful for sending messages between clients and the server. If the receiver is a vanilla client, the method will eagerly localize and format the provided translation key in sender's locale, or American English if no locale is loaded; the modded server may allow vanilla clients to join, and they will lack localization data required to localize the message itself. Otherwise, the method will create the component with `TranslatableContents`. +- `createComponentTranslation(CommandSource, String, Object...)`根据接收者创建本地化并格式化的`MutableComponent`。如果接收者是一个原版客户端,那么本地化和格式化就很容易完成。如果没有,本地化和格式化将使用包含`TranslatableContents`的`Component`惰性地进行。只有当服务端允许原版客户端连接时,这才有用。 -[langs]: https://minecraft.wiki/w/Language#Languages +[langs]: https://minecraft.fandom.com/wiki/Language#Languages [converter]: https://tterrag.com/lang2json/ -[formatting]: ../misc/components.md#text-formatting -[components]: ../misc/components.md diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/concepts/lifecycle.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/concepts/lifecycle.md index 99e66f2fd..c221dcdcd 100644 --- a/i18n/zh-CN/docusaurus-plugin-content-docs/current/concepts/lifecycle.md +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/concepts/lifecycle.md @@ -1,11 +1,11 @@ -Mod Lifecycle -============== +模组生命周期 +=========== -During the mod loading process, the various lifecycle events are fired on the mod-specific event bus. Many actions are performed during these events, such as [registering objects][registering], preparing for [data generation][datagen], or [communicating with other mods][imc]. +在模组加载过程中,各种生命周期事件在模组特定的事件总线上触发。在这些事件期间许多操作被执行,例如[注册对象][registering]、准备[数据生成][datagen]或[与其他模组通信][imc]。 -Event listeners should be registered either using `@EventBusSubscriber(bus = Bus.MOD)` or in the mod constructor: +事件监听器应使用`@EventBusSubscriber(bus = Bus.MOD)`或在模组构造函数中被注册: -```java +```Java @Mod.EventBusSubscriber(modid = "mymod", bus = Mod.EventBusSubscriber.Bus.MOD) public class MyModEventSubscriber { @SubscribeEvent @@ -22,52 +22,50 @@ public class MyMod { } ``` -:::caution -Most of the lifecycle events are fired in parallel: all mods will concurrently receive the same event. +!!! 警告 + 大多数生命周期事件都是并行触发的(多线程——译者注):所有模组都将同时接收相同的事件。 + + 模组必须注意线程安全,就像调用其他模组的API或访问原版系统一样。延迟代码,以便稍后通过`ParallelDispatchEvent#enqueueWork`执行。 -Mods *must* take care to be thread-safe, like when calling other mods' APIs or accessing vanilla systems. Defer code for later execution via `ParallelDispatchEvent#enqueueWork`. -::: +注册表事件 +--------- -Registry Events ---------------- +注册表事件是在模组实例构造之后激发的。注册表事件有三种:`NewRegistryEvent`、`DataPackRegistryEvent$NewRegistry`和`RegisterEvent`。这些事件在模组加载期间同步触发。 -The registry events are fired after the mod instance construction. There are three: `NewRegistryEvent`, `DataPackRegistryEvent$NewRegistry` and `RegisterEvent`. These events are fired synchronously during mod loading. +`NewRegistryEvent`允许模组开发者使用`RegistryBuilder`类注册自己的自定义注册表。 -`NewRegistryEvent` allows modders to register their own custom registries, using the `RegistryBuilder` class. +`DataPackRegistryEvent$NewRegistry`允许模组开发者通过提供`Codec`对JSON中的对象进行编码和解码来注册自定义数据包注册表。 -`DataPackRegistryEvent$NewRegistry` allows modders to register custom datapack registries by providing a `Codec` to encode and decode the object from JSON. +`RegisterEvent`用于[将对象注册到注册表中][registering]。每个注册表都会触发该事件。 -`RegisterEvent` is for [registering objects][registering] into the registries. The event is fired for each registry. +数据生成 +------- -Data Generation ---------------- +如果游戏被设置为运行[数据生成器][datagen],那么`GatherDataEvent`将是最后一个触发的事件。此事件用于将模组的数据提供者注册到其关联的数据生成器。此事件也是同步触发的。 -If the game is setup to run [data generators][datagen], then the `GatherDataEvent` will be the last event to fire. This event is for registering mods' data providers to their associated data generator. This event is also fired synchronously. +通用初始化 +--------- -Common Setup ------------- +`FMLCommonSetupEvent`用于物理客户端和物理服务端通用的操作,例如注册[Capability][capabilities]。 -`FMLCommonSetupEvent` is for actions that are common to both physical client and server, such as registering [capabilities][capabilities]. +单端初始化 +--------- -Sided Setup ------------ - -The sided-setup events are fired on their respective [physical sides][sides]: `FMLClientSetupEvent` on the physical client, and `FMLDedicatedServerSetupEvent` for the dedicated server. This is where physical side-specific initialization should occur, such as registering client-side key bindings. +单端初始化事件在其各自的[物理端][sides]触发:物理客户端上触发`FMLClientSetupEvent`,dedicated服务端上触发`FMLDedicatedServerSetupEvent`。这就是应该进行各物理端特定的初始化的地方,例如注册客户端键盘绑定。 InterModComms ------------- -This is where messages can be sent to mods for cross-mod compatibility. There are two events: `InterModEnqueueEvent` and `InterModProcessEvent`. +这是模组间可以相互通信以实现跨模组兼容性的地方。有两个相关的事件:`InterModEnqueueEvent`和`InterModProcessEvent`。 -`InterModComms` is the class responsible for holding messages for mods. The methods are safe to call during the lifecycle events, as it is backed by a `ConcurrentMap`. +`InterModComms`是负责为模组间交换消息的类。其方法在生命周期事件期间可以安全调用,因为它有`ConcurrentMap`支持。 -During the `InterModEnqueueEvent`, use `InterModComms#sendTo` to send messages to different mods. These methods take in the mod id that will be sent the message, the key associated with the message data, and a supplier holding the message data. Additionally, the sender of the message can also be specified, but by default it will be the mod id of the caller. +在`InterModEnqueueEvent`期间,使用`InterModComms#sendTo`以向不同的模组发送消息。这些方法接收所发消息的目的模组的mod id、与消息数据相关的键以及持有消息数据的Supplier。此外,还可以指定消息的发送者,但默认情况下,它将是调用者的mod id。 -Then during the `InterModProcessEvent`, use `InterModComms#getMessages` to get a stream of all received messages. The mod id supplied will almost always be the mod id of the mod the method is called on. Additionally, a predicate can be specified to filter out the message keys. This will return a stream of `IMCMessage`s which hold the sender of the data, the receiver of the data, the data key, and the supplied data itself. +之后在`InterModProcessEvent`期间,使用`InterModComms#getMessages`获取所有接收到的消息的Stream。提供的mod id几乎总是先前调用发送消息方法的模组的mod id。此外,可以指定一个Predicate来对消息键进行过滤。这将返回一个带有`IMCMessages`的Stream,其中包含数据的发送方、数据的接收方、数据键以及所提供的数据本身。 -:::tip -There are two other lifecycle events: `FMLConstructModEvent`, fired directly after mod instance construction but before the `RegisterEvent`, and `FMLLoadCompleteEvent`, fired after the `InterModComms` events, for when the mod loading process is complete. -::: +!!! 注意 + 还有另外两个生命周期事件:`FMLConstructModEvent`,在模组实例构造之后但在`RegisterEvent`之前直接触发;`FMLLoadCompleteEvent`,在`InterModComms`事件之后触发,用于模组加载过程完成时。 [registering]: ./registries.md#methods-for-registering [capabilities]: ../datastorage/capabilities.md diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/concepts/registries.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/concepts/registries.md index 22325c439..59fc70ba4 100644 --- a/i18n/zh-CN/docusaurus-plugin-content-docs/current/concepts/registries.md +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/concepts/registries.md @@ -1,22 +1,22 @@ -Registries -========== +注册表 +====== -Registration is the process of taking the objects of a mod (such as items, blocks, sounds, 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. +注册是获取模组的对象(如物品、方块、音效等)并使其为游戏所知的过程。注册东西很重要,因为如果没有注册,游戏将根本不知道这些对象,这将导致无法解释的行为和崩溃。 -Most things that require registration in the game are handled by the Forge registries. A registry is an object similar to a map that assigns values to keys. Forge uses registries with [`ResourceLocation`][ResourceLocation] keys to register objects. This allows the `ResourceLocation` to act as the "registry name" for objects. +游戏中的大多数注册相关事项都由Forge注册表处理。注册表是一个与为键分配值的Map的行为类似的对象。Forge使用带有[`ResourceLocation`][ResourceLocation]键的注册表来注册对象。这允许`ResourceLocation`充当对象的“注册表名称”。 -Every type of registrable object has its own registry. To see all registries wrapped by Forge, see the `ForgeRegistries` class. All registry names within a registry must be unique. However, names in different registries will not collide. For example, there's a `Block` registry, and an `Item` registry. A `Block` and an `Item` may be registered with the same name `example:thing` without colliding; however, if two different `Block`s or `Item`s were registered with the same exact name, the second object will override the first. +每种类型的可注册对象都有自己的注册表。要查看由Forge封装的所有注册表,请参阅`ForgeRegistries`类。注册表中的所有注册表名称必须是唯一的。但是,不同注册表中的名称不会发生冲突。例如,有一个`Block`注册表和一个`Item`注册表。一个方块和一个物品可以用相同的名称`example:thing`注册而不冲突;但是,如果两个不同的方块(或物品)以相同的名称被注册,则第二个对象将覆盖第一个对象。 -Methods for Registering ------------------- +注册的方式 +--------- -There are two proper ways to register objects: the `DeferredRegister` class, and the `RegisterEvent` lifecycle event. +有两种正确的方式来注册对象:`DeferredRegister`类和`RegisterEvent`生命周期事件。 ### DeferredRegister -`DeferredRegister` is the recommended way to register objects. It allows the use and convenience of static initializers while avoiding the issues associated with it. It simply maintains a list of suppliers for entries and registers the objects from those suppliers during `RegisterEvent`. +`DeferredRegister`是注册对象的推荐方式。它包容静态初始化的使用与便利,同时也避免与之相关的问题。它只需维护一系列的Supplier,并在`RegisterEvent`期间注册这些Supplier所提供的对象。(Supplier是Java 8加入的新语法。——译者注) -An example of a mod registering a custom block: +以下是一个模组注册一个自定义方块的案例: ```java private static final DeferredRegister BLOCKS = DeferredRegister.create(ForgeRegistries.BLOCKS, MODID); @@ -30,9 +30,9 @@ public ExampleMod() { ### `RegisterEvent` -`RegisterEvent` is the second way to register objects. This [event] is fired for each registry after the mod constructors and before the loading of configs. Objects are registered using `#register` by passing in the registry key, the name of the registry object, and the object itself. There is an additional `#register` overload which takes in a consumed helper to register an object with a given name. It is recommended to use this method to avoid unnecessary object creation. +`RegisterEvent`是注册对象的第二种方式。在模组构造函数之后和加载configs之前,该[事件][event]会为每个注册表激发。对象通过调用`#register`并传入注册表键、注册表对象的名称和对象本身而得以注册。还有一个额外的`#register`重载,它接收一个已使用的助手来注册具有给定名称的对象。建议使用此方法以避免不必要的对象创建。 -Here is an example: (the event handler is registered on the *mod event bus*) +案例如下:(事件处理器已被注册到*模组事件总线*) ```java @SubscribeEvent @@ -48,13 +48,12 @@ public void register(RegisterEvent event) { } ``` -### Registries that aren't Forge Registries +### 未被Forge封装的注册表 -Not all registries are wrapped by Forge. These can be static registries, like `LootItemConditionType`, which are safe to use. There are also dynamic registries, like `ConfiguredFeature` and some other worldgen registries, which are typically represented in JSON. `DeferredRegister#create` has an overload which allows modders to specify the registry key of which vanilla registry to create a `RegistryObject` for. The registry method and attaching to the mod event bus is the same as other `DeferredRegister`s. +并非所有的注册表都由Forge封装。这些可以是静态注册表,如`LootItemConditionType`,使用起来是安全的。还有动态注册表,如`ConfiguredFeature`和其他一些世界生成注册表,它们通常以JSON表示。`DeferredRegister#create`有一个重载,允许模组开发者指定原版注册表所创建的`RegistryObject`的注册表键。注册表方法和模组事件总线的附加与其他`DeferredRegister`相同。 -:::danger -Dynamic registry objects can **only** be registered through data files (e.g. JSON). They **cannot** be registered in-code. -::: +!!! 重要 + 动态注册表对象**只能**通过数据文件(如JSON)被注册。它们**不能**在代码中被注册。 ```java private static final DeferredRegister REGISTER = DeferredRegister.create(Registries.LOOT_CONDITION_TYPE, "examplemod"); @@ -62,137 +61,132 @@ private static final DeferredRegister REGISTER = Deferred public static final RegistryObject EXAMPLE_LOOT_ITEM_CONDITION_TYPE = REGISTER.register("example_loot_item_condition_type", () -> new LootItemConditionType(...)); ``` -:::note -Some classes cannot by themselves be registered. Instead, `*Type` classes are registered, and used in the formers' constructors. For example, [`BlockEntity`][blockentity] has `BlockEntityType`, and `Entity` has `EntityType`. These `*Type` classes are factories that simply create the containing type on demand. +!!! 注意 + 有些类无法自行注册。相反,`*Type`类被注册,并在前者的构造函数中被使用。例如,[`BlockEntity`][blockentity]具有`BlockEntityType`,`Entity`具有`EntityType`。这些`*Type`类是工厂,它们只是根据需要创建包含类型。 + + 这些工厂是通过使用它们的`*Type$Builder`类创建的。例如:(`REGISTER`指的是`DeferredRegister`) + ```java + public static final RegistryObject> EXAMPLE_BLOCK_ENTITY = REGISTER.register( + "example_block_entity", () -> BlockEntityType.Builder.of(ExampleBlockEntity::new, EXAMPLE_BLOCK.get()).build(null) + ); + ``` -These factories are created through the use of their `*Type$Builder` classes. An example: (`REGISTER` refers to a `DeferredRegister`) -```java -public static final RegistryObject> EXAMPLE_BLOCK_ENTITY = REGISTER.register( - "example_block_entity", () -> BlockEntityType.Builder.of(ExampleBlockEntity::new, EXAMPLE_BLOCK.get()).build(null) -); -``` -::: +引用已注册的对象 +--------------- -Referencing Registered Objects ------------------------------- +已注册的对象在创建和注册时不应存储在字段中。每当为相应的注册表触发`RegisterEvent`时,它们应总是新创建并注册的。这是为了允许在未来版本的Forge中动态加载和卸载模组。 -Registered objects should not be stored in fields when they are created and registered. They are to be always newly created and registered whenever `RegisterEvent` is fired for that registry. This is to allow dynamic loading and unloading of mods in a future version of Forge. +已注册的对象必须始终通过`RegistryObject`或带有`@ObjectHolder`的字段引用。 -Registered objects must always be referenced through a `RegistryObject` or a field with `@ObjectHolder`. +### 使用RegistryObjects -### Using RegistryObjects +一旦注册对象可用,就可以使用`RegistryObjects`检索对这些对象的引用。`DeferredRegister`使用它们来返回对已注册对象的引用。在为其注册表触发`RegisterEvent`后,它们的引用以及带有`@ObjectHolder`注释的字段都将被更新。 -`RegistryObject`s can be used to retrieve references to registered objects once they are available. These are used by `DeferredRegister` to return a reference to the registered objects. Their references are updated after `RegisterEvent` is called for their registry, along with the `@ObjectHolder` annotations. +要获取`RegistryObject`,请使用可注册对象的`IForgeRegistry`和一个`ResourceLocation`调用`RegistryObject#create`。亦可使用自定义注册表,方式是向其提供注册表名称。请将`RegistryObject`存储在一个`public static final`字段中,并在需要该已注册对象时调用`#get`。 -To get a `RegistryObject`, call `RegistryObject#create` with a `ResourceLocation` and the `IForgeRegistry` of the registrable object. Custom registries can also be used by supplying the registry name instead. Store the `RegistryObject` in a `public static final` field, and call `#get` whenever you need the registered object. - -An example of using `RegistryObject`: +使用`RegistryObject`的一个案例: ```java public static final RegistryObject BOW = RegistryObject.create(new ResourceLocation("minecraft:bow"), ForgeRegistries.ITEMS); -// assume that 'neomagicae:mana_type' is a valid registry, and 'neomagicae:coffeinum' is a valid object within that registry +// 假设'neomagicae:mana_type'是一个合法的注册表,且'neomagicae:coffeinum'是该注册表中一个合法的对象 public static final RegistryObject COFFEINUM = RegistryObject.create(new ResourceLocation("neomagicae", "coffeinum"), new ResourceLocation("neomagicae", "mana_type"), "neomagicae"); ``` -### Using @ObjectHolder +### 使用@ObjectHolder -Registered objects from registries can be injected into the `public static` fields by annotating classes or fields with `@ObjectHolder` and supplying enough information to construct a `ResourceLocation` to identify a specific object in a specific registry. +通过使用`@ObjectHolder`注释类或字段,并提供足够的信息来构造`ResourceLocation`以标识特定注册表中的特定对象,可以将注册表中的已注册对象注入`public static`字段。 -The rules for `@ObjectHolder` are as follows: +使用`@ObjectHolder`的规则如下: -* If the class is annotated with `@ObjectHolder`, its value will be the default namespace for all fields within if not explicitly defined -* If the class is annotated with `@Mod`, the modid will be the default namespace for all annotated fields within if not explicitly defined -* A field is considered for injection if: - * it has at least the modifiers `public static`; - * the **field** is annotated with `@ObjectHolder`, and: - * the name value is explicitly defined; and - * the registry name value is explicitly defined - * _A compile-time exception is thrown if a field does not have a corresponding registry or name._ -* _An exception is thrown if the resulting `ResourceLocation` is incomplete or invalid (non-valid characters in path)_ -* If no other errors or exceptions occur, the field will be injected -* If all of the above rules do not apply, no action will be taken (and a message may be logged) +* 若类被使用`@ObjectHolder`注释,则如果未明确定义,其值将是该类中所有字段的默认命名空间 +* 若类被使用`@Mod`注释,则如果未明确定义,modid将是其中所有已注释字段的默认命名空间 +* 若符合下列条件,该类中的一个字段将会被考虑注入: + * 其至少包含修饰符`public static`; + * 该**字段**被`@ObjectHolder`注释,并且: + * name值已被显式指明;并且 + * registry name值已被显式指明 + * _如果某个字段没有相应的注册表(registry name)或名称(name),则会引发编译时异常。_ +* _如果最终的`ResourceLocation`不完整或无效(路径中存在无效字符),则会引发异常。_ +* 如果没有发生其他错误或异常,则该字段将被注入 +* 如果以上所有规则都不适用,则不会采取任何操作(并且日志可能会输出一条信息) -`@ObjectHolder`-annotated fields are injected with their values after `RegisterEvent` is fired for their registry, along with the `RegistryObject`s. +被`@ObjectHolder`注释的字段会在`RegisterEvent`为其注册表激发之后注入其值,与`RegistryObjects`的引用的更新同时发生。 -:::note -If the object does not exist in the registry when it is to be injected, a debug message will be logged and no value will be injected. -::: +!!! 注意 + 如果要注入对象时该对象不存在于注册表中,那么日志会记录一条调试信息,并且不会注入任何值。 -As these rules are rather complicated, here are some examples: +由于这些规则相当复杂,案例如下: ```java class Holder { @ObjectHolder(registryName = "minecraft:enchantment", value = "minecraft:flame") - public static final Enchantment flame = null; // Annotation present. [public static] is required. [final] is optional. - // Registry name is explicitly defined: "minecraft:enchantment" - // Resource location is explicitly defined: "minecraft:flame" - // To inject: "minecraft:flame" from the [Enchantment] registry + public static final Enchantment flame = null; // 注释存在。[public static]是必需的。[final]是可选的。 + // Registry name已被显式指明:"minecraft:enchantment" + // Resource location已被显式指明:"minecraft:flame" + // 将注入:[Enchantment]注册表中的"minecraft:flame" - public static final Biome ice_flat = null; // No annotation on the field. - // Therefore, the field is ignored. + public static final Biome ice_flat = null; // 该字段无注释。 + // 因此,该字段被忽略。 @ObjectHolder("minecraft:creeper") - public static Entity creeper = null; // Annotation present. [public static] is required. - // The registry has not been specified on the field. - // Therefore, THIS WILL PRODUCE A COMPILE-TIME EXCEPTION. + public static Entity creeper = null; // 注释存在。[public static]是必需的。 + // 该字段未指明注册表。 + // 因此,其将引发编译时异常。 @ObjectHolder(registryName = "potion") - public static final Potion levitation = null; // Annotation present. [public static] is required. [final] is optional. - // Registry name is explicitly defined: "minecraft:potion" - // Resource location is not specified on the field - // Therefore, THIS WILL PRODUCE A COMPILE-TIME EXCEPTION. + public static final Potion levitation = null; // 注释存在。[public static]是必需的。[final]是可选的。 + // Registry name已被显式指明:"minecraft:potion" + // Resource location未在该字段中指明 + // 因此,其将引发编译时异常。 } ``` -Creating Custom Forge Registries --------------------------------- +创建自定义的Forge注册表 +---------------------- -Custom registries can usually just be a simple map of key to value. This is a common style; however, it forces a hard dependency on the registry being present. It also requires that any data that needs to be synced between sides must be done manually. Custom Forge Registries provide a simple alternative for creating soft dependents along with better management and automatic syncing between sides (unless told otherwise). Since the objects also use a Forge registry, registration becomes standardized in the same way. +自定义注册表通常只是一个简单的键值映射。这是一种常见的风格;然而,它强制对存在的注册表进行严格的依赖。它还要求任何需要在端位之间同步的数据都必须手动完成。自定义Forge注册表为创建软依赖项提供了一个简单的替代方案,同时提供了更好的管理手段和端位之间的自动同步(除非另有说明)。由于这些对象也使用Forge注册表,注册也以同样的方式标准化。 -Custom Forge Registries are created with the help of a `RegistryBuilder`, through either `NewRegistryEvent` or the `DeferredRegister`. The `RegistryBuilder` class takes various parameters (such as the registry's name, id range, and various callbacks for different events happening on the registry). New registries are registered to the `RegistryManager` after `NewRegistryEvent` finishes firing. +自定义Forge注册表是在`RegistryBuilder`的帮助下通过`NewRegistryEvent`或`DeferredRegister`创建的。`RegistryBuilder`类接受多种参数(例如注册表的名称、id范围以及注册表上发生的不同事件的各种回调)。`NewRegistryEvent`完成激发后,新的注册表将被注册到`RegistryManager`。 -Any newly created registry should use its associated [registration method][registration] to register the associated objects. +任何新创建的注册表都应该使用其关联的[注册方法][registration]来注册关联的对象。 -### Using NewRegistryEvent +### 使用NewRegistryEvent -When using `NewRegistryEvent`, calling `#create` with a `RegistryBuilder` will return a supplier-wrapped registry. The supplied registry can be accessed after `NewRegistryEvent` has finished posting to the mod event bus. Getting the custom registry from the supplier before `NewRegistryEvent` finishes firing will result in a `null` value. +使用`NewRegistryEvent`时,用`RegistryBuilder`调用`#create`将返回一个用Supplier包装的注册表。`NewRegistryEvent`在模组事件总线处理完毕后,这个Supplier注册表就可以访问了。在`NewRegistryEvent`被处理完毕之前试图从Supplier获取该自定义注册表将得到`null`值。 -#### New Datapack Registries +#### 新的数据包注册表 -New datapack registries can be added using the `DataPackRegistryEvent$NewRegistry` event on the mod event bus. The registry is created via `#dataPackRegistry` by passing in the `ResourceKey` representing the registry name and the `Codec` used to encode and decode the data from JSON. An optional `Codec` can be provided to sync the datapack registry to the client. +可以使用模组事件总线上的`DataPackRegistryEvent$NewRegistry`事件添加新的数据包注册表。注册表是通过`#dataPackRegistry`创建的,方法是传入表示注册表名称的`ResourceKey`和用于对JSON中的数据进行编码和解码的`Codec`。可以提供可选的`Codec`来将数据包注册表同步到客户端。 -:::note -Datapack Registries cannot be created with `DeferredRegister`. They can only be created through the event. -::: +!!! 重要 + 数据包注册表不能用`DeferredRegister`创建。它们只能通过这个事件创建。 -### With DeferredRegister +### 使用DeferredRegister -The `DeferredRegister` method is once again another wrapper around the above event. Once a `DeferredRegister` is created in a constant field using the `#create` overload which takes in the registry name and the mod id, the registry can be constructed via `DeferredRegister#makeRegistry`. This takes in a supplied `RegistryBuilder` containing any additional configurations. The method already populates `#setName` by default. Since this method can be returned at any time, a supplied version of an `IForgeRegistry` is returned instead. Getting the custom registry from the supplier before `NewRegistryEvent` is fired will result in a `null` value. +`DeferredRegister`方法又是上述事件的另一个包装。一旦使用`#create`重载在常量字段中创建了`DeferredRegister`(该重载接受注册表名称和mod id),就可以通过`DeferredRegistry#makeRegistry`构建注册表。该方法接受了由Supplier提供的包含任何其他配置的`RegistryBuilder`。默认情况下,该方法已调用`#setName`。由于此方法可以在任何时候返回,因此会返回由Supplier提供的`IForgeRegistry`版本。在激发NewRegistryEvent之前试图从Supplier获取自定义注册表将得到`null`值。 -:::caution -`DeferredRegister#makeRegistry` must be called before the `DeferredRegister` is added to the mod event bus via `#register`. `#makeRegistry` also uses the `#register` method to create the registry during `NewRegistryEvent`. -::: +!!! 重要 + 在通过`#register`将`DeferredRegister`添加到模组事件总线之前,必须调用`DeferredRegister#makeRegistry`。`#makeRegistry`也使用`#register`方法在`NewRegistryEvent`期间创建注册表。 -Handling Missing Entries ------------------------- +处理缺失的注册表条目 +------------------ -There are cases where certain registry objects will cease to exist whenever a mod is updated or, more likely, removed. It is possible to specify actions to handle the missing mapping through the third of the registry events: `MissingMappingsEvent`. Within this event, a list of missing mappings can be obtained either by `#getMappings` given a registry key and mod id or all mappings via `#getAllMappings` given a registry key. +在某些情况下,每当更新模组或删除模组(更可能的情况)时,某些注册表对象将不复存在。可以通过第三个注册表事件指定操作来处理丢失的映射:`MissingMappingsEvent`。在该事件中,既可以通过给定注册表项和mod id的`#getMappings`获取丢失映射的列表,也可以通过给定注册项的`#getAllMappings`获取所有映射。 -:::caution -`MissingMappingsEvent` is fired on the **Forge** event bus. -::: +!!! 重要 + `MissingMappingsEvent`在**Forge**事件总线上触发。 -For each `Mapping`, one of four mapping types can be selected to handle the missing entry: +对于每个映射(`Mapping`),可以选择四种映射类型之一来处理丢失的条目: -| Action | Description | +| 操作 | 描述 | | :---: | :--- | -| IGNORE | Ignores the missing entry and abandons the mapping. | -| WARN | Generates a warning in the log. | -| FAIL | Prevents the world from loading. | -| REMAP | Remaps the entry to an already registered, non-null object. | +| IGNORE | 忽略丢失的条目并丢弃映射。 | +| WARN | 在日志中生成警告。 | +| FAIL | 阻止世界加载。 | +| REMAP | 将条目重新映射到已注册的非null对象。 | -If no action is specified, then the default action will occur by notifying the user about the missing entry and whether they still would like to load the world. All actions besides remapping will prevent any other registry object from taking the place of the existing id in case the associated entry ever gets added back into the game. +如果未指定任何操作,则默认操作为通过通知用户丢失的条目以及用户是否仍要加载世界。除了重新映射之外的所有操作都将防止任何其他注册表对象取代现有id,以防止相关条目被添加回游戏中。 [ResourceLocation]: ./resources.md#resourcelocation [registration]: #methods-for-registering diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/concepts/resources.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/concepts/resources.md index d0d0b2074..b5717ec18 100644 --- a/i18n/zh-CN/docusaurus-plugin-content-docs/current/concepts/resources.md +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/concepts/resources.md @@ -1,20 +1,18 @@ -Resources -========= +资源 +==== -A resource is extra data used by the game, and is stored in a data file, instead of being in the code. -Minecraft has two primary resource systems active: one on the logical client used for visuals such as models, textures, and localization called `assets`, and one on the logical server used for gameplay such as recipes and loot tables called `data`. -[Resource packs][respack] control the former, while [Datapacks][datapack] control the latter. +资源是游戏使用的额外数据,存储在数据文件中,而不是代码中。Minecraft有两个主要的资源系统:一个在逻辑客户端上,用于模型、纹理和本地化等视觉效果,称为`assets`(资源),另一个在用于游戏的逻辑服务端上,如配方和战利品表,称为`data`(数据)。[资源包(Resource pack)][respack]控制前者,而[数据包(Datapack)][datapack]控制后者。 -In the default mod development kit, assets and data directories are located under the `src/main/resources` directory of the project. +在默认的模组开发工具包中,assets和data目录位于项目的`src/main/resources`目录下。 -When multiple resource packs or data packs are enabled, they are merged. Generally, files from packs at the top of the stack override those below; however, for certain files, such as localization files and tags, data is actually merged contentwise. Mods define resource and data packs in their `resources` directories, but they are seen as subsets of the "Mod Resources" pack. Mod resource packs cannot be disabled, but they can be overridden by other resource packs. Mod datapacks can be disabled with the vanilla `/datapack` command. +如果启用了多个资源包或数据包,它们会被合并。通常,堆栈顶部包中的文件会覆盖下面的文件;但是,对于某些文件,例如本地化文件和标签,数据实际上是按内容合并的。模组在其`resources`目录中定义资源和数据包,但它们被视为“模组资源”包的子集。不能禁用模组资源包,但它们可以被其他资源包覆盖。可以使用原版的`/datapack`命令禁用模组数据包。 -All resources should have snake case paths and filenames (lowercase, using "_" for word boundaries), which is enforced in 1.11 and above. +所有资源都应该有遵循蛇形命名法(Snake Case)的路径和文件名(小写,使用“_”表示单词边界),这在1.11及更高版本中得到了强制执行。 `ResourceLocation` ------------------ -Minecraft identifies resources using `ResourceLocation`s. A `ResourceLocation` contains two parts: a namespace and a path. It generally points to the resource at `assets///`, where `ctx` is a context-specific path fragment that depends on how the `ResourceLocation` is being used. When a `ResourceLocation` is written/read as from a string, it is seen as `:`. If the namespace and the colon are left out, then when the string is read into an `ResourceLocation` the namespace will always default to `"minecraft"`. A mod should put its resources into a namespace with the same name as its mod id (e.g. a mod with the id `examplemod` should place its resources in `assets/examplemod` and `data/examplemod` respectively, and `ResourceLocation`s pointing to those files would look like `examplemod:`.). This is not a requirement, and in some cases it can be desirable to use a different (or even more than one) namespace. `ResourceLocation`s are used outside the resource system, too, as they happen to be a great way to uniquely identify objects (e.g. [registries][]). +Minecraft使用`ResourceLocation`识别资源。`ResourceLocation`包含两部分:命名空间和路径。它通常指向`assets///`处的资源,其中`ctx`是特定于上下文的路径片段,取决于`ResourceLocation`的使用方式。当从字符串中写入/读取为`ResourceLocation`时,它被视为`:`。如果省略了`:`,那么当字符串被读取为`ResourceLocation`时,命名空间将始终默认为`minecraft`。模组应该将其资源放入与其mod id同名的命名空间中(例如,id为`examplemod`的模组应该分别将其资源放置在`assets/examplemod`和`data/examplemod`中,指向这些文件的`ResourceLocation`看起来像`examplemod:`。)。这不是要求,并且在某些情况下,可能希望使用不同的(或者甚至不止一个)命名空间。`ResourceLocation`也在资源系统之外使用,因为它们恰好是唯一标识对象(例如[注册表][])的好方法。 [respack]: ../resources/client/index.md [datapack]: ../resources/server/index.md diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/concepts/sides.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/concepts/sides.md index f3e66ae15..102ad34df 100644 --- a/i18n/zh-CN/docusaurus-plugin-content-docs/current/concepts/sides.md +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/concepts/sides.md @@ -1,51 +1,50 @@ -Sides in Minecraft -=================== +Minecraft中的端位 +================ -A very important concept to understand when modding Minecraft are the two sides: *client* and *server*. There are many, many common misconceptions and mistakes regarding siding, which can lead to bugs that might not crash the game, but can rather have unintended and often unpredictable effects. +为Minecraft开发模组时需要理解的一个非常重要的概念是两个端位:*客户端*和*服务端*。关于端位有很多常见的误解和错误,这可能会导致bug,而这些bug虽然可能不会破坏游戏,但是一定能够产生意想不到的、往往不可预测的影响。 -Different Kinds of Sides ------------------------- +不同种类的端位 +------------- -When we say "client" or "server", it usually follows with a fairly intuitive understanding of what part of the game we are talking about. After all, a client is what the user interacts with, and a server is where the user connects for a multiplayer game. Easy, right? +当我们说“客户端”或“服务端”时,我们通常会对所谈论的游戏的哪个部分有相当直观的理解。毕竟,客户端是用户交互的对象,服务端是用户连接多人游戏的地方。很简单,对吧? -As it turns out, there can be some ambiguity even with two such terms. Here we disambiguate the four possible meanings of "client" and "server": +而事实是,即使有两个这样的术语,也可能存在一些歧义。在这里,我们消除了“客户端”和“服务端”的四个可能含义的歧义: -* Physical client - The *physical client* is the entire program that runs whenever you launch Minecraft from the launcher. All threads, processes, and services that run during the game's graphical, interactable lifetime are part of the physical client. -* Physical server - Often known as the dedicated server, the *physical server* is the entire program that runs whenever you launch any sort of `minecraft_server.jar` that does not bring up a playable GUI. -* Logical server - The *logical server* is what runs game logic: mob spawning, weather, updating inventories, health, AI, and all other game mechanics. The logical server is present within a physical server, but it also can run inside a physical client together with a logical client, as a single player world. The logical server always runs in a thread named the `Server Thread`. -* Logical client - The *logical client* is what accepts input from the player and relays it to the logical server. In addition, it also receives information from the logical server and makes it available graphically to the player. The logical client runs in the `Render Thread`, though often several other threads are spawned to handle things like audio and chunk render batching. +* 物理客户端 - 无论何时从启动器启动Minecraft,*物理客户端*都是运行的整个程序。在游戏的图形化、可交互的生命周期中运行的所有线程、进程和服务都是物理客户端的一部分。 +* 物理服务端 - 通常被称为dedicated服务端,*物理服务端*是在你启动任何类型的`minecraft_server.jar`时运行的整个程序,该程序不会显示可用于游玩的GUI。 +* 逻辑服务端 - *逻辑服务端*运行游戏逻辑:生物的生成,天气,物品栏、生命值、AI的更新以及其他所有游戏机制。逻辑服务端存在于物理服务端中,但它也可以与逻辑客户端一起在物理客户端中运行,作为一个单机世界。逻辑服务端始终在名为`Server Thread`的线程中运行。 +* 逻辑客户端 - *逻辑客户端*接受玩家的输入并将其转发到逻辑服务端。此外,它还从逻辑服务端接收信息,并以图形方式呈现给玩家。逻辑客户端在`Render Thread`中运行,但通常会派生出几个其他线程来处理音频和方块渲染批处理等事务。 -In the MinecraftForge codebase, the physical side is represented by an enum called `Dist`, while the logical side is represented by an enum called `LogicalSide`. +在MinecraftForge代码库中,物理端由一个名为`Dist`的枚举表示,而逻辑端则由一个名为`LogicalSide`的枚举表示。 -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. That is, if this field is `true`, the level is currently 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 (in other words, a single player world). +这种boolean检查将是你最常用的检查端位的方法。在`Level`对象上查询此字段将建立该Level所属的**逻辑**端。也就是说,如果此字段为`true`,则该Level当前正在逻辑客户端上运行。如果该字段为`false`,则表示该Level正在逻辑服务端上运行。因此,物理服务端在该字段中总是包含`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`后才能这样做。在最好的情况下,将游戏逻辑应用于逻辑客户端可能会导致去同步(幽灵实体、去同步状态等),在最坏的情况下会导致崩溃。 -This check should be used as your go-to default. Aside from `DistExecutor`, rarely will you need the other ways of determining side and adjusting behavior. +这种检查应该成为习惯。你很少需要除`DistExecutitor`以外的其他方式来确定端位和调整行为。 ### `DistExecutor` -Considering the use of a single "universal" jar for client and server mods, and the separation of the physical sides into two jars, an important question comes to mind: How do we use code that is only present on one physical side? All code in `net.minecraft.client` is only present on the physical client. If any class you write references those names in any way, they will crash the game when that respective class is loaded in an environment where those names do not exist. A very common mistake in beginners is to call `Minecraft.getInstance().()` in block or block entity classes, which will crash any physical server as soon as the class is loaded. +考虑到客户端和服务端的模组都使用同一个“通用”的jar,以及将物理端分离为两个jar,我们想到了一个重要的问题:我们该如何使用只存在于某一个物理端的代码?`net.minecraft.client`下的所有代码仅存在于物理客户端上。如果你编写的任何类以任何方式引用了上述包下的类型名称,那么当在不存在这些类型名称的环境中加载相应的类时,它们将导致游戏崩溃。初学者的一个非常常见的错误是在他们的方块或方块实体类中调用`Minecraft.getInstance().()`,一旦加载这些类,就会导致任何物理服务端崩溃。 -How do we resolve this? Luckily, FML has `DistExecutor`, which provides various methods to run different methods on different physical sides, or a single method only on one side. +我们如何解决这个问题?幸运的是,FML有一个`DistExecutor`,它提供了各种方法来在不同的物理端运行不同的方法,或者只在某一物理端运行单个方法。 -:::note -It is important to understand that FML checks based on the **physical** side. A single player world (logical server + logical client within a physical client) will always use `Dist.CLIENT`! -::: +!!! 注意 + 对FML基于**物理**端进行检查的理解尤为重要。单机世界(包含逻辑服务端+物理客户端的逻辑客户端)将始终使用`Dist.CLIENT`! -`DistExecutor` works by taking in a supplied supplier executing a method, effectively preventing classloading by taking advantage of the [`invokedynamic` JVM instruction][invokedynamic]. The executed method should be static and within a different class. Additionally, if no parameters are present for the static method, a method reference should be used instead of a supplier executing a method. +`DistExecutor`的工作原理是接收所提供的执行方法的Supplier,通过利用[JVM指令`invokedynamic`][invokedynamic]有效地防止类加载。被执行的方法应该是静态的并且在不同的类中。此外,如果这个静态方法没有参数,则应使用该方法的引用,而不是一个执行方法的Supplier。 -There are two main methods within `DistExecutor`: `#runWhenOn` and `#callWhenOn`. The methods take in the physical side the executing method should run on and the supplied executing method which either runs or returns a result respectively. +`DistExecutor`中有两个主要方法:`#runWhenOn`和`#callWhenOn`。方法接受的参数为将被执行的方法和该方法应该运行的物理端,该方法(将被执行的方法)既可有返回值,也可无返回值。 -These two methods are subdivided further into `#safe*` and `#unsafe*` variants. Safe and unsafe variants are misnomers for their purposes. The main difference is that when in a development environment, the `#safe*` methods will validate that the supplied executing method is a lambda returning a method reference to another class with an error being thrown otherwise. Within the production environment, `#safe*` and `#unsafe*` are functionally the same. +这两种方法被进一步细分为`#safe*`和`#unsafe*`变体。安全(safe)和不安全(unsafe)这两种命名方式其实差强人意。主要区别在于,在开发环境中,`#safe*`方法将验证所提供的执行方法是否是返回的对另一个类的方法引用的lambda,否则将抛出错误。在产品环境中,`#safe*`和`#unsafe*`在功能上是相同的。 ```java -// In a client class: ExampleClass +// 在一个客户端类中:ExampleClass public static void unsafeRunMethodExample(Object param1, Object param2) { // ... } @@ -54,66 +53,65 @@ public static Object safeCallMethodExample() { // ... } -// In some common class +// 在一个通用类中 DistExecutor.unsafeRunWhenOn(Dist.CLIENT, () -> ExampleClass.unsafeRunMethodExample(var1, var2)); DistExecutor.safeCallWhenOn(Dist.CLIENT, () -> ExampleClass::safeCallMethodExample); ``` -:::caution -Due to a change in how `invokedynamic` works in Java 9+, all `#safe*` variants of the `DistExecutor` methods throw the original exception wrapped within a `BootstrapMethodError` in the development environment. `#unsafe*` variants or a check to [`FMLEnvironment#dist`][dist] should be used instead. -::: +!!! 警告 + 由于`invokedynamic`在Java 9+中的工作方式发生了变化,`DistExecutor`方法的所有`#safe*`变体都会在开发环境中抛出封装在`BootstrapMethodError`中的原始异常。应该使用`#unsafe*`变体或对[`FMLEnvironment#dist`][dist]的检查作为替代。 -### Thread Groups +### 线程组 -If `Thread.currentThread().getThreadGroup() == SidedThreadGroups.SERVER` is true, it is likely the current thread is on the logical server. Otherwise, it is likely on the logical client. This is useful to retrieve the **logical** side when you do not have access to a `Level` object to check `isClientSide`. It *guesses* which logical side you are on by looking at the group of the currently running thread. Because it is a guess, this method should only be used when other options have been exhausted. In nearly every case, you should prefer checking `Level#isClientSide`. +如果`Thread.currentThread().getThreadGroup() == SidedThreadGroups.SERVER`为true,则很可能当前线程位于逻辑服务端上。否则,它很可能在逻辑客户端上。当你无法访问`Level`对象以检查`isClientSide`时,这对于检索**逻辑**端非常有用。它通过查看当前运行的线程组来*猜测*你处于哪个逻辑端。因为这是一种猜测,所以只有在用尽其他选项时才应该使用这种方法。在几乎所有情况下,你应该优先检查`Level#isClientSide`。 -### `FMLEnvironment#dist` and `@OnlyIn` +### `FMLEnvironment#dist`和`@OnlyIn` -`FMLEnvironment#dist` holds the **physical** side your code is running on. Since it is determined at startup, it does not rely on guessing to return its result. The number of use cases for this is limited, however. +`FMLEnvironment#dist`表示当前你的代码正在运行的**物理**端。由于它是在启动时确定的,所以它不依赖于猜测来返回结果。然而,在这方面的用例并不是很多。 -Annotating a method or field with the `@OnlyIn(Dist)` annotation indicates to the loader that the respective member should be completely stripped out of the definition not on the specified **physical** side. Usually, these are only seen when browsing through the decompiled Minecraft code, indicating methods that the Mojang obfuscator stripped out. There is **NO** reason for using this annotation directly. Use `DistExecutor` or a check on `FMLEnvironment#dist` instead. +使用`@OnlyIn(Dist)`注释对方法或字段进行注释会向加载器表明,应该将相应的成员在非指定的**物理**端中从定义里完全剥离。通常,这些只有在浏览反编译的Minecraft代码时才能看到,暗示着Mojang混淆器删除了的方法。**没有**理由直接使用此注释。请改用`DistExecutor`或检查`FMLEnvironment#dist`。 -Common Mistakes ---------------- +常见错误 +-------- -### Reaching Across Logical Sides +### 跨逻辑端访问 -Whenever you want to send information from one logical side to another, you must **always** use network packets. It is incredibly tempting, when in a single player scenario, to directly transfer data from the logical server to the logical client. +每当你想将信息从一个逻辑端发送到另一个逻辑端时,必须**始终**使用网络数据包。即便在单机场景中,将数据从逻辑服务端直接传输到逻辑客户端是非常诱人的。 -This is actually very commonly inadvertently done through static fields. Since the logical client and logical server share the same JVM in a single player scenario, both threads writing to and reading from static fields will cause all sorts of race conditions and the classical issues associated with threading. +实际上,这通常是通过静态字段无意中完成的。由于在单机场景中,逻辑客户端和逻辑服务端共享相同的JVM,因此向静态字段写入和从静态字段读取的线程都会导致各种竞争条件以及与线程相关的经典问题。 -This mistake can also be made explicitly by accessing physical client-only classes such as `Minecraft` from common code that runs or can run on the logical server. This mistake is easy to miss for beginners who debug in a physical client. The code will work there, but it will immediately crash on a physical server. +通过从逻辑服务端上运行或可以运行的公共代码访问仅物理客户端的类(如`Minecraft`),也可能会明确地犯下这个错误。对于在物理客户端中调试的初学者来说,这个错误很容易被忽略。代码会在那里工作,但它会立即在物理服务端上崩溃。 -Writing One-Sided Mods ----------------------- +编写单端模组 +----------- -In recent versions, Minecraft Forge has removed a "sidedness" attribute from the mods.toml. This means that your mods are expected to work whether they are loaded on the physical client or the physical server. So for one-sided mods, you would typically register your event handlers inside a `DistExecutor#safeRunWhenOn` or `DistExecutor#unsafeRunWhenOn` instead of directly calling the relevant registration methods in your mod constructor. Basically, if your mod is loaded on the wrong side, it should simply do nothing, listen to no events, and so on. A one-sided mod by nature should not register blocks, items, ... since they would need to be available on the other side, too. +在最近的版本中,Minecraft Forge从mods.toml中删除了一个“sidedness”属性。这意味着无论你的模组是加载在物理客户端还是物理服务端上,它们都可以工作。因此,对于单端模组,你通常会在`DistExecutor#safeRunWhenOn`或`DistExecutor#unsafeRunWhen`中注册事件处理程序,而不是直接调用模组构造函数中的相关注册方法。基本上,如果你的模组加载在错误的一端,它应该什么都不做,不监听任何事件,等等。单端模组本质上不应该注册方块、物品……因为它们也需要在另一端可用。 -Additionally, if your mod is one-sided, it typically does not forbid the user from joining a server that is lacking that mod. Therefore, you should set the `displayTest` property in your [mods.toml][structuring] to whatever value is necessary. +此外,如果你的模组是单端的,它通常不会禁止用户加入缺乏该模组的服务端。因此,你应该将mods.toml中的`displayTest`属性设置为任何必要的值。 ```toml [[mods]] # ... - # MATCH_VERSION means that your mod will cause a red X if the versions on client and server differ. This is the default behaviour and should be what you choose if you have server and client elements to your mod. - # IGNORE_SERVER_VERSION means that your mod will not cause a red X if it's present on the server but not on the client. This is what you should use if you're a server only mod. - # IGNORE_ALL_VERSION means that your mod will not cause a red X if it's present on the client or the server. This is a special case and should only be used if your mod has no server component. - # NONE means that no display test is set on your mod. You need to do this yourself, see IExtensionPoint.DisplayTest for more information. You can define any scheme you wish with this value. - # IMPORTANT NOTE: this is NOT an instruction as to which environments (CLIENT or DEDICATED SERVER) your mod loads on. Your mod should load (and maybe do nothing!) whereever it finds itself. - displayTest="IGNORE_ALL_VERSION" # MATCH_VERSION is the default if nothing is specified (#optional) + # MATCH_VERSION表示如果客户端和服务端上的版本不同,你的模组将导致红X。这是默认行为,如果你的模组有服务端和客户端元素,这就是你应该使用的。 + # IGNORE_SERVER_VERSION表示如果你的模组出现在服务端上但不在客户端上,它不会导致红X。如果你的模组是一个仅限服务端的模组,这就是你应该使用的。 + # IGNORE_ALL_VERSION表示如果你的模组出现在客户端或服务端上,它不会导致红X。这是一个特殊情况,只有当你的模组没有服务端成分时才应该使用。 + # NONE表示没有在你的模组上设置显示检测。你需要自己完成此操作,有关详细信息,请参阅IExtensionPoint.DisplayTest。你可以使用此值定义任何你想要的方案。 + # 重要提示:这不是关于你的模组加载在哪个环境(客户端或dedicated服务端)上的说明。你的模组必然会加载(也许什么都不做!)。 + displayTest="IGNORE_ALL_VERSION" # 如果未指定任何内容,则MATCH_VERSION为默认值 (#可选) ``` -If a custom display test is to be used, then the `displayTest` option should be set to `NONE`, and an `IExtensionPoint$DisplayTest` extension should be registered: +如果要使用自定义显示检测,则`displayTest`选项应设置为`NONE`,并且应注册`IExtensionPoint$displayTest`扩展: ```java -//Make sure the mod being absent on the other network side does not cause the client to display the server as incompatible +//确保另一个网络端上缺失的模组不会导致客户端将服务端显示为不兼容 ModLoadingContext.get().registerExtensionPoint(IExtensionPoint.DisplayTest.class, () -> new IExtensionPoint.DisplayTest(() -> NetworkConstants.IGNORESERVERONLY, (a, b) -> true)); ``` -This tells the client that it should ignore the server version being absent, and the server that it should not tell the client this mod should be present. So this snippet works both for client- and server-only-sided mods. +这告诉客户端它应该忽略服务端版本不存在,服务端不应该告诉客户端这个模组应该存在。因此,这个代码片段适用于仅客户端和服务端的模组。 [invokedynamic]: https://docs.oracle.com/javase/specs/jvms/se17/html/jvms-6.html#jvms-6.5.invokedynamic diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/datagen/client/localization.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/datagen/client/localization.md index c497b3387..c3eb1942a 100644 --- a/i18n/zh-CN/docusaurus-plugin-content-docs/current/datagen/client/localization.md +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/datagen/client/localization.md @@ -1,16 +1,16 @@ -Language Generation -=================== +语言生成 +======== -[Language files][lang] can be generated for a mod by subclassing `LanguageProvider` and implementing `#addTranslations`. Each `LanguageProvider` subclass created represents a separate [locale] (`en_us` represents American English, `es_es` represents Spanish, etc.). After implementation, the provider must be [added][datagen] to the `DataGenerator`. +可以通过子类化`LanguageProvider`并实现`#addTranslations`为模组生成[语言文件][lang]。创建的每个`LanguageProvider`子类代表一个单独的[locale](`en_us`代表美式英语,`es_es`代表西班牙语等)。实现后,必须将提供者[添加][datagen]到`DataGenerator`中。 ```java -// On the MOD event bus +// 在模组事件总线上 @SubscribeEvent public void gatherData(GatherDataEvent event) { event.getGenerator().addProvider( - // Tell generator to run only when client assets are generating + // 告诉生成器仅在生成客户端资源时运行 event.includeClient(), - // Localizations for American English + // 美式英语的本地化 output -> new MyLanguageProvider(output, MOD_ID, "en_us") ); } @@ -19,23 +19,22 @@ public void gatherData(GatherDataEvent event) { `LanguageProvider` ------------------ -Each language provider is simple a map of strings where each translation key is mapped to a localized name. A translation key mapping can be added using `#add`. Additionally, there are methods which use the translation key of a `Block`, `Item`, `ItemStack`, `Enchantment`, `MobEffect`, and `EntityType`. +每个语言提供者都是一个简单的字符串映射,其中每个翻译键都映射到一个本地化名称。可以使用`#add`添加翻译键映射。此外,还有一些方法使用`Block`、`Item`、`ItemStack`、`Enchantment`、`MobEffect`和`EntityType`的翻译键。 ```java -// In LanguageProvider#addTranslations +// 在LanguageProvider#addTranslations中 this.addBlock(EXAMPLE_BLOCK, "Example Block"); this.add("object.examplemod.example_object", "Example Object"); ``` -:::tip -Localized names which contain alphanumeric values not in American English can be supplied as is. The provider automatically translates the characters into their unicode equivalents to be read by the game. +!!! 提示 + 包含非美式英语字母数字值的本地化名称可以按原样提供。提供者会自动将字符翻译为等效的unicode,供游戏读取。 -```java -// Encdoded as 'Example with a d\u00EDacritic' -this.addItem("example.diacritic", "Example with a díacritic"); -``` -::: + ```java + // 编码为'Example with a d\u00EDacritic' + this.addItem("example.diacritic", "Example with a díacritic"); + ``` [lang]: ../../concepts/internationalization.md -[locale]: https://minecraft.wiki/w/Language#Languages +[locale]: https://minecraft.fandom.com/wiki/Language#Languages [datagen]: ../index.md#data-providers diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/datagen/client/modelproviders.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/datagen/client/modelproviders.md index 5075164c7..800cc6111 100644 --- a/i18n/zh-CN/docusaurus-plugin-content-docs/current/datagen/client/modelproviders.md +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/datagen/client/modelproviders.md @@ -1,17 +1,17 @@ -Model Generation -================ +模型生成 +======== -[Models] can be generated for models or block states by default. Each provides a method of generating the necessary JSONs (`ModelBuilder#toJson` for models and `IGeneratedBlockState#toJson` for block states). After implementation, the [associated providers][provider] must be [added][datagen] to the `DataGenerator`. +默认情况下,可以为模型或方块状态生成[模型][Models]。每种都提供了一种生成必要JSON的方法(`ModelBuilder#toJson`用于模型,`IGeneratedBlockState#toJson`用于方块状态)。实现后,必须将[关联的提供者][provider] [添加][datagen]到`DataGenerator`中。 ```java -// On the MOD event bus +// 在模组事件总线上 @SubscribeEvent public void gatherData(GatherDataEvent event) { DataGenerator gen = event.getGenerator(); ExistingFileHelper efh = event.getExistingFileHelper(); gen.addProvider( - // Tell generator to run only when client assets are generating + // 告诉生成器仅在生成客户端资源时运行 event.includeClient(), output -> new MyItemModelProvider(output, MOD_ID, efh) ); @@ -22,299 +22,291 @@ public void gatherData(GatherDataEvent event) { } ``` -Model Files ------------ +模型文件 +------- -A `ModelFile` acts as the base for all models referenced or generated by a provider. Each model file stores the location relative to the `models` subdirectory and can assert whether the file exists. +`ModelFile`充当提供者引用或生成的所有模型的基础。每个模型文件存储相对于`models`子目录的位置,并可以断言该文件是否存在。 -### Existing Model Files +### 现存的模型文件 -`ExistingModelFile` is a subclass of `ModelFile` which checks via [`ExistingFileHelper#exists`][efh] whether the model already exists within the `models` subdirectory. All non-generated models are usually referenced through `ExistingModelFile`s. +`ExistingModelFile`是`ModelFile`的子类,它通过[`ExistingFileHelper#exists`][efh]检查模型是否已存在于`models`子目录中。所有未生成的模型通常通过`ExistingModelFile`引用。 -### Unchecked Model Files +### 未检查的模型文件 -`UncheckedModelFile` is a subclass of `ModelFile` which assumes the specified model exists in some location. +`UncheckedModelFile`是`ModelFile`的一个子类,它假定指定的模型存在于某个位置。 -:::note -There should be no cases where an `UncheckedModelFile` is used to reference a model. If there is, then the associated resources are not properly being tracked by `ExistingFileHelper`. -::: +!!! 注意 + 不应存在使用`UncheckedModelFile`引用模型的情况。如果存在,则`ExistingFileHelper`无法正确跟踪关联的资源。 -Model Builders --------------- +模型生成器 +--------- -A `ModelBuilder` represents a to-be-generated `ModelFile`. It contains all the data about a model: its parent, faces, textures, transformations, lighting, and [loader]. +`ModelBuilder`表示要生成的`ModelFile`。它包含了关于模型的所有数据:它的父级、面、纹理、变换、照明和[加载器][loader]。 -:::tip -While a complex model can be generated, it is recommended that those models be constructed using a modeling software beforehand. Then, the data provider can generate the children models with specific textures applied through the defined references in the parent complex model. -::: +!!! 提示 + 虽然可以生成复杂的模型,但建议事先使用建模软件构建这些模型。然后,数据提供者可以生成具有通过父复杂模型中定义的引用应用的特定纹理的子模型。 -The parent (via `ModelBuilder#parent`) of the builder can be any `ModelFile`: generated or existing. Generated files are added to `ModelProvider`s as soon as the builder is created. The builder itself can be passed in as a parent, or the `ResourceLocation` can supplied alternatively. +生成器的父级(通过`ModelBuilder#parent`)可以是任何`ModelFile`:生成的或现有的。一旦创建了生成器,生成的文件就会添加到`ModelProvider`中。生成器本身可以作为父级传入,也可以提供`ResourceLocation`。 -:::caution -If the parent is not generated before the child model when passing in a `ResourceLocation`, then an exception will be thrown. -::: +!!! 警告 + 如果在传递`ResourceLocation`时父模型不是在子模型之前生成的,则将引发异常。 -Each element (via `ModelBuilder#element`) within a model is defined as cube using two three-dimensional points (`ElementBuilder#from` and `#to` respectively) where each axis is limited to the values `[-16,32]` (between -16 and 32 inclusive). Each face (`ElementBuilder#face`) of the cube can specify when the face is culled (`FaceBuilder#cullface`), [tint index][color] (`FaceBuilder#tintindex`), texture reference from the `textures` keys (`FaceBuilder#texture`), UV coordinate on the texture (`FaceBuilder#uvs`), and rotation in 90 degree intervals (`FaceBuilder#rotation`). +模型中的每个元素(通过`ModelBuilder#element`)都被定义为使用两个三维点(分别为`ElementBuilder#from`和`#to`)的立方体,其中每个轴都被限制为值`[-16,32]`(包括-16和32)。多维数据集的每个面(`ElementBuilder#face`)都可以指定面何时被剔除(`FaceBuilder#cullface`)、[色调索引][color](`FaceBuilder#tintindex`)、来自`textures`键的纹理引用(`FaceBuilder#texture`)、纹理上的UV坐标(`FaceBuilder#uvs`)以及以90度间隔旋转(`FaceBuilder#rotation`)。 -:::note -It recommended for block models which have elements that exceed a bound of `[0,16]` on any axis to separate into multiple blocks, such as for a multiblock structure, to avoid lighting and culling issues. -::: +!!! 注意 + 建议在任何轴上元素超过`[0,16]`界限的方块模型分离为多个方块,例如多方块结构,以避免照明和剔除问题。 -Each cube can additionally be rotated (`ElementBuilder#rotation`) around a specified point (`RotationBuilder#origin`) for a given axis (`RotationBuilder#axis`) in 22.5 degree intervals (`RotationBuilder#angle`). The cube can scale all faces in relation to the entire model as well (`RotationBuilder#rescale`). The cube can also determine whether its shadows should be rendered (`ElementBuilder#shade`). +每个立方体还可以围绕指定点(`RotationBuilder#origin`)以22.5度的间隔(`RotationBuilder#angle`)为给定轴(`RotationBuilder#axis`)旋转(`ElementBuilder#rotation`)。立方体也可以相对于整个模型缩放所有面(`RotationBuilder#rescale`)。多维数据集还可以确定是否应渲染其阴影(`ElementBuilder#shade`)。 -Each model defines a list of texture keys (`ModelBuilder#texture`) which points to either a location or a reference. Each key can then be referenced in any element by prefixing using a `#` (a texture key of `example` can be referenced in an element using `#example`). A location specifies where a texture is in `assets//textures/.png`. A reference is used by any models parenting the current model as keys to define textures for later. +每个模型都定义了一个纹理键列表(`ModelBuilder#texture`),该列表指向一个位置或引用。然后,通过使用`#`前缀,可以在任何元素中引用每个键(`example`的纹理键可以在使用`#example`元素中引用)。位置指定纹理在`assets//textures/.png`中的位置。引用由作为当前模型的子级的任何模型使用,作为以后定义纹理的键。 -The model can additionally be transformed (`ModelBuilder#transforms`) for any defined perspective (in the left hand in first person, in the gui, on the ground, etc.). For any perspective (`TransformsBuilder#transform`), the rotation (`TransformVecBuilder#rotation`), translation (`TransformVecBuilder#translation`), and scale (`TransformVecBuilder#scale`) can be set. +对于任何定义的透视图(在第一人称的左手、在图形用户界面、在地面等),还可以对模型进行转换(`ModelBuilder#transforms`)。对于任何透视图(`TransformsBuilder#transform`),都可以设置旋转(`TransformVecBuilder#rotation`)、平移(`TransformVecBuilder#translation`)和缩放(`TransformVecBuilder#scale`)。 -Finally, the model can set whether to use ambient occlusion in a level (`ModelBuilder#ao`) and from what location to light and shade the model from `ModelBuilder#guiLight`. +最后,模型可以设置是否在某个存档(`ModelBuilder#ao`)中使用环境遮挡,以及从哪个位置从`ModelBuilder#guiLight`对模型进行明暗处理。 ### `BlockModelBuilder` -A `BlockModelBuilder` represents a block model to-be-generated. In addition to the `ModelBuilder`, a transform to the entire model (`BlockModelBuilder#rootTransform`) can be generated. The root can be translated (`RootTransformBuilder#transform`), rotated (`RootTransformBuilder#rotation`, `RootTransformBuilder#postRotation`), and scaled (`RootTransformBuilder#scale`) either individually or all in one transformation (`RootTransformBuilder#transform`) around some origin (`RootTransformBuilder#origin`). +`BlockModelBuilder`表示要生成的方块模型。除了`ModelBuilder`之外,还可以生成对整个模型的转换(`BlockModelBuilder#rootTransform`)。根可以围绕某个原点(`RootTransformBuilder#origin`)单独或全部在一个变换(`RootTransformBuilder#transform`)中进行平移(`RootTransformBuilder#transform`)、旋转(`RootTransformBuilder#rotation`、`RootTransformBuilder#postRotation`)和缩放(`RootTransformBuilder#origin`)。 ### `ItemModelBuilder` -An `ItemModelBuilder` represents an item model to-be-generated. In addition to the `ModelBuilder`, [overrides] (`OverrideBuilder#override`) can be generated. Each override applied to a model can apply conditions which represent for a given property that must be above the specified value (`OverrideBuilder#predicate`). If the conditions are met, then the specified model (`OverrideBuilder#model`) will be rendered instead of this model. +`ItemModelBuilder`表示要生成的物品模型。除了`ModelBuilder`之外,还可以生成[overrides](`OverrideBuilder#override`)。应用于模型的每个重写都可以应用表示给定属性的条件,该属性必须高于指定值(`OverrideBuilder#predicate`)。如果满足条件,则将呈现指定的模型(`OverrideBuilder#model`),而不是此模型。 -Model Providers ---------------- +模型提供者 +--------- -The `ModelProvider` subclasses are responsible for generating the constructed `ModelBuilder`s. The provider takes in the generator, mod id, subdirectory in the `models` folder to generate within, a `ModelBuilder` factory, and the existing file helper. Each provider subclass must implement `#registerModels`. +`ModelProvider`子类负责生成构造的`ModelBuilder`。提供者接收生成器、mod id、要在其中生成的`models`文件夹中的子目录、`ModelBuilder`工厂和现有文件助手。每个提供器子类都必须实现`#registerModels`。 -The provider contains basic methods which either create the `ModelBuilder` or provides convenience for getting texture or model references: +提供器包含创建`ModelBuilder`或为获取纹理或模型引用提供便利的基本方法: -Method | Description +方法 | 描述 :---: | :--- `getBuilder` | Creates a new `ModelBuilder` within the provider's subdirectory for the given mod id. `withExistingParent` | Creates a new `ModelBuilder` for the given parent. Should be used when the parent is not generated by the builder. `mcLoc` | Creates a `ResourceLocation` for the path in the `minecraft` namespace. `modLoc` | Creates a `ResourceLocation` for the path in the given mod id's namespace. -Additionally, there are several helpers for easily generating common models using vanilla templates. Most are for block models with only a few being universal. +此外,还有几个助手可以使用普通模板轻松生成通用模型。大多数是方块模型,只有少数是通用的。 -:::note -Although the models are within a specific subdirectory, that does **not** mean that the model cannot be referenced by a model in another subdirectory. Usually, it is indicative of that model being used for that type of object. -::: +!!! 注意 + 尽管模型在一个特定的子目录中,但**并不**意味着该模型不能被另一个子目录中的模型引用。通常,它表示该模型用于该类型的对象。 ### `BlockModelProvider` -The `BlockModelProvider` is used for generating block models via `BlockModelBuilder` in the `block` folder. Block models should typically parent `minecraft:block/block` or one of its children models for use with item models. +`BlockModelProvider`用于通过`block`文件夹中的`BlockModelBuilder`生成方块模型。方块模型通常应为`minecraft:block/block`或其子模型之一的父模型,以便与物品模型一起使用。 -:::note -Block models and its item model counterpart are typically not generated through a direct subclass of `BlockModelProvider` and `ItemModelProvider` but through [`BlockStateProvider`][blockstateprovider]. -::: +!!! 注意 + 方块模型及其物品模型对应物通常不是通过`BlockModelProvider`和`ItemModelProvider`的直接子类生成的,而是通过[`BlockStateProvider`][blockstateprovider]生成的。 ### `ItemModelProvider` -The `ItemModelProvider` is used for generating block models via `ItemModelBuilder` in the `item` folder. Most item models parent `item/generated` and use `layer0` to specify their texture, which can be done using `#singleTexture`. +`ItemModelProvider`用于通过`item`文件夹中的`ItemModelBuilder`生成块模型。大多数物品模型的父级为`item/generated`,并使用`layer0`来指定其纹理,这可以使用`#singleTexture`来完成。 -:::note -`item/generated` can support five texture layers stacked on top of each other: `layer0`, `layer1`, `layer2`, `layer3`, and `layer4`. -::: +!!! 注意 + `item/generated`可以支持堆叠在一起的五个纹理层:`layer0`、`layer1`、`layer2`、`layer3`和`layer4`。 ```java -// In some ItemModelProvider#registerModels +// 在某个ItemModelProvider#registerModels中 -// Will generate 'assets//models/item/example_item.json' -// Parent will be 'minecraft:item/generated' -// For the texture key 'layer0' -// It will be at 'assets//textures/item/example_item.png' +// 将会生成'assets//models/item/example_item.json' +// 父级将是'minecraft:item/generated' +// 对于纹理键'layer0' +// 其将会在'assets//textures/item/example_item.png' this.basicItem(EXAMPLE_ITEM.get()); ``` -:::note -Item models for blocks should typically parent an existing block model instead of generating a separate model for an item. -::: +!!! 注意 + 方块的物品模型通常应作为现有方块模型的父级,而不是为物品生成单独的模型。 -Block State Provider --------------------- +方块状态提供者 +------------- -A `BlockStateProvider` is responsible for generating [block state JSONs][blockstate] in `blockstates`, block models in `models/block`, and item models in `models/item` for said blocks. The provider takes in the data generator, mod id, and existing file helper. Each `BlockStateProvider` subclass must implement `#registerStatesAndModels`. +`BlockStateProvider`负责为所述方块生成`blockstates`中的[方块状态JSON][blockstate]、`models/block`中的方块模型以及`models/item`中的物品模型。提供器接收数据生成器、mod id和现有的文件助手。每个`BlockStateProvider`子类都必须实现`#registerStatesAndModels`。 -The provider contains basic methods for generating block state JSONs and block models. Item models must be generated separately as a block state JSON may define multiple models to use in different contexts. There are a number of common methods, however, that that the modder should be aware of when dealing with more complex tasks: +提供者包含用于生成方块状态JSON和方块模型的基本方法。物品模型必须单独生成,因为方块状态JSON可以定义多个模型以在不同的上下文中使用。然而,在处理更复杂的任务时,模组开发者应该注意一些常见的方法: -Method | Description +方法 | 描述 :---: | :--- -`models` | Gets the [`BlockModelProvider`][blockmodels] used to generate the item block models. -`itemModels` | Gets the [`ItemModelProvider`][itemmodels] used to generate the item block models. -`modLoc` | Creates a `ResourceLocation` for the path in the given mod id's namespace. -`mcLoc` | Creates a `ResourceLocation` for the path in the `minecraft` namespace. -`blockTexture` | References a texture within `textures/block` which has the same name as the block. -`simpleBlockItem` | Creates an item model for a block given the associated model file. -`simpleBlockWithItem` | Creates a single block state for a block model and an item model using the block model as its parent. +`models` | 获取用于生成物品方块模型的[`BlockModelProvider`][blockmodels]。 +`itemModels` | 获取用于生成物品方块模型的[`ItemModelProvider`][itemmodels]。 +`modLoc` | 为给定mod id的命名空间中的路径创建`ResourceLocation`。 +`mcLoc` | 为`minecraft`命名空间中的路径创建`ResourceLocation`。 +`blockTexture` | 引用`textures/block`中与方块同名的纹理。 +`simpleBlockItem` | 为给定关联模型文件的方块创建物品模型。 +`simpleBlockWithItem` | 使用方块模型作为其父级,为方块模型和物品模型创建单个方块状态。 -A block state JSON is made up of variants or conditions. Each variant or condition references a `ConfiguredModelList`: a list of `ConfiguredModel`s. Each configured model contains the model file (via `ConfiguredModel$Builder#modelFile`), the X and Y rotation in 90 degree intervals (via `#rotationX` and `rotationY` respectively), whether the texture can rotate when the model is rotated by the block state JSON (via `#uvLock`), and the weight of the model appearing compared to other models in the list (via `#weight`). +方块状态JSON由变量或条件组成。每个变量或条件都引用一个`ConfiguredModelList`:`ConfiguredModel`的列表。每个配置的模型都包含模型文件(通过`ConfiguredModel$Builder#modelFile`)、90度间隔的X和Y旋转(分别通过`#rotationX`和`rotationY`)、当模型通过方块状态JSON旋转时纹理是否可以旋转(通过`#uvLock`),以及与列表中其他模型相比出现的模型的权重(通过`#weight`)。 -The builder (`ConfiguredModel#builder`) can also create an array of `ConfiguredModel`s by creating the next model using `#nextModel` and repeating the settings until `#build` is called. +生成器(`ConfiguredModel#builder`)还可以通过使用`#nextModel`创建下一个模型并重复设置直到调用`#build`来创建`ConfiguredModel`的数组。 ### `VariantBlockStateBuilder` -Variants can be generated using `BlockStateProvider#getVariantBuilder`. Each variant specifies a list of [properties] (`PartialBlockstate`) which when matches a `BlockState` in a level, will display a model chosen from the corresponding model list. An exception is thrown if there is a `BlockState` which is not covered by any variant defined. Only one variant can be true for any `BlockState`. +可以使用`BlockStateProvider#getVariantBuilder`生成变量。每个变体都指定了一个[属性][properties]列表(`PartialBlockstate`),当该列表与存档中的`BlockState`匹配时,将显示从相应模型列表中选择的模型。如果存在未被定义的任何变体覆盖的`BlockState`,则抛出异常。对于任何`BlockState`,只有一种变体可以为true。 -A `PartialBlockstate` is typically defined using one of three methods: +`PartialBlockstate`通常使用以下三种方法之一进行定义: -Method | Description +方法 | 描述 :---: | :--- -`partialState` | Creates a `PartialBlockstate` to be defined. -`forAllStates` | Defines a function where a given `BlockState` can be represented by an array of `ConfiguredModel`s. -`forAllStatesExcept` | Defines a function similar to `#forAllStates`; however, it also specifies which properties do not affect the models rendered. +`partialState` | 创建要定义的`PartialBlockstate`。 +`forAllStates` | 定义一个函数,其中给定的`BlockState`可以由`ConfiguredModel`的数组表示。 +`forAllStatesExcept` | 定义一个类似于`#forAllStates`的函数;但是,它还指定了哪些属性不会影响渲染的模型。 -For a `PartialBlockstate`, the properties defined can be specified (`#with`). The configured models can be set (`#setModels`), appended to the existing models (`#addModels`), or built (`#modelForState` and then `ConfiguredModel$Builder#addModel` once finished instead of `#ConfiguredModel$Builder#build`). +对于`PartialBlockstate`,可以指定定义的属性(`#with`)。 配置的模型可以设置(`#setModels`),附加到现有模型(`#addModels`),或构建(`#modelForState`,然后是`ConfiguredModel$Builder#addModel`,而不是`#ConfiguredModel$Builder#build`)。 ```java -// In some BlockStateProvider#registerStatesAndModels - -// EXAMPLE_BLOCK_1: Has Property BlockStateProperties#AXIS -this.getVariantBuilder(EXAMPLE_BLOCK_1) // Get variant builder - .partialState() // Construct partial state - .with(AXIS, Axis.Y) // When BlockState AXIS = Y - .modelForState() // Set models when AXIS = Y - .modelFile(yModelFile1) // Can show 'yModelFile1' - .nextModel() // Adds another model when AXIS = Y - .modelFile(yModelFile2) // Can show 'yModelFile2' - .weight(2) // Will show 'yModelFile2' 2/3 of the time - .addModel() // Finalizes models when AXIS = Y - .with(AXIS, Axis.Z) // When BlockState AXIS = Z - .modelForState() // Set models when AXIS = Z - .modelFile(hModelFile) // Can show 'hModelFile' - .addModel() // Finalizes models when AXIS = Z - .with(AXIS, Axis.X) // When BlockState AXIS = X - .modelForState() // Set models when AXIS = X - .modelFile(hModelFile) // Can show 'hModelFile' - .rotationY(90) // Rotates 'hModelFile' 90 degrees on the Y axis - .addModel(); // Finalizes models when AXIS = X - -// EXAMPLE_BLOCK_2: Has Property BlockStateProperties#HORIZONTAL_FACING -this.getVariantBuilder(EXAMPLE_BLOCK_2) // Get variant builder - .forAllStates(state -> // For all possible states - ConfiguredModel.builder() // Creates configured model builder - .modelFile(modelFile) // Can show 'modelFile' - .rotationY((int) state.getValue(HORIZONTAL_FACING).toYRot()) // Rotates 'modelFile' on the Y axis depending on the property - .build() // Creates the array of configured models +// 在某个BlockStateProvider#registerStatesAndModels中 + +// EXAMPLE_BLOCK_1:拥有属性BlockStateProperties#AXIS +this.getVariantBuilder(EXAMPLE_BLOCK_1) // 获取变量生成器 + .partialState() // 构建部分状态 + .with(AXIS, Axis.Y) // 当 BlockState AXIS = Y 时 + .modelForState() // 当 AXIS = Y 时设置模型 + .modelFile(yModelFile1) // 可以显示'yModelFile1' + .nextModel() // 当 AXIS = Y 时添加另一个模型 + .modelFile(yModelFile2) // 可以显示'yModelFile2' + .weight(2) // 此时将显示'yModelFile2' 2/3 + .addModel() // 完成当 AXIS = Y 时的模型 + .with(AXIS, Axis.Z) // 当 BlockState AXIS = Z 时 + .modelForState() // 当 AXIS = Z 时设置模型 + .modelFile(hModelFile) // 可以显示'hModelFile' + .addModel() // 完成当 AXIS = Z 时的模型 + .with(AXIS, Axis.X) // 当 BlockState AXIS = X 时 + .modelForState() // 当 AXIS = X 时设置模型 + .modelFile(hModelFile) // 可以显示'hModelFile' + .rotationY(90) // 绕Y轴将'hModelFile'旋转90度 + .addModel(); // 完成当 AXIS = X 时的模型 + +// EXAMPLE_BLOCK_2:拥有属性BlockStateProperties#HORIZONTAL_FACING +this.getVariantBuilder(EXAMPLE_BLOCK_2) // 获取变量生成器 + .forAllStates(state -> // 对于全部可能的状态 + ConfiguredModel.builder() // 创建配置模型生成器 + .modelFile(modelFile) // 可以显示'modelFile' + .rotationY((int) state.getValue(HORIZONTAL_FACING).toYRot()) // 根据变量需求将'modelFile'绕Y轴旋转 + .build() // 创建配置模型的数组 ); -// EXAMPLE_BLOCK_3: Has Properties BlockStateProperties#HORIZONTAL_FACING, BlockStateProperties#WATERLOGGED -this.getVariantBuilder(EXAMPLE_BLOCK_3) // Get variant builder - .forAllStatesExcept(state -> // For all HORIZONTAL_FACING states - ConfiguredModel.builder() // Creates configured model builder - .modelFile(modelFile) // Can show 'modelFile' - .rotationY((int) state.getValue(HORIZONTAL_FACING).toYRot()) // Rotates 'modelFile' on the Y axis depending on the property - .build(), // Creates the array of configured models - WATERLOGGED); // Ignores WATERLOGGED property +// EXAMPLE_BLOCK_3:拥有属性BlockStateProperties#HORIZONTAL_FACING, BlockStateProperties#WATERLOGGED +this.getVariantBuilder(EXAMPLE_BLOCK_3) // 获取变量生成器 + .forAllStatesExcept(state -> // 对于全部HORIZONTAL_FACING状态 + ConfiguredModel.builder() // 创建配置模型生成器 + .modelFile(modelFile) // 可以显示'modelFile' + .rotationY((int) state.getValue(HORIZONTAL_FACING).toYRot()) // 根据变量需求将'modelFile'绕Y轴旋转 + .build(), // 创建配置模型的数组 + WATERLOGGED); // 忽略WATERLOGGED属性 ``` ### `MultiPartBlockStateBuilder` -Multiparts can be generated using `BlockStateProvider#getMultipartBuilder`. Each part (`MultiPartBlockStateBuilder#part`) specifies a group of conditions of properties which when matches a `BlockState` in a level, will display a model from the model list. All condition groups that match the `BlockState` will display their chosen model overlaid on each other. +可以使用`BlockStateProvider#getMultipartBuilder`生成多部分。每个部分(`MultiPartBlockStateBuilder#part`)指定一组属性条件,当与存档中的`BlockState`匹配时,将显示模型列表中的模型。所有与`BlockState`匹配的条件组将显示它们所选的模型。 -For any part (obtained via `ConfiguredModel$Builder#addModel`), a condition can be added (via `#condition`) when a property is one of the specified values. Conditions must all succeed or, when `#useOr` is set, at least one must succeed. Conditions can be grouped (via `#nestedGroup`) as long as the current grouping only contains other groups and no single conditions. Groups of conditions can be left using `#endNestedGroup` and a given part can be finished via `#end`. +对于任何部分(通过`ConfiguredModel$Builder#addModel`获得),当属性是指定值之一时,可以添加条件(通过`#condition`)。条件必须全部成功,或者当设置了`#useOr`时,必须至少有一个成功。只要当前分组只包含其他组而不包含单个条件,就可以对条件进行分组(通过`#nestedGroup`)。条件组可以使用`#endNestedGroup`留下,给定的部分可以通过`#end`完成。 ```java -// In some BlockStateProvider#registerStatesAndModels - -// Redstone Wire -this.getMultipartBuilder(REDSTONE) // Get multipart builder - .part() // Create part - .modelFile(redstoneDot) // Can show 'redstoneDot' - .addModel() // 'redstoneDot' is displayed when... - .useOr() // At least one of these conditions are true - .nestedGroup() // true when all grouped conditions are true - .condition(WEST_REDSTONE, NONE) // true when WEST_REDSTONE is NONE - .condition(EAST_REDSTONE, NONE) // true when EAST_REDSTONE is NONE - .condition(SOUTH_REDSTONE, NONE) // true when SOUTH_REDSTONE is NONE - .condition(NORTH_REDSTONE, NONE) // true when NORTH_REDSTONE is NONE - .endNestedGroup() // End group - .nestedGroup() // true when all grouped conditions are true - .condition(EAST_REDSTONE, SIDE, UP) // true when EAST_REDSTONE is SIDE or UP - .condition(NORTH_REDSTONE, SIDE, UP) // true when NORTH_REDSTONE is SIDE or UP - .endNestedGroup() // End group - .nestedGroup() // true when all grouped conditions are true - .condition(EAST_REDSTONE, SIDE, UP) // true when EAST_REDSTONE is SIDE or UP - .condition(SOUTH_REDSTONE, SIDE, UP) // true when SOUTH_REDSTONE is SIDE or UP - .endNestedGroup() // End group - .nestedGroup() // true when all grouped conditions are true - .condition(WEST_REDSTONE, SIDE, UP) // true when WEST_REDSTONE is SIDE or UP - .condition(SOUTH_REDSTONE, SIDE, UP) // true when SOUTH_REDSTONE is SIDE or UP - .endNestedGroup() // End group - .nestedGroup() // true when all grouped conditions are true - .condition(WEST_REDSTONE, SIDE, UP) // true when WEST_REDSTONE is SIDE or UP - .condition(NORTH_REDSTONE, SIDE, UP) // true when NORTH_REDSTONE is SIDE or UP - .endNestedGroup() // End group - .end() // Finish part - .part() // Create part - .modelFile(redstoneSide0) // Can show 'redstoneSide0' - .addModel() // 'redstoneSide0' is displayed when... - .condition(NORTH_REDSTONE, SIDE, UP) // NORTH_REDSTONE is SIDE or UP - .end() // Finish part - .part() // Create part - .modelFile(redstoneSideAlt0) // Can show 'redstoneSideAlt0' - .addModel() // 'redstoneSideAlt0' is displayed when... - .condition(SOUTH_REDSTONE, SIDE, UP) // SOUTH_REDSTONE is SIDE or UP - .end() // Finish part - .part() // Create part - .modelFile(redstoneSideAlt1) // Can show 'redstoneSideAlt1' - .rotationY(270) // Rotates 'redstoneSideAlt1' 270 degrees on the Y axis - .addModel() // 'redstoneSideAlt1' is displayed when... - .condition(EAST_REDSTONE, SIDE, UP) // EAST_REDSTONE is SIDE or UP - .end() // Finish part - .part() // Create part - .modelFile(redstoneSide1) // Can show 'redstoneSide1' - .rotationY(270) // Rotates 'redstoneSide1' 270 degrees on the Y axis - .addModel() // 'redstoneSide1' is displayed when... - .condition(WEST_REDSTONE, SIDE, UP) // WEST_REDSTONE is SIDE or UP - .end() // Finish part - .part() // Create part - .modelFile(redstoneUp) // Can show 'redstoneUp' - .addModel() // 'redstoneUp' is displayed when... - .condition(NORTH_REDSTONE, UP) // NORTH_REDSTONE is UP - .end() // Finish part - .part() // Create part - .modelFile(redstoneUp) // Can show 'redstoneUp' - .rotationY(90) // Rotates 'redstoneUp' 90 degrees on the Y axis - .addModel() // 'redstoneUp' is displayed when... - .condition(EAST_REDSTONE, UP) // EAST_REDSTONE is UP - .end() // Finish part - .part() // Create part - .modelFile(redstoneUp) // Can show 'redstoneUp' - .rotationY(180) // Rotates 'redstoneUp' 180 degrees on the Y axis - .addModel() // 'redstoneUp' is displayed when... - .condition(SOUTH_REDSTONE, UP) // SOUTH_REDSTONE is UP - .end() // Finish part - .part() // Create part - .modelFile(redstoneUp) // Can show 'redstoneUp' - .rotationY(270) // Rotates 'redstoneUp' 270 degrees on the Y axis - .addModel() // 'redstoneUp' is displayed when... - .condition(WEST_REDSTONE, UP) // WEST_REDSTONE is UP - .end(); // Finish part +// 在某个BlockStateProvider#registerStatesAndModels中 + +// 红石线 +this.getMultipartBuilder(REDSTONE) // 获取多部分生成器 + .part() // 创建一个部分 + .modelFile(redstoneDot) // 可以显示'redstoneDot' + .addModel() // 'redstoneDot'被显示,当... + .useOr() // 这些条件中至少一个为true + .nestedGroup() // 当所有组合条件均为true时,true + .condition(WEST_REDSTONE, NONE) // 当WEST_REDSTONE为NONE时,true + .condition(EAST_REDSTONE, NONE) // 当EAST_REDSTONE为NONE时,true + .condition(SOUTH_REDSTONE, NONE) // 当SOUTH_REDSTONE为NONE时,true + .condition(NORTH_REDSTONE, NONE) // 当NORTH_REDSTONE为NONE时,true + .endNestedGroup() // 结束组合 + .nestedGroup() // 当所有组合条件均为true时,true + .condition(EAST_REDSTONE, SIDE, UP) // 当EAST_REDSTONE为SIDE或UP时,true + .condition(NORTH_REDSTONE, SIDE, UP) // 当NORTH_REDSTONE为SIDE或UP时,true + .endNestedGroup() // 结束组合 + .nestedGroup() // 当所有组合条件均为true时,true + .condition(EAST_REDSTONE, SIDE, UP) // 当EAST_REDSTONE为SIDE或UP时,true + .condition(SOUTH_REDSTONE, SIDE, UP) // 当SOUTH_REDSTONE为SIDE或UP时,true + .endNestedGroup() // 结束组合 + .nestedGroup() // 当所有组合条件均为true时,true + .condition(WEST_REDSTONE, SIDE, UP) // 当WEST_REDSTONE为SIDE或UP时,true + .condition(SOUTH_REDSTONE, SIDE, UP) // 当SOUTH_REDSTONE为SIDE或UP时,true + .endNestedGroup() // 结束组合 + .nestedGroup() // 当所有组合条件均为true时,true + .condition(WEST_REDSTONE, SIDE, UP) // 当WEST_REDSTONE为SIDE或UP时,true + .condition(NORTH_REDSTONE, SIDE, UP) // 当NORTH_REDSTONE为SIDE或UP时,true + .endNestedGroup() // 结束组合 + .end() // 结束该部分 + .part() // 创建一个部分 + .modelFile(redstoneSide0) // 可以显示'redstoneSide0' + .addModel() // 'redstoneSide0'被显示,当... + .condition(NORTH_REDSTONE, SIDE, UP) // NORTH_REDSTONE为SIDE或UP + .end() // 结束该部分 + .part() // 创建一个部分 + .modelFile(redstoneSideAlt0) // 可以显示'redstoneSideAlt0' + .addModel() // 'redstoneSideAlt0'被显示,当... + .condition(SOUTH_REDSTONE, SIDE, UP) // SOUTH_REDSTONE为SIDE或UP + .end() // 结束该部分 + .part() // 创建一个部分 + .modelFile(redstoneSideAlt1) // 可以显示'redstoneSideAlt1' + .rotationY(270) // 将'redstoneSideAlt1'绕Y轴旋转270度 + .addModel() // 'redstoneSideAlt1'被显示,当... + .condition(EAST_REDSTONE, SIDE, UP) // EAST_REDSTONE为SIDE或UP + .end() // 结束该部分 + .part() // 创建一个部分 + .modelFile(redstoneSide1) // 可以显示'redstoneSide1' + .rotationY(270) // 将'redstoneSide1'绕Y轴旋转270度 + .addModel() // 'redstoneSide1'被显示,当... + .condition(WEST_REDSTONE, SIDE, UP) // WEST_REDSTONE为SIDE或UP + .end() // 结束该部分 + .part() // 创建一个部分 + .modelFile(redstoneUp) // 可以显示'redstoneUp' + .addModel() // 'redstoneUp'被显示,当... + .condition(NORTH_REDSTONE, UP) // NORTH_REDSTONE为UP + .end() // 结束该部分 + .part() // 创建一个部分 + .modelFile(redstoneUp) // 可以显示'redstoneUp' + .rotationY(90) // 将'redstoneUp'绕Y轴旋转90度 + .addModel() // 'redstoneUp'被显示,当... + .condition(EAST_REDSTONE, UP) // EAST_REDSTONE为UP + .end() // 结束该部分 + .part() // 创建一个部分 + .modelFile(redstoneUp) // 可以显示'redstoneUp' + .rotationY(180) // 将'redstoneUp'绕Y轴旋转180度 + .addModel() // 'redstoneUp'被显示,当... + .condition(SOUTH_REDSTONE, UP) // SOUTH_REDSTONE为UP + .end() // 结束该部分 + .part() // 创建一个部分 + .modelFile(redstoneUp) // 可以显示'redstoneUp' + .rotationY(270) // 将'redstoneUp'绕Y轴旋转270度 + .addModel() // 'redstoneUp'被显示,当... + .condition(WEST_REDSTONE, UP) // WEST_REDSTONE为UP + .end(); // 结束该部分 ``` -Model Loader Builders ---------------------- +模型加载器生成器 +--------------- -Custom model loaders can also be generated for a given `ModelBuilder`. Custom model loaders subclass `CustomLoaderBuilder` and can be applied to a `ModelBuilder` via `#customLoader`. The factory method passed in creates a new loader builder to which configurations can be made. After all the changes have been finished, the custom loader can return back to the `ModelBuilder` via `CustomLoaderBuilder#end`. +还可以为给定的`ModelBuilder`生成自定义模型加载器。自定义模型加载器子类`CustomLoaderBuilder`,可以通过`#customLoader`应用于`ModelBuilder`。传入的工厂方法创建了一个新的加载器生成器,可以对其进行配置。完成所有更改后,自定义加载器可以通过`CustomLoaderBuilder#end`返回到`ModelBuilder`。 -Model Builder | Factory Method | Description +模型生成器 | 工厂方法 | 描述 :---: | :---: | :--- -`DynamicFluidContainerModelBuilder` | `#begin` | Generates a bucket model for the specified fluid. -`CompositeModelBuilder` | `#begin` | Generates a model composed of models. -`ItemLayersModelBuilder` | `#begin` | Generates a Forge implementation of an `item/generated` model. -`SeparateTransformsModelBuilder` | `#begin` | Generates a model which changes based on the specified [transform]. -`ObjModelBuilder` | `#begin` | Generates an [OBJ model][obj]. +`DynamicFluidContainerModelBuilder` | `#begin` | 为特定的流体生成一个桶模型。 +`CompositeModelBuilder` | `#begin` | 生成一个由模型组成的模型。 +`ItemLayersModelBuilder` | `#begin` | 生成一个`item/generated`模型的Forge实现。 +`SeparateTransformsModelBuilder` | `#begin` | 生成一个模型,其修改基于特定的[变换][transform]。 +`ObjModelBuilder` | `#begin` | 生成一个[OBJ模型][obj]。 ```java -// For some BlockModelBuilder builder -builder.customLoader(ObjModelBuilder::begin) // Custom loader 'forge:obj' - .modelLocation(modLoc("models/block/model.obj")) // Set the OBJ model location - .flipV(true) // Flips the V coordinate in the supplied .mtl texture - .end() // Finish custom loader configuration -.texture("particle", mcLoc("block/dirt")) // Set particle texture to dirt -.texture("texture0", mcLoc("block/dirt")); // Set 'texture0' texture to dirt +// 对于某个BlockModelBuilder生成器 +builder.customLoader(ObjModelBuilder::begin) // 自定义加载器'forge:obj' + .modelLocation(modLoc("models/block/model.obj")) // 设置OBJ模型位置 + .flipV(true) // 在提供的.mtl纹理中翻转V坐标 + .end() // 完成自定义加载器配置 +.texture("particle", mcLoc("block/dirt")) // 将粒子纹理设置为泥土 +.texture("texture0", mcLoc("block/dirt")); // 将'texture0'纹理设置为泥土 ``` -Custom Model Loader Builders ----------------------------- +自定义模型加载器生成器 +-------------------- -Custom loader builders can be created by extending `CustomLoaderBuilder`. The constructor can still have a `protected` visibility with the `ResourceLocation` hardcoded to the loader id registered via `ModelEvent$RegisterGeometryLoaders#register`. The builder can then be initialized via a static factory method or the constructor if made `public`. +可以通过扩展`CustomLoaderBuilder`来创建自定义加载器生成器。构造函数仍然可以具有`protected`的可见性,其中`ResourceLocation`硬编码为通过`ModelEvent$RegisterGeometryLoaders#register`注册的加载器id。然后,可以通过静态工厂方法或构造函数(如果设置为`public`)初始化生成器。 ```java public class ExampleLoaderBuilder> extends CustomLoaderBuilder { @@ -331,14 +323,14 @@ public class ExampleLoaderBuilder> extends CustomLoade Afterwards, any configurations specified by the loader should be added as chainable methods. ```java -// In ExampleLoaderBuilder +// 在ExampleLoaderBuilder中 public ExampleLoaderBuilder exampleInt(int example) { - // Set int + // 设置int return this; } public ExampleLoaderBuilder exampleString(String example) { - // Set string + // 设置string return this; } ``` @@ -346,21 +338,21 @@ public ExampleLoaderBuilder exampleString(String example) { If any additional configuration is specified, `#toJson` should be overridden to write the additional properties. ```java -// In ExampleLoaderBuilder +// 在ExampleLoaderBuilder中 @Override public JsonObject toJson(JsonObject json) { - json = super.toJson(json); // Handle base loader properties - // Encode custom loader properties + json = super.toJson(json); // 处理基础加载器属性 + // 编码自定义加载器属性 return json; } ``` -Custom Model Providers ----------------------- +自定义模型提供者 +--------------- -Custom model providers require a `ModelBuilder` subclass, which defines the base of the model to generate, and a `ModelProvider` subclass, which generates the models. +自定义模型提供者需要`ModelBuilder`子类和`ModelProvider`子类,前者定义要生成的模型的基础,后者生成模型。 -The `ModelBuilder` subclass contains any special properties to which can be applied specifically to those types of models (item models can have overrides). If any additional properties are added, `#toJson` needs to be overridden to write the additional information. +`ModelBuilder`子类包含任何特殊属性,这些属性可以专门应用于这些类型的模型(物品模型可以具有重写)。如果添加了任何附加属性,则需要重写`#toJson`以写入附加信息。 ```java public class ExampleModelBuilder extends ModelBuilder { @@ -368,22 +360,22 @@ public class ExampleModelBuilder extends ModelBuilder { } ``` -The `ModelProvider` subclass requires no special logic. The constructor should hardcode the subdirectory within the `models` folder and the `ModelBuilder` to represent the to-be-generated models. +`ModelProvider`子类不需要特殊的逻辑。构造函数应硬编码`models`文件夹和`ModelBuilder`中的子目录,以表示要生成的模型。 ```java public class ExampleModelProvider extends ModelProvider { public ExampleModelProvider(PackOutput output, String modid, ExistingFileHelper existingFileHelper) { - // Models will be generated to 'assets//models/example' if no 'modid' is specified in '#getBuilder' + // 如果'#getBuilder'中未指定'modid',则模型将生成到'assets//models/example' super(output, modid, "example", ExampleModelBuilder::new, existingFileHelper); } } ``` -Custom Model Consumers ----------------------- +自定义模型Consumer +----------------- -Custom model consumers like [`BlockStateProvider`][blockstateprovider] can be created by manually generating the models themselves. The `ModelProvider` subclass used to generate the models should be specified and made available. +自定义模型Consumer,如[`BlockStateProvider`][blockstateprovider],可以通过手动生成模型来创建。应指定用于生成模型的`ModelProvider`子类并使其可用。 ```java public class ExampleModelConsumerProvider implements IDataProvider { @@ -394,19 +386,19 @@ public class ExampleModelConsumerProvider implements IDataProvider { } ``` -Once the data provider is running, the models within the `ModelProvider` subclass can be generated using `ModelProvider#generateAll`. +一旦数据提供者运行,就可以使用`ModelProvider#generateAll`生成`ModelProvider`子类中的模型。 ```java -// In ExampleModelConsumerProvider +// 在ExampleModelConsumerProvider中 @Override public CompletableFuture run(CachedOutput cache) { - // Populate the model provider - CompletableFuture exampleFutures = this.example.generateAll(cache); // Generate the models + // 填入模型提供者 + CompletableFuture exampleFutures = this.example.generateAll(cache); // 生成模型 - // Run logic and create CompletableFuture(s) for writing files + // 运行逻辑并创建CompletableFuture以写入文件 // ... - // Assume we have a new CompletableFuture providerFuture + // 假设我们有一个新的CompletableFuture providerFuture return CompletableFuture.allOf(exampleFutures, providerFuture); } ``` @@ -419,7 +411,7 @@ public CompletableFuture run(CachedOutput cache) { [color]: ../../resources/client/models/tinting.md#blockcoloritemcolor [overrides]: ../../resources/client/models/itemproperties.md [blockstateprovider]: #block-state-provider -[blockstate]: https://minecraft.wiki/w/Tutorials/Models#Block_states +[blockstate]: https://minecraft.fandom.com/wiki/Tutorials/Models#Block_states [blockmodels]: #blockmodelprovider [itemmodels]: #itemmodelprovider [properties]: ../../blocks/states.md#implementing-block-states diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/datagen/client/sounds.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/datagen/client/sounds.md index 6f0ec9136..1073fac29 100644 --- a/i18n/zh-CN/docusaurus-plugin-content-docs/current/datagen/client/sounds.md +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/datagen/client/sounds.md @@ -1,81 +1,80 @@ -Sound Definition Generation -=========================== +音效定义生成 +=========== -The `sounds.json` file can be generated for a mod by subclassing `SoundDefinitionsProvider` and implementing `#registerSounds`. After implementation, the provider must be [added][datagen] to the `DataGenerator`. +通过子类化`SoundDefinitionsProvider`并实现`#registerSounds`,可以为模组生成`sounds.json`文件。实现后,必须将提供者[添加][datagen]到`DataGenerator`中。 ```java -// On the MOD event bus +// 在模组事件总线上 @SubscribeEvent public void gatherData(GatherDataEvent event) { event.getGenerator().addProvider( - // Tell generator to run only when client assets are generating + // 告诉生成器仅在生成客户端资源时运行 event.includeClient(), output -> new MySoundDefinitionsProvider(output, MOD_ID, event.getExistingFileHelper()) ); } ``` -Adding a Sound --------------- +添加一个音效 +----------- -A sound definition can be generated by specifying the sound name and definition via `#add`. The sound name can either be provided from a [`SoundEvent`][soundevent], `ResourceLocation`, or string. +可以通过`#add`指定音效名称和定义来生成音效定义。音效名称可以从[`SoundEvent`][soundevent]、`ResourceLocation`或字符串中提供。 -:::caution -The sound name supplied will always assume the namespace is the mod id supplied to the constructor of the provider. There is no validation performed on the namespace of the sound name! -::: +!!! 警告 + 提供的音效名称将始终假定命名空间是提供给提供者的构造函数的mod id。没有对音效名称的命名空间执行验证! ### `SoundDefinition` -The `SoundDefinition` can be created using `#definition`. The definition contains the data to define a sound instance. +可以使用`#definition`创建`SoundDefinition`。定义包含用于定义音效实例的数据。 -A definition specifies a few methods: +定义指定了一些方法: -Method | Description +方法 | 描述 :---: | :--- -`with` | Adds a sound(s) which may be played when the definition is selected. -`subtitle` | Sets the translation key of the definition. -`replace` | When `true`, removes the sounds already defined by other `sounds.json` for this definition instead of appending to it. +`with` | 添加选择定义时可能播放的音效。 +`subtitle` | 设置定义的翻译键。 +`replace` | 当为`true`时,将删除其他`sounds.json`为该定义定义的音效,而不是附加到该定义。 ### `SoundDefinition$Sound` -A sound supplied to the `SoundDefinition` can be specified using `SoundDefinitionsProvider#sound`. These methods take in the reference of the sound and a `SoundType` if specified. +提供给`SoundDefinition`的音效可以使用`SoundDefinitionsProvider#sound`指定。这些方法采用音效的引用和`SoundType`(如果已指定)。 -The `SoundType` can be one of two values: +`SoundType`可以是两个值之一: -Sound Type | Definition +音效类型 | 定义 :---: | :--- -`SOUND` | Specifies a reference to the sound located at `assets//sounds/.ogg`. -`EVENT` | Specifies a reference to the name of another sound defined by the `sounds.json`. +`SOUND` | 指定位于`assets//sounds/.ogg`的音效的一个引用。 +`EVENT` | 指定由`sounds.json`定义的另一个音效的名称的引用。 -Each `Sound` created from `SoundDefinitionsProvider#sound` can specify additional configurations on how to load and play the sound provided: +从`SoundDefinitionsProvider#sound`创建的每个`Sound`都可以指定关于如何加载和播放所提供音效的其他配置: -Method | Description +方法 | 描述 :---: | :--- -`volume` | Sets the volume scale of the sound, must be greater than `0`. -`pitch` | Sets the pitch scale of the sound, must be greater than `0`. -`weight` | Sets the likelihood of the sound getting played when the sound is selected. -`stream` | When `true`, reads the sound from file instead of loading the sound into memory. Recommended for long sounds: background music, music discs, etc. -`attenuationDistance` | Sets the number of blocks the sound can be heard from. -`preload` | When `true`, immediately loads the sound into memory as soon as the resource pack is loaded. +`volume` | 设置音效的音量大小,必须大于`0`。 +`pitch` | 设置音效的音高大小,必须大于`0`。 +`weight` | 设置音效被选定时播放音效的可能性。 +`stream` | 当为`true`时,从文件中读取音效,而不是将音效加载到内存中。推荐用于长音效:背景音乐、音乐唱片等。 +`attenuationDistance` | 设置可以听到音效的所距离的方块数。 +`preload` | 当为`true`时,一旦加载资源包,就会立即将音效加载到内存中。 ```java -// In some SoundDefinitionsProvider#registerSounds +// 在某个SoundDefinitionsProvider#registerSounds中 this.add(EXAMPLE_SOUND_EVENT, definition() - .subtitle("sound.examplemod.example_sound") // Set translation key + .subtitle("sound.examplemod.example_sound") // 设置翻译键 .with( - sound(new ResourceLocation(MODID, "example_sound_1")) // Set first sound - .weight(4) // Has a 4 / 5 = 80% chance of playing - .volume(0.5), // Scales all volumes called on this sound by half - sound(new ResourceLocation(MODID, "example_sound_2")) // Set second sound - .stream() // Streams the sound + sound(new ResourceLocation(MODID, "example_sound_1")) // 设置第一个音效 + .weight(4) // 具有4 / 5 = 80%的播放机率 + .volume(0.5), // 将调用此音效的所有音量缩放一半 + sound(new ResourceLocation(MODID, "example_sound_2")) // 设置第二个音效 + .stream() // 流播该音效 ) ); this.add(EXAMPLE_SOUND_EVENT_2, definition() - .subtitle("sound.examplemod.example_sound") // Set translation key + .subtitle("sound.examplemod.example_sound") // 设置翻译键 .with( - sound(EXAMPLE_SOUND_EVENT.getLocation(), SoundType.EVENT) // Adds sounds from 'EXAMPLE_SOUND_EVENT' - .pitch(0.5) // Scales all pitches called on this sound by half + sound(EXAMPLE_SOUND_EVENT.getLocation(), SoundType.EVENT) // 从'EXAMPLE_SOUND_EVENT'添加音效 + .pitch(0.5) // 将调用此音效的所有音高缩放一半 ) ); ``` diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/datagen/index.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/datagen/index.md index eaf6e05ba..35bb5aea7 100644 --- a/i18n/zh-CN/docusaurus-plugin-content-docs/current/datagen/index.md +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/datagen/index.md @@ -1,73 +1,73 @@ -Data Generators -=============== +数据生成 +======== -Data generators are a way to programmatically generate the assets and data of mods. It allows the definition of the contents of these files in the code and their automatic generation, without worrying about the specifics. +数据生成器是以编程方式生成模组的资源(asset)和数据(data)的一种方式。它允许在代码中定义这些文件的内容并自动生成它们,而不必担心细节。 -The data generator system is loaded by the main class `net.minecraft.data.Main`. Different command-line arguments can be passed to customize which mods' data are gathered, what existing files are considered, etc. The class responsible for data generation is `net.minecraft.data.DataGenerator`. +数据生成器系统由主类`net.minecraft.data.Main`加载。可以传递不同的命令行参数来自定义收集了哪些模组的数据,考虑了哪些现有文件等。负责数据生成的类是`net.minecraft.data.DataGenerator`。 -The default configurations in the MDK `build.gradle` adds the `runData` task for running the data generators. +MDK的`build.gradle`中的默认配置添加了用于运行数据生成器的`runData`任务。 -Existing Files --------------- -All references to textures or other data files not generated for data generation must reference existing files on the system. This is to ensure that all referenced textures are in the correct places, so typos can be found and corrected. +现存的文件 +--------- +对未为数据生成而生成的纹理或其他数据文件的所有引用都必须引用系统上的现有文件。这是为了确保所有引用的纹理都在正确的位置,这样就可以找到并更正拼写错误。 -`ExistingFileHelper` is the class responsible for validating the existence of those data files. An instance can be retrieved from `GatherDataEvent#getExistingFileHelper`. +`ExistingFileHelper`是负责验证这些数据文件是否存在的类。可以从`GatherDataEvent#getExistingFileHelper`中检索实例。 -The `--existing ` argument allows the specified folder and its subfolders to be used when validating the existence of files. Additionally, the `--existing-mod ` argument allows the resources of a loaded mod to be used for validation. By default, only the vanilla datapack and resources are available to the `ExistingFileHelper`. +`--existing `参数允许在验证文件是否存在时使用指定的文件夹及其子文件夹。此外,`--existing-mod `参数允许将加载的模组的资源用于验证。默认情况下,只有普通的数据包和资源可用于`ExistingFileHelper`。 -Generator Modes ---------------- +生成器模式 +--------- -The data generator can be configured to run 4 different data generations, which are configured from the command-line parameters, and can be checked from `GatherDataEvent#include***` methods. +数据生成器可以配置为运行4个不同的数据生成,这些数据生成是通过命令行参数配置的,并且可以通过`GatherDataEvent#include***`方法进行检查。 * __Client Assets__ - * Generates client-only files in `assets`: block/item models, blockstate JSONs, language files, etc. + * 在`assets`中生成仅客户端文件:f方块/物品模型、方块状态JSON、语言文件等。 * __`--client`__, `#includeClient` * __Server Data__ - * Generates server-only files in `data`: recipes, advancements, tags, etc. + * 在`data`中生成仅服务端文件:配方、进度、标签等。 * __`--server`__, `#includeServer` * __Development Tools__ - * Runs some development tools: converting SNBT to NBT and vice-versa, etc. + * 运行一些开发工具:将SNBT转换为NBT,反之亦然,等等。 * __`--dev`__, `#includeDev` * __Reports__ - * Dumps all registered blocks, items, commands, etc. + * 转储所有已注册的方块、物品、命令等。 * __`--reports`__, `#includeReports` -All of the generators can be included using `--all`. +所有的生成器都可以使用`--all`包含在内。 -Data Providers --------------- +数据提供者 +--------- -Data providers are the classes that actually define what data will be generated and provided. All data providers implement `DataProvider`. Minecraft has abstract implementations for most assets and data, so modders need only to extend and override the specified method. +数据提供者是实际定义将生成和提供哪些数据的类。所有数据提供者都实现`DataProvider`。Minecraft对大多数asset和data都有抽象实现,因此模组开发者只需要扩展和覆盖指定的方法。 -The `GatherDataEvent` is fired on the mod event bus when the data generator is being created, and the `DataGenerator` can be obtained from the event. Create and register data providers using `DataGenerator#addProvider`. +当创建数据生成器时,在模组事件总线上触发`GatherDataEvent`,并且可以从事件中获取`DataGenerator`。使用`DataGenerator#addProvider`创建和注册数据提供者。 -### Client Assets -* [`net.minecraftforge.common.data.LanguageProvider`][langgen] - for [language strings][lang]; implement `#addTranslations` -* [`net.minecraftforge.common.data.SoundDefinitionsProvider`][soundgen] - for [`sounds.json`][sounds]; implement `#registerSounds` -* [`net.minecraftforge.client.model.generators.ModelProvider`][modelgen] - for [models]; implement `#registerModels` - * [`ItemModelProvider`][itemmodelgen] - for item models - * [`BlockModelProvider`][blockmodelgen] - for block models -* [`net.minecraftforge.client.model.generators.BlockStateProvider`][blockstategen] - for blockstate JSONs and their block and item models; implement `#registerStatesAndModels` +### 客户端资源(Assets) +* [`net.minecraftforge.common.data.LanguageProvider`][langgen] - 针对[语言设置][lang];实现`#addTranslations` +* [`net.minecraftforge.common.data.SoundDefinitionsProvider`][soundgen] - 针对[`sounds.json`][sounds];实现`#registerSounds` +* [`net.minecraftforge.client.model.generators.ModelProvider`][modelgen] - 针对[模型];实现`#registerModels` + * [`ItemModelProvider`][itemmodelgen] - 针对物品模型 + * [`BlockModelProvider`][blockmodelgen] - 针对方块模型 +* [`net.minecraftforge.client.model.generators.BlockStateProvider`][blockstategen] - 针对方块状态JSON以及其方块和物品模型;实现`#registerStatesAndModels` -### Server Data +### 服务端数据(Data) -**These classes are under the `net.minecraftforge.common.data` package**: +**这些类在`net.minecraftforge.common.data`包之下**: -* [`GlobalLootModifierProvider`][glmgen] - for [global loot modifiers][glm]; implement `#start` -* [`DatapackBuiltinEntriesProvider`][datapackregistriesgen] for datapack registry objects; pass in `RegistrySetBuilder` to the constructor +* [`GlobalLootModifierProvider`][glmgen] - 针对[全局战利品修改器][glm];实现`#start` +* [`DatapackBuiltinEntriesProvider`][datapackregistriesgen] - 针对数据包注册表对象;向构造函数传递`RegistrySetBuilder` -**These classes are under the `net.minecraft.data` package**: +**这些类在`net.minecraft.data`包之下**: -* [`loot.LootTableProvider`][loottablegen] - for [loot tables][loottable]; pass in `LootTableProvider$SubProviderEntry`s to the constructor -* [`recipes.RecipeProvider`][recipegen] - for [recipes] and their unlocking advancements; implement `#buildRecipes` -* [`tags.TagsProvider`][taggen] - for [tags]; implement `#addTags` -* [`advancements.AdvancementProvider`][advgen] - for [advancements]; pass in `AdvancementSubProvider`s to the constructor +* [`loot.LootTableProvider`][loottablegen] - 针对[战利品表][loottable];向构造函数传递`LootTableProvider$SubProviderEntry` +* [`recipes.RecipeProvider`][recipegen] - 针对[配方]以及其解锁的进度;实现`#buildRecipes` +* [`tags.TagsProvider`][taggen] - 针对[标签];实现`#addTags` +* [`advancements.AdvancementProvider`][advgen] - 针对[进度];向构造函数传递`AdvancementSubProvider` [langgen]: ./client/localization.md -[lang]: https://minecraft.wiki/w/Language +[lang]: https://minecraft.fandom.com/wiki/Language [soundgen]: ./client/sounds.md -[sounds]: https://minecraft.wiki/w/Sounds.json +[sounds]: https://minecraft.fandom.com/wiki/Sounds.json [modelgen]: ./client/modelproviders.md [models]: ../resources/client/models/index.md [itemmodelgen]: ./client/modelproviders.md#itemmodelprovider diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/datagen/server/advancements.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/datagen/server/advancements.md index 4b27b85d1..6f818b01b 100644 --- a/i18n/zh-CN/docusaurus-plugin-content-docs/current/datagen/server/advancements.md +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/datagen/server/advancements.md @@ -1,23 +1,23 @@ -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`来为模组生成[进度][Advancements]。进度既可以手动创建和提供,也可以为方便起见,使用`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 +// 在模组事件总线上 @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, /*...*/) ) ); @@ -27,39 +27,39 @@ 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` | 设置要显示在聊天、toast和进度屏幕上的信息。 +`rewards` | 设置此进度完成时获得的奖励。 +`addCriterion` | 为此进度添加一个条件。 +`requirements` | 指定是所有条件都必须返回true,还是至少有一个条件必须返回true。可以使用额外的重载来混合和匹配这些操作。 -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 diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/datagen/server/datapackregistries.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/datagen/server/datapackregistries.md index 3f105921e..82190759b 100644 --- a/i18n/zh-CN/docusaurus-plugin-content-docs/current/datagen/server/datapackregistries.md +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/datagen/server/datapackregistries.md @@ -1,24 +1,25 @@ -Datapack Registry Object Generation -================================== +数据包注册表对象生成 +================== Datapack registry objects can be generated for a mod by constructing a new `DatapackBuiltinEntriesProvider` and providing a `RegistrySetBuilder` with the new objects to register. The provider must be [added][datagen] to the `DataGenerator`. +通过构造新的`DatapackBuiltinEntriesProvider`并为`RegistrySetBuilder`提供要注册的新对象,可以为模组生成数据包注册表对象。该提供者必须被[添加][datagen]到`DataGenerator`中。 -!!! note - `DatapackBuiltinEntriesProvider` is a Forge extension on top of `RegistriesDatapackGenerator` which properly handles referencing existing datapack registry objects without exploding the entry. So, this documentation will use `DatapackBuiltinEntriesProvider`. +!!! 注意 + `DatapackBuiltinEntriesProvider`是`RegistriesDatapackGenerator`之上的一个Forge扩展,它可以正确处理引用现有数据包注册表对象而不会分解条目。因此,本文档将使用`DatapackBuiltinEntriesProvider`。 ```java -// 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 DatapackBuiltinEntriesProvider( output, event.getLookupProvider(), - // The builder containing the datapack registry objects to generate + // 包含要生成的数据包注册表对象的生成器 new RegistrySetBuilder().add(/* ... */), - // Set of mod ids to generate the datapack registry objects of + // 用于生成的数据包注册表对象的mod id集合 Set.of(MOD_ID) ) ); @@ -28,29 +29,29 @@ public void gatherData(GatherDataEvent event) { `RegistrySetBuilder` -------------------- -A `RegistrySetBuilder` is responsible for building all datapack registry objects to be used within the game. The builder can add a new entry for a registry, which can then register objects to that registry. +`RegistrySetBuilder`负责构建游戏中使用的所有数据包注册表对象。生成器可以为注册表添加一个新条目,然后注册表可以将对象注册到该注册表中。 -First, a new instance of a `RegistrySetBuilder` can be initialized by calling the constructor. Then, the `#add` method (which takes in the `ResourceKey` of the registry, a `RegistryBootstrap` consumer containing the `BootstapContext` to register the objects, and an optional `Lifecycle` argument to indicate the registry's current lifecycle status) can be called to handle a specific registry for registration. +首先,可以通过调用构造函数来初始化`RegistrySetBuilder`的新实例。然后,可以调用`#add`方法(它接受注册表的`ResourceKey`,一个包含`BootstapContext`的`RegistryBootstrap` Consumer来注册对象,以及一个可选的`Lifecycle`参数来指示注册表的当前生命周期状态)来处理特定注册表进行注册。 ```java new RegistrySetBuilder() - // Create configured features + // 创建已配置的特性 .add(Registries.CONFIGURED_FEATURE, bootstrap -> { - // Register configured features here + // 在此处注册已配置的特性 }) - // Create placed features + // 创建已放置的特性 .add(Registries.PLACED_FEATURE, bootstrap -> { - // Register placed features here + // 在此处注册已放置的特性 }); ``` -!!! note - Datapack registries created through Forge can also generate their objects using this builder by also passing in the associated `ResourceKey`. +!!! 注意 + 通过Forge创建的数据包注册表也可以通过传递相关的`ResourceKey`来使用该生成器生成它们的对象。 -Registering with `BootstapContext` ----------------------------------- +使用`BootstapContext`注册 +------------------------- -The `#register` method in the `BootstapContext` provided by the builder can be used to register objects. It takes in the `ResourceKey` representing the registry name of the object, the object to register, and an optional `Lifecycle` argument to indicate the registry object's current lifecycle status. +生成器提供的`BootstapContext`中的`#register`方法可用于注册对象。它采用`ResourceKey`表示对象的注册表名称、要注册的对象,以及一个可选的`Lifecycle`参数来指示注册表对象的当前生命周期状态。 ```java public static final ResourceKey> EXAMPLE_CONFIGURED_FEATURE = ResourceKey.create( @@ -58,32 +59,32 @@ public static final ResourceKey> EXAMPLE_CONFIGURED_FEAT new ResourceLocation(MOD_ID, "example_configured_feature") ); -// In some constant location or argument +// 在某个恒定的位置或参数中 new RegistrySetBuilder() - // Create configured features + // 创建已配置的特性 .add(Registries.CONFIGURED_FEATURE, bootstrap -> { - // Register configured features here + // 在此处注册已配置的特性 bootstrap.register( - // The resource key for the configured feature + // 已配置的特性的资源键 EXAMPLE_CONFIGURED_FEATURE, new ConfiguredFeature<>( - Feature.ORE, // Create an ore feature + Feature.ORE, // 创建一个矿物特性 new OreConfiguration( - List.of(), // Does nothing - 8 // in veins of at most 8 + List.of(), // 不做任何事情 + 8 // 在最多8个矿脉中 ) ) ); }) - // Create placed features + // 创建已放置的特性 .add(Registries.PLACED_FEATURE, bootstrap -> { - // Register placed features here + // 在此处注册已放置的特性 }); ``` ### Datapack Registry Object Lookup -Sometimes datapack registry objects may want to use other datapack registry objects or tags containing datapack registry objects. In those cases, you can look up another datapack registry using `BootstapContext#lookup` to get a `HolderGetter`. From there, you can get a `Holder$Reference` to the datapack registry object or a `HolderSet$Named` for the tag via `#getOrThrow` by passing in the associated key. +有时,数据包注册表对象可能希望使用其他数据包注册表对象或包含数据包注册表对象的标签。在这种情况下,你可以使用`BootstapContext#lookup`查找另一个数据包注册表以获得`HolderGetter`。从那里,你可以通过`#getOrThrow`传递相关的键,获得数据包注册表对象的`Holder$Reference`或标签的`HolderSet$Named`。 ```java public static final ResourceKey> EXAMPLE_CONFIGURED_FEATURE = ResourceKey.create( @@ -96,30 +97,30 @@ public static final ResourceKey EXAMPLE_PLACED_FEATURE = Resource new ResourceLocation(MOD_ID, "example_placed_feature") ); -// In some constant location or argument +// 在某个恒定的位置或参数中 new RegistrySetBuilder() - // Create configured features + // 创建已配置的特性 .add(Registries.CONFIGURED_FEATURE, bootstrap -> { - // Register configured features here + // 在此处注册已配置的特性 bootstrap.register( - // The resource key for the configured feature + // 已配置的特性的资源键 EXAMPLE_CONFIGURED_FEATURE, new ConfiguredFeature(/* ... */) ); }) - // Create placed features + // 创建已放置的特性 .add(Registries.PLACED_FEATURE, bootstrap -> { - // Register placed features here + // 在此处注册已放置的特性 - // Get configured feature registry + // 获取已配置的特性的注册表 HolderGetter> configured = bootstrap.lookup(Registries.CONFIGURED_FEATURE); bootstrap.register( - // The resource key for the placed feature + // 已放置的特性的资源键 EXAMPLE_PLACED_FEATURE, new PlacedFeature( - configured.getOrThrow(EXAMPLE_CONFIGURED_FEATURE), // Get the configured feature - List.of() // and do nothing to the placement location + configured.getOrThrow(EXAMPLE_CONFIGURED_FEATURE), // 获取已配置的特性 + List.of() // 并对于放置位置不做任何事情 ) ) }); diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/datagen/server/glm.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/datagen/server/glm.md index 1192b2377..ae0388ac2 100644 --- a/i18n/zh-CN/docusaurus-plugin-content-docs/current/datagen/server/glm.md +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/datagen/server/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`来为模组生成[全局战利品修改器(GLM)][glm]。每个GLM都可以通过调用`#add`并指定要序列化的修改器和[修改器实例][instance]的名称来添加生成。实现后,该提供者必须被[添加][datagen]到`DataGenerator`中。 ```java -// 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) ); } -// 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, diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/datagen/server/loottables.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/datagen/server/loottables.md index 2436a2d2b..e907d2974 100644 --- a/i18n/zh-CN/docusaurus-plugin-content-docs/current/datagen/server/loottables.md +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/datagen/server/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 +// 在模组事件总线上 @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,18 +24,18 @@ 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`,该`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生成战利品表 } } ``` @@ -43,101 +43,101 @@ public class ExampleSubProvider implements LootTableSubProvider { The table can then be added to `LootTableProvider#getTables` for any available `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`。这些方法是为了确保Iterable中的所有对象都有一个战利品表。 -!!! 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`用于注册模组的对象,则可以通过`DeferredRegister#getEntries`向`#getKnown*`方法提供条目: ```java - // In some BlockLootSubProvider subclass for some DeferredRegister BLOCK_REGISTRAR + // 在针对某个DeferredRegister BLOCK_REGISTRAR的某个BlockLootSubProvider子类中 @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; // 创建该Iterable } ``` -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 + // 在此处添加战利品表 } ``` -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`。之后,在`LootTableProvider$SubProviderEntry`中设置指定的`LootContextParamSet`,然后通过`#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`的子类型。默认情况下,所有指定的战利品条件都必须返回true才能执行操作。战利品条件也可以指定为只有一个必须返回true(通过`#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 diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/datagen/server/recipes.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/datagen/server/recipes.md index b74e98b96..7574269d9 100644 --- a/i18n/zh-CN/docusaurus-plugin-content-docs/current/datagen/server/recipes.md +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/datagen/server/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`来为模组生成配方。一旦Consumer接受`FinishedRecipe`视图,就会提供一个用于生成数据的配方。`FinishedRecipe`既可以手动创建和提供,也可以为方便起见,使用`RecipeBuilder`创建。 -After implementation, the provider must be [added][datagen] to the `DataGenerator`. +实现后,该提供者必须被[添加][datagen]到`DataGenerator`。 ```java -// 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,101 +20,101 @@ 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 @@ -122,50 +122,49 @@ SmithingTrimRecipe builder = SmithingTrimRecipe.smithingTrim(template, base, add `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 -// 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`为每个配方添加进度。或者,可以使用`#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 + // 添加当条件为true时返回的配方 .addRecipe(...) - // Add the next conditions for the next recipe + // 为下一个配方添加接下来的条件 .addCondition(...) - // Add next recipe to return when the next conditions are true + // 添加当条件为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' + // 或(OR)'examplemod:example_item2'存在 + // 并且(AND) + // 非FALSE(NOT FALSE) // Methods are defined by IConditionBuilder and( @@ -180,13 +179,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。此外,还指定了配方的名称和序列化器,以了解在加载时向何处写入以及可以解码对象的内容。构造完`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 [ingredients]: ../../resources/server/recipes/ingredients.md#forge-types diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/datagen/server/tags.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/datagen/server/tags.md index ce3db346b..ba30fe7f3 100644 --- a/i18n/zh-CN/docusaurus-plugin-content-docs/current/datagen/server/tags.md +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/datagen/server/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`来为模组生成[标签][Tags]。实现后,该提供者必须被[添加][datagen]到`DataGenerator`中。 ```java -// 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(), - // Extends net.minecraftforge.common.data.BlockTagsProvider + // 扩展net.minecraftforge.common.data.BlockTagsProvider output -> new MyBlockTagsProvider( output, event.getLookupProvider(), @@ -24,41 +24,41 @@ 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`,它充当要添加到标签中的元素的可链接Consumer: -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` @@ -76,21 +76,21 @@ Registry Object Type | Tag Provider `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,10 +100,10 @@ public RecipeTypeTagsProvider(PackOutput output, CompletableFuture registries, ExistingFileHelper fileHelper) { super( output, diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/datastorage/capabilities.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/datastorage/capabilities.md index 9204511d5..fea74d04b 100644 --- a/i18n/zh-CN/docusaurus-plugin-content-docs/current/datastorage/capabilities.md +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/datastorage/capabilities.md @@ -1,59 +1,58 @@ -The Capability System -===================== +Capability系统 +============== -Capabilities allow exposing features in a dynamic and flexible way without having to resort to directly implementing many interfaces. +Capability允许以动态和灵活的方式公开Capability,而不必直接实现许多接口。 -In general terms, each capability provides a feature in the form of an interface. +一般来说,每个Capability都以接口的形式提供了一个Capability。 -Forge adds capability support to BlockEntities, Entities, ItemStacks, Levels, and LevelChunks, which can be exposed either by attaching them through an event or by overriding the capability methods in your own implementations of the objects. This will be explained in more detail in the following sections. +Forge为BlockEntity、Entity、ItemStack、Level和LevelChunk添加了Capability支持,这些Capability可以通过事件附加它们,也可以通过重写你自己的对象实现中的Capability方法来公开。这将在接下来的章节中进行更详细的解释。 -Forge-provided Capabilities ---------------------------- +Forge提供的Capability +--------------------- -Forge provides three capabilities: `IItemHandler`, `IFluidHandler` and `IEnergyStorage` +Forge提供三种Capability:`IItemHandler`、`IFluidHandler`和`IEnergyStorage`。 -`IItemHandler` exposes an interface for handling inventory slots. It can be applied to BlockEntities (chests, machines, etc.), Entities (extra player slots, mob/creature inventories/bags), or ItemStacks (portable backpacks and such). It replaces the old `Container` and `WorldlyContainer` with an automation-friendly system. +`IItemHandler`公开了一个用于处理物品栏Slot的接口。它可以应用于BlockEntity(箱子、机器等)、Entity(额外的玩家Slot、生物/生物物品栏/袋子)或ItemStack(便携式背包等)。它用一个自动化友好的系统取代了旧的`Container`和`WorldlyContainer`。 -`IFluidHandler` exposes an interface for handling fluid inventories. It can also be applied to BlockEntities, Entities, or ItemStacks. +`IFluidHandler`公开了一个用于处理流体物品栏的接口。它也可以应用于BlockEntitiy、Entity或ItemStack。 -`IEnergyStorage` exposes an interface for handling energy containers. It can be applied to BlockEntities, Entities, or ItemStacks. It is based on the RedstoneFlux API by TeamCoFH. +`IEnergyStorage`公开了一个用于处理能源容器的接口。它可以应用于BlockEntity、Entity或ItemStack。它基于TeamCoFH的RedstoneFlux API。 -Using an Existing Capability ----------------------------- +使用现存的Capability +------------------- -As mentioned earlier, BlockEntities, Entities, and ItemStacks implement the capability provider feature through the `ICapabilityProvider` interface. This interface adds the method `#getCapability`, which can be used to query the capabilities present in the associated provider objects. +如前所述,BlockEntity、Entity和ItemStack通过`ICapabilityProvider`接口实现了Capability提供者Capability。此接口添加了方法`#getCapability`,该方法可用于查询相关提供者对象中存在的Capability。 -In order to obtain a capability, you will need to refer it by its unique instance. In the case of the `IItemHandler`, this capability is primarily stored in `ForgeCapabilities#ITEM_HANDLER`, but it is possible to get other instance references by using `CapabilityManager#get` +为了获得一个Capability,你需要通过它的唯一实例来引用它。在`IItemHandler`的情况下,此Capability主要存储在`ForgeCapabilities#ITEM_HANDLER`中,但也可以使用`CapabilityManager#get`获取其他实例引用。 ```java public static final Capability ITEM_HANDLER = CapabilityManager.get(new CapabilityToken<>(){}); ``` -When called, `CapabilityManager#get` provides a non-null capability for your associated type. The anonymous `CapabilityToken` allows Forge to keep a soft dependency system while still having the necessary generic information to get the correct capability. +当被调用时,`CapabilityManager#get`为你的相关类型提供一个非null的Capability。匿名的`CapabilityToken`允许Forge保持软依赖系统,同时仍然拥有获得正确Capability所需的泛型信息。 -:::danger -Even if you have a non-null capability available to you at all times, it does not mean the capability itself is usable or registered yet. This can be checked via `Capability#isRegistered`. -::: +!!! 重要 + 即使你在任何时候都可以使用非null的Capability,但这并不意味着该Capability本身是可用的或已注册的。这可以通过`Capability#isRegistered`进行检查。 -The `#getCapability` method has a second parameter, of type `Direction`, which can be used to request the specific instance for that one face. If passed `null`, it can be assumed that the request comes either from within the block or from some place where the side has no meaning, such as a different dimension. In this case a general capability instance that does not care about sides will be requested instead. The return type of `#getCapability` will correspond to a `LazyOptional` of the type declared in the capability passed to the method. For the Item Handler capability, this is `LazyOptional`. If the capability is not available for a particular provider, it will return an empty `LazyOptional` instead. +`#getCapability`方法有另一个参数,类型为`Direction`,可用于请求那一面的特定实例。如果传递`null`,则可以假设请求来自方块内,或者来自某个侧面没有意义的地方,例如不同的维度。在这种情况下,将请求一个不关侧面的一个通用的Capability实例。`#getCapability`的返回类型将对应于传递给方法的Capability中声明的类型的`LazyOptional`。对于物品处理器Capability,其为`LazyOptional`。如果该Capability不适用于特定的提供者,它将返回一个空的`LazyOptional`。 -Exposing a Capability ---------------------- +公开一个Capability +------------------ -In order to expose a capability, you will first need an instance of the underlying capability type. Note that you should assign a separate instance to each object that keeps the capability, since the capability will most probably be tied to the containing object. +为了公开一个Capability,你首先需要一个底层Capability类型的实例。请注意,你应该为每个保有该Capability的对象分配一个单独的实例,因为该Capability很可能与所包含的对象绑定。 -In the case of `IItemHandler`, the default implementation uses the `ItemStackHandler` class, which has an optional argument in the constructor, to specify a number of slots. However, relying on the existence of these default implementations should be avoided, as the purpose of the capability system is to prevent loading errors in contexts where the capability is not present, so instantiation should be protected behind a check testing if the capability has been registered (see the remarks about `CapabilityManager#get` in the previous section). +在`IItemHandler`的情况下,默认实现使用`ItemStackHandler`类来指定多个Slot,该类在构造函数中有一个可选参数。然而,应避免依赖这些默认实现的存在,因为Capability系统的目的是防止在不存在Capability的情况下出现加载错误,因此如果Capability已注册,则应在检查测试之后对实例化进行保护(请参阅上一节中关于`CapabilityManager#get`的备注)。 -Once you have your own instance of the capability interface, you will want to notify users of the capability system that you expose this capability and provide a `LazyOptional` of the interface reference. This is done by overriding the `#getCapability` method, and comparing the capability instance with the capability you are exposing. If your machine has different slots based on which side is being queried, you can test this with the `side` parameter. For Entities and ItemStacks, this parameter can be ignored, but it is still possible to have side as a context, such as different armor slots on a player (`Direction#UP` exposing the player's helmet slot), or about the surrounding blocks in the inventory (`Direction#WEST` exposing the input slot of a furnace). Do not forget to fall back to `super`, otherwise existing attached capabilities will stop working. +一旦你拥有了自己的Capability接口实例,你将希望通知Capability系统的用户你公开了此Capability,并提供接口引用的`LazyOptional`。这是通过重写`#getCapability`方法来完成的,并将Capability实例与你要公开的Capability进行比较。如果你的机器根据被查询的一侧有不同的Slot,你可以使用`side`参数进行测试。对于实体和物品栈,此参数可以忽略,但仍然可以将侧面作为上下文,例如玩家上的不同护甲Slot(`Direction#UP`暴露玩家的头盔Slot),或物品栏中的周围方块(`Direction#WEST`暴露熔炉的输入Slot)。不要忘记回到`super`,否则现有的附加Capability将停止工作。 -Capabilities must be invalidated at the end of the provider's lifecycle via `LazyOptional#invalidate`. For owned BlockEntities and Entities, the `LazyOptional` can be invalidated within `#invalidateCaps`. For non-owned providers, a runnable supplying the invalidation should be passed into `AttachCapabilitiesEvent#addListener`. +在提供者生命周期结束时,必须通过`LazyOptional#invalidate`使Capability失效。对于拥有的BlockEntitiy和Entity,`LazyOptional`可以在`#invalidateCaps`内失效。对于非拥有者提供者,提供失效过程的Runnable应传递到`AttachCapabilitiesEvent#addListener`中。 ```java -// Somewhere in your BlockEntity subclass +// 在你BlockEntity子类中的某处 LazyOptional inventoryHandlerLazyOptional; -// Supplied instance (e.g. () -> inventoryHandler) -// Ensure laziness as initialization should only happen when needed +// 被提供的对象(例如:() -> inventoryHandler) +// 确保惰性,因为初始化只应在需要时发生 inventoryHandlerLazyOptional = LazyOptional.of(inventoryHandlerSupplier); @Override @@ -71,46 +70,45 @@ public void invalidateCaps() { } ``` -:::tip -If only one capability is exposed on a given object, you can use `Capability#orEmpty` as an alternative to the if/else statement. +!!! 提示 + 如果给定对象上只公开了一个Capability,则可以使用`Capability#orEmpty`作为if/else语句的替代语句。 -```java -@Override -public LazyOptional getCapability(Capability cap, Direction side) { - return ForgeCapabilities.ITEM_HANDLER.orEmpty(cap, inventoryHandlerLazyOptional); -} -``` -::: + ```java + @Override + public LazyOptional getCapability(Capability cap, Direction side) { + return ForgeCapabilities.ITEM_HANDLER.orEmpty(cap, inventoryHandlerLazyOptional); + } + ``` -`Item`s are a special case since their capability providers are stored on an `ItemStack`. Instead, a provider should be attached through `Item#initCapabilities`. This should hold your capabilities for the lifecycle of the stack. +`Item`是一种特殊情况,因为它们的Capability提供者存储在`ItemStack`上。相反的是,应该通过`Item#initCapabilities`附加提供者。其应该在物品栈的生命周期中保持你的Capability。 -It is strongly suggested that direct checks in code are used to test for capabilities instead of attempting to rely on maps or other data structures, since capability tests can be done by many objects every tick, and they need to be as fast as possible in order to avoid slowing down the game. +强烈建议在代码中使用直接检查来测试Capability,而不是试图依赖Map或其他数据结构,因为每个游戏刻都可以由许多对象进行Capability测试,并且它们需要尽可能快,以避免减慢游戏速度。 -Attaching Capabilities ----------------------- +Capability的附加 +---------------- -As mentioned, attaching capabilities to existing providers, `Level`s, and `LevelChunk`s can be done using `AttachCapabilitiesEvent`. The same event is used for all objects that can provide capabilities. `AttachCapabilitiesEvent` has 5 valid generic types providing the following events: +如前所述,可以使用`AttachCapabilitiesEvent`将Capability附加到现有提供者、`Level`和`LevelChunk`。同一事件用于所有可以提供Capability的对象。`AttachCapabilitiesEvent`有5个有效的泛型类型,提供以下事件: -* `AttachCapabilitiesEvent`: Fires only for entities. -* `AttachCapabilitiesEvent`: Fires only for block entities. -* `AttachCapabilitiesEvent`: Fires only for item stacks. -* `AttachCapabilitiesEvent`: Fires only for levels. -* `AttachCapabilitiesEvent`: Fires only for level chunks. +* `AttachCapabilitiesEvent`: 仅为实体触发。 +* `AttachCapabilitiesEvent`: 仅为方块实体触发。 +* `AttachCapabilitiesEvent`: 仅为物品栈触发。 +* `AttachCapabilitiesEvent`: 仅为存档触发。 +* `AttachCapabilitiesEvent`: 仅为存档区块触发。 -The generic type cannot be more specific than the above types. For example: If you want to attach capabilities to `Player`, you have to subscribe to the `AttachCapabilitiesEvent`, and then determine that the provided object is an `Player` before attaching the capability. +泛型类型不能比上述类型更具体。例如:如果要将Capability附加到`Player`,则必须订阅`AttachCapabilitiesEvent`,然后在附加Capability之前确定所提供的对象是`Player`。 -In all cases, the event has a method `#addCapability` which can be used to attach capabilities to the target object. Instead of adding capabilities themselves to the list, you add capability providers, which have the chance to return capabilities only from certain sides. While the provider only needs to implement `ICapabilityProvider`, if the capability needs to store data persistently, it is possible to implement `ICapabilitySerializable` which, on top of returning the capabilities, will provide tag save/load functions. +在所有情况下,该事件都有一个方法`#addCapability`,可用于将Capability附加到目标对象。不是将Capability本身添加到列表中,而是添加Capability提供者,这些提供者有机会仅从某些面返回Capability。虽然提供者只需要实现`ICapabilityProvider`,但如果该Capability需要持久存储数据,则可以实现`ICapabilitySerializable`,该Capability除了返回Capability外,还将提供标签保存/加载Capability。 -For information on how to implement `ICapabilityProvider`, refer to the [Exposing a Capability][expose] section. +有关如何实现`ICapabilityProvider`的信息,请参阅[公开一个Capability][expose]部分。 -Creating Your Own Capability ----------------------------- +创建你自己的Capability +--------------------- -A capability can be registered using one of two ways: `RegisterCapabilitiesEvent` or `@AutoRegisterCapability`. +Capability可通过以下两种方式之一被注册:`RegisterCapabilitiesEvent`或`@AutoRegisterCapability`。 ### RegisterCapabilitiesEvent -A capability can be registered using `RegisterCapabilitiesEvent` by supplying the class of the capability type to the `#register` method. The event is [handled] on the mod event bus. +通过向`#register`方法提供Capability类型的类,可以使用`RegisterCapabilitiesEvent`注册Capability。该事件在模组事件总线上[被处理][handled]。 ```java @SubscribeEvent @@ -121,7 +119,7 @@ public void registerCaps(RegisterCapabilitiesEvent event) { ### @AutoRegisterCapability -A capability is registered using `@AutoRegisterCapability` by annotating the capability type. +Capability也可通过使用`@AutoRegisterCapability`注释以被注册。 ```java @AutoRegisterCapability @@ -130,12 +128,12 @@ public interface IExampleCapability { } ``` -Persisting LevelChunk and BlockEntity capabilities --------------------------------------------- +LevelChunk和BlockEntity的Capability的持久化 +------------------------------------------ -Unlike Levels, Entities, and ItemStacks, LevelChunks and BlockEntities are only written to disk when they have been marked as dirty. A capability implementation with persistent state for a LevelChunk or a BlockEntity should therefore ensure that whenever its state changes, its owner is marked as dirty. +与Level、Entity和ItemStack不同,LevelChunk和BlockEntity只有在标记为脏时才会写入磁盘。因此,LevelChunk或BlockEntity具有持久状态的Capability实现应确保无论何时其状态发生变化,其所有者都被标记为脏。 -`ItemStackHandler`, commonly used for inventories in BlockEntities, has an overridable method `void onContentsChanged(int slot)` designed to be used to mark the BlockEntity as dirty. +`ItemStackHandler`通常用于BlockEntity中的物品栏,它有一个可重写的方法`void onContentsChanged(int slot)`,用于将BlockEntity标记为脏。 ```java public class MyBlockEntity extends BlockEntity { @@ -152,25 +150,25 @@ public class MyBlockEntity extends BlockEntity { } ``` -Synchronizing Data with Clients -------------------------------- +向客户端同步数据 +--------------- -By default, capability data is not sent to clients. In order to change this, the mods have to manage their own synchronization code using packets. +默认情况下,Capability数据不会发送到客户端。为了改变这一点,模组必须使用数据包管理自己的同步代码。 -There are three different situations in which you may want to send synchronization packets, all of them optional: +在三种不同的情况下,你可能希望发送同步数据包,所有这些情况都是可选的: -1. When the entity spawns in the level, or the block is placed, you may want to share the initialization-assigned values with the clients. -2. When the stored data changes, you may want to notify some or all of the watching clients. -3. When a new client starts viewing the entity or block, you may want to notify it of the existing data. +1. 当实体在存档中生成或放置方块时,你可能希望与客户端共享初始化指定的值。 +2. 当存储的数据发生更改时,你可能需要通知部分或全部正在监视的客户端。 +3. 当新客户端开始查看实体或方块时,你可能希望将现有数据通知它。 -Refer to the [Networking][network] page for more information on implementing network packets. +有关实现网络数据包的更多信息,请参阅[网络][network]页面。 -Persisting across Player Deaths -------------------------------- +在玩家死亡时的持久化 +------------------- -By default, the capability data does not persist on death. In order to change this, the data has to be manually copied when the player entity is cloned during the respawn process. +默认情况下,Capability数据不会在死亡时持续存在。为了改变这一点,在重生过程中克隆玩家实体时,必须手动复制数据。 -This can be done 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`方法可以用于区分死后重生和从末地返回。这一点很重要,因为从末地返回时数据已经存在,因此在这种情况下必须注意不要重复值。 [expose]: #exposing-a-capability [handled]: ../concepts/events.md#creating-an-event-handler diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/datastorage/codecs.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/datastorage/codecs.md index 0d08b3dd6..d630374f2 100644 --- a/i18n/zh-CN/docusaurus-plugin-content-docs/current/datastorage/codecs.md +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/datastorage/codecs.md @@ -1,84 +1,84 @@ -# Codecs +# 编解码器(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. +编解码器(Codecs)是源于Mojang的[DataFixerUpper]的一个序列化工具,用于描述对象如何在不同格式之间转换,例如JSON的`JsonElement`和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`序列化:定义标准JSON文件的`JsonOps#INSTANCE`和允许将数据压缩为单个字符串的`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`,其中包含成功时的结果,以及失败时部分转换的对象。该方法接收字符串Consumer,以确定如何报告错误消息(如果存在)。 ```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`类包含某些已定义的原始类型的编解码器的静态实例。 -Codec | Java Type +Codec | Java类型 :---: | :--- `BOOL` | `Boolean` `BYTE` | `Byte` @@ -94,32 +94,32 @@ Codec | Java Type `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` is an object used to represent `null` objects. +\*\* `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. -::: +!!! 警告 + `CompoundTag`无法使用`JsonOps`解码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,148 +132,148 @@ public class SomeObject { } ``` -#### Fields +#### 字段 -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. +一个`Instance`可以使用`#group`定义多达16个字段。每个字段都必须是一个应用,定义为其创建对象的实例和对象的类型。满足这一要求的最简单方法是使用`Codec`,设置要解码的字段的名称,并设置用于编码字段的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. +如果字段是必需的,则可以使用`#fieldOf`从`Codec`创建字段;如果字段被包装在`Optional`或默认值中,则使用`#optionalFieldOf`创建字段。任一方法都需要一个字符串,该字符串包含编码对象中字段的名称。然后,可以使用`#forGetter`设置用于对字段进行编码的getter,接受一个给定对象并返回字段数据的函数。 -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 +// A类 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 +A是否完全等效于B | B是否完全等效于A | 转换方法 :---: | :---: | :--- -Yes | Yes | `#xmap` -Yes | No | `#flatComapMap` -No | Yes | `#comapFlatMap` -No | No | `#flatXMap` +是 | 是 | `#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."); } }, - 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`实现了整数(int)、浮点数(float)和双精度小数(double)。 ```java 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 ``` ```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) @@ -281,19 +281,19 @@ public static final Codec> LIST_CODEC = BlockPos.CODEC.listOf(); ] ``` -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. +使用列表编解码器解码的列表对象存储在**不可变**列表中。如果需要可变列表,则应将[转换器][transformer]应用于列表编解码器。 ### 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. +键和值对象映射(Map)的编解码器可以通过`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 +301,16 @@ 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. +使用无界映射编解码器解码的映射对象存储在**不可变**映射中。如果需要一个可变映射,则应该将[转换器][transformer]应用于映射编解码器。 -:::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]列表编解码器可以用来绕过这个限制。 ### 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. +成对编解码器通过首先解码成对中的左对象,然后取编码对象的剩余部分并从中解码右对象来解码对象。因此,编解码器必须在解码后表达关于编码对象的某些内容(例如[记录][records]),或者必须将它们扩充为`MapCodec`,并通过`#codec`转换为常规编解码器。这通常可以通过使编解码器成为某个对象的[字段][field]来实现。 ```java public static final Codec> PAIR_CODEC = Codec.pair( @@ -321,22 +320,21 @@ 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]. -::: +!!! 提示 + 可以使用[转换器][transformer]应用的键值对列表对具有非字符串键的映射编解码器进行编码/解码。 ### 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 +344,31 @@ 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. -::: +!!! 提示 + 这可以与[转换器][transformer]结合使用,从两种不同的编码方法中获取特定对象。 ### 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. +Dispatch编解码器首先尝试从某个字符串关键字(通常为`type`)中获取编码类型。从那里,类型被解码,为用于解码实际对象的特定编解码器调用getter。如果用于解码对象的`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 +376,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 +393,7 @@ public class ComplexObject extends ExampleObject { public int i() { /* ... */ } public Codec type() { - // A registered registry object + // 一个已注册的注册表对象 // "complex": // RecordCodecBuilder.create(instance -> // instance.group( @@ -408,26 +405,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 } diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/datastorage/saveddata.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/datastorage/saveddata.md index 7ab85faca..d4e56d64f 100644 --- a/i18n/zh-CN/docusaurus-plugin-content-docs/current/datastorage/saveddata.md +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/datastorage/saveddata.md @@ -1,41 +1,42 @@ Saved Data ========== -The Saved Data (SD) system is an alternative to level capabilities that can attach data per level. +Saved Data(SD)系统是存档Capability功能的替代方案,可以按存档附加数据。 -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 existing data will persist. +* `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`从来没有在一个存档上创建过,那么它就不存在了。 -`SavedData`s are created and loaded from the `DimensionDataStorage`, which can be accessed by 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 three arguments: a function to load NBT data into a SD and return it, a supplier to construct a new instance of the SD, and the name of the `.dat` file stored within the `data` folder for the implemented level. +`DimensionDataStorage#computeIfAbsent`接受三个参数:一个将NBT数据加载到SD并返回它的函数,一个构造SD新实例的Supplier,以及存储在所实现的存档的`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 + // 加载saved data return data; } -// In some method within the class +// 在该类的某个方法中 netherDataStorage.computeIfAbsent(this::load, this::create, "example"); ``` -To persist a SD across levels, a 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`获得。主世界是唯一一个从未完全卸载的维度,因此非常适合在其上存储多存档数据。 diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/gameeffects/particles.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/gameeffects/particles.md index 91a216bdc..5fc5c86aa 100644 --- a/i18n/zh-CN/docusaurus-plugin-content-docs/current/gameeffects/particles.md +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/gameeffects/particles.md @@ -1,102 +1,99 @@ -Particles -========= +粒子效果 +======= -Particles are an effect within the game used as polish to better improve immersion. Their usefulness also requires great caution because of their methods of creation and reference. +粒子是游戏中的一种效果,用于打磨游戏,以更好地提高沉浸感。由于它们的创建和引用方法,其有用性也需要非常谨慎地对待。 -Creating a Particle -------------------- +创建一个粒子 +----------- -Particles are broken up between its [**client only**][sides] implementation to display the particle and its common implementation to reference the particle or sync data from the server. +粒子被分解为仅用于显示粒子的[**仅客户端**][sides]实现和用于引用来自服务端的粒子或同步数据的通用实现。 -| Class | Side | Description | +| 类 | 物理端 | 描述 | | :--- | :---: | :--- | -| ParticleType | BOTH | The registry object of a particle's type definition used to reference the particle on either side | -| ParticleOptions | BOTH | A data holder used to sync information from the network or a command to the associated client(s) | -| ParticleProvider | CLIENT | A factory registered by the `ParticleType` used to construct a `Particle` from the associated `ParticleOptions`. -| Particle | CLIENT | The renderable logic to display on the associated client(s) | +| ParticleType | BOTH | 粒子类型定义的注册表对象,用于引用任一端位的粒子 | +| ParticleOptions | BOTH | 用于将来自网络或命令的信息同步到相关客户端的数据保持器 | +| ParticleProvider | CLIENT | 由`ParticleType`注册的工厂,用于从关联的`ParticleOptions`构造`Particle`。 | +| Particle | CLIENT | 要在关联客户端上显示的可渲染逻辑 | ### ParticleType -A `ParticleType` is the registry object defining what a particular particle type is and provides an available reference to the specific particle on both sides. As such, every `ParticleType` must be [registered][registration]. +`ParticleType`是定义特定粒子类型的注册表对象,并提供对两端位特定粒子的可用引用。因此,每个`ParticleType`都必须[注册][registration]。 -Each `ParticleType` takes in two parameters: an `overrideLimiter` which determines whether the particle renders regardless of distance, and a `ParticleOptions$Deserializer` which is used to read the sent `ParticleOptions` on the client. As the base `ParticleType` is abstract, a single method needs to be implemented: `#codec`. This represents how to encode and decode the associated `ParticleOptions` of the type. +每个`ParticleType`都有两个参数:一个`overrideLimiter`,用于确定粒子是否在不考虑距离的情况下渲染,以及一个`ParticleOptions$Deserializer`,用于读取客户端上发送的`ParticleOptions`。由于基类`ParticleType`是抽象类,因此需要实现一个方法:`#codec`。其表示如何对与该类型相关的`ParticleOptions`进行编码和解码。 -:::note -`ParticleType#codec` is only used within the biome codec for vanilla implementations. -::: +!!! 注意 + `ParticleType#codec`仅在用于原版实现的生物群系编解码器中使用。 -In most cases, there is no need to have any particle data sent to the client. For these instances, it is easier to create a new instance of `SimpleParticleType`: an implementation of `ParticleType` and `ParticleOptions` which does not send any custom data to the client besides the type. Most vanilla implementations use `SimpleParticleType` besides redstone dust for coloring and block/item dependent particles. +在大多数情况下,不需要将任何粒子数据发送到客户端。对于这些例子,更容易创建`SimpleParticleType`的新实例:一个对`ParticleType`和`ParticleOptions`的实现,除了类型之外,它不向客户端发送任何自定义数据。除了红石粉之外,对于着色和依赖方块/物品的粒子而言,大多数原版实现还使用`SimpleParticleType`。 -:::info -A `ParticleType` is not needed to make a particle spawn if only referenced on the client. However, it is necessary to use any of the prebuilt logic within `ParticleEngine` or spawn a particle from the server. -::: +!!! 重要 + 如果仅在客户端上引用,则生成粒子时`ParticleType`非必要。但是,有必要使用`ParticleEngine`中的任何预构建逻辑,或者从服务端生成粒子。 ### ParticleOptions -An `ParticleOptions` represents the data that each particle takes in. It is also used to send data from particles spawned via the server. All particle spawning methods take in a `ParticleOptions` such that it knows the type of the particle and the data associated with spawning one. +`ParticleOptions`表示每个粒子所接收的数据。它还用于发送通过服务端生成的粒子的数据。所有粒子生成方法都接受一个`ParticleOptions`,这样它就知道粒子的类型以及与生成方法关联的数据。 -`ParticleOptions` is broken down into three methods: +`ParticleOptions`被拆分为三种方法: -| Method | Description | +| 方法 | 描述 | | :--- | :--- | -| getType | Gets the type definition of the particle, or the `ParticleType` -| writeToNetwork | Writes the particle data to a buffer on the server to send to the client -| writeToString | Writes the particle data to a string +| getType | 获取粒子的类型定义,或`ParticleType` +| writeToNetwork | 将粒子数据写入服务端上的缓冲区以发送到客户端 +| writeToString | 将粒子数据写入字符串 -These objects are either constructed on the fly as needed, or they are singletons as a result of being a `SimpleParticleType`. +这些对象要么是根据需要动态构建的,要么是作为`SimpleParticleType`的结果而产生的单体。 #### ParticleOptions$Deserializer -To receive the `ParticleOptions` on the client, or to reference the data within a command, the particle data must be deserialized via `ParticleOptions$Deserializer`. Each method within `ParticleOptions$Deserializer` has a parity encoding method within `ParticleOptions`: +要在客户端上接收`ParticleOptions`,或引用命令中的数据,必须通过`ParticleOptions$Deserializer`对粒子数据进行反序列化。`ParticleOptions$Deserializer`中的每个方法都对等`ParticleOptions`的编码方法: -| Method | ParticleOptions Encoder | Description | +| 方法 | ParticleOptions编码器 | 描述 | | :--- | :---: | :--- | -| fromCommand | writeToString | Decodes a particle data from a string, usually from a command. | -| fromNetwork | writeToNetwork | Decodes a particle data from a buffer on the client. | +| fromCommand | writeToString | 从字符串(通常是从命令)中解码粒子数据。 | +| fromNetwork | writeToNetwork | 解码客户端缓冲区中的粒子数据。 | -This object, when needing to send custom particle data, is passed into the constructor of the `ParticleType`. +当需要发送自定义粒子数据时,此对象会传递到`ParticleType`的构造函数中。 ### Particle -A `Particle` provides the rendering logic needed to draw said data onto the screen. To create any `Particle`, two methods must be implemented: +`Particle`提供将所述数据绘制到屏幕上所需的渲染逻辑。要创建任何`Particle`,必须实现两个方法: -| Method | Description | +| 方法 | 描述 | | :--- | :--- | -| render | Renders the particle onto the screen. | -| getRenderType | Gets the render type of the particle. | +| render | 将粒子渲染到屏幕上。 | +| getRenderType | 获取粒子的渲染类型。 | -A common subclass of `Particle` to render textures is `TextureSheetParticle`. While `#getRenderType` needs to be implemented, whatever the texture sprite is set will be rendered at the particle's location. +用于渲染纹理的`Particle`的一个常见子类是`TextureSheetParticle`。虽然需要实现`#getRenderType`,但无论设置了什么纹理sprite,都将在粒子的位置进行渲染。 #### ParticleRenderType -`ParticleRenderType` is a variation on `RenderType` which constructs the startup and teardown phase for every particle of that type and then renders them all at once via the `Tesselator`. There are six different render types a particle can be in. +`ParticleRenderType`是`RenderType`的一个变体,它为该类型的每个粒子构造启动和拆卸阶段,然后通过`Tesselator`同时渲染所有粒子。粒子可以使用六种不同的渲染类型。 -| Render Type | Description | +| 渲染类型 | 描述 | | :--- | :--- | -| TERRAIN_SHEET | Renders a particle whose texture is located within the available blocks. | -| PARTICLE_SHEET_OPAQUE | Renders a particle whose texture is opaque and located within the available particles. | -| PARTICLE_SHEET_TRANSLUCENT | Renders a particle whose texture is translucent and located within the available particles. | -| PARTICLE_SHEET_LIT | Same as `PARTICLE_SHEET_OPAQUE` except without using the particle shader. | -| CUSTOM | Provides setup for blending and depth mask but provides no rendering functionality as that would be implemented within `Particle#render`. | -| NO_RENDER | The particle will never render. | +| TERRAIN_SHEET | 渲染纹理位于可用方块内的粒子。 | +| PARTICLE_SHEET_OPAQUE | 渲染纹理不透明且位于可用粒子内的粒子。 | +| PARTICLE_SHEET_TRANSLUCENT | 渲染纹理为半透明且位于可用粒子内的粒子。 | +| PARTICLE_SHEET_LIT | 与`PARTICLE_SHEET_OPAQUE`相同,但不使用粒子着色器。 | +| CUSTOM | 提供混合和深度遮罩的设置,但不提供将在`Particle#render`中实现的渲染功能。 | +| NO_RENDER | 粒子将永远不会渲染。 | -Implementing a custom render type will be left as an exercise to the reader. +实现自定义渲染类型将留给读者练习。 ### ParticleProvider -Finally, a particle is usually created via an `ParticleProvider`. A factory has a single method `#createParticle` which is used to create a particle given the particle data, client level, position, and movement delta. Since a `Particle` is not beholden to any particular `ParticleType`, it can be reused in different factories as necessary. +最后,粒子通常是通过`ParticleProvider`创建的。工厂有一个单一的方法`ParticleProvider`,用于在给定粒子数据、客户端存档、位置和移动增量的情况下创建粒子。由于`Particle`不受任何特定`ParticleType`的约束,因此可以根据需要在不同的工厂中重复使用。 -An `ParticleProvider` must be registered by subscribing to the `RegisterParticleProvidersEvent` on the **mod event bus**. Within the event, the factory can be registered via `#registerSpecial` by supplying an instance of the factory to the method. +必须通过订阅**模组事件总线**上的`RegisterParticleProvidersEvent`以注册`ParticleProvider`。在事件中,可以通过向方法提供工厂实例,通过`#registerSpecial`注册工厂。 -:::danger -`RegisterParticleProvidersEvent` should only be called on the client and thus sided off in some isolated client class, referenced by either `DistExecutor` or `@EventBusSubscriber`. -::: +!!! 重要 + `RegisterParticleProvidersEvent`应仅在客户端上调用,因此在某些客户端类中被单端化独立,并被`DistExecutor`或`@EventBusSubscriber`引用。 -#### ParticleDescription, SpriteSet, and SpriteParticleRegistration +#### ParticleDescription、SpriteSet、以及SpriteParticleRegistration -There are three particle render types that cannot use the above method of registration: `PARTICLE_SHEET_OPAQUE`, `PARTICLE_SHEET_TRANSLUCENT`, and `PARTICLE_SHEET_LIT`. This is because all three of these particle render types use a sprite set that is loaded by the `ParticleEngine` directly. As such, the textures supplied must be obtained and registered through a different method. This will assume your particle is a subtype of `TextureSheetParticle` as that is the only vanilla implementation for this logic. +有三种粒子渲染类型不能使用上述注册方法: `PARTICLE_SHEET_OPAQUE`、`PARTICLE_SHEET_TRANSLUCENT`和`PARTICLE_SHEET_LIT`。这是因为这三种粒子渲染类型都使用由`ParticleEngine`直接加载的sprite集。因此,所提供的纹理必须通过不同的方法获得和注册。这将假设你的粒子是`TextureSheetParticle`的子类型,因为这是该逻辑的唯一原版实现。 -To add a texture to a particle, a new JSON file must be added to `assets//particles`. This is known as the `ParticleDescription`. The name of this file will represent the registry name of the `ParticleType` the factory is being attached to. Each particle JSON is an object. The object stores a single key `textures` which holds an array of `ResourceLocation`s. Any `:` texture represented here will point to a texture at `assets//textures/particle/.png`. +要将纹理添加到粒子,必须将一个新的JSON文件添加到`assets//particles`。这被称为`ParticleDescription`。该文件的名称将代表工厂所附加的`ParticleType`的注册表名称。每个粒子JSON都是一个对象。该对象存储单个关键的`textures`,该键包含`ResourceLocation`的一个数组。此处表示的任何`:`纹理都将指向`assets//textures/particle/.png`处的纹理。 ```js { @@ -112,18 +109,17 @@ To add a texture to a particle, a new JSON file must be added to `assets/ } ``` -To reference a particle texture, the subtype of `TextureSheetParticle` should either take in an `SpriteSet` or a `TextureAtlasSprite` obtained from `SpriteSet`. `SpriteSet` holds a list of textures which refer to the sprites as defined by our `ParticleDescription`. `SpriteSet` has two methods, both of which grab a `TextureAtlasSprite` in different methods. The first method takes in two integers. The backing implementation allows the sprite to have a texture change as it ages. The second method takes in a `Random` instance to get a random texture from the sprite set. The sprite can be set within `TextureSheetParticle` by using one of the helper methods that takes in the `SpriteSet`: `#pickSprite` which uses the random method of picking a texture, and `#setSpriteFromAge` which uses the percentage method of two integers to pick the texture. +若要引用一个粒子纹理,`TextureSheetParticle`的子类型应采用`SpriteSet`或从`SpriteSet`获得的`TextureAtlasSprite`。`SpriteSet`包含一个纹理列表,这些纹理引用了我们的`ParticleDescription`定义的sprite。`SpriteSet`有两个方法,这两个方法都以不同的方法获取`TextureAtlasSprite`。第一种方法接受两个整数。其背后的实现允许sprite在老化时进行纹理更改。第二种方法接受一个`Random`实例,从sprite集中获取随机纹理。可以使用`SpriteSet`中的一个辅助方法在`TextureSheetParticle`中设置sprite:`#pickSprite`使用拾取纹理的随机方法,`#setSpriteFromAge`使用两个整数的百分比方法拾取纹理。 -To register these particle textures, a `SpriteParticleRegistration` needs to be supplied to the `RegisterParticleProvidersEvent#registerSpriteSet` method. This method takes in an `SpriteSet` holding the associated sprite set for the particle and creates an `ParticleProvider` to create the particle. The simplest method of implementation can be done by implementing `ParticleProvider` on some class and having the constructor take in an `SpriteSet`. Then the `SpriteSet` can be passed to the particle as normal. +要注册这些粒子纹理,需要向`RegisterParticleProvidersEvent#registerSpriteSet`方法提供一个`SpriteParticleRegistration`。此方法接收一个`SpriteSet`,其中包含粒子的相关sprite集,并创建一个`ParticleProvider`来创建粒子。最简单的实现方法可以通过在某个类上实现`ParticleProvider`并让构造函数接受`SpriteSet`来完成。然后,`SpriteSet`可以正常地传递给粒子。 -:::note -If you are registering a `TextureSheetParticle` subtype which only contains one texture, then you can supply a `ParticleProvider$Sprite` instead to the `#registerSprite` method, which has essentially the same functional interface method as `ParticleProvider`. -::: +!!! 注意 + 如果你注册的是仅包含一个纹理的`TextureSheetParticle`子类型,则可以转而向`#registerSprite`方法提供`ParticleProvider$Sprite`,其与`ParticleProvider`具有基本相同的功能接口方法。 -Spawning a Particle -------------------- +生成一个粒子 +----------- -Particles can be spawned from either level instance. However, each side has a specific way to spawn a particle. If on the `ClientLevel`, `#addParticle` can be called to spawn a particle or `#addAlwaysVisibleParticle` can be called to spawn a particle that is visible from any distance. If on the `ServerLevel`, `#sendParticles` can be called to send a packet to the client to spawn the particle. Calling the two `ClientLevel` methods on the server will result in nothing. +粒子可以在任一存档实例中生成。但是,每一端都有一种特定的方式来生成粒子。如果在`ClientLevel`上,可以调用`#addParticle`来生成粒子,或者可以调用`#addAlwaysVisibleParticle`以生成从任何距离可见的粒子。如果在`ServerLevel`上,则可以调用`#sendParticles`向客户端发送数据包以生成粒子。在服务端上调用两个`ClientLevel`方法将会一无所获。 [sides]: ../concepts/sides.md [registration]: ../concepts/registries.md#methods-for-registering diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/gameeffects/sounds.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/gameeffects/sounds.md index 070f108b9..f62ed3a80 100644 --- a/i18n/zh-CN/docusaurus-plugin-content-docs/current/gameeffects/sounds.md +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/gameeffects/sounds.md @@ -1,21 +1,21 @@ -Sounds -====== +音效 +==== -Terminology ------------ +术语 +---- -| Term | Description | +| 术语 | 描述 | |----------------|----------------| -| Sound Events | Something that triggers a sound effect. Examples include `minecraft:block.anvil.hit` or `botania:spreader_fire`. | -| Sound Category | The category of the sound, for example `player`, `block` or simply `master`. The sliders in the sound settings GUI represent these categories. | -| Sound File | The literal file on disk that is played: an .ogg file. | +| 音效事件 | 触发音效效果的东西。例子包括`minecraft:block.anvil.hit`或`botania:spreader_fire`。 | +| 音效类别 | 音效的类别,例如`player`、`block`或只不过是`master`。音效设置GUI中的滑块展示这些类别。 | +| 音效文件 | 字面意义上的磁盘上播放的文件:一个.ogg文件。 | `sounds.json` ------------- -This JSON defines sound events, and defines which sound files they play, the subtitle, etc. Sound events are identified with [`ResourceLocation`][loc]s. `sounds.json` should be located at the root of a resource namespace (`assets//sounds.json`), and it defines sound events in that namespace (`assets//sounds.json` defines sound events in the namespace `namespace`.). +此JSON定义音效事件,并定义它们播放的音效文件、字幕等。音效事件用[`ResourceLocation`][loc]标识。`sounds.json`应该位于资源命名空间的根目录(`assets//sounds.json`),且在该命名空间中定义音效事件(`assets//soundes.json`在名称空间`namespace`中定义音效事件。)。 -A full specification is available on the vanilla [wiki][], but this example highlights the important parts: +原版[wiki][]上提供了完整的规范,但这个例子强调了重要的部分: ```js { @@ -34,78 +34,77 @@ A full specification is available on the vanilla [wiki][], but this example high } ``` -Underneath the top-level object, each key corresponds to a sound event. Note that the namespace is not given, as it is taken from the namespace of the JSON itself. Each event specifies a localization key to be shown when subtitles are enabled. Finally, the actual sound files to be played are specified. Note that the value is an array; if multiple sound files are specified, the game will randomly choose one to play whenever the sound event is triggered. +在顶级对象的下面,每个键都对应一个音效事件。请注意,没有给出命名空间,因为它取自JSON本身的命名空间。每个事件指定启用字幕时要显示的本地化翻译键。最后,指定要播放的实际音效文件。请注意,该值是一个数组;如果指定了多个音效文件,则每当触发音效事件时,游戏将随机选择一个播放。 -The two examples represent two different ways to specify a sound file. The [wiki] has precise details, but generally, long sound files such as background music or music discs should use the second form, because the "stream" argument tells Minecraft to not load the entire sound file into memory but to stream it from disk. The second form can also specify the volume, pitch, and weight of a sound file. +这两个示例代表了指定音效文件的两种不同方式。[wiki]有精确的细节,但一般来说,长音效文件(如背景音乐或音乐光盘)应该使用第二种形式,因为"stream"参数告诉Minecraft不要将整个音效文件加载到内存中,而是从磁盘流形式传输。第二种形式还可以指定音效文件的音量、音高和重量。 -In all cases, the path to a sound file for namespace `namespace` and path `path` is `assets//sounds/.ogg`. Therefore `mymod:open_chest_sound_file` points to `assets/mymod/sounds/open_chest_sound_file.ogg`, and `mymod:music/epic_music` points to `assets/mymod/sounds/music/epic_music.ogg`. +在所有情况下,命名空间`namespace`和路径`path`的音效文件路径都是`assets//sounds/.ogg`。因此,`mymod:open_chest_sound_file`指向`assets/mymod/sounds/open_chest_sound_file.ogg`,而`mymod:music/epic_music`指向`assets/mymod/sounds/music/epic_music.ogg`。 -A `sounds.json` can be [data generated][datagen]. +`sounds.json`可以是[数据生成][datagen]的。 -Creating Sound Events ---------------------- +创建音效事件 +----------- -In order to reference sounds on the server, a `SoundEvent` holding a corresponding entry in `sounds.json` must be created. This `SoundEvent` must then be [registered][registration]. Normally, the location used to create a sound event should be set as it's registry name. +为了引用服务端上的音效,必须创建一个在`sounds.json`中包含相应条目的`SoundEvent`。然后必须对`SoundEvent`进行[注册][registration]。通常,用于创建音效事件的位置应设置为其注册表名称。 -The `SoundEvent` acts as a reference to the sound and is passed around to play them. If a mod has an API, it should expose its `SoundEvent`s in the API. +`SoundEvent`作为对音效的一个引用,并被传递以播放它们。如果一个模组有API,应该在API中公开它的`SoundEvent`。 -:::note -As long as a sound is registered within the `sounds.json`, it can still be referenced on the logical client regardless of whether there is a referencing `SoundEvent`. -::: +!!! 注意 + 只要音效在`sounds.json`中被注册,它就仍然可以在逻辑客户端上被引用,而不管是否存在引用其的`SoundEvent`。 -Playing Sounds --------------- +播放音效 +------- -Vanilla has lots of methods for playing sounds, and it is unclear which to use at times. +原版有很多播放音效的方法,有时很难清楚该用哪种。 -Note that each takes a `SoundEvent`, the ones registered above. Additionally, the terms *"Server Behavior"* and *"Client Behavior"* refer to the respective [**logical** side][sides]. +请注意,每个方法都要接受一个`SoundEvent`,即上面注册的事件。此外,术语 *“服务端行为”* 和 *“客户端行为”* 指其分别的[**逻辑**端][side]。 ### `Level` 1. `playSound(Player, BlockPos, SoundEvent, SoundSource, volume, pitch)` - - Simply forwards to [overload (2)](#level-playsound-pxyzecvp), adding 0.5 to each coordinate of the `BlockPos` given. + - 简单地转发到[重载 (2)](#level-playsound-pxyzecvp),在给定的`BlockPos`的每个坐标上加0.5。 2. `playSound(Player, double x, double y, double z, SoundEvent, SoundSource, volume, pitch)` - - **Client Behavior**: If the passed in player is *the* client player, plays the sound event to the client player. - - **Server Behavior**: Plays the sound event to everyone nearby **except** the passed in player. Player can be `null`. - - **Usage**: The correspondence between the behaviors implies that these two methods are to be called from some player-initiated code that will be run on both logical sides at the same time: the logical client handles playing it to the user, and the logical server handles everyone else hearing it without re-playing it to the original user. They can also be used to play any sound in general at any position server-side by calling it on the logical server and passing in a `null` player, thus letting everyone hear it. + - **客户端行为**: 如果传入的玩家是*客户端*玩家,则向客户端玩家播放该音效事件。 + - **服务端行为**: 向附近的所有人播放音效事件,除了传入的玩家以外。玩家可以为`null`。 + - **用法**: 行为之间的对应关系意味着这两个方法将从一些玩家启动的代码中调用,这些代码将同时在两逻辑端运行:逻辑客户端处理向用户播放,逻辑服务端处理其他所有听到它的人,而不向原始用户重新播放。它们还可以用于在服务端端的任何位置播放任何音效,方法是在逻辑服务端上调用它并传入`null`玩家,从而让每个人都能听到。 3. `playLocalSound(double x, double y, double z, SoundEvent, SoundSource, volume, pitch, distanceDelay)` - - **Client Behavior**: Just plays the sound event in the client level. If `distanceDelay` is `true`, then delays the sound based on how far it is from the player. - - **Server Behavior**: Does nothing. - - **Usage**: This method only works client-side, and thus is useful for sounds sent in custom packets, or other client-only effect-type sounds. Used for thunder. + - **客户端行为**: 只是在客户端存档播放音效事件。如果`distanceDelay`为`true`,则根据音效与玩家的距离来延迟音效。 + - **服务端行为**: 不做任何事情。 + - **用法**: 此方法仅适用于客户端,因此对于在自定义数据包中发送的音效或其他仅客户端效果类型的音效非常有用。打雷就用了该方法。 ### `ClientLevel` 1. `playLocalSound(BlockPos, SoundEvent, SoundSource, volume, pitch, distanceDelay)` - - Simply forwards to `Level`'s [overload (3)](#level-playsound-xyzecvpd), adding 0.5 to each coordinate of the `BlockPos` given. + - 简单地转发到`Level`的[overload (3)](#level-playsound-xyzecvpd),在给定的`BlockPos`的每个坐标上加0.5。 ### `Entity` 1. `playSound(SoundEvent, volume, pitch)` - - Forwards to `Level`'s [overload (2)](#level-playsound-pxyzecvp), passing in `null` as the player. - - **Client Behavior**: Does nothing. - - **Server Behavior**: Plays the sound event to everyone at this entity's position. - - **Usage**: Emitting any sound from any non-player entity server-side. + - 简单地转发到`Level`的[overload (2)](#level-playsound-pxyzecvp),将玩家传递为`null`。 + - **客户端行为**: 不做任何事情。 + - **服务端行为**: 向该实体所在位置的所有人播放音效事件。 + - **用法**: 在服务端从任何非玩家实体发出任何音效。 ### `Player` 1. `playSound(SoundEvent, volume, pitch)` (overriding the one in [`Entity`](#entity-playsound-evp)) - - Forwards to `Level`'s [overload (2)](#level-playsound-pxyzecvp), passing in `this` as the player. - - **Client Behavior**: Does nothing, see override in [`LocalPlayer`](#localplayer-playsound-evp). - - **Server Behavior**: Plays the sound to everyone nearby *except* this player. - - **Usage**: See [`LocalPlayer`](#localplayer-playsound-evp). + - 简单地转发到`Level`的[overload (2)](#level-playsound-pxyzecvp),将玩家传递为`null`。 + - **客户端行为**: 不做任何事情,参见[`LocalPlayer`](#localplayer-playsound-evp)中的重载。 + - **服务端行为**: 向附近*除了*该玩家以外的所有人播放该音效。 + - **用法**: 参见[`LocalPlayer`](#localplayer-playsound-evp)。 ### `LocalPlayer` 1. `playSound(SoundEvent, volume, pitch)` (overriding the one in [`Player`](#player-playsound-evp)) - - Forwards to `Level`'s [overload (2)](#level-playsound-pxyzecvp), passing in `this` as the player. - - **Client Behavior**: Just plays the Sound Event. - - **Server Behavior**: Method is client-only. - - **Usage**: Just like the ones in `Level`, these two overrides in the player classes seem to be for code that runs together on both sides. The client handles playing the sound to the user, while the server handles everyone else hearing it without re-playing to the original user. + - 简单地转发到`Level`的[overload (2)](#level-playsound-pxyzecvp),将玩家传递为`this`。 + - **客户端行为**: 仅仅播放该音效事件。 + - **服务端行为**: 该方法仅客户端适用。 + - **用法**: 就像`Level`中的方法一样,玩家类中的这两个重写似乎是针对在两端同时运行的代码。客户端处理向用户播放音效,而服务端处理其他所有听到音效的人,而不向原始用户重新播放。 [loc]: ../concepts/resources.md#resourcelocation -[wiki]: https://minecraft.wiki/w/Sounds.json +[wiki]: https://minecraft.fandom.com/wiki/Sounds.json [datagen]: ../datagen/client/sounds.md [registration]: ../concepts/registries.md#methods-for-registering [sides]: ../concepts/sides.md diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/gettingstarted/index.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/gettingstarted/index.md index e3f8a5dac..8315d2d24 100644 --- a/i18n/zh-CN/docusaurus-plugin-content-docs/current/gettingstarted/index.md +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/gettingstarted/index.md @@ -1,126 +1,118 @@ -# Getting Started with Forge +Forge入门 +========= -:::caution -Please note that this documentation may not be up to date considering the recent creation of NeoForged. +如果你之前从未制作过一个Forge模组,本节将提供设置Forge开发环境所需的最少信息。其余的文档是关于从这里开始的内容。 -Until NeoForged releases its first version, you should refer to Forge documentation. The Forge documentation for 1.20 has been archived here. -::: +先决条件 +-------- -If you have never made a Forge mod before, this section will provide the minimum amount of information needed to setup a Forge development environment. The rest of the documentation is about where to go from here. +* 安装Java 17开发包(JDK)和64位JVM。Forge推荐并官方支持[Eclipse Temurin][jdk]。 -## Prerequisites + !!! 警告 + 确保你正在使用64位的JVM。一种检查方式是在终端中运行`java -version`。使用32位的JVM会导致在使用[ForgeGradle]的过程中出现问题。 -- An installation of the Java 17 Development Kit (JDK) and 64-bit Java Virtual Machine (JVM). Forge recommends and officially supports [Eclipse Temurin][jdk]. +* 熟练使用一款集成开发环境(IDE)。 + * 建议使用一款集成了Gradle功能的IDE。 -:::caution +从零开始模组开发 +---------------- -Make sure you are using a 64-bit JVM. One way of checking is to run `java -version` in a terminal. Using a 32-bit JVM will cause some problems when using [ForgeGradle]. +1. 从[Forge文件站][files]下载Mod开发包(MDK)。点击“Mdk”,等待一段时间之后点击右上角的“Skip”按钮。如果可能的话,推荐下载最新版本的Forge。 +1. 解压所下载的MDK到一个空文件夹中。它会成为你的模组的目录,且现在应该已包含一些gradle文件和一个含有example模组的`src`子目录。 -::: + !!! 注意 + 许多文件可以在不同的模组中重复使用。这些文件是: -- Familiarity with an Integrated Development Environment (IDE). - - It is recommended to use an IDE with Gradle integration. + * `gradle`子目录 + * `build.gradle` + * `gradlew` + * `gradlew.bat` + * `settings.gradle` -## From Zero to Modding + `src`子目录不需要跨工作区进行复制;但是,如果稍后创建java(`src/main/java`)和resource(`src/main/resources`),则可能需要刷新Gradle项目。 -1. Download the Mod Developer Kit (MDK) from the [Forge file site][files] by clicking 'Mdk' followed by the 'Skip' button in the top right after waiting for a period of time. It is recommended to download the latest version of Forge whenever possible. -2. Extract the downloaded MDK into an empty directory. This will be your mod's directory, which should now contain some gradle files and a `src` subdirectory containing the example mod. +1. 打开你选择的IDE: + * Forge只明确支持在Eclipse和IntelliJ IDEA上进行开发,但还有其他针对Visual Studio代码的运行配置。无论如何,从Apache NetBeans到Vim/Emacs的任何开发环境都可被使用。 + * Eclipse和IntelliJ IDEA的Gradle集成,都是已默认安装和启用的,将在导入或打开时处理其余的初始工作区设置。这包括从Mojang、MinecraftForge等下载必要的软件包。如果你使用Visual Studio,则需要安装“Gradle for Java”插件。 + * Gradle将需要被调用来重新评估项目中对其相关文件的几乎所有更改(如`build.gradle`、`settings.gradle`等等)。有些IDE带有“刷新”按钮来完成此操作;然而,它也可以通过在终端上运行`gradlew`来完成。 +1. 为你选择的IDE生成运行配置: + * **Eclipse**: 运行`genEclipseRuns`任务。 + * **IntelliJ IDEA**: 运行`genIntellijRuns`任务。如果发生了"module not specified"错误,请将[`ideaModule`属性][config]设置为你的'main'模块(通常为`${project.name}.main`)。 + * **Visual Studio Code**: 运行`getVSCodeRuns`任务。 + * **Other IDEs**: 你可以通过`gradle run*`来直接运行这些配置(如`runClient`、`runServer`、`runData`、`runGameTestServer`)。这对于已提供支持的IDE同样有效。 -:::info +自定义你的模组信息 +----------------- -A number of files can be reused across different mods. These files are: +编辑`build.gradle`文件以自定义你的模组的构建方式(如文件名称、artifact版本等等)。 -- the `gradle` subdirectory -- `build.gradle` -- `gradlew` -- `gradlew.bat` -- `settings.gradle` +!!! 重要 + 除非你知道你在做什么,否则**不要**编辑`settings.gradle`。该文件指定[ForgeGradle]所上传的仓库。 -The `src` subdirectory does not need to be copied across workspaces; however, you may need to refresh the Gradle project if the java (`src/main/java`) and resource (`src/main/resources`) are created later. -::: +### 建议的`build.gradle`自定义项目 -3. Open your selected IDE: - - Forge only explicitly supports development on Eclipse and IntelliJ IDEA, but there are additional run configurations for Visual Studio Code. Regardless, any environment, from Apache NetBeans to Vim / Emacs, can be used. - - Eclipse and IntelliJ IDEA's Gradle integration, both installed and enabled by default, will handle the rest of the initial workspace setup on import or open. This includes downloading the necessary packages from Mojang, MinecraftForge, etc. The 'Gradle for Java' plugin is needed for Visual Studio Code to do the same. - - Gradle will need to be invoked to re-evaluate the project for almost all changes to its associated files (e.g., `build.gradle`, `settings.gradle`, etc.). Some IDEs come with 'Refresh' buttons to do this; however, it can be done through the terminal via `gradlew`. -4. Generate run configurations for your selected IDE: - - **Eclipse**: Run the `genEclipseRuns` task. - - **IntelliJ IDEA**: Run the `genIntellijRuns` task. If a "module not specified" error occurs, set the [`ideaModule` property][config] to your 'main' module (typically `${project.name}.main`). - - **Visual Studio Code**: Run the `getVSCodeRuns` task. - - **Other IDEs**: You can run the configurations directly using `gradle run*` (e.g., `runClient`, `runServer`, `runData`, `runGameTestServer`). These can also be used with the supported IDEs. +#### Mod Id替换 -## Customizing Your Mod Information - -Edit the `build.gradle` file to customize how your mod is built (e.g., file name, artifact version, etc.). - -:::danger -Do **not** edit the `settings.gradle` unless you know what you are doing. The file specifies the repository that [ForgeGradle] is uploaded to. -::: - -### Recommended `build.gradle` Customizations - -#### Mod Id Replacement - -Replace all occurrences of `examplemod`, including [`mods.toml` and the main mod file][modfiles] with the mod id of your mod. This also includes changing the name of the file you build by setting `base.archivesName` (this is typically set to your mod id). +将包括[mods.toml和主mod文件][modfiles]在内的所有出现的examplemo替换为你的模组的mod id。这还包括通过设置`base.archivesName`(通常设置为你的mod id)来更改你构建的文件的名称。 ```gradle -// In some build.gradle +// 在某个build.gradle文件中 base.archivesName = 'mymod' ``` #### Group Id -The `group` property should be set to your [top-level package][packaging], which should either be a domain you own or your email address: +`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` ```gradle -// In some build.gradle +// 在某个build.gradle文件中 group = '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: +java源文件(`src/main/java`)中的包现在也应该符合这种结构,更深层的包表示mod id: ```text com -- example (top-level package specified in group property) - - mymod (the mod id) - - MyMod.java (renamed ExampleMod.java) +- example (在group属性中所指定的顶级程序包) + - mymod (mod id) + - MyMod.java (重命名后的ExampleMod.java) ``` -#### Version +#### 版本 -Set the `version` property to the current version of your mod. We recommend using a [variation of Maven versioning][mvnver]. +将`version`属性设置为你的模组的当前版本。我们推荐采用[Maven版本号命名格式][mvnver]。 ```gradle -// In some build.gradle -version = '1.20-1.0.0.0' +// 在某个build.gradle文件中 +version = '1.19.4-1.0.0.0' ``` -### Additional Configurations +### 额外配置 -Additional configurations can be found on the [ForgeGradle] docs. +额外配置可在[ForgeGradle]文档中找到。 -## Building and Testing Your Mod +构建并测试你的模组 +----------------- -1. To build your mod, run `gradlew build`. This will output a file in `build/libs` with the name `[archivesBaseName]-[version].jar`, by default. This file can be placed in the `mods` folder of a Forge-enabled Minecraft setup or distributed. -1. 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 run directory (default 'run') 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. -1. If you are running a dedicated server, whether through the run configuration or `gradlew runServer`, the server will initially 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, which can then be accessed via a direct connect to `localhost`. +1. 要构建你的模组,请运行`gradlew build`。这将在`build/libs`输出一个默认名为`[archivesBaseName]-[version].jar`的文件。这个文件可以被放在已安装了Forge的Minecraft的`mods`文件夹中,也可以被分发。 +1. 要在测试环境中运行你的模组,你既可以使用已生成的运行配置,也可以运行功能类似的Gradle任务(例如`gradlew runClient`)。这将使用任何所指定的源码集从run文件夹中启动Minecraft。默认的MDK包括`main`源码集,因此任何在`src/main/java`中编写的源代码都会被应用。 +1. 如果你想要运行dedicated服务端,无论是通过运行配置,还是通过`gradlew runServer`,服务端都会立刻宕机。你需要通过编辑run文件夹中的`eula.txt`文件同意Minecraft EULA。一旦同意后,服务器就会加载,之后就可以通过直连`localhost`进行访问了。 -:::tip - -You should always test your mod in a dedicated server environment. This includes [client-only mods][client] as they should not do anything when loaded on the server. - -::: +!!! 注意 + 在服务端环境测试你的模组是必要的。这包括[只针对客户端的模组][client],因为在加载到服务端后它们不应该做任何事。 [jdk]: https://adoptium.net/temurin/releases?version=17 "Eclipse Temurin 17 Prebuilt Binaries" -[ForgeGradle]: https://docs.neoforged.net/neogradle/docs/ +[ForgeGradle]: https://docs.minecraftforge.net/en/fg-6.x + [files]: https://files.minecraftforge.net "Forge Files distribution site" -[config]: https://docs.neoforged.net/neogradle/docs/configuration/runs +[config]: https://docs.minecraftforge.net/en/fg-6.x/configuration/runs/ + [modfiles]: ./modfiles.md [packaging]: ./structuring.md#packaging [mvnver]: ./versioning.md diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/gettingstarted/modfiles.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/gettingstarted/modfiles.md index aabf06118..f3a88a721 100644 --- a/i18n/zh-CN/docusaurus-plugin-content-docs/current/gettingstarted/modfiles.md +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/gettingstarted/modfiles.md @@ -1,14 +1,14 @@ -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. +模组文件负责确定哪些文件会被打包到你模组的JAR文件中,在“Mods”菜单中显示哪些信息,以及你的模组如何被加载到游戏中。 mods.toml --------- -The `mods.toml` file defines the metadata of your mod(s). It also contains additional information that is displayed within the 'Mods' menu and how your mod(s) should be loaded into the game. +`mods.toml`定义你的一个或多个模组的元数据。它也包含一些附加信息,这些信息将在Mods菜单中被展示,并决定你的模组如何被加载进游戏。 -The file uses the [Tom's Obvious Minimal Language, or TOML][toml], format. The file must be stored under the `META-INF` folder in the resource directory of the source set you are using (`src/main/resources/META-INF/mods.toml` for the `main` source set). A `mods.toml` file may look something like this: +该文件采用[Tom's Obvious Minimal Language][toml](简称TOML)格式。这个文件必须保存在你所使用的源码集的resource目录中的`META-INF`文件夹下(例如对于`main`源码集,其路径为`src/main/resources/META-INF/mods.toml`)。`mods.toml`文件看起来长这样: ```toml modLoader="javafml" @@ -47,98 +47,96 @@ showAsResourcePack=false side="BOTH" ``` -`mods.toml` is broken 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. Each of the properties associated with the `mods.toml` file will be explained below, where `required` means that a value must be specified or an exception will be thrown. +`mods.toml`被分为三个部分:非模组特定属性,与模组文件相关联;模组特定属性,对每个模组都有单独的小节;以及依赖配置,对每个模组依赖都有单独的小节。下面将解释与`mods.toml`文件相关的各个属性,其中`required`表示必须指定一个值,否则将引发异常。 -### 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. +非模组特定属性是与JAR文件本身相关的属性,指明如何加载模组和任何附加的全局元数据。 -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. Forge provides the Java loader `"javafml"` and low/no code loader `"lowcodefml"`. | `"javafml"` -`loaderVersion` | string | **mandatory** | The acceptable version range of the language loader, expressed as a [Maven Version Range][mvr]. For `javafml` and `lowcodefml`, the version is the major version of the Forge version. | `"[46,)"` -`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. | `"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. | `true` -`services` | array | `[]` | An array of services your mod **uses**. This is consumed as part of the created module for the mod from Forge's implementation of the Java Platform Module System. | `["net.minecraftforge.forgespi.language.IModLanguageProvider"]` -`properties` | table | `{}` | A table of substitution properties. This is used by `StringSubstitutor` to replace `${file.}` with its corresponding value. This is currently only used to replace the `version` in the [mod-specific properties][modsp]. | `{ "example" = "1.2.3" }` referenced by `${file.example}` -`issueTrackerURL` | string | *nothing* | A URL representing the place to report and track issues with the mod(s). | `"https://forums.minecraftforge.net/"` +`modLoader` | string | **必需** | 模组所使用的语言加载器。可用于支持额外的语言结构,如为主文件定义的Kotlin对象,或确定入口点的不同方法,如接口或方法。Forge提供Java加载器`"javafml"`和低/无代码加载器`"lowcodefml"`。 | `"javafml"` +`loaderVersion` | string | **必需** | 可接受的语言加载器版本范围,以[Maven版本范围][mvr]表示。对于`javafml`和`lowcodefml`,其版本是Forge版本的主版本号。 | `"[46,)"` +`license` | string | **必需** | 该JAR文件中的模组所遵循的许可证。建议将其设置为你正在使用的[SPDX标识符][spdx]和/或许可证的链接。你可以访问 https://choosealicense.com/ 以帮助选取你想使用的许可证。 | `"MIT"` +`showAsResourcePack` | boolean | `false` | 当为`true`时,模组的资源会以一个单独的资源包的形式在“资源包”菜单中展示,而不是与“模组资源”包融为一体。 | `true` +`services` | array | `[]` | 表示你的模组所**使用**的一系列服务的数组。这是从Forge的Java平台模块系统实现中为模组创建的模块的一部分。 | `["net.minecraftforge.forgespi.language.IModLanguageProvider"]` +`properties` | table | `{}` | 替换属性表。`StringSubstitutor`使用它将`${file.}`替换为相应的值。该功能目前仅用于替换模组特定属性中的`version`。 | 由`${file.example}`引用的`{ "example" = "1.2.3" }` +`issueTrackerURL` | string | *无* | 指向报告与追踪模组问题的地点的URL。 | `"https://forums.minecraftforge.net/"` -:::note -The `services` property is functionally equivalent to specifying the [`uses` directive in a module][uses], which allows [*loading*][serviceload] a service of a given type. -::: +!!! 重要 + `services`属性在功能上等效于在指定[在模块中的`uses`指令][uses],该指令允许加载给定类型的服务。 -### 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. +模组特定属性通过`[[mods]]`头与指定的模组绑定。其本质是一个[表格数组(Array of Tables)][array];直到下一个头之前的所有键/值对都会被关联到那个模组。 ```toml -# Properties for examplemod1 +# examplemod1的属性 [[mods]] modId = "examplemod1" -# Properties for examplemod2 +# 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). | `"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. | `"example"` -`version` | string | `"1"` | The version of the mod, preferably in a [variation of Maven versioning][mvnver]. 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). | `"1.20-1.0.0.0"` -`displayName` | string | value of `modId` | The pretty name of the mod. Used when representing the mod on a screen (e.g., mod list, mod mismatch). | `"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]. | `"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). | `"example_logo.png"` -`logoBlur` | boolean | `true` | Whether to use `GL_LINEAR*` (true) or `GL_NEAREST*` (false) to render the `logoFile`. | `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. | `"https://files.minecraftforge.net/net/minecraftforge/forge/promotions_slim.json"` -`features` | table | `{}` | See '[features]'. | `{ java_version = "17" }` -`modproperties` | table | `{}` | A table of key/values associated with this mod. Currently unused by Forge, but is mainly for use by mods. | `{ example = "value" }` -`modUrl` | string | *nothing* | A URL to the download page of the mod. Currently unused. | `"https://files.minecraftforge.net/"` -`credits` | string | *nothing* | Credits and acknowledges for the mod shown on the mod list screen. | `"The person over here and there."` -`authors` | string | *nothing* | The authors of the mod shown on the mod list screen. | `"Example Person"` -`displayURL` | string | *nothing* | A URL to the display page of the mod shown on the mod list screen. | `"https://minecraftforge.net/"` -`displayTest` | string | `"MATCH_VERSION"` | See '[sides]'. | `"NONE"` - -#### 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, Forge provides the following features: - -Feature | Description | Example +`modId` | string | **必需** | 代表这个模组的唯一标识符。该标识符必须匹配`^[a-z][a-z0-9_]{1,63}$`(一个长度在[2,64]闭区间内的字符串;以小写字母开头;由小写字母、数字或下划线组成)。 | `"examplemod"` +`namespace` | string | `modId`的值 | 该模组的一个重载命名空间。该命名空间必须匹配`^[a-z][a-z0-9_.-]{1,63}$`(一个长度在[2,64]闭区间内的字符串;以小写字母开头;由小写字母、数字、下划线、点或短横线组成)。目前无作用。 | `"example"` +`version` | string | `"1"` | 该模组的版本,最好符合[Maven版本号命名格式][mvnver]。当设置为`${file.jarVersion}`时,它将被替换为JAR清单文件中`Implementation-Version`属性的值(在开发环境下默认显示为`0.0NONE`)。 | `"1.20-1.0.0.0"` +`displayName` | string | `modId`的值 | 该模组的更具可读性的名字。用于将模组展示到屏幕上时(如模组列表、模组不匹配)。 | `"Example Mod"` +`description` | string | `"MISSING DESCRIPTION"` | 在模组列表中展示的该模组的描述。建议使用一个[多行文字字符串][multiline]。 | `"This is an example."` +`logoFile` | string | *无* | 在模组列表中展示的该模组的logo图像文件的名称和扩展名。该logo必须位于JAR文件的根目录或直接位于源码集的根目录。 | `"example_logo.png"` +`logoBlur` | boolean | `true` | 决定使用`GL_LINEAR*`(true)或`GL_NEAREST*`(false)渲染`logoFile`。 | `false` +`updateJSONURL` | string | *无* | 被[更新检查器][update]用来检查你所使用的模组是否为最新版本的指向一个JSON文件的URL。 | `"https://files.minecraftforge.net/net/minecraftforge/forge/promotions_slim.json"` +`features` | table | `{}` | 参见 '[features]'。 | `{ java_version = "17" }` +`modproperties` | table | `{}` | 与本模组相关联的一个键/值对表。目前尚未被Forge使用,但主要被模组使用。 | `{ example = "value" }` +`modUrl` | string | *无* | 指向本模组下载界面的URL。目前无作用。 | `"https://files.minecraftforge.net/"` +`credits` | string | *无* | 在模组列表中展示的致谢声明。 | `"The person over here and there."` +`authors` | string | *无* | 在模组列表中展示的本模组的作者。 | `"Example Person"` +`displayURL` | string | *无* | 在模组列表中展示的本模组的展示页面(项目主页)。 | `"https://minecraftforge.net/"` +`displayTest` | string | `"MATCH_VERSION"` | 参见 '[sides]'。 | `"NONE"` + +#### 功能 + +功能系统允许模组在加载系统时要求某些设置、软件或硬件可用。当某个功能不满足时,模组加载将失败,并将要求通知给用户。目前,Forge提供以下功能: + +功能 | 描述 | 样例 :---: | :---: | :--- -`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. | `"[17,)"` +`java_version` | 可支持的Java版本范围,以[Maven版本范围][mvr]表示。该范围须能够支持Minecraft所使用的Java版本。 | `"[17,)"` -### Dependency Configurations +### 依赖配置 -Mods can specify their dependencies, which are checked by Forge before loading the mods. These configurations are created using the [array of tables][array] `[[dependencies.]]` where `modid` is the identifier of the mod the dependency is for. +模组可以指定它们的依赖项,这些依赖项在加载模组之前由Forge检查。这些配置是使用[表格数组(Array of Tables)][array]`[[dependencies.]]`创建的,其中`modid`是所依赖的模组的标识符。 -Property | Type | Default | Description | Example +属性 | 类型 | 缺省值 | 描述 | 样例 :--- | :---: | :---: | :---: | :--- -`modId` | string | **mandatory** | The identifier of the mod added as a dependency. | `"example_library"` -`mandatory` | boolean | **mandatory** | Whether the game should crash when this dependency is not met. | `true` -`versionRange` | string | `""` | The acceptable version range of the language loader, expressed as a [Maven Version Range][mvr]. An empty string matches any version. | `"[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"` | `"AFTER"` -`side` | string | `"BOTH"` | The [physical side][dist] the dependency must be present on: `"CLIENT"`, `"SERVER"`, or `"BOTH"`.| `"CLIENT"` -`referralUrl` | string | *nothing* | A URL to the download page of the dependency. Currently unused. | `"https://library.example.com/"` +`modId` | string | **必需** | 被添加为依赖的模组的标识符。 | `"example_library"` +`mandatory` | boolean | **必需** | 当依赖未满足时游戏是否崩溃。 | `true` +`versionRange` | string | `""` | 可接受的语言加载器版本范围,以[Maven版本范围][mvr]表示。空字符串表示匹配所有版本。 | `"[1, 2)"` +`ordering` | string | `"NONE"` | 定义本模组是否必须在所依赖的模组之前(`"BEFORE"`)或之后(`"AFTER"`)加载。`"NONE"`表示不规定顺序。 | `"AFTER"` +`side` | string | `"BOTH"` | 所依赖模组必须位于的[端位][dist]:`"CLIENT"`、`"SERVER"`或`"BOTH"`。 | `"CLIENT"` +`referralUrl` | string | *无* | 指向依赖下载界面的URL。目前无作用。 | `"https://library.example.com/"` -:::danger -The `ordering` of two mods may cause a crash due to a cyclic dependency: for example, mod A must load `"BEFORE"` mod B and mod B `"BEFORE"` mod A. -::: +!!! 警告 + 两个模组的`ordering`可能会因循环依赖而造成崩溃:例如模组A必须在模组B之前(`"BEFORE"`)加载,而模组B也必须在模组A之前(`"BEFORE"`)加载。 -Mod Entrypoints ---------------- +模组入口点 +---------- -Now that the `mods.toml` is filled out, we need to provide an entrypoint to being programming 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 Forge 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], [adding `DeferredRegister`s][registration]) can be specified within the constructor of the class. The mod bus can be obtained from `FMLJavaModLoadingContext`. +`javafml`是Forge为Java编程语言提供的语言加载器。入口点是通过使用带有`@Mod`注释的公共类来定义的。`@Mod`的值必须包含`mods.toml`中指定的一个Mod id。从那里,所有初始化逻辑(例如[注册事件][events]、添加[`DeferredRegister`][registration])都可以在类的构造函数中写明。模组总线可以从`FMLJavaModLoadingContext`获得。 ```java -@Mod("examplemod") // Must match mods.toml +@Mod("examplemod") // 必须匹配mods.toml public class Example { public Example() { - // Initialize logic here + // 此处初始化逻辑 var modBus = FMLJavaModLoadingContext.get().getModEventBus(); // ... @@ -148,7 +146,7 @@ public class Example { ### `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`,用于将来可能需要的最少量代码的小添加。 [toml]: https://toml.io/ [mvr]: https://maven.apache.org/enforcer/enforcer-rules/versionRanges.html diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/gettingstarted/structuring.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/gettingstarted/structuring.md index 641015478..ed86f32f8 100644 --- a/i18n/zh-CN/docusaurus-plugin-content-docs/current/gettingstarted/structuring.md +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/gettingstarted/structuring.md @@ -1,25 +1,24 @@ -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 Forge are listed below. +结构分明的模组有利于维护和做出贡献,并提供对底层代码库的更清晰理解。下面列举了由Java、Minecraft和Forge提出的一些建议。 -:::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 @@ -27,56 +26,55 @@ module A - 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 + - package X // 此包将导致模组加载器崩溃,因为已经有一个模块将包X导出 - class R - class S - class T ``` -As such, your top level package should be something that you own: a domain, email address, a subdomain of where your 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. +正因如此,你的顶级程序包应该是你自己的东西:域名、电子邮件地址、网站的子域等。它甚至可以是你的名字或用户名,只要你能保证它在预期目标中是唯一可识别的。 -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. +下一个级别的包应该是你的mod id(例如`com.example.examplemod`,其中`examplemod`是mod 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` or `blocks`, entities under `entity` or `entities`, etc. Mojang uses this structure with the singular version of the word. -* **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`或`blocks`子包下,实体相关的类可被置于`entity`或`entities`子包下等等。Mojang就在使用这种结构,单词用的是单数形式(`block`、`entity`)。 +* **按逻辑分组**: 将具有共同逻辑的类归入同一个子包。例如,如果你正在创建一种新配方,你可以将它的方块、菜单、物品等等都放在`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 while code only on the dedicated server should go in a `server` package. +通常,仅用于给定端位或运行时的代码都应该在单独的子包中与其他类隔离。例如,与[数据生成][datagen]相关的代码应该放在`data`子包中,而仅与dedicated服务器相关的代码应该在`server`子包中。 -However, 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. As such, having a dedicated package would provide a decent sanity check to verify you are not reaching across sides within your mod. +然而,强烈建议在`client`子包中隔离[仅限客户端][sides]的代码。这是因为dedicated服务器不应有任何权限访问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: +类的名称通常以其类型作为后缀,例如: -* 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. While this does improve code formatting, it also avoid any weird interactions or redundancies that may occur (e.g. your event listener executing twice). +执行特定任务的方法有很多:注册对象、监听事件等。通常建议使用单一方法来完成给定的任务以保持一致。这在改善了代码格式的同时,也避免了可能发生的任何奇怪的交互或冗余(例如,你的事件监听器执行了两次)。 [naming]: https://docs.oracle.com/javase/tutorial/java/package/namingpkgs.html [datagen]: ../datagen/index.md diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/gettingstarted/versioning.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/gettingstarted/versioning.md index 2db561eb4..2a68eedc0 100644 --- a/i18n/zh-CN/docusaurus-plugin-content-docs/current/gettingstarted/versioning.md +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/gettingstarted/versioning.md @@ -1,55 +1,54 @@ -Versioning -========== +版本号 +====== -In general projects, [semantic versioning][semver] is often used (which has the format `MAJOR.MINOR.PATCH`). However, in the case of modding it may be more beneficial to use the format `MCVERSION-MAJORMOD.MAJORAPI.MINOR.PATCH` to be able to differentiate between world-breaking and API-breaking changes of a mod. +在一般项目中,语义式的版本号(格式为`MAJOR.MINOR.PATCH`)被经常使用。然而,在长期性地修改的情况下,使用格式`MCVERSION-MAJORMOD.MAJORAPI.MINOR.PATCH`可能更有利于将模组的创世性的修改与API变更性的修改区分开来。 -:::caution -Forge uses [Maven version ranges][cmpver] to compare version strings, which is not fully compatible with the Semantic Versioning 2.0.0 spec, such as the 'prerelease' tag. -::: +!!! 重要 + Forge使用[Maven版本范围][cmpver]来比较版本字符串,这与Semantic Versioning 2.0.0规范不完全兼容,例如“prerelease”标签。 -Examples --------- +样例 +---- -Here is a list of examples that can increment the various variables. +以下是在不同情形下能递增各种变量的示例列表。 * `MCVERSION` - * Always matches the Minecraft version the mod is for. + * 始终与该模组所适用的Minecraft版本相匹配。 * `MAJORMOD` - * Removing items, blocks, block entities, etc. - * Changing or removing previously existing mechanics. - * Updating to a new Minecraft version. + * 移除物品、方块、方块实体等。 + * 改变或移除之前存在的机制。 + * 升级到新的Minecraft版本。 * `MAJORAPI` - * Changing the order or variables of enums. - * Changing return types of methods. - * Removing public methods altogether. + * 更改枚举的顺序或变量。 + * 更改方法的返回类型。 + * 一并移除公共方法。 * `MINOR` - * Adding items, blocks, block entities, etc. - * Adding new mechanics. - * Deprecating public methods. (This is not a `MAJORAPI` increment since it doesn't break an API.) + * 添加物品、方块、方块实体等。 + * 添加新机制。 + * 废弃公共方法。(这不是一次`MAJORAPI`递增,因为它并未改变API。) * `PATCH` - * Bugfixes. + * Bug修复。 -When incrementing any variable, all lesser variables should reset to `0`. For instance, if `MINOR` would increment, `PATCH` would become `0`. If `MAJORMOD` would increment, all other variables would become `0`. +当递增任何变量时,所有更小级别的变量都应重置为`0`。例如,如果`MINOR`递增,`PATCH`将变为`0`。如果`MAJORMOD`递增,则所有其他变量将变为`0`。 -### Work In Progress +### 项目初始阶段 -If you are in the initial development stage of your mod (before any official releases), the `MAJORMOD` and `MAJORAPI` should always be `0`. Only `MINOR` and `PATCH` should be updated every time you build your mod. Once you build an official release (most of the time with a stable API), you should increment `MAJORMOD` to version `1.0.0.0`. For any further development stages, refer to the [Prereleases][pre] and [Release candidates][rc] section of this document. +如果你正处于模组的初始开发阶段(在任何正式发布之前),`MAJORMOD`和`MAJORAPI`应始终为`0`。只有`MINOR`和`PATCH`应该在每次构建你的模组时更新。一旦你构建了一个官方版本(大多数情况下应使用稳定的API),你应该将`MAJORMOD`增加到版本`1.0.0.0`。有关任何进一步的开发阶段,请参阅本文档的[预发布][pre]和[候选发布][rc]部分。 -### Multiple Minecraft Versions +### 多个Minecraft版本 -If the mod upgrades to a new version of Minecraft, and the old version will only receive bug fixes, the `PATCH` variable should be updated based on the version before the upgrade. If the mod is still in active development in both the old and the new version of Minecraft, it is advised to append the version to **both** build numbers. For example, if the mod is upgraded to version `3.0.0.0` due to a Minecraft version change, the old mod should also be updated to `3.0.0.0`. The old version will become, for example, version `1.7.10-3.0.0.0`, while the new version will become `1.8-3.0.0.0`. If there are no changes at all when building for a newer Minecraft version, all variables except for the Minecraft version should stay the same. +如果模组升级到新版本的Minecraft,而旧版本将只会得到bug修复,则`PATCH`变量应根据升级前的版本进行更新。如果模组针对旧版本和新版本的Minecraft都仍在积极开发中,建议将该版本附加到**所有**两个Minecraft版本号之后。例如,如果模组由于Minecraft版本的更改而升级到`3.0.0.0`版本,那么旧版本的模组也应该更新到`3.0.0.0`。又例如,旧版本将变成`1.7.10-3.0.0.0`版本,而新版本将变成`1.8-3.0.0.0`版本。如果在为新的Minecraft版本构建时模组本身并没有任何更改,那么除了Minecraft版本之外的所有变量都应该保持不变。 -### Final Release +### 最终发布 -When dropping support for a Minecraft version, the last build for that version should get the `-final` suffix. This denotes that the mod will no longer be supported for the denoted `MCVERSION` and that players should upgrade to a newer version of the mod to continue receiving updates and bug fixes. +当放弃对某个Minecraft版本的支持时,针对该版本的最后一个模组构建版本应该有`-final`后缀。这意味着模组对于所表示的`MCVERSION`将不再支持,玩家应该升级到模组所支持的新版本的Minecraft,以继续接收更新和bug修复。 -### Pre-releases +### 预发布 -It is also possible to prerelease work-in-progress features, which means new features are released that are not quite done yet. These can be seen as a sort of "beta". These versions should be appended with `-betaX`, where `X` is the number of the prerelease. (This guide does not use `-pre` since, at the time of writing, it is not a valid alias for `-beta`.) Note that already released versions and versions before the initial release can not go into prerelease; variables (mostly `MINOR`, but `MAJORAPI` and `MAJORMOD` can also prerelease) should be updated accordingly before adding the `-beta` suffix. Versions before the initial release are simply work-in-progress builds. +(本指南不使用`-pre`,因为在撰写本文时,它不是`-beta`的有效别名。)请注意,已经发布的版本和首次发布之前的版本不能进入预发布;变量(主要是`MINOR`,但`MAJORAPI`和`MAJORMOD`也可以预发布)应该在添加`-beta`后缀之前进行相应的更新。首次发布之前的版本只是在建版本。 -### Release Candidates +### 候选发布 -Release candidates act as prereleases before an actual version change. These versions should be appended with `-rcX`, where `X` is the number of the release candidate which should, in theory, only be increased for bugfixes. Already released versions can not receive release candidates; variables (mostly `MINOR`, but `MAJORAPI` and `MAJORMOD` can also prerelease) should be updated accordingly before adding the `-rc` suffix. When releasing a release candidate as stable build, it can either be exactly the same as the last release candidate or have a few more bug fixes. +候选发布在实际版本更替之前充当预发布。这些版本应该附加`-rcX`,其中`X`是候选版本的数量,理论上,只有在修复bug时才应该增加。已经发布的版本无法接收候选版本;在添加`-rc`后缀之前,应该相应地更新变量(主要是`MINOR`,但`MAJORAPI`和`MAJORMOD`也可以预发布)。当作为稳定构建版本发布候选版本时,它既可以与上一个候选版本完全相同,也可以有更多的bug修复。 [semver]: https://semver.org/ [cmpver]: https://maven.apache.org/ref/3.5.2/maven-artifact/apidocs/org/apache/maven/artifact/versioning/ComparableVersion.html diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/gui/menus.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/gui/menus.md index 3a5f63c25..749afb34a 100644 --- a/i18n/zh-CN/docusaurus-plugin-content-docs/current/gui/menus.md +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/gui/menus.md @@ -1,139 +1,135 @@ -# Menus +# 菜单(Menus) -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. +菜单(Menus)是图形用户界面(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`必须被[注册][registered]。 ### `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]. +`MenuType`是通过将`MenuSupplier`和`FeatureFlagSet`传递给其构造函数来创建的。`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); // ... } ``` -:::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对于单个玩家是唯一的。这意味着,两个不同玩家上的相同容器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`,它可以存储从服务端发送的附加信息。`MenuType`可以通过`IForgeMenuType#create`使用`IContainerFactory`创建。 ```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 + // 从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` and `ContainerLevelAccess` +### `#stillValid`和`ContainerLevelAccess` -`#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`确定菜单是否应该为给定的玩家保持打开状态。这通常指向静态的`#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. +一些数据需要同时出现在服务端和客户端上才能显示给玩家。为此,菜单实现了数据同步的基本层,以便在当前数据与上次同步到客户端的数据不匹配时进行同步。对于玩家来说,这是每个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 Forge in favor of using the [`IItemHandler` capability][cap], the rest of the explanation will revolve around using the capability variant: `SlotItemHandler`. -::: +!!! 注意 + 由于`Slot`使用的`Container`已被Forge弃用,取而代之的是使用[`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`表示物品栈所在的物品栏,该Slot具体表示的物品栈索引,以及该Slot左上角将在屏幕上呈现的相对于`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都会首先添加,然后是玩家的物品栏,最后以玩家的快捷栏结束。要从菜单中访问任何单独的`Slot`,必须根据添加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. +每次初始化新菜单时,都应该重新创建上述内容以及Slot。 -:::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`存储一个整数(int),但由于它在网络上发送数值的方式,它实际上被限制为**short**类型(-32768到32767)。该整数(int)的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 + // 检查数据物品栏大小是否为某个固定值 + // 然后,为数据物品栏添加Slot this.addSlot(new SlotItemHandler(dataInventory, /*...*/)); - // Add slots for player inventory + // 为玩家物品栏添加Slot this.addSlot(new Slot(playerInventory, /*...*/)); - // Add data slots for handled integers + // 为被处理的整数添加Slot this.addDataSlot(dataSingle); // ... @@ -142,162 +138,158 @@ 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`来引用这些整数。此接口用作索引查找,以便每个索引表示不同的整数。如果通过`#addDataSlots`将`ContainerData`添加到菜单中,则也可以在数据对象本身中构造`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 + // 为被处理的整数添加Slot this.addDataSlots(dataMultiple); // ... } ``` -:::caution -As `ContainerData` delegates to `DataSlot`s, these are also limited to a **short** (-32768 to 32767). -::: +!!! 警告 + 由于`ContainerData`委托`DataSlot`,这些整数也被限制为**short**(-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单击或快速移出其当前Slot,直到物品栈完全移出其上一个Slot,或者物品栈没有其他位置可去时,就会调用此方法。该方法返回正在快速移动的Slot中物品栈的一个副本。 -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`). +物品栈通常使用`#moveItemStackTo`在Slot之间移动,它将物品栈移动到第一个可用的Slot中。它接受要移动的物品栈、尝试将物品栈移动到的第一个Slot的索引(包括)、最后一个Slot的索引,以及是以从第一个到最后一个(当`false`时)还是从最后一个到第一个(当`true`时)的顺序检查Slot。 -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)并输出到一个结果Slot(索引0) +// 我们也有27个玩家物品栏Slot和9个快捷栏Slot +// 这样,真正的Slot索引按如下编排: +// - 数据物品栏:结果(0),输入(1 - 4) +// - 玩家物品栏(5 - 31) +// - 玩家快捷栏(32 - 40) @Override public ItemStack quickMoveStack(Player player, int quickMovedSlotIndex) { - // The quick moved slot stack + // 快速移动的Slot的物品栈 ItemStack quickMovedStack = ItemStack.EMPTY; - // The quick moved slot + // 快速移动的Slot Slot quickMovedSlot = this.slots.get(quickMovedSlotIndex) - // If the slot is in the valid range and the slot is not empty + // 如果该Slot在合理范围内且不为空 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 + // 获取原始物品栈以用于移动 + ItemStack rawStack = quickMovedSlot.getItem(); + // 将Slot物品栈设置为该原始物品栈的副本 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 + // 如果快速移动在数据物品栏的结果Slot上进行 if (quickMovedSlotIndex == 0) { - // Try to move the result slot into the player inventory/hotbar + // 尝试将结果Slot移入玩家物品栏/快捷栏 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的快速移动逻辑 slot.onQuickCraft(rawStack, quickMovedStack); } - // Else if the quick move was performed on the player inventory or hotbar slot + // 否则如果快速移动在玩家物品栏或快捷栏Slot上进行 else if (quickMovedSlotIndex >= 5 && quickMovedSlotIndex < 41) { - // Try to move the inventory/hotbar slot into the data inventory input slots + // 尝试将物品栏/快捷栏Slot移入数据物品栏输入Slot if (!this.moveItemStackTo(rawStack, 1, 5, false)) { - // If cannot move and in player inventory slot, try to move to hotbar + // 如果无法移动且在玩家物品栏Slot内,尝试移入快捷栏 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 + // 否则就尝试将快捷栏移入玩家物品栏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 + // 否则如果快速移动在数据物品栏的输入Slot上进行,尝试将其移入玩家物品栏/快捷栏 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 + // 如果原始物品栈已完全移出当前Slot,将该Slot置空 quickMovedSlot.set(ItemStack.EMPTY); } else { - // Otherwise, notify the slot that that the stack count has changed + // 否则,通知该Slot物品栈数量已改变 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 + // 如果原始物品栈不能被移动到另一个Slot,就不再进行快速移动 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; // 返回该Slot物品栈 } ``` -## 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)][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)][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. +应通过重写`BlockBehaviour#getMenuProvider`来实现`MenuProvider`。原版方法使用这个来显示旁观者模式下的菜单。 ```java -// In some Block subclass +// 在某个Block的子类中 @Override public MenuProvider getMenuProvider(BlockState state, Level level, BlockPos pos) { return new SimpleMenuProvider(/* ... */); @@ -312,13 +304,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通常通过重写`Mob#mobInteract`来实现菜单。这与方块实现类似,唯一的区别是`Mob`本身应该实现`MenuProvider`以支持旁观者模式下的显示。 ```java public class MyMob extends Mob implements MenuProvider { @@ -334,9 +325,8 @@ 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 [acm]: #abstractcontainermenu diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/gui/screens.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/gui/screens.md index b1591033f..f4c4daeff 100644 --- a/i18n/zh-CN/docusaurus-plugin-content-docs/current/gui/screens.md +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/gui/screens.md @@ -1,233 +1,226 @@ -# Screens +# 屏幕(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)][menus]相结合,为类似物品栏的视图创建通信网络,也可以是独立的,模组开发者可以通过自己的[网络][network]实现来处理。 -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. +关于如何将坐标相对化的信息将在[屏幕][screen]部分中呈现。 -:::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][argb]格式。 -### 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`方法,它在屏幕上绘制一个矩形。Line方法在内部调用此方法。其接受左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`][component]传入,因为它们处理各种用例,包括方法的另外两个重载。 -### 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. +纹理是通过blitting的方式绘制的,因此方法名为`#blit`,为此,它复制图像的比特并将其直接绘制到屏幕上。这些是通过位置纹理着色器绘制的。虽然有许多不同的`#blit`重载,但我们只讨论两个静态的`#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`取六个整数,并假设渲染的纹理位于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`所调用的另一个静态`#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坐标较大的渲染在前景中渲染。z偏移量可以通过`#translate`直接设置在`PoseStack`本身上。一些基本的偏移逻辑在`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位置,以及游戏刻增量(自上一帧以来经过了多少游戏刻)。 -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 -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`和`GuiComponent`,添加了setter和getter逻辑用于拖动和聚焦子级。 ## 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 +// 在某个Screen子类中 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 +// 在某个Screen子类中 @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 +// 在某个Screen子类中 @Override public void tick() { super.tick(); - // Add ticking logic for EditBox in editBox + // 在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 +// 在某个Screen子类中 -// 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 + // 然后是窗口小部件,如果这是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 +// 在某个Screen子类中 @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. +如果一个屏幕直接连接到[菜单(Menu)][menus],那么其应改为继承`AbstractContainerScreen`。`AbstractContainerScreen`充当菜单的渲染器和输入处理程序,包含用于与Slot同步和交互的逻辑。因此,通常只需要重写或实现两个方法就可以拥有一个可工作的容器屏幕。同样,为了更容易理解,容器屏幕的组件将按通常遇到的顺序提及。 -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: +`AbstractContainerScreen`通常需要三个参数:打开的容器菜单(用泛型`T`表示)、玩家物品栏(仅用于显示名称)和屏幕本身的标题。在这里,可以设置多个定位字段: -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. -:::caution + `leftPos`和`topPos`也被用作渲染背景的方便方式,因为它们已经表示要传递到`#blit`方法中的位置。 ```java -// In some AbstractContainerScreen subclass +// 在某个AbstractContainerScreen子类中 public MyContainerScreen(MyMenu menu, Inventory playerInventory, Component title) { super(menu, playerInventory, title); @@ -235,115 +228,102 @@ 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. +当菜单被传递给屏幕时,菜单中的任何值(通过Slot、数据Slot或自定义系统)都可以通过`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. +当玩家活着并通过`#containerTick`查看屏幕时,容器屏幕在`#tick`方法中计时。这基本上取代了容器屏幕中的`#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. + * 该方法由容器屏幕添加,用于渲染悬停在其上的任何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. - */ - 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' + // 假设我们有个组件'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 `MenuScreens#register` within the `FMLClientSetupEvent` on the [**mod event bus**][modbus]. +要将`AbstractContainerScreen`与菜单一起使用,需要对其进行注册。这可以通过调用[**模组事件总线**][modbus]上的`FMLClientSetupEvent`中的`MenuScreens#register`来完成。 ```java -// Event is listened to on the mod event bus +// 该事件已在模组事件总线上被监听 private void clientSetup(FMLClientSetupEvent event) { event.enqueueWork( - // Assume RegistryObject> MY_MENU - // Assume MyContainerScreen which takes in three parameters + // 假设:RegistryObject> MY_MENU + // 假设MyContainerScreen,其接受三个参数 () -> MenuScreens.register(MY_MENU.get(), MyContainerScreen::new) ); } ``` -:::danger -`MenuScreens#register` is not thread-safe, so it needs to be called inside `#enqueueWork` provided by the parallel dispatch event. -::: +!!! 警告 + `MenuScreens#register`不是线程安全的,因此它需要在并行调度事件提供的`#enqueueWork`内部调用。 [menus]: ./menus.md [network]: ../networking/index.md diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/items/bewlr.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/items/bewlr.md index fcabedad8..ef7d5c23d 100644 --- a/i18n/zh-CN/docusaurus-plugin-content-docs/current/items/bewlr.md +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/items/bewlr.md @@ -1,22 +1,21 @@ 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`系统简单得多,旧的`ItemStack`系统需要`BlockEntity`,并且不允许访问`ItemStack`。 -Using BlockEntityWithoutLevelRenderer --------------------------- +使用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,将访问该Item的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() { @@ -29,8 +28,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不需要额外的设置。 diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/items/index.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/items/index.md index 2b592394d..7b5813e63 100644 --- a/i18n/zh-CN/docusaurus-plugin-content-docs/current/items/index.md +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/items/index.md @@ -1,60 +1,61 @@ -Items -===== +物品 +==== -Along with blocks, items are a key component of most mods. While blocks make up the level around you, items exist within inventories. +与方块一样,物品也是大多数模组的关键组成部分。方块在构成了你身边的存档的同时,物品也存在于物品栏中。 -Creating an Item ----------------- +创建一个物品 +----------- -### Basic Items +### 基础物品 -Basic items that need no special functionality (think sticks or sugar) do not need custom classes. You can create an item by instantiating the `Item` class with an `Item$Properties` object. This `Item$Properties` object can be made via the constructor and customized by calling its methods. For instance: +对于不需要特殊功能的简单物品(比如木棍或糖),不必自定义一个类。你可以通过使用`Item$Properties`对象实例化`Item`类来创建一个物品。这个`Item$Properties`对象可以通过调用其构造函数生成并通过调用其方法进行自定义。例如: -| Method | Description | +| 方法 | 描述 | |:------------------:|:----------------------------------------------| -| `requiredFeatures` | Sets the required `FeatureFlag`s needed to see this item in the `CreativeModeTab` it is added to. | -| `durability` | Sets the maximum damage value for this item. If it is over `0`, two item properties "damaged" and "damage" are added. | -| `stacksTo` | Sets the maximum stack size. You cannot have an item that is both damageable and stackable. | -| `setNoRepair` | Makes this item impossible to repair, even if it is damageable. | -| `craftRemainder` | Sets this item's container item, the way that lava buckets give you back an empty bucket when they are used. | +| `requiredFeatures` | 设置在所添加到的`CreativeModeTab`中查看此物品所需的`FeatureFlag`。 | +| `durability` | 设置该物品的最大耐久。如果超过`0`,两个物品属性“damaged”和“damage”会被添加。 | +| `stacksTo` | 设置最大物品栈大小。你不能拥有一件既有耐久又可堆叠的物品。 | +| `setNoRepair` | 使此物品无法修复,即使它是有耐久的。 | +| `craftRemainder` | 设置该物品的容器物品,即熔岩桶在使用后将空桶还给你的方式。 | -The above methods are chainable, meaning they `return this` to facilitate calling them in series. +上面的方法是可链接的,这意味着它们`return this`以便于串行调用它们。 -### Advanced Items +### 进阶物品 -Setting the properties of an item as above only works for simple items. If you want more complicated items, you should subclass `Item` and override its methods. +如上所述设置物品属性的方式仅适用于简单物品。如果你想要更复杂的物品,你应该继承`Item`类并重写其方法。 -## Creative Tabs +## 创造模式物品栏 -An item can be added to a `CreativeModeTab` via `BuildCreativeModeTabContentsEvent` on the [mod event bus][modbus]. An item(s) can be added without any additional configurations via `#accept`. +可以通过[模组事件总线][modbus]上的`BuildCreativeModeTabContentsEvent`将物品添加到`CreativeModeTab`。可以通过`#accept`添加物品,而无需任何其他配置。 ```java -// Registered on the MOD event bus -// Assume we have RegistryObject and RegistryObject called ITEM and BLOCK +// 已在模组事件总线上注册 +// 假设我们有一个名为ITEM的RegistryObject和一个名为BLOCK的RegistryObject @SubscribeEvent public void buildContents(BuildCreativeModeTabContentsEvent event) { - // Add to ingredients tab + // 添加到ingredients创造模式物品栏 if (event.getTabKey() == CreativeModeTabs.INGREDIENTS) { event.accept(ITEM); - event.accept(BLOCK); // Takes in an ItemLike, assumes block has registered item + event.accept(BLOCK); // 接受一个ItemLike,假设方块已注册其物品 } } ``` -You can also enable or disable items being added through a `FeatureFlag` in the `FeatureFlagSet` or a boolean determining whether the player has permissions to see operator creative tabs. +你还可以通过`FeatureFlagSet`中的`FeatureFlag`或一个用于确定玩家是否有权查看管理员创造模式物品栏的boolean值来启用或禁用物品。 -### Custom Creative Tabs +### 自定义创造模式物品栏 -A custom `CreativeModeTab` must be [registered][registering]. The builder can be created via `CreativeModeTab#builder`. The tab can set the title, icon, default items, and a number of other properties. In addition, Forge provides additional methods to customize the tab's image, label and slot colors, where the tab should be ordered, etc. +自定义`CreativeModeTab`必须[已被注册][registering]。生成器可以通过`CreativeModeTab#builder`创建。选项卡可以设置标题、图标、默认物品和许多其他属性。此外,Forge还提供了额外的方法来定制标签的图像、标签和插槽颜色,以及选项卡的排序位置等。 ```java -// Assume we have a DeferredRegister called REGISTRAR +// 假设我们有一个名为REGISTRAR的DeferredRegister +// 假设我们有一个名为ITEM的RegistryObject和一个名为BLOCK的RegistryObject public static final RegistryObject EXAMPLE_TAB = REGISTRAR.register("example", () -> CreativeModeTab.builder() - // Set name of tab to display + // 设置所要展示的页的名称 .title(Component.translatable("item_group." + MOD_ID + ".example")) - // Set icon of creative tab + // 设置页图标 .icon(() -> new ItemStack(ITEM.get())) - // Add default items to tab + // 为物品栏页添加默认物品 .displayItems((params, output) -> { output.accept(ITEM.get()); output.accept(BLOCK.get()); @@ -63,10 +64,10 @@ public static final RegistryObject EXAMPLE_TAB = REGISTRAR.regi ); ``` -Registering an Item -------------------- +注册一个物品 +----------- -Items must be [registered][registering] to function. +物品必须经过[注册][registering]后才能发挥作用。 [modbus]: ../concepts/events.md#mod-event-bus [registering]: ../concepts/registries.md#methods-for-registering diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/legacy/index.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/legacy/index.md new file mode 100644 index 000000000..9a53959b7 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/legacy/index.md @@ -0,0 +1,29 @@ +旧版本的文档 +=========== + +Forge已经存在多年了,你仍然可以轻松访问Minecraft 1.1版本的Forge版本。每个版本之间都有显著的差异,支持这么多不同的版本是不可能的。因此,Forge使用了一个LTS系统,其中以前的主要Minecraft版本被视为“LTS”(长期支持)。只有最新版本和任何当前的LTS版本才会有易于访问的文档,并包含在侧边栏的版本下拉列表中。然而,一些旧版本曾经是LTS,或者在某个时候是最新版本,并编写了文档。可以在这里找到带有这些版本文档的旧网站链接。 + +!!! 重要 + 这些旧文档网站仅供参考。不要在Forge discord或Forge论坛上寻求旧版本的帮助。**当你使用旧版本时,将不会获得支持。** + +### 以前已有文档的版本列表 + +不幸的是,并非所有版本都使用了相当长的时间,那些版本的文档可能不完整。无论何时发布新版本,都会随着时间的推移复制和调整上一版本的文档,以包含新的和更新的信息。当某个版本长期不受支持时,信息永远不会更新。准确率百分比表示本应更新的信息实际更新了多少。 + +| 版本 | 准确率 | 链接 | +|:-------------:|:----------:|:------------------------------------------| +| 1.12.x | 100% | https://docs.minecraftforge.net/en/1.12.x/ | +| 1.13.x | 10% | https://docs.minecraftforge.net/en/1.13.x/ | +| 1.14.x | 10% | https://docs.minecraftforge.net/en/1.14.x/ | +| 1.15.x | 85% | https://docs.minecraftforge.net/en/1.15.x/ | +| 1.16.x | 85% | https://docs.minecraftforge.net/en/1.16.x/ | +| 1.17.x | 85% | https://docs.minecraftforge.net/en/1.17.x/ | +| 1.18.x | 90% | https://docs.minecraftforge.net/en/1.18.x/ | +| 1.19.2 | 90% | https://docs.minecraftforge.net/en/1.19.2/ | +| 1.19.x | 90% | https://docs.minecraftforge.net/en/1.19.x/ | + +### RetroGradle + +**RetroGradle**是一项档案计划,旨在更新旧版ForgeGradle 1.x至2.3工具链及其Minecraft版本,以使用现代ForgeGradle 4.x及以上工具链。目标是通过将Minecraft Forge的所有过去发布的版本移动到可验证的工作和现代工具链来保留它们,该工具链是数据驱动的,而不是针对特定版本的工作流进行硬编码。 + +如果任何开发人员希望为这项档案工作做出贡献,请访问The Forge Project discord服务器,并询问指定频道的方向。请注意,此计划仅旨在为社区的利益 _保留_ 这些旧版本, _而**不是**支持为这些不受支持的旧版本开发模组。_ 不支持使用或开发不受支持的版本。 diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/legacy/porting.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/legacy/porting.md index cc7c0480b..5ff5796b5 100644 --- a/i18n/zh-CN/docusaurus-plugin-content-docs/current/legacy/porting.md +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/legacy/porting.md @@ -1,9 +1,9 @@ -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] | @@ -11,7 +11,7 @@ Here you can find a list of primers on how to port from old versions to the curr | 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.19.4 -> 1.20 | [Primer by ChampionAsh5357][1194to120] | [112to114]: https://gist.github.com/williewillus/353c872bcf1a6ace9921189f6100d09a [114to115]: https://gist.github.com/williewillus/30d7e3f775fe93c503bddf054ef3f93e diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/misc/config.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/misc/config.md index e940fdb93..514905531 100644 --- a/i18n/zh-CN/docusaurus-plugin-content-docs/current/misc/config.md +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/misc/config.md @@ -1,137 +1,133 @@ -Configuration -============= +配置 +==== -Configurations define settings and consumer preferences that can be applied to a mod instance. Forge uses a configuration system using [TOML][toml] files and read with [NightConfig][nightconfig]. +配置定义了可以应用于模组实例的设置和Consumer偏好。Forge使用一个采用[TOML][toml]文件的配置系统,并使用[NightConfig][nightconfig]进行读取。 -Creating a Configuration ------------------------- +创建一个配置 +----------- -A configuration can be created using a subtype of `IConfigSpec`. Forge implements the type via `ForgeConfigSpec` and enables its construction through `ForgeConfigSpec$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`的子类型创建配置。Forge通过`ForgeConfigSpec`实现该类型,并通过`ForgeConfigSpec$Builder`实现其构造。生成器可以通过`Builder#push`创建一个部分,通过`Builder#pop`留下一个部分以将配置值分隔为多个部分。之后,可以使用以下两种方法之一构建配置: - Method | Description + 方法 | 描述 :--- | :--- -`build` | Creates the `ForgeConfigSpec`. -`configure` | Creates a pair of the class holding the config values and the `ForgeConfigSpec`. +`build` | 创建`ForgeConfigSpec`. +`configure` | 创建一对包含配置值和`ForgeConfigSpec`的类。 -:::note -`ForgeConfigSpec$Builder#configure` is typically used with a `static` block and a class that takes in `ForgeConfigSpec$Builder` as part of its constructor to attach and hold the values: +!!! 注意 + `ForgeConfigSpec$Builder#configure`通常与`static`块和一个类一起使用,该类将`ForgeConfigSpec$Builder`作为其构造函数的一部分,用于附加和保存值: -```java -// In some config class -ExampleConfig(ForgeConfigSpec.Builder builder) { - // Define values here in final fields -} - -// Somewhere the constructor is accessible -static { - Pair pair = new ForgeConfigSpec.Builder() - .configure(ExampleConfig::new); - // Store pair values in some constant field -} -``` -::: + ```java + // 在某个配置类中 + ExampleConfig(ForgeConfigSpec.Builder builder) { + // 在此处在final字段中定义值 + } + + // 在该构造函数可被访问的某处 + static { + Pair pair = new ForgeConfigSpec.Builder() + .configure(ExampleConfig::new); + // 在某个常量字段中存储成对的值 + } + ``` -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 ForgeConfigSpec$Builder builder +// 对于某个ForgeConfigSpec$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. - -#### 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 - -:::note -`DoubleValue`s, `IntValue`s, and `LongValue`s are range values which specify the class as `Double`, `Integer`, and `Long` respectively. -::: - -* **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: +可以使用`ConfigValue#get`获取值本身。这些值会被额外缓存,以防止从文件中进行多次读取。 + +#### 附加的配置值类型 + +* **范围值** + * 描述: 值必须在所定义的范围之间 + * 类型: `Comparable` + * 方法名称: `#defineInRange` + * 附加组件: + * 配置值可能的最小值和最大值 + * 表示配置值的数据类型的类 + +!!! 注意 + `DoubleValue`、`IntValue`和`LongValue`是将类型分别指定为`Double`、`Integer`和`Long`的范围值。 + +* **白名单值** + * 描述: 值必须在所提供的集合中 + * 类型: `T` + * 方法名称: `#defineInList` + * 附加组件: + * 配置所允许的值的集合 + +* **列表值** + * 描述: 值是一个条目列表 + * 类型: `List` + * 方法名称: `#defineList`,`#defineListAllowEmpty`(如果列表可为空) + * 附加组件: + * 用于确保列表中反序列化元素有效的验证器 + +* **枚举值** + * 描述: 在所提供的集合中的一个枚举值 + * 类型: `Enum` + * 方法名称: `#defineEnum` + * 附加组件: * 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` +* **布尔值** + * 描述: A `boolean` value + * 类型: `Boolean` + * 方法名称: `#define` -Registering a Configuration ---------------------------- +注册一个配置 +----------- -Once a `ForgeConfigSpec` has been built, it must be registered to allow Forge 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 `ForgeConfigSpec`, and optionally a specific file name for the configuration. +一旦构建了`ForgeConfigSpec`,就必须对其进行注册,以允许Forge根据需要加载、跟踪和同步配置设置。配置应通过`ModLoadingContext#registerConfig`在模组构造函数中注册。配置可以注册为表示配置所属侧的给定类型`ForgeConfigSpec`,以及配置的特定文件名(可选)。 ```java -// In the mod constructor with a ForgeConfigSpec CONFIG +// 在具有一个ForgeConfigSpec 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 -Forge documents the [config types][type] within their codebase. -::: +!!! 提示 + Forge在相应的代码库中用文档详述了[配置类型][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/ [nightconfig]: https://github.com/TheElectronWill/night-config diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/misc/debugprofiler.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/misc/debugprofiler.md index f59e33b56..bcfb23bbe 100644 --- a/i18n/zh-CN/docusaurus-plugin-content-docs/current/misc/debugprofiler.md +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/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数据、存档数据和tick信息,以查找耗时的代码。考虑到像`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. -::: +!!! 注意 + 当然,你只能分析实际到达的代码路径。要分析的实体和方块实体必须存在于存档中才能显示在结果中。 -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`”文件。在顶部,它首先告诉它运行了多长时间(以毫秒为单位)以及在这段时间内运行了多少tick。 -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,19 @@ 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,它是一tick所用时间的百分比。对于层级1,它是其父层所用时间的百分比。 | 第二个百分比告诉整个tick所花的时间。 -## 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`实例。 +现在,你只需要在结果文件中搜索你的部分的名称。 diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/misc/gametest.mdx b/i18n/zh-CN/docusaurus-plugin-content-docs/current/misc/gametest.mdx index 90b1f0470..8de5aab5e 100644 --- a/i18n/zh-CN/docusaurus-plugin-content-docs/current/misc/gametest.mdx +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/misc/gametest.mdx @@ -1,141 +1,138 @@ -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. 加载一个结构或模板,其中包含测试交互或行为的场景(scene)。 +1. 一种方法执行要在场景中执行的逻辑。 +1. 逻辑执行的方法。如果达到成功状态,则测试成功。否则,测试将失败,结果将存储在场景附近的讲台(lectern)内。 -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 { @GameTest public static void exampleTest(GameTestHelper helper) { - // Do stuff + // 做一些事情 } } ``` -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个tick来设置执行 + 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]加载结构,将玩家放置在所需位置,最后运行`/test pos`命令来获得。这将获取玩家相对于玩家200个方块内最近结构的坐标。该命令将相对位置导出为聊天中的可复制文本组件,用作最终的本地变量。 -:::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(...);' -``` -:::tip + ```bash + /test pos # 导出'final BlockPos = new BlockPos(...);' + ``` -#### 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` | 如果没有抛出`GameTestAssertException`,则会立即测试所提供的`Runnable`并成功。如果测试在该瞬时tick上没有成功,则将其标记为失败。 +`#succeedWhen` | 所提供的`Runnable`在超时之前每tick都会进行测试,如果对其中一个tick的检查没有引发`GameTestAssertException`,则会成功。 +`#succeedOnTickWhen` | 提供的`Runnable`在指定的tick上进行测试,如果没有抛出`GameTestAssertException`,则会成功。如果`Runnable`在任何其他tick上成功,则将其标记为失败。 -:::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. -::: +!!! 重要 + 游戏测试每tick都会执行,直到测试被标记为成功。因此,在给定的tick上安排成功的方法必须小心,不要总是在之前的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` | 操作将在指定的tick上运行。 +`#runAfterDelay` | 操作将在当前tick后`x`tick时运行。 +`#onEachTick` | 操作在每个tick都会运行。 -#### 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. +批处理本身并没有提供任何有用的东西。但是,批处理可以用于在测试运行的当前存档上执行设置和拆卸(teardown)状态。这是通过用`@BeforeBatch`注释方法来完成的,用`@AfterBatch`来进行设置或拆卸。`#batch`方法必须与提供给游戏测试的字符串匹配。 -Batch methods are `Consumer` references, meaning they take in a `ServerLevel` and return nothing: +批处理方法是`Consumer`引用,这意味着它们接受`ServerLevel`而不返回任何内容: ```java public class ExampleGameTests { @BeforeBatch(batch = "firstBatch") public static void beforeTest(ServerLevel level) { - // Perform setup + // 进行设置(setup) } @GameTest(batch = "firstBatch") public static void exampleTest2(GameTestHelper helper) { - // Do stuff + // 做一些事情 } } ``` -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,139 +143,116 @@ 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]到模组事件总线。以这种方式注册的测试方法必须在每个用`@GameTest`注释的方法上向`GameTest#templateNamespace`提供其mod id。 ```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 + // 进行设置(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`. +游戏测试是在由结构或模板加载的场景中执行的。所有模板都定义了场景的尺寸以及将要加载的初始数据(方块和实体)。模板必须存储为`data//structures`中的`.nbt`文件。 -:::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命令后面:`/test `。 -Buildscript Configurations +构建脚本(buildscript)配置 -------------------------- -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`. - -
- Important infromation on 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. +!!! 警告 + 命名空间之间不能有空格;否则,将无法正确加载命名空间。 -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 -} -``` -::: -
+### 游戏测试服务端运行配置 +游戏测试服务端是一种运行构建服务端的特殊配置。构建服务端返回所需的失败游戏测试数的退出代码。所有失败的测试都被记录到日志,无论是必需的还是可选的。此服务端可以使用`gradlew runGameTestServer`运行。 -### 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' ``` diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/misc/keymappings.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/misc/keymappings.md index 5d72a8cfe..9017aebb0 100644 --- a/i18n/zh-CN/docusaurus-plugin-content-docs/current/misc/keymappings.md +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/misc/keymappings.md @@ -1,157 +1,151 @@ -# 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]. +键盘布局(键映射)或键盘绑定定义了应与输入绑定的特定操作:鼠标单击、按键等。每当客户端可以进行输入时,都可以检查键盘布局定义的每个操作。此外,每个键盘布局都可以通过[控制选项菜单][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`. +`KeyMapping`可以通过仅在物理客户端上监听[**模组事件总线**][modbus]上的`RegisterKeyMappingsEvent`并调用`#register`来注册。 ```java -// In some physical client only class +// 在某个仅物理客户端的类中 -// Key mapping is lazily initialized so it doesn't exist until it is registered +// 键盘布局是延迟初始化的,因此在注册之前它不存在 public static final Lazy EXAMPLE_MAPPING = Lazy.of(() -> /*...*/); -// Event is on the mod event bus only on the physical client +// 事件仅在物理客户端上的模组事件总线上 @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" // 映射将在杂项(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`与`CTRL + G`),则修改器可能不希望映射具有相同的行为。为了解决这个问题,Forge在构造函数中添加了一个额外的参数来接受一个`KeyModifier`,它可以将control(`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) { // 仅调用代码一次,因为tick事件在每个tick调用两次 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 -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内,可以使用`IForgeKeyMapping#isActiveAndMatches`在其中一个`GuiEventListener`方法中检查映射。可以检查的最常见方法是`#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); } ``` -:::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位置和单击的按钮。通过使用带有`MOUSE`输入的`InputConstants$Type#getOrCreate`创建输入,可以根据映射检查鼠标按钮。 ```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); } ``` -:::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#mod-event-bus -[controls]: https://minecraft.wiki/w/Options#Controls +[controls]: https://minecraft.fandom.com/wiki/Options#Controls [tk]: ../concepts/internationalization.md#translatablecontents [keyinput]: https://www.glfw.org/docs/3.3/input_guide.html#input_key [forgebus]: ../concepts/events.md#creating-an-event-handler diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/misc/updatechecker.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/misc/updatechecker.md index 85d7960ce..2c731d359 100644 --- a/i18n/zh-CN/docusaurus-plugin-content-docs/current/misc/updatechecker.md +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/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提供了一个非常轻量级的可选择性加入的更新检查框架。如果任何模组有可用的更新,它将在主菜单和模组列表的'Mods'按钮上显示一个闪烁的图标,以及相应的更改日志。它*不会*自动下载更新。 -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。这个文件可以托管在你自己的网络服务器、GitHub或任何你想要的地方,只要你的模组的所有用户都能可靠地访问它。 -Update JSON format ------------------- +更新JSON格式 +------------ -The JSON itself has a relatively simple format as follows: +JSON本身有一个相对简单的格式,如下所示: ```js { "homepage": "", "": { "": "", - // List all versions of your mod for the given Minecraft version, along with their changelogs + // 列出给定Minecraft版本的所有模组版本,以及它们的更改日志 // ... }, "promos": { "-latest": "", - // Declare the latest "bleeding-edge" version of your mod for the given Minecraft version + // 为给定的Minecraft版本声明你的模组的最新"bleeding-edge"版本 "-recommended": "", - // Declare the latest "stable" version of your mod for the given Minecraft version + // 为给定的Minecraft版本声明你的模组的最新"stable"版本 // ... } } ``` -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`下的链接是当模组过时时将向用户显示的链接。 +* Forge使用内部算法来确定你的模组的一个版本字符串是否比另一个“新”。大多数版本控制方案应该是兼容的,但如果你担心方案是否受支持,请参阅`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 -------------------------------- +检索更新检查结果 +--------------- -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(<你的modId>)`或`ModList.get().getModContainerByObject(<你的模组实例>)`来获取`ModContainer`。你可以使用`ModList.get().getModContainerById()`获取任何其他模组的`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 diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/networking/entities.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/networking/entities.md index 466b6cfcb..0bdab032f 100644 --- a/i18n/zh-CN/docusaurus-plugin-content-docs/current/networking/entities.md +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/networking/entities.md @@ -1,37 +1,35 @@ -Entities -======== +实体 +==== -In addition to regular network messages, there are various other systems provided to handle synchronizing entity data. +除了常规的网络消息之外,Forge还提供了各种其他系统来处理同步实体数据。 -Spawn Data ----------- +生成数据 +------- -In general, the spawning of modded entities is handled separately, by Forge. +一般来说,由模组编写的实体的生成是由Forge单独处理的。 -:::note -This means that simply extending a vanilla entity class may not inherit all its behavior. You may need to implement certain vanilla behaviors yourself. -::: +!!! 注意 + 这意味着简单地继承一个原版实体类可能不会继承它的所有行为。你可能需要自己实施某些原版行为。 -You can add extra data to the spawn packet Forge sends by implementing the following interface. +你可以通过实现以下接口向Forge发送的生成数据包添加额外的数据。 ### IEntityAdditionalSpawnData -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. +如果你的实体具有客户端所需的数据,但不会随时间变化,则可以使用此接口将其添加到实体生成数据包中。`#writeSpawnData`和`#readSpawnData`控制如何将数据编码到网络缓冲区/从网络缓冲区解码数据。 -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`实例获取并设置这些值。所做的更改将自动同步到客户端。 diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/networking/index.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/networking/index.md index 9d88ae644..1a2abdf49 100644 --- a/i18n/zh-CN/docusaurus-plugin-content-docs/current/networking/index.md +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/networking/index.md @@ -1,20 +1,20 @@ -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 are a variety of techniques provided by Forge to facilitate communication mostly built on top of [netty][]. +Forge提供了多种技术来促进通信,这些技术大多建立在[netty][]之上。 -The simplest, for a new mod, would be [SimpleImpl][channel], where most of the complexity of the netty system is abstracted away. It uses a message and handler style system. +对于一个新模组来说,最简单的当是[SimpleImpl][channel],在这里,网络系统的大部分复杂性都被抽象掉了。它使用消息和处理器样式的系统。 [netty]: https://netty.io "Netty Website" [channel]: ./simpleimpl.md "SimpleImpl in Detail" diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/networking/simpleimpl.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/networking/simpleimpl.md index 88380f089..d8cb0b480 100644 --- a/i18n/zh-CN/docusaurus-plugin-content-docs/current/networking/simpleimpl.md +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/networking/simpleimpl.md @@ -1,12 +1,12 @@ SimpleImpl ========== -SimpleImpl is the name given to the packet system that revolves around the `SimpleChannel` class. Using this system is by far the easiest way to send custom data between clients and the server. +SimpleImpl是围绕`SimpleChannel`类的数据包系统的名称。使用此系统是迄今为止在客户端和服务端之间发送自定义数据的最简单方法。 -Getting Started ---------------- +快速入门 +-------- -First you need to create your `SimpleChannel` object. We recommend that you do this in a separate class, possibly something like `ModidPacketHandler`. Create your `SimpleChannel` as a static field in this class, like so: +首先,你需要创建`SimpleChannel`对象。我们建议你在单独的类中执行此操作,可能类似于`ModidPacketHandler`。将`SimpleChannel`创建为此类中的静态字段,如下所示: ```java private static final String PROTOCOL_VERSION = "1"; @@ -18,102 +18,101 @@ public static final SimpleChannel INSTANCE = NetworkRegistry.newSimpleChannel( ); ``` -The first argument is a name for the channel. The second argument is a `Supplier` returning the current network protocol version. The third and fourth arguments respectively are `Predicate` checking whether an incoming connection protocol version is network-compatible with the client or server, respectively. -Here, we simply compare with the `PROTOCOL_VERSION` field directly, meaning that the client and server `PROTOCOL_VERSION`s must always match or FML will deny login. +第一个参数是通道的名称。第二个参数是返回当前网络协议版本的`Supplier`。第三个和第四个参数分别是`Predicate`,分别检查传入的连接协议版本是否与客户端或服务端网络兼容。在这里,我们只需直接与`PROTOCOL_VERSION`字段进行比较,这意味着客户端和服务端`PROTOCOL_VERSION`必须始终匹配,否则FML将拒绝登录。 -The Version Checker -------------------- +版本检查器 +--------- -If your mod does not require the other side to have a specific network channel, or to be a Forge instance at all, you should take care that you properly define your version compatibility checkers (the `Predicate` parameters) to handle additional "meta-versions" (defined in `NetworkRegistry`) that can be received by the version checker. These are: +如果你的模组不要求另一端拥有特定的网络通道,或者根本不要求对方是Forge实例,你应该注意正确定义你的版本兼容性检查器(`Predicate`参数),以处理版本检查器可以接收的其他“元版本”(在`NetworkRegistry`中定义)。这些是: -* `ABSENT` - if this channel is missing on the other endpoint. Note that in this case, the endpoint is still a Forge endpoint, and may have other mods. -* `ACCEPTVANILLA` - if the endpoint is a vanilla (or non-Forge) endpoint. +* `ABSENT` - 如果该通道在另一个端点上丢失。请注意,在这种情况下,端点仍然是Forge端点,并且可能具有其他模组。 +* `ACCEPTVANILLA` - 如果端点是原版(或非Forge)端点(如Fabric——译者注)。 -Returning `false` for both means that this channel must be present on the other endpoint. If you just copy the code above, this is what it does. Note that these values are also used during the list ping compatibility check, which is responsible for showing the green check / red cross in the multiplayer server select screen. +对两者返回`false`意味着该通道必须存在于另一端上。如果你只是复制上面的代码,这就是它的作用。请注意,在列表ping兼容性检查期间也会使用这些值,该检查负责在多人服务器选择屏幕中显示绿色复选框/红叉。 -Registering Packets -------------------- +注册数据包 +--------- -Next, we must declare the types of messages that we would like to send and receive. This is done using `INSTANCE#registerMessage`, which takes 5 parameters: +接下来,我们必须声明要发送和接收的消息类型。这是使用`INSTANCE#registerMessage`完成的,它接受5个参数: -- The first parameter is the discriminator for the packet. This is a per-channel unique ID for the packet. We recommend you use a local variable to hold the ID, and then call registerMessage using `id++`. This will guarantee 100% unique IDs. -- The second parameter is the actual packet class `MSG`. -- The third parameter is a `BiConsumer` responsible for encoding the message into the provided `FriendlyByteBuf`. -- The fourth parameter is a `Function` responsible for decoding the message from the provided `FriendlyByteBuf`. -- The final parameter is a `BiConsumer>` responsible for handling the message itself. +- 第一个参数是数据包的鉴别器。这是数据包的每个通道的唯一ID。我们建议你使用本地变量来保存ID,然后使用`id++`调用registerMessage。这将保证100%的唯一ID。 +- 第二个参数是实际的数据包类`MSG`。 +- 第三个参数是`BiConsumer`,负责将消息编码到所提供的`FriendlyByteBuf`中。 +- 第四个参数是`Function`,负责从所提供的`FriendlyByteBuf`中解码消息。 +- 最后一个参数是负责处理消息本身的`BiConsumer>`。 -The last three parameters can be method references to either static or instance methods in Java. Remember that an instance method `MSG#encode(FriendlyByteBuf)` still satisfies `BiConsumer`; the `MSG` simply becomes the implicit first argument. +最后三个参数可以是Java中静态方法或实例方法的方法引用。请记住,实例方法`MSG#encode(FriendlyByteBuf)`仍然满足`BiConsumer`;`MSG`只不过成为隐含的第一个自变量。 -Handling Packets ----------------- +处理数据包 +--------- -There are a couple things to highlight in a packet handler. A packet handler has both the message object and the network context available to it. The context allows access to the player that sent the packet (if on the server), and a way to enqueue thread-safe work. +在数据包处理器中,有几件事需要强调。数据包处理器同时具有对其可用消息对象和网络上下文。该上下文允许访问发送数据包的玩家(如果在服务端上),并允许一种方式将线程安全工作排入队列。 ```java public static void handle(MyMessage msg, Supplier ctx) { ctx.get().enqueueWork(() -> { - // Work that needs to be thread-safe (most work) - ServerPlayer sender = ctx.get().getSender(); // the client that sent this packet - // Do stuff + // 要求线程安全的工作(大多数工作) + ServerPlayer sender = ctx.get().getSender(); // 发送该数据包的客户端 + // 处理一些事情 }); ctx.get().setPacketHandled(true); } ``` -Packets sent from the server to the client should be handled in another class and wrapped via `DistExecutor#unsafeRunWhenOn`. +从服务端发送到客户端的数据包应在另一个类中进行处理,并通过`DistExecutor#unsafeRunWhenOn`进行包装。 ```java -// In Packet class +// 在Packet类中 public static void handle(MyClientMessage msg, Supplier ctx) { ctx.get().enqueueWork(() -> - // Make sure it's only executed on the physical client + // 确保其仅在物理客户端上执行 DistExecutor.unsafeRunWhenOn(Dist.CLIENT, () -> () -> ClientPacketHandlerClass.handlePacket(msg, ctx)) ); ctx.get().setPacketHandled(true); } -// In ClientPacketHandlerClass +// 在ClientPacketHandlerClass中 public static void handlePacket(MyClientMessage msg, Supplier ctx) { - // Do stuff + // 处理一些事情 } ``` -Note the presence of `#setPacketHandled`, which is used to tell the network system that the packet has successfully completed handling. +请注意`#setPacketHandled`的存在,它用于告诉网络系统该数据包已成功完成处理。 -:::caution -As of Minecraft 1.8 packets are by default handled on the network thread. -That means that your handler can _not_ interact with most game objects directly. Forge provides a convenient way to make your code execute on the main thread instead through the supplied `NetworkEvent$Context`. Simply call `NetworkEvent$Context#enqueueWork(Runnable)`, which will call the given `Runnable` on the main thread at the next opportunity. -::: +!!! 警告 + 从Minecraft 1.8开始,默认情况下在网络线程上处理数据包。 -:::caution -Be defensive when handling packets on the server. A client could attempt to exploit the packet handling by sending unexpected data. + 这意味着你的处理器 _不_ 能直接与大多数游戏对象交互。Forge提供了一种方便的方法,可以通过提供的`NetworkEvent$Context`在主线程上执行代码。只需调用`NetworkEvent$Context#enqueueWork(Runnable)`,它将在下一次有机会时调用主线程上的给定`Runnable`。 -A common problem is vulnerability to **arbitrary chunk generation**. This typically happens when the server is trusting a block position sent by a client to access blocks and block entities. When accessing blocks and block entities in unloaded areas of the level, the server will either generate or load this area from disk, then promptly write it to disk. This can be exploited to cause **catastrophic damage** to a server's performance and storage space without leaving a trace. +!!! 警告 + 在服务端上处理数据包时要采取防御措施。客户端可能试图通过发送意外数据来对数据包处理过程施压。 -To avoid this problem, a general rule of thumb is to only access blocks and block entities if `Level#hasChunkAt` is true. -::: + 一个常见的问题是易受**任意区块生成**的攻击。当服务端信任客户端发送的方块位置来访问方块和块方实体时,通常会发生这种情况。当访问存档中的未加载区域中的方块和方块实体时,服务端将会要么生成要么从磁盘加载该区域,然后立即将其写入磁盘。利用这一点,可以在不留下痕迹的情况下对服务端的性能和存储空间造成**灾难性破坏**。 -Sending Packets ---------------- + 为了避免这个问题,一个普遍的经验法则是,仅访问`Level#hasChunkAt`为true的方块和方块实体。 -### Sending to the Server -There is but one way to send a packet to the server. This is because there is only ever *one* server the client can be connected to at once. To do so, we must again use that `SimpleChannel` that was defined earlier. Simply call `INSTANCE.sendToServer(new MyMessage())`. The message will be sent to the handler for its type, if one exists. +发送数据包 +--------- -### Sending to Clients +### 向服务端发送 -Packets can be sent directly to a client using the `SimpleChannel`: `HANDLER.sendTo(new MyClientMessage(), serverPlayer.connection.getConnection(), NetworkDirection.PLAY_TO_CLIENT)`. However, this can be quite inconvenient. Forge has some convenience functions that can be used: +只有一种方法可以将数据包发送到服务端。这是因为客户端一次只能连接到一个服务端。要做到这一点,我们必须再次使用前面定义的`SimpleChannel`。只需调用`INSTANCE.sendToServer(new MyMessage())`。消息将被发送到对应其类型的处理器(如果存在)。 + +### 向客户端发送 + +数据包可以使用`SimpleChannel`直接发送到客户端:`HANDLER.sendTo(new MyClientMessage(), serverPlayer.connection.getConnection(), NetworkDirection.PLAY_TO_CLIENT)`。但是,这可能很不方便。Forge有一些可以使用的便利功能: ```java -// Send to one player +// 向一位玩家发送 INSTANCE.send(PacketDistributor.PLAYER.with(serverPlayer), new MyMessage()); -// Send to all players tracking this level chunk +// 向正在追踪该存档某个区块的所有玩家发送 INSTANCE.send(PacketDistributor.TRACKING_CHUNK.with(levelChunk), new MyMessage()); -// Send to all connected players +// 向所有已连接的玩家发送 INSTANCE.send(PacketDistributor.ALL.noArg(), new MyMessage()); ``` -There are additional `PacketDistributor` types available; check the documentation on the `PacketDistributor` class for more details. +还有其他类型的`PacketDistributor`可用;有关更多详细信息,请查看`PacketDistributor`类的文档。 diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/rendering/modelextensions/ambientocclusion_annotated.png b/i18n/zh-CN/docusaurus-plugin-content-docs/current/rendering/modelextensions/ambientocclusion_annotated.png new file mode 100644 index 0000000000000000000000000000000000000000..17009b076dcc793c929a9c0b62c1ecf42a40aa05 GIT binary patch literal 98157 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:special_cutout` and `:special_translucent`. +然后,这些可以在JSON中寻址为`:special_cutout`和`:special_translucent`。 [mipmapping]: https://en.wikipedia.org/wiki/Mipmap \ No newline at end of file diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/rendering/modelextensions/transforms.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/rendering/modelextensions/transforms.md index 100ee4670..eb0f2eccf 100644 --- a/i18n/zh-CN/docusaurus-plugin-content-docs/current/rendering/modelextensions/transforms.md +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/rendering/modelextensions/transforms.md @@ -1,13 +1,13 @@ -Root Transforms -=============== +根变换 +====== -Adding the `transform` entry at the top level of a model JSON suggests to the loader that a transformation should be applied to all geometry right before the rotations in the [blockstate] file in the case of a block model, and before the [display transforms][displaytransform] in the case of an item model. The transformation is available through `IGeometryBakingContext#getRootTransform()` in `IUnbakedGeometry#bake()`. +在模型JSON的顶层添加`transform`条目向加载器建议,在方块模型的情况下,应在[方块状态][blockstate]文件中的旋转之前对所有几何体应用变换,在物品模型的情况中,应在[显示变换][displaytransform]之前对其应用变换。转换可通过`IUnbakedGeometry#bake()`中的`IGeometryBakingContext#getRootTransform()`获得。 -Custom model loaders may ignore this field entirely. +自定义模型加载器可能会完全忽略此字段。 -The root transforms can be specified in two formats: +根变换可以用两种格式指定: -1. A JSON object containing a singular `matrix` entry containing a raw transformation matrix in the form of a nested JSON array with the last row omitted (3*4 matrix, row major order). The matrix is the composition of the translation, left rotation, scale, right rotation and the transformation origin in that order. Example demonstrating the structure: +1. 一个JSON对象,包含一个奇异的`matrix`条目,该条目包含一个嵌套JSON数组形式的原始转换矩阵,省略了最后一行(3*4矩阵,行主序)。矩阵是按平移、左旋转、缩放、右旋转和变换原点的顺序组成的。结构示例: ```js "transform": { "matrix": [ @@ -17,18 +17,18 @@ The root transforms can be specified in two formats: ] } ``` -2. A JSON object containing any combination of the following optional entries: - * `origin`: origin point used for the rotations and scaling - * `translation`: relative translation - * `rotation` or `left_rotation`: rotation around the translated origin to be applied before scaling - * `scale`: scale relative to the translated origin - * `right_rotation` or `post_rotation`: rotation around the translated origin to be applied after scaling +2. 一个JSON对象,包含以下可选项的任意组合: + * `origin`:用于旋转和缩放的原点 + * `translation`:相对平移 + * `rotation`或`left_rotation`:在缩放之前围绕要应用的平移原点旋转 + * `scale`:相对于平移原点的比例 + * `right_rotation`或`post_rotation`:在缩放之后要应用的围绕平移原点的旋转 -Element-wise specification -------------------------- +元素的指定 +--------- -If the transformation is specified as a combination of the entries mentioned in option 4, these entries will be applied in the order of `translation`, `left_rotation`, `scale`, `right_rotation`. -The transformation is moved to the specified origin as a last step. +如果转换被指定为选项4中提到的条目的组合,则这些条目将按照`translation`、`left_rotation`、`scale`、`right_rotation`的顺序应用。 +转换将移动到指定的原点,作为最后一步。 ```js { @@ -41,36 +41,36 @@ The transformation is moved to the specified origin as a last step. } ``` -The elements are expected to be defined as follows: +这些元素的定义应为如下: -### Origin +### 原点 -The origin can be specified either as an array of 3 floating point values representing a three-dimensional vector: `[ x, y, z ]` or as one of the three default values: +原点可以指定为表示三维矢量的3个浮点数的数组:`[ x, y, z ]`,也可以指定为三个默认值之一: * `"corner"` (0, 0, 0) * `"center"` (.5, .5, .5) * `"opposing-corner"` (1, 1, 1) -If the origin is not specified, it defaults to `"opposing-corner"`. +如果未指定原点,则其默认为`"opposing-corner"`。 -### Translation +### 平移 -The translation must be specified as an array of 3 floating point values representing a three-dimensional vector: `[ x, y, z ]` and defaults to (0, 0, 0) if not present. +平移必须指定为表示三维矢量的3个浮点数的数组:`[ x, y, z ]`,如果不存在,则默认为(0, 0, 0)。 -### Left and Right Rotation +### 左旋转和右旋转 -The rotations can be specified in any one of the following four ways: +可以通过以下四种方式中的任何一种指定旋转: -* Single JSON object with a single axis => rotation degree mapping: `{ "x": 90 }` -* Array of an arbitrary amount of JSON objects with the above format (applied in the order they are specified in): `[ { "x": 90 }, { "y": 45 }, { "x": -22.5 } ]` -* Array of 3 floating point values specifying the rotation in degrees around each axis: `[ 90, 180, 45 ]` -* Array of 4 floating point values specifying a quaternion directly: `[ 0.38268346, 0, 0, 0.9238795 ]` (example equals 45 degrees around the X axis) +* 具有单轴=>旋转度映射的单个JSON对象:`{ "x": 90 }` +* 具有上述格式的任意数量的JSON对象的数组(按指定顺序应用):`[ { "x": 90 }, { "y": 45 }, { "x": -22.5 } ]` +* 由3个浮点数组成的数组,指定围绕每个轴的旋转(以度为单位):`[ 90, 180, 45 ]` +* 直接指定四元数的4个浮点数的数组:`[ 0.38268346, 0, 0, 0.9238795 ]`(示例等于绕X轴45度) -If the respective rotation is not specified, it will default to no rotation. +如果未指定相应的旋转,则默认为无旋转。 -### Scale +### 比例 -The scale must be specified as an array of 3 floating point values representing a three-dimensional vector: `[ x, y, z ]` and defaults to (1, 1, 1) if not present. +比例必须指定为表示三维矢量的3个浮点数的数组:`[ x, y, z ]`,如果不存在,则默认为(1, 1, 1)。 -[blockstate]: https://minecraft.wiki/w/Tutorials/Models#Block_states +[blockstate]: https://minecraft.fandom.com/wiki/Tutorials/Models#Block_states [displaytransform]: ../modelloaders/transform.md \ No newline at end of file diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/rendering/modelextensions/visibility.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/rendering/modelextensions/visibility.md index d9a1f71b5..89c1b8b0d 100644 --- a/i18n/zh-CN/docusaurus-plugin-content-docs/current/rendering/modelextensions/visibility.md +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/rendering/modelextensions/visibility.md @@ -1,9 +1,9 @@ -Part Visibility -=============== +部分可见度 +========= -Adding the `visibility` entry at the top level of a model JSON allows control over the visibility of different parts of the model to decide whether they should be baked into the final [`BakedModel`][bakedmodel]. The definition of a "part" is dependent on the model loader loading this model and custom model loaders are free to ignore this entry completely. Out of the model loaders provided by Forge only the [composite model loader][composite] and the [OBJ model loader][obj] make use of this functionality. The visibility entries are specified as `"part name": boolean` entries. +在模型JSON的顶层添加`visibility`条目可以控制模型不同部分的可见性,以决定是否应将它们烘焙到最终的[`BakedModel`][bakedmodel]中。“零件”的定义取决于加载此模型的模型加载器,自定义模型加载器可以完全忽略此条目。在Forge提供的模型加载器中,只有[复合模型加载器][composite]和[OBJ模型加载器][obj]使用了此功能。可见性条目被指定为`"part name": boolean`条目。 -Example of a composite model with two parts, the second of which will not be baked into the final model, and two child models overriding this visibility to have only the first part and both parts visible respectively: +具有两个部分的复合模型的示例,其中第二个部分不会烘焙到最终模型中,并且两个子模型覆盖此可见性,分别只显示第一个部分和两个部分: ```js // mycompositemodel.json { @@ -39,13 +39,13 @@ Example of a composite model with two parts, the second of which will not be bak } ``` -The visibility of a given part is determined by checking whether the model specifies a visibility for this part and, if not present, recursively checking the model's parent until either an entry is found or there is no further parent to check, in which case it defaults to true. +给定部分的可见性是通过检查模型是否指定了该部分的可见性来确定的,如果不存在,则递归地检查模型的父级,直到找到条目或没有其他父级要检查,在这种情况下,它默认为true。 -This allows setups like the following where multiple models use different parts of a single composite model: +这允许进行以下设置,其中多个模型使用单个复合模型的不同部分: -1. A composite model specifies multiple components -2. Multiple models specify this composite model as their parent -3. These child models individually specify different visibilities for the parts +1. 复合模型指定多个组件 +2. 多个模型将此复合模型指定为其父模型 +3. 这些子模型分别指定部分的不同可见性 [bakedmodel]: ../modelloaders/bakedmodel.md [composite]: ../modelloaders/index.md/#composite-models diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/rendering/modelloaders/bakedmodel.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/rendering/modelloaders/bakedmodel.md index 91be720fe..ce5455e5e 100644 --- a/i18n/zh-CN/docusaurus-plugin-content-docs/current/rendering/modelloaders/bakedmodel.md +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/rendering/modelloaders/bakedmodel.md @@ -1,56 +1,55 @@ -`BakedModel` -============= +烘焙模型(`BakedModel`) +======================= -`BakedModel` is the result of calling `UnbakedModel#bake` for the vanilla model loader or `IUnbakedGeometry#bake` for custom model loaders. Unlike `UnbakedModel` or `IUnbakedGeometry`, which purely represents a shape without any concept of items or blocks, `BakedModel` is not as abstract. It represents geometry that has been optimized and reduced to a form where it is (almost) ready to go to the GPU. It can also process the state of an item or block to change the model. +`BakedModel`是对普通模型加载器调用`UnbakedModel#bake`或对自定义模型加载器调用`IUnbakedGeometry#bake`的结果。与`UnbakedModel`或`IUnbakedGeometry`不同,`BakedModel`纯粹代表一种没有任何物品或方块概念的形状,而不是抽象的。它表示已经优化并简化为可以(几乎)进入GPU的几何体。它还可以处理物品或方块的状态以更改模型。 -In a majority of cases, it is not really necessary to implement this interface manually. One can instead use one of the existing implementations. +在大多数情况下,实际上没有必要手动实现此接口。相反,可以使用现有的实现之一。 ### `getOverrides` -Returns the [`ItemOverrides`][overrides] to use for this model. This is only used if this model is being rendered as an item. +返回要用于此模型的[`ItemOverrides`][overrides]。仅当此模型被渲染为物品时才使用此选项。 ### `useAmbientOcclusion` -If the model is rendered as a block in the level, the block in question does not emit any light, and ambient occlusion is enabled. This causes the model to be rendered with [ambient occlusion](ambocc). +如果模型在存档中渲染为方块,则有问题的方块不会发出任何光,并且环境光遮挡处于启用状态。这将导致使用[环境光遮挡](ambocc)来渲染模型。 ### `isGui3d` -If the model is rendered as an item in an inventory, on the ground as an entity, on an item frame, etc., this makes the model look "flat." In GUIs, this also disables the lighting. +如果模型被渲染为物品栏中的物品,在地面上被渲染为实体,在物品框架上,等等,这会使模型看起来“扁平”。在GUI中,这也会禁用照明。 ### `isCustomRenderer` -:::caution -Unless you know what you're doing, just `return false` from this and continue on. -::: +!!! 重要 + 除非你知道自己在做什么,否则只需`return false`然后继续其他事项。 -When rendering this as an item, returning `true` causes the model to not be rendered, instead falling back to `BlockEntityWithoutLevelRenderer#renderByItem`. For certain vanilla items such as chests and banners, this method is hardcoded to copy data from the item into a `BlockEntity`, before using a `BlockEntityRenderer` to render that BE in place of the item. For all other items, it will use the `BlockEntityWithoutLevelRenderer` instance provided by `IClientItemExtensions#getCustomRenderer`. Refer to [BlockEntityWithoutLevelRenderer][bewlr] page for more information. +将其渲染为物品时,返回`true`将导致模型不被渲染,转而回到`BlockEntityWithoutLevelRenderer#renderByItem`。对于某些原版物品,如箱子和旗帜,此方法被硬编码为将数据从物品复制到`BlockEntity`中,然后使用`BlockEntityRenderer`来渲染BE以代替物品。对于所有其他物品,它将使用由`IClientItemExtensions#getCustomRenderer`提供的`BlockEntityWithoutLevelRenderer`实例。有关详细信息,请参阅[BlockEntityWithoutLevelRenderer][bewlr]页。 ### `getParticleIcon` -Whatever texture should be used for the particles. For blocks, this shows when an entity falls on it, when it breaks, etc. For items, this shows when it breaks or when it's eaten. +粒子应使用的任何纹理。对于方块,它将在实体掉落在其上或其被破坏时显示。对于物品,它将在报废或被吃掉时显示。 -!!! important - The vanilla method with no parameters has been deprecated in favor of `#getParticleIcon(ModelData)` since model data can have an effect on how a particular model might be rendered. +!!! 重要 + 由于模型数据可能会对特定模型的渲染方式产生影响,因此不推荐使用不带参数的原版方法,而推荐使用`#getParticleIcon(ModelData)`。 ### `getTransforms` -Deprecated in favor of implementing `#applyTransform`. The default implementation is fine if `#applyTransform` is implemented. See [Transform][transform]. +此方法被废弃,推荐实现`#applyTransform`。如果实现了`#applyTransform`,则该默认实现是足够的。参见[变换][transform]。 ### `applyTransform` -See [Transform][transform]. +参见[变换][transform]。 ### `getQuads` -This is the main method of `BakedModel`. It returns a list of `BakedQuad`s: objects which contain the low-level vertex data that will be used to render the model. If the model is being rendered as a block, then the `BlockState` passed in is non-null. If the model is being rendered as an item, the `ItemOverrides` returned from `#getOverrides` is responsible for handling the state of the item, and the `BlockState` parameter will be `null`. +这是`BakedModel`的主要方法。它返回一个`BakedQuad`的列表:包含将用于渲染模型的低级顶点数据的对象。如果模型被呈现为方块,那么传入的`BlockState`是非空的。如果模型被呈现为物品,则从`#getOverrides`返回的`ItemOverrides`负责处理物品的状态,并且`BlockState`参数将为`null`。 -The `Direction` passed in is used for face culling. If the block against the given side of another block being rendered is opaque, then the faces associated with that side are not rendered. If that parameter is `null`, all faces not associated with a side are returned (that will never be culled). +传入的`Direction`用于面剔除。如果正在渲染的另一个方块的给定边上的块是不透明的,则不会渲染与该边关联的面。如果该参数为`null`,则返回与边不关联的所有面(其永远不会被剔除)。 -The `rand` parameter is an instance of Random. +`rand`参数是Random的一个实例。 -It also takes in a non null `ModelData` instance. This can be used to define extra data when rendering the specific model via `ModelProperty`s. For example, one such property is `CompositeModel$Data`, which is used to store any additional submodel data for a model using the `forge:composite` model loader. +它还接受一个非null的`ModelData`实例。这可用于在通过`ModelProperty`渲染特定模型时定义额外数据。例如,一个这样的属性是`CompositeModel$Data`,用于使用`forge:composite`模型加载器存储模型的任何附加子模型数据。 -Note that this method is called very often: once for every combination of non-culled face and supported block render layer (anywhere between 0 to 28 times) *per block in a level*. This method should be as fast as possible, and should probably cache heavily. +请注意,此方法经常被调用:对于*一个存档中的每个方块*,非剔除面和支持的方块渲染层的每个组合(任何位置从0到28次)调用一次。这给方法应该尽可能快,并且可能需要大量缓存。 [overrides]: ./itemoverrides.md [ambocc]: https://en.wikipedia.org/wiki/Ambient_occlusion diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/rendering/modelloaders/index.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/rendering/modelloaders/index.md index 9856d688a..a21060022 100644 --- a/i18n/zh-CN/docusaurus-plugin-content-docs/current/rendering/modelloaders/index.md +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/rendering/modelloaders/index.md @@ -1,25 +1,24 @@ -Custom Model Loaders -==================== +自定义模型加载器 +=============== -A "model" is simply a shape. It can be a simple cube, it can be several cubes, it can be a truncated icosidodecahedron, or anything in between. Most models you'll see will be in the vanilla JSON format. Models in other formats are loaded into `IUnbakedGeometry`s by an `IGeometryLoader` at runtime. Forge provides default implementations for WaveFront OBJ files, buckets, composite models, models in different render layers, and a reimplementation of Vanilla's `builtin/generated` item model. Most things do not care about what loaded the model or what format it's in as they are all eventually represented by an `BakedModel` in code. +“模型”只是一种形状。它可以是一个简单的立方体,可以是几个立方体,也可以是截角二十面体,或者介于两者之间的任何东西。你将看到的大多数模型都是普通的JSON格式。其他格式的模型在运行时由`IGeometryLoader`加载到`IUnbakedGeometry`中。Forge为WaveFront OBJ文件、bucket、复合模型、不同渲染层中的模型提供了默认实现,并重新实现了原版的`builtin/generated`物品模型。大多数事情都不关心加载了什么模型或模型的格式,因为它们最终都由代码中的`BakedModel`表示。 -:::caution -Specifying a custom model loader through the top-level `loader` entry in a model JSON will cause the `elements` entry to be ignored unless it is consumed by the custom loader. All other vanilla entries will still be loaded and available in the unbaked `BlockModel` representation and may be consumed outside of the custom loader. -::: +!!! 警告 + 通过模型JSON中的顶级`loader`条目指定自定义模型加载程序将导致`elements`条目被忽略,除非它被自定义加载程序使用。所有其他普通条目仍将被加载并在未烘焙的`BlockModel`表示中可用,并且可能在自定义加载程序之外被使用。 -WaveFront OBJ Models --------------------- +WaveFront OBJ模型 +----------------- -Forge adds a loader for the `.obj` file format. To use these models, the JSON must reference the `forge:obj` loader. This loader accepts any model location that is in a registered namespace and whose path ends in `.obj`. The `.mtl` file should be placed in the same location with the same name as the `.obj` to be used automatically. The `.mtl` file will probably have to be manually edited to change the paths pointing to textures defined within the JSON. Additionally, the V axis for textures may be flipped depending on the external program that created the model (i.e. V = 0 may be the bottom edge, not the top). This may be rectified in the modelling program itself or done in the model JSON like so: +Forge为`.obj`文件格式添加了一个加载程序。要使用这些模型,JSON必须引用`forge:obj`加载程序。此加载程序接受位于已注册命名空间中且路径以`.obj`结尾的任何模型位置。`.mtl`文件应放置在与要自动使用的`.obj`具有相同名称的相同位置。`.mtl`文件可能需要手动编辑才能更改指向JSON中定义的纹理的路径。此外,纹理的V轴可以根据创建模型的外部程序翻转(即,V=0可能是底部边缘,而不是顶部边缘)。这可以在建模程序本身中纠正,也可以在模型JSON中这样做: ```js { - // Add the following line on the same level as a 'model' declaration + // 在与'model'声明相同的级别上添加以下行 "loader": "forge:obj", "flip_v": true, "model": "examplemod:models/block/model.obj", "textures": { - // Can refer to in .mtl using #texture0 + // 可在.mtl中用#texture0引用 "texture0": "minecraft:block/dirt", "particle": "minecraft:block/dirt" } diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/rendering/modelloaders/itemoverrides.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/rendering/modelloaders/itemoverrides.md index 63ec2d882..594ee0d67 100644 --- a/i18n/zh-CN/docusaurus-plugin-content-docs/current/rendering/modelloaders/itemoverrides.md +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/rendering/modelloaders/itemoverrides.md @@ -1,41 +1,41 @@ -`ItemOverrides` -================== +物品重载(`ItemOverrides`) +========================== -`ItemOverrides` provides a way for an [`BakedModel`][baked] to process the state of an `ItemStack` and return a new `BakedModel`; thereafter, the returned model replaces the old one. `ItemOverrides` represents an arbitrary function `(BakedModel, ItemStack, ClientLevel, LivingEntity, int)` → `BakedModel`, making it useful for dynamic models. In vanilla, it is used to implement item property overrides. +`ItemOverrides`为[`BakedModel`][baked]提供了一种处理`ItemStack`状态并返回新`BakedModel`的方法;此后,返回的模型将替换旧模型。`ItemOverrides`表示任意函数`(BakedModel, ItemStack, ClientLevel, LivingEntity, int)` → `BakedModel`,使其适用于动态模型。在原版中,它用于实现物品属性重写。 ### `ItemOverrides()` -Given a list of `ItemOverride`s, the constructor copies and bakes the list. The baked overrides may be accessed with `#getOverrides`. +给定`ItemOverride`的列表,该构造函数将复制并烘焙该列表。可以使用`#getOverrides`访问烘焙后的覆盖。 ### `resolve` -This takes an `BakedModel`, an `ItemStack`, a `ClientLevel`, a `LivingEntity`, and an `int` to produce another `BakedModel` to use for rendering. This is where models can handle the state of their items. +这需要一个`BakedModel`、`ItemStack`、`ClientLevel`、`LivingEntity`和`int`来生成另一个用于渲染的`BakedModel`。这是模型可以处理其物品状态的地方。 -This should not mutate the level. +这不应该改变存档。 ### `getOverrides` -Returns an immutable list containing all the [`BakedOverride`][override]s used by this `ItemOverrides`. If none are applicable, this returns the empty list. +返回一个不可变列表,该列表包含此`ItemOverrides`使用的所有[`BakedOverride`][override]。如果不适用,则返回空列表。 ## `BakedOverride` -This class represents a vanilla item override, which holds several `ItemOverrides$PropertyMatcher` for the properties on an item and a model to use in case those matchers are satisfied. They are the objects in the `overrides` array of a vanilla item JSON model: +这个类表示一个原版的物品覆盖,它为一个物品和一个模型的属性保存了几个`ItemOverrides$PropertyMatcher`,以备满足这些匹配器时使用。它们是原版物品JSON模型的`overrides`数组中的对象: ```js { - // Inside a vanilla JSON item model + // 在一个原版JSON物品模型内 "overrides": [ { - // This is an ItemOverride + // 这是一个ItemOverride "predicate": { - // This is the Map, containing the names of properties and their minimum values + // 这是Map,包含属性的名称以及它们的最小值 "example1:prop": 0.5 }, - // This is the 'location', or target model, of the override, which is used if the predicate above matches + // 这是该覆盖的'location'或目标模型,如果上面的predicate匹配,则使用它 "model": "example1:item/model" }, { - // This is another ItemOverride + // 这是另一个ItemOverride "predicate": { "example2:prop": 1 }, diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/rendering/modelloaders/transform.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/rendering/modelloaders/transform.md index 58a55a5f5..de2c75820 100644 --- a/i18n/zh-CN/docusaurus-plugin-content-docs/current/rendering/modelloaders/transform.md +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/rendering/modelloaders/transform.md @@ -1,37 +1,37 @@ -Transform -========== +变换 +==== -When an [`BakedModel`][bakedmodel] is being rendered as an item, it can apply special handling depending on which transform it is being rendered in. "Transform" means in what context the model is being rendered. The possible transforms are represented in code by the `ItemDisplayContext` enum. There are two systems for handling transform: the deprecated vanilla system, constituted by `BakedModel#getTransforms`, `ItemTransforms`, and `ItemTransform`, and the Forge system, embodied by the method `IForgeBakedModel#applyTransform`. The vanilla code is patched to favor using `applyTransform` over the vanilla system whenever possible. +当[`BakedModel`][bakedmodel]被渲染为物品时,它可以根据在哪个变换中渲染它来应用特殊处理。“变换”是指在什么上下文中渲染模型。可能的转换在代码中由`ItemDisplayContext`枚举表示。有两种处理转换的系统:不推荐使用的原版系统,由`BakedModel#getTransforms`、`ItemTransforms`和`ItemTransform`构成;Forge系统,由方法`IForgeBakedModel#applyTransform`实现。原版代码被进行了修补,以便尽可能使用`applyTransform`而不是原版系统。 `ItemDisplayContext` ---------------- +-------------------- -`NONE` - Used for the display entity by default when no context is set and by Forge when a `Block`'s `RenderShape` is set to `#ENTITYBLOCK_ANIMATED`. +`NONE` - 默认情况下,当未设置上下文时,用于显示实体;当`Block`的`RenderShape`设置为`#ENTITYBLOCK_ANIMATED`时,被Forge使用。 -`THIRD_PERSON_LEFT_HAND`/`THIRD_PERSON_RIGHT_HAND`/`FIRST_PERSON_LEFT_HAND`/`FIRST_PERSON_RIGHT_HAND` - The first person values represent when the player is holding the item in their own hand. The third person values represent when another player is holding the item and the client is looking at them in the 3rd person. Hands are self-explanatory. +`THIRD_PERSON_LEFT_HAND`/`THIRD_PERSON_RIGHT_HAND`/`FIRST_PERSON_LEFT_HAND`/`FIRST_PERSON_RIGHT_HAND` - 第一人称值表示玩家何时将物品握在自己手中。第三人称值表示当另一个玩家拿着物品,而客户端用第三人称看着它们时。手的含义是不言自明的。 -`HEAD` - Represents when any player is wearing the item in the helmet slot (e.g. pumpkins). +`HEAD` - 表示当任何玩家在头盔槽中佩戴该物品时(例如南瓜)。 -`GUI` - Represents when the item is being rendered in a `Screen`. +`GUI` - 表示当该物品被在一个`Screen`中渲染时。 -`GROUND` - Represents when the item is being rendered in the level as an `ItemEntity`. +`GROUND` - 表示该物品在存档中作为一个`ItemEntity`被渲染时。 -`FIXED` - Used for item frames. +`FIXED` - 用于物品展示框。 -The Vanilla Way ---------------- +原版的方式 +--------- -The vanilla way of handling transform is through `BakedModel#getTransforms`. This method returns an `ItemTransforms`, which is a simple object that contains various `ItemTransform`s as `public final` fields. An `ItemTransform` represents a rotation, a translation, and a scale to be applied to the model. The `ItemTransforms` is a container for these, holding one for each of the `ItemDisplayContext`s except `NONE`. In the vanilla implementation, calling `#getTransform` for `NONE` results in the default transform, `ItemTransform#NO_TRANSFORM`. +原版处理转换的方式是通过`BakedModel#getTransforms`。此方法返回一个`ItemTransforms`,这是一个简单的对象,包含各种作为`public final`的`ItemTransform`字段。`ItemTransform`表示要应用于模型的旋转、平移和比例。`ItemTransforms`是这些的容器,除了`NONE`之外,每个`ItemDisplayContext`都有一个容器。在原版实现中,为`NONE`调用`#getTransform`会产生默认转换`ItemTransform#NO_TRANSFORM`。 -The entire vanilla system for handling transforms is deprecated by Forge, and most implementations of `BakedModel` should simply `return ItemTransforms#NO_TRANSFORMS` (which is the default implementation) from `BakedModel#getTransforms`. Instead, they should implement `#applyTransform`. +Forge废弃了使用处理转换的整个原版系统,`BakedModel`的大多数实现应该简单地从`BakedModel#getTransforms`中`return ItemTransforms#NO_TRANSFORMS`(这是默认实现)。相反,他们应该实现`#applyTransform`。 -The Forge Way -------------- +Forge的方式 +----------- -The Forge way of handling transforms is `#applyTransform`, a method patched into `BakedModel`. It supersedes the `#getTransforms` method. +Forge处理转换的方法是`#applyTransform`,这是一种修补到`BakedModel`中的方法。它取代了`#getTransforms`方法。 #### `BakedModel#applyTransform` -Given a `ItemDisplayContext`, `PoseStack`, and a boolean to determine whether to apply the transform for the left hand, this method produces an `BakedModel` to be rendered. Because the returned `BakedModel` can be a totally new model, this method is more flexible than the vanilla method (e.g. a piece of paper that looks flat in hand but crumpled on the ground). +给定一个`ItemDisplayContext`、`PoseStack`和一个布尔值来确定是否对左手应用变换,此方法将生成一个要渲染的`BakedModel`。因为返回的`BakedModel`可以是一个全新的模型,所以这种方法比原版方法(例如,一张手里看起来很平但在地上皱巴巴的纸)更灵活。 [bakedmodel]: ./bakedmodel.md diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/resources/client/index.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/resources/client/index.md index f8ea1ed7c..f29a9f4e3 100644 --- a/i18n/zh-CN/docusaurus-plugin-content-docs/current/resources/client/index.md +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/resources/client/index.md @@ -1,15 +1,15 @@ -Resource Packs -============== +资源包 +====== -[Resource Packs][respack] allow for the customization of client resources through the `assets` directory. This includes textures, models, sounds, localizations, and others. Your mod (as well as Forge itself) can also have resource packs. Any user can therefore modify all the textures, models, and other assets defined within this directory. +[资源包][respack]允许通过`assets`目录自定义客户端资源。这包括纹理、模型、声音、本地化和其他。你的模组(以及Forge本身)也可以有资源包。因此,任何用户都可以修改该目录中定义的所有纹理、模型和其他资源。 -### Creating a Resource Pack -Resource Packs are stored within your project's resources. The `assets` directory contains the contents of the pack, while the pack itself is defined by the `pack.mcmeta` alongside the `assets` folder. -Your mod can have multiple asset domains, since you can add or modify already existing resource packs, like vanilla's, Forge's, or another mod's. -You can then follow the steps found [at the Minecraft Wiki][createrespack] to create any resource pack. +### 创建一个资源包 +资源包存储在项目的资源中。`assets`目录包含该包的内容,而该包本身则由`assets`文件夹旁边的`pack.mcmeta`定义。 +你的模组可以有多个资源域,因为你可以添加或修改现有的资源包,比如原版的、Forge的或其他模组的。 +然后,你可以按照[在Minecraft Wiki][createrespack]中找到的步骤创建任何资源包。 -Additional reading: [Resource Locations][resourcelocation] +附加阅读:[资源位置][resourcelocation] -[respack]: https://minecraft.wiki/w/Resource_Pack -[createrespack]: https://minecraft.wiki/w/Tutorials/Creating_a_resource_pack +[respack]: https://minecraft.fandom.com/wiki/Resource_Pack +[createrespack]: https://minecraft.fandom.com/wiki/Tutorials/Creating_a_resource_pack [resourcelocation]: ../../concepts/resources.md#ResourceLocation diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/resources/client/models/index.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/resources/client/models/index.md index a999c5622..9cb958697 100644 --- a/i18n/zh-CN/docusaurus-plugin-content-docs/current/resources/client/models/index.md +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/resources/client/models/index.md @@ -1,25 +1,24 @@ -Models -====== +模型 +==== -The [model system][models] is Minecraft's way of giving blocks and items their shapes. Through the model system, blocks and items are mapped to their models, which define how they look. One of the main goals of the model system is to allow not only textures but the entire shape of a block/item to be changed by resource packs. Indeed, any mod that adds items or blocks also contains a mini-resource pack for their blocks and items. +[模型系统][models]是Minecraft赋予方块和物品形状的方式。通过模型系统,方块和物品被映射到它们的模型,这些模型定义了它们的外观。模型系统的主要目标之一是不仅允许纹理,还允许资源包更改方块/物品的整个形状。事实上,任何添加物品或方块的模组也包含用于其方块和物品的迷你资源包。 -Model Files ------------ +模型文件 +------- -Models and textures are linked through [`ResourceLocation`][resloc]s but are stored in the `ModelManager` using `ModelResourceLocation`s. Models are referenced in different locations through the block or item's registry name depending on whether they are referencing [block states][statemodel] or [item models][itemmodels]. Blocks will have their `ModelResourceLocation` represent their registry name along with a stringified version of its current [`BlockState`][state] while items will use their registry name followed by `inventory`. +模型和纹理通过[`ResourceLocation`][resloc]链接,但使用`ModelResourceLocation`存储在`ModelManager`中。模型通过方块或物品的注册表名称在不同位置引用,具体取决于它们是引用[方块状态][statemodel]还是[物品模型][itemmodels]。方块将使其`ModelResourceLocation`代表其注册表名称及其当前[`BlockState`][state]的字符串化版本,而物品将使用其注册表名称后跟`inventory`。 -:::note -JSON models only support cuboid elements; there is no way to express a triangular wedge or anything like it. To have more complicated models, another format must be used. -::: +!!! 注意 + JSON模型只支持长方体元素;没有办法表达三角楔或类似的东西。要有更复杂的模型,必须使用另一种格式。 -### Textures +### 纹理 -Textures, like models, are contained within resource packs and are referred to with `ResourceLocation`s. In Minecraft, the [UV coordinates][uv] (0,0) are taken to mean the **top-left** corner. UVs are *always* from 0 to 16. If a texture is larger or smaller, the coordinates are scaled to fit. A texture should also be square, and the side length of a texture should be a power of two, as doing otherwise breaks mipmapping (e.g. 1x1, 2x2, 8x8, 16x16, and 128x128 are good. 5x5 and 30x30 are not recommended because they are not powers of 2. 5x10 and 4x8 are completely broken as they are not square.). Textures should only ever be not a square if it is [animated][animated]. +纹理和模型一样,包含在资源包中,并被称为`ResourceLocation`。在《我的世界》中,[UV坐标][UV] (0,0)表示**左上角**。UV*总是*从0到16。如果纹理较大或较小,则会缩放坐标以进行拟合。纹理也应该是正方形的,纹理的边长应该是2的幂,否则会破坏mipmapping(例如1x1、2x2、8x8、16x16和128x128是好的。不建议使用5x5和30x30,因为它们不是2的幂。5x10和4x8会完全断裂,因为它们不是正方形的。)。只有当纹理是[动画化的][animated]时,纹理才应该不是正方形。 -[models]: https://minecraft.wiki/w/Tutorials/Models#File_path +[models]: https://minecraft.fandom.com/wiki/Tutorials/Models#File_path [resloc]: ../../../concepts/resources.md#resourcelocation -[statemodel]: https://minecraft.wiki/w/Tutorials/Models#Block_states -[itemmodels]: https://minecraft.wiki/w/Tutorials/Models#Item_models +[statemodel]: https://minecraft.fandom.com/wiki/Tutorials/Models#Block_states +[itemmodels]: https://minecraft.fandom.com/wiki/Tutorials/Models#Item_models [state]: ../../../blocks/states.md [uv]: https://en.wikipedia.org/wiki/UV_mapping -[animated]: https://minecraft.wiki/w/Resource_Pack?so=search#Animation +[animated]: https://minecraft.fandom.com/wiki/Resource_Pack?so=search#Animation diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/resources/client/models/itemproperties.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/resources/client/models/itemproperties.md index bf0a43ca5..f9c1b250d 100644 --- a/i18n/zh-CN/docusaurus-plugin-content-docs/current/resources/client/models/itemproperties.md +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/resources/client/models/itemproperties.md @@ -1,32 +1,29 @@ -Item Properties -=============== +物品属性 +======== -Item properties are a way for the "properties" of items to be exposed to the model system. An example is the bow, where the most important property is how far the bow has been pulled. This information is then used to choose a model for the bow, creating an animation for pulling it. +物品属性是将物品的“属性”公开给模型系统的一种方式。一个例子是弓,其中最重要的特性是弓被拉了多远。然后,这些信息用于选择弓的模型,创建拉动弓的动画。 -An item property assigns a certain `float` value to every `ItemStack` it is registered for, and vanilla item model definitions can use these values to define "overrides", where an item defaults to a certain model, but if an override matches, it overrides the model and uses another. They are useful mainly because they are continuous. For example, bows use item properties to define their pull animation. The item models are decided by the 'float' number predicates, it is not limited but generally between `0.0F` and `1.0F`. This allows resource packs to add as many models as they want for the bow pulling animation along that spectrum, instead of being stuck with four "slots" for their models in the animation. The same is true of the compass and clock. +物品属性为其注册的每个`ItemStack`分配一个特定的`float`值,原版物品模型定义可以使用这些值来定义“覆盖”,其中物品默认为某个模型,但如果覆盖匹配,则覆盖该模型并使用另一个模型。它们之所以有用,主要是因为它们是连续的。例如,弓使用物品属性来定义其拉动动画。物品模型由'float'数字谓词决定,它不受限制,但通常在`0.0F`和`1.0F`之间。这允许资源包为拉弓动画添加他们想要的任意多个模型,而不是在动画中为他们的模型设置四个“槽”。指南针和时钟也是如此。 -Adding Properties to Items --------------------------- +向物品添加属性 +------------- -`ItemProperties#register` is used to add a property to a certain item. The `Item` parameter is the item the property is being attached to (e.g. `ExampleItems#APPLE`). The `ResourceLocation` parameter is the name given to the property (e.g. `new ResourceLocation("pull")`). The `ItemPropertyFunction` is a functional interface that takes the `ItemStack`, the `ClientLevel` it is in (may be null), the `LivingEntity` that holds it (may be null), and the `int` containing the id of the holding entity (may be `0`), returning the `float` value for the property. For modded item properties, it is recommended that the mod id of the mod is used as the namespace (e.g. `examplemod:property` and not just `property`, as that really means `minecraft:property`). These should be done in `FMLClientSetupEvent`. -There's also another method `ItemProperties#registerGeneric` that is used to add properties to all items, and it does not take `Item` as its parameter since all items will apply this property. +`ItemProperties#register`用于向某个物品添加属性。`Item`参数是要附加属性的物品(例如`ExampleItems#APPLE`)。`ResourceLocation`参数是所要赋予属性的名称(例如`new ResourceLocation("pull")`)。`ItemPropertyFunction`是一个函数接口,它接受`ItemStack`、它所在的`ClientLevel`(可以为null)、持有它的`LivingEntity`(可以是null)和包含持有实体的id的`int`(可能是`0`),返回属性的`float`值。对于修改后的物品属性,建议将模组的mod id用作命名空间(例如`examplemod:property`,而不仅仅是`property`,因为这实际上意味着`minecraft:property`)。这些操作应在`FMLClientSetupEvent`中完成。 +还有另一个方法`ItemProperties#registerGeneric`用于向所有物品添加属性,并且它不将`Item`作为其参数,因为所有物品都将应用此属性。 -:::caution -Use `FMLClientSetupEvent#enqueueWork` to proceed with the tasks, since the data structures in `ItemProperties` are not thread-safe. -::: +!!! 重要 + 使用`FMLClientSetupEvent#enqueueWork`执行这些任务,因为`ItemProperties`中的数据结构不是线程安全的。 -:::note -`ItemPropertyFunction` is deprecated by Mojang in favor of using the subinterface `ClampedItemPropertyFunction` which clamps the result between `0` and `1`. -::: +!!! 注意 + Mojang反对使用`ItemPropertyFunction`而推荐使用`ClampedItemPropertyFunction`子接口,该子接口将结果夹在`0`和`1`之间。 -Using Overrides ---------------- +覆盖的使用 +--------- -The format of an override can be seen on the [wiki][format], and a good example can be found in `model/item/bow.json`. For reference, here is a hypothetical example of an item with an `examplemod:power` property. If the values have no match, the default is the current model, but if there are multiple matches, the last match in the list will be selected. +覆盖的格式可以在[wiki][format]上看到,一个很好的例子可以在`model/item/bow.json`中找到。为了参考,这里是一个具有`examplemod:power`属性的物品的假设例子。如果值不匹配,则默认为当前模型,但如果有多个匹配,则会选择列表中的最后一个匹配。 -:::caution -A predicate applies to all values *greater than or equal to* the given value. -::: +!!! 重要 + predicate适用于*大于或等于*给定值的所有值。 ```js { @@ -47,7 +44,7 @@ A predicate applies to all values *greater than or equal to* the given value. } ``` -And here is a hypothetical snippet from the supporting code. Unlike the older versions (lower than 1.16.x), this needs to be done on client side only because `ItemProperties` does not exist on server. +下面是支持代码中的一个假设片段。与旧版本(低于1.16.x)不同,这只需要在客户端完成,因为服务端上不存在`ItemProperties`。 ```java private void setup(final FMLClientSetupEvent event) @@ -62,4 +59,4 @@ private void setup(final FMLClientSetupEvent event) } ``` -[format]: https://minecraft.wiki/w/Tutorials/Models#Item_models +[format]: https://minecraft.fandom.com/wiki/Tutorials/Models#Item_models diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/resources/client/models/tinting.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/resources/client/models/tinting.md index d5347239c..7dc313bff 100644 --- a/i18n/zh-CN/docusaurus-plugin-content-docs/current/resources/client/models/tinting.md +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/resources/client/models/tinting.md @@ -1,17 +1,17 @@ -Coloring Textures -================= +纹理色调 +======== -Many blocks and items in vanilla change their texture color depending on where they are or what properties they have, such as grass. Models support specifying "tint indices" on faces, which are integers that can then be handled by `BlockColor`s and `ItemColor`s. See the [wiki][] for information on how tint indices are defined in vanilla models. +原版中的许多方块和物品会根据它们的位置或特性(如草)改变其纹理颜色。模型支持在面上指定“色调索引”,这是可以由`BlockColor`和`ItemColor`处理的整数。有关如何在原版模型中定义色调索引的信息,请参阅[wiki][]。 ### `BlockColor`/`ItemColor` -Both of these are single-method interfaces. `BlockColor` takes a `BlockState`, a (nullable) `BlockAndTintGetter`, and a (nullable) `BlockPos`. `ItemColor` takes an `ItemStack`. Both of them take an `int` parameter `tintIndex`, which is the tint index of the face being colored. Both of them return an `int`, a color multiplier. This `int` is treated as 4 unsigned bytes, alpha, red, green, and blue, in that order, from most significant byte to least. For each pixel in the tinted face, the value of each color channel is `(int)((float) base * multiplier / 255.0)`, where `base` is the original value for the channel, and `multiplier` is the associated byte from the color multiplier. Note that blocks do not use the alpha channel. For example, the grass texture, untinted, looks white and gray. The `BlockColor` and `ItemColor` for grass return color multipliers with low red and blue components, but high alpha and green components, (at least in warm biomes) so when the multiplication is performed, the green is brought out and the red/blue diminished. +这两个都是单方法接口。`BlockColor`接受一个`BlockState`、一个(可为空的)`BlockAndTintGetter`和一个(可为空的)`BlockPos`。`ItemColor`接受一个`ItemStack`。它们都采用一个`int`参数`tintIndex`,它是正在着色的面的色调索引。 它们都返回一个`int`,一个颜色乘数。这个`int`被视为4个无符号字节,即alpha、red、green和blue,按照从最高有效字节到最低有效字节的顺序。对于着色面上的每个像素,每个颜色通道的值是`(int)((float) base * multiplier / 255.0)`,其中`base`是通道的原始值,`multiplier`是颜色乘数的关联字节。 请注意,方块不使用Alpha通道。例如,未着色的草纹理看起来是白色和灰色的。草的`BlockColor`和`ItemColor`返回颜色乘数,red和blue分量较低,但alpha和green分量较高(至少在温暖的生物群系中),因此当执行乘法时,绿色会被带出,红色/蓝色会减少。 -If an item inherits from the `builtin/generated` model, each layer ("layer0", "layer1", etc.) has a tint index corresponding to its layer index. +如果物品继承自`builtin/generated`模型,则每个层(“layer0”、“layer1”等)都有与其层索引相对应的色调索引。 -### Creating Color Handlers +### 创建颜色处理器 -`BlockColor`s need to be registered to the `BlockColors` instance of the game. `BlockColors` can be acquired through `RegisterColorHandlersEvent$Block`, and an `BlockColor` can be registered by `#register`. Note that this does not cause the `BlockItem` for the given block to be colored. `BlockItem`s are items and need to be colored with an `ItemColor`. +`BlockColor`需要注册到游戏的`BlockColors`实例中。`BlockColors`可以通过`RegisterColorHandlersEvent$Block`获取,`BlockColor`可以通过`#register`注册。请注意,这不会导致给定方块的`BlockItem`被着色。`BlockItem`是物品,需要使用`ItemColor`进行着色。 ```java @SubscribeEvent @@ -20,7 +20,7 @@ public void registerBlockColors(RegisterColorHandlersEvent.Block event){ } ``` -`ItemColor`s need to be registered to the `ItemColors` instance of the game. `ItemColors` can be acquired through `RegisterColorHandlersEvent$Item`, and an `ItemColor` can be registered by `#register`. This method is overloaded to also take `Block`s, which simply registers the color handler for the item `Block#asItem` (i.e. the block's `BlockItem`). +`ItemColor`需要注册到游戏的`ItemColors`实例中。`ItemColors`可以通过`RegisterColorHandlersEvent$Item`获取,`ItemColor`可以通过`#register`注册。此方法也被重载为接受`Block`,它只是将物品`Block#asItem`的颜色处理器注册为物品(即方块的`BlockItem`)。 ```java @SubscribeEvent @@ -29,4 +29,4 @@ public void registerItemColors(RegisterColorHandlersEvent.Item event){ } ``` -[wiki]: https://minecraft.wiki/w/Tutorials/Models#Block_models +[wiki]: https://minecraft.fandom.com/wiki/Tutorials/Models#Block_models diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/resources/server/advancements.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/resources/server/advancements.md index 79d412d35..25d9dd84a 100644 --- a/i18n/zh-CN/docusaurus-plugin-content-docs/current/resources/server/advancements.md +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/resources/server/advancements.md @@ -1,21 +1,21 @@ -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.). +原版中的所有进度实现都是通过JSON进行数据驱动的。这意味着模组不需要创建新的进度,只需要[数据包][datapack]。关于如何创建这些进度并将其放入模组的`resources`中的完整列表可以在[Minecraft Wiki][wiki]上找到。此外,进度可以[有条件加载和或保持默认][conditional],这取决于存在的信息(模组被加载、物品的存在等)。 -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 +// 在某个进度JSON中 -// List of defined criteria to meet +// 所定义的要满足的标准的列表 "criteria": { "example_criterion1": { /*...*/ }, "example_criterion2": { /*...*/ }, @@ -23,10 +23,10 @@ Requirements are defined as an array of string arrays representing the name of t "example_criterion4": { /*...*/ } }, -// This advancement is only unlocked once -// - Criteria 1 AND 2 have been met -// OR -// - Criteria 3 and 4 have been met +// 该进度只能解锁一次 +// - 标准1和2均被满足 +// 或 +// - 标准3和4均被满足 "requirements": [ [ "example_criterion1", @@ -39,108 +39,106 @@ 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 can be created by implementing `SimpleCriterionTrigger` for the created `AbstractCriterionTriggerInstance` subclass. +可以通过为已创建的`AbstractCriterionTriggerInstance`子类实现`SimpleCriterionTrigger`来创建自定义条件触发器。 -### AbstractCriterionTriggerInstance Subclass +### AbstractCriterionTriggerInstance子类 -The `AbstractCriterionTriggerInstance` represents a single criteria defined in the `criteria` object. Trigger instances are responsible for holding the defined conditions, returning whether the inputs match the condition, and writing the instance to JSON for data generation. +`AbstractCriterionTriggerInstance`表示在`criteria`对象中定义的单个标准。触发器实例负责保存定义的条件,返回输入是否与条件匹配,并将实例写入JSON用于数据生成。 -Conditions are usually passed in through the constructor. The `AbstractCriterionTriggerInstance` super constructor requires the instance to define the registry name of the trigger and the conditions the player must meet as an `ContextAwarePredicate`. The registry name of the trigger should be supplied to the super directly while the conditions of the player should be a constructor parameter. +条件通常通过构造函数传递。`AbstractCriterionTriggerInstance`父级构造函数要求实例将触发器的注册表名和玩家必须满足的条件定义为`ContextAwarePredicate`。触发器的注册表名称应该直接提供给父级,而玩家的条件应该是构造函数参数。 ```java -// Where ID is the registry name of the trigger +// 其中ID是该触发器的注册表名称 public ExampleTriggerInstance(ContextAwarePredicate player, ItemPredicate item) { super(ID, player); - // Store the item condition that must be met + // 存储必须满足的物品条件 } ``` -:::note -Typically, trigger instances have a static constructor which allow these instances to be easily created for data generation. These static factory methods can also be statically imported instead of the class itself. +!!! 注意 + 通常,触发器实例有一个静态构造函数,允许轻松创建这些实例以生成数据。这些静态工厂方法也可以静态导入,而不是类本身。 -```java -public static ExampleTriggerInstance instance(ContextAwarePredicate player, ItemPredicate item) { - return new ExampleTriggerInstance(player, item); -} -``` -::: + ```java + public static ExampleTriggerInstance instance(ContextAwarePredicate player, ItemPredicate item) { + return new ExampleTriggerInstance(player, item); + } + ``` -Additionally, the `#serializeToJson` method should be overridden. The method should add the conditions of the instance to the other JSON data. +此外,应该重写`#serializeToJson`方法。该方法应该将实例的条件添加到其他JSON数据中。 ```java @Override public JsonObject serializeToJson(SerializationContext context) { JsonObject obj = super.serializeToJson(context); - // Write conditions to json + // 将条件写入json中 return obj; } ``` -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 +// 此方法对于每个实例都是唯一的,因此不会被重写 public boolean matches(ItemStack stack) { - // Since ItemPredicate matches a stack, a stack is the input + // 由于ItemPredicate与一个物品栈匹配,因此一个物品栈是输入 return this.item.matches(stack); } ``` ### SimpleCriterionTrigger -The `SimpleCriterionTrigger` subclass, where `T` is the type of the trigger instance, is responsible for specifying the registry name of the trigger, creating a trigger instance, and a method to check trigger instances and run attached listeners on success. +`SimpleCriterionTrigger`子类,其中`T`是触发器实例的类型,负责指定触发器的注册表名、创建触发器实例以及检查触发器实例和在成功时运行附加监听器的方法。 -The registry name of the trigger is supplied to `#getId`. This should match the registry name supplied to the trigger instance. +触发器的注册表名称被提供给`#getId`。这应该与提供给触发器实例的注册表名称相匹配。 -A trigger instance is created via `#createInstance`. This method reads a criteria from JSON. +触发器实例是通过`#createInstance`创建的。此方法从JSON中读取一个标准。 ```java @Override public ExampleTriggerInstance createInstance(JsonObject json, ContextAwarePredicate player, DeserializationContext context) { - // Read conditions from JSON: item + // 从JSON中读取条件:item return new ExampleTriggerInstance(player, item); } ``` -Finally, a method is defined 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 `AbstractCriterionTriggerInstance` subclass. This method should internally call `SimpleCriterionTrigger#trigger` to properly handle checking all listeners. Most trigger instances call this method `#trigger`. +最后,定义了一个方法来检查所有触发器实例,并在满足它们的条件时运行监听器。此方法接受`ServerPlayer`和`AbstractCriterionTriggerInstance`子类中匹配的方法定义的任何其他数据。此方法应在内部调用`SimpleCriterionTrigger#trigger`以正确处理检查所有监听器。大多数触发器实例从称这个方法为`#trigger`。 ```java -// This method is unique for each trigger and is as such not overridden +// 此方法对于每个触发器都是唯一的,因此不会被重写 public void trigger(ServerPlayer player, ItemStack stack) { this.trigger(player, - // The condition checker method within the AbstractCriterionTriggerInstance subclass + // AbstractCriterionTriggerInstance子类中的条件检查器方法 triggerInstance -> triggerInstance.matches(stack) ); } ``` -Afterwards, an instance should be registered using `CriteriaTriggers#register` during `FMLCommonSetupEvent`. +之后,应在`FMLCommonSetupEvent`期间使用`CriteriaTriggers#register`注册实例。 -:::danger -`CriteriaTriggers#register` must be enqueued to the synchronous work queue via `FMLCommonSetupEvent#enqueueWork` as the method is not thread-safe. -::: +!!! 重要 + `CriteriaTriggers#register`必须通过`FMLCommonSetupEvent#enqueueWork`排入同步工作队列,因为该方法不是线程安全的。 -### 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 -// Where EXAMPLE_CRITERIA_TRIGGER is the custom criteria trigger +// 在执行操作的某段代码中 +// 其中EXAMPLE_CRITERIA_TRIGGER是自定义标准触发器 public void performExampleAction(ServerPlayer player, ItemStack stack) { - // Run code to perform action + // 运行代码以执行操作 EXAMPLE_CRITERIA_TRIGGER.trigger(player, 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. +当进度达成时,可以给予奖励。其可以是经验点数、战利品表、配方书的配方的组合,也可以是作为创造模式玩家执行的[函数][function]。 ```js // In some advancement JSON @@ -160,8 +158,8 @@ When an advancement is completed, rewards may be given out. These can be a combi } ``` -[datapack]: https://minecraft.wiki/w/Data_pack -[wiki]: https://minecraft.wiki/w/Advancement/JSON_format +[datapack]: https://minecraft.fandom.com/wiki/Data_pack +[wiki]: https://minecraft.fandom.com/wiki/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 +[function]: https://minecraft.fandom.com/wiki/Function_(Java_Edition) +[triggers]: https://minecraft.fandom.com/wiki/Advancement/JSON_format#List_of_triggers diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/resources/server/conditional.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/resources/server/conditional.md index 89b8562f0..04edb6591 100644 --- a/i18n/zh-CN/docusaurus-plugin-content-docs/current/resources/server/conditional.md +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/resources/server/conditional.md @@ -1,171 +1,169 @@ -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 ---------------- +实现 +---- -Currently, conditional loading is implemented for recipes and advancements. For any conditional recipe or advancement, a list of conditions to datum pair is loaded. If the conditions specified for a datum in the list is true, then that datum is returned. Otherwise, the datum is discarded. +目前,条件加载已针对配方和进度实现。对于任何有条件的配方或进度,都会加载一个条件到数据对的列表。如果为列表中的某个数据指定的条件为true,则返回该数据。否则,将丢弃该数据。 ```js { - // The type needs to be specified for recipes as they can have custom serializers - // Advancements do not need this type + // 需要为配方指定类型,因为它们可以具有自定义序列化器 + // 进度不需要这种类型 "type": "forge:conditional", - "recipes": [ // Or 'advancements' for Advancements + "recipes": [ // 或'advancements'(对于进度) { - // The conditions to check + // 要检查的条件 "conditions": [ - // Conditions in the list are ANDed together + // 该列表中的条件用逻辑和(AND)相连 { - // Condition 1 + // 条件1 }, { - // Condition 2 + // 条件2 } ], - "recipe": { // Or 'advancement' for Advancements - // The recipe to use if all conditions succeed + "recipe": { // 或'advancement'(对于进度) + // 如果所有条件都成功,则使用的配方 } }, { - // Next condition to check if the previous fails + // 如果上一个条件失败,则接下来要检查的条件 }, ] } ``` -Conditionally-loaded data additionally have wrappers for [data generation][datagen] through `ConditionalRecipe$Builder` and `ConditionalAdvancement$Builder`. +通过`ConditionalRecipe$Builder`和`ConditionalAdvancement$Builder`,条件加载的数据还具有用于[数据生成][datagen]的包装。 -Conditions ----------- +条件 +---- -Conditions are specified by setting `type` to the name of the condition as specified by [`IConditionSerializer#getID`][serializer]. +条件是通过将`type`设置为[`IConditionSerializer#getID`][serializer]指定的条件名称来指定的。 -### True and False +### True和False -Boolean conditions consist of no data and return the expected value of the condition. They are represented by `forge:true` and `forge:false`. +布尔条件不包含任何数据,并返回条件的期望值。它们用`forge:true`和`forge:false`来表示。 ```js -// For some condition +// 对于某个条件 { - // Will always return true (or false for 'forge:false') + // 将始终返回true(或为'forge:false'时始终返回false) "type": "forge:true" } ``` -### Not, And, and Or +### Not、And和Or -Boolean operator conditions consist of the condition(s) being operated upon and apply the following logic. They are represented by `forge:not`, `forge:and`, and `forge:or`. +布尔运算符条件由正在操作的条件组成,并应用以下逻辑。它们用`forge:not`、`forge:and`和`forge:or`表示。 ```js -// For some condition +// 对于某个条件 { - // Inverts the result of the stored condition + // 反转存储条件的结果 "type": "forge:not", "value": { - // A condition + // 一个条件 } } ``` ```js -// For some condition +// 对于某个条件 { - // ANDs the stored conditions together (or ORs for 'forge:or') + // 将存储条件用逻辑和(AND)相连(或为'forge:or'时将存储条件用逻辑或(OR)相连) "type": "forge:and", "values": [ { - // First condition + // 第一个条件 }, { - // Second condition to be ANDed (or ORed for 'forge:or') + // 第二个要用逻辑和(AND)连接的条件(或为'forge:or'时用逻辑或(OR)连接) } ] } ``` -### Mod Loaded +### 模组被加载 -`ModLoadedCondition` returns true whenever the specified mod with the given id is loaded in the current application. This is represented by `forge:mod_loaded`. +只要在当前应用程序中加载了具有给定id的指定模组,`ModLoadedCondition`就会返回true。其由`forge:mod_loaded`表示。 ```js -// For some condition +// 对于某个条件 { "type": "forge:mod_loaded", - // Returns true if 'examplemod' is loaded + // 如果'examplemod'已被加载,则返回true "modid": "examplemod" } ``` -### Item Exists +### 物品存在 -`ItemExistsCondition` returns true whenever the given item has been registered in the current application. This is represented by `forge:item_exists`. +只要给定物品已在当前应用程序中注册,`ItemExistsCondition`就会返回true。其由`forge:item_exists`表示。 ```js -// For some condition +// 对于某个条件 { "type": "forge:item_exists", - // Returns true if 'examplemod:example_item' has been registered + // 如果'examplemod:example_item'已被注册,则返回true "item": "examplemod:example_item" } ``` -### Tag Empty +### 标签为空 -`TagEmptyCondition` returns true whenever the given item tag has no items within it. This is represented by `forge:tag_empty`. +只要给定的物品标签中没有物品,`TagEmptyCondition`就会返回true。其由`forge:tag_empty`表示。 ```js -// For some condition +// 对于某个条件 { "type": "forge:tag_empty", - // Returns true if 'examplemod:example_tag' is an item tag with no entries + // 如果'examplemod:example_tag'是一个没有条目的物品标签,则返回true "tag": "examplemod:example_tag" } ``` -Creating Custom Conditions --------------------------- +创建自定义条件 +------------- -Custom conditions can be created by implementing `ICondition` and its associated `IConditionSerializer`. +可以通过实现`ICondition`及与其关联的`IConditionSerializer`来创建自定义条件。 ### ICondition -Any condition only need to implement two methods: +任何条件只需要实现两种方法: -Method | Description +方法 | 描述 :---: | :--- -getID | The registry name of the condition. Must be equivalent to [`IConditionSerializer#getID`][serializer]. Used only for [data generation][datagen]. -test | Returns true if the condition has been satisfied. +getID | 该条件的注册表名称。必须等效于[`IConditionSerializer#getID`][serializer]。仅用于[数据生成][datagen]。 +test | 当条件满足时返回true。 -:::note -Every `#test` has access to some `IContext` representing the state of the game. Currently, only tags can be obtained from a registry. -::: +!!! 注意 + 每个`#test`都可以访问一些代表游戏状态的`IContext`。目前,从注册表中只能获取标签。 ### IConditionSerializer -Serializers need to implement three methods: +序列化器需要实现三种方法: -Method | Description +方法 | 描述 :---: | :--- -getID | The registry name of the condition. Must be equivalent to [`ICondition#getID`][condition]. -read | Reads the condition data from JSON. -write | Writes the given condition data to JSON. +getID | 该条件的注册表名称。必须等效于[`ICondition#getID`][condition]。 +read | 从JSON中读取条件数据。 +write | 将给定的条件数据写入JSON。 -:::note -Condition serializers are not responsible for writing or reading the type of the serializer, similar to other serializer implementations in Minecraft. -::: +!!! 注意 + 条件序列化器不负责写入或读取序列化器的类型,类似于Minecraft中的其他序列化器实现。 -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`. +之后,应声明一个静态实例来保存初始化的序列化器,然后在`RecipeSerializer`的`RegisterEvent`期间或在`FMLCommonSetupEvent`期间使用`CraftingHelper#register`进行注册。 ```java -// In some serializer class +// 在某个序列化器类中 public static final ExampleConditionSerializer INSTANCE = new ExampleConditionSerializer(); -// In some handler class +// 在某个处理器类中 public void registerSerializers(RegisterEvent event) { event.register(ForgeRegistries.Keys.RECIPE_SERIALIZERS, helper -> CraftingHelper.register(INSTANCE) @@ -173,9 +171,8 @@ public void registerSerializers(RegisterEvent event) { } ``` -:::danger -If using `FMLCommonSetupEvent` to register a condition 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`不是线程安全的。 [datagen]: ../../datagen/server/recipes.md [serializer]: #iconditionserializer diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/resources/server/glm.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/resources/server/glm.md index 71fae565a..d4f510c65 100644 --- a/i18n/zh-CN/docusaurus-plugin-content-docs/current/resources/server/glm.md +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/resources/server/glm.md @@ -1,40 +1,39 @@ -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你的修改器以及类似于[tags][标签]的工作。 +2. 代表修改器的序列化json。 + * 这将包含有关你修改的所有数据,并允许数据包调整你的效果。 +3. 一个继承自`IGlobalLootModifier`的类。 + * 使修改器工作的操作代码。大多数模组开发者都可以继承`LootModifier`,因为它提供了基本功能。 +4. 最后,使用编解码器对操作类进行编码和解码。 + * 其应像任何其他`IForgeRegistryEntry`一样被[注册][registered]。 -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 { - "replace": false, // Must be present + "replace": false, // 必须存在 "entries": [ - // Represents a loot modifier in 'data/examplemod/loot_modifiers/example_glm.json' + // 代表'data/examplemod/loot_modifiers/example_glm.json'中的一个战利品修改器 "examplemod:example_glm", "examplemod:example_glm2" // ... @@ -42,27 +41,26 @@ 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文件的[编解码器][codec]的注册表名称。这必须始终存在。 -`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`作为子类,则所有条件都将用**逻辑与(AND)**相连,并检查是否应应用修改器。 -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 +// 在data/examplemod/loot_modifiers/example_glm.json内 { "type": "examplemod:example_loot_modifier", "conditions": [ - // Normal loot table conditions + // 普通的战利品表条件 // ... ], "prop1": "val1", @@ -74,56 +72,55 @@ 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`返回已注册的[编解码器][codec],用于将修改器编码到JSON或从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`的数组定义了在修改战利品之前必须为true的条件列表。所提供的条件是用**逻辑和(AND)**连在一起的,这意味着所有条件都必须为true。 -The `#doApply` method works the same as the `#apply` method except that it only executes once all conditions return true. +`#doApply`方法的工作原理与`#apply`方法相同,只是它只在所有条件都返回true时执行。 ```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 + // 返回用于编码和解码此修改器的编解码器 } } ``` -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 +// 对于某个DeferredRegister> REGISTRAR public static final RegistryObject> = REGISTRAR.register("example_codec", () -> RecordCodecBuilder.create( inst -> LootModifier.codecStart(inst).and( @@ -137,7 +134,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]: ../../concepts/resources.md#ResourceLocation diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/resources/server/index.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/resources/server/index.md index bc5b93139..d8b755b90 100644 --- a/i18n/zh-CN/docusaurus-plugin-content-docs/current/resources/server/index.md +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/resources/server/index.md @@ -1,14 +1,14 @@ -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`目录修改逻辑服务端的文件。这包括进度、战利品表(loot_tables)、结构、配方、标签等。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 +[datapack]: https://minecraft.fandom.com/wiki/Data_pack +[createdatapack]: https://minecraft.fandom.com/wiki/Tutorials/Creating_a_data_pack [resourcelocation]: ../../concepts/resources.md#ResourceLocation diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/resources/server/loottables.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/resources/server/loottables.md index 52d7eed62..2a6a19616 100644 --- a/i18n/zh-CN/docusaurus-plugin-content-docs/current/resources/server/loottables.md +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/resources/server/loottables.md @@ -1,55 +1,54 @@ -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`. +战利品表由其指向`data//loot_tables/.json`的`ResourceLocation`引用。与引用相关联的`LootTable`可以使用`LootDataResolver#getLootTable`获得,其中`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$Builder`生成器的构造函数创建`LootParams`,并通过传递`LootContextParamSet`通过`LootParams$Builder#create`构建`LootParams`。 -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`通过传递表示要使用的随机实例的可为null的`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事件总线上触发的[事件][event],每当加载战利品表时就会触发。如果事件被取消,则会加载一个空的战利品表。 -:::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 +// 对于某个战利品池 { - "name": "example_pool", // Pool will be named 'example_pool' + "name": "example_pool", // 战利品池将被命名为'example_pool' "rolls": { // ... }, @@ -59,53 +58,53 @@ 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 +### 战利品表Id条件 -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 +// 在某个战利品池或池条目中 { "conditions": [ { "condition": "forge:loot_table_id", - // Will apply when the loot table is for dirt + // 当该战利品表对于泥土时将适用 "loot_table_id": "minecraft:blocks/dirt" } ] } ``` -### 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 +// 在某个战利品池或池条目中 { "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 +[datapack]: https://minecraft.fandom.com/wiki/Data_pack +[wiki]: https://minecraft.fandom.com/wiki/Loot_table [event]: ../../concepts/events.md#creating-an-event-handler [glm]: ./glm.md diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/resources/server/recipes/custom.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/resources/server/recipes/custom.md index 1c77d2109..1a69abc21 100644 --- a/i18n/zh-CN/docusaurus-plugin-content-docs/current/resources/server/recipes/custom.md +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/resources/server/recipes/custom.md @@ -1,49 +1,46 @@ -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`. -::: +!!! 重要 + 传递到配方中的`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`可以通过调用`Ingredient#test`进行验证。 -If the recipe has been chosen, it is then built using `#assemble` which may use data from the inputs to create the result. +如果已经选择了配方,则使用`#assemble`构建配方,该`#assemble`可以使用来自输入的数据来创建结果。 -:::tip -`#assemble` should always produce a unique `ItemStack`. If unsure whether `#assemble` does so, call `ItemStack#copy` on the result before returning. -::: +!!! 提示 + `#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 { - // Implement methods here + // 在此处实现方法 } ``` -:::note -While a record is used in the above example, it is not required to do so in your own implementation. -::: +!!! 注意 + 虽然在上面的示例中使用了一个记录,但在你自己的实现中不需要这样做。 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#BLASTING`。在高炉中进行熔炼的类型为`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. +`RecipeType`实例必须由新配方子类型中的`Recipe#getType`返回。 ```java -// For some RegistryObject EXAMPLE_TYPE -// In ExampleRecipe +// 对于某个RegistryObject EXAMPLE_TYPE +// 在ExampleRecipe中 @Override public RecipeType getType() { return EXAMPLE_TYPE.get(); @@ -53,80 +50,79 @@ 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`中的唯一实例。`RecipeSerializer`必须[已被注册][forge]。 -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. +然后,新配方子类型中的`Recipe#getSerializer`必须返回该`RecipeSerializer`实例。 ```java -// For some RegistryObject EXAMPLE_SERIALIZER -// In ExampleRecipe +// 对于某个RegistryObject EXAMPLE_SERIALIZER +// 在ExampleRecipe中 @Override public RecipeSerializer getSerializer() { return EXAMPLE_SERIALIZER.get(); } ``` -:::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 + // 自定义序列化器的注册表名称 "type": "examplemod:example_serializer", "input": { - // Some ingredient input + // 某些原料输入 }, - "data": 0, // Some data wanted for the recipe + "data": 0, // 配方所需的一些数据 "output": { - // Some stack output + // 某些物品栈输出 } } ``` -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`实例中。从那里,特定`RecipeType`的所有配方都可以通过`RecipeManager#getAllRecipesFor`获得,然后使用新实现的方法进行检查和/或提供结果。 ```java -// In some Recipe subimplementation ExampleRecipe +// 在某个Recipe子实现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 + .getAllRecipesFor(exampleRecipeType) // 获取所有配方 + .stream() // 在所有配方中查阅类型 + .filter(recipe -> recipe.matches(level, pos)) // 检查该配方输入是否合法 + .findFirst(); // 查找与输入匹配的第一个配方 } ``` -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 +[json]: https://minecraft.fandom.com/wiki/Recipe#JSON_format [manager]: ./index.md#recipe-manager [datagen]: ../../../datagen/server/recipes.md#custom-recipe-serializers diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/resources/server/recipes/incode.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/resources/server/recipes/incode.md index 26d1f7ce4..4778347c6 100644 --- a/i18n/zh-CN/docusaurus-plugin-content-docs/current/resources/server/recipes/incode.md +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/resources/server/recipes/incode.md @@ -1,37 +1,35 @@ -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 -// 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(...); @@ -41,21 +39,20 @@ public void updateAnvil(AnvilUpdateEvent event) { } ``` -The update event must be [attached] to the Forge event bus. +该更新事件必须被[绑定][attached]到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`,但可以在织布机中创建和应用自定义图案。旗帜图案可以通过[注册][registering]一个`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 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")); ``` diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/resources/server/recipes/index.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/resources/server/recipes/index.md index d41b60501..4c8c4f5c5 100644 --- a/i18n/zh-CN/docusaurus-plugin-content-docs/current/resources/server/recipes/index.md +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/resources/server/recipes/index.md @@ -1,19 +1,19 @@ -Recipes -======= +配方 +==== -Recipes are a way to transform some number of objects into other objects within a Minecraft world. Although the vanilla system deals purely with item transformations, the system as a whole can be expanded to use any object the programmer creates. +配方是一种将一定数量的对象转换为Minecraft世界中其他对象的方法。尽管原版系统纯粹处理物品转换,但整个系统可以扩展为使用程序员创建的任何对象。 -Data-Driven Recipes -------------------- +由数据驱动的配方 +----------------- -Most recipe implementations within vanilla are data driven via JSON. This means that a mod is not necessary to create a new recipe, only a [Data pack][datapack]. A full list on how to create and put these recipes within the mod's `resources` folder can be found on the [Minecraft Wiki][wiki]. +原版中的大多数配方实现都是通过JSON进行数据驱动的。这意味着创建新配方不需要模组,只需要[数据包][datapack]。关于如何创建这些配方并将其放入模组的`resources`文件夹的完整列表可以在[Minecraft Wiki][wiki]上找到。 -A recipe can be obtained within the Recipe Book as a reward for completing an [advancement][advancement]. Recipe advancements always have `minecraft:recipes/root` as their parent, to not to appear on the advancement screen. The default criteria to gain the recipe advancement is a check if the user has unlocked the recipe from using it once or receiving it through a command like `/recipe`: +可以在配方书中获得配方,作为完成[进度][advancement]的奖励。配方进度总是以`minecraft:recipes/root`为其父项,以免出现在进度屏幕上。获得配方进度的默认标准是检查用户是否已通过一次使用解锁配方或通过某个如`/recipe`的命令接收配方: ```js -// Within some recipe advancement json -"has_the_recipe": { // Criteria label - // Succeeds if examplemod:example_recipe is used +// 在某个配方进度json中 +"has_the_recipe": { // 条件标签 + // 如果examplemod:example_recipe被使用,则成功 "trigger": "minecraft:recipe_unlocked", "conditions": { "recipe": "examplemod:example_recipe" @@ -23,81 +23,78 @@ A recipe can be obtained within the Recipe Book as a reward for completing an [a "requirements": [ [ "has_the_recipe" - // ... Other criteria labels to be ORed against to unlock recipe + // ... 解锁配方所需的其他用逻辑或相连的条件标签 ] ] ``` -Data-driven recipes and their unlocking advancement can be [generated][datagen] via `RecipeProvider`. +由数据驱动的配方及其解锁的进度可以通过`RecipeProvider`[生成][datagen]。 -Recipe Manager --------------- +配方管理器 +----------- -Recipes are loaded and stored via the `RecipeManager`. Any operations relating to getting available recipe(s) are handled by this manager. There are two important methods to know of: +配方是通过`RecipeManager`加载和存储的。任何与获取可用配方相关的操作都由该管理器负责。有两种重要的方法需要了解: - Method | Description + 方法 | 描述 :---: | :--- -`getRecipeFor` | Gets the first recipe that matches the current input. -`getRecipesFor` | Gets all recipes that match the current input. +`getRecipeFor` | 获取与当前输入匹配的第一个配方。 +`getRecipesFor` | 获取与当前输入匹配的所有配方。 -Each method takes in a `RecipeType`, which denotes what method is being applied to use the recipe (crafting, smelting, etc.), a `Container` which holds the configuration of the inputs, and the current level which is passed to `Recipe#matches` along with the container. +每个方法都接受一个`RecipeType`,表示使用配方的方法(合成、烧炼等),一个保存输入配置的`Container`,以及与容器一起传递给`Recipe#matches`的当前存档。 -:::tip -Forge provides the `RecipeWrapper` utility class which extends `Container` for wrapping around `IItemHandler`s and passing them to methods which requires a `Container` parameter. +!!! 重要 + Forge提供了`RecipeWrapper`实用类,该类扩展了`Container`,用于包装`IItemHandler`,并将其传递给需要`Container`参数的方法。 -```java -// Within some method with IItemHandlerModifiable handler -recipeManger.getRecipeFor(RecipeType.CRAFTING, new RecipeWrapper(handler), level); -``` -::: + ```java + // 在具有IItemHandlerModifiable处理器的某给方法中 + recipeManger.getRecipeFor(RecipeType.CRAFTING, new RecipeWrapper(handler), level); + ``` -Additional Features -------------------- +附加特性 +-------- -Forge provides some additional behavior to the recipe schema and its implementations for greater control of the system. +Forge为配方纲要及其实现提供了一些额外的行为,以更好地控制系统。 -### Recipe ItemStack Result +### 配方的ItemStack结果 -Except for `minecraft:stonecutting` recipes, all vanilla recipe serializers expand the `result` tag to take in a full `ItemStack` as a `JsonObject` instead of just the item name and amount in some cases. +除了`minecraft:stonecutting`配方外,所有原版配方序列化器都会扩展`result`标签,以将完整的`ItemStack`作为`JsonObject`,而不是在某些情况下仅仅是物品名称和数量。 ```js -// In some recipe JSON +// 在某个配方JSON中 "result": { - // The name of the registry item to give as a result + // 要作为结果提供的注册表物品的名称 "item": "examplemod:example_item", - // The number of items to return + // 要返回的物品数量 "count": 4, - // The tag data of the stack, can also be a string + // 物品栈的标签数据,也可以是一个字符串 "nbt": { - // Add tag data here + // 在此处添加标签数据 } } ``` -:::note -The `nbt` tag can alternatively be a string containing a stringified NBT (or SNBT) for data which cannot be properly represented as a JSON object (such as `IntArrayTag`s). -::: +!!! 注意 + `nbt`标签也可以是一个字符串,其中包含无法正确表示为JSON对象(如`IntArrayTag`)的数据的字符串化NBT(或SNBT)。 -### Conditional Recipes +### 条件性配方 -Recipes and their unlocking advancement can be [loaded conditionally and defaulted][conditional] depending on what information is present (mod loaded, item exists, etc.). +配方及其所解锁的进度可以[有条件地加载和保持默认][conditional],具体取决于存在的信息(模组的被加载、物品的存在等)。 -### Larger Crafting Grids +### 更大的合成网格 -By default, vanilla declares a maximum width and height for a crafting grid to be a 3x3 square. This can be expanded by calling `ShapedRecipe#setCraftingSize` with the new width and height in `FMLCommonSetupEvent`. +默认情况下,原版声明合成网格的最大宽度和高度为3x3正方形。这可以通过在`FMLCommonSetupEvent`中使用新的宽度和高度调用`ShapedRecipe#setCraftingSize`来扩展。 -:::caution -`ShapedRecipe#setCraftingSize` is **NOT** thread-safe. As such, it should be enqueued to the synchronous work queue via `FMLCommonSetupEvent#enqueueWork`. -::: +!!! 警告 + `ShapedRecipe#setCraftingSize`**不**是线程安全的。因此,它应该通过`FMLCommonSetupEvent#enqueueWork`排入同步工作队列。 -Larger crafting grids in recipes can be [data generated][datagen]. +配方中较大的合成网格可以是[数据生成的][datagen]。 -### Ingredient Types +### 原料类型 -A few additional [ingredient types][ingredients] are added to allow recipes to have inputs which check tag data or combine multiple ingredients into a single input checker. +一些额外的[原料类型][ingredients]被添加,以允许配方具有检查标签数据或将多种原料组合到单个输入检查器中的输入。 -[datapack]: https://minecraft.wiki/w/Data_pack -[wiki]: https://minecraft.wiki/w/Recipe +[datapack]: https://minecraft.fandom.com/wiki/Data_pack +[wiki]: https://minecraft.fandom.com/wiki/Recipe [advancement]: ../advancements.md [datagen]: ../../../datagen/server/recipes.md [cap]: ../../../datastorage/capabilities.md diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/resources/server/recipes/ingredients.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/resources/server/recipes/ingredients.md index 7d5d3629d..6ca6ab75a 100644 --- a/i18n/zh-CN/docusaurus-plugin-content-docs/current/resources/server/recipes/ingredients.md +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/resources/server/recipes/ingredients.md @@ -1,30 +1,30 @@ -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`是基于物品的输入的predicate处理器,用于检查某个`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 +// 对于某个输入 [ - // At least one of these ingredients must match to succeed + // 这些原料中必须至少有一种必须匹配才能成功 { - // Ingredient + // 原料 }, { - // Custom ingredient + // 自定义原料 "type": "examplemod:example_ingredient" } ] @@ -32,30 +32,30 @@ 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 +// 对于某个输入 { "type": "forge:nbt", "item": "examplemod:example_item", "nbt": { - // Add nbt data (must match exactly what is on the stack) + // 添加nbt数据(必须与物品栈上的数据完全匹配) } } ``` ### 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 +// 对于某个输入 { "type": "forge:partial_nbt", - // Either 'item' or 'items' must be specified - // If both are specified, only 'item' will be read + // 'item'或'items'必须被指定 + // 如果都指定了,那么只有'item'会被读取 "item": "examplemod:example_item", "items": [ "examplemod:example_item", @@ -64,11 +64,11 @@ Though they are functionally identical, Compound ingredients replaces the way on ], "nbt": { - // Checks only for equivalency on 'key1' and 'key2' - // All other keys in the stack will not be checked + // 仅检查'key1'和'key2'的等效性 + // 不会检查物品栈中的所有其他键 "key1": "data1", "key2": { - // Data 2 + // 数据2 } } } @@ -76,20 +76,20 @@ 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 +// 对于某个输入 { "type": "forge:intersection", - // All of these ingredients must return true to succeed + // 所有这些原料都必须返回true才能成功 "children": [ { - // Ingredient 1 + // 原料1 }, { - // Ingredient 2 + // 原料2 } // ... ] @@ -98,79 +98,77 @@ 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 +// 对于某个输入 { "type": "forge:difference", "base": { - // Ingredient the stack is in + // 该物品栈所存在的原料 }, "subtracted": { - // Ingredient the stack is NOT in + // 该物品栈所不存在的原料 } } ``` -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 +### 原料的子类 -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 | 返回用于读取和写入原料的[serializer]。 +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`,以便与[数据生成][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`. +之后,应声明一个静态实例来保存初始化的序列化器,然后在`RecipeSerializer`的`RegisterEvent`期间或在`FMLCommonSetupEvent`期间使用`CraftingHelper#register`进行注册。`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 -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 +[recipes]: https://minecraft.fandom.com/wiki/Recipe#List_of_recipe_types [nbt]: #strictnbtingredient [serializer]: #iingredientserializer [compound]: #compoundingredient diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/resources/server/tags.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/resources/server/tags.md index 4a5883d9a..a6f21ed0f 100644 --- a/i18n/zh-CN/docusaurus-plugin-content-docs/current/resources/server/tags.md +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/resources/server/tags.md @@ -1,15 +1,15 @@ -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来附加或覆盖在其他域(如原版)中声明的标签。 +例如,要将你自己的模组的树苗添加到原版树苗标签中,你可以在`/data/minecraft/tags/blocks/saplings.json`中指定它,如果`replace`选项为false,原版将在重载时将所有内容合并到一个标签中。 +如果`replace`为true,那么指定`replace`的json之前的所有条目都将被删除。 +列出的不存在的值将导致标签出错,除非使用`id`字符串和设置为false的`required`布尔值列出该值,如以下示例所示: ```js { @@ -25,48 +25,47 @@ 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. +有关基本语法的描述,请参阅[原版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扩展。 +你可以声明一个与`values`数组格式相同的`remove`数组。此处列出的任何值都将从标签中删除。这相当于原版`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 +#### 自定义注册表 -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`. +自定义注册表可以在分别通过`#createTagKey`或`#createOptionalTagKey`构造其`DeferredRegister`时创建标签。然后,可以使用通过调用`DeferredRegister#makeRegistry`获得的`IForgeRegistry`来检查它们的标签或注册表对象。 -### Referencing Tags +### 引用标签 -There are four methods of creating a tag wrapper: +创建标签包装有四种方法: -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`. +`*Tags#create` | `BannerPattern`、`Biome`、`Block`、`CatVariant`、`DamageType`、`EntityType`、`FlatLevelGeneratorPreset`、`Fluid`、`GameEvent`、`Instrument`、`Item`、`PaintingVariant`、`PoiType`、`Structure`以及`WorldPreset`,其中`*`代表这些类型之一。 +`ITagManager#createTagKey` | 由Forge包装的原版注册表,可从`ForgeRegistries`取得。 +`DeferredRegister#createTagKey` | 自定义的Forge注册表。 +`TagKey#create` | 无Forge包装的原版注册表,可从`Registry`取得。 -Registry objects can check their tags or registry objects either through their `Holder` or through `ITag`/`IReverseTag` for vanilla or forge registry objects respectively. +注册表对象可以通过其`Holder`或通过`ITag`/`IReverseTag`分别检查其标签或注册表对象是否为原版或Forge注册表对象。 -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`. +原版注册表对象可以使用`Registry#getHolder`或`Registry#getHolderOrThrow`获取其关联的持有者,然后使用`Holder#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. +Forge注册表对象可以使用`ITagManager#getTag`或`ITagManager#getReverseTag`获取其标签定义,然后分别使用`ITag#contains`或`IReverseTag#containsTag`比较注册表对象是否有标签。 -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. +持有标签的注册表对象在其注册表对象或状态感知类中包含一个名为`#is`的方法,以检查该对象是否属于某个标签。 -As an example: +举一个例子: ```java public static final TagKey myItemTag = ItemTags.create(new ResourceLocation("mymod", "myitemgroup")); @@ -74,7 +73,7 @@ public static final TagKey myPotionTag = ForgeRegistries.POTIONS.tags(). public static final TagKey myVillagerTypeTag = TagKey.create(Registries.VILLAGER_TYPE, new ResourceLocation("mymod", "myvillagertypegroup")); -// In some method: +// 在某个方法中: ItemStack stack = /*...*/; boolean isInItemGroup = stack.is(myItemTag); @@ -86,36 +85,36 @@ 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.). +* 如果有适合你的方块或物品的原版标签,请将其添加到该标签中。请参阅[原版标签列表][taglist]。 +* 如果有一个Forge标签适合你的方块或物品,请将其添加到该标签中。Forge声明的标签列表可以在[GitHub][forgetags]上看到。 +* 如果有一组你认为应该由社区共享的东西,请使用`forge`命名空间,而不是你的mod id。 +* 标签命名约定应遵循原版约定。特别是,物品和方块组别是复数而不是单数(例如`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. +* 对于配方,标签可以直接以原版配方格式使用(见下文)。 +* 有关代码中的匹配物品,请参阅上面的章节。 +* 如果你要声明一种新类型的物品组别,请遵循以下几个命名约定: + * 使用`domain:type/material`。当名称是所有模组开发者都应该采用的通用名称时,请使用`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. +原版直接支持标签。有关用法的详细信息,请参阅[配方][recipes]和[进度][advancements]的原版wiki页面。 [datapack]: ./index.md -[tags]: https://minecraft.wiki/w/Tag#JSON_format -[taglist]: https://minecraft.wiki/w/Tag#List_of_tags +[tags]: https://minecraft.fandom.com/wiki/Tag#JSON_format +[taglist]: https://minecraft.fandom.com/wiki/Tag#List_of_tags [forgetags]: https://github.com/MinecraftForge/MinecraftForge/tree/1.19.x/src/generated/resources/data/forge/tags -[recipes]: https://minecraft.wiki/w/Recipe#JSON_format -[advancements]: https://minecraft.wiki/w/Advancement +[recipes]: https://minecraft.fandom.com/wiki/Recipe#JSON_format +[advancements]: https://minecraft.fandom.com/wiki/Advancement From dab8ee296389760a92f167ea41fca5e920e67e18 Mon Sep 17 00:00:00 2001 From: src_resources Date: Tue, 10 Oct 2023 12:16:59 +0800 Subject: [PATCH 04/15] Remove irrelevant _category_.json files Signed-off-by: src_resources --- .../current/advanced/_category_.json | 3 --- .../current/concepts/_category_.json | 3 --- .../current/datastorage/_category_.json | 3 --- .../current/gameeffects/_category_.json | 3 --- .../current/gettingstarted/_category_.json | 4 ---- .../current/gui/_category_.json | 3 --- .../current/legacy/_category_.json | 3 --- .../current/misc/_category_.json | 3 --- .../current/rendering/_category_.json | 3 --- .../current/resources/_category_.json | 3 --- 10 files changed, 31 deletions(-) delete mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/advanced/_category_.json delete mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/concepts/_category_.json delete mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/datastorage/_category_.json delete mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/gameeffects/_category_.json delete mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/gettingstarted/_category_.json delete mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/gui/_category_.json delete mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/legacy/_category_.json delete mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/misc/_category_.json delete mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/rendering/_category_.json delete mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current/resources/_category_.json diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/advanced/_category_.json b/i18n/zh-CN/docusaurus-plugin-content-docs/current/advanced/_category_.json deleted file mode 100644 index c392eef49..000000000 --- a/i18n/zh-CN/docusaurus-plugin-content-docs/current/advanced/_category_.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "label": "Advanced Topics" -} \ No newline at end of file diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/concepts/_category_.json b/i18n/zh-CN/docusaurus-plugin-content-docs/current/concepts/_category_.json deleted file mode 100644 index 5e1f44f64..000000000 --- a/i18n/zh-CN/docusaurus-plugin-content-docs/current/concepts/_category_.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "label": "Concepts" -} \ No newline at end of file diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/datastorage/_category_.json b/i18n/zh-CN/docusaurus-plugin-content-docs/current/datastorage/_category_.json deleted file mode 100644 index 7b4467f7c..000000000 --- a/i18n/zh-CN/docusaurus-plugin-content-docs/current/datastorage/_category_.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "label": "Data Storage" -} \ No newline at end of file diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/gameeffects/_category_.json b/i18n/zh-CN/docusaurus-plugin-content-docs/current/gameeffects/_category_.json deleted file mode 100644 index 818267c27..000000000 --- a/i18n/zh-CN/docusaurus-plugin-content-docs/current/gameeffects/_category_.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "label": "Game Effects" -} \ No newline at end of file diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/gettingstarted/_category_.json b/i18n/zh-CN/docusaurus-plugin-content-docs/current/gettingstarted/_category_.json deleted file mode 100644 index da8a48332..000000000 --- a/i18n/zh-CN/docusaurus-plugin-content-docs/current/gettingstarted/_category_.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "label": "Getting Started with Neo", - "position": 1 -} \ No newline at end of file diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/gui/_category_.json b/i18n/zh-CN/docusaurus-plugin-content-docs/current/gui/_category_.json deleted file mode 100644 index 59cfbca67..000000000 --- a/i18n/zh-CN/docusaurus-plugin-content-docs/current/gui/_category_.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "label": "GUIs" -} \ No newline at end of file diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/legacy/_category_.json b/i18n/zh-CN/docusaurus-plugin-content-docs/current/legacy/_category_.json deleted file mode 100644 index da5b88dbc..000000000 --- a/i18n/zh-CN/docusaurus-plugin-content-docs/current/legacy/_category_.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "label": "Legacy" -} \ No newline at end of file diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/misc/_category_.json b/i18n/zh-CN/docusaurus-plugin-content-docs/current/misc/_category_.json deleted file mode 100644 index 26fda970d..000000000 --- a/i18n/zh-CN/docusaurus-plugin-content-docs/current/misc/_category_.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "label": "Miscellaneous" -} \ No newline at end of file diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/rendering/_category_.json b/i18n/zh-CN/docusaurus-plugin-content-docs/current/rendering/_category_.json deleted file mode 100644 index 461293ccb..000000000 --- a/i18n/zh-CN/docusaurus-plugin-content-docs/current/rendering/_category_.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "label": "Rendering" -} \ No newline at end of file diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/resources/_category_.json b/i18n/zh-CN/docusaurus-plugin-content-docs/current/resources/_category_.json deleted file mode 100644 index 678d589f3..000000000 --- a/i18n/zh-CN/docusaurus-plugin-content-docs/current/resources/_category_.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "label": "Resources" -} \ No newline at end of file From 1756eba6f99efc364c86ae1561d9502aa1b41074 Mon Sep 17 00:00:00 2001 From: src_resources Date: Tue, 10 Oct 2023 12:22:48 +0800 Subject: [PATCH 05/15] Append Chinese translations for JSON files Signed-off-by: src_resources --- i18n/zh-CN/code.json | 408 ++++++++++++++++++ .../options.json | 14 + .../current.json | 14 + .../version-5.x.json | 18 + .../current.json | 98 +++++ .../version-1.19.x.json | 6 + .../docusaurus-theme-classic/footer.json | 34 ++ .../docusaurus-theme-classic/navbar.json | 22 + 8 files changed, 614 insertions(+) create mode 100644 i18n/zh-CN/code.json create mode 100644 i18n/zh-CN/docusaurus-plugin-content-blog/options.json create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs-neogradle/current.json create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs-neogradle/version-5.x.json create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/current.json create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs/version-1.19.x.json create mode 100644 i18n/zh-CN/docusaurus-theme-classic/footer.json create mode 100644 i18n/zh-CN/docusaurus-theme-classic/navbar.json diff --git a/i18n/zh-CN/code.json b/i18n/zh-CN/code.json new file mode 100644 index 000000000..05e4822ba --- /dev/null +++ b/i18n/zh-CN/code.json @@ -0,0 +1,408 @@ +{ + "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.NotFound.title": { + "message": "找不到页面", + "description": "The title of the 404 page" + }, + "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.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.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.caution": { + "message": "警告", + "description": "The default label used for the Caution admonition (:::caution)" + }, + "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.tags.tagsListLabel": { + "message": "标签:", + "description": "The label alongside a tag list" + }, + "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.toggleCollapsedCategoryAriaLabel": { + "message": "打开/收起侧边栏菜单「{label}」", + "description": "The ARIA label to toggle the collapsible 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.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.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.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.SearchBar.seeAll": { + "message": "查看全部 {count} 个结果" + }, + "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.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.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" + } +} diff --git a/i18n/zh-CN/docusaurus-plugin-content-blog/options.json b/i18n/zh-CN/docusaurus-plugin-content-blog/options.json new file mode 100644 index 000000000..9239ff706 --- /dev/null +++ b/i18n/zh-CN/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-CN/docusaurus-plugin-content-docs-neogradle/current.json b/i18n/zh-CN/docusaurus-plugin-content-docs-neogradle/current.json new file mode 100644 index 000000000..70b83951f --- /dev/null +++ b/i18n/zh-CN/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 配置选项", + "description": "The label for category ForgeGradle Configurations in sidebar ngSidebar" + }, + "sidebar.ngSidebar.category.Dependencies": { + "message": "依赖", + "description": "The label for category Dependencies in sidebar ngSidebar" + } +} diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs-neogradle/version-5.x.json b/i18n/zh-CN/docusaurus-plugin-content-docs-neogradle/version-5.x.json new file mode 100644 index 000000000..8a8d0d233 --- /dev/null +++ b/i18n/zh-CN/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-CN/docusaurus-plugin-content-docs/current.json b/i18n/zh-CN/docusaurus-plugin-content-docs/current.json new file mode 100644 index 000000000..92ca56ce2 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current.json @@ -0,0 +1,98 @@ +{ + "version.label": { + "message": "1.20.x", + "description": "The label for version current" + }, + "sidebar.mainSidebar.category.Getting Started with Neo": { + "message": "入门Neo", + "description": "The label for category Getting Started with Neo in sidebar mainSidebar" + }, + "sidebar.mainSidebar.category.Advanced Topics": { + "message": "进阶主题", + "description": "The label for category Advanced Topics in sidebar mainSidebar" + }, + "sidebar.mainSidebar.category.方块实体": { + "message": "方块实体", + "description": "The label for category 方块实体 in sidebar mainSidebar" + }, + "sidebar.mainSidebar.category.方块": { + "message": "方块", + "description": "The label for category 方块 in sidebar mainSidebar" + }, + "sidebar.mainSidebar.category.Concepts": { + "message": "核心概念", + "description": "The label for category Concepts in sidebar mainSidebar" + }, + "sidebar.mainSidebar.category.数据生成": { + "message": "数据生成", + "description": "The label for category 数据生成 in sidebar mainSidebar" + }, + "sidebar.mainSidebar.category.Client": { + "message": "客户端", + "description": "The label for category Client in sidebar mainSidebar" + }, + "sidebar.mainSidebar.category.Server": { + "message": "服务端", + "description": "The label for category Server in sidebar mainSidebar" + }, + "sidebar.mainSidebar.category.Data Storage": { + "message": "数据储存", + "description": "The label for category Data Storage in sidebar mainSidebar" + }, + "sidebar.mainSidebar.category.Game Effects": { + "message": "游戏特效", + "description": "The label for category Game Effects in sidebar mainSidebar" + }, + "sidebar.mainSidebar.category.GUIs": { + "message": "图形用户界面", + "description": "The label for category GUIs in sidebar mainSidebar" + }, + "sidebar.mainSidebar.category.物品": { + "message": "物品", + "description": "The label for category 物品 in sidebar mainSidebar" + }, + "sidebar.mainSidebar.category.Legacy": { + "message": "旧版本", + "description": "The label for category Legacy in sidebar mainSidebar" + }, + "sidebar.mainSidebar.category.Miscellaneous": { + "message": "杂项功能", + "description": "The label for category Miscellaneous in sidebar mainSidebar" + }, + "sidebar.mainSidebar.category.网络": { + "message": "网络", + "description": "The label for category 网络 in sidebar mainSidebar" + }, + "sidebar.mainSidebar.category.Rendering": { + "message": "渲染", + "description": "The label for category Rendering in sidebar mainSidebar" + }, + "sidebar.mainSidebar.category.Model Extensions": { + "message": "模型扩展", + "description": "The label for category Model Extensions in sidebar mainSidebar" + }, + "sidebar.mainSidebar.category.自定义模型加载器": { + "message": "自定义模型加载器", + "description": "The label for category 自定义模型加载器 in sidebar mainSidebar" + }, + "sidebar.mainSidebar.category.Resources": { + "message": "游戏资源", + "description": "The label for category Resources in sidebar mainSidebar" + }, + "sidebar.mainSidebar.category.资源包": { + "message": "资源包", + "description": "The label for category 资源包 in sidebar mainSidebar" + }, + "sidebar.mainSidebar.category.模型": { + "message": "模型", + "description": "The label for category 模型 in sidebar mainSidebar" + }, + "sidebar.mainSidebar.category.数据包": { + "message": "数据包", + "description": "The label for category 数据包 in sidebar mainSidebar" + }, + "sidebar.mainSidebar.category.配方": { + "message": "配方", + "description": "The label for category 配方 in sidebar mainSidebar" + } +} diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.19.x.json b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.19.x.json new file mode 100644 index 000000000..84a6c0928 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.19.x.json @@ -0,0 +1,6 @@ +{ + "version.label": { + "message": "1.19.x", + "description": "The label for version 1.19.x" + } +} diff --git a/i18n/zh-CN/docusaurus-theme-classic/footer.json b/i18n/zh-CN/docusaurus-theme-classic/footer.json new file mode 100644 index 000000000..191b05d3b --- /dev/null +++ b/i18n/zh-CN/docusaurus-theme-classic/footer.json @@ -0,0 +1,34 @@ +{ + "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.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-CN/docusaurus-theme-classic/navbar.json b/i18n/zh-CN/docusaurus-theme-classic/navbar.json new file mode 100644 index 000000000..d0c194698 --- /dev/null +++ b/i18n/zh-CN/docusaurus-theme-classic/navbar.json @@ -0,0 +1,22 @@ +{ + "title": { + "message": "NeoForged 文档", + "description": "The title in the navbar" + }, + "logo.alt": { + "message": "NeoForged Logo", + "description": "The alt text of navbar logo" + }, + "item.label.Getting Started": { + "message": "入门", + "description": "Navbar item with label Getting Started" + }, + "item.label.NeoGradle Documentation": { + "message": "NeoGradle 文档", + "description": "Navbar item with label NeoGradle Documentation" + }, + "item.label.GitHub": { + "message": "GitHub", + "description": "Navbar item with label GitHub" + } +} From 787fe2d07d2667a95dce00539b28125999b5e673 Mon Sep 17 00:00:00 2001 From: src_resources Date: Tue, 10 Oct 2023 22:28:39 +0800 Subject: [PATCH 06/15] Import existing Chinese translations of NeoGradle Documentation (FG6) In spite of some abnormal settings with Docusaurus which make the translations unloaded into the webpage, still add the files and leave the fixing chores behind to complete. Signed-off-by: src_resources --- .../current/configuration/advanced.md | 16 ++ .../current/configuration/index.md | 104 +++++++++++++ .../current/configuration/runs.md | 144 ++++++++++++++++++ .../current/contributing.md | 4 + .../current/dependencies/index.md | 100 ++++++++++++ .../current/dependencies/jarinjar.md | 105 +++++++++++++ .../current/gettingstarted/index.md | 73 +++++++++ .../current/index.md | 73 +++++++++ .../current/porting/5.x_to_6.0.md | 94 ++++++++++++ .../current/porting/eclipse_launch_groups.png | Bin 0 -> 11855 bytes 10 files changed, 713 insertions(+) create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs-neogradle/current/configuration/advanced.md create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs-neogradle/current/configuration/index.md create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs-neogradle/current/configuration/runs.md create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs-neogradle/current/contributing.md create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs-neogradle/current/dependencies/index.md create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs-neogradle/current/dependencies/jarinjar.md create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs-neogradle/current/gettingstarted/index.md create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs-neogradle/current/index.md create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs-neogradle/current/porting/5.x_to_6.0.md create mode 100644 i18n/zh-CN/docusaurus-plugin-content-docs-neogradle/current/porting/eclipse_launch_groups.png diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs-neogradle/current/configuration/advanced.md b/i18n/zh-CN/docusaurus-plugin-content-docs-neogradle/current/configuration/advanced.md new file mode 100644 index 000000000..09c9232f6 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs-neogradle/current/configuration/advanced.md @@ -0,0 +1,16 @@ +进阶配置 +======== + +ForgeGradle包含一些特定或细微差别的配置技术,具体取决于你的构建项目的复杂性。 + +重新混淆源集 +----------- + +默认情况下,`reobf*`abd`rename*`任务只包含主源集classpath上的文件。要在不同的classpath上重新生成文件,需要将它们添加到任务中的`libraries`属性中。 + +```gradle +// 添加另一个源集的classpath到'reobf'任务。 +tasks.withType('reobfJar') { + libraries.from sourceSets.api.classpath +} +``` diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs-neogradle/current/configuration/index.md b/i18n/zh-CN/docusaurus-plugin-content-docs-neogradle/current/configuration/index.md new file mode 100644 index 000000000..d1b7ea28c --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs-neogradle/current/configuration/index.md @@ -0,0 +1,104 @@ +ForgeGradle配置 +=============== + +ForgeGradle有许多配置可以改变开发环境的配置方式。大多数配置都是使用`minecraft`块设置的;然而,其他一些可以在`dependencies`块中指定,或者修改构建的`jar`,例如`reobfJar`。 + +启用访问转换器 +------------- + +[访问转换器][at]可以扩大可见性或修改Minecraft类、方法和字段的`final`标志。要在生产环境中启用访问转换器,可以将`accessTransformer`设置为有问题的配置文件: + +```gradle +minecraft { + // ... + + // 添加相对于项目目录的访问转换器文件 + accessTransformer = project.file('src/main/resources/META-INF/accesstransformer.cfg') +} +``` + +!!! 重要 + 虽然开发环境中的访问转换器可以从用户指定的任何位置读取,但在生产中,该文件只能从`META-INF/accesstransformer.cfg`读取。 + +人类可读的映射 +------------- + +Minecraft的源代码被混淆了。因此,所有类、方法和字段都具有机器生成的名称,而没有包结构。同时,由于本地变量表的存储方式,函数的局部变量名变成了雪人(`☃`)。使用模糊名称创建模组很困难,因为反向工程很乏味,可能会在不同版本之间更改,并且虽然可能在语言本身中无效,但在字节码中却不是无效的。 + +为了解决最后两个问题,Forge通过[ForgeAutoRenamingTool][fart]将每个类、方法、字段和参数模糊地映射到一个唯一的标识符,即SRG名称。当游戏由用户客户端运行时,SRG映射在生产中使用。 + +为了便于开发,ForgeGradle允许用户选择一个映射集来应用于SRG映射之上,我们将其称为人类可读的映射。ForgeGradle知道如何通过`reobf*`任务将模组jar转换为SRG映射,以便在生产中使用。 + +可以通过设置`minecraft`块中的`mappings`字段来指定所使用的映射集。`mappings`字段接受两个参数:`channel`是映射集的类型,`version`是要应用的映射集的版本。 + +目前,ForgeGradle中内置了三个默认映射集: + +* `official` - 这使用Mojang提供的映射集。这些映射没有参数名称,仅从1.14开始存在。 +* `stable` - 这使用MCP生成的映射集。这些通常是不完整的,并且自1.17起不再存在。 +* `snapshot` - 这也是MCP生成的映射集,类似于程序的隔夜构建。这些通常也是不完整的,并且从1.17起不再存在。 + +!!! 注意 + 生产中使用的类名从1.17之前的`stable`到1.17之后的`official`。 + +```gradle +mappings { + // 将映射设置为在Minecraft 1.19.4中使用来自Mojang的映射。 + mappings channel: 'official', version: '1.19.4' + + // ... +} +``` + +### Parchment + +Parchment是ParchmentMC维护的一个官方项目,它在`official`映射集之上提供开放的、社区源代码的参数名称和javadoc。你可以在[他们的网站][parchment]上学习如何设置和使用Parchment映射。 + +准备运行任务 +----------- + +运行任务(`run*`)有两个独立的管道,这取决于它们是通过`gradlew`还是运行配置执行的。默认情况下,有两个任务用于准备工作区以便执行: + +首先,有在`run*`任务之前执行的`prepare*`任务,并确保为游戏准备映射文件。`prepare*Compile`任务通常仅作为`run*`任务的依赖项来执行,以确保游戏在运行之前已编译。 + +如果你的IDE是Eclipse或IntelliJ IDEA,则可以将运行配置配置为在启动游戏之前执行`prepare*`任务,方法是分别将`enableEclipsePrepareRuns`或`enableIdeaPrepareRuns`设置为`true`。这将允许你在IDE启动游戏之前调用自定义Gradle任务。 + +```gradle +minecraft { + // ... + + // 为运行配置启用'prepare*'任务 + enableEclipsePrepareRuns true + enableIdeaPrepareRuns true +} +``` + +### 复制IDE资源 + +`copyIdeResources`属性可用于将`processResources`任务配置的资源复制到IDE的资源输出目录。这允许不调用Gradle(IntelliJ配置为使用IDEA运行器或Eclipse)的IDE运行配置使用构建脚本可配置资源。通常,在替换`mods.toml`等文件中的值时,需要启用此属性。 +这仅通过`copyEclipseResources`和`copyIntellijResources`任务分别适用于Eclipse和IntelliJ IDEA。 + +```gradle +minecraft { + // ... + + // 将文件从'processResources'复制到IDE的资源输出目录 + copyIdeResources true +} +``` + +### 运行配置文件夹 + +如果`generateRunFolders`设置为`true`,则可以将运行配置排序到文件夹中。这将读取特定[运行配置][run]中设置的`folderName`属性,以确定组织性的结构。 + +```gradle +minecraft { + // ... + + // 如果为true,运行配置将按其'folderName'分组到文件夹中 + generateRunFolders true +} +``` + +[at]: https://docs.minecraftforge.net/en/latest/advanced/accesstransformers/ +[fart]: https://github.com/MinecraftForge/ForgeAutoRenamingTool +[parchment]: https://parchmentmc.org/docs/getting-started diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs-neogradle/current/configuration/runs.md b/i18n/zh-CN/docusaurus-plugin-content-docs-neogradle/current/configuration/runs.md new file mode 100644 index 000000000..1176362f0 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs-neogradle/current/configuration/runs.md @@ -0,0 +1,144 @@ +运行配置 +======== + +运行配置定义了游戏实例的运行方式。这包括参数、工作目录、任务名称等。运行配置在`minecraft.runs`块中定义。虽然默认情况下没有配置任何运行,但[Forge][userdev]确实提供了`client`、`server`、`data`或`gameTestServer`的配置。 + +```gradle +minecraft { + // ... + runs { + // 在此处配置运行 + } +} +``` + +可以使用闭包添加类似于任何`NamedDomainObjectContainer`的运行配置。 + +```gradle +// 在minecraft块内部 +runs { + // 创建或配置名为'client'的运行配置 + client { + // 配置运行 + } +} +``` + +以下配置属性可用: + +```gradle +// 在runs块内部 +client { + // Gradle运行任务的名称, + // 默认为'runX',其中X是容器名称 + taskName 'runThing' + + // 设置程序启动的入口点 + // Forge将userdev main设置为'cpw.mods.bootstraplauncher.BootstrapLauncher' + main 'com.example.Main' + + // 设置该配置的工作目录 + // 默认为'./run' + workingDirectory 'run' + + // 为IntelliJ IDEA设置要为其运行配置的模块的名称 + // 默认为'.main' + ideaModule 'example.main' + + // 设置运行配置应添加到的文件夹的名称 + // 默认为项目名称 + folderName 'example' + + // 设置这是否应该运行一个Minecraft客户端 + // 若未指定,将进行以下检查 + // - 是否存在包含'client'的环境变量'thing' + // - 配置名称是否存在'client' + // - main是否设置为'mcp.client.Start' + // - main是否设置为'net.minecraft.client.main.Main' + client true + + // 设置该配置应继承自的父级 + parent runs.example + + // 设置该配置的子级 + children runs.child + + // 合并此配置并指定是否覆盖现有属性 + merge runs.server, true + + // 如果不为false,则会将父级的参数与此配置合并 + inheritArgs false + + // 如果不为false,则会将父级的JVM参数与此配置合并 + inheritJvmArgs false + + // 将一个源集(sourceset)添加到classpath + // 若未指定,则添加sourceSet.main + source sourceSets.api + + // 为该运行设置一个环境变量 + // 值将作为一个文件(file)或一个字符串(string)被解释 + environment 'envKey', 'value' + + // 设置一个系统属性 + // 值将作为一个文件(file)或一个字符串(string)被解释 + property 'propKey', 'value' + + // 设置将传递给应用的参数 + // 可用'args'指定多个 + arg 'hello' + + // 设置一个JVM参数 + // 可用'jvmArgs'指定多个 + jvmArg '-Xmx2G' + + // 设置一个令牌(token) + // 目前,下列令牌被使用: + // - runtime_classpath + // - minecraft_classpath + token 'tokenKey', 'value' + + // 设置一个被惰性初始化的令牌 + // 通常应替代'token'使用,例如当令牌解析Gradle配置时 + lazyToken('lazyTokenKey') { + 'value' + } + + // 如果为true,则编译所有项目,而不是当前任务的项目 + // 这仅由IntelliJ IDEA使用 + buildAllProjects false +} +``` + +!!! 提示 + 你可以在[MinecraftForge构建脚本][buildscript]中看到所有配置的userdev属性的列表。 + +模组配置 +-------- + +当前环境中的模组可以使用运行配置中的`mods`块添加。Mod块也是`NamedDomainObjectContainer`。 + +```gradle +// 在runs块中 +client { + // ... + + mods { + other_mod { + // ... + } + + // 配置'example'模组 + example { + // 将一个源集添加到模组的源 + source sourceSets.main + + // 合并此配置并指定是否覆盖现有属性 + merge mods.other_mod, true + } + } +} +``` + +[userdev]: https://github.com/MinecraftForge/MinecraftForge/blob/42115d37d6a46856e3dc914b54a1ce6d33b9872a/build.gradle#L374-L430 +[buildscript]: https://github.com/MinecraftForge/MinecraftForge/blob/d4836bc769da003528b6cebc7e677a5aa23a8228/build.gradle#L434-L470 diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs-neogradle/current/contributing.md b/i18n/zh-CN/docusaurus-plugin-content-docs-neogradle/current/contributing.md new file mode 100644 index 000000000..746e478e1 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs-neogradle/current/contributing.md @@ -0,0 +1,4 @@ +向文档做出贡献 +================================== + +贡献指南尚在编写中。你可以等待一段时间再做出贡献。 diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs-neogradle/current/dependencies/index.md b/i18n/zh-CN/docusaurus-plugin-content-docs-neogradle/current/dependencies/index.md new file mode 100644 index 000000000..e2e408377 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs-neogradle/current/dependencies/index.md @@ -0,0 +1,100 @@ +依赖 +==== + +依赖关系不仅用于开发模组之间的互操作性或为游戏添加额外的库,还决定了为哪个版本的Minecraft开发。这将提供关于如何修改`repositories`和`dependencies`块以将依赖项添加到开发环境的快速概述。 + +> 这不会深入解释Gradle的概念。强烈建议你在继续之前阅读[Gradle依赖管理指南][guide]。 + +`minecraft` +----------- + +`minecraft`依赖项指定要使用的Minecraft版本,并且必须包含在`dependencies`块中。除了具有组`net.minecraft`的artifact外,任何artifact都将应用随依赖项提供的任何补丁。这通常只指定`net.minecraftforge:forge` artifact。 + +```gradle +dependencies { + // Forge artifact的版本遵循格式'-' + // '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' +} +``` + +模组依赖 +-------- + +在一个典型的开发环境中,Minecraft被降级为中间映射,用于生产,然后转换为模组指定的任何[人类可读的映射][mappings]。模组artifact在构建时会被混淆为生产映射(SRG),因此无法直接用作Gradle依赖项。 + +因此,在添加到预期配置之前,所有模组依赖项都需要用`fg.deobf`包装。 + +```gradle +dependencies { + // 假设我们已经指定了'minecraft'依赖 + + // 假设我们有某个可从一个指定仓库获取的artifact 'examplemod' + implementation fg.deobf('com.example:examplemod:1.0') +} +``` + +### 本地模组依赖 + +如果你试图依赖的模组在Maven存储库上不可用(例如,[Maven Central][central]、[CurseMaven]、[Modrinth]),你可以使用[平坦目录][flat directory]添加模组依赖: + +```gradle +repositories { + // 将项目目录中的'libs'文件夹添加为一个平坦目录 + flatDir { + dir 'libs' + } +} + +dependencies { + // ... + + // 给定某个::: + // 具有指定的扩展名 + // 平坦目录中的项目将按以下顺序解决: + // - -. + // - --. + // - . + // - -. + + // 如果明确指定了一个分类器(classifier) + // 具有分类器的artifact将具有优先级: + // - examplemod-1.0-api.jar + // - examplemod-api.jar + // - examplemod-1.0.jar + // - examplemod.jar + implementation fg.deobf('com.example:examplemod:1.0:api') +} +``` + +!!! 注意 + 组名称可以是任何内容,但对于平坦目录条目,不能为空,因为解析artifact文件时不会检查这些条目。 + +非Minecraft依赖 +--------------- + +在开发环境中,Forge默认不会加载非Minecraft依赖项。为了让Forge识别非Minecraft依赖项,必须将它们添加到`minecraftLibrary`配置中。`minecraftLibrary`的工作原理类似于Gradle中的`implementation`配置,在编译时和运行时应用。 + +```gradle +dependencies { + // ... + + // 假设有一个非Minecraft库'dummy-lib' + minecraftLibrary 'com.dummy:dummy-lib:1.0' +} +``` + +> 默认情况下,添加到开发环境中的非Minecraft依赖项不会包含在构建的artifact中!你必须使用[Jar-In-Jar][jij]在构建时将依赖项包含在artifact中。 + +[guide]: https://docs.gradle.org/8.1.1/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/8.1.1/userguide/declaring_repositories.html#sub:flat_dir_resolver + +[jij]: ./jarinjar.md diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs-neogradle/current/dependencies/jarinjar.md b/i18n/zh-CN/docusaurus-plugin-content-docs-neogradle/current/dependencies/jarinjar.md new file mode 100644 index 000000000..663bed9a5 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs-neogradle/current/dependencies/jarinjar.md @@ -0,0 +1,105 @@ +Jar-in-Jar +========== + +Jar-in-Jar是从模组的jar中加载模组依赖项的一种方式。为了实现这一点,Jar-in-Jar在构建时在`META-INF/jarjar/metadata.json`中生成一个元数据json,其中包含要从jar中加载的artifact。 + +Jar-in-Jar是一个完全可选的系统,可以在`minecraft`块之前使用`jarJar#enable`来启用。这将把`jarJar`配置中的所有依赖项都包含到`jarJar`任务中。你能够以类似于其他jar任务的方式来配置该任务: + +```gradle +// 在build.gradle中 + +// 为你的模组启用Jar-in-Jar系统 +jarJar.enable() + + +// 配置'jarJar'任务 +// 'all'是默认的分类器(classifier) +tasks.named('jarJar') { + // ... +} +``` + +添加依赖 +-------- + +你可以使用`jarJar`配置添加要包含在jar中的依赖项。由于Jar-in-Jar是一个协商(negotiation)系统,所有版本都应该提供一个支持的范围。 + +```gradle +// 在build.gradle中 +dependencies { + // 根据examplelib在从2.0(包括)到3.0(不包括)之间的最高支持版本编译并包含该版本 + jarJar(group: 'com.example', name: 'examplelib', version: '[2.0,3.0)') +} +``` + +如果需要指定要包含的确切版本,而不是该范围中支持的最高版本,则可以在依赖关系闭包中使用`jarJar#pin`。在这些情况下,artifact版本将在编译时使用,而固定(pinned)版本将捆绑在模组jar中。 + +```gradle +// 在build.gradle中 +dependencies { + // 根据examplelib在从2.0(包括)到3.0(不包括)之间的最高支持版本编译并包含该版本 + jarJar(group: 'com.example', name: 'examplelib', version: '[2.0,3.0)') { + // 包含examplelib 2.8.0 + jarJar.pin(it, '2.8.0') + } +} +``` + +你可以在针对特定版本进行编译时附加固定一个版本范围: + +```gradle +// 在build.gradle中 +dependencies { + // 编译examplelib 2.8.0 + jarJar(group: 'com.example', name: 'examplelib', version: '2.8.0') { + // 包含从2.0(包括)到3.0(不包括)之间的最高支持版本的examplelib + jarJar.pin(it, '[2.0,3.0)') + } +} +``` + +### 使用运行时依赖 + +如果你想在jar中包含模组的运行时依赖项,你可以在构建脚本中调用`jarJar#fromRuntimeConfiguration`。如果你决定使用此选项,强烈建议你包含依赖项筛选器;否则,包括Minecraft和Forge在内的每一个依赖项都将捆绑在jar中。为了支持更灵活的语句,`dependency`配置已添加到`jarJar`扩展和任务中。使用此选项,可以指定要包含在配置中或从配置中排除的模式: + +```gradle +// 在build.gradle中 + +// 为jar添加运行时依赖项 +jarJar.fromRuntimeConfiguration() + +// ... + +jarJar { + // 在此处包含或排除运行时依赖项 + dependencies { + // 排除以'com.google.gson.'开头的任何依赖项 + exclude(dependency('com.google.gson.*')) + } +} +``` + +!!! 提示 + 使用`#fromRuntimeConfiguration`时,通常建议至少设置一个`include`筛选器。 + +将一个Jar-in-Jar发布到Maven +-------------------------- + +出于存档原因,ForgeGradle支持将Jar-in-Jar artifact发布给选定的Maven,类似于[Shadow插件][shadow]处理它的方式。在实践中,这不是有用的,也不推荐使用。 + +```gradle +// I在build.gradle中(具有'maven-publish'插件) + +publications { + mavenJava(MavenPublication) { + // 添加标准java组件和Jar-in-Jar artifact + from components.java + jarJar.component(it) + + // ... + } +} +``` + + +[shadow]: https://imperceptiblethoughts.com/shadow/getting-started/ diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs-neogradle/current/gettingstarted/index.md b/i18n/zh-CN/docusaurus-plugin-content-docs-neogradle/current/gettingstarted/index.md new file mode 100644 index 000000000..1463c24aa --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs-neogradle/current/gettingstarted/index.md @@ -0,0 +1,73 @@ +入门ForgeGradle +=============== + +如果你以前从未使用过ForgeGradle,以下是设置开发环境所需的最低信息量。 + +#### 先决条件 + +* Java开发包(JDK)的安装 + +Minecraft版本 | Java开发包版本 +:---: | :---: +1.12 - 1.16 | [JDK 8][jdk8] +1.17 | [JDK 16][jdk16] +1.18 - 1.19 | [JDK 17][jdk17] + +* 熟练使用一款集成开发环境(IDE)。 + * 建议使用一款集成了Gradle功能的IDE。 + +## 安装ForgeGradle + +1. 首先从MinecraftForge下载[Modder Development Kit(MDK)][mdk]的一个副本,并将zip解压到一个空目录中。 +1. 在你选择的IDE中打开提取MDK的目录。如果你的IDE集成了Gradle,请将其作为Gradle项目导入。 +1. 为你的模组自定义你的Gradle构建脚本: + 1. 将`archivesBaseName`设置为所需的mod id。此外,用该mod id替换所有出现的`examplemod`。 + 1. 将`group`更改为所需的程序包名称。请确保遵循现有的[命名约定][group]。 + 1. 更改`version`编号以反映你的模组的当前版本。强烈建议使用[Forge对语义版本控制的扩展][semver]。 + + +!!! 重要 + 确保对mod id的任何更改都反映在mods.toml和模组主类中。有关更多信息,请参阅Forge文档上的[规划你的模组结构][structuring]。 + +4. 使用IDE重新加载或刷新Gradle项目。如果你的IDE没有集成Gradle,请从项目目录中的shell运行以下命令: + +```sh +./gradlew build --refresh-dependencies +``` + +5. 如果你的IDE是Eclipse、IntelliJ IDEA或Visual Studio Code,则可以分别使用以下命令之一生成运行配置: + +#### Eclipse + +```sh +./gradlew genEclipseRuns +``` + +#### IntelliJ IDEA + +```sh +./gradlew genIntellijRuns +``` + +#### Visual Studio Code + +```sh +./gradlew genVSCodeRuns +``` + +你可以使用生成的运行配置之一来运行客户端、服务端等。 + +!!! 提示 + 如果你的IDE没有被列出,你仍然可以使用`./gradlew run*`(例如,`runClient`、`runServer`、`runData`)。你也可以将这些命令与支持的IDE一起使用。 + +恭喜你,现在你已经建立了一个开发环境! + + +[jdk8]: https://adoptium.net/temurin/releases/?version=8 +[jdk16]: https://adoptium.net/temurin/releases/?version=16 +[jdk17]: https://adoptium.net/temurin/releases/?version=17 + +[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/ diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs-neogradle/current/index.md b/i18n/zh-CN/docusaurus-plugin-content-docs-neogradle/current/index.md new file mode 100644 index 000000000..510b30409 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs-neogradle/current/index.md @@ -0,0 +1,73 @@ +NeoGradle 文档 中文翻译 +====================== + +:::info +欢迎访问[NeoForged文档中文翻译官方仓库][translation-repo],对我们的翻译内容提出意见或建议。 +::: + +# 前言 + +:::caution +请注意,由于NeoForged处于创始阶段,本文档可能未紧跟最新版本。 + +在NeoGradle发布其第一个版本之前,你应该参考ForgeGradle文档。ForgeGradle第6和5版的文档在此处归档。 +::: + +这里是[ForgeGradle]的官方文档,用于开发[MinecraftForge]以及使用MinecraftForge的模组的一个[Gradle]插件。 + +该文档 _仅_ 针对ForgeGradle编纂,**而不是一个Java、Groovy或Gradle教程**。 + +如果你愿意对文档做出贡献,请阅读[向文档做出贡献][contributing]。 + +添加该插件 +--------- + +ForgeGradle使用Gradle 8;它可以使用`build.gradle`中的`plugins`块添加,方法是将以下信息添加到`settings.gradle`: + +```gradle +// 在settings.gradle中 +pluginManagement { + repositories { + // ... + + // 添加MinecraftForge maven + maven { url = 'https://maven.minecraftforge.net/' } + } +} + +plugins { + // 添加toolchain resolver + id 'org.gradle.toolchains.foojay-resolver-convention' version '0.5.0' +} +``` + +```gradle +// 在build.gradle中 +plugins { + // 添加ForgeGradle插件 + id 'net.minecraftforge.gradle' version '[6.0,6.2)' + + // ... +} +``` + +# 目录 +- [主页](./index.md) +- [向这篇文档做出贡献](./contributing.md) +- 入门 + - [概述](./gettingstarted/index.md) +- 配置选项 + - [概述](./configuration/index.md) + - [运行配置](./configuration/runs.md) + - [进阶主题](./configuration/advanced.md) +- 依赖 + - [概述](./dependencies/index.md) + - [Jar-in-Jar](./dependencies/jarinjar.md) +- [移植到当前版本](./porting/5.x_to_6.0.md) + +[translation-repo]: https://github.com/srcres258/neo-doc +[contributing]: ./contributing.md +[ForgeGradle]: https://github.com/MinecraftForge/ForgeGradle +[Gradle]: https://gradle.org/ +[MinecraftForge]: https://github.com/MinecraftForge/MinecraftForge +[contributing]: ./contributing.md diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs-neogradle/current/porting/5.x_to_6.0.md b/i18n/zh-CN/docusaurus-plugin-content-docs-neogradle/current/porting/5.x_to_6.0.md new file mode 100644 index 000000000..8991d92eb --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-docs-neogradle/current/porting/5.x_to_6.0.md @@ -0,0 +1,94 @@ +# ForgeGradle 5 -> 6 迁移入门 + +这是一个关于如何将你的构建脚本从ForgeGradle 5迁移到6的高级、非详尽的概述。 + +如果有任何不正确或遗漏的信息,请在下面留言。谢谢! + +## 添加Foojay Toolchains插件 + +Gradle现在使用[Foojay Toolchains插件](https://github.com/gradle/foojay-toolchains)以管理Java工具链。必须将插件添加到`settings.gradle[.kts]`中: + +使用Gradle DSL: + +```gradle +// 在settings.gradle中 +plugins { + id 'org.gradle.toolchains.foojay-resolver-convention' version '0.5.0' +} +``` + +使用Kotlin DSL: + +```kotlin +// 在settings.gradle.kts中 +plugins { + id("org.gradle.toolchains.foojay-resolver-convention") version "0.5.0" +} +``` + +## 将Gradle升级到8.1.1 + +首先,你需要更新Gradle Wrapper以使用8.1.1。你可以在[Gradle的迁移指南](https://docs.gradle.org/current/userguide/upgrading_version_7.html)上找到有关从7迁移到8的其他信息。 + +这可以通过以下两种方式之一实现: + +### `gradle wrapper` + +首选选项是将`gradle wrapper`命令与`--gradle-version`选项一起使用。 + +```bash +# 在Gradle项目的根目录内部 +./gradlew wrapper --gradle-version=8.1.1 +``` + +### gradle-wrapper.properties + +不建议使用此方法,并且只有在上面的`gradlew wrapper`命令失败时才应使用此方法。 + +更新`gradle/wrapper/gradle-wrapper.properties`中的`distributionUrl`属性中的版本。然后运行`./gradlew wrapper`。 + +```properties +distributionUrl=https\://services.gradle.org/distributions/gradle-8.1.1-bin.zip +``` + +## 更新ForgeGradle插件版本 + +更新Gradle后,将`build.gradle[.kts]`中的`ForgeGradle`版本更新为`[6.0,6.2)`: + +使用Gradle DSL: + +```gradle +// 在build.gradle中 +plugins { + id 'net.minecraftforge.gradle' version '[6.0,6.2)' +} +``` + +使用Kotlin DSL: + +```kotlin +// 在build.gradle.kts中 +plugins { + id("net.minecraftforge.gradle") version "[6.0,6.2)" +} +``` + +## 从运行配置中移除`forceExit` + +ForgeGradle现已删除运行配置中的`forceExit`属性。对运行任务的更改仅适用于当前项目,不适用于任何子项目。 + +### 次要的添加、变更和移除 + +### 重新混淆的Classpath继承 + +如果你在`reobf*`任务配置中使用`classpath.from`来声明额外的库,则需要考虑`reobf*`任务;它应该迁移到`libraries.from`属性。 + +### Eclipse启动器组(Launcher Groups) + +如果ForgeGradle的设置使得Eclipse应该在开始游戏之前运行Gradle任务,那么你必须从`Launch Group`文件夹执行运行配置。只有在`minecraft`块中启用了新属性时,情况才会如此。 + +![Eclipse启动组演示](eclipse_launch_groups.png) + +### ModConfig中`resources`和`classes`的移除 + +运行配置的`mods`块中的属性`resources`和`classes`已从ForgeGradle 6中移除。 diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs-neogradle/current/porting/eclipse_launch_groups.png b/i18n/zh-CN/docusaurus-plugin-content-docs-neogradle/current/porting/eclipse_launch_groups.png new file mode 100644 index 0000000000000000000000000000000000000000..b17242c17c16e97b684120fe161b59aed4f28900 GIT binary patch literal 11855 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 Date: Tue, 10 Oct 2023 22:31:54 +0800 Subject: [PATCH 07/15] Correct rendering of information blocks Correct rendering of information blocks of ":::tip", ":::caution" and ":::danger". Signed-off-by: src_resources --- .../current/advanced/accesstransformers.md | 3 ++- .../current/blockentities/index.md | 12 ++++++---- .../current/blocks/index.md | 3 ++- .../current/blocks/states.md | 3 ++- .../current/concepts/events.md | 6 +++-- .../current/concepts/internationalization.md | 6 +++-- .../current/concepts/lifecycle.md | 6 +++-- .../current/concepts/registries.md | 6 +++-- .../current/concepts/sides.md | 6 +++-- .../current/datagen/client/localization.md | 3 ++- .../current/datagen/client/modelproviders.md | 24 ++++++++++++------- .../current/datagen/client/sounds.md | 3 ++- .../current/datagen/server/advancements.md | 3 ++- .../datagen/server/datapackregistries.md | 6 +++-- .../current/datagen/server/loottables.md | 6 +++-- .../current/datagen/server/recipes.md | 9 ++++--- .../current/datagen/server/tags.md | 3 ++- .../current/datastorage/capabilities.md | 3 ++- .../current/datastorage/codecs.md | 12 ++++++---- .../current/gameeffects/particles.md | 6 +++-- .../current/gameeffects/sounds.md | 3 ++- .../current/gettingstarted/index.md | 9 ++++--- .../current/gettingstarted/modfiles.md | 3 ++- .../current/gettingstarted/structuring.md | 6 +++-- .../current/gui/menus.md | 21 ++++++++++------ .../current/gui/screens.md | 18 +++++++++----- .../current/items/bewlr.md | 3 ++- .../current/misc/config.md | 12 ++++++---- .../current/misc/debugprofiler.md | 3 ++- .../current/misc/gametest.mdx | 18 +++++++++----- .../current/misc/keymappings.md | 18 +++++++++----- .../current/networking/entities.md | 6 +++-- .../current/networking/simpleimpl.md | 6 +++-- .../rendering/modelextensions/facedata.md | 9 ++++--- .../rendering/modelextensions/rendertypes.md | 3 ++- .../current/rendering/modelloaders/index.md | 3 ++- .../current/resources/client/models/index.md | 3 ++- .../resources/client/models/itemproperties.md | 3 ++- .../current/resources/server/advancements.md | 3 ++- .../current/resources/server/conditional.md | 6 +++-- .../current/resources/server/glm.md | 3 ++- .../current/resources/server/loottables.md | 3 ++- .../resources/server/recipes/custom.md | 9 ++++--- .../resources/server/recipes/incode.md | 3 ++- .../current/resources/server/recipes/index.md | 6 +++-- .../resources/server/recipes/ingredients.md | 6 +++-- .../current/resources/server/tags.md | 3 ++- 47 files changed, 212 insertions(+), 106 deletions(-) diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/advanced/accesstransformers.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/advanced/accesstransformers.md index 49bbd10f8..03cc46da9 100644 --- a/i18n/zh-CN/docusaurus-plugin-content-docs/current/advanced/accesstransformers.md +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/advanced/accesstransformers.md @@ -38,10 +38,11 @@ minecraft { 一个特殊的修饰符`+f`和`-f`可以附加到前面提到的修饰符中,以分别添加或删除`final`修饰符,这可以在应用时防止子类化、方法重写或字段修改。 -!!! 警告 +:::danger 指令只修改它们直接引用的方法;任何重写方法都不会进行访问转换。建议确保转换后的方法没有限制可见性的未转换重写,这将导致JVM抛出错误(Error)。 可以安全转换的方法示例有`private`方法、`final`方法(或`final`类中的方法)和`static`方法。 +::: 目标和指令 --------- diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/blockentities/index.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/blockentities/index.md index 5b1b32a8c..be7e0e679 100644 --- a/i18n/zh-CN/docusaurus-plugin-content-docs/current/blockentities/index.md +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/blockentities/index.md @@ -5,9 +5,10 @@ 原版Minecraft中的一些例子是处理箱子的物品栏、熔炉的熔炼逻辑或信标的区域效果。 模组中存在更高级的示例,例如采石场(如BC)、分拣机(如IC2)、管道(如BC)和显示器(如OC)。(括号内容为译者注。) -!!! 注意 +:::caution `BlockEntities`并不是万能的解决方案,如果使用错误,它们可能会导致游戏卡顿。 如果可能的话,尽量避免使用。 +::: ## 注册 @@ -42,8 +43,9 @@ BlockEntity#load(CompoundTag tag) 每当包含`BlockEntity`的`LevelChunk`从标签加载/保存到标签时,都会调用这些方法。 使用它们以读取和写入你的方块实体类的字段。 -!!! 注意 +:::caution 每当你的数据发生改变时,你需要调用`BlockEntity#setChanged`;否则,保存存档时可能会跳过包含你的`BlockEntity`的`LevelChunk`。 +::: !!! 重要 调用`super`方法非常重要! @@ -68,8 +70,9 @@ public static void tick(Level level, BlockPos pos, BlockState state, MyBlockEnti } ``` -!!! 注意 +:::caution 这个方法在每个游戏刻都会调用;因此,你应该避免在这里进行复杂的计算。如果可能的话,你应该每X个游戏刻进行更复杂的计算。(一秒钟内的游戏刻数量可能低于20(二十),但不会更高) +::: ## 向客户端同步数据 @@ -125,8 +128,9 @@ Level#sendBlockUpdated(BlockPos pos, BlockState oldState, BlockState newState, i 这种同步方式可能是最复杂的,但通常是最优化的,因为你可以确保只有需要同步的数据才是真正同步的。在尝试之前,你应该先查看[`Networking`][networking]部分,尤其是[`SimpleImpl`][simple_impl]。一旦你创建了自定义网络消息,你就可以使用`SimpleChannel#send(PacketDistributor$PacketTarget, MSG)`将其发送给所有加载了该`BlockEntity`的用户。 -!!! 警告 +:::danger 进行安全检查很重要,当消息到达玩家时,`BlockEntity`可能已经被销毁/替换!你还应该检查区块是否已加载(`Level#hasChunkAt(BlockPos)`)。 +::: [registration]: ../concepts/registries.md#methods-for-registering [storing-data]: #storing-data-within-your-blockentity diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/blocks/index.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/blocks/index.md index 5239cae04..95743c7da 100644 --- a/i18n/zh-CN/docusaurus-plugin-content-docs/current/blocks/index.md +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/blocks/index.md @@ -17,8 +17,9 @@ 所有这些方法都是*可链接*的,这意味着你可以串联地调用它们。有关此方面的示例,请参见`Blocks`类。 -!!! 注意 +:::caution `CreativeModeTab`未针对方块定义setter。如果方块有与之关联的物品(例如`BlockItem`),则由[`BuildCreativeModeTabContentsEvent`][creativetabs]处理。此外,也没有针对翻译键的setter,因为它是通过`Block#getDescriptionId`从注册表名称生成的。 +::: ### 进阶方块 diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/blocks/states.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/blocks/states.md index 472a761ea..51ac80ab1 100644 --- a/i18n/zh-CN/docusaurus-plugin-content-docs/current/blocks/states.md +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/blocks/states.md @@ -38,8 +38,9 @@ switch (meta) { 并非所有方块和情况都需要使用`BlockState`;只有方块的最基本属性才应该被放入`BlockState`,而任何其他情况都最好有一个`BlockEntity`或是一个区分开的`Block`。要始终考虑是否确实需要出于你的目的而使用方块状态。 -!!! 注意 +:::caution 一个很好的经验法则是:**如果它有不同的名称,那么它应该是一个单独的方块**。 +::: 一个案例是制作椅子方块:椅子的*朝向*应该是一个*属性*,而*不同类型的木材*应该被分成不同的块。朝向东方的“橡木椅子”(`oak_chair[facing=east]`)与朝向西方的“云杉椅子”(`oak_chair[facing=east]`)不同。 diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/concepts/events.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/concepts/events.md index 8f4c93013..54bbdf12a 100644 --- a/i18n/zh-CN/docusaurus-plugin-content-docs/current/concepts/events.md +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/concepts/events.md @@ -84,8 +84,9 @@ public class MyStaticClientOnlyEventHandler { } ``` -!!! 注意 +:::caution 这不会注册类的实例;它注册类本身(即事件处理方法必须是静态的)。 +::: 事件的取消 --------- @@ -125,8 +126,9 @@ public class MyStaticClientOnlyEventHandler { * `InterModEnqueueEvent` * `InterModProcessEvent` -!!! 注意 +:::caution `FMLClientSetupEvent`和`FMLDedicatedServerSetupEvent`仅在各自的分发版本(物理端——译者注)上调用。 +::: 这四个生命周期事件都是并行运行的,因为它们都是`ParallelDispatchEvent`的子类。如果你想在任何`ParallelDispatchEvent`期间在主线程上运行运行代码,可以使用`#enqueueWork`来执行此操作。 diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/concepts/internationalization.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/concepts/internationalization.md index 3e8272030..5261ff671 100644 --- a/i18n/zh-CN/docusaurus-plugin-content-docs/current/concepts/internationalization.md +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/concepts/internationalization.md @@ -33,17 +33,19 @@ Block、Item和其他一些Minecraft类都内置了用于显示其名称的翻 } ``` -!!! 注意 +:::caution 翻译键的唯一目的是国际化。不要把它们用于代码的逻辑处理部分。请改用注册表名称。 +::: 本地化相关方法 ------------- -!!! 警告 +:::danger 一个常见的问题是让服务端为客户端进行本地化。服务端只能在自己的语言设置中进行本地化,这不一定与所连接的客户端的语言设置相匹配。 为了尊重客户端的语言设置,服务端应该让客户端使用`TranslatableComponent`或其他保留语言中性翻译键的方法在自己的语言设置中本地化文本。 +::: ### `net.minecraft.client.resources.language.I18n` (仅客户端) diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/concepts/lifecycle.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/concepts/lifecycle.md index c221dcdcd..c1f1ac48a 100644 --- a/i18n/zh-CN/docusaurus-plugin-content-docs/current/concepts/lifecycle.md +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/concepts/lifecycle.md @@ -22,10 +22,11 @@ public class MyMod { } ``` -!!! 警告 +:::danger 大多数生命周期事件都是并行触发的(多线程——译者注):所有模组都将同时接收相同的事件。 模组必须注意线程安全,就像调用其他模组的API或访问原版系统一样。延迟代码,以便稍后通过`ParallelDispatchEvent#enqueueWork`执行。 +::: 注册表事件 --------- @@ -64,8 +65,9 @@ InterModComms 之后在`InterModProcessEvent`期间,使用`InterModComms#getMessages`获取所有接收到的消息的Stream。提供的mod id几乎总是先前调用发送消息方法的模组的mod id。此外,可以指定一个Predicate来对消息键进行过滤。这将返回一个带有`IMCMessages`的Stream,其中包含数据的发送方、数据的接收方、数据键以及所提供的数据本身。 -!!! 注意 +:::caution 还有另外两个生命周期事件:`FMLConstructModEvent`,在模组实例构造之后但在`RegisterEvent`之前直接触发;`FMLLoadCompleteEvent`,在`InterModComms`事件之后触发,用于模组加载过程完成时。 +::: [registering]: ./registries.md#methods-for-registering [capabilities]: ../datastorage/capabilities.md diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/concepts/registries.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/concepts/registries.md index 59fc70ba4..be52de57d 100644 --- a/i18n/zh-CN/docusaurus-plugin-content-docs/current/concepts/registries.md +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/concepts/registries.md @@ -61,7 +61,7 @@ private static final DeferredRegister REGISTER = Deferred public static final RegistryObject EXAMPLE_LOOT_ITEM_CONDITION_TYPE = REGISTER.register("example_loot_item_condition_type", () -> new LootItemConditionType(...)); ``` -!!! 注意 +:::caution 有些类无法自行注册。相反,`*Type`类被注册,并在前者的构造函数中被使用。例如,[`BlockEntity`][blockentity]具有`BlockEntityType`,`Entity`具有`EntityType`。这些`*Type`类是工厂,它们只是根据需要创建包含类型。 这些工厂是通过使用它们的`*Type$Builder`类创建的。例如:(`REGISTER`指的是`DeferredRegister`) @@ -70,6 +70,7 @@ public static final RegistryObject EXAMPLE_LOOT_ITEM_COND "example_block_entity", () -> BlockEntityType.Builder.of(ExampleBlockEntity::new, EXAMPLE_BLOCK.get()).build(null) ); ``` +::: 引用已注册的对象 --------------- @@ -113,8 +114,9 @@ public static final RegistryObject COFFEINUM = RegistryObject.create(n 被`@ObjectHolder`注释的字段会在`RegisterEvent`为其注册表激发之后注入其值,与`RegistryObjects`的引用的更新同时发生。 -!!! 注意 +:::caution 如果要注入对象时该对象不存在于注册表中,那么日志会记录一条调试信息,并且不会注入任何值。 +::: 由于这些规则相当复杂,案例如下: diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/concepts/sides.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/concepts/sides.md index 102ad34df..550abc070 100644 --- a/i18n/zh-CN/docusaurus-plugin-content-docs/current/concepts/sides.md +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/concepts/sides.md @@ -34,8 +34,9 @@ Minecraft中的端位 我们如何解决这个问题?幸运的是,FML有一个`DistExecutor`,它提供了各种方法来在不同的物理端运行不同的方法,或者只在某一物理端运行单个方法。 -!!! 注意 +:::caution 对FML基于**物理**端进行检查的理解尤为重要。单机世界(包含逻辑服务端+物理客户端的逻辑客户端)将始终使用`Dist.CLIENT`! +::: `DistExecutor`的工作原理是接收所提供的执行方法的Supplier,通过利用[JVM指令`invokedynamic`][invokedynamic]有效地防止类加载。被执行的方法应该是静态的并且在不同的类中。此外,如果这个静态方法没有参数,则应使用该方法的引用,而不是一个执行方法的Supplier。 @@ -60,8 +61,9 @@ DistExecutor.safeCallWhenOn(Dist.CLIENT, () -> ExampleClass::safeCallMethodExamp ``` -!!! 警告 +:::danger 由于`invokedynamic`在Java 9+中的工作方式发生了变化,`DistExecutor`方法的所有`#safe*`变体都会在开发环境中抛出封装在`BootstrapMethodError`中的原始异常。应该使用`#unsafe*`变体或对[`FMLEnvironment#dist`][dist]的检查作为替代。 +::: ### 线程组 diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/datagen/client/localization.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/datagen/client/localization.md index c3eb1942a..385a82146 100644 --- a/i18n/zh-CN/docusaurus-plugin-content-docs/current/datagen/client/localization.md +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/datagen/client/localization.md @@ -27,13 +27,14 @@ this.addBlock(EXAMPLE_BLOCK, "Example Block"); this.add("object.examplemod.example_object", "Example Object"); ``` -!!! 提示 +:::tip 包含非美式英语字母数字值的本地化名称可以按原样提供。提供者会自动将字符翻译为等效的unicode,供游戏读取。 ```java // 编码为'Example with a d\u00EDacritic' this.addItem("example.diacritic", "Example with a díacritic"); ``` +::: [lang]: ../../concepts/internationalization.md [locale]: https://minecraft.fandom.com/wiki/Language#Languages diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/datagen/client/modelproviders.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/datagen/client/modelproviders.md index 800cc6111..aa8635522 100644 --- a/i18n/zh-CN/docusaurus-plugin-content-docs/current/datagen/client/modelproviders.md +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/datagen/client/modelproviders.md @@ -35,26 +35,30 @@ public void gatherData(GatherDataEvent event) { `UncheckedModelFile`是`ModelFile`的一个子类,它假定指定的模型存在于某个位置。 -!!! 注意 +:::caution 不应存在使用`UncheckedModelFile`引用模型的情况。如果存在,则`ExistingFileHelper`无法正确跟踪关联的资源。 +::: 模型生成器 --------- `ModelBuilder`表示要生成的`ModelFile`。它包含了关于模型的所有数据:它的父级、面、纹理、变换、照明和[加载器][loader]。 -!!! 提示 +:::tip 虽然可以生成复杂的模型,但建议事先使用建模软件构建这些模型。然后,数据提供者可以生成具有通过父复杂模型中定义的引用应用的特定纹理的子模型。 +::: 生成器的父级(通过`ModelBuilder#parent`)可以是任何`ModelFile`:生成的或现有的。一旦创建了生成器,生成的文件就会添加到`ModelProvider`中。生成器本身可以作为父级传入,也可以提供`ResourceLocation`。 -!!! 警告 +:::danger 如果在传递`ResourceLocation`时父模型不是在子模型之前生成的,则将引发异常。 +::: 模型中的每个元素(通过`ModelBuilder#element`)都被定义为使用两个三维点(分别为`ElementBuilder#from`和`#to`)的立方体,其中每个轴都被限制为值`[-16,32]`(包括-16和32)。多维数据集的每个面(`ElementBuilder#face`)都可以指定面何时被剔除(`FaceBuilder#cullface`)、[色调索引][color](`FaceBuilder#tintindex`)、来自`textures`键的纹理引用(`FaceBuilder#texture`)、纹理上的UV坐标(`FaceBuilder#uvs`)以及以90度间隔旋转(`FaceBuilder#rotation`)。 -!!! 注意 +:::caution 建议在任何轴上元素超过`[0,16]`界限的方块模型分离为多个方块,例如多方块结构,以避免照明和剔除问题。 +::: 每个立方体还可以围绕指定点(`RotationBuilder#origin`)以22.5度的间隔(`RotationBuilder#angle`)为给定轴(`RotationBuilder#axis`)旋转(`ElementBuilder#rotation`)。立方体也可以相对于整个模型缩放所有面(`RotationBuilder#rescale`)。多维数据集还可以确定是否应渲染其阴影(`ElementBuilder#shade`)。 @@ -88,22 +92,25 @@ public void gatherData(GatherDataEvent event) { 此外,还有几个助手可以使用普通模板轻松生成通用模型。大多数是方块模型,只有少数是通用的。 -!!! 注意 +:::caution 尽管模型在一个特定的子目录中,但**并不**意味着该模型不能被另一个子目录中的模型引用。通常,它表示该模型用于该类型的对象。 +::: ### `BlockModelProvider` `BlockModelProvider`用于通过`block`文件夹中的`BlockModelBuilder`生成方块模型。方块模型通常应为`minecraft:block/block`或其子模型之一的父模型,以便与物品模型一起使用。 -!!! 注意 +:::caution 方块模型及其物品模型对应物通常不是通过`BlockModelProvider`和`ItemModelProvider`的直接子类生成的,而是通过[`BlockStateProvider`][blockstateprovider]生成的。 +::: ### `ItemModelProvider` `ItemModelProvider`用于通过`item`文件夹中的`ItemModelBuilder`生成块模型。大多数物品模型的父级为`item/generated`,并使用`layer0`来指定其纹理,这可以使用`#singleTexture`来完成。 -!!! 注意 +:::caution `item/generated`可以支持堆叠在一起的五个纹理层:`layer0`、`layer1`、`layer2`、`layer3`和`layer4`。 +::: ```java // 在某个ItemModelProvider#registerModels中 @@ -115,8 +122,9 @@ public void gatherData(GatherDataEvent event) { this.basicItem(EXAMPLE_ITEM.get()); ``` -!!! 注意 +:::caution 方块的物品模型通常应作为现有方块模型的父级,而不是为物品生成单独的模型。 +::: 方块状态提供者 ------------- diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/datagen/client/sounds.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/datagen/client/sounds.md index 1073fac29..a374b3781 100644 --- a/i18n/zh-CN/docusaurus-plugin-content-docs/current/datagen/client/sounds.md +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/datagen/client/sounds.md @@ -20,8 +20,9 @@ public void gatherData(GatherDataEvent event) { 可以通过`#add`指定音效名称和定义来生成音效定义。音效名称可以从[`SoundEvent`][soundevent]、`ResourceLocation`或字符串中提供。 -!!! 警告 +:::danger 提供的音效名称将始终假定命名空间是提供给提供者的构造函数的mod id。没有对音效名称的命名空间执行验证! +::: ### `SoundDefinition` diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/datagen/server/advancements.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/datagen/server/advancements.md index 6f818b01b..25cf6bbc9 100644 --- a/i18n/zh-CN/docusaurus-plugin-content-docs/current/datagen/server/advancements.md +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/datagen/server/advancements.md @@ -3,8 +3,9 @@ 可以通过构建新的`AdvancementProvider`并提供`AdvancementSubProvider`来为模组生成[进度][Advancements]。进度既可以手动创建和提供,也可以为方便起见,使用`Advancement$Builder`创建。该提供者必须被[添加][datagen]到`DataGenerator`中。 -!!! 注意 +:::caution Forge为`AdvancementProvider`提供了一个名为`ForgeAdvancementProvider`的扩展,它可以更好地集成以生成进度。因此,本文档将使用`ForgeAdvancementProvider`和子提供者接口`ForgeAdvancementProvider$AdvancementGenerator`。 +::: ```java // 在模组事件总线上 diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/datagen/server/datapackregistries.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/datagen/server/datapackregistries.md index 82190759b..cb1af8baa 100644 --- a/i18n/zh-CN/docusaurus-plugin-content-docs/current/datagen/server/datapackregistries.md +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/datagen/server/datapackregistries.md @@ -4,8 +4,9 @@ Datapack registry objects can be generated for a mod by constructing a new `DatapackBuiltinEntriesProvider` and providing a `RegistrySetBuilder` with the new objects to register. The provider must be [added][datagen] to the `DataGenerator`. 通过构造新的`DatapackBuiltinEntriesProvider`并为`RegistrySetBuilder`提供要注册的新对象,可以为模组生成数据包注册表对象。该提供者必须被[添加][datagen]到`DataGenerator`中。 -!!! 注意 +:::caution `DatapackBuiltinEntriesProvider`是`RegistriesDatapackGenerator`之上的一个Forge扩展,它可以正确处理引用现有数据包注册表对象而不会分解条目。因此,本文档将使用`DatapackBuiltinEntriesProvider`。 +::: ```java // 在模组事件总线上 @@ -45,8 +46,9 @@ new RegistrySetBuilder() }); ``` -!!! 注意 +:::caution 通过Forge创建的数据包注册表也可以通过传递相关的`ResourceKey`来使用该生成器生成它们的对象。 +::: 使用`BootstapContext`注册 ------------------------- diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/datagen/server/loottables.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/datagen/server/loottables.md index e907d2974..b0e8fd421 100644 --- a/i18n/zh-CN/docusaurus-plugin-content-docs/current/datagen/server/loottables.md +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/datagen/server/loottables.md @@ -75,7 +75,7 @@ public MyEntityLootSubProvider() { 要使用它们,所有注册的对象必须分别提供给`BlockLootSubProvider#getKnownBlocks`和`EntityLootSubProvider#getKnownEntityTypes`。这些方法是为了确保Iterable中的所有对象都有一个战利品表。 -!!! 提示 +:::tip 如果`DeferredRegister`用于注册模组的对象,则可以通过`DeferredRegister#getEntries`向`#getKnown*`方法提供条目: ```java @@ -88,6 +88,7 @@ public MyEntityLootSubProvider() { ::iterator; // 创建该Iterable } ``` +::: 战利品表本身可以通过实现`#generate`方法来添加。 @@ -104,8 +105,9 @@ public void generate() { 要生成战利品表,它们被`LootTableSubProvider`接受为`LootTable$Builder`。之后,在`LootTableProvider$SubProviderEntry`中设置指定的`LootContextParamSet`,然后通过`#build`生成。在构建之前,生成器可以指定影响战利品表功能的条目、条件和修改器。 -!!! 注意 +:::caution 战利品表的功能非常广泛,因此本文档不会对其进行全面介绍。取而代之的是,将对每个组件进行简要描述。每个组件的特定子类型可以使用IDE找到。它们的实现将留给读者练习。 +::: ### LootTable diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/datagen/server/recipes.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/datagen/server/recipes.md index 7574269d9..ead9509a9 100644 --- a/i18n/zh-CN/docusaurus-plugin-content-docs/current/datagen/server/recipes.md +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/datagen/server/recipes.md @@ -25,13 +25,15 @@ public void gatherData(GatherDataEvent event) { !!! 重要 原版配方生成器中不支持配方中的[`ItemStack`输出][stack]。对于现有的原版配方序列化器,必须以不同的方式构建`FinishedRecipe`才能生成此数据。 -!!! 警告 +:::danger 正在生成的物品结果必须指定有效的`RecipeCategory`;否则,将引发`NullPointerException`。 +::: 除[`SpecialRecipeBuilder`]外的所有配方构建器都需要指定一个进度标准。如果玩家以前使用过配方,则所有配方都会生成解锁配方的标准。然而,必须指定一个额外的标准,允许玩家在没有任何先验知识的情况下获得配方。如果指定的任何标准为真,则玩家将获得配方书的配方。 -!!! 提示 +:::tip 配方标准通常使用`InventoryChangeTrigger`在用户物品栏中存在某些物品时解锁配方。 +::: ### ShapedRecipeBuilder @@ -184,8 +186,9 @@ ConditionalRecipe.builder() 自定义配方序列化器可以是通过创建可以构造`FinishedRecipe`的生成器生成的数据。完成的配方将配方数据及其所解锁的进度(如果存在)编码为JSON。此外,还指定了配方的名称和序列化器,以了解在加载时向何处写入以及可以解码对象的内容。构造完`FinishedRecipe`后,只需将其传递给`RecipeProvider#buildRecipes`提供的`Consumer`。 -!!! 提示 +:::tip `FinishedRecipe`足够灵活,任何对象转换都可以是数据生成的,而不仅仅是物品。 +::: [datagen]: ../index.md#data-providers [ingredients]: ../../resources/server/recipes/ingredients.md#forge-types diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/datagen/server/tags.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/datagen/server/tags.md index ba30fe7f3..30032fcee 100644 --- a/i18n/zh-CN/docusaurus-plugin-content-docs/current/datagen/server/tags.md +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/datagen/server/tags.md @@ -26,8 +26,9 @@ public void gatherData(GatherDataEvent event) { 标签提供者有两种用于生成标签的方法:通过`#tag`创建带有对象和其他标签的标签,或通过`#getOrCreateRawBuilder`使用其他对象类型的标签生成标签数据。 -!!! 注意 +:::caution 通常,提供者不会直接调用`#getOrCreateRawBuilder`,除非注册表包含来自不同注册表的对象表示(方块具有物品表示以获得物品栏中的方块)。 +::: 当调用`#tag`时,将创建一个`TagAppender`,它充当要添加到标签中的元素的可链接Consumer: diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/datastorage/capabilities.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/datastorage/capabilities.md index fea74d04b..bf088a988 100644 --- a/i18n/zh-CN/docusaurus-plugin-content-docs/current/datastorage/capabilities.md +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/datastorage/capabilities.md @@ -70,7 +70,7 @@ public void invalidateCaps() { } ``` -!!! 提示 +:::tip 如果给定对象上只公开了一个Capability,则可以使用`Capability#orEmpty`作为if/else语句的替代语句。 ```java @@ -79,6 +79,7 @@ public void invalidateCaps() { return ForgeCapabilities.ITEM_HANDLER.orEmpty(cap, inventoryHandlerLazyOptional); } ``` +::: `Item`是一种特殊情况,因为它们的Capability提供者存储在`ItemStack`上。相反的是,应该通过`Item#initCapabilities`附加提供者。其应该在物品栈的生命周期中保持你的Capability。 diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/datastorage/codecs.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/datastorage/codecs.md index d630374f2..5c70d372a 100644 --- a/i18n/zh-CN/docusaurus-plugin-content-docs/current/datastorage/codecs.md +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/datastorage/codecs.md @@ -102,8 +102,9 @@ Codec | Java类型 Minecraft和Forge为经常编码和解码的对象定义了许多编解码器。一些示例包括`ResourceLocation`的`ResourceLocation#CODEC`,`DateTimeFormatter#ISO_INSTANT`格式的`Instant`的`ExtraCodecs#INSTANT_ISO8601`,以及`CompoundTag`的`CompoundTag#CODEC`。 -!!! 警告 +:::danger `CompoundTag`无法使用`JsonOps`解码JSON中的数字列表。转换时,`JsonOps`将数字设置为其最窄的类型。`ListTag`强制为其数据指定一个特定类型,因此具有不同类型的数字(例如,`64`将是`byte`,`384`为`short`)将在转换时引发错误。 +::: 原版和Forge注册表也具有注册表所包含对象类型的编解码器(例如`Registry#BLOCK`或`ForgeRegistries#BLOCKS`具有`Codec`)。`Registry#byNameCodec`和`IForgeRegistry#getCodec`将把注册表对象编码为其注册表名称,或者如果被压缩,则编码为整数标识符。原版注册表还有一个`Registry#holderByNameCodec`,它编码为注册表名称,并解码为`Holder`中包装的注册表对象。 @@ -303,8 +304,9 @@ public static final Codec> MAP_CODEC = Codec.unboundedMap( 使用无界映射编解码器解码的映射对象存储在**不可变**映射中。如果需要一个可变映射,则应该将[转换器][transformer]应用于映射编解码器。 -!!! 警告 +:::danger 无界映射仅支持对字符串进行编码/解码的键。键值[对][pair]列表编解码器可以用来绕过这个限制。 +::: ### Pair @@ -327,8 +329,9 @@ public static final Codec> PAIR_CODEC = Codec.pair( } ``` -!!! 提示 +:::tip 可以使用[转换器][transformer]应用的键值对列表对具有非字符串键的映射编解码器进行编码/解码。 +::: ### Either @@ -351,8 +354,9 @@ public static final Codec> EITHER_CODEC = Codec.either( "value" ``` -!!! 提示 +:::tip 这可以与[转换器][transformer]结合使用,从两种不同的编码方法中获取特定对象。 +::: ### Dispatch diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/gameeffects/particles.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/gameeffects/particles.md index 5fc5c86aa..031966bf9 100644 --- a/i18n/zh-CN/docusaurus-plugin-content-docs/current/gameeffects/particles.md +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/gameeffects/particles.md @@ -21,8 +21,9 @@ 每个`ParticleType`都有两个参数:一个`overrideLimiter`,用于确定粒子是否在不考虑距离的情况下渲染,以及一个`ParticleOptions$Deserializer`,用于读取客户端上发送的`ParticleOptions`。由于基类`ParticleType`是抽象类,因此需要实现一个方法:`#codec`。其表示如何对与该类型相关的`ParticleOptions`进行编码和解码。 -!!! 注意 +:::caution `ParticleType#codec`仅在用于原版实现的生物群系编解码器中使用。 +::: 在大多数情况下,不需要将任何粒子数据发送到客户端。对于这些例子,更容易创建`SimpleParticleType`的新实例:一个对`ParticleType`和`ParticleOptions`的实现,除了类型之外,它不向客户端发送任何自定义数据。除了红石粉之外,对于着色和依赖方块/物品的粒子而言,大多数原版实现还使用`SimpleParticleType`。 @@ -113,8 +114,9 @@ 要注册这些粒子纹理,需要向`RegisterParticleProvidersEvent#registerSpriteSet`方法提供一个`SpriteParticleRegistration`。此方法接收一个`SpriteSet`,其中包含粒子的相关sprite集,并创建一个`ParticleProvider`来创建粒子。最简单的实现方法可以通过在某个类上实现`ParticleProvider`并让构造函数接受`SpriteSet`来完成。然后,`SpriteSet`可以正常地传递给粒子。 -!!! 注意 +:::caution 如果你注册的是仅包含一个纹理的`TextureSheetParticle`子类型,则可以转而向`#registerSprite`方法提供`ParticleProvider$Sprite`,其与`ParticleProvider`具有基本相同的功能接口方法。 +::: 生成一个粒子 ----------- diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/gameeffects/sounds.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/gameeffects/sounds.md index f62ed3a80..64f044d6a 100644 --- a/i18n/zh-CN/docusaurus-plugin-content-docs/current/gameeffects/sounds.md +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/gameeffects/sounds.md @@ -49,8 +49,9 @@ `SoundEvent`作为对音效的一个引用,并被传递以播放它们。如果一个模组有API,应该在API中公开它的`SoundEvent`。 -!!! 注意 +:::caution 只要音效在`sounds.json`中被注册,它就仍然可以在逻辑客户端上被引用,而不管是否存在引用其的`SoundEvent`。 +::: 播放音效 ------- diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/gettingstarted/index.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/gettingstarted/index.md index 8315d2d24..b20bfc612 100644 --- a/i18n/zh-CN/docusaurus-plugin-content-docs/current/gettingstarted/index.md +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/gettingstarted/index.md @@ -8,8 +8,9 @@ Forge入门 * 安装Java 17开发包(JDK)和64位JVM。Forge推荐并官方支持[Eclipse Temurin][jdk]。 - !!! 警告 + :::danger 确保你正在使用64位的JVM。一种检查方式是在终端中运行`java -version`。使用32位的JVM会导致在使用[ForgeGradle]的过程中出现问题。 + ::: * 熟练使用一款集成开发环境(IDE)。 * 建议使用一款集成了Gradle功能的IDE。 @@ -20,7 +21,7 @@ Forge入门 1. 从[Forge文件站][files]下载Mod开发包(MDK)。点击“Mdk”,等待一段时间之后点击右上角的“Skip”按钮。如果可能的话,推荐下载最新版本的Forge。 1. 解压所下载的MDK到一个空文件夹中。它会成为你的模组的目录,且现在应该已包含一些gradle文件和一个含有example模组的`src`子目录。 - !!! 注意 + :::caution 许多文件可以在不同的模组中重复使用。这些文件是: * `gradle`子目录 @@ -30,6 +31,7 @@ Forge入门 * `settings.gradle` `src`子目录不需要跨工作区进行复制;但是,如果稍后创建java(`src/main/java`)和resource(`src/main/resources`),则可能需要刷新Gradle项目。 + ::: 1. 打开你选择的IDE: * Forge只明确支持在Eclipse和IntelliJ IDEA上进行开发,但还有其他针对Visual Studio代码的运行配置。无论如何,从Apache NetBeans到Vim/Emacs的任何开发环境都可被使用。 @@ -104,8 +106,9 @@ version = '1.19.4-1.0.0.0' 1. 要在测试环境中运行你的模组,你既可以使用已生成的运行配置,也可以运行功能类似的Gradle任务(例如`gradlew runClient`)。这将使用任何所指定的源码集从run文件夹中启动Minecraft。默认的MDK包括`main`源码集,因此任何在`src/main/java`中编写的源代码都会被应用。 1. 如果你想要运行dedicated服务端,无论是通过运行配置,还是通过`gradlew runServer`,服务端都会立刻宕机。你需要通过编辑run文件夹中的`eula.txt`文件同意Minecraft EULA。一旦同意后,服务器就会加载,之后就可以通过直连`localhost`进行访问了。 -!!! 注意 +:::caution 在服务端环境测试你的模组是必要的。这包括[只针对客户端的模组][client],因为在加载到服务端后它们不应该做任何事。 +::: [jdk]: https://adoptium.net/temurin/releases?version=17 "Eclipse Temurin 17 Prebuilt Binaries" [ForgeGradle]: https://docs.minecraftforge.net/en/fg-6.x diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/gettingstarted/modfiles.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/gettingstarted/modfiles.md index f3a88a721..57a43f6af 100644 --- a/i18n/zh-CN/docusaurus-plugin-content-docs/current/gettingstarted/modfiles.md +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/gettingstarted/modfiles.md @@ -119,8 +119,9 @@ modId = "examplemod2" `side` | string | `"BOTH"` | 所依赖模组必须位于的[端位][dist]:`"CLIENT"`、`"SERVER"`或`"BOTH"`。 | `"CLIENT"` `referralUrl` | string | *无* | 指向依赖下载界面的URL。目前无作用。 | `"https://library.example.com/"` -!!! 警告 +:::danger 两个模组的`ordering`可能会因循环依赖而造成崩溃:例如模组A必须在模组B之前(`"BEFORE"`)加载,而模组B也必须在模组A之前(`"BEFORE"`)加载。 +::: 模组入口点 ---------- diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/gettingstarted/structuring.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/gettingstarted/structuring.md index ed86f32f8..90edc8730 100644 --- a/i18n/zh-CN/docusaurus-plugin-content-docs/current/gettingstarted/structuring.md +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/gettingstarted/structuring.md @@ -3,8 +3,9 @@ 结构分明的模组有利于维护和做出贡献,并提供对底层代码库的更清晰理解。下面列举了由Java、Minecraft和Forge提出的一些建议。 -!!! 注意 +:::caution 你不必遵循以下建议;你可以以任何你认为合适的方式规划你的模组。然而,我们仍强烈建议这样做。 +::: 程序包 ------ @@ -68,8 +69,9 @@ module B * 一个叫作`NotDirt`的`Block` -> `NotDirtBlock`。 * 为`Oven`设计的一个菜单 -> `OvenMenu`。 -!!! 注意 +:::caution Mojang通常对除实体以外的所有类命名时都遵循类似的结构。而实体只用它们的名字来表示(例如`Pig`、`Zombie`等)。 +::: 选择仅用一个方法而非多个 ---------------------- diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/gui/menus.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/gui/menus.md index 749afb34a..20562a039 100644 --- a/i18n/zh-CN/docusaurus-plugin-content-docs/current/gui/menus.md +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/gui/menus.md @@ -23,8 +23,9 @@ public MyMenu(int containerId, Inventory playerInv) { } ``` -!!! 注意 +:::caution 容器id对于单个玩家是唯一的。这意味着,两个不同玩家上的相同容器id将代表两个不同的菜单,即使他们正在查看相同的数据持有者。 +::: `MenuSupplier`通常负责在客户端上创建一个菜单,其中包含用于存储来自服务端数据持有者的同步信息并与之交互的伪数据引用。 @@ -97,8 +98,9 @@ public boolean stillValid(Player player) { Minecraft默认支持两种形式的数据同步:通过`Slot`进行的`ItemStack`同步和通过`DataSlot`进行的整数同步。`Slot`和`DataSlot`是保存对数据存储的引用的视图,假设操作有效,玩家可以在屏幕中修改这些数据存储。这些可以通过`#addSlot`和`#addDataSlot`在菜单的构造函数中添加。 -!!! 注意 +:::caution 由于`Slot`使用的`Container`已被Forge弃用,取而代之的是使用[`IItemHandler`功能][cap],因此其余解释将围绕使用功能变体:`SlotItemHandler`展开。 +::: `SlotItemHandler`包含四个参数:`IItemHandler`表示物品栈所在的物品栏,该Slot具体表示的物品栈索引,以及该Slot左上角将在屏幕上呈现的相对于`AbstractContainerScreen#leftPos`和`#topPos`的x和y位置。客户端菜单构造函数应该始终提供相同大小的物品栏的空实例。 @@ -108,8 +110,9 @@ Minecraft默认支持两种形式的数据同步:通过`Slot`进行的`ItemSta 每次初始化新菜单时,都应该重新创建上述内容以及Slot。 -!!! 警告 +:::danger 尽管`DataSlot`存储一个整数(int),但由于它在网络上发送数值的方式,它实际上被限制为**short**类型(-32768到32767)。该整数(int)的16个高比特位被忽略。 +::: ```java // 假设我们有一个来自大小为5的数据对象的物品栏 @@ -160,8 +163,9 @@ public MyMenuAccess(int containerId, Inventory playerInventory, ContainerData da } ``` -!!! 警告 +:::danger 由于`ContainerData`委托`DataSlot`,这些整数也被限制为**short**(-32768到32767)。 +::: #### `#quickMoveStack` @@ -261,8 +265,9 @@ public ItemStack quickMoveStack(Player player, int quickMovedSlotIndex) { 一旦注册了菜单类型,菜单本身已经完成,并且一个[屏幕(Screen)][screen]已被附加,玩家就可以打开菜单。可以通过在逻辑服务端上调用`NetworkHooks#openScreen`来打开菜单。该方法让玩家打开菜单,服务端端菜单的`MenuProvider`,如果需要将额外数据同步到客户端,还可以选择`FriendlyByteBuf`。 -!!! 注意 +:::caution 只有在使用[`IContainerFactory`][icf]创建菜单类型时,才应使用带有`FriendlyByteBuf`参数的`NetworkHooks#openScreen`。 +::: #### `MenuProvider` @@ -304,8 +309,9 @@ public InteractionResult use(BlockState state, Level level, BlockPos pos, Player } ``` -!!! 注意 +:::caution 这是实现逻辑的最简单的方法,而不是唯一的方法。如果你希望方块仅在特定条件下打开菜单,则需要提前将一些数据同步到客户端,以便在不满足条件的情况下返回`InteractionResult#PASS`或`#FAIL`。 +::: #### 生物的实现 @@ -325,8 +331,9 @@ public class MyMob extends Mob implements MenuProvider { } ``` -!!! 注意 +:::caution 再次说明,这是实现逻辑的最简单的方法,而不是唯一的方法。 +::: [registered]: ../concepts/registries.md#methods-for-registering [acm]: #abstractcontainermenu diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/gui/screens.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/gui/screens.md index f4c4daeff..95c9843a3 100644 --- a/i18n/zh-CN/docusaurus-plugin-content-docs/current/gui/screens.md +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/gui/screens.md @@ -31,8 +31,9 @@ Minecraft渲染的任何GUI通常都是使用`GuiGraphics`完成的。`GuiGraphi 字符串是通过其`Font`绘制的,通常由它们自己的普通、透视和偏移模式的着色器组成。可以渲染两种对齐的字符串,每种都有一个后阴影:左对齐字符串(`#drawString`)和居中对齐字符串(`#drawCenteredString`)。这两者都采用了字符串将被渲染的字体、要绘制的字符串、分别表示字符串左侧或中心的x坐标、顶部的y坐标和颜色。 -!!! 注意 +:::caution 字符串通常应作为[`Component`][component]传入,因为它们处理各种用例,包括方法的另外两个重载。 +::: ### 纹理 @@ -40,8 +41,9 @@ Minecraft渲染的任何GUI通常都是使用`GuiGraphics`完成的。`GuiGraphi 第一个静态`#blit`取六个整数,并假设渲染的纹理位于256 x 256 PNG文件上。它接受左侧x和顶部y屏幕坐标,PNG中的左侧x和底部y坐标,以及要渲染的图像的宽度和高度。 -!!! 注意 +:::caution 必须指定PNG文件的大小,以便可以规范化坐标以获得关联的UV值。 +::: 第一个`#blit`所调用的另一个静态`#blit`将参数扩展为九个整数,仅假设图像位于PNG文件上。它获取左侧x和顶部y屏幕坐标、z坐标(称为blit偏移)、PNG中的左侧x和上部y坐标、要渲染的图像的宽度和高度以及PNG文件的宽度和高。 @@ -72,8 +74,9 @@ Minecraft渲染的任何GUI通常都是使用`GuiGraphics`完成的。`GuiGraphi 聚焦允许在事件执行期间,例如在键盘事件或拖动鼠标期间,首先检查并处理特定的子项。焦点通常通过`#setFocused`设置。此外,可以使用`#nextFocusPath`循环可交互的子级,根据传入的`FocusNavigationEvent`选择子级。 -!!! 注意 +:::caution 屏幕通过`AbstractContainerEventHandler`实现了`ContainerEventHandler`和`GuiComponent`,添加了setter和getter逻辑用于拖动和聚焦子级。 +::: ## NarratableEntry @@ -81,8 +84,9 @@ Minecraft渲染的任何GUI通常都是使用`GuiGraphics`完成的。`GuiGraphi `NarratableEntry`有三种方法:一种是确定元素的优先级(`#narrationPriority`),一种是决定是否说出讲述(`#isActive`),最后一种是将讲述提供给相关的输出(说出或读取)(`#updateNarration`)。 -!!! 注意 +:::caution Minecraft中的所有小部件都是`NarratableEntry`,因此如果使用可用的子类型,通常不需要手动实现。 +::: ## 屏幕子类型 @@ -304,8 +308,9 @@ protected void renderLabels(GuiGraphics graphics, int mouseX, int mouseY) { } ``` -!!! 注意 +:::caution 渲染标签时,**不**需要指定`leftPos`和`topPos`偏移量。这些已经在`PoseStack`中进行了转换,因此该方法中的所有内容都是相对于这些坐标绘制的。 +::: ## 注册一个AbstractContainerScreen @@ -322,8 +327,9 @@ private void clientSetup(FMLClientSetupEvent event) { } ``` -!!! 警告 +:::danger `MenuScreens#register`不是线程安全的,因此它需要在并行调度事件提供的`#enqueueWork`内部调用。 +::: [menus]: ./menus.md [network]: ../networking/index.md diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/items/bewlr.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/items/bewlr.md index ef7d5c23d..0ea3edffa 100644 --- a/i18n/zh-CN/docusaurus-plugin-content-docs/current/items/bewlr.md +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/items/bewlr.md @@ -9,8 +9,9 @@ BlockEntityWithoutLevelRenderer允许你使用`public void renderByItem(ItemStac 为了使用BEWLR,`Item`必须首先满足其模型的`BakedModel#isCustomRenderer`返回true。如果没有,它将使用默认的`ItemRenderer#getBlockEntityRenderer`。一旦返回true,将访问该Item的BEWLR进行渲染。 -!!! 注意 +:::caution 如果`Block#getRenderShape`设置为`RenderShape#ENTITYBLOCK_ANIMATED`,`Block`也会使用BEWLR进行渲染。 +::: 若要设置物品的BEWLR,必须在`Item#initializeClient`中使用`IClientItemExtensions`的一个匿名实例。在该匿名实例中,应重写`IClientItemExtensions#getCustomRenderer`以返回你的BEWLR的实例: diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/misc/config.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/misc/config.md index 514905531..9840affcc 100644 --- a/i18n/zh-CN/docusaurus-plugin-content-docs/current/misc/config.md +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/misc/config.md @@ -13,8 +13,9 @@ `build` | 创建`ForgeConfigSpec`. `configure` | 创建一对包含配置值和`ForgeConfigSpec`的类。 -!!! 注意 +:::caution `ForgeConfigSpec$Builder#configure`通常与`static`块和一个类一起使用,该类将`ForgeConfigSpec$Builder`作为其构造函数的一部分,用于附加和保存值: +::: ```java // 在某个配置类中 @@ -70,8 +71,9 @@ ConfigValue value = builder.comment("Comment") * 配置值可能的最小值和最大值 * 表示配置值的数据类型的类 -!!! 注意 +:::caution `DoubleValue`、`IntValue`和`LongValue`是将类型分别指定为`Double`、`Integer`和`Long`的范围值。 +::: * **白名单值** * 描述: 值必须在所提供的集合中 @@ -118,16 +120,18 @@ CLIENT | 仅在客户端 | 否 | `.minecraft/config` COMMON | 在两端 | 否 | `.minecraft/config` | `/config` | `-common` SERVER | 仅在服务端 | 是 | `.minecraft/saves//serverconfig` | `/world/serverconfig` | `-server` -!!! 提示 +:::tip Forge在相应的代码库中用文档详述了[配置类型][type]。 +::: 配置事件 -------- 每当加载或重新加载配置时发生的操作可以使用`ModConfigEvent$Loading`和`ModConfigEvent$Reloading`事件来完成。事件必须[注册][events]到模组事件总线。 -!!! 警告 +:::danger 这些事件对于模组的所有配置都被调用;所提供的`ModConfig`对象应被用于表示正在加载或重新加载哪个配置。 +::: [toml]: https://toml.io/ [nightconfig]: https://github.com/TheElectronWill/night-config diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/misc/debugprofiler.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/misc/debugprofiler.md index bcfb23bbe..efa99119e 100644 --- a/i18n/zh-CN/docusaurus-plugin-content-docs/current/misc/debugprofiler.md +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/misc/debugprofiler.md @@ -6,8 +6,9 @@ Minecraft提供了一个调试分析器,它提供系统数据、当前游戏 调试分析器使用起来非常简单。它需要调试绑定键`F3 + L`来启动分析器。10秒后,它将自动停止;但是,可以通过再次按绑定键提前停止。 -!!! 注意 +:::caution 当然,你只能分析实际到达的代码路径。要分析的实体和方块实体必须存在于存档中才能显示在结果中。 +::: 停止调试器后,它将在运行目录中的`debug/profiling`子目录中创建一个新的zip。 文件名的格式为日期和时间`yyyy-mm-dd_hh_mi_ss-WorldName-VersionNumber.zip` diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/misc/gametest.mdx b/i18n/zh-CN/docusaurus-plugin-content-docs/current/misc/gametest.mdx index 8de5aab5e..09de0690a 100644 --- a/i18n/zh-CN/docusaurus-plugin-content-docs/current/misc/gametest.mdx +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/misc/gametest.mdx @@ -46,12 +46,13 @@ public static void exampleConfiguredTest(GameTestHelper helper) { 结构模板的相对位置可以在游戏中通过[test命令][test]加载结构,将玩家放置在所需位置,最后运行`/test pos`命令来获得。这将获取玩家相对于玩家200个方块内最近结构的坐标。该命令将相对位置导出为聊天中的可复制文本组件,用作最终的本地变量。 -!!! 提示 +:::tip `/test pos`生成的局部变量可以通过将其附加到命令末尾来指定其引用名称: ```bash /test pos # 导出'final BlockPos = new BlockPos(...);' ``` +::: #### 成功完成 @@ -100,8 +101,9 @@ public class ExampleGameTests { `TestFunction`是`@GameTest`注释和运行测试的方法所包含的包装信息。 -!!! 提示 +:::tip 任何使用`@GameTest`注释的方法都会使用`GameTestRegistry#turnMethodIntoTestFunction`转换为`TestFunction`。该方法可以用作创建`TestFunction`的引用,而无需使用注释。 +::: ### 批量处理 @@ -158,16 +160,18 @@ public static void exampleTest3(GameTestHelper helper) { } ``` -!!! 注意 +:::caution 提供给`GameTestHolder#value`和`GameTest#templateNamespace`的值可能与当前的mod id不同。需要更改[buildscript][namespaces]中的配置。 +::: 结构模板 -------- 游戏测试是在由结构或模板加载的场景中执行的。所有模板都定义了场景的尺寸以及将要加载的初始数据(方块和实体)。模板必须存储为`data//structures`中的`.nbt`文件。 -!!! 提示 +:::tip 可以使用结构方块创建和保存结构模板。 +::: 模板的位置由以下几个因素指定: @@ -223,8 +227,9 @@ public class ExampleGameTests { `runthese` | 运行离玩家200个方块内的测试。 `runfailed` | 运行上一次运行中失败的所有测试。 -!!! 注意 +:::caution 子命令跟在test命令后面:`/test `。 +::: 构建脚本(buildscript)配置 -------------------------- @@ -240,8 +245,9 @@ public class ExampleGameTests { property 'forge.enabledGameTestNamespaces', 'modid1,modid2,modid3' ``` -!!! 警告 +:::danger 命名空间之间不能有空格;否则,将无法正确加载命名空间。 +::: ### 游戏测试服务端运行配置 diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/misc/keymappings.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/misc/keymappings.md index 9017aebb0..a806859f0 100644 --- a/i18n/zh-CN/docusaurus-plugin-content-docs/current/misc/keymappings.md +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/misc/keymappings.md @@ -23,8 +23,9 @@ public void registerBindings(RegisterKeyMappingsEvent event) { `KeyMapping`可以使用其构造函数创建。`KeyMapping`接受一个定义映射名称的[翻译键][tk],映射的默认输入,以及定义映射将放在[控制选项菜单][controls]中的类别的[翻译键][tk]。 -!!! 提示 +:::tip 通过提供原版未提供的类别[翻译键][tk],可以将`KeyMapping`添加到自定义类别中。自定义类别转换键应包含mod id(例如`key.categories.examplemod.examplecategory`)。 +::: ### 默认输入 @@ -32,8 +33,9 @@ public void registerBindings(RegisterKeyMappingsEvent event) { 原版提供三种类型的输入:`KEYSYM`,通过提供的`GLFW`键标记定义键盘,`SCANCODE`,通过平台特定扫描码定义键盘,以及`MOUSE`,定义鼠标。 -!!! 注意 +:::caution 强烈建议键盘使用`KEYSYM`而不是`SCANCODE`,因为`GLFW`键令牌不与任何特定系统绑定。你可以在[GLFW文档][keyinput]上阅读更多内容。 +::: 整数取决于提供的类型。所有输入代码都在`GLFW`中定义:`KEYSYM`令牌以`GLFW_KEY_*`为前缀,而`MOUSE`代码以`GLFW_MOUSE_*`作为前缀。 @@ -46,8 +48,9 @@ new KeyMapping( ) ``` -!!! 注意 +:::caution 如果键映射不应映射到默认值,则应将输入设置为`InputConstants#UNKNOWN`。原版构造函数将要求你通过`InputConstants$Key#getValue`提取输入代码,而Forge构造函数可以提供原始输入字段。 +::: ### `IKeyConflictContext` @@ -103,8 +106,9 @@ public void onClientTick(ClientTickEvent event) { } ``` -!!! 警告 +:::danger 不要将`InputEvent`用作`ClientTickEvent`的替代项。只有键盘和鼠标输入有单独的事件,所以它们不会处理任何额外的输入。 +::: ### Inside a GUI @@ -124,8 +128,9 @@ public boolean keyPressed(int key, int scancode, int mods) { } ``` -!!! 注意 +:::caution 如果你不拥有要检查**键**的屏幕,你可以在[**Forge事件总线**][forgebus]上监听`ScreenEvent$KeyPressed`的`Pre`或`Post`事件。 +::: `#mouseClicked`获取鼠标的x位置、y位置和单击的按钮。通过使用带有`MOUSE`输入的`InputConstants$Type#getOrCreate`创建输入,可以根据映射检查鼠标按钮。 @@ -141,8 +146,9 @@ public boolean mouseClicked(double x, double y, int button) { } ``` -!!! 注意 +:::caution 如果你不拥有要检查**鼠标**的屏幕,你可以在[**Forge事件总线**][forgebus]上监听`ScreenEvent$MouseButtonPressed`的`Pre`或`Post`事件。 +::: [modbus]: ../concepts/events.md#mod-event-bus [controls]: https://minecraft.fandom.com/wiki/Options#Controls diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/networking/entities.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/networking/entities.md index 0bdab032f..8051f28be 100644 --- a/i18n/zh-CN/docusaurus-plugin-content-docs/current/networking/entities.md +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/networking/entities.md @@ -8,8 +8,9 @@ 一般来说,由模组编写的实体的生成是由Forge单独处理的。 -!!! 注意 +:::caution 这意味着简单地继承一个原版实体类可能不会继承它的所有行为。你可能需要自己实施某些原版行为。 +::: 你可以通过实现以下接口向Forge发送的生成数据包添加额外的数据。 @@ -26,9 +27,10 @@ 首先,对于要保持同步的数据,你需要一个`EntityDataAccessor`。这应该存储为你的实体类中的`static final`字段,通过调用`SynchedEntityData#defineId`并传递实体类和该类型数据的序列化器来获得。可用的序列化器实现可以在`EntityDataSerializers`类中的静态常量找到。 -!!! 警告 +:::danger 你应该 __只__ _在相应实体的类_ 中为自己的实体创建数据参数。 向并非你所控制的实体添加参数可能会导致用于通过网络发送数据的ID不同步,从而导致难以调试的崩溃。 +::: 然后,重写`Entity#defineSynchedData`并为每个数据参数调用`this.entityData.define(...)`,传递参数和要使用的初始值。请记住始终首先调用`super`方法! diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/networking/simpleimpl.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/networking/simpleimpl.md index d8cb0b480..53e10f0bd 100644 --- a/i18n/zh-CN/docusaurus-plugin-content-docs/current/networking/simpleimpl.md +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/networking/simpleimpl.md @@ -80,17 +80,19 @@ public static void handlePacket(MyClientMessage msg, Supplier getSerializer() { } ``` -!!! 提示 +:::tip 有一些有用的方法可以让配方的读写数据变得更容易。`Ingredient`可以使用`#fromJson`、`#toNetwork`和`#fromNetwork`,而`ItemStack`可以使用`CraftingHelper#getItemStack`、`FriendlyByteBuf#writeItem`和`FriendlyByteBuf#readItem`。 +::: 构建JSON -------- diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/resources/server/recipes/incode.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/resources/server/recipes/incode.md index 4778347c6..5f468297c 100644 --- a/i18n/zh-CN/docusaurus-plugin-content-docs/current/resources/server/recipes/incode.md +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/resources/server/recipes/incode.md @@ -8,8 +8,9 @@ 酿造是代码中为数不多的仍然存在的配方之一。酿造配方是作为`PotionBrewing`中的引导程序的一部分添加的,用于容器、容器配方和药水混合物。为了扩展现有系统,Forge允许通过在`FMLCommonSetupEvent`中调用`BrewingRecipeRegistry#addRecipe`来添加酿造配方。 -!!! 警告 +:::danger `BrewingRecipeRegistry#addRecipe`必须在同步工作队列中通过`#enqueueWork`调用,因为该方法不是线程安全的。 +::: 默认实现接受标准实现的输入成分、催化剂成分和物品栈输出。此外,还可以提供一个`IBrewingRecipe`实例来执行转换。 diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/resources/server/recipes/index.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/resources/server/recipes/index.md index 4c8c4f5c5..a1a173825 100644 --- a/i18n/zh-CN/docusaurus-plugin-content-docs/current/resources/server/recipes/index.md +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/resources/server/recipes/index.md @@ -73,8 +73,9 @@ Forge为配方纲要及其实现提供了一些额外的行为,以更好地控 } ``` -!!! 注意 +:::caution `nbt`标签也可以是一个字符串,其中包含无法正确表示为JSON对象(如`IntArrayTag`)的数据的字符串化NBT(或SNBT)。 +::: ### 条件性配方 @@ -84,8 +85,9 @@ Forge为配方纲要及其实现提供了一些额外的行为,以更好地控 默认情况下,原版声明合成网格的最大宽度和高度为3x3正方形。这可以通过在`FMLCommonSetupEvent`中使用新的宽度和高度调用`ShapedRecipe#setCraftingSize`来扩展。 -!!! 警告 +:::danger `ShapedRecipe#setCraftingSize`**不**是线程安全的。因此,它应该通过`FMLCommonSetupEvent#enqueueWork`排入同步工作队列。 +::: 配方中较大的合成网格可以是[数据生成的][datagen]。 diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/resources/server/recipes/ingredients.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/resources/server/recipes/ingredients.md index 6ca6ab75a..534038177 100644 --- a/i18n/zh-CN/docusaurus-plugin-content-docs/current/resources/server/recipes/ingredients.md +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/resources/server/recipes/ingredients.md @@ -118,8 +118,9 @@ Forge提供了一些额外的`Ingredient`类型供程序员实现。 可以通过为创建的`Ingredient`子类实现`IIngredientSerializer`来创建自定义原料。 -!!! 提示 +:::tip 自定义原料应该是`AbstractIngredient`的子类,因为它提供了一些有用的抽象以便于实现。 +::: ### 原料的子类 @@ -165,8 +166,9 @@ public IIngredientSerializer getSerializer() { } ``` -!!! 提示 +:::tip 如果使用`FMLCommonSetupEvent`注册原料序列化器,则必须通过`FMLCommonSetupEvent#enqueueWork`将其排入同步工作队列,因为`CraftingHelper#register`不是线程安全的。 +::: [recipes]: https://minecraft.fandom.com/wiki/Recipe#List_of_recipe_types [nbt]: #strictnbtingredient diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/resources/server/tags.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/resources/server/tags.md index a6f21ed0f..212e3514e 100644 --- a/i18n/zh-CN/docusaurus-plugin-content-docs/current/resources/server/tags.md +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/resources/server/tags.md @@ -35,8 +35,9 @@ --------------- 登录和重新加载时,所有注册表的标签都会自动从服务器发送到任何远程客户端。`Block`、`Item`、`EntityType`、`Fluid`和`GameEvent`都被特殊地包装,因为它们具有`Holder`,允许通过对象本身访问可用标签。 -!!! 注意 +:::caution 在未来版本的Minecraft中,侵入性的`Holder`可能会被移除。如果被移除了,则可以使用以下方法来查询关联的`Holder`。 +::: ### ITagManager From 679610b1b57c9f51d82b45772b8280c0a462e5b9 Mon Sep 17 00:00:00 2001 From: src_resources Date: Wed, 11 Oct 2023 16:54:23 +0800 Subject: [PATCH 08/15] Sync changes from official de4f6dcf Update Minecraft Wiki links to new domain after fork (#12) Signed-off-by: src_resources --- .../current/concepts/internationalization.md | 2 +- .../current/datagen/client/localization.md | 2 +- .../current/datagen/client/modelproviders.md | 2 +- .../current/datagen/index.md | 4 ++-- .../current/gameeffects/sounds.md | 2 +- .../current/misc/keymappings.md | 2 +- .../current/rendering/modelextensions/transforms.md | 2 +- .../current/resources/client/index.md | 4 ++-- .../current/resources/client/models/index.md | 8 ++++---- .../current/resources/client/models/itemproperties.md | 2 +- .../current/resources/client/models/tinting.md | 2 +- .../current/resources/server/advancements.md | 8 ++++---- .../current/resources/server/index.md | 4 ++-- .../current/resources/server/loottables.md | 4 ++-- .../current/resources/server/recipes/custom.md | 2 +- .../current/resources/server/recipes/index.md | 4 ++-- .../current/resources/server/recipes/ingredients.md | 2 +- .../current/resources/server/tags.md | 8 ++++---- 18 files changed, 32 insertions(+), 32 deletions(-) diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/concepts/internationalization.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/concepts/internationalization.md index 5261ff671..7a6a1ed0a 100644 --- a/i18n/zh-CN/docusaurus-plugin-content-docs/current/concepts/internationalization.md +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/concepts/internationalization.md @@ -65,5 +65,5 @@ Block、Item和其他一些Minecraft类都内置了用于显示其名称的翻 - `createComponentTranslation(CommandSource, String, Object...)`根据接收者创建本地化并格式化的`MutableComponent`。如果接收者是一个原版客户端,那么本地化和格式化就很容易完成。如果没有,本地化和格式化将使用包含`TranslatableContents`的`Component`惰性地进行。只有当服务端允许原版客户端连接时,这才有用。 -[langs]: https://minecraft.fandom.com/wiki/Language#Languages +[langs]: https://minecraft.wiki/w/Language#Languages [converter]: https://tterrag.com/lang2json/ diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/datagen/client/localization.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/datagen/client/localization.md index 385a82146..ae0111a2c 100644 --- a/i18n/zh-CN/docusaurus-plugin-content-docs/current/datagen/client/localization.md +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/datagen/client/localization.md @@ -37,5 +37,5 @@ this.add("object.examplemod.example_object", "Example Object"); ::: [lang]: ../../concepts/internationalization.md -[locale]: https://minecraft.fandom.com/wiki/Language#Languages +[locale]: https://minecraft.wiki/w/Language#Languages [datagen]: ../index.md#data-providers diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/datagen/client/modelproviders.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/datagen/client/modelproviders.md index aa8635522..b68146cc5 100644 --- a/i18n/zh-CN/docusaurus-plugin-content-docs/current/datagen/client/modelproviders.md +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/datagen/client/modelproviders.md @@ -419,7 +419,7 @@ public CompletableFuture run(CachedOutput cache) { [color]: ../../resources/client/models/tinting.md#blockcoloritemcolor [overrides]: ../../resources/client/models/itemproperties.md [blockstateprovider]: #block-state-provider -[blockstate]: https://minecraft.fandom.com/wiki/Tutorials/Models#Block_states +[blockstate]: https://minecraft.wiki/w/Tutorials/Models#Block_states [blockmodels]: #blockmodelprovider [itemmodels]: #itemmodelprovider [properties]: ../../blocks/states.md#implementing-block-states diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/datagen/index.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/datagen/index.md index 35bb5aea7..1a67f3573 100644 --- a/i18n/zh-CN/docusaurus-plugin-content-docs/current/datagen/index.md +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/datagen/index.md @@ -65,9 +65,9 @@ MDK的`build.gradle`中的默认配置添加了用于运行数据生成器的`ru * [`advancements.AdvancementProvider`][advgen] - 针对[进度];向构造函数传递`AdvancementSubProvider` [langgen]: ./client/localization.md -[lang]: https://minecraft.fandom.com/wiki/Language +[lang]: https://minecraft.wiki/w/Language [soundgen]: ./client/sounds.md -[sounds]: https://minecraft.fandom.com/wiki/Sounds.json +[sounds]: https://minecraft.wiki/w/Sounds.json [modelgen]: ./client/modelproviders.md [models]: ../resources/client/models/index.md [itemmodelgen]: ./client/modelproviders.md#itemmodelprovider diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/gameeffects/sounds.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/gameeffects/sounds.md index 64f044d6a..bed0fc18a 100644 --- a/i18n/zh-CN/docusaurus-plugin-content-docs/current/gameeffects/sounds.md +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/gameeffects/sounds.md @@ -105,7 +105,7 @@ - **用法**: 就像`Level`中的方法一样,玩家类中的这两个重写似乎是针对在两端同时运行的代码。客户端处理向用户播放音效,而服务端处理其他所有听到音效的人,而不向原始用户重新播放。 [loc]: ../concepts/resources.md#resourcelocation -[wiki]: https://minecraft.fandom.com/wiki/Sounds.json +[wiki]: https://minecraft.wiki/w/Sounds.json [datagen]: ../datagen/client/sounds.md [registration]: ../concepts/registries.md#methods-for-registering [sides]: ../concepts/sides.md diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/misc/keymappings.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/misc/keymappings.md index a806859f0..4601ae010 100644 --- a/i18n/zh-CN/docusaurus-plugin-content-docs/current/misc/keymappings.md +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/misc/keymappings.md @@ -151,7 +151,7 @@ public boolean mouseClicked(double x, double y, int button) { ::: [modbus]: ../concepts/events.md#mod-event-bus -[controls]: https://minecraft.fandom.com/wiki/Options#Controls +[controls]: https://minecraft.wiki/w/Options#Controls [tk]: ../concepts/internationalization.md#translatablecontents [keyinput]: https://www.glfw.org/docs/3.3/input_guide.html#input_key [forgebus]: ../concepts/events.md#creating-an-event-handler diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/rendering/modelextensions/transforms.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/rendering/modelextensions/transforms.md index eb0f2eccf..abfa0b03f 100644 --- a/i18n/zh-CN/docusaurus-plugin-content-docs/current/rendering/modelextensions/transforms.md +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/rendering/modelextensions/transforms.md @@ -72,5 +72,5 @@ 比例必须指定为表示三维矢量的3个浮点数的数组:`[ x, y, z ]`,如果不存在,则默认为(1, 1, 1)。 -[blockstate]: https://minecraft.fandom.com/wiki/Tutorials/Models#Block_states +[blockstate]: https://minecraft.wiki/w/Tutorials/Models#Block_states [displaytransform]: ../modelloaders/transform.md \ No newline at end of file diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/resources/client/index.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/resources/client/index.md index f29a9f4e3..1132d5ff2 100644 --- a/i18n/zh-CN/docusaurus-plugin-content-docs/current/resources/client/index.md +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/resources/client/index.md @@ -10,6 +10,6 @@ 附加阅读:[资源位置][resourcelocation] -[respack]: https://minecraft.fandom.com/wiki/Resource_Pack -[createrespack]: https://minecraft.fandom.com/wiki/Tutorials/Creating_a_resource_pack +[respack]: https://minecraft.wiki/w/Resource_Pack +[createrespack]: https://minecraft.wiki/w/Tutorials/Creating_a_resource_pack [resourcelocation]: ../../concepts/resources.md#ResourceLocation diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/resources/client/models/index.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/resources/client/models/index.md index 9b9345a52..a4c244e48 100644 --- a/i18n/zh-CN/docusaurus-plugin-content-docs/current/resources/client/models/index.md +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/resources/client/models/index.md @@ -16,10 +16,10 @@ 纹理和模型一样,包含在资源包中,并被称为`ResourceLocation`。在《我的世界》中,[UV坐标][UV] (0,0)表示**左上角**。UV*总是*从0到16。如果纹理较大或较小,则会缩放坐标以进行拟合。纹理也应该是正方形的,纹理的边长应该是2的幂,否则会破坏mipmapping(例如1x1、2x2、8x8、16x16和128x128是好的。不建议使用5x5和30x30,因为它们不是2的幂。5x10和4x8会完全断裂,因为它们不是正方形的。)。只有当纹理是[动画化的][animated]时,纹理才应该不是正方形。 -[models]: https://minecraft.fandom.com/wiki/Tutorials/Models#File_path +[models]: https://minecraft.wiki/w/Tutorials/Models#File_path [resloc]: ../../../concepts/resources.md#resourcelocation -[statemodel]: https://minecraft.fandom.com/wiki/Tutorials/Models#Block_states -[itemmodels]: https://minecraft.fandom.com/wiki/Tutorials/Models#Item_models +[statemodel]: https://minecraft.wiki/w/Tutorials/Models#Block_states +[itemmodels]: https://minecraft.wiki/w/Tutorials/Models#Item_models [state]: ../../../blocks/states.md [uv]: https://en.wikipedia.org/wiki/UV_mapping -[animated]: https://minecraft.fandom.com/wiki/Resource_Pack?so=search#Animation +[animated]: https://minecraft.wiki/w/Resource_Pack?so=search#Animation diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/resources/client/models/itemproperties.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/resources/client/models/itemproperties.md index d5b97a087..b81c55b59 100644 --- a/i18n/zh-CN/docusaurus-plugin-content-docs/current/resources/client/models/itemproperties.md +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/resources/client/models/itemproperties.md @@ -60,4 +60,4 @@ private void setup(final FMLClientSetupEvent event) } ``` -[format]: https://minecraft.fandom.com/wiki/Tutorials/Models#Item_models +[format]: https://minecraft.wiki/w/Tutorials/Models#Item_models diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/resources/client/models/tinting.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/resources/client/models/tinting.md index 7dc313bff..e15a521b1 100644 --- a/i18n/zh-CN/docusaurus-plugin-content-docs/current/resources/client/models/tinting.md +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/resources/client/models/tinting.md @@ -29,4 +29,4 @@ public void registerItemColors(RegisterColorHandlersEvent.Item event){ } ``` -[wiki]: https://minecraft.fandom.com/wiki/Tutorials/Models#Block_models +[wiki]: https://minecraft.wiki/w/Tutorials/Models#Block_models diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/resources/server/advancements.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/resources/server/advancements.md index 7da3120c4..aa2386ec3 100644 --- a/i18n/zh-CN/docusaurus-plugin-content-docs/current/resources/server/advancements.md +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/resources/server/advancements.md @@ -159,8 +159,8 @@ public void performExampleAction(ServerPlayer player, ItemStack stack) { } ``` -[datapack]: https://minecraft.fandom.com/wiki/Data_pack -[wiki]: https://minecraft.fandom.com/wiki/Advancement/JSON_format +[datapack]: https://minecraft.wiki/w/Data_pack +[wiki]: https://minecraft.wiki/w/Advancement/JSON_format [conditional]: ./conditional.md#implementations -[function]: https://minecraft.fandom.com/wiki/Function_(Java_Edition) -[triggers]: https://minecraft.fandom.com/wiki/Advancement/JSON_format#List_of_triggers +[function]: https://minecraft.wiki/w/Function_(Java_Edition) +[triggers]: https://minecraft.wiki/w/Advancement/JSON_format#List_of_triggers diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/resources/server/index.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/resources/server/index.md index d8b755b90..afff20a0a 100644 --- a/i18n/zh-CN/docusaurus-plugin-content-docs/current/resources/server/index.md +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/resources/server/index.md @@ -9,6 +9,6 @@ 附加阅读:[资源位置][resourcelocation] -[datapack]: https://minecraft.fandom.com/wiki/Data_pack -[createdatapack]: https://minecraft.fandom.com/wiki/Tutorials/Creating_a_data_pack +[datapack]: https://minecraft.wiki/w/Data_pack +[createdatapack]: https://minecraft.wiki/w/Tutorials/Creating_a_data_pack [resourcelocation]: ../../concepts/resources.md#ResourceLocation diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/resources/server/loottables.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/resources/server/loottables.md index 2340416d9..2216ddc3d 100644 --- a/i18n/zh-CN/docusaurus-plugin-content-docs/current/resources/server/loottables.md +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/resources/server/loottables.md @@ -105,7 +105,7 @@ Forge添加了一个额外的`LootItemCondition`,用于检查给定的`LootCon } ``` -[datapack]: https://minecraft.fandom.com/wiki/Data_pack -[wiki]: https://minecraft.fandom.com/wiki/Loot_table +[datapack]: https://minecraft.wiki/w/Data_pack +[wiki]: https://minecraft.wiki/w/Loot_table [event]: ../../concepts/events.md#creating-an-event-handler [glm]: ./glm.md diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/resources/server/recipes/custom.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/resources/server/recipes/custom.md index adad01c07..44f6cb745 100644 --- a/i18n/zh-CN/docusaurus-plugin-content-docs/current/resources/server/recipes/custom.md +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/resources/server/recipes/custom.md @@ -126,6 +126,6 @@ public Optional getRecipeFor(Level level, BlockPos pos) { 所有自定义配方,无论输入或输出数据如何,都可以使用`RecipeProvider`创建到用于[数据生成][datagen]的`FinishedRecipe`中。 [forge]: ../../../concepts/registries.md#methods-for-registering -[json]: https://minecraft.fandom.com/wiki/Recipe#JSON_format +[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-CN/docusaurus-plugin-content-docs/current/resources/server/recipes/index.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/resources/server/recipes/index.md index a1a173825..44b55a432 100644 --- a/i18n/zh-CN/docusaurus-plugin-content-docs/current/resources/server/recipes/index.md +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/resources/server/recipes/index.md @@ -95,8 +95,8 @@ Forge为配方纲要及其实现提供了一些额外的行为,以更好地控 一些额外的[原料类型][ingredients]被添加,以允许配方具有检查标签数据或将多种原料组合到单个输入检查器中的输入。 -[datapack]: https://minecraft.fandom.com/wiki/Data_pack -[wiki]: https://minecraft.fandom.com/wiki/Recipe +[datapack]: https://minecraft.wiki/w/Data_pack +[wiki]: https://minecraft.wiki/w/Recipe [advancement]: ../advancements.md [datagen]: ../../../datagen/server/recipes.md [cap]: ../../../datastorage/capabilities.md diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/resources/server/recipes/ingredients.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/resources/server/recipes/ingredients.md index 534038177..8e09a9c6e 100644 --- a/i18n/zh-CN/docusaurus-plugin-content-docs/current/resources/server/recipes/ingredients.md +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/resources/server/recipes/ingredients.md @@ -170,7 +170,7 @@ public IIngredientSerializer getSerializer() { 如果使用`FMLCommonSetupEvent`注册原料序列化器,则必须通过`FMLCommonSetupEvent#enqueueWork`将其排入同步工作队列,因为`CraftingHelper#register`不是线程安全的。 ::: -[recipes]: https://minecraft.fandom.com/wiki/Recipe#List_of_recipe_types +[recipes]: https://minecraft.wiki/w/Recipe#List_of_recipe_types [nbt]: #strictnbtingredient [serializer]: #iingredientserializer [compound]: #compoundingredient diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/resources/server/tags.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/resources/server/tags.md index 212e3514e..35daa718f 100644 --- a/i18n/zh-CN/docusaurus-plugin-content-docs/current/resources/server/tags.md +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/resources/server/tags.md @@ -114,8 +114,8 @@ boolean isInVillagerTypeGroup = BuiltInRegistries.VILLAGER_TYPE.getHolder(villag 原版直接支持标签。有关用法的详细信息,请参阅[配方][recipes]和[进度][advancements]的原版wiki页面。 [datapack]: ./index.md -[tags]: https://minecraft.fandom.com/wiki/Tag#JSON_format -[taglist]: https://minecraft.fandom.com/wiki/Tag#List_of_tags +[tags]: https://minecraft.wiki/w/Tag#JSON_format +[taglist]: https://minecraft.wiki/w/Tag#List_of_tags [forgetags]: https://github.com/MinecraftForge/MinecraftForge/tree/1.19.x/src/generated/resources/data/forge/tags -[recipes]: https://minecraft.fandom.com/wiki/Recipe#JSON_format -[advancements]: https://minecraft.fandom.com/wiki/Advancement +[recipes]: https://minecraft.wiki/w/Recipe#JSON_format +[advancements]: https://minecraft.wiki/w/Advancement From 25a967c6a03e80a3cbb3993f9cd18a0b93406a92 Mon Sep 17 00:00:00 2001 From: src_resources Date: Wed, 11 Oct 2023 17:05:43 +0800 Subject: [PATCH 09/15] Sync translations from official 16dfa809 Rebrand accesstransformers.md (#3) Signed-off-by: src_resources --- .../current/advanced/accesstransformers.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/advanced/accesstransformers.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/advanced/accesstransformers.md index 03cc46da9..5355ca784 100644 --- a/i18n/zh-CN/docusaurus-plugin-content-docs/current/advanced/accesstransformers.md +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/advanced/accesstransformers.md @@ -3,7 +3,7 @@ 访问转换器(简称AT)允许扩大可见性并修改类、方法和字段的`final`标志。它们允许模组开发者访问和修改其控制之外的类中不可访问的成员。 -[规范文档][specs]可以在Minecraft Forge GitHub上查看。 +[规范文档][specs]可以在NeoForged GitHub上查看。 添加AT ------ @@ -109,5 +109,5 @@ public net.minecraft.Util m_137477_(Ljava/lang/String;)Ljava/util/concurrent/Exe public net.minecraft.core.UUIDUtil m_235872_(JJ)[I #leastMostToIntArray ``` -[specs]: https://github.com/MinecraftForge/AccessTransformers/blob/master/FMLAT.md +[specs]: https://github.com/NeoForged/AccessTransformers/blob/master/FMLAT.md [jvmdescriptors]: https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.3.2 From 61013502a706341b4d1bcc3ebdef745e31c55df5 Mon Sep 17 00:00:00 2001 From: src_resources Date: Wed, 11 Oct 2023 18:23:46 +0800 Subject: [PATCH 10/15] Sync translations for zh-CN from official b9d295fd Add explanation for Component and ComponentContents (#4) Signed-off-by: src_resources --- .../current/concepts/internationalization.md | 10 ++- .../current/misc/components.md | 88 +++++++++---------- 2 files changed, 50 insertions(+), 48 deletions(-) diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/concepts/internationalization.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/concepts/internationalization.md index 7a6a1ed0a..52b5cba98 100644 --- a/i18n/zh-CN/docusaurus-plugin-content-docs/current/concepts/internationalization.md +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/concepts/internationalization.md @@ -44,7 +44,7 @@ Block、Item和其他一些Minecraft类都内置了用于显示其名称的翻 :::danger 一个常见的问题是让服务端为客户端进行本地化。服务端只能在自己的语言设置中进行本地化,这不一定与所连接的客户端的语言设置相匹配。 - 为了尊重客户端的语言设置,服务端应该让客户端使用`TranslatableComponent`或其他保留语言中性翻译键的方法在自己的语言设置中本地化文本。 + 为了尊重客户端的语言设置,服务端应该让客户端使用`TranslatableContents`或其他保留语言中性翻译键的方法在自己的语言设置中本地化文本。 ::: ### `net.minecraft.client.resources.language.I18n` (仅客户端) @@ -57,13 +57,15 @@ Block、Item和其他一些Minecraft类都内置了用于显示其名称的翻 `TranslatableContents`是一个经过惰性的本地化和格式化的`ComponentContents`。它在向玩家发送消息时非常有用,因为它将在玩家自己的语言设置中自动本地化。 -`TranslatableContents(String, Object...)`构造函数的第一个参数是翻译键,其余参数用于格式化。唯一支持的格式说明符是`%s`和`%1$s`、`%2$s`、`%3$s`等。格式化参数可能是将插入到格式化结果文本中并保留其所有属性的`Component`。 +`TranslatableContents(String, Object...)`构造函数的第一个参数是翻译键,其余参数用于[格式化][formatting]。 -通过传入`TranslatableContents`的参数,可以使用`Component#translatable`创建`MutableComponent`。它也可以使用`MutableComponent#create`通过传入`ComponentContents`本身来创建。 +通过传入`TranslatableContents`的参数,可以使用`Component#translatable`创建`MutableComponent`。它也可以使用`MutableComponent#create`通过传入`ComponentContents`本身来创建。阅读[组件][components]以了解更多详细信息。 ### `TextComponentHelper` -- `createComponentTranslation(CommandSource, String, Object...)`根据接收者创建本地化并格式化的`MutableComponent`。如果接收者是一个原版客户端,那么本地化和格式化就很容易完成。如果没有,本地化和格式化将使用包含`TranslatableContents`的`Component`惰性地进行。只有当服务端允许原版客户端连接时,这才有用。 +- `createComponentTranslation(CommandSource, String, Object...)`对于在客户端和服务器之间发送消息非常有用。如果接收者是一个原版客户端,则该方法将急切地将所提供的翻译键本地化并格式化为发送方的语言环境,如果未加载语言环境,则为美式英语;加载了模组的服务端可能允许原版客户端加入,并且它们将缺少本地化消息本身所需的本地化数据。否则,该方法将创建具有`TranslatableContents`的组件。 [langs]: https://minecraft.wiki/w/Language#Languages [converter]: https://tterrag.com/lang2json/ +[formatting]: ../misc/components.md#text-formatting +[components]: ../misc/components.md diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/misc/components.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/misc/components.md index 296ea9db9..1eeaf3138 100644 --- a/i18n/zh-CN/docusaurus-plugin-content-docs/current/misc/components.md +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/misc/components.md @@ -1,71 +1,71 @@ -Text Components -================== +文本组件 +======== -A `Component` is a holder for text which can be formatted and chained with other components via its subtype `MutableComponent`. A component can be created using one of the available static helpers: +`Component`是一个文本持有者,可以通过其子类型`MutableComponent`对其进行格式化并与其他组件链接。可以使用以下可用静态辅助方法之一创建组件: -| Method Name | Description | +| 方法名称 | 描述 | |----------------|------------------------------------------------------------------------------------------------------------------------------------| -| `literal` | it creates a component which simply wraps the passed in text. | -| `nullToEmpty` | it's the same as `#literal` except it creates an empty component if null has been passed | -| `translatable` | it creates a component which is represented as localized text to user, read [internationalization] for more details. | -| `empty` | it creates an empty component | -| `keybind` | it creates a component which is represented as the name of current keyboard key of the passed [key mapping][keymapping]. | -| `nbt` | it creates a component for representing nbt data specified by `path` inside `dataSource` | -| `score` | it creates a component for representing the `objective`'s score of an entity specified by the [entity selector][selectors] `name`. | -| `selector` | it creates a component for displaying the list of names of entities selected by the [entity selector][selectors] `pattern`. | +| `literal` | 其创建一个组件,该组件简单地封装传入的文本。 | +| `nullToEmpty` | 其与`#literal`相同,只是当传递null时,其会创建一个空组件 | +| `translatable` | 其创建一个组件,该组件以本地化文本的形式展示给用户,请阅读[国际化][internationalization]了解更多详细信息。 | +| `empty` | 其创建一个空组件 | +| `keybind` | 其创建一个组件,该组件表示为传递的[键盘布局][keymapping]中的当前键盘键的名称。 | +| `nbt` | 其创建一个组件,用于表示由`dataSource`内部的`path`指定的nbt数据 | +| `score` | 其创建一个组件,用于表示由[实体选择器][selectors]`name`指定的实体的`objective`的分数。 | +| `selector` | 其创建一个组件,用于显示由[实体选择器][选择器]`pattern`选择的实体的名称列表。 | -A component's text contents are represented by `ComponentContents`. Notably, the subtype `TranslatableContents` not only supports [localization][internationalization] but also [text formatting][formatting]. +组件的文本内容由`ComponentContents`表示。值得注意的是,其子类型`TranslatableContents`不仅支持[本地化][internationalization],还支持[文本格式化][formatting]。 -Applying Style --------------- +应用格式 +-------- -Components can be formatted (e.g., bold, click actions, color) via `Style`s. `Style`s are immutable, creating a new `Style` each time when modified. The empty style `Style#EMPTY` can be used as a base for configuration. +组件可以通过`Style`进行格式化(例如,粗体、单击操作、颜色)。`Style`是不可变的,每次修改时都会创建一个新的`Style`。空样式`Style#EMPTY`可以用作配置的基础。 -Multiple styles can be merged together with `#applyTo(Style other)`; `other` will override all non-configured of the current object. +可以使用`#applyTo(Style other)`将多个样式合并在一起;`other`将覆盖当前对象的所有未配置项。 -After configuring a style, it can be applied to a component with either `MutableComponent#setStyle` for overwriting, or `#withStyle` for merging: +配置样式后,可以将其应用于具有`MutableComponent#setStyle`用于覆盖的组件,或具有`#withStyle`用于合并的组件: ```java -// Creates MutableComponent wrapping literal "Hello!" +// 创建MutableComponent以包装文本"Hello!" MutableComponent text = Component.literal("Hello!"); -// Copies empty style and sets color to blue and makes it italic +// 复制空样式并将颜色设置为蓝色并使其变为斜体 Style blueItalic = Style.EMPTY .withColor(0x0000FF) .withItalic(true); -// Copies empty style and sets color to red +// 复制空样式并将颜色设置为红色 Style red = Style.EMPTY .withColor(0xFF0000); -// Copies empty style and makes it bold +// 复制空样式并使其变为粗体 Style bold = Style.EMPTY .withBold(true); -// Copies empty style and makes it both underlined and strikethrough +// 复制空样式并使其同时带下划线和删除线 Style doubleLines = Style.EMPTY .withUnderlined(true) .withStrikethrough(true); -// Sets style of the text to be blue and italic +// 将文本的样式设置为蓝色和斜体 text.setStyle(blueItalic); -// Overwrites blue and italic style to be red, bold, underlined, and strikethrough +// 将蓝色和斜体覆盖为红色、粗体、下划线和删除线 text.withStyle(red).withStyle(bold).withStyle(doubleLines); ``` -This creates a red, bold text with two lines: +这将创建一个带有两行的红色粗体文本: ![red_hello] -Chaining Components -------------------- +链接组件 +-------- -`MutableComponent#append` can chain multiple components together. Chained components can be retrieved with `MutableComponent#getSiblings`. +`MutableComponent#append`可以将多个组件链接在一起。可以使用`MutableComponent#getSiblings`检索链接的组件。 -`Component` stores its siblings like a tree and is traversed in preorder; the parent style is merged with those of its siblings. +`Component`像树一样存储其同级,并按预定顺序遍历;父样式与其同级样式合并。 ![tree] -The code below will create a component with the same structure in the above example: +下面的代码将创建一个具有与上例中相同结构的组件: ```java -// Create text only components +// 创建仅文本组件 MutableComponent first = Component.literal("first "); MutableComponent second = Component.literal("second "); MutableComponent third = Component.literal("third "); @@ -74,32 +74,32 @@ MutableComponent fifth = Component.literal("fifth "); MutableComponent sixth = Component.literal("sixth "); MutableComponent seventh = Component.literal("seventh "); -// Create components with style +// 创建带有样式的组件 MutableComponent red = Component.litearl("red ").withStyle(Style.EMPTY.withColor(0xFF0000)); MutableComponent blue = Component.literal("blue ").withStyle(Style.EMPTY.withColor(0x0000FF)); MutableComponent bold = Component.literal("bold ").withStyle(Style.EMPTY.withBold(true)); -// Structure created components in the same way as the image +// 以与下图相同的方式为所创建的组件组织结构 red.append(first).append(blue).append(seventh); blue.append(second).append(third).append(bold); bold.append(fourth).append(fifth).append(sixth); ``` ![style_annotated] -Text Formatting ---------------- +文本格式化 +---------- -Text formatting is the process of inserting data as text into predefined larger text. It can be used for displaying coordinates, showing unit measurements, etc. **Format specifiers** are used for indicating where a text can be inserted. +文本格式化是将数据作为文本插入到预定义的较大文本中的过程。它可以用于显示坐标、显示单位测量值等。**格式说明符**用于指示文本可以插入的位置。 -`TranslatableContents` allows two types of format specifiers: `%s` and `%n$s`. The component uses the second parameter onwards, denoted as `args` , for holding what object to insert in place of a format specifier. +`TranslatableContents`允许两种类型的格式说明符:`%s`和`%n$s`。该组件使用第二个参数,表示为`args`,用于保存要插入以代替格式说明符的对象。 -`%s` is replaced with elements of `args` in order they appear, i.e., the first `%s` is replaced with the first element of `args`, and so on. -`%n$s` is positional specifier; each positional specifier can denote which element in `args` will replace the specifier via the number `n`. -* Formatting `x:%s y:%s z:%s` with `[1, 2, 3]` as `args` results in `x:1 y:2 z:3` -* Formatting `Time: %1$s ms` with `17` as `args` results in `Time: 17 ms` -* Formatting `Player name: %2$s, HP: %1$s` with `[10.2, Dev]` as `args` results in `Player name: Dev, HP: 10.2` +`%s`按其出现的顺序被替换为`args`的元素,即,第一个`%s`被替换为`args`的第一个元素,依此类推。 +`%n$s`是位置说明符;每个位置说明符可以通过数字`n`表示`args`中的哪个元素将替换说明符。 +* 使用`[1, 2, 3]`作为`args`格式化`x:%s y:%s z:%s`将得到`x:1 y:2 z:3` +* 使用`17`作为`args`格式化`Time: %1$s ms`将得到`Time: 17 ms` +* 使用`[10.2, Dev]`作为`args`格式化`Player name: %2$s, HP: %1$s`将得到`Player name: Dev, HP: 10.2` -Any `Component` element within `args` will be transformed into a formatted text string. +`args`中的任何`Component`元素都将转换为格式化的文本字符串。 [internationalization]: ../concepts/internationalization.md [selectors]: https://minecraft.wiki/w/Target_selectors From 013a46e353110f940369b8c033a7a7bc7b4023bc Mon Sep 17 00:00:00 2001 From: src_resources Date: Thu, 12 Oct 2023 18:53:50 +0800 Subject: [PATCH 11/15] Correct rendering of information blocks Correct rendering of information blocks of ":::note". Signed-off-by: src_resources --- .../current/advanced/accesstransformers.md | 3 ++- .../current/blockentities/index.md | 6 ++++-- .../current/blocks/index.md | 3 ++- .../current/concepts/events.md | 6 ++++-- .../current/concepts/registries.md | 12 ++++++++---- .../current/datagen/server/recipes.md | 3 ++- .../current/datagen/server/tags.md | 3 ++- .../current/datastorage/capabilities.md | 3 ++- .../current/gameeffects/particles.md | 6 ++++-- .../current/gettingstarted/index.md | 3 ++- .../current/gettingstarted/modfiles.md | 3 ++- .../current/gettingstarted/versioning.md | 3 ++- .../current/gui/menus.md | 3 ++- .../current/gui/screens.md | 9 ++++++--- .../current/items/bewlr.md | 3 ++- .../current/legacy/index.md | 3 ++- .../current/misc/gametest.mdx | 3 ++- .../current/rendering/modelloaders/bakedmodel.md | 6 ++++-- .../resources/client/models/itemproperties.md | 6 ++++-- .../current/resources/server/advancements.md | 3 ++- .../current/resources/server/conditional.md | 3 ++- .../current/resources/server/glm.md | 6 ++++-- .../current/resources/server/loottables.md | 3 ++- .../current/resources/server/recipes/custom.md | 3 ++- .../current/resources/server/recipes/incode.md | 6 ++++-- .../current/resources/server/recipes/index.md | 3 ++- 26 files changed, 76 insertions(+), 38 deletions(-) diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/advanced/accesstransformers.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/advanced/accesstransformers.md index 5355ca784..f71e59baa 100644 --- a/i18n/zh-CN/docusaurus-plugin-content-docs/current/advanced/accesstransformers.md +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/advanced/accesstransformers.md @@ -47,8 +47,9 @@ minecraft { 目标和指令 --------- -!!! 重要 +:::note 在Minecraft类上使用访问转换器时,字段和方法必须使用SRG名称。 +::: ### 类 转换为目标类: diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/blockentities/index.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/blockentities/index.md index be7e0e679..7817849ed 100644 --- a/i18n/zh-CN/docusaurus-plugin-content-docs/current/blockentities/index.md +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/blockentities/index.md @@ -47,10 +47,11 @@ BlockEntity#load(CompoundTag tag) 每当你的数据发生改变时,你需要调用`BlockEntity#setChanged`;否则,保存存档时可能会跳过包含你的`BlockEntity`的`LevelChunk`。 ::: -!!! 重要 +:::note 调用`super`方法非常重要! 标签名称`id`、`x`、`y`、`z`、`ForgeData`和`ForgeCaps`均由`super`方法保留。 +::: ## 计时的`BlockEntity` @@ -88,8 +89,9 @@ IForgeBlockEntity#handleUpdateTag(CompoundTag tag) ``` 同样,这非常简单,第一个方法收集应该发送到客户端的数据,而第二个方法处理这些数据。如果你的`BlockEntity`不包含太多数据,你可以使用[将数据存储到你的`BlockEntity`][storing-data]小节之外的方法。 -!!! 重要 +:::note 为方块实体同步过多/无用的数据可能会导致网络拥塞。你应该通过在客户端需要时仅发送客户端需要的信息来优化网络使用。例如,在更新标签中发送方块实体的物品栏通常是没有必要的,因为这可以通过其[`AbstractContainerMenu`][menu]进行同步。 +::: ### 在方块更新时同步 diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/blocks/index.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/blocks/index.md index 95743c7da..0d87db079 100644 --- a/i18n/zh-CN/docusaurus-plugin-content-docs/current/blocks/index.md +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/blocks/index.md @@ -30,10 +30,11 @@ 方块必须经过[注册][registering]后才能发挥作用。 -!!! 重要 +:::note 存档中的方块和物品栏中的“方块”是非常不同的东西。存档中的方块由`BlockState`表示,其行为由一个`Block`类的实例定义。同时,物品栏中的物品是由`Item`控制的`ItemStack`。作为`Block`和`Item`二者之间的桥梁,有一个`BlockItem`类。`BlockItem`是`Item`的一个子类,它有一个字段`block`,其中包含对它所代表的`Block`的引用。`BlockItem`将“方块”的一些行为定义为物品,例如右键单击如何放置方块。存在一个没有其`BlockItem`的`Block`也是可能的。(例如`minecraft:water`是一个方块,但不是一个物品。因此,不可能将其作为一个物品保存在物品栏中。) 当一个方块被注册时,也*仅仅*意味着一个方块被注册了。该方块不会自动具有`BlockItem`。要为块创建基本的`BlockItem`,应该将`BlockItem`的注册表名称设置为其`Block`的注册表名称。`BlockItem`的自定义子类也可以使用。一旦为方块注册了`BlockItem`,就可以使用`Block#asItem`来获取它。如果该方块没有`BlockItem`,`Block#asItem`将返回`Items#AIR`,因此,如果你不确定你正在使用的方块是否有`BlockItem`,请检查其`Block#asItem`是否返回`Items#AIR`。 +::: #### 选择性地注册方块 diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/concepts/events.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/concepts/events.md index 54bbdf12a..b253500c1 100644 --- a/i18n/zh-CN/docusaurus-plugin-content-docs/current/concepts/events.md +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/concepts/events.md @@ -93,16 +93,18 @@ public class MyStaticClientOnlyEventHandler { 如果一个事件可以被取消,它将带有`@Cancelable`注释,并且方法`Event#isCancelable()`将返回`true`。可取消事件的取消状态可以通过调用`Event#setCanceled(boolean canceled)`来修改,其中传递布尔值`true`意为取消事件,传递布尔值`false`被解释为“不取消”事件。但是,如果无法取消事件(如`Event#isCancelable()`所定义),则无论传递的布尔值如何,都将抛出`UnsupportedOperationException`,因为不可取消事件事件的取消状态被认为是不可变的。 -!!! 重要 +:::note 并非所有事件都可以取消!试图取消不可取消的事件将导致抛出未经检查的`UnsupportedOperationException`,可能将导致游戏崩溃!在尝试取消某个事件之前,请始终使用`Event#isCancelable()`检查该事件是否可以取消! +::: 事件的结果 --------- 某些事件具有`Event$Result`。结果可以是以下三种情况之一:`DENY`(停止事件)、`DEFAULT`(使用默认行为)和`ALLOW`(强制执行操作,而不管最初是否执行)。事件的结果可以通过调用`#setResult`并用一个`Event$Result`来设置。并非所有事件都有结果;带有结果的事件将用`@HasResult`进行注释。 -!!! 重要 +:::note 不同的事件可能以不同的方式处理结果,在使用事件的结果之前请参阅事件的JavaDoc。 +::: 事件处理优先级 ------------- diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/concepts/registries.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/concepts/registries.md index be52de57d..a99e938a3 100644 --- a/i18n/zh-CN/docusaurus-plugin-content-docs/current/concepts/registries.md +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/concepts/registries.md @@ -52,8 +52,9 @@ public void register(RegisterEvent event) { 并非所有的注册表都由Forge封装。这些可以是静态注册表,如`LootItemConditionType`,使用起来是安全的。还有动态注册表,如`ConfiguredFeature`和其他一些世界生成注册表,它们通常以JSON表示。`DeferredRegister#create`有一个重载,允许模组开发者指定原版注册表所创建的`RegistryObject`的注册表键。注册表方法和模组事件总线的附加与其他`DeferredRegister`相同。 -!!! 重要 +:::note 动态注册表对象**只能**通过数据文件(如JSON)被注册。它们**不能**在代码中被注册。 +::: ```java private static final DeferredRegister REGISTER = DeferredRegister.create(Registries.LOOT_CONDITION_TYPE, "examplemod"); @@ -161,23 +162,26 @@ class Holder { 可以使用模组事件总线上的`DataPackRegistryEvent$NewRegistry`事件添加新的数据包注册表。注册表是通过`#dataPackRegistry`创建的,方法是传入表示注册表名称的`ResourceKey`和用于对JSON中的数据进行编码和解码的`Codec`。可以提供可选的`Codec`来将数据包注册表同步到客户端。 -!!! 重要 +:::note 数据包注册表不能用`DeferredRegister`创建。它们只能通过这个事件创建。 +::: ### 使用DeferredRegister `DeferredRegister`方法又是上述事件的另一个包装。一旦使用`#create`重载在常量字段中创建了`DeferredRegister`(该重载接受注册表名称和mod id),就可以通过`DeferredRegistry#makeRegistry`构建注册表。该方法接受了由Supplier提供的包含任何其他配置的`RegistryBuilder`。默认情况下,该方法已调用`#setName`。由于此方法可以在任何时候返回,因此会返回由Supplier提供的`IForgeRegistry`版本。在激发NewRegistryEvent之前试图从Supplier获取自定义注册表将得到`null`值。 -!!! 重要 +:::note 在通过`#register`将`DeferredRegister`添加到模组事件总线之前,必须调用`DeferredRegister#makeRegistry`。`#makeRegistry`也使用`#register`方法在`NewRegistryEvent`期间创建注册表。 +::: 处理缺失的注册表条目 ------------------ 在某些情况下,每当更新模组或删除模组(更可能的情况)时,某些注册表对象将不复存在。可以通过第三个注册表事件指定操作来处理丢失的映射:`MissingMappingsEvent`。在该事件中,既可以通过给定注册表项和mod id的`#getMappings`获取丢失映射的列表,也可以通过给定注册项的`#getAllMappings`获取所有映射。 -!!! 重要 +:::note `MissingMappingsEvent`在**Forge**事件总线上触发。 +::: 对于每个映射(`Mapping`),可以选择四种映射类型之一来处理丢失的条目: diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/datagen/server/recipes.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/datagen/server/recipes.md index ead9509a9..a1dab8616 100644 --- a/i18n/zh-CN/docusaurus-plugin-content-docs/current/datagen/server/recipes.md +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/datagen/server/recipes.md @@ -22,8 +22,9 @@ public void gatherData(GatherDataEvent event) { `RecipeBuilder`是一个方便的实现,用于创建要生成的`FinishedRecipe`。它提供了解锁、分组、保存和获取配方结果的基本定义。这分别通过`#unlockedBy`、`#group`、`#save`和`#getResult`来完成。 -!!! 重要 +:::note 原版配方生成器中不支持配方中的[`ItemStack`输出][stack]。对于现有的原版配方序列化器,必须以不同的方式构建`FinishedRecipe`才能生成此数据。 +::: :::danger 正在生成的物品结果必须指定有效的`RecipeCategory`;否则,将引发`NullPointerException`。 diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/datagen/server/tags.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/datagen/server/tags.md index 30032fcee..20ae83749 100644 --- a/i18n/zh-CN/docusaurus-plugin-content-docs/current/datagen/server/tags.md +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/datagen/server/tags.md @@ -52,8 +52,9 @@ this.tag(EXAMPLE_TAG_2) .remove(EXAMPLE_OBJECT) // 从该标签中移除一个对象 ``` -!!! 重要 +:::note 如果模组的标签软依赖于另一个模组的标签(另一个模组可能在运行时存在,也可能不存在),则应使用可选方法引用其他模组的标签。 +::: ### Existing Providers diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/datastorage/capabilities.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/datastorage/capabilities.md index bf088a988..080743726 100644 --- a/i18n/zh-CN/docusaurus-plugin-content-docs/current/datastorage/capabilities.md +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/datastorage/capabilities.md @@ -31,8 +31,9 @@ public static final Capability ITEM_HANDLER = CapabilityManager.ge 当被调用时,`CapabilityManager#get`为你的相关类型提供一个非null的Capability。匿名的`CapabilityToken`允许Forge保持软依赖系统,同时仍然拥有获得正确Capability所需的泛型信息。 -!!! 重要 +:::note 即使你在任何时候都可以使用非null的Capability,但这并不意味着该Capability本身是可用的或已注册的。这可以通过`Capability#isRegistered`进行检查。 +::: `#getCapability`方法有另一个参数,类型为`Direction`,可用于请求那一面的特定实例。如果传递`null`,则可以假设请求来自方块内,或者来自某个侧面没有意义的地方,例如不同的维度。在这种情况下,将请求一个不关侧面的一个通用的Capability实例。`#getCapability`的返回类型将对应于传递给方法的Capability中声明的类型的`LazyOptional`。对于物品处理器Capability,其为`LazyOptional`。如果该Capability不适用于特定的提供者,它将返回一个空的`LazyOptional`。 diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/gameeffects/particles.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/gameeffects/particles.md index 031966bf9..7ebff209b 100644 --- a/i18n/zh-CN/docusaurus-plugin-content-docs/current/gameeffects/particles.md +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/gameeffects/particles.md @@ -27,8 +27,9 @@ 在大多数情况下,不需要将任何粒子数据发送到客户端。对于这些例子,更容易创建`SimpleParticleType`的新实例:一个对`ParticleType`和`ParticleOptions`的实现,除了类型之外,它不向客户端发送任何自定义数据。除了红石粉之外,对于着色和依赖方块/物品的粒子而言,大多数原版实现还使用`SimpleParticleType`。 -!!! 重要 +:::note 如果仅在客户端上引用,则生成粒子时`ParticleType`非必要。但是,有必要使用`ParticleEngine`中的任何预构建逻辑,或者从服务端生成粒子。 +::: ### ParticleOptions @@ -87,8 +88,9 @@ 必须通过订阅**模组事件总线**上的`RegisterParticleProvidersEvent`以注册`ParticleProvider`。在事件中,可以通过向方法提供工厂实例,通过`#registerSpecial`注册工厂。 -!!! 重要 +:::note `RegisterParticleProvidersEvent`应仅在客户端上调用,因此在某些客户端类中被单端化独立,并被`DistExecutor`或`@EventBusSubscriber`引用。 +::: #### ParticleDescription、SpriteSet、以及SpriteParticleRegistration diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/gettingstarted/index.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/gettingstarted/index.md index b20bfc612..1b2c1a6d1 100644 --- a/i18n/zh-CN/docusaurus-plugin-content-docs/current/gettingstarted/index.md +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/gettingstarted/index.md @@ -48,8 +48,9 @@ Forge入门 编辑`build.gradle`文件以自定义你的模组的构建方式(如文件名称、artifact版本等等)。 -!!! 重要 +:::note 除非你知道你在做什么,否则**不要**编辑`settings.gradle`。该文件指定[ForgeGradle]所上传的仓库。 +::: ### 建议的`build.gradle`自定义项目 diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/gettingstarted/modfiles.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/gettingstarted/modfiles.md index 57a43f6af..435540209 100644 --- a/i18n/zh-CN/docusaurus-plugin-content-docs/current/gettingstarted/modfiles.md +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/gettingstarted/modfiles.md @@ -63,8 +63,9 @@ showAsResourcePack=false `properties` | table | `{}` | 替换属性表。`StringSubstitutor`使用它将`${file.}`替换为相应的值。该功能目前仅用于替换模组特定属性中的`version`。 | 由`${file.example}`引用的`{ "example" = "1.2.3" }` `issueTrackerURL` | string | *无* | 指向报告与追踪模组问题的地点的URL。 | `"https://forums.minecraftforge.net/"` -!!! 重要 +:::note `services`属性在功能上等效于在指定[在模块中的`uses`指令][uses],该指令允许加载给定类型的服务。 +::: ### 模组特定属性 diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/gettingstarted/versioning.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/gettingstarted/versioning.md index 2a68eedc0..3bf622366 100644 --- a/i18n/zh-CN/docusaurus-plugin-content-docs/current/gettingstarted/versioning.md +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/gettingstarted/versioning.md @@ -3,8 +3,9 @@ 在一般项目中,语义式的版本号(格式为`MAJOR.MINOR.PATCH`)被经常使用。然而,在长期性地修改的情况下,使用格式`MCVERSION-MAJORMOD.MAJORAPI.MINOR.PATCH`可能更有利于将模组的创世性的修改与API变更性的修改区分开来。 -!!! 重要 +:::note Forge使用[Maven版本范围][cmpver]来比较版本字符串,这与Semantic Versioning 2.0.0规范不完全兼容,例如“prerelease”标签。 +::: 样例 ---- diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/gui/menus.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/gui/menus.md index 20562a039..208b11e41 100644 --- a/i18n/zh-CN/docusaurus-plugin-content-docs/current/gui/menus.md +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/gui/menus.md @@ -49,8 +49,9 @@ public MyMenuExtra(int containerId, Inventory playerInv, FriendlyByteBuf extraDa 所有菜单都是从`AbstractContainerMenu`继承而来的。菜单包含两个参数,即表示菜单本身类型的[`MenuType`][mt]和表示当前访问者的菜单唯一标识符的容器id。 -!!! 重要 +:::note 玩家一次只能打开100个唯一的菜单。 +::: 每个菜单应该包含两个构造函数:一个用于初始化服务端上的菜单,另一个用于启动客户端上的菜单。用于初始化客户端菜单的构造函数是提供给`MenuType`的构造函数。服务端菜单构造函数包含的任何字段都应该具有客户端菜单构造函数的一些默认值。 diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/gui/screens.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/gui/screens.md index 95c9843a3..a4f6a06ac 100644 --- a/i18n/zh-CN/docusaurus-plugin-content-docs/current/gui/screens.md +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/gui/screens.md @@ -10,8 +10,9 @@ 关于如何将坐标相对化的信息将在[屏幕][screen]部分中呈现。 -!!! 重要 +:::note 如果选择使用固定坐标或不正确地缩放屏幕,则渲染的对象可能看起来很奇怪或错位。检查坐标是否正确相对化的一个简单方法是单击视频设置中的“Gui比例”按钮。在确定GUI渲染的比例时,此值用作显示器宽度和高度的除数。 +::: ## Gui图形 @@ -51,8 +52,9 @@ Minecraft渲染的任何GUI通常都是使用`GuiGraphics`完成的。`GuiGraphi 渲染纹理时的z坐标通常设置为blit偏移。偏移量负责在查看屏幕时对渲染进行适当分层。z坐标较小的渲染在背景中渲染,反之亦然,z坐标较大的渲染在前景中渲染。z偏移量可以通过`#translate`直接设置在`PoseStack`本身上。一些基本的偏移逻辑在`GuiGraphics`的某些方法(例如物品渲染)中内部应用。 -!!! 重要 +:::note 设置blit偏移时,必须在渲染对象后重置它。否则,屏幕内的其他对象可能会在不正确的层中渲染,从而导致图形问题。建议在平移前推动当前姿势,然后在偏移处完成所有渲染后弹出。 +::: ## Renderable @@ -216,12 +218,13 @@ public void removed() { `inventoryLabelX` | 将渲染玩家物品栏名称的位置的相对x坐标。 `inventoryLabelY` | 将渲染玩家物品栏名称的位置的相对y坐标。 -!!! 重要 +:::note 在上一节中提到应该在`#init`方法中设置预先计算的相对坐标。这仍然保持正确,因为这里提到的值不是预先计算的坐标,而是静态值和相对坐标。 图像值是静态的且不变,因为它们表示背景纹理大小。为了在渲染时更容易,在`#init`方法中预先计算了两个附加值(`leftPos`和`topPos`),该方法标记了将渲染背景的左上角。标签坐标相对于这些值。 `leftPos`和`topPos`也被用作渲染背景的方便方式,因为它们已经表示要传递到`#blit`方法中的位置。 +::: ```java // 在某个AbstractContainerScreen子类中 diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/items/bewlr.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/items/bewlr.md index 0ea3edffa..3f526e140 100644 --- a/i18n/zh-CN/docusaurus-plugin-content-docs/current/items/bewlr.md +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/items/bewlr.md @@ -29,7 +29,8 @@ public void initializeClient(Consumer consumer) { } ``` -!!! 重要 +:::note 每个模组都应该只有一个自定义BEWLR的实例。 +::: 这就行了,使用BEWLR不需要额外的设置。 diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/legacy/index.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/legacy/index.md index 9a53959b7..8a3406fcd 100644 --- a/i18n/zh-CN/docusaurus-plugin-content-docs/current/legacy/index.md +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/legacy/index.md @@ -3,8 +3,9 @@ Forge已经存在多年了,你仍然可以轻松访问Minecraft 1.1版本的Forge版本。每个版本之间都有显著的差异,支持这么多不同的版本是不可能的。因此,Forge使用了一个LTS系统,其中以前的主要Minecraft版本被视为“LTS”(长期支持)。只有最新版本和任何当前的LTS版本才会有易于访问的文档,并包含在侧边栏的版本下拉列表中。然而,一些旧版本曾经是LTS,或者在某个时候是最新版本,并编写了文档。可以在这里找到带有这些版本文档的旧网站链接。 -!!! 重要 +:::note 这些旧文档网站仅供参考。不要在Forge discord或Forge论坛上寻求旧版本的帮助。**当你使用旧版本时,将不会获得支持。** +::: ### 以前已有文档的版本列表 diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/misc/gametest.mdx b/i18n/zh-CN/docusaurus-plugin-content-docs/current/misc/gametest.mdx index 09de0690a..8449118b6 100644 --- a/i18n/zh-CN/docusaurus-plugin-content-docs/current/misc/gametest.mdx +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/misc/gametest.mdx @@ -67,8 +67,9 @@ public static void exampleConfiguredTest(GameTestHelper helper) { `#succeedWhen` | 所提供的`Runnable`在超时之前每tick都会进行测试,如果对其中一个tick的检查没有引发`GameTestAssertException`,则会成功。 `#succeedOnTickWhen` | 提供的`Runnable`在指定的tick上进行测试,如果没有抛出`GameTestAssertException`,则会成功。如果`Runnable`在任何其他tick上成功,则将其标记为失败。 -!!! 重要 +:::note 游戏测试每tick都会执行,直到测试被标记为成功。因此,在给定的tick上安排成功的方法必须小心,不要总是在之前的tick上失败。 +::: #### 计划操作 diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/rendering/modelloaders/bakedmodel.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/rendering/modelloaders/bakedmodel.md index ce5455e5e..14d7c70a1 100644 --- a/i18n/zh-CN/docusaurus-plugin-content-docs/current/rendering/modelloaders/bakedmodel.md +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/rendering/modelloaders/bakedmodel.md @@ -19,8 +19,9 @@ ### `isCustomRenderer` -!!! 重要 +:::note 除非你知道自己在做什么,否则只需`return false`然后继续其他事项。 +::: 将其渲染为物品时,返回`true`将导致模型不被渲染,转而回到`BlockEntityWithoutLevelRenderer#renderByItem`。对于某些原版物品,如箱子和旗帜,此方法被硬编码为将数据从物品复制到`BlockEntity`中,然后使用`BlockEntityRenderer`来渲染BE以代替物品。对于所有其他物品,它将使用由`IClientItemExtensions#getCustomRenderer`提供的`BlockEntityWithoutLevelRenderer`实例。有关详细信息,请参阅[BlockEntityWithoutLevelRenderer][bewlr]页。 @@ -28,8 +29,9 @@ 粒子应使用的任何纹理。对于方块,它将在实体掉落在其上或其被破坏时显示。对于物品,它将在报废或被吃掉时显示。 -!!! 重要 +:::note 由于模型数据可能会对特定模型的渲染方式产生影响,因此不推荐使用不带参数的原版方法,而推荐使用`#getParticleIcon(ModelData)`。 +::: ### `getTransforms` diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/resources/client/models/itemproperties.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/resources/client/models/itemproperties.md index b81c55b59..6516facc2 100644 --- a/i18n/zh-CN/docusaurus-plugin-content-docs/current/resources/client/models/itemproperties.md +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/resources/client/models/itemproperties.md @@ -11,8 +11,9 @@ `ItemProperties#register`用于向某个物品添加属性。`Item`参数是要附加属性的物品(例如`ExampleItems#APPLE`)。`ResourceLocation`参数是所要赋予属性的名称(例如`new ResourceLocation("pull")`)。`ItemPropertyFunction`是一个函数接口,它接受`ItemStack`、它所在的`ClientLevel`(可以为null)、持有它的`LivingEntity`(可以是null)和包含持有实体的id的`int`(可能是`0`),返回属性的`float`值。对于修改后的物品属性,建议将模组的mod id用作命名空间(例如`examplemod:property`,而不仅仅是`property`,因为这实际上意味着`minecraft:property`)。这些操作应在`FMLClientSetupEvent`中完成。 还有另一个方法`ItemProperties#registerGeneric`用于向所有物品添加属性,并且它不将`Item`作为其参数,因为所有物品都将应用此属性。 -!!! 重要 +:::note 使用`FMLClientSetupEvent#enqueueWork`执行这些任务,因为`ItemProperties`中的数据结构不是线程安全的。 +::: :::caution Mojang反对使用`ItemPropertyFunction`而推荐使用`ClampedItemPropertyFunction`子接口,该子接口将结果夹在`0`和`1`之间。 @@ -23,8 +24,9 @@ 覆盖的格式可以在[wiki][format]上看到,一个很好的例子可以在`model/item/bow.json`中找到。为了参考,这里是一个具有`examplemod:power`属性的物品的假设例子。如果值不匹配,则默认为当前模型,但如果有多个匹配,则会选择列表中的最后一个匹配。 -!!! 重要 +:::note predicate适用于*大于或等于*给定值的所有值。 +::: ```js { diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/resources/server/advancements.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/resources/server/advancements.md index aa2386ec3..11446d458 100644 --- a/i18n/zh-CN/docusaurus-plugin-content-docs/current/resources/server/advancements.md +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/resources/server/advancements.md @@ -120,8 +120,9 @@ public void trigger(ServerPlayer player, ItemStack stack) { 之后,应在`FMLCommonSetupEvent`期间使用`CriteriaTriggers#register`注册实例。 -!!! 重要 +:::note `CriteriaTriggers#register`必须通过`FMLCommonSetupEvent#enqueueWork`排入同步工作队列,因为该方法不是线程安全的。 +::: ### 触发器的调用 diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/resources/server/conditional.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/resources/server/conditional.md index 0213b92bc..86ae02cc4 100644 --- a/i18n/zh-CN/docusaurus-plugin-content-docs/current/resources/server/conditional.md +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/resources/server/conditional.md @@ -173,8 +173,9 @@ public void registerSerializers(RegisterEvent event) { } ``` -!!! 重要 +:::note 如果使用`FMLCommonSetupEvent`注册条件序列化器,则必须通过 `FMLCommonSetupEvent#enqueueWork`将其排入同步工作队列,因为`CraftingHelper#register`不是线程安全的。 +::: [datagen]: ../../datagen/server/recipes.md [serializer]: #iconditionserializer diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/resources/server/glm.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/resources/server/glm.md index 92e85c267..8ebb9151c 100644 --- a/i18n/zh-CN/docusaurus-plugin-content-docs/current/resources/server/glm.md +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/resources/server/glm.md @@ -22,8 +22,9 @@ `global_loot_modifiers.json`表示要加载到游戏中的所有战利品修改器。此文件**必须**放在`data/forge/loot_modifiers/global_loot_modifiers.json`。 -!!! 重要 +:::note `global_loot_modifiers.json`只能在`forge`命名空间中被读取。如果该文件位于模组的命名空间下,则会被忽略。 +::: `entries`是将要加载的修改器的*有序列表*。指定的[ResourceLocation][resloc]指向其在`data//loot_modifiers/.json`中的关联条目。这主要与数据包生成器有关,用于解决独立模组的修改器之间的冲突。 @@ -50,8 +51,9 @@ `conditions`应该表示该修改器要激活的战利品表条件。条件应该避免被硬编码,以允许数据包作者尽可能灵活地调整标准。这也必须始终存在。 -!!! 重要 +:::note 尽管`conditions`应该表示修改器激活所需的内容,但只有在使用捆绑的Forge类时才会出现这种情况。如果使用`LootModifier`作为子类,则所有条件都将用**逻辑与(AND)**相连,并检查是否应应用修改器。 +::: 还可以指定由序列化器读取并由修改器定义的任何附加属性。 diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/resources/server/loottables.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/resources/server/loottables.md index 2216ddc3d..44b21c8f3 100644 --- a/i18n/zh-CN/docusaurus-plugin-content-docs/current/resources/server/loottables.md +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/resources/server/loottables.md @@ -38,8 +38,9 @@ Forge为战利品表提供了一些额外的行为,以更好地控制系统。 `LootTableLoadEvent`是在Forge事件总线上触发的[事件][event],每当加载战利品表时就会触发。如果事件被取消,则会加载一个空的战利品表。 -!!! 重要 +:::note **不要**通过此事件修改战利品表的掉落。这些修改应该使用[全局战利品修改器][glm]来完成。 +::: ### 战利品池名称 diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/resources/server/recipes/custom.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/resources/server/recipes/custom.md index 44f6cb745..762645b42 100644 --- a/i18n/zh-CN/docusaurus-plugin-content-docs/current/resources/server/recipes/custom.md +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/resources/server/recipes/custom.md @@ -8,8 +8,9 @@ `Recipe`接口描述配方数据和执行逻辑。这包括匹配输入并提供相关联的结果。由于配方子系统默认执行物品转换,因此输入是通过`Container`子类型提供的。 -!!! 重要 +:::note 传递到配方中的`Container`应被视为其内容是不可变的。任何可变操作都应该通过`ItemStack#copy`对输入的一份副本执行。 +::: 为了能够从管理器获得配方实例,`#matches`必须返回true。此方法根据提供的容器进行检查,以查看相关联的输入是否有效。`Ingredient`可以通过调用`Ingredient#test`进行验证。 diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/resources/server/recipes/incode.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/resources/server/recipes/incode.md index 5f468297c..dbb38b412 100644 --- a/i18n/zh-CN/docusaurus-plugin-content-docs/current/resources/server/recipes/incode.md +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/resources/server/recipes/incode.md @@ -18,8 +18,9 @@ `IBrewingRecipe`是一个伪[`Recipe`][recipe]接口,用于检查输入和催化剂是否有效,并在有效时提供相关输出。它分别通过`#isInput`、`#isIngredient`和`#getOutput`提供。输出方法可以访问输入和催化剂物品栈来构建结果。 -!!! 重要 +:::note 在`ItemStack`或`CompoundTag`之间复制数据时,请确保使用它们各自的`#copy`方法来创建唯一的实例。 +::: 没有类似原版的包装来添加额外的药水容器或药水混合物。需要添加一个新的`IBrewingRecipe`实现来复制此行为。 @@ -47,8 +48,9 @@ public void updateAnvil(AnvilUpdateEvent event) { 织布机负责将染料和图案(从织布机或物品上)应用到旗帜上。虽然旗帜和染料必须分别为`BannerItem`或`DyeItem`,但可以在织布机中创建和应用自定义图案。旗帜图案可以通过[注册][registering]一个`BannerPattern`来创建。 -!!! 重要 +:::note `minecraft:no_item_required`标签中的`BannerPattern`在织布机中作为一个选项出现。不在此标签中的图案必须有一个附带的`BannerPatternItem`才能与关联的标签一起使用。 +::: ```java private static final DeferredRegister REGISTER = DeferredRegister.create(Registries.BANNER_PATTERN, "examplemod"); diff --git a/i18n/zh-CN/docusaurus-plugin-content-docs/current/resources/server/recipes/index.md b/i18n/zh-CN/docusaurus-plugin-content-docs/current/resources/server/recipes/index.md index 44b55a432..94e1cb44d 100644 --- a/i18n/zh-CN/docusaurus-plugin-content-docs/current/resources/server/recipes/index.md +++ b/i18n/zh-CN/docusaurus-plugin-content-docs/current/resources/server/recipes/index.md @@ -42,13 +42,14 @@ 每个方法都接受一个`RecipeType`,表示使用配方的方法(合成、烧炼等),一个保存输入配置的`Container`,以及与容器一起传递给`Recipe#matches`的当前存档。 -!!! 重要 +:::note Forge提供了`RecipeWrapper`实用类,该类扩展了`Container`,用于包装`IItemHandler`,并将其传递给需要`Container`参数的方法。 ```java // 在具有IItemHandlerModifiable处理器的某给方法中 recipeManger.getRecipeFor(RecipeType.CRAFTING, new RecipeWrapper(handler), level); ``` +::: 附加特性 -------- From f4959a0c36bd2a796f2cf85ae98daf913053eceb Mon Sep 17 00:00:00 2001 From: ChampionAsh5357 Date: Wed, 11 Oct 2023 16:13:52 -0400 Subject: [PATCH 12/15] Contributing Guidelines for the Documentation Repository (#14) Co-authored-by: sciwhiz12 Co-authored-by: Dennis C --- .node-version | 1 + README.md | 2 +- docusaurus.config.js | 9 ++ src/pages/contributing.md | 43 ------ src/pages/contributing.mdx | 295 +++++++++++++++++++++++++++++++++++++ 5 files changed, 306 insertions(+), 44 deletions(-) create mode 100644 .node-version delete mode 100644 src/pages/contributing.md create mode 100644 src/pages/contributing.mdx diff --git a/.node-version b/.node-version new file mode 100644 index 000000000..02c8b485e --- /dev/null +++ b/.node-version @@ -0,0 +1 @@ +18.18.0 diff --git a/README.md b/README.md index bac7904c0..07c79632a 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ The documentation is built using [Docusaurus 2](https://docusaurus.io) ## Contributing -*Contribution guidelines are currently pending.* +You can read the [contribution guidelines on the docs](https://docs.neoforged.net/contributing/). If you wish to contribute to the documentation, fork and clone this repository. diff --git a/docusaurus.config.js b/docusaurus.config.js index 96293cebe..d9bf7f585 100644 --- a/docusaurus.config.js +++ b/docusaurus.config.js @@ -118,6 +118,11 @@ const config = { position: "right", docsPluginId: "neogradle", }, + { + to: "/contributing", + label: "Contributing", + position: "right", + }, { href: "https://github.com/neoforged/documentation", label: "GitHub", @@ -139,6 +144,10 @@ const config = { to: "/neogradle/docs/", label: "NeoGradle Documentation", }, + { + to: "/contributing", + label: "Contributing to the Documentation" + } ], }, { diff --git a/src/pages/contributing.md b/src/pages/contributing.md deleted file mode 100644 index 4f8c0e48d..000000000 --- a/src/pages/contributing.md +++ /dev/null @@ -1,43 +0,0 @@ -Contributing to the Documentation -================================== - -Contribution guidelines are currently being written. You may contribute at a later date. - diff --git a/src/pages/contributing.mdx b/src/pages/contributing.mdx new file mode 100644 index 000000000..e2e223d24 --- /dev/null +++ b/src/pages/contributing.mdx @@ -0,0 +1,295 @@ +# 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]. + +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 # or nvs use on Windows +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. + +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]. + +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] +* [University of Helsinki][helsinki] +* [Oracle][oracle] +* [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. + +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. + +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; + // 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 + +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. + +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() { + // ... +} +``` + + + + + + +```java +public void previousMethod() { + // ... +} +``` + + + + + +```java +public void firstMethod() { + // ... +} +``` + + + +```` + +Output: + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + + + + + +```java +public void latestMethod() { + // ... +} +``` + + + + + +```java +public void previousMethod() { + // ... +} +``` + + + + + +```java +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. + +### 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. + +There are other metadata fields like `title` and `description`, but those are typically parsed from the page itself. + +```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. + +```js +{ + // 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 + +### Building and Testing Your 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. + +### 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. + +```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. + +```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 JavaScript (`js`) syntax highlighter should be used to allow comments. + +````md + +```java +public void run() { + //... +} +``` + + +```js +{ + // 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 + +There are [two] different types of [link references][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]. + +```md +:::note +I'm within an admonition! +::: +``` + +[docs]: https://github.com/neoforged/Documentation + +[npm]: https://www.npmjs.com/ +[nvm]: https://github.com/nvm-sh/nvm +[nvs]: https://github.com/jasongin/nvs + +[parchment]: https://github.com/ParchmentMC/Parchment +[neo]: https://github.com/neoforged/NeoForge/ + +[jetbrains]: https://www.jetbrains.com/academy/ +[codeacademy]: https://www.codecademy.com/learn/learn-java +[helsinki]: https://java-programming.mooc.fi/ +[oracle]: https://docs.oracle.com/javase/tutorial/ +[eck]: http://math.hws.edu/javanotes/ + +[docusaurus]: https://docusaurus.io/docs/markdown-features +[mdx]: https://mdxjs.com/guides/ + +[admonition]: https://docusaurus.io/docs/markdown-features/admonitions From 88367dc5c86212ce347549753eb56d9308bdeb9b Mon Sep 17 00:00:00 2001 From: src_resources Date: Thu, 12 Oct 2023 21:57:54 +0800 Subject: [PATCH 13/15] Add Chinese translations for pages Signed-off-by: src_resources --- .../contributing.mdx | 295 ++++++++++++++++++ .../docusaurus-plugin-content-pages/index.mdx | 38 +++ 2 files changed, 333 insertions(+) create mode 100644 i18n/zh-CN/docusaurus-plugin-content-pages/contributing.mdx create mode 100644 i18n/zh-CN/docusaurus-plugin-content-pages/index.mdx diff --git a/i18n/zh-CN/docusaurus-plugin-content-pages/contributing.mdx b/i18n/zh-CN/docusaurus-plugin-content-pages/contributing.mdx new file mode 100644 index 000000000..1cad0ca14 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-pages/contributing.mdx @@ -0,0 +1,295 @@ +# 向文档做出贡献 + +这是一个非详尽的指南,用于为[NeoForged文档][docs]仓库做出贡献。贡献可以通过fork和clone仓库来草拟,然后通过[GitHub][docs]上的pull request(即PR)来请求。 + +你可以使用[npm]在本地运行网站。建议使用Node版本管理器,如[nvm](Mac、Linux)或[nvs](Windows)来设置和安装npm和Node。从那里,你可以运行以下命令: + +```bash +nvm use # 在Windows上为nvs use +npm install +npm run start +``` + +## 原则 + +本文档是帮助模组开发者理解和实现Minecraft或NeoForged中给定概念的指南。 + +本文档**并非**是一个允许修改者复制粘贴示例的教程。如果你正在寻找教程,有大量视频和页面(此处未链接),你可以使用并遵循。 + +这个文档也**不**是一个类的文档。在编写指南时,提供一个元素的描述是不可避免的;然而,如果你想为一个类写文档,你应该贡献给[Parchment for Minecraft][parchment]或[NeoForge for NeoForged][neo]。 + +最后,本文档**并非**用于解释Java的概念。本文档是为那些已经有了坚实Java基础的人准备的。如果需要解释Java概念以更好地理解该概念(例如访问转换器的JVM描述符),则应提供到原始资源的链接。否则,如果你不熟悉Java,有很多在线资源可以学习: + +* [JetBrains Academy][jetbrains] +* [Codeacademy][codeacademy] +* [University of Helsinki][helsinki] +* [Oracle][oracle] +* [Introduction to Programming using Java by David J. Eck][eck] + +## 概念 + +每一页都应该为模组开发者提供一个特定概念的引导。如果概念的范围太大,则应将概念拆分为单独的子概念,每个子概念都在自己的页面中。例如,如果你正在写一个配方,那么每个配方都可以有一个页面,而不是一个页面包含所有配方。 + +在描述一个概念时,你应该首先介绍这个概念是什么,它在Minecraft中的使用位置,为什么应该使用它,以及如何使用它。概念中的每个部分都应该有一个标题。如有必要,也可以将一个部分分解为多个子部分。例如,食谱中的每一个配方都可以有一个关于原料和配方本身的小节。 + +如果你需要引用其他概念,则应将相关页面与摘要和/或一些示例一起链接,以了解其具体的应用。 + +## 示例 + +代码示例通常应该是类似伪代码的对象,以增强模组开发者的理解。在本文档中,类伪代码指的是用所需语言的结构和语法编写的代码块,注释用作特定逻辑的占位符,模组开发者可以选择自己实现这些逻辑。代码块不一定需要可编译,但每一行都应该具有所需语言的有效语法和结构。 + +当实现一个方法时,它通常是特定于模组开发者试图实现的期望目标。作为指南,本文档旨在对模组开发者的特定目标进行某种程度的不可知论,而不是涵盖一般用例。 + +比方说,我们正在使用一种名为`#applyDiscount`的方法来从当前价格中减去一些价值。并不是每个人都会在方法中实现相同的逻辑。因此,伪代码可以留下一条注释,说明要做什么: + +```java +// 在某个类中 + +public float applyDiscount(float price) { + float newPrice = price; + // 向newPrice应用折扣 + // ... + return newPrice; +} +``` + +:::tip +如果伪代码的解释性不足以理解这个概念,那么可以使用完整的代码示例。完整的代码示例应该提供伪值并解释它们所代表的内容。 +::: + +## 次要更改和补丁更改 + +如果在NeoForge的次要版本或补丁版本之间发生更改,则文档中的相关更改应拆分为单独的部分或放入选项卡(Tab)中。这保持了信息的准确性,这取决于模组开发者目前正在开发的版本。 + +选项卡必须位于`.mdx`文件中,而不是`.md`文件中,才能获得正确的IDE支持。 + +````md +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + + + + + + + +```java +public void latestMethod() { + // ... +} +``` + + + + + + +```java +public void previousMethod() { + // ... +} +``` + + + + + +```java +public void firstMethod() { + // ... +} +``` + + + +```` + +输入: + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + + + + + +```java +public void latestMethod() { + // ... +} +``` + + + + + +```java +public void previousMethod() { + // ... +} +``` + + + + + +```java +public void firstMethod() { + // ... +} +``` + + + + +## 样式指南 + +本文档使用[Docusaurus][docusaurus](内部使用[MDX][mdx])来生成页面。你可以在他们的页面上找到有关可用功能的详细信息。本样式指南将更加关注Markdown文件中使用的常见功能和格式。 + +### 前置内容 + +前置内容定义了元数据字段,这些字段可以影响页面的呈现方式。其用类似于代码块的`---`表示。通常应该定义的唯一前置内容是`sidebar_position`,它决定了页面应该在边栏上的何处呈现。 + +还有其他元数据字段,如`title`和`description`,但这些字段通常是从页面本身解析的。 + +```md + +--- +sidebar_position: 2 +--- +``` + +#### 类别 + +类别(Categories)是文档中的文件夹。它们继承了`index.md`中的标题和位置数据。如果`index.md`不在子文件夹中,则应创建一个`_category_.json`文件,指定`label`以表示节的名称,`position`以表示节在侧边栏上的位置。 + +```js +{ + // 要显示的类别名称 + "label": "Example Title", + + // 这将被渲染为当前级别的边栏上的第三个元素。 + "position": 3 +} +``` + +### 标题 + +标题使用最多六个标签(`#`)来定义每个部分。标题除了不重要的单词外,其他都应该大写。 + +```md + +# 向本文档做出贡献的指南 + +### 构建并测试你的模组 +``` + +### 用词 + +拼写、语法和句法应遵循美式英语。避免在句子中使用缩写;使用两个单独的单词(用'is not'代替'isn't')。此外,尽可能避免使用代词(如I、me、you),除非你需要直接指代读者。应谨慎使用示范语(如this、that、its),以免混淆读者。优先使用所指的实际宾语或名词。 + +### 段落 + +段落应该是一个连续的块,用换行符分隔。段落**不**应该让每个句子都在一个新行上。 + +```md +这是我的第一个段落。看看下一个句子是怎么在同一行的?你可以在编辑器中使用换行来阻止该行从屏幕上消失。 + +这是我的下一个段落。它被一个新行隔开。 +``` + +### 缩进 + +当要缩进行时,使用四个空格而非制表符。大多数Markdown功能都需要四个空格来识别缩进,这样它允许整个文档保持一致。 + +```md +* Hello World + * Four Spaces In +``` + +### Importance + +强调单词时应使用**粗体**或_斜体_。请用两个星号(`**`)表示粗体,用下划线(`_`)表示斜体,以使Markdown中的分隔更加明显。 + +```md +这些字是 **粗体** 。 + +这些字是 _斜体_ 。 +``` + +### 代码引用 + +当引用代码块之外的元素时,它们应该用反引号(`` ` ``)括起来。类应该使用它们的简单名称。方法和字段应指定类名,后跟`#`。如果类名是隐含的,则方法或字段可以简单地以`#`为前缀。内部类应指定外部类的名称,后跟`.`。 + +```md + +`MyClass` +`MyClass.InnerClass` + + +`MyClass#foo` +`MyClass.InnerClass#bar` +`#SOME_CONSTANT` +``` + +代码块应在三重反引号(`` ``` ``)后指定其语言。在编写JSON块时,应该使用JavaScript(`js`)语法高亮显示来允许注释。 + +````md + +```java +public void run() { + //... +} +``` + + +```js +{ + // 此处是允许注释的 + "text": "Hiya" +} +``` +```` + +### 链接 + +所有链接都应使用括号(`[]`)来引用在Markdown页面底部指定的链接。如果第一对括号之间的名称已被使用,则可以省略第二对括号。 + +```md + +There are [two] different types of [link references][linkref]. + + +[two]: https://linkrefwithoutref.donotclick +[linkref]: https://linkref.donotclick +``` + +### 警告(Admonitions) + +可以在页面上使用三个冒号(`:::`)并指定其类型来指定警告。警告格式可以在[Docusaurus wiki][admonition]上找到。 + +```md +:::note +我在一个警告之中! +::: +``` + +[docs]: https://github.com/neoforged/Documentation + +[npm]: https://www.npmjs.com/ +[nvm]: https://github.com/nvm-sh/nvm +[nvs]: https://github.com/jasongin/nvs + +[parchment]: https://github.com/ParchmentMC/Parchment +[neo]: https://github.com/neoforged/NeoForge/ + +[jetbrains]: https://www.jetbrains.com/academy/ +[codeacademy]: https://www.codecademy.com/learn/learn-java +[helsinki]: https://java-programming.mooc.fi/ +[oracle]: https://docs.oracle.com/javase/tutorial/ +[eck]: http://math.hws.edu/javanotes/ + +[docusaurus]: https://docusaurus.io/docs/markdown-features +[mdx]: https://mdxjs.com/guides/ + +[admonition]: https://docusaurus.io/docs/markdown-features/admonitions diff --git a/i18n/zh-CN/docusaurus-plugin-content-pages/index.mdx b/i18n/zh-CN/docusaurus-plugin-content-pages/index.mdx new file mode 100644 index 000000000..b534443c0 --- /dev/null +++ b/i18n/zh-CN/docusaurus-plugin-content-pages/index.mdx @@ -0,0 +1,38 @@ +import Card from "/src/theme/Card.tsx"; + +# NeoForged 文档 + +:::caution +请注意,由于NeoForged处于创始阶段,本文档可能未紧跟最新版本。 +::: + +这里是Minecraft模组API——[NeoForged]的官方文档。 + +该文档 _仅_ 针对Forge编纂,**而不是一个Java教程**。 + +如果你愿意对文档做出贡献,请阅读[向文档做出贡献][contributing]。 + +
+
+
+ +
+
+ +
+
+
+ +[NeoForged]: https://neoforged.net +[contributing]: ./contributing From d472dff131903380627bafda02302b82d1ed26a4 Mon Sep 17 00:00:00 2001 From: src_resources Date: Thu, 12 Oct 2023 22:15:55 +0800 Subject: [PATCH 14/15] Fix an ignored translation Signed-off-by: src_resources --- i18n/zh-CN/docusaurus-plugin-content-pages/contributing.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/i18n/zh-CN/docusaurus-plugin-content-pages/contributing.mdx b/i18n/zh-CN/docusaurus-plugin-content-pages/contributing.mdx index 1cad0ca14..5e4637da8 100644 --- a/i18n/zh-CN/docusaurus-plugin-content-pages/contributing.mdx +++ b/i18n/zh-CN/docusaurus-plugin-content-pages/contributing.mdx @@ -207,7 +207,7 @@ sidebar_position: 2 * Four Spaces In ``` -### Importance +### 强调 强调单词时应使用**粗体**或_斜体_。请用两个星号(`**`)表示粗体,用下划线(`_`)表示斜体,以使Markdown中的分隔更加明显。 From 8f1002b22e3708d4395a3bbfa5b2c7a8bfbb66d6 Mon Sep 17 00:00:00 2001 From: src_resources Date: Fri, 13 Oct 2023 21:32:55 +0800 Subject: [PATCH 15/15] Correct a wrong Chinese translation Signed-off-by: src_resources --- i18n/zh-CN/docusaurus-plugin-content-pages/contributing.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/i18n/zh-CN/docusaurus-plugin-content-pages/contributing.mdx b/i18n/zh-CN/docusaurus-plugin-content-pages/contributing.mdx index 5e4637da8..a6c678281 100644 --- a/i18n/zh-CN/docusaurus-plugin-content-pages/contributing.mdx +++ b/i18n/zh-CN/docusaurus-plugin-content-pages/contributing.mdx @@ -104,7 +104,7 @@ public void firstMethod() { ```` -输入: +输出: import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem';