From 7c42d8f3853a499bd6cbbd9485696fb69b82ba4b Mon Sep 17 00:00:00 2001 From: ChampionAsh5357 Date: Fri, 10 Jan 2025 17:33:06 -0500 Subject: [PATCH] Update to 1.21.4 (#211) Co-authored-by: IchHabeHunger54 <52419336+IchHabeHunger54@users.noreply.github.com> Co-authored-by: Dennis C --- docs/advanced/featureflags.md | 2 +- docs/blockentities/ber.md | 66 +- docs/blocks/index.md | 2 +- docs/concepts/events.md | 4 +- docs/concepts/registries.md | 13 +- docs/datastorage/attachments.md | 2 +- docs/gettingstarted/index.md | 4 +- docs/gettingstarted/modfiles.md | 3 + docs/items/armor.md | 102 +- docs/items/index.md | 7 +- docs/items/mobeffects.md | 6 +- docs/items/tools.md | 5 +- docs/legacy/porting.md | 4 +- docs/misc/config.md | 6 +- docs/misc/resourcelocation.md | 5 +- docs/resources/client/models/bakedmodel.md | 53 +- docs/resources/client/models/datagen.md | 486 +++--- docs/resources/client/models/index.md | 152 +- docs/resources/client/models/items.md | 1383 +++++++++++++++++ docs/resources/client/models/modelloaders.md | 414 ++--- docs/resources/client/particles.md | 9 +- docs/resources/client/sounds.md | 7 +- docs/resources/index.md | 31 +- docs/resources/server/advancements.md | 21 +- docs/resources/server/conditions.md | 85 +- docs/resources/server/datamaps/builtin.md | 4 +- .../server/loottables/lootfunctions.md | 39 +- docs/resources/server/recipes/custom.md | 2 +- docs/resources/server/tags.md | 77 +- docs/worldgen/biomemodifier.md | 18 +- docusaurus.config.js | 3 + .../version-1.20.4/concepts/registries.md | 4 +- .../resources/client/models/index.md | 12 +- .../version-1.20.6/concepts/registries.md | 4 +- .../resources/client/models/index.md | 12 +- .../version-1.21.1/concepts/registries.md | 4 +- .../resources/client/models/index.md | 12 +- .../version-1.21.3/advanced/_category_.json | 4 + .../advanced/accesstransformers.md | 147 ++ .../advanced/extensibleenums.md | 148 ++ .../version-1.21.3/advanced/featureflags.md | 287 ++++ .../blockentities/_category_.json | 4 + .../version-1.21.3/blockentities/ber.md | 133 ++ .../version-1.21.3/blockentities/container.md | 312 ++++ .../version-1.21.3/blockentities/index.md | 244 +++ .../version-1.21.3/blocks/_category_.json | 4 + versioned_docs/version-1.21.3/blocks/index.md | 324 ++++ .../version-1.21.3/blocks/states.md | 145 ++ .../version-1.21.3/concepts/_category_.json | 4 + .../version-1.21.3/concepts/events.md | 212 +++ .../version-1.21.3/concepts/registries.md | 346 +++++ .../version-1.21.3/concepts/sides.md | 89 ++ .../datastorage/_category_.json | 4 + .../version-1.21.3/datastorage/attachments.md | 118 ++ .../datastorage/capabilities.md | 340 ++++ .../version-1.21.3/datastorage/codecs.md | 593 +++++++ .../version-1.21.3/datastorage/nbt.md | 105 ++ .../version-1.21.3/datastorage/saveddata.md | 60 + .../gettingstarted/_category_.json | 4 + .../version-1.21.3/gettingstarted/index.md | 61 + .../version-1.21.3/gettingstarted/modfiles.md | 247 +++ .../gettingstarted/structuring.md | 80 + .../gettingstarted/versioning.md | 96 ++ .../version-1.21.3/gui/_category_.json | 4 + versioned_docs/version-1.21.3/gui/menus.md | 354 +++++ versioned_docs/version-1.21.3/gui/screens.md | 426 +++++ .../version-1.21.3/items/_category_.json | 4 + versioned_docs/version-1.21.3/items/armor.md | 423 +++++ .../version-1.21.3/items/consumables.md | 368 +++++ .../version-1.21.3/items/datacomponents.md | 331 ++++ versioned_docs/version-1.21.3/items/index.md | 255 +++ .../items/interactionpipeline.md | 75 + .../version-1.21.3/items/mobeffects.md | 204 +++ versioned_docs/version-1.21.3/items/tools.md | 175 +++ .../version-1.21.3/legacy/_category_.json | 4 + .../version-1.21.3/legacy/porting.md | 31 + .../version-1.21.3/misc/_category_.json | 4 + versioned_docs/version-1.21.3/misc/config.md | 213 +++ .../version-1.21.3/misc/debugprofiler.md | 50 + .../version-1.21.3/misc/gametest.md | 282 ++++ .../version-1.21.3/misc/keymappings.md | 154 ++ .../version-1.21.3/misc/resourcelocation.md | 48 + .../version-1.21.3/misc/updatechecker.md | 59 + .../version-1.21.3/networking/_category_.json | 4 + .../networking/configuration-tasks.md | 133 ++ .../version-1.21.3/networking/entities.md | 30 + .../version-1.21.3/networking/index.md | 17 + .../version-1.21.3/networking/payload.md | 193 +++ .../version-1.21.3/networking/streamcodecs.md | 383 +++++ .../version-1.21.3/resources/_category_.json | 4 + .../resources/client/_category_.json | 3 + .../version-1.21.3/resources/client/i18n.md | 190 +++ .../resources/client/models/bakedmodel.md | 137 ++ .../resources/client/models/datagen.md | 283 ++++ .../resources/client/models/index.md | 328 ++++ .../resources/client/models/modelloaders.md | 488 ++++++ .../resources/client/particles.md | 268 ++++ .../version-1.21.3/resources/client/sounds.md | 315 ++++ .../resources/client/textures.md | 73 + .../version-1.21.3/resources/index.md | 218 +++ .../resources/server/_category_.json | 3 + .../resources/server/advancements.md | 293 ++++ .../resources/server/conditions.md | 202 +++ .../resources/server/damagetypes.md | 134 ++ .../resources/server/datamaps/builtin.md | 228 +++ .../resources/server/datamaps/index.md | 390 +++++ .../resources/server/enchantments/builtin.md | 341 ++++ .../resources/server/enchantments/index.md | 329 ++++ .../resources/server/loottables/custom.md | 298 ++++ .../resources/server/loottables/glm.md | 194 +++ .../resources/server/loottables/index.md | 433 ++++++ .../server/loottables/lootconditions.md | 400 +++++ .../server/loottables/lootfunctions.md | 859 ++++++++++ .../resources/server/recipes/builtin.md | 388 +++++ .../resources/server/recipes/custom.md | 756 +++++++++ .../resources/server/recipes/index.md | 218 +++ .../resources/server/recipes/ingredients.md | 162 ++ .../version-1.21.3/resources/server/tags.md | 290 ++++ .../version-1.21.3/worldgen/_category_.json | 4 + .../version-1.21.3/worldgen/biomemodifier.md | 717 +++++++++ .../version-1.21.3-sidebars.json | 8 + versions.json | 1 + 122 files changed, 19413 insertions(+), 940 deletions(-) create mode 100644 docs/resources/client/models/items.md create mode 100644 versioned_docs/version-1.21.3/advanced/_category_.json create mode 100644 versioned_docs/version-1.21.3/advanced/accesstransformers.md create mode 100644 versioned_docs/version-1.21.3/advanced/extensibleenums.md create mode 100644 versioned_docs/version-1.21.3/advanced/featureflags.md create mode 100644 versioned_docs/version-1.21.3/blockentities/_category_.json create mode 100644 versioned_docs/version-1.21.3/blockentities/ber.md create mode 100644 versioned_docs/version-1.21.3/blockentities/container.md create mode 100644 versioned_docs/version-1.21.3/blockentities/index.md create mode 100644 versioned_docs/version-1.21.3/blocks/_category_.json create mode 100644 versioned_docs/version-1.21.3/blocks/index.md create mode 100644 versioned_docs/version-1.21.3/blocks/states.md create mode 100644 versioned_docs/version-1.21.3/concepts/_category_.json create mode 100644 versioned_docs/version-1.21.3/concepts/events.md create mode 100644 versioned_docs/version-1.21.3/concepts/registries.md create mode 100644 versioned_docs/version-1.21.3/concepts/sides.md create mode 100644 versioned_docs/version-1.21.3/datastorage/_category_.json create mode 100644 versioned_docs/version-1.21.3/datastorage/attachments.md create mode 100644 versioned_docs/version-1.21.3/datastorage/capabilities.md create mode 100644 versioned_docs/version-1.21.3/datastorage/codecs.md create mode 100644 versioned_docs/version-1.21.3/datastorage/nbt.md create mode 100644 versioned_docs/version-1.21.3/datastorage/saveddata.md create mode 100644 versioned_docs/version-1.21.3/gettingstarted/_category_.json create mode 100644 versioned_docs/version-1.21.3/gettingstarted/index.md create mode 100644 versioned_docs/version-1.21.3/gettingstarted/modfiles.md create mode 100644 versioned_docs/version-1.21.3/gettingstarted/structuring.md create mode 100644 versioned_docs/version-1.21.3/gettingstarted/versioning.md create mode 100644 versioned_docs/version-1.21.3/gui/_category_.json create mode 100644 versioned_docs/version-1.21.3/gui/menus.md create mode 100644 versioned_docs/version-1.21.3/gui/screens.md create mode 100644 versioned_docs/version-1.21.3/items/_category_.json create mode 100644 versioned_docs/version-1.21.3/items/armor.md create mode 100644 versioned_docs/version-1.21.3/items/consumables.md create mode 100644 versioned_docs/version-1.21.3/items/datacomponents.md create mode 100644 versioned_docs/version-1.21.3/items/index.md create mode 100644 versioned_docs/version-1.21.3/items/interactionpipeline.md create mode 100644 versioned_docs/version-1.21.3/items/mobeffects.md create mode 100644 versioned_docs/version-1.21.3/items/tools.md create mode 100644 versioned_docs/version-1.21.3/legacy/_category_.json create mode 100644 versioned_docs/version-1.21.3/legacy/porting.md create mode 100644 versioned_docs/version-1.21.3/misc/_category_.json create mode 100644 versioned_docs/version-1.21.3/misc/config.md create mode 100644 versioned_docs/version-1.21.3/misc/debugprofiler.md create mode 100644 versioned_docs/version-1.21.3/misc/gametest.md create mode 100644 versioned_docs/version-1.21.3/misc/keymappings.md create mode 100644 versioned_docs/version-1.21.3/misc/resourcelocation.md create mode 100644 versioned_docs/version-1.21.3/misc/updatechecker.md create mode 100644 versioned_docs/version-1.21.3/networking/_category_.json create mode 100644 versioned_docs/version-1.21.3/networking/configuration-tasks.md create mode 100644 versioned_docs/version-1.21.3/networking/entities.md create mode 100644 versioned_docs/version-1.21.3/networking/index.md create mode 100644 versioned_docs/version-1.21.3/networking/payload.md create mode 100644 versioned_docs/version-1.21.3/networking/streamcodecs.md create mode 100644 versioned_docs/version-1.21.3/resources/_category_.json create mode 100644 versioned_docs/version-1.21.3/resources/client/_category_.json create mode 100644 versioned_docs/version-1.21.3/resources/client/i18n.md create mode 100644 versioned_docs/version-1.21.3/resources/client/models/bakedmodel.md create mode 100644 versioned_docs/version-1.21.3/resources/client/models/datagen.md create mode 100644 versioned_docs/version-1.21.3/resources/client/models/index.md create mode 100644 versioned_docs/version-1.21.3/resources/client/models/modelloaders.md create mode 100644 versioned_docs/version-1.21.3/resources/client/particles.md create mode 100644 versioned_docs/version-1.21.3/resources/client/sounds.md create mode 100644 versioned_docs/version-1.21.3/resources/client/textures.md create mode 100644 versioned_docs/version-1.21.3/resources/index.md create mode 100644 versioned_docs/version-1.21.3/resources/server/_category_.json create mode 100644 versioned_docs/version-1.21.3/resources/server/advancements.md create mode 100644 versioned_docs/version-1.21.3/resources/server/conditions.md create mode 100644 versioned_docs/version-1.21.3/resources/server/damagetypes.md create mode 100644 versioned_docs/version-1.21.3/resources/server/datamaps/builtin.md create mode 100644 versioned_docs/version-1.21.3/resources/server/datamaps/index.md create mode 100644 versioned_docs/version-1.21.3/resources/server/enchantments/builtin.md create mode 100644 versioned_docs/version-1.21.3/resources/server/enchantments/index.md create mode 100644 versioned_docs/version-1.21.3/resources/server/loottables/custom.md create mode 100644 versioned_docs/version-1.21.3/resources/server/loottables/glm.md create mode 100644 versioned_docs/version-1.21.3/resources/server/loottables/index.md create mode 100644 versioned_docs/version-1.21.3/resources/server/loottables/lootconditions.md create mode 100644 versioned_docs/version-1.21.3/resources/server/loottables/lootfunctions.md create mode 100644 versioned_docs/version-1.21.3/resources/server/recipes/builtin.md create mode 100644 versioned_docs/version-1.21.3/resources/server/recipes/custom.md create mode 100644 versioned_docs/version-1.21.3/resources/server/recipes/index.md create mode 100644 versioned_docs/version-1.21.3/resources/server/recipes/ingredients.md create mode 100644 versioned_docs/version-1.21.3/resources/server/tags.md create mode 100644 versioned_docs/version-1.21.3/worldgen/_category_.json create mode 100644 versioned_docs/version-1.21.3/worldgen/biomemodifier.md create mode 100644 versioned_sidebars/version-1.21.3-sidebars.json diff --git a/docs/advanced/featureflags.md b/docs/advanced/featureflags.md index 42a56237e..d6d72a760 100644 --- a/docs/advanced/featureflags.md +++ b/docs/advanced/featureflags.md @@ -170,7 +170,7 @@ This file differs from the one in your mod's `resources/` directory. This file d "examplemod:experimental" ] }, - "pack": { ... } + "pack": { /*...*/ } } ``` diff --git a/docs/blockentities/ber.md b/docs/blockentities/ber.md index ef0d8262e..2014def2c 100644 --- a/docs/blockentities/ber.md +++ b/docs/blockentities/ber.md @@ -60,70 +60,11 @@ public static void registerEntityRenderers(EntityRenderersEvent.RegisterRenderer } ``` -## `BlockEntityWithoutLevelRenderer` +## Item Block Rendering -`BlockEntityWithoutLevelRenderer`, colloquially known as BEWLR, is an adaptation of the regular `BlockEntityRenderer` for special [item] rendering (hence "without level", as items do not have level context). Its overall purpose is the same: do special rendering for cases where static models aren't enough. +As not all block entities with renderers can be rendered using static models, you can create a special renderer to customize the item rendering process. This is done using [`SpecialModelRenderer`s][special]. In these cases, both a special model renderer must be created to render the item correctly, and a corresponding registered special block model renderer for scenarios when a block is being rendered as an item (e.g., enderman carrying a block). -To add a BEWLR, create a class that extends `BlockEntityWithoutLevelRenderer` and overrides `#renderByItem`. It also requires some additional constructor setup: - -```java -public class MyBlockEntityWithoutLevelRenderer extends BlockEntityWithoutLevelRenderer { - // We need some boilerplate in the constructor, telling the superclass where to find the central block entity and entity renderers. - public MyBlockEntityWithoutLevelRenderer() { - super(Minecraft.getInstance().getBlockEntityRenderDispatcher(), Minecraft.getInstance().getEntityModels()); - } - - @Override - public void renderByItem(ItemStack stack, ItemDisplayContext transform, PoseStack poseStack, MultiBufferSource bufferSource, int packedLight, int packedOverlay) { - // Do the rendering here. - } -} -``` - -Keep in mind that, like with BERs, there is only one instance of your BEWLR. Stack-specific properties should therefore be stored in the stack, not the BEWLR. - -Unlike BERs, we do not register BEWLRs directly. Instead, we register an instance of `IClientItemExtensions` to the `RegisterClientExtensionsEvent`. `IClientItemExtensions` is an interface that allows us to specify a number of rendering-related behaviors on items, such as (but not limited to) a BEWLR. As such, our implementation of that interface could look like so: - -```java -public class MyClientItemExtensions implements IClientItemExtensions { - // Cache our BEWLR in a field. - private final MyBlockEntityWithoutLevelRenderer myBEWLR = new MyBlockEntityWithoutLevelRenderer(); - - // Return our BEWLR here. - @Override - public BlockEntityWithoutLevelRenderer getCustomRenderer() { - return myBEWLR; - } -} -``` - -And then, we can register our `IClientItemExtensions` to the event: - -```java -@SubscribeEvent -public static void registerClientExtensions(RegisterClientExtensionsEvent event) { - event.registerItem( - // The only instance of our IClientItemExtensions, and as such, the only instance of our BEWLR. - new MyClientItemExtensions(), - // A vararg list of items that use this BEWLR. - MyItems.ITEM_1, MyItems.ITEM_2 - ); -} -``` - -:::info -`IClientItemExtensions` are generally expected to be treated as singletons. Do not construct them outside `RegisterClientExtensionsEvent`! -::: - -Finally, the item has to know that it should use the BEWLR for its rendering. This is done by having the final [`BakedModel`][bakedmodel] return true for `#isCustomRenderer`. The easiest way to do this is to have the [item model JSON][model] with a `parent` of `minecraft:builtin/entity`: - -```json5 -// In some item model file assets//models/item/.json -{ - "parent": "minecraft: builtin/entity", - // ... -} -``` +Please refer to the [client item documentation][special] for more information. [block]: ../blocks/index.md [blockentity]: index.md @@ -131,3 +72,4 @@ Finally, the item has to know that it should use the BEWLR for its rendering. Th [eventbus]: ../concepts/events.md#event-buses [item]: ../items/index.md [model]: ../resources/client/models/index.md +[special]: ../resources/client/models/items.md#special-models diff --git a/docs/blocks/index.md b/docs/blocks/index.md index 2d5353bb9..21e3cb441 100644 --- a/docs/blocks/index.md +++ b/docs/blocks/index.md @@ -120,7 +120,7 @@ public static final DeferredRegister> REGISTRAR = Defe public static final Supplier> SIMPLE_CODEC = REGISTRAR.register( "simple", - () -> simpleCodec(SimpleBlock::new) + () -> BlockBehaviour.simpleCodec(SimpleBlock::new) ); ``` diff --git a/docs/concepts/events.md b/docs/concepts/events.md index 75dfb0380..3231949ca 100644 --- a/docs/concepts/events.md +++ b/docs/concepts/events.md @@ -197,12 +197,12 @@ Then, during `InterModProcessEvent`, you can use `InterModComms#getMessages` to Next to the lifecycle events, there are a few miscellaneous events that are fired on the mod event bus, mostly for legacy reasons. These are generally events where you can register, set up, or initialize various things. Most of these events are not ran in parallel in contrast to the lifecycle events. A few examples: -- `RegisterColorHandlersEvent.Block`, `.Item`, `.ColorResolvers` +- `RegisterColorHandlersEvent.Block`, `.ItemTintSources`, `.ColorResolvers` - `ModelEvent.BakingCompleted` - `TextureAtlasStitchedEvent` :::warning -Most of these events are planned to be moved to the main event bus in a future version. +Most of these events are planned to be moved to the game event bus in a future version. ::: [modbus]: #event-buses diff --git a/docs/concepts/registries.md b/docs/concepts/registries.md index 34d0e82dc..5dd0c5489 100644 --- a/docs/concepts/registries.md +++ b/docs/concepts/registries.md @@ -43,14 +43,14 @@ We can then add our registry entries as static final fields using one of the fol ```java public static final DeferredHolder EXAMPLE_BLOCK_1 = BLOCKS.register( // Our registry name. - "example_block" + "example_block", // A supplier of the object we want to register. () -> new Block(...) ); public static final DeferredHolder EXAMPLE_BLOCK_2 = BLOCKS.register( // Our registry name. - "example_block" + "example_block", // A function creating the object we want to register // given its registry name as a ResourceLocation. registryName -> new SlabBlock(...) @@ -64,14 +64,14 @@ The class `DeferredHolder` holds our object. The type parameter ```java public static final Supplier EXAMPLE_BLOCK_1 = BLOCKS.register( // Our registry name. - "example_block" + "example_block", // A supplier of the object we want to register. () -> new Block(...) ); public static final Supplier EXAMPLE_BLOCK_2 = BLOCKS.register( // Our registry name. - "example_block" + "example_block", // A function creating the object we want to register // given its registry name as a ResourceLocation. registryName -> new SlabBlock(...) @@ -236,7 +236,10 @@ public static void registerDatapackRegistries(DataPackRegistryEvent.NewRegistry // May be null. If null, registry entries will not be synced to the client at all. // May be omitted, which is functionally identical to passing null (a method overload // with two parameters is called that passes null to the normal three parameter method). - Spell.CODEC + Spell.CODEC, + // A consumer which configures the constructed registry via the RegistryBuilder. + // May be omitted, which is functionally identical to passing builder -> {}. + builder -> builder.maxId(256) ); } ``` diff --git a/docs/datastorage/attachments.md b/docs/datastorage/attachments.md index 13562c28f..d71025514 100644 --- a/docs/datastorage/attachments.md +++ b/docs/datastorage/attachments.md @@ -3,7 +3,7 @@ sidebar_position: 3 --- # Data Attachments -The data attachment system allows mods to attach and store additional data on block entities, chunks, and entities. +The data attachment system allows mods to attach and store additional data on block entities, chunks, entities, and levels. _To store additional level data, you can use [SavedData][saveddata]._ diff --git a/docs/gettingstarted/index.md b/docs/gettingstarted/index.md index 686f25893..aa793f640 100644 --- a/docs/gettingstarted/index.md +++ b/docs/gettingstarted/index.md @@ -55,7 +55,7 @@ You should always test your mod in a dedicated server environment. This includes [github]: https://github.com/ [intellij]: https://www.jetbrains.com/idea/ [jdk]: https://learn.microsoft.com/en-us/java/openjdk/download#openjdk-21 -[mdgmdk]: https://github.com/NeoForgeMDKs/MDK-1.21-ModDevGradle -[ngmdk]: https://github.com/NeoForgeMDKs/MDK-1.21-NeoGradle +[mdgmdk]: https://github.com/NeoForgeMDKs/MDK-1.21.4-ModDevGradle +[ngmdk]: https://github.com/NeoForgeMDKs/MDK-1.21.4-NeoGradle [neogradle]: https://docs.neoforged.net/neogradle/docs/ [properties]: modfiles.md#gradleproperties diff --git a/docs/gettingstarted/modfiles.md b/docs/gettingstarted/modfiles.md index 258209793..6a1140b5d 100644 --- a/docs/gettingstarted/modfiles.md +++ b/docs/gettingstarted/modfiles.md @@ -12,6 +12,9 @@ Most values are also explained as comments in [the MDK's `gradle.properties` fil |---------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------| | `org.gradle.jvmargs` | Allows you to pass extra JVM arguments to Gradle. Most commonly, this is used to assign more/less memory to Gradle. Note that this is for Gradle itself, not Minecraft. | `org.gradle.jvmargs=-Xmx3G` | | `org.gradle.daemon` | Whether Gradle should use the daemon when building. | `org.gradle.daemon=false` | +| `org.gradle.parallel` | Whether Gradle should fork JVMs to execute projects in parallel. | `org.gradle.parallel=false` | +| `org.gradle.caching` | Whether Gradle should reuse task outputs from previous builds. | `org.gradle.caching=false` | +| `org.gradle.configuration-cache` | Whether Gradle should reuse the build configuration from previous builds. | `org.gradle.configuration-cache=false` | | `org.gradle.debug` | Whether Gradle is set to debug mode. Debug mode mainly means more Gradle log output. Note that this is for Gradle itself, not Minecraft. | `org.gradle.debug=false` | | `minecraft_version` | The Minecraft version you are modding on. Must match with `neo_version`. | `minecraft_version=1.20.6` | | `minecraft_version_range` | The Minecraft version range this mod can use, as a [Maven Version Range][mvr]. Note that [snapshots, pre-releases and release candidates][mcversioning] are not guaranteed to sort properly, as they do not follow maven versioning. | `minecraft_version_range=[1.20.6,1.21)` | diff --git a/docs/items/armor.md b/docs/items/armor.md index 86204da70..dafe865f2 100644 --- a/docs/items/armor.md +++ b/docs/items/armor.md @@ -61,9 +61,9 @@ public static final ArmorMaterial COPPER_ARMOR_MATERIAL = new ArmorMaterial( 0, // The tag that determines what items can repair this armor. Tags.Items.INGOTS_COPPER, - // The relative location of the EquipmentModel JSON discussed below - // Points to assets/examplemod/models/equipment/copper.json - ResourceLocation.fromNamespaceAndPath("examplemod", "copper") + // The resource key of the EquipmentClientInfo JSON discussed below + // Points to assets/examplemod/equipment/copper.json + ResourceKey.create(EquipmentAssets.ROOT_ID, ResourceLocation.fromNamespaceAndPath("examplemod", "copper")) ); ``` @@ -115,10 +115,6 @@ Now, creating armor or an armor-like item does not need to extend `ArmorItem` or - Allowing the item to be enchanted via `Item.Properties#enchantable`. - Adding your armor to some of the `minecraft:enchantable/*` `ItemTags` so that your item can have certain enchantments applied to it. -:::note -The only logic that has no alternative to using an `ArmorItem` base is when mobs replace their current held item via `Mob#canReplaceCurrentItem`. Mobs will always check for an instance of `SwordItem`, followed by `BowItem`, `CrossbowItem`, `ArmorItem`, and `DiggerItem`, with all other items not considered for specialized logic. -::: - ### `Equippable` `Equippable` is a data component that contains how an entity can equip this item and what handles the rendering in game. This allows any item, regardless of whether it is considered 'armor', to be equipped if this component is available (for example carpets on llamas). Each item with this component can only be equipped to a single `EquipmentSlot`. @@ -137,10 +133,10 @@ public static final DeferredItem EQUIPPABLE = ITEMS.registerSimpleItem( // This is wrapped with a Holder. // Defaults to SoundEvents#ARMOR_EQUIP_GENERIC. .setEquipSound(SoundEvents.ARMOR_EQUIP_GENERIC) - // The relative location of the EquipmentModel JSON discussed below. - // Points to assets/examplemod/models/equipment/equippable.json + // The resource key of the EquipmentClientInfo JSON discussed below. + // Points to assets/examplemod/equipment/equippable.json // When not set, does not render the equipment. - .setModel(ResourceLocation.fromNamespaceAndPath("examplemod", "equippable")) + .setAsset(ResourceKey.create(EquipmentAssets.ROOT_ID, ResourceLocation.fromNamespaceAndPath("examplemod", "equippable"))) // The relative location of the texture to overlay on the player screen when wearing (e.g., pumpkin blur). // Points to assets/examplemod/textures/equippable.png // When not set, does not render an overlay. @@ -163,21 +159,17 @@ public static final DeferredItem EQUIPPABLE = ITEMS.registerSimpleItem( ); ``` -## Equipment Models +## Equipment Assets -Now we have some armor in game, but if we try to wear it, nothing will render since we never specified how to render the equipment. To do so, we need to create an `EquipmentModel` JSON at the location specified by `Equippable#model`, relative to the `models/equipment` folder of the [resource pack][respack] (`assets` folder). The `EquipmentModel` specifies the associated textures to use for each layer to render. - -:::note -`EquipmentModel` is a bit of a misnomer as it does **not** specify the model to use when rendering, only the textures. The model used is the one passed in during [equipment rendering][rendering]. -::: +Now we have some armor in game, but if we try to wear it, nothing will render since we never specified how to render the equipment. To do so, we need to create an `EquipmentClientInfo` JSON at the location specified by `Equippable#assetId`, relative to the `equipment` folder of the [resource pack][respack] (`assets` folder). The `EquipmentClientInfo` specifies the associated textures to use for each layer to render. -An `EquipmentModel` is functionally a map of `EquipmentModel.LayerType`s to a list of `EquipmentModel.Layer`s to apply. +An `EquipmentClientInfo` is functionally a map of `EquipmentClientInfo.LayerType`s to a list of `EquipmentClientInfo.Layer`s to apply. -The `LayerType` can be thought of as a group of textures to render for some instance. For example, `LayerType#HUMANOID` is used by the `HumanoidArmorLayer` to render the head, chest, and feet on humanoid entities; `LayerType#WOLF_BODY` is used by `WolfArmorLayer` to render the body armor. These can be combined into one equipment model JSON if they are for the same type of equippable, like copper armor. +The `LayerType` can be thought of as a group of textures to render for some instance. For example, `LayerType#HUMANOID` is used by the `HumanoidArmorLayer` to render the head, chest, and feet on humanoid entities; `LayerType#WOLF_BODY` is used by `WolfArmorLayer` to render the body armor. These can be combined into one equipment info JSON if they are for the same type of equippable, like copper armor. -The `LayerType` maps to some list of `Layer`s to apply and render the model in the order provided. A `Layer` effectively represents a single texture to render. The first parameter represents the location of the texture, relative to `textures/entity/equipment`. +The `LayerType` maps to some list of `Layer`s to apply and render the textures in the order provided. A `Layer` effectively represents a single texture to render. The first parameter represents the location of the texture, relative to `textures/entity/equipment`. -The second parameter is an optional that indicates whether the [texture can be tinted][tinting] as a `EquipmentModel.Dyeable`. The `Dyeable` object holds an integer that, when present, indicates the default RGB color to tint the texture with. If this optional is not present, then pure white is used. +The second parameter is an optional that indicates whether the [texture can be tinted][tinting] as an `EquipmentClientInfo.Dyeable`. The `Dyeable` object holds an integer that, when present, indicates the default RGB color to tint the texture with. If this optional is not present, then pure white is used. :::warning For a tint other than the undyed color to be applied to the item, the item must be in the [`ItemTags#DYEABLE`][tag] and have the `DataComponents#DYED_COLOR` component set to some RGB value. @@ -185,17 +177,17 @@ For a tint other than the undyed color to be applied to the item, the item must The third parameter is a boolean that indicates whether the texture provided during rendering should be used instead of the one defined within the `Layer`. An example of this is a custom cape or custom elytra texture for the player. -Let's create a equipment model for the copper armor material. We'll also assume that for each layer there are two textures: one for the actual armor and one that is overlayed and tinted. For the animal armor, we'll say that there is some dynamic texture to be used that can be passed in. +Let's create an equipment info for the copper armor material. We'll also assume that for each layer there are two textures: one for the actual armor and one that is overlayed and tinted. For the animal armor, we'll say that there is some dynamic texture to be used that can be passed in. ```json5 -// In assets/examplemod/models/equipment/copper.json +// In assets/examplemod/equipment/copper.json { // The layer map "layers": { - // The serialized name of the EquipmentModel.LayerType to apply. + // The serialized name of the EquipmentClientInfo.LayerType to apply. // For humanoid head, chest, and feet "humanoid": [ // A list of layers to render in the order provided @@ -258,24 +250,24 @@ Let's create a equipment model for the copper armor material. We'll also assume ```java -public class MyEquipmentModelProvider implements DataProvider { +public class MyEquipmentInfoProvider implements DataProvider { private final PackOutput.PathProvider path; - public MyEquipmentModelProvider(PackOutput output) { - this.path = output.createPathProvider(PackOutput.Target.RESOURCE_PACK, "models/equipment"); + public MyEquipmentInfoProvider(PackOutput output) { + this.path = output.createPathProvider(PackOutput.Target.RESOURCE_PACK, "equipment"); } - private void add(BiConsumer registrar) { + private void add(BiConsumer registrar) { registrar.accept( - // Must match Equippable#model + // Must match Equippable#assetId ResourceLocation.fromNamespaceAndPath("examplemod", "copper"), - EquipmentModel.builder() + EquipmentClientInfo.builder() // For humanoid head, chest, and feet .addLayers( - EquipmentModel.LayerType.HUMANOID, + EquipmentClientInfo.LayerType.HUMANOID, // Base texture - new EquipmentModel.Layer( + new EquipmentClientInfo.Layer( // The relative texture of the armor // Points to assets/examplemod/textures/entity/equipment/copper/outer.png ResourceLocation.fromNamespaceAndPath("examplemod", "copper/outer"), @@ -283,37 +275,37 @@ public class MyEquipmentModelProvider implements DataProvider { false ), // Overlay texture - new EquipmentModel.Layer( + new EquipmentClientInfo.Layer( // The overlay texture // Points to assets/examplemod/textures/entity/equipment/copper/outer_overlay.png ResourceLocation.fromNamespaceAndPath("examplemod", "copper/outer_overlay"), // An RGB value (always opaque color) // When not specified, set to 0 (meaning transparent or invisible) - Optional.of(new EquipmentModel.Dyeable(Optional.of(0x7683DE))), + Optional.of(new EquipmentClientInfo.Dyeable(Optional.of(0x7683DE))), false ) ) // For humanoid legs .addLayers( - EquipmentModel.LayerType.HUMANOID_LEGGINGS, - new EquipmentModel.Layer( + EquipmentClientInfo.LayerType.HUMANOID_LEGGINGS, + new EquipmentClientInfo.Layer( // Points to assets/examplemod/textures/entity/equipment/copper/inner.png ResourceLocation.fromNamespaceAndPath("examplemod", "copper/inner"), Optional.empty(), false ), - new EquipmentModel.Layer( + new EquipmentClientInfo.Layer( // Points to assets/examplemod/textures/entity/equipment/copper/inner_overlay.png ResourceLocation.fromNamespaceAndPath("examplemod", "copper/inner_overlay"), - Optional.of(new EquipmentModel.Dyeable(Optional.of(0x7683DE))), + Optional.of(new EquipmentClientInfo.Dyeable(Optional.of(0x7683DE))), false ) ) // For wolf armor .addLayers( - EquipmentModel.LayerType.WOLF_BODY, + EquipmentClientInfo.LayerType.WOLF_BODY, // Base texture - new EquipmentModel.Layer( + new EquipmentClientInfo.Layer( // Points to assets/examplemod/textures/entity/equipment/copper/wolf.png ResourceLocation.fromNamespaceAndPath("examplemod", "copper/wolf"), Optional.empty(), @@ -323,9 +315,9 @@ public class MyEquipmentModelProvider implements DataProvider { ) // For horse armor .addLayers( - EquipmentModel.LayerType.HORSE_BODY, + EquipmentClientInfo.LayerType.HORSE_BODY, // Base texture - new EquipmentModel.Layer( + new EquipmentClientInfo.Layer( // Points to assets/examplemod/textures/entity/equipment/copper/horse.png ResourceLocation.fromNamespaceAndPath("examplemod", "copper/horse"), Optional.empty(), @@ -338,18 +330,18 @@ public class MyEquipmentModelProvider implements DataProvider { @Override public CompletableFuture run(CachedOutput cache) { - Map map = new HashMap<>(); - this.add((name, model) -> { - if (map.putIfAbsent(name, model) != null) { - throw new IllegalStateException("Tried to register equipment model twice for id: " + name); + Map map = new HashMap<>(); + this.add((name, info) -> { + if (map.putIfAbsent(name, info) != null) { + throw new IllegalStateException("Tried to register equipment client info twice for id: " + name); } }); - return DataProvider.saveAll(cache, EquipmentModel.CODEC, this.pathProvider, map); + return DataProvider.saveAll(cache, EquipmentClientInfo.CODEC, this.pathProvider, map); } @Override public String getName() { - return "Equipment Model Definitions: " + MOD_ID; + return "Equipment Client Infos: " + MOD_ID; } } @@ -361,7 +353,7 @@ public static void gatherData(GatherDataEvent event) { // Other providers here event.getGenerator().addProvider( event.includeClient(), - new MyEquipmentModelProvider(output) + new MyEquipmentInfoProvider(output) ); } ``` @@ -371,9 +363,9 @@ public static void gatherData(GatherDataEvent event) { ## Equipment Rendering -The equipment models are rendered via the `EquipmentLayerRenderer` in the render function of an `EntityRenderer` or one of its `RenderLayer`s. `EquipmentLayerRenderer` is obtained as part of the render context via `EntityRendererProvider.Context#getEquipmentRenderer`. If the `EquipmentModel`s are required, they are also available via `EntityRendererProvider.Context#getEquipmentModels`. +The equipment infos are rendered via the `EquipmentLayerRenderer` in the render function of an `EntityRenderer` or one of its `RenderLayer`s. `EquipmentLayerRenderer` is obtained as part of the render context via `EntityRendererProvider.Context#getEquipmentRenderer`. If the `EquipmentClientInfo`s are required, they are also available via `EntityRendererProvider.Context#getEquipmentAssets`. -By default, the following layers render the associated `EquipmentModel.LayerType`: +By default, the following layers render the associated `EquipmentClientInfo.LayerType`: | `LayerType` | `RenderLayer` | Used by | |:-------------------:|:--------------------:|:---------------------------------------------------------------| @@ -390,11 +382,11 @@ By default, the following layers render the associated `EquipmentModel.LayerType // In some render method where EquipmentLayerRenderer equipmentLayerRenderer is a field this.equipmentLayerRenderer.renderLayers( // The layer type to render - EquipmentModel.LayerType.HUMANOID, - // The model id representing the EquipmentModel JSON - // This would be set in the `EQUIPPABLE` data component via `model` - stack.get(DataComponents.EQUIPPABLE).model().orElseThrow(), - // The model to apply the textures to + EquipmentClientInfo.LayerType.HUMANOID, + // The resource key representing the EquipmentClientInfo JSON + // This would be set in the `EQUIPPABLE` data component via `assetId` + stack.get(DataComponents.EQUIPPABLE).assetId().orElseThrow(), + // The model to apply the equipment info to // These are usually separate models from the entity model // and are separate ModelLayers linking to a LayerDefinition model, diff --git a/docs/items/index.md b/docs/items/index.md index d4691346c..6f2928c75 100644 --- a/docs/items/index.md +++ b/docs/items/index.md @@ -23,7 +23,6 @@ Like with basic blocks, for basic items that need no special functionality (thin - This **must** be set on every item; otherwise, an exception will be thrown. - `overrideDescription` - Sets the translation key of the item. The created `Component` is stored in `DataComponents#ITEM_NAME`. - `useBlockDescriptionPrefix` - Convenience helper that calls `overrideDescription` with the translation key `block..`. This should be called on any `BlockItem`. -- `overrideModel` - Sets the `ResourceLocation` representing the item model and expands to `assets//models/item/.json`. The `ResourceLocation` is stored in `DataComponents#ITEM_MODEL`. - `requiredFeatures` - Sets the required feature flags for this item. This is mainly used for vanilla's feature locking system in minor versions. It is discouraged to use this, unless you're integrating with a system locked behind feature flags by vanilla. - `stacksTo` - Sets the max stack size (via `DataComponents#MAX_STACK_SIZE`) of this item. Defaults to 64. Used e.g. by ender pearls or other items that only stack to 16. - `durability` - Sets the durability (via `DataComponents#MAX_DAMAGE`) of this item and the initial damage to 0 (via `DataComponents#DAMAGE`). Defaults to 0, which means "no durability". For example, iron tools use 250 here. Note that setting the durability automatically locks the max stack size to 1. @@ -133,9 +132,9 @@ If you keep your registered blocks in a separate class, you should classload you ### Resources -If you register your item and get your item (via `/give` or through a [creative tab][creativetabs]), you will find it to be missing a proper model and texture. This is because textures and models are handled by Minecraft's resource system. +If you register your item and get your item (via `/give` or through a [creative tab][creativetabs]), you will find it to be missing a proper model and texture. This is because textures and models are handled by Minecraft's resource system. -To apply a simple texture to an item, you must add an item model JSON and a texture PNG. See the section on [resources][resources] for more information. +To apply a simple texture to an item, you must create a client item, model JSON, and a texture PNG. See the section on [client items][citems] for more information. ## `ItemStack`s @@ -240,6 +239,7 @@ It is also possible to implement `ItemLike` on your custom objects. Simply overr [block]: ../blocks/index.md [blockstates]: ../blocks/states.md [breaking]: ../blocks/index.md#breaking-a-block +[citems]: ../resources/client/models/items.md [creativetabs]: #creative-tabs [datacomponents]: datacomponents.md [datagen]: ../resources/index.md#data-generation @@ -250,6 +250,5 @@ It is also possible to implement `ItemLike` on your custom objects. Simply overr [modbus]: ../concepts/events.md#event-buses [recipes]: ../resources/server/recipes/index.md [registering]: ../concepts/registries.md#methods-for-registering -[resources]: ../resources/index.md#assets [sides]: ../concepts/sides.md [tools]: tools.md diff --git a/docs/items/mobeffects.md b/docs/items/mobeffects.md index 9e1469324..7a6e7634b 100644 --- a/docs/items/mobeffects.md +++ b/docs/items/mobeffects.md @@ -24,7 +24,7 @@ public class MyMobEffect extends MobEffect { } @Override - public boolean applyEffectTick(LivingEntity entity, int amplifier) { + public boolean applyEffectTick(ServerLevel level, LivingEntity entity, int amplifier) { // Apply your effect logic here. // If this returns false when shouldApplyEffectTickThisTick returns true, the effect will immediately be removed @@ -84,7 +84,7 @@ public class MyMobEffect extends InstantenousMobEffect { } @Override - public void applyEffectTick(LivingEntity entity, int amplifier) { + public void applyEffectTick(ServerLevel level, LivingEntity entity, int amplifier) { // Apply your effect logic here. } } @@ -139,7 +139,7 @@ MobEffectInstance instance = new MobEffectInstance(...); livingEntity.addEffect(instance); ``` -Similarly, `MobEffectInstance` can also be removed from an `LivingEntity`. Since a `MobEffectInstance` overwrites pre-existing `MobEffectInstance`s of the same `MobEffect` on the entity, there can only ever be one `MobEffectInstance` per `MobEffect` and entity. As such, specifying the `MobEffect` suffices when removing: +Similarly, `MobEffectInstance`s can also be removed from an `LivingEntity`. Since a `MobEffectInstance` overwrites pre-existing `MobEffectInstance`s of the same `MobEffect` on the entity, there can only ever be one `MobEffectInstance` per `MobEffect` and entity. As such, specifying the `MobEffect` suffices when removing: ```java livingEntity.removeEffect(MobEffects.REGENERATION); diff --git a/docs/items/tools.md b/docs/items/tools.md index 67d76b22f..2b074f000 100644 --- a/docs/items/tools.md +++ b/docs/items/tools.md @@ -147,10 +147,7 @@ Creating any tool or multitool-like item (i.e. an item that combines two or more - Overriding `IItemExtension#canPerformAction` to determine what [`ItemAbility`s][itemability] the item can perform. - Calling `IBlockExtension#getToolModifiedState` if you want your item to modify the block state on right click based on the `ItemAbility`s. - Adding your tool to some of the `minecraft:enchantable/*` `ItemTags` so that your item can have certain enchantments applied to it. - -:::note -The only logic that has no alternative to using a `DiggerItem` or `SwordItem` base is when mobs replace their current held item via `Mob#canReplaceCurrentItem`. Mobs will always check for an instance of `SwordItem`, followed by `BowItem`, `CrossbowItem`, `ArmorItem`, and `DiggerItem`, with all other items not considered for specialized logic. -::: +- Adding your tool to some of the `minecraft:*_preferred_weapons` tags to allow mobs to favor your weapon to pickup and use. ## `ItemAbility`s diff --git a/docs/legacy/porting.md b/docs/legacy/porting.md index ceccede94..19fcbce44 100644 --- a/docs/legacy/porting.md +++ b/docs/legacy/porting.md @@ -1,4 +1,4 @@ -# Porting to Minecraft 1.21.3 +# Porting to Minecraft 1.21.4 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. @@ -16,6 +16,7 @@ Here you can find a list of primers on how to port from old versions to the curr | 1.20.6 -> 1.21 | [Primer by ChampionAsh5357][1206to121] | | 1.21 -> 1.21.1 | [Primer by ChampionAsh5357][121to1211] | | 1.21.1 -> 1.21.2/3 | [Primer by ChampionAsh5357][1211to1212] | +| 1.21.3 -> 1.21.4 | [Primer by ChampionAsh5357][1213to1214] | [112to114]: https://gist.github.com/williewillus/353c872bcf1a6ace9921189f6100d09a [114to115]: https://gist.github.com/williewillus/30d7e3f775fe93c503bddf054ef3f93e @@ -29,3 +30,4 @@ Here you can find a list of primers on how to port from old versions to the curr [1206to121]: https://github.com/neoforged/.github/blob/main/primers/1.21/index.md [121to1211]: https://github.com/neoforged/.github/blob/main/primers/1.21.1/index.md [1211to1212]: https://github.com/neoforged/.github/blob/main/primers/1.21.2/index.md +[1213to1214]: https://github.com/neoforged/.github/blob/main/primers/1.21.4/index.md diff --git a/docs/misc/config.md b/docs/misc/config.md index 1c15a1c23..a6c9c9879 100644 --- a/docs/misc/config.md +++ b/docs/misc/config.md @@ -43,6 +43,7 @@ Each config value can be supplied with additional context to provide additional | `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. | +| `gameRestart` | The game must be restarted before the config value can be changed. | ### ConfigValue @@ -168,11 +169,12 @@ Configurations registered under the `STARTUP` type can cause desyncs between the ## 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. +Operations that occur whenever a config is loaded, reloaded, or unloaded can be done using the `ModConfigEvent.Loading`, `ModConfigEvent.Reloading`, and `ModConfigEvent.Unloading` 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. ::: + ## Configuration Screen A configuration screen allows users to edit the config values for a mod while in-game without needing to open any files. The screen will automatically parse your registered config files and populate the screen. @@ -208,6 +210,6 @@ To make translating easier, open the configuration screen and visit all of the c [toml]: https://toml.io/ [nightconfig]: https://github.com/TheElectronWill/night-config [configtype]: #configuration-types -[type]: https://github.com/neoforged/FancyModLoader/blob/1b6af92893464a4f477cab310256639f39d41ea7/loader/src/main/java/net/neoforged/fml/config/ModConfig.java#L81-L114 +[type]: https://github.com/neoforged/FancyModLoader/blob/b4a1040118547a37c0f5f58146ac4cecc7817f82/loader/src/main/java/net/neoforged/fml/config/ModConfig.java#L86-L119 [events]: ../concepts/events.md#registering-an-event-handler [client]: ../concepts/sides.md#mod diff --git a/docs/misc/resourcelocation.md b/docs/misc/resourcelocation.md index dda7c1de6..96f15ddc8 100644 --- a/docs/misc/resourcelocation.md +++ b/docs/misc/resourcelocation.md @@ -24,15 +24,14 @@ Some places, for example registries, use `ResourceLocation`s directly. Some othe - `ResourceLocation`s are used as identifiers for GUI background. For example, the furnace GUI uses the resource location `minecraft:textures/gui/container/furnace.png`. This maps to the file `assets/minecraft/textures/gui/container/furnace.png` on disk. Note that the `.png` suffix is required in this resource location. - `ResourceLocation`s are used as identifiers for block models. For example, the block model of dirt uses the resource location `minecraft:block/dirt`. This maps to the file `assets/minecraft/models/block/dirt.json` on disk. Note that the `.json` suffix is not required here. Note as well that this resource location automatically maps into the `models` subfolder. +- `ResourceLocations` are used as identifiers for client items. For example, the client item of the apple uses the resource location `minecraft:apple` (as defined by `DataComponents#ITEM_MODEL`). This maps to the file `assets/minecraft/items/apple.json`. Note that the `.json` suffix is not required here. Note as well that this resource location automatically maps into the `items` subfolder. - `ResourceLocation`s are used as identifiers for recipes. For example, the iron block crafting recipe uses the resource location `minecraft:iron_block`. This maps to the file `data/minecraft/recipe/iron_block.json` on disk. Note that the `.json` suffix is not required here. Note as well that this resource location automatically maps into the `recipe` subfolder. Whether the `ResourceLocation` expects a file suffix, or what exactly the resource location resolves to, depends on the use case. ## `ModelResourceLocation`s -`ModelResourceLocation`s are a special kind of resource location that includes a third part, called the variant. Minecraft uses these mainly to differentiate between different variants of models, where the different variants are used in different display contexts (for example with tridents, which have different models in first person, third person and inventories). The variant is always `inventory` for items, and the comma-delimited string of property-value pairs for blockstates (for example `facing=north,waterlogged=false`, empty for blocks with no blockstate properties). - -The variant is appended to the regular resource location, along with a `#`. For example, the full name of the diamond sword's item model is `minecraft:diamond_sword#inventory`. However, in most contexts, the `inventory` variant can be omitted. +`ModelResourceLocation`s are a special kind of resource location for block states that includes a third part, called the variant. Minecraft uses these mainly to differentiate between different variants of models. The variant is the comma-delimited string of property-value pairs for blockstates (for example `facing=north,waterlogged=false`, empty for blocks with no blockstate properties), appended to a regular resource location with a `#`. `ModelResourceLocation` is a [client only][sides] class. This means that servers referencing this class will crash with a `NoClassDefFoundError`. diff --git a/docs/resources/client/models/bakedmodel.md b/docs/resources/client/models/bakedmodel.md index 99a9089be..d030bfdf0 100644 --- a/docs/resources/client/models/bakedmodel.md +++ b/docs/resources/client/models/bakedmodel.md @@ -1,9 +1,13 @@ # Baked Models -`BakedModel`s are the in-code representation of a shape with textures. They can originate from multiple sources, for example from a call to `UnbakedModel#bake` (default model loader) or `IUnbakedGeometry#bake` ([custom model loaders][modelloader]). Some [block entity renderers][ber] also make use of baked models. There is no limit to how complex a model may be. +`BakedModel`s are the in-code representation of a shape with textures. Every model JSON, deserialized into an `UnbakedModel`, is resolved into this renderable entry via some call to `UnbakedModel#bake`. Some [block entity renderers][ber] also make use of baked models. There is no limit to how complex a model may be. Models are stored in the `ModelManager`, which can be accessed through `Minecraft.getInstance().modelManager`. Then, you can call `ModelManager#getModel` to get a certain model by its [`ModelResourceLocation`][mrl]. Mods will basically always reuse a model that was previously automatically loaded and baked. +:::warning +These are not to be confused with [item models][itemmodels], which consume `BakedModel`s for rendering. +::: + ## Methods of `BakedModel` ### `getQuads` @@ -39,13 +43,10 @@ Other methods in `BakedModel` that you may override and/or query include: | `TriState useAmbientOcclusion()` | Whether to use [ambient occlusion][ao] or not. Accepts a `BlockState`, `RenderType` and `ModelData` parameter and returns a `TriState` which allows not only force-disabling AO but also force-enabling AO. Has two overloads that each return a `boolean` parameter and accept either only a `BlockState` or no parameters at all; both of these are deprecated for removal in favor of the first variant. | | `boolean isGui3d()` | Whether this model renders as 3d or flat in GUI slots. | | `boolean usesBlockLight()` | Whether to use 3D lighting (`true`) or flat lighting from the front (`false`) when lighting the model. | -| `boolean isCustomRenderer()` | If true, skips normal rendering and calls an associated [`BlockEntityWithoutLevelRenderer`][bewlr]'s `renderByItem` method instead. If false, renders through the default renderer. | -| `BakedOverrides overrides()` | Returns the [`BakedOverrides`][bakedoverrides] associated with this model. This is only relevant on item models. | | `ModelData getModelData(BlockAndTintGetter, BlockPos, BlockState, ModelData)` | Returns the model data to use for the model. This method is passed an existing `ModelData` that is either the result of `BlockEntity#getModelData()` if the block has an associated block entity, or `ModelData.EMPTY` if that is not the case. This method can be used for blocks that need model data, but do not have a block entity, for example for blocks with connected textures. | | `TextureAtlasSprite getParticleIcon(ModelData)` | Returns the particle sprite to use for the model. May use the model data to use different particle sprites for different model data values. NeoForge-added, replacing the vanilla `getParticleIcon()` overload with no parameters. | | `ChunkRenderTypeSet getRenderTypes(BlockState, RandomSource, ModelData)` | Returns a `ChunkRenderTypeSet` containing the render type(s) to use for rendering the block model. A `ChunkRenderTypeSet` is a set-backed ordered `Iterable`. By default falls back to [getting the render type from the model JSON][rendertype]. Only used for block models, item models use the overload below. | -| `List getRenderTypes(ItemStack)` | Returns a `List` containing the render type(s) to use for rendering the item model. By default falls back to the normal model-bound render type lookup, which always yields a list with one element. Only used for item models, block models use the overload above. | -| `List getRenderPasses(ItemStack)` | Returns a `List` containing the models used by the item. Each of those models' render types will be queired using `getRenderTypes`. | +| `RenderType getRenderType(ItemStack)` | Returns the `RenderType` to use for rendering the item model. By default falls back to the normal model-bound render type lookup. Only used by [item models][itemmodels], block models use `getRenderTypes`. | ## Perspectives @@ -63,27 +64,14 @@ Minecraft's render engine recognizes a total of 8 perspective types (9 if you in | `FIXED` | `"fixed"` | Item frames | | `NONE` | `"none"` | Fallback purposes in code, should not be used in JSON | -## `BakedOverrides` - -`BakedOverrides` is a class that provides a way for baked models to process the state of an [`ItemStack`][itemstack] and return a new baked model through the `#findOverride` method. `#findOverride` has four parameters: - -- An `ItemStack`: The item stack being rendered. -- A `ClientLevel`: The level the model is being rendered in. This should only be used for querying the level, not mutating it in any way. May be null. -- A `LivingEntity`: The entity the model is rendered on. May be null, e.g. when rendering from a [block entity renderer][ber]. -- An `int`: A seed for randomizing. +## `DelegateBakedModel` -`BakedOverrides` also hold the model's override options as `BakedOverride`s. An object of `BakedOverride` is an in-code representation of a model's [`overrides`][overrides] block. It can be used by baked models to return different models depending on its contents. A list of all `BakedOverride`s of an `BakedOverrides` instance can be retrieved through `BakedOverrides#getOverrides()`. - -## `BakedModelWrapper` - -A `BakedModelWrapper` can be used to modify an already existing `BakedModel`. `BakedModelWrapper` is a subclass of `BakedModel` that accepts another `BakedModel` (the "original" model) in the constructor and by default redirects all methods to the original model. Your implementation can then override only select methods, like so: +A `DelegateBakedModel` can be used to modify an already existing `BakedModel`. `DelegateBakedModel` is a subclass of `BakedModel` that accepts another `BakedModel` (the "original" model) in the constructor and by default redirects all methods to the original model. Your implementation can then override only select methods, like so: ```java -// The generic parameter may optionally be a more specific subclass of BakedModel. -// If it is, the constructor parameter must match that type. -public class MyBakedModelWrapper extends BakedModelWrapper { +public class MyDelegateBakedModel extends DelegateBakedModel { // Pass the original model to super. - public MyBakedModelWrapper(BakedModel originalModel) { + public MyDelegateBakedModel(BakedModel originalModel) { super(originalModel); } @@ -97,41 +85,28 @@ After writing your model wrapper class, you must apply the wrappers to the model @SubscribeEvent public static void modifyBakingResult(ModelEvent.ModifyBakingResult event) { // For block models - event.getModels().computeIfPresent( + event.getBakingResult().blockStateModels().computeIfPresent( // The model resource location of the model to modify. Get it from // BlockModelShaper#stateToModelLocation with the blockstate to be affected as a parameter. BlockModelShaper.stateToModelLocation(MyBlocksClass.EXAMPLE_BLOCK.defaultBlockState()), // A BiFunction with the location and the original models as parameters, returning the new model. - (location, model) -> new MyBakedModelWrapper(model); - ); - // For item models - event.getModels().computeIfPresent( - // The model resource location of the model to modify. - ModelResourceLocation.inventory( - ResourceLocation.fromNamespaceAndPath("examplemod", "example_item") - ), - // A BiFunction with the location and the original models as parameters, returning the new model. - (location, model) -> new MyBakedModelWrapper(model); + (location, model) -> new MyDelegateBakedModel(model); ); } ``` :::warning -It is generally encouraged to use a [custom model loader][modelloader] over wrapping baked models in `ModelEvent.ModifyBakingResult` when possible. Custom model loaders can also use `BakedModelWrapper`s if needed. +It is generally encouraged to use a [custom model loader][modelloader] over wrapping baked models in `ModelEvent.ModifyBakingResult` when possible. Custom model loaders can also use `DelegateBakedModel`s if needed. ::: [ao]: https://en.wikipedia.org/wiki/Ambient_occlusion [ber]: ../../../blockentities/ber.md -[bewlr]: ../../../blockentities/ber.md#blockentitywithoutlevelrenderer [blockstate]: ../../../blocks/states.md [event]: ../../../concepts/events.md -[bakedoverrides]: #bakedoverrides -[itemstack]: ../../../items/index.md#itemstacks +[itemmodels]: items.md#manually-rendering-an-item [modbus]: ../../../concepts/events.md#event-buses [modelloader]: modelloaders.md [mrl]: ../../../misc/resourcelocation.md#modelresourcelocations -[overrides]: index.md#overrides [perspective]: #perspectives [rendertype]: index.md#render-types -[rl]: ../../../misc/resourcelocation.md [sides]: ../../../concepts/sides.md diff --git a/docs/resources/client/models/datagen.md b/docs/resources/client/models/datagen.md index 2a11b8ac6..ab4f22f88 100644 --- a/docs/resources/client/models/datagen.md +++ b/docs/resources/client/models/datagen.md @@ -1,283 +1,331 @@ # Model Datagen -Like most JSON data, block and item models can be [datagenned][datagen]. Since some things are common between item and block models, so is some of the datagen code. +Like most JSON data, block and item models, along with their necessary blockstate files and [client items][citems], can be [datagenned][datagen]. This is all handled through the vanilla `ModelProvider`, with extensions provided by NeoForge via the `ExtendedModelTemplateBuilder`. Since the model JSON itself is similar between block and item models, the datagen code is relatively similar. -## Model Datagen Classes +## Model Templates -### `ModelBuilder` +Every model starts out as a `ModelTemplate`. For vanilla, the `ModelTemplate` acts as a parent to some pre-generated model file, defining the parent model, the required texture slots, and the file suffix to apply. For the NeoForge case, the `ExtendedModelTemplate` is constructed via an `ExtendedModelTemplateBuilder`, allowing the user to generate the model down to its base elements and faces, along with any NeoForge-added functionality. -Every model starts out as a `ModelBuilder` of some sort - usually a `BlockModelBuilder` or an `ItemModelBuilder`, depending on what you are generating. It contains all the properties of the model: its parent, its textures, its elements, its transforms, its loader, etc. Each of the properties can be set by a method: +A `ModelTemplate` is created by using one of the methods in `ModelTemplates` or calling the constructor. For the constructor, it takes in the optional `ResourceLocation` of the parent model relative to the `models` directory, an optional string to apply to the end of file path (e.g., for a pressed button, it is suffixed with `_pressed`), and a varargs of `TextureSlot`s that must be defined for the datagen not to crash. `TextureSlot`s are just a string that define the 'key' of a texture in the `textures` map. Each key can also have a parent `TextureSlot` that it will resolve to if no texture is specified for the specific slot. For example, `TextureSlot#PARTICLE` will first look for a defined `particle` texture, then check for a defined `texture` value, and finally checking `all`. If the slot or its parents are not defined, then a crash is thrown during data generation. + +```java +// Assumes there is a texture referenced as '#base' +// Can be resolved by either specifying 'base' or 'all' +public static final TextureSlot BASE = TextureSlot.create("base", TextureSlot.ALL); + +// Assume there exists some model 'examplemod:block/example_template' +public static final ModelTemplate EXAMPLE_TEMPLATE = new ModelTemplate( + // The parent model location + Optional.of( + ModelLocationUtils.decorateBlockModelLocation("examplemod:example_template") + ), + // The suffix to apply to the end of any model that uses this template + Optional.of("_example"), + // All texture slots that must be defined + // Should be as specific as possible based on what's undefined in the parent model + TextureSlot.PARTICLE, + BASE +); +``` + +The NeoForge-added `ExtendedModelTemplate` can be constructed via `ExtendedModelTemplateBuilder#builder` or `ModelTemplate#extend` for an existing vanilla template. The builder can then be resolved into the template using `#build`. The builder's methods provide full control over the construction of the model JSON: | Method | Effect | |--------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| `#texture(String key, ResourceLocation texture)` | Adds a texture variable with the given key and the given texture location. Has an overload where the second parameter is a `String`. | -| `#renderType(ResourceLocation renderType)` | Sets the render type. Has an overload where the parameter is a `String`. For a list of valid values, see the `RenderType` class. | -| `#ao(boolean ao)` | Sets whether to use [ambient occlusion][ao] or not. | -| `#guiLight(GuiLight light)` | Sets the GUI light. May be `GuiLight.FRONT` or `GuiLight.SIDE`. | -| `#element()` | Adds a new `ElementBuilder` (equivalent to adding a new [element][elements] to the model). Returns said `ElementBuilder` for further modification. | -| `#transforms()` | Returns the builder's `TransformVecBuilder`, used for setting the `display` on a model. | -| `#customLoader(BiFunction customLoaderFactory)` | Using the given factory, makes this model use a [custom loader][custommodelloader], and thus, a custom loader builder. This changes the builder type, and as such may use different methods, depending on the loader's implementation. NeoForge provides a few custom loaders out of the box, see the linked article for more info (including datagen). | +| `#parent(ResourceLocation parent)` | Sets the parent model location relative to the `models` directory. | +| `#suffix(String suffix)` | Appends the string to the end of the model file path. | +| `#requiredTextureSlot(TextureSlot slot)` | Adds a texture slot that must be defined within the `TextureMapping` for generation. | +| `#renderType(ResourceLocation renderType)` | Sets the render type. Has an overload where the parameter is a `String`. For a list of valid values, see the `RenderType` class. | +| `transform(ItemDisplayContext type, Consumer action)` | Adds a `TransformVecBuilder` that is configured via the consumer, used for setting the `display` on a model. | +| `#ambientOcclusion(boolean ambientOcclusion)` | Sets whether to use [ambient occlusion][ao] or not. | +| `#guiLight(UnbakedModel.GuiLight light)` | Sets the GUI light. May be `GuiLight.FRONT` or `GuiLight.SIDE`. | +| `#element(Consumer action)` | Adds a new `ElementBuilder` (equivalent to adding a new [element][elements] to the model) that is configured via the consumer. | | +| `#customLoader(Supplier customLoaderFactory, Consumer action)` | Using the given factory, makes this model use a [custom loader][custommodelloader], and thus, a custom loader builder that is configured via the consumer. This changes the builder type, and as such may use different methods, depending on the loader's implementation. NeoForge provides a few custom loaders out of the box, see the linked article for more info (including datagen). | +| `rootTransforms(Consumer action)` | Configures the transforms of the model to apply before item display and block state transformations via the consumer. | :::tip While elaborate and complex models can be created through datagen, it is recommended to instead use modeling software such as [Blockbench][blockbench] to create more complex models and then have the exported models be used, either directly or as parents for other models. ::: -### `ModelProvider` +### Creating the Model Instance -Both block and item model datagen utilize subclasses of `ModelProvider`, named `BlockModelProvider` and `ItemModelProvider`, respectively. While item model datagen directly extends `ItemModelProvider`, block model datagen uses the `BlockStateProvider` base class, which has an internal `BlockModelProvider` that can be accessed via `BlockStateProvider#models()`. Additionally, `BlockStateProvider` also has its own internal `ItemModelProvider`, accessible via `BlockStateProvider#itemModels()`. The most important part of `ModelProvider` is the `getBuilder(String path)` method, which returns a `BlockModelBuilder` (or `ItemModelBuilder`) at the given location. +Now that we have a `ModelTemplate`, we can generate the model itself by calling one of the `ModelTemplate#create*` methods. Although each create method takes in different parameters, at their core, they all take in the `ResourceLocation` representing the name of the file, a `TextureMapping` which maps a `TextureSlot` to some `ResourceLocation` relative to the `textures` directory, and the model output as a `BiConsumer`. Then, the method essentially creates the `JsonObject` used to generate the model, throwing an error if any duplicates are provided. + +:::note +Calling the base `create` method does not apply the stored suffix. Only `create*` methods that takes in the block or item do so. +::: + +```java +// Given some BiConsumer modelOutput +// Assume there is a DeferredBlock EXAMPLE_BLOCK +EXAMPLE_TEMPLATE.create( + // Creates the model at 'assets/minecraft/models/block/example_block_example.json' + EXAMPLE_BLOCK.get(), + // Define textures in slots + new TextureMapping() + // "particle": "examplemod:item/example_block" + .put(TextureSlot.PARTICLE, TextureMapping.getBlockTexture(EXAMPLE_BLOCK.get())) + // "base": "examplemod:item/example_block_base" + .put(TextureSlot.BASE, TextureMapping.getBlockTexture(EXAMPLE_BLOCK.get(), "_base")), + // The consumer of the generated model json + modelOutput +); +``` -However, `ModelProvider` also contains various helper methods. The most important helper method is probably `withExistingParent(String name, ResourceLocation parent)`, which returns a new builder (via `getBuilder(name)`) and sets the given `ResourceLocation` as model parent. Two other very common helpers are `mcLoc(String name)`, which returns a `ResourceLocation` with the namespace `minecraft` and the given name as path, and `modLoc(String name)`, which does the same but with the provider's mod id (so usually your mod id) instead of `minecraft`. Furthermore, it provides various helper methods that are shortcuts for `#withExistingParent` for common things such as slabs, stairs, fences, doors, etc. +Sometimes, generated models use similar model templates and naming patterns for their textures (e.g., the texture for a regular block is just the name of the block). In these cases, a `TextureModel.Provider` can be created to help remove any redundancy. The provider is effectively a functional interface that takes in some `Block` and returns a `TexturedModel` (a `ModelTemplate`/`TextureMapping` pair) to generate the model. The interface is constructed via `TexturedModel#createDefault`, which takes a function to map a `Block` to its `TextureMapping` along with the `ModelTemplate` to use. Then the model can be generated by calling `TexturedModel.Provider#create` with the `Block` to generate for. -### `ModelFile` +```java +public static final TexturedModel.Provider EXAMPLE_TEMPLATE_PROVIDER = TexturedModel.createDefault( + // Block to texture mapping + block -> new TextureMapping() + .put(TextureSlot.PARTICLE, TextureMapping.getBlockTexture(block)) + .put(TextureSlot.BASE, TextureMapping.getBlockTexture(block, "_base")), + // The template to generate from + EXAMPLE_TEMPLATE +); -Finally, the last important class is `ModelFile`. A `ModelFile` is an in-code representation of a model JSON on disk. `ModelFile` is an abstract class and has two inner subclasses `ExistingModelFile` and `UncheckedModelFile`. An `ExistingModelFile`'s existence is verified using an `ExistingFileHelper`, while an `UncheckedModelFile` is assumed to be existent without further checking. In addition, a `ModelBuilder` is considered to be a `ModelFile` as well. +// Given some BiConsumer modelOutput +// Assume there is a DeferredBlock EXAMPLE_BLOCK +EXAMPLE_TEMPLATE_PROVIDER.create( + // Creates the model at 'assets/minecraft/models/block/example_block_example.json' + EXAMPLE_BLOCK.get(), + // The consumer of the generated model json + modelOutput +) +``` -## Block Model Datagen +## `ModelProvider` -Now, to actually generate blockstate and block model files, extend `BlockStateProvider` and override the `registerStatesAndModels()` method. Note that block models will always be placed in the `models/block` subfolder, but references are relative to `models` (i.e. they must always be prefixed with `block/`). In most cases, it makes sense to choose from one of the many predefined helper methods: +Both block and item model datagen utilize generators provided by `registerModels`, named `BlockModelGenerators` and `ItemModelGenerators`, respectively. Each generator generates both the model JSON along with any additional required files (blockstate, client items). Each generator contains various helper methods which batches the construction of all the files into a single, easy-to-use method, such as `ItemModelGenerators#generateFlatItem` to create a basic `item/generated` model or `BlockModelGenerators#createTrivialCube` for a basic `block/cube_all` model. ```java -public class MyBlockStateProvider extends BlockStateProvider { - // Parameter values are provided by GatherDataEvent. - public MyBlockStateProvider(PackOutput output, ExistingFileHelper existingFileHelper) { +public class ExampleModelProvider extends ModelProvider { + + public ExampleModelProvider(PackOutput output) { // Replace "examplemod" with your own mod id. - super(output, "examplemod", existingFileHelper); + super(output, "examplemod"); } - - @Override - protected void registerStatesAndModels() { - // Placeholders, their usages should be replaced with real values. See above for how to use the model builder, - // and below for the helpers the model builder offers. - ModelFile exampleModel = this.models().withExistingParent("example_model", this.mcLoc("block/cobblestone")); - Block block = MyBlocksClass.EXAMPLE_BLOCK.get(); - ResourceLocation exampleTexture = modLoc("block/example_texture"); - ResourceLocation bottomTexture = modLoc("block/example_texture_bottom"); - ResourceLocation topTexture = modLoc("block/example_texture_top"); - ResourceLocation sideTexture = modLoc("block/example_texture_front"); - ResourceLocation frontTexture = modLoc("block/example_texture_front"); - // Create a simple block model with the same texture on each side. - // The texture must be located at assets//textures/block/.png, where - // and are the block's registry name's namespace and path, respectively. - // Used by the majority of (full) blocks, such as planks, cobblestone or bricks. - this.simpleBlock(block); - // Overload that accepts a model file to use. - this.simpleBlock(block, exampleModel); - // Overload that accepts one or multiple (vararg) ConfiguredModel objects. - // See below for more info about ConfiguredModel. - this.simpleBlock(block, ConfiguredModel.builder().build()); - // Adds an item model file with the block's name, parenting the given model file, for a block item to pick up. - this.simpleBlockItem(block, exampleModel); - // Shorthand for calling #simpleBlock() (model file overload) and #simpleBlockItem. - this.simpleBlockWithItem(block, exampleModel); - - // Adds a log block model. Requires two textures at assets//textures/block/.png and - // assets//textures/block/_top.png, referencing the side and top texture, respectively. - // Note that the block input here is limited to RotatedPillarBlock, which is the class vanilla logs use. - this.logBlock(block); - // Like #logBlock, but the textures are named _side.png and _end.png instead of - // .png and _top.png, respectively. Used by quartz pillars and similar blocks. - // Has an overload that allow you to specify a different texture base name, that is then suffixed - // with _side and _end as needed, an overload that allows you to specify two resource locations - // for the side and end textures, and an overload that allows specifying side and end model files. - this.axisBlock(block); - // Variants of #logBlock and #axisBlock that additionally allow for render types to be specified. - // Comes in string and resource location variants for the render type, - // in all combinations with all variants of #logBlock and #axisBlock. - this.logBlockWithRenderType(block, "minecraft:cutout"); - this.axisBlockWithRenderType(block, mcLoc("cutout_mipped")); - - // Specifies a horizontally-rotatable block model with a side texture, a front texture, and a top texture. - // The bottom will use the side texture as well. If you don't need the front or top texture, - // just pass in the side texture twice. Used by e.g. furnaces and similar blocks. - this.horizontalBlock(block, sideTexture, frontTexture, topTexture); - // Specifies a horizontally-rotatable block model with a model file that will be rotated as needed. - // Has an overload that instead of a model file accepts a Function, - // allowing for different rotations to use different models. Used e.g. by the stonecutter. - this.horizontalBlock(block, exampleModel); - // Specifies a horizontally-rotatable block model that is attached to a face, e.g. for buttons or levers. - // Accounts for placing the block on the ground and on the ceiling, and rotates them accordingly. - // Like #horizontalBlock, has an overload that accepts a Function instead. - this.horizontalFaceBlock(block, exampleModel); - // Similar to #horizontalBlock, but for blocks that are rotatable in all directions, including up and down. - // Again, has an overload that accepts a Function instead. - this.directionalBlock(block, exampleModel); + @Override + protected void registerModels(BlockModelGenerators blockModels, ItemModelGenerators itemModels) { + // Generate models and associated files here } } ``` -Additionally, helpers for the following common block models exist in `BlockStateProvider`: - -- Stairs -- Slabs -- Buttons -- Pressure Plates -- Signs -- Fences -- Fence Gates -- Walls -- Panes -- Doors -- Trapdoors - -In some cases, the blockstates don't need special casing, but the models do. For this case, the `BlockModelProvider`, accessible via `BlockStateProvider#models()`, provides a few additional helpers, all of which accept a name as the first parameter and most of which are in some way related to full cubes. They will typically be used as model file parameters for e.g. `simpleBlock`. The helpers include supporting methods for the ones in `BlockStateProvider`, as well as: - -- `withExistingParent`: Already mentioned before, this method returns a new model builder with the given parent. The parent must either already exist or be created before the model. -- `getExistingFile`: Performs a lookup in the model provider's `ExistingFileHelper`, returning the corresponding `ModelFile` if present and throwing an `IllegalStateException` otherwise. -- `singleTexture`: Accepts a parent and a single texture location, returning a model with the given parent, and with the texture variable `texture` set to the given texture location. -- `sideBottomTop`: Accepts a parent and three texture locations, returning a model with the given parent and the side, bottom and top textures set to the three texture locations. -- `cube`: Accepts six texture resource locations for the six sides, returning a full cube model with the six sides set to the six textures. -- `cubeAll`: Accepts a texture location, returning a full cube model with the given texture applied to all six sides. A mix between `singleTexture` and `cube`, if you will. -- `cubeTop`: Accepts two texture locations, returning a full cube model with the first texture applied to the sides and the bottom, and the second texture applied to the top. -- `cubeBottomTop`: Accepts three texture locations, returning a full cube model with the side, bottom and top textures set to the three texture locations. A mix between `cube` and `sideBottomTop`, if you will. -- `cubeColumn` and `cubeColumnHorizontal`: Accepts two texture locations, returning a "standing" or "laying" pillar cube model with the side and end textures set to the two texture locations. Used by `BlockStateProvider#logBlock`, `BlockStateProvider#axisBlock` and their variants. -- `orientable`: Accepts three texture locations, returning a cube with a "front" texture. The three texture locations are the side, front and top texture, respectively. -- `orientableVertical`: Variant of `orientable` that omits the top parameter, instead using the side parameter as well. -- `orientableWithBottom`: Variant of `orientable` that has a fourth parameter for a bottom texture between the front and top parameter. -- `crop`: Accepts a texture location, returning a crop-like model with the given texture, as used by the four vanilla crops. -- `cross`: Accepts a texture location, returning a cross model with the given texture, as used by flowers, saplings and many other foliage blocks. -- `torch`: Accepts a texture location, returning a torch model with the given texture. -- `wall_torch`: Accepts a texture location, returning a wall torch model with the given texture (wall torches are separate blocks from standing torches). -- `carpet`: Accepts a texture location, returning a carpet model with the given texture. - -Finally, don't forget to register your block state provider to the event: +And like all data providers, don't forget to register your provider to the event: ```java @SubscribeEvent public static void gatherData(GatherDataEvent event) { DataGenerator generator = event.getGenerator(); PackOutput output = generator.getPackOutput(); - ExistingFileHelper existingFileHelper = event.getExistingFileHelper(); // other providers here generator.addProvider( event.includeClient(), - new MyBlockStateProvider(output, existingFileHelper) + new ExampleModelProvider(output) ); } ``` -### `ConfiguredModel.Builder` +### Block Model Datagen + +Now, to actually generate blockstate and block model files, you can either call one of the many public methods in `BlockModelGenerators` within `ModelProvider#registerModels`, or pass in the generated files yourself to the `blockStateOutput` for blockstate files, `itemModelOutput` for non-trivial client items, and `modelOutput` for the model JSONs. -If the default helpers won't do it for you, you can also directly build model objects using a `ConfiguredModel.Builder` and then use them in a `VariantBlockStateBuilder` to build a `variants` blockstate file, or in a `MultiPartBlockStateBuilder` to build a `multipart` blockstate file: +:::note +If you have an associated `BlockItem` registered for your block with no generated client item, the `ModelProvider` will automatically generate a client item using the default block model location `assets//models/block/.json` as its model. +::: ```java -// Create a ConfiguredModel.Builder. Alternatively, you can use one of the ways demonstrated below -// (VariantBlockStateBuilder.PartialBlockstate#modelForState or MultiPartBlockStateBuilder#part) where applicable. -ConfiguredModel.Builder builder = ConfiguredModel.builder() -// Use a model file. As mentioned previously, can either be an ExistingModelFile, an UncheckedModelFile, -// or some sort of ModelBuilder. See above for how to use ModelBuilder. - .modelFile(this.models().withExistingParent("example_model", this.mcLoc("block/cobblestone"))) - // Set rotations around the x and y axes. - .rotationX(90) - .rotationY(180) - // Set a uvlock. - .uvlock(true) - // Set a weight. - .weight(5); -// Build the configured model. The return type is an array -// to account for multiple possible models in the same blockstate. -ConfiguredModel[] model = builder.build(); - -// Get a variant block state builder. -VariantBlockStateBuilder variantBuilder = this.getVariantBuilder(MyBlocksClass.EXAMPLE_BLOCK.get()); -// Create a partial state and set properties on it. -VariantBlockStateBuilder.PartialBlockstate partialState = variantBuilder.partialState(); -// Add one or multiple models for a partial blockstate. The models are a vararg parameter. -variantBuilder.addModels(partialState, - // Specify at least one ConfiguredModel.Builder, as seen above. Create through #modelForState(). - partialState.modelForState() - .modelFile(this.models().withExistingParent("example_variant_model", this.mcLoc("block/cobblestone"))) - .uvlock(true) -); -// Alternatively, forAllStates(Function) creates a model for every state. -// The passed function will be called once for each possible state. -variantBuilder.forAllStates(state -> { - // Return a ConfiguredModel depending on the state's properties. - // For example, the following code will rotate the model depending on the horizontal rotation of the block. - return ConfiguredModel.builder() - .modelFile(this.models().withExistingParent("example_variant_model", this.mcLoc("block/cobblestone"))) - .rotationY((int) state.getValue(BlockStateProperties.HORIZONTAL_FACING).toYRot()) - .build(); -}); - -// Get a multipart block state builder. -MultiPartBlockStateBuilder multipartBuilder = this.getMultipartBuilder(MyBlocksClass.EXAMPLE_BLOCK.get()); -// Add a new part. Starts with .part() and ends with .end(). -multipartBuilder.part() - // Step one: Build the model. multipartBuilder.part() returns a ConfiguredModel.Builder, - // meaning that all methods seen above can be used here as well. - .modelFile(this.models().withExistingParent("example_multipart_model", this.mcLoc("block/cobblestone"))) - // Call .addModel(). Now that the model is built, we can proceed to step two: add the part data. - .addModel() - // Add a condition for the part. Requires a property - // and at least one property value; property values are a vararg. - .condition(BlockStateProperties.FACING, Direction.NORTH, Direction.SOUTH) - // Set the multipart conditions to be ORed instead of the default ANDing. - .useOr() - // Creates a nested condition group. - .nestedGroup() - // Adds a condition to the nested group. - .condition(BlockStateProperties.FACING, Direction.NORTH) - // Sets only this condition group to be ORed instead of ANDed. - .useOr() - // Creates yet another nested condition group. There is no limit on how many groups can be nested. - .nestedGroup() - // Ends the nested condition group, returning to the owning part builder or condition group level. - // Called twice here since we currently have two nested groups. - .endNestedGroup() - .endNestedGroup() - // End the part builder and add the resulting part to the multipart builder. - .end(); -``` +public class ExampleModelProvider extends ModelProvider { -## Item Model Datagen + public ExampleModelProvider(PackOutput output) { + // Replace "examplemod" with your own mod id. + super(output, "examplemod"); + } -Generating item models is considerably simpler, which is mainly due to the fact that we operate directly on an `ItemModelProvider` instead of using an intermediate class like `BlockStateProvider`, which is of course because item models don't have an equivalent to blockstate files and are instead used directly. + @Override + protected void registerModels(BlockModelGenerators blockModels, ItemModelGenerators itemModels) { + // Placeholders, their usages should be replaced with real values. See above for how to use the model builder, + // and below for the helpers the model builder offers. + Block block = MyBlocksClass.EXAMPLE_BLOCK.get(); -Similar to above, we create a class and have it extend the base provider, in this case `ItemModelProvider`. Since we are directly in a subclass of `ModelProvider`, all `models()` calls become `this` (or are omitted). + // Create a simple block model with the same texture on each side. + // The texture must be located at assets//textures/block/.png, where + // and are the block's registry name's namespace and path, respectively. + // Used by the majority of (full) blocks, such as planks, cobblestone or bricks. + blockModels.createTrivialCube(block); -```java -public class MyItemModelProvider extends ItemModelProvider { - public MyItemModelProvider(PackOutput output, ExistingFileHelper existingFileHelper) { - super(output, "examplemod", existingFileHelper); - } - - // Assume that EXAMPLE_BLOCK_ITEM and EXAMPLE_ITEM are both DeferredItems - @Override - protected void registerModels() { - // Block items generally use their corresponding block models as parent. - this.withExistingParent(MyItemsClass.EXAMPLE_BLOCK_ITEM.getId().toString(), modLoc("block/example_block")); - // Items generally use a simple parent and one texture. The most common parents are item/generated and item/handheld. - // In this example, the item texture would be located at assets/examplemod/textures/item/example_item.png. - // If you want a more complex model, you can use getBuilder() and then work from that, like you would with block models. - this.withExistingParent(MyItemsClass.EXAMPLE_ITEM.getId().toString(), mcLoc("item/generated")).texture("layer0", "item/example_item"); - // The above line is so common that there is a shortcut for it. Note that the item registry name and the - // texture path, relative to textures/item, must match. - this.basicItem(MyItemsClass.EXAMPLE_ITEM.get()); + // Overload that accepts a `TexturedModel.Provider` to use. + blockModels.createTrivialBlock(block, EXAMPLE_TEMPLATE_PROVIDER); + + // Block items have a model generated automatically + // But let's assume you want to generate a different item, such as a flat item + blockModels.registerSimpleFlatItemModel(block); + + // Adds a log block model. Requires two textures at assets//textures/block/.png and + // assets//textures/block/_top.png, referencing the side and top texture, respectively. + // Note that the block input here is limited to RotatedPillarBlock, which is the class vanilla logs use. + blockModels.woodProvider(block).log(block); + + // Like WoodProvider#logWithHorizontal. Used by quartz pillars and similar blocks. + blockModels.createRotatedPillarWithHorizontalVariant(block, TexturedModel.COLUMN_ALT, TexturedModel.COLUMN_HORIZONTAL_ALT); + + // Using the `ExtendedModelTemplate` to specify the render type to use. + blockModels.createRotatedPillarWithHorizontalVariant(block, + TexturedModel.COLUMN_ALT.updateTemplate(template -> + template.extend().renderType("minecraft:cutout").build() + ), + TexturedModel.COLUMN_HORIZONTAL_ALT.updateTemplate(template -> + template.extend().renderType(this.mcLocation("cutout_mipped")).build() + ) + ); + + // Specifies a horizontally-rotatable block model with a side texture, a front texture, and a top texture. + // The bottom will use the side texture as well. If you don't need the front or top texture, + // just pass in the side texture twice. Used by e.g. furnaces and similar blocks. + blockModels.createHorizontallyRotatedBlock( + block, + TexturedModel.Provider.ORIENTABLE_ONLY_TOP.updateTexture(mapping -> + mapping.put(TextureSlot.SIDE, this.modLocation("block/example_texture_side")) + .put(TextureSlot.FRONT, this.modLocation("block/example_texture_front")) + .put(TextureSlot.TOP, this.modLocation("block/example_texture_top")) + ) + ); + + // Specifies a horizontally-rotatable block model that is attached to a face, e.g. for buttons. + // Accounts for placing the block on the ground and on the ceiling, and rotates them accordingly. + blockModels.familyWithExistingFullBlock(block).button(block); + + // Create a model to use for blockstatefiles + ResourceLocation modelLoc = TexturedModel.CUBE.create(block, blockModels.modelOutput); + + // Basic single variant model + blockModels.blockStateOutput.accept( + MultiVariantGenerator.multiVariant( + block, + Variant.variant() + // Set and generate model + .with(VariantProperties.MODEL, modelLoc) + // Set rotations around the x and y axes + .with(VariantProperties.X_ROT, VariantProperties.Rotation.R90) + .with(VariantProperties.Y_ROT, VariantProperties.Rotation.R180) + // Set a uvlock + .with(VariantProperties.UV_LOCK, true) + // Set a weight + .with(VariantProperties.WEIGHT, 5) + ) + ); + + // Add one or multiple models based on the block state properties + blockModels.blockStateOutput.accept( + MultiVariantGenerator.multiVariant(block) + .with( + // Or properties for selecting on multiple properties + PropertyDispatch.property(BlockStateProperties.AXIS) + // Select the property and apply the models + .select(Direction.Axis.Y, Variant.variant().with(VariantProperties.MODEL, modelLoc)) + .select( + Direction.Axis.Z, + Variant.variant().with(VariantProperties.MODEL, modelLoc) + .with(VariantProperties.X_ROT, VariantProperties.Rotation.R90) + ) + .select( + Direction.Axis.X, + Variant.variant() + .with(VariantProperties.MODEL, modelLoc) + .with(VariantProperties.X_ROT, VariantProperties.Rotation.R90) + .with(VariantProperties.Y_ROT, VariantProperties.Rotation.R90) + ) + ) + ); + + // Modify simple model settings with property dispatch + // Example rotates model depending on the horizontal rotation of the block + blockModels.blockStateOutput.accept( + MultiVariantGenerator.multiVariant( + block, + Variant.variant().with(VariantProperties.MODEL, modelLoc) + .with(BlockModelGenerators.createHorizontalFacingDispatch()) + ) + ); + + // Generate a multipart + blockModels.blockStateOutput.accept( + MultiPartGenerator.multiPart(block) + // Provide the base model + .with(Variant.variant().with(VariantProperties.MODEL, modelLoc)) + // Add conditions for variant to appear + .with( + // Add conditions to apply + Condition.or( + // Where at least one of the conditions are true + Condition.condition().term(BlockStateProperties.FACING, Direction.NORTH, Direction.SOUTH) + // Can nest as many conditions or groups as necessary + Condition.and( + Condition.condition().term(BlockStateProperties.FACING, Direction.NORTH) + ) + ), + // Supply variant to generate + Variant.variant().with(VariantProperties.MODEL, modelLoc) + ) + ); } } ``` -And like all data providers, don't forget to register your provider to the event: +## Item Model Datagen + +Generating item models is considerably simpler, which is mainly due to all of the helper methods for within `ItemModelGenerators` and `ItemModelUtils` for property information. Similar to above, you can either call one of the many public methods in `ItemModelGenerators` within `ModelProvider#registerModels`, or pass in the generated files yourself to the `itemModelOutput` for non-trivial client items and `modelOutput` for the model JSONs. ```java -@SubscribeEvent -public static void gatherData(GatherDataEvent event) { - DataGenerator generator = event.getGenerator(); - PackOutput output = generator.getPackOutput(); - ExistingFileHelper existingFileHelper = event.getExistingFileHelper(); +public class ExampleModelProvider extends ModelProvider { - // other providers here - generator.addProvider( - event.includeClient(), - new MyItemModelProvider(output, existingFileHelper) - ); + public ExampleModelProvider(PackOutput output) { + // Replace "examplemod" with your own mod id. + super(output, "examplemod"); + } + + @Override + protected void registerModels(BlockModelGenerators blockModels, ItemModelGenerators itemModels) { + // The most common item + // item/generated with the layer0 texture as the item name + itemModels.generateFlatItem(MyItemsClass.EXAMPLE_ITEM.get(), ModelTemplates.FLAT_ITEM); + + // A bow-like item + ItemModel.Unbaked bow = ItemModelUtils.plainModel(ModelLocationUtils.getModelLocation(MyItemsClass.EXAMPLE_ITEM.get())); + ItemModel.Unbaked pullingBow0 = ItemModelUtils.plainModel(this.createFlatItemModel(MyItemsClass.EXAMPLE_ITEM.get(), "_pulling_0", ModelTemplates.BOW)); + ItemModel.Unbaked pullingBow1 = ItemModelUtils.plainModel(this.createFlatItemModel(MyItemsClass.EXAMPLE_ITEM.get(), "_pulling_1", ModelTemplates.BOW)); + ItemModel.Unbaked pullingBow2 = ItemModelUtils.plainModel(this.createFlatItemModel(MyItemsClass.EXAMPLE_ITEM.get(), "_pulling_2", ModelTemplates.BOW)); + this.itemModelOutput.accept( + MyItemsClass.EXAMPLE_ITEM.get(), + // Conditional model for item + ItemModelUtils.conditional( + // Checks if item is being used + ItemModelUtils.isUsingItem(), + // When true, select model based on use duration + ItemModelUtils.rangeSelect( + new UseDuration(false), + // Scalar to apply to the thresholds + 0.05F, + pullingBow0, + // Threshold when 0.65 + ItemModelUtils.override(pullingBow1, 0.65F), + // Threshold when 0.9 + ItemModelUtils.override(pullingBow2, 0.9F) + ), + // When false, use the base bow model + bow + ) + ); + } } ``` [ao]: https://en.wikipedia.org/wiki/Ambient_occlusion [blockbench]: https://www.blockbench.net +[citems]: items.md [custommodelloader]: modelloaders.md#datagen [datagen]: ../../index.md#data-generation [elements]: index.md#elements diff --git a/docs/resources/client/models/index.md b/docs/resources/client/models/index.md index c9f9b388c..4f24b0f1e 100644 --- a/docs/resources/client/models/index.md +++ b/docs/resources/client/models/index.md @@ -2,8 +2,8 @@ Models are JSON files that determine the visual shape and texture(s) of a block or item. A model consists of cuboid elements, each with their own size, that then get assigned a texture to each face. -Each item gets an item model assigned to it by its registry name. For example, an item with the registry name `examplemod:example_item` would get the model at `assets/examplemod/models/item/example_item.json` assigned to it. For blocks, this is a bit more complicated, as they get assigned a blockstate file first. See [below][bsfile] for more information. - +Items use the associated model(s) defined by its [client item][citems] while blocks use the associated model in the [blockstate file][bsfile]. These locations are relative to the `models` directory, so a model referenced by the name `examplemod:item/example_model` would be defined by the JSON at `assets/examplemod/models/item/example_model.json`. + ## Specification _See also: [Model][mcwikimodel] on the [Minecraft Wiki][mcwiki]_ @@ -20,16 +20,14 @@ A model is a JSON file with the following optional properties in the root tag: - `minecraft:block/cross`: Model that uses two planes with the same texture, one rotated 45° clockwise and the other rotated 45° counter-clockwise, forming an X when viewed from above (hence the name). Examples include most plants, e.g. grass, saplings and flowers. - `minecraft:item/generated`: Parent for classic 2D flat item models. Used by most items in the game. Ignores an `elements` block since its quads are generated from the textures. - `minecraft:item/handheld`: Parent for 2D flat item models that appear to be actually held by the player. Used predominantly by tools. Submodel of `item/generated`, which causes it to ignore the `elements` block as well. - - `minecraft:builtin/entity`: Specifies no textures other than `particle`. If this is the parent, [`BakedModel#isCustomRenderer()`][iscustomrenderer] returns `true` to allow use of a [`BlockEntityWithoutLevelRenderer`][bewlr]. - - Block items commonly (but not always) use their corresponding block models as parent. For example, the cobblestone item model uses the parent `minecraft:block/cobblestone`. + - Block items commonly (but not always) use their corresponding block models for their [item model][itemmodels]. For example, the cobblestone client item uses the `minecraft:block/cobblestone` model. - `ambientocclusion`: Whether to enable [ambient occlusion][ao] or not. Only effective on block models. Defaults to `true`. If your custom block model has weird shading, try setting this to `false`. -- `render_type`: See [Render Types][rendertype]. +- `render_type`: NeoForge-added. Sets the render type to use. See [Render Types][rendertype] for more information. - `gui_light`: Can be `"front"` or `"side"`. If `"front"`, light will come from the front, useful for flat 2D models. If `"side"`, light will come from the side, useful for 3D models (especially block models). Defaults to `"side"`. Only effective on item models. - `textures`: A sub-object that maps names (known as texture variables) to [texture locations][textures]. Texture variables can then be used in [elements]. They can also be specified in elements, but left unspecified in order for child models to specify them. - Block models should additionally specify a `particle` texture. This texture is used when falling on, running across, or breaking the block. - Item models can also use layer textures, named `layer0`, `layer1`, etc., where layers with a higher index are rendered above those with a lower index (e.g. `layer1` would be rendered above `layer0`). Only works if the parent is `item/generated`, and only works for up to 5 layers (`layer0` through `layer4`). - `elements`: A list of cuboid [elements]. -- `overrides`: A list of [override models][overrides]. Only effective on item models. - `display`: A sub-object that holds the different display options for different [perspectives], see linked article for possible keys. Only effective on item models, but often specified in block models so that item models can inherit the display options. Every perspective is an optional sub-object that may contain the following options, which are applied in that order: - `translation`: The translation of the model, specified as `[x, y, z]`. - `rotation`: The rotation of the model, specified as `[x, y, z]`. @@ -51,15 +49,15 @@ Using the optional NeoForge-added `render_type` field, you can set a render type - `minecraft:cutout_mipped_all`: Variant of `minecraft:cutout_mipped` which applies mipmapping to item models as well. - `minecraft:translucent`: Used for blocks where any pixel may be partially transparent, for example stained glass. - `minecraft:tripwire`: Used by blocks with the special requirement of being rendered to the weather target, i.e. tripwire. +- `neoforge:item_unlit`: NeoForge-added. Should be used by blocks that, when rendered from an item, do not take the light directions into account. Selecting the correct render type is a question of performance to some degree. Solid rendering is faster than cutout rendering, and cutout rendering is faster than translucent rendering. Because of this, you should specify the "strictest" render type applicable for your use case, as it will also be the fastest. -If you want, you can also add your own render types. To do so, subscribe to the [mod bus][modbus] [event] `RegisterNamedRenderTypesEvent` and `#register` your render types. `#register` has three or four parameters: +If you want, you can also add your own render types. To do so, subscribe to the [mod bus][modbus] [event] `RegisterNamedRenderTypesEvent` and `#register` your render types. `#register` has three parameters: -- The name of the render type. Will be prefixed with your mod id. For example, using `"my_cutout"` here will provide `examplemod:my_cutout` as a new render type for you to use (provided that your mod id is `examplemod`, of course). +- The name of the render type. Should be a `ResourceLocation` prefixed with your mod id. - The chunk render type. Any of the types in the list returned by `RenderType.chunkBufferLayers()` can be used. - The entity render type. Must be a render type with the `DefaultVertexFormat.NEW_ENTITY` vertex format. -- Optional: The fabulous render type. Must be a render type with the `DefaultVertexFormat.NEW_ENTITY` vertex format. Will be used instead of the regular entity render type if the graphics mode is set to _Fabulous!_. If omitted, falls back to the regular render type. Generally recommended to set if the render type uses transparency in some way. ### Elements @@ -98,88 +96,6 @@ Extra face data (`neoforge_data`) can be applied to both an element and a single - `sky_light`: Overrides the sky light value used for this face. Defaults to 0. - `ambient_occlusion`: Disables or enables ambient occlusion for this face. Defaults to the value set in the model. -Using the custom `neoforge:item_layers` loader, you can also specify extra face data to apply to all the geometry in an `item/generated` model. In the following example, layer 1 will be tinted red and glow at full brightness: - -```json5 -{ - "loader": "neoforge:item_layers", - "parent": "minecraft:item/generated", - "textures": { - "layer0": "minecraft:item/stick", - "layer1": "minecraft:item/glowstone_dust" - }, - "neoforge_data": { - "1": { - "color": "0xFFFF0000", - "block_light": 15, - "sky_light": 15, - "ambient_occlusion": false - } - } -} -``` - -### Overrides - -Item overrides can assign a different model to an item based on a float value, called the override value. For example, bows and crossbows use this to change the texture depending on how long they have been drawn. Overrides have both a model and a code side to them. - -The model can specify one or multiple override models that should be used when the override value is equal to or greater than the given threshold value. For example, the bow uses two different properties `pulling` and `pull`. `pulling` is treated as a boolean value, with 1 being interpreted as pulling and 0 as not pulling, while `pull` represents how much the bow is currently pulled. It then uses these properties to specify usage of three alternative models when charged to below 65% (`pulling` 1, no `pull` value), 65% (`pulling` 1, `pull` 0.65) and 90% (`pulling` 1, `pull` 0.9). If multiple models apply (because the value keeps on becoming bigger), the last element of the list matches, so make sure your order is correct. The overrides look as follows: - -```json5 -{ - // other stuff here - "overrides": [ - { - // pulling = 1 - "predicate": { - "pulling": 1 - }, - "model": "item/bow_pulling_0" - }, - { - // pulling = 1, pull >= 0.65 - "predicate": { - "pulling": 1, - "pull": 0.65 - }, - "model": "item/bow_pulling_1" - }, - // pulling = 1, pull >= 0.9 - { - "predicate": { - "pulling": 1, - "pull": 0.9 - }, - "model": "item/bow_pulling_2" - } - ] -} -``` - -The code side of things is pretty simple. Assuming that we want to add a property named `examplemod:property` to our item, we would use the following code in a [client-side][side] [event handler][eventhandler]: - -```java -@SubscribeEvent -public static void onClientSetup(FMLClientSetupEvent event) { - event.enqueueWork(() -> { // ItemProperties#register is not threadsafe, so we need to call it on the main thread - ItemProperties.register( - // The item to apply the property to. - ExampleItems.EXAMPLE_ITEM, - // The id of the property. - ResourceLocation.fromNamespaceAndPath("examplemod", "property"), - // A reference to a method that calculates the override value. - // Parameters are the used item stack, the level context, the player using the item, - // and a random seed you can use. - (stack, level, player, seed) -> someMethodThatReturnsAFloat() - ); - }); -} -``` - -:::info -Vanilla Minecraft only allows for float values between 0 and 1. NeoForge patches this to allow arbitrary float values. -::: - ### Root Transforms Adding the `transform` property at the top level of a model tells the loader that a transformation to all geometry should be applied right before the rotations in a [blockstate file][bsfile] (for block models) or the transformations in a `display` block (for item models) are applied. This is added by NeoForge. @@ -230,9 +146,13 @@ In contrast, inside a `multipart` block, elements are combined depending on the - The `when` block specifies either a string representation of a blockstate or a list of properties that must be met for the element to apply. The lists can either be named `"OR"` or `"AND"`, performing the respective logical operation on its contents. Both single blockstate and list values can additionally specify multiple actual values by separating them with `|` (for example `facing=east|facing=west`). - The `apply` block specifies the model object or an array of model objects to use. This works exactly like with a `variants` block. +## Client Items + +[Client items][citems] are used by the game to assign a model or multiple models to the states of the `ItemStack`. While there are some item-specific fields in the model JSON, client items consume models to render based on context, so most of their information has been moved to their own separate [section][citems]. + ## Tinting -Some blocks, such as grass or leaves, change their texture color based on their location and/or properties. [Model elements][elements] can specify a tint index on their faces, which will allow a color handler to handle the respective faces. The code side of things works through two events, one for block color handlers and one for item color handlers. They both work pretty similar, so let's have a look at a block handler first: +Some blocks, such as grass or leaves, change their texture color based on their location and/or properties. [Model elements][elements] can specify a tint index on their faces, which will allow a color handler to handle the respective faces. The code side of things works through three events, one for block color handlers, one for block tints based on biome (used in conjunction with the block color handlers), and one for item tint sources. They work pretty similar, so let's have a look at a block handler first: ```java // Client-side mod bus event handler @@ -251,24 +171,22 @@ public static void registerBlockColorHandlers(RegisterColorHandlersEvent.Block e } ``` -Item handlers work pretty much the same, except for some naming and the lambda parameters: +Here is an example for a color resolver: ```java // Client-side mod bus event handler @SubscribeEvent -public static void registerItemColorHandlers(RegisterColorHandlersEvent.Item event) { - // Parameters are the item stack and the tint index. - event.register((stack, tintIndex) -> { - // Like above, replace with your own calculation. Vanilla values are in the ItemColors class. - // Also like above, tint index -1 means no tint and should use a default value instead. +public static void registerColorResolvers(RegisterColorHandlersEvent.ColorResolvers event) { + // Parameters are the current biome, the block's X position, and the block's Z position. + event.register((biome, x, z) -> { + // Replace with your own calculation. See the BiomeColors class for vanilla references. + // Colors are in ARGB format. return 0xFFFFFFFF; - }, - // A varargs of items to apply the tinting to - EXAMPLE_ITEM.get(), ...); + }); } ``` -Be aware that the `item/generated` model specifies tint indices for its various layers - `layer0` has tint index 0, `layer1` has tint index 1, etc. Also, remember that block items are items, not blocks, and require an item color handler to be colored. +For item tinting, please see the [relevant section in the client items article][itemtints]. ## Registering Additional Models @@ -278,49 +196,29 @@ Models that are not associated with a block or item in some way, but are still r // Client-side mod bus event handler @SubscribeEvent public static void registerAdditional(ModelEvent.RegisterAdditional event) { - event.register(new ModelResourceLocation( - // The id of the model - ResourceLocation.fromNamespaceAndPath("examplemod", "block/example_unused_model"), - // The string representing what variant of the model this is for - // In normal vanilla, this would be one of three values: - // - Blocks: The stringified block state - // - Items: 'inventory' as it is the inventory model - // - Standalone: 'standalone' as this does not refer to any other model - "variant_type=true" - )); - - // An inventory model example - event.register(ModelResourceLocation.inventory( - ResourceLocation.fromNamespaceAndPath("examplemod", "item/example_unused_inventory_model") - )); - - // A standalone model example - event.register(ModelResourceLocation.standalone( - ResourceLocation.fromNamespaceAndPath("examplemod", "block/example_unused_standalone_model") - )); + // The model id, relative to `assets//models/.json` + event.register(ResourceLocation.fromNamespaceAndPath("examplemod", "block/example_unused_model")); } ``` [ao]: https://en.wikipedia.org/wiki/Ambient_occlusion [ber]: ../../../blockentities/ber.md -[bewlr]: ../../../blockentities/ber.md#blockentitywithoutlevelrenderer [bsfile]: #blockstate-files [custommodelloader]: modelloaders.md [elements]: #elements [event]: ../../../concepts/events.md -[eventhandler]: ../../../concepts/events.md#registering-an-event-handler [extrafacedata]: #extra-face-data -[iscustomrenderer]: bakedmodel.md#others +[citems]: items.md +[itemmodel]: items.md#a-basic-model +[itemtints]: items.md#tinting [mcwiki]: https://minecraft.wiki [mcwikiblockstate]: https://minecraft.wiki/w/Tutorials/Models#Block_states [mcwikimodel]: https://minecraft.wiki/w/Model [mipmapping]: https://en.wikipedia.org/wiki/Mipmap [modbus]: ../../../concepts/events.md#event-buses -[overrides]: #overrides [perspectives]: bakedmodel.md#perspectives [rendertype]: #render-types [roottransforms]: #root-transforms [rl]: ../../../misc/resourcelocation.md -[side]: ../../../concepts/sides.md [textures]: ../textures.md [tinting]: #tinting diff --git a/docs/resources/client/models/items.md b/docs/resources/client/models/items.md new file mode 100644 index 000000000..4afbda124 --- /dev/null +++ b/docs/resources/client/models/items.md @@ -0,0 +1,1383 @@ +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +# Client Items + +Client Items are the in-code representation of how an `ItemStack` should be rendered within the game, specifying what models to use given what state. The client items are located within the `items` subdirectory within the [`assets` folder][assets], specified by the relative location within `DataComponents#ITEM_MODEL`. By default, this is the registry name of the object (e.g. `minecraft:apple` would be located at `assets/minecraft/items/apple.json` by default). + +The client items are stored within the `ModelManager`, which can be accessed through `Minecraft.getInstance().modelManager`. Then, you can call `ModelManager#getItemModel` or `getItemProperties` to get the client item information by its [`ResourceLocation`][rl]. + +:::warning +These are not to be confused with [baked models][bakedmodels], which define the models, along with their quads, that are actually rendered in-game. +::: + +## Overview + +The JSON of a client item can be broken into two parts: the model, defined by `model`; and the properties, defined by `properties`. The `model` is responsible for defining what model JSONs to use when rendering the `ItemStack` in a given context. The `properties`, on the other hand, is responsible for settings used by the renderer. + + + + +```json5 +// For some item 'examplemod:example_item' +// JSON at 'assets/examplemod/items/example_item.json' +{ + // Defines the model to render + "model": { + "type": "minecraft:model", + // Points to a model JSON relative to the 'models' directory + // Located at 'assets/examplemod/models/item/example_item.json' + "model": "examplemod:item/example_item" + }, + // Defines some settings to use during the rendering process + "properties": { + // When false, disables the animation where the item is raised + // up towards its normal position on item swap + "hand_animation_on_swap": false + } +} +``` + + + + + +```java +// Assume there is some DeferredItem EXAMPLE_ITEM +// Within an extended ModelProvider +@Override +protected void registerModels(BlockModelGenerators blockModels, ItemModelGenerators itemModels) { + itemModels.itemModelOutput.register( + EXAMPLE_ITEM.get(), + new ClientItem( + // Defines the model to render + new BlockModelWrapper.Unbaked( + // Points to a model JSON relative to the 'models' directory + // Located at 'assets/examplemod/models/item/example_item.json' + ModelUtils.getModelLocation(EXAMPLE_ITEM.get()), + Collections.emptyList() + ), + // Defines some settings to use during the rendering process + new ClientItem.Properties( + // When false, disables the animation where the item is raised + // up towards its normal position on item swap + false + ) + ) + ); +} +``` + + + + +More information about how item models are rendered can be found [below][itemmodel]. + +## A Basic Model + +The `type` field within `model` determines how to choose the model to render for the item. The simplest type is handled by `minecraft:model` (or `BlockModelWrapper`), which functionally defines the model JSON to render, relative to the `models` directory (e.g. `assets//models/.json`). + + + + +```json5 +// For some item 'examplemod:example_item' +// JSON at 'assets/examplemod/items/example_item.json' +{ + "model": { + "type": "minecraft:model", + // Points to a model JSON relative to the 'models' directory + // Located at 'assets/examplemod/models/item/example_item.json' + "model": "examplemod:item/example_item" + } +} +``` + + + + + +```java +// Assume there is some DeferredItem EXAMPLE_ITEM +// Within an extended ModelProvider +@Override +protected void registerModels(BlockModelGenerators blockModels, ItemModelGenerators itemModels) { + itemModels.itemModelOutput.accept( + EXAMPLE_ITEM.get(), + new BlockModelWrapper.Unbaked( + // Points to a model JSON relative to the 'models' directory + // Located at 'assets/examplemod/models/item/example_item.json' + ModelUtils.getModelLocation(EXAMPLE_ITEM.get()), + Collections.emptyList() + ) + ); +} +``` + + + + +### Tinting + +Like most models, client items can change the color of the specified texture based on the properties of the stack. As such, the `minecraft:model` type has the `tints` field to define the opaque colors to apply. These are known as `ItemTintSource`s, which are defined in `ItemTintSources`. They also have a `type` field to define which source to use. The `tintindex` they are applied to is specified by their index within the list. + + + + +```json5 +// For some item 'examplemod:example_item' +// JSON at 'assets/examplemod/items/example_item.json' +{ + "model": { + "type": "minecraft:model", + // Points to 'assets/examplemod/models/item/example_item.json' + "model": "examplemod:item/example_item", + // A list of tints to apply + "tints": [ + { + // For when tintindex: 0 + "type": "minecraft:constant", + // 0x00FF00 (or pure green) + "value": 65280 + }, + { + // For when tintindex: 1 + "type": "minecraft:dye", + // 0x0000FF (or pure blue) + // Only is called if `DataComponents#DYED_COLOR` is not set + "default": 255 + } + ] + } +} +``` + + + + + +```java +// Assume there is some DeferredItem EXAMPLE_ITEM +// Within an extended ModelProvider +@Override +protected void registerModels(BlockModelGenerators blockModels, ItemModelGenerators itemModels) { + itemModels.itemModelOutput.accept( + EXAMPLE_ITEM.get(), + new BlockModelWrapper.Unbaked( + // Points to 'assets/examplemod/models/item/example_item.json' + ModelUtils.getModelLocation(EXAMPLE_ITEM.get()), + // A list of tints to apply + List.of( + // For when tintindex: 0 + new Constant( + // Pure green + 0x00FF00 + ), + // For when tintindex: 1 + new Dye( + // Pure blue + // Only is called if `DataComponents#DYED_COLOR` is not set + 0x0000FF + ) + ) + ) + ); +} +``` + + + + +Creating your own `ItemTintSource` is similar to any other codec-based registry object. You make a class that implements `ItemTintSource`, create a `MapCodec` to encode and decode the object, and register the codec to its registry via `RegisterColorHandlersEvent.ItemTintSources` on the [mod event bus][modbus]. The `ItemTintSource` only contains one method `calculate`, which takes in the current `ItemStack`, the level the stack is in, and the entity holding the stack to return an opaque color in ARGB format, where the top 8 bits are 0xFF. + +```java +public record DamageBar(int defaultColor) implements ItemTintSource { + + // The map codec to register + public static final MapCodec MAP_CODEC = ExtraCodecs.RGB_COLOR_CODEC.fieldOf("default") + .xmap(DamageBar::new, DamageBar::defaultColor); + + public DamageBar(int defaultColor) { + // Make sure the passed in color is opaque + this.defaultColor = ARGB.opaque(defaultColor); + } + + @Override + public int calculate(ItemStack stack, @Nullable ClientLevel level, @Nullable LivingEntity entity) { + return stack.isDamaged() ? ARGB.opaque(stack.getBarColor()) : defaultColor; + } + + @Override + public MapCodec type() { + return MAP_CODEC; + } +} + +// In some client class where the event is registered to the mod event bus +@SubscribeEvent +public static void registerItemTintSources(RegisterColorHandlersEvent.ItemTintSources event) { + event.register( + // The name to reference as the type + ResourceLocation.fromNamespaceAndPath("examplemod", "damage_bar"), + // The map codec + DamageBar.MAP_CODEC + ) +} +``` + + + + +```json5 +// For some item 'examplemod:example_item' +// JSON at 'assets/examplemod/items/example_item.json' +{ + "model": { + "type": "minecraft:model", + // Points to 'assets/examplemod/models/item/example_item.json' + "model": "examplemod:item/example_item", + // A list of tints to apply + "tints": [ + { + // For when tintindex: 0 + "type": "examplemod:damage_bar", + // 0x00FF00 (or pure green) + "default": 65280 + } + ] + } +} +``` + + + + + +```java +// Assume there is some DeferredItem EXAMPLE_ITEM +// Within an extended ModelProvider +@Override +protected void registerModels(BlockModelGenerators blockModels, ItemModelGenerators itemModels) { + itemModels.itemModelOutput.accept( + EXAMPLE_ITEM.get(), + new BlockModelWrapper.Unbaked( + // Points to 'assets/examplemod/models/item/example_item.json' + ModelUtils.getModelLocation(EXAMPLE_ITEM.get()), + // A list of tints to apply + List.of( + // For when tintindex: 0 + new DamageBar( + // Pure green + 0x00FF00 + ) + ) + ) + ); +} +``` + + + + +## Composite Models + +Sometimes, you may want to register multiple models for a single item. While this can be done directly with the [composite model loader][composite], for item models, there is a custom `minecraft:composite` type which takes a list of models to render. + + + + +```json5 +// For some item 'examplemod:example_item' +// JSON at 'assets/examplemod/items/example_item.json' +{ + "model": { + "type": "minecraft:composite", + + // The models to render + // Will render in the order they appear in the list + "models": [ + { + "type": "minecraft:model", + // Points to 'assets/examplemod/models/item/example_item_1.json' + "model": "examplemod:item/example_item_1" + }, + { + "type": "minecraft:model", + // Points to 'assets/examplemod/models/item/example_item_2.json' + "model": "examplemod:item/example_item_2" + } + ] + } +} +``` + + + + + +```java +// Assume there is some DeferredItem EXAMPLE_ITEM +// Within an extended ModelProvider +@Override +protected void registerModels(BlockModelGenerators blockModels, ItemModelGenerators itemModels) { + itemModels.itemModelOutput.accept( + EXAMPLE_ITEM.get(), + new CompositeModel.Unbaked( + // The models to render + // Will render in the order they appear in the list + List.of( + new BlockModelWrapper.Unbaked( + // Points to 'assets/examplemod/models/item/example_item_1.json' + ResourceLocation.fromNamespaceAndPath("examplemod", "item/example_item_1"), + // A list of tints to apply + Collections.emptyList() + ), + new BlockModelWrapper.Unbaked( + // Points to 'assets/examplemod/models/item/example_item_2.json' + ResourceLocation.fromNamespaceAndPath("examplemod", "item/example_item_2"), + // A list of tints to apply + Collections.emptyList() + ) + ) + ) + ); +} +``` + + + + +## Property Models + +Some items change their state depending on the data stored in their stack (e.g., pulling a bow, breaking an elytra, a clock when in a given dimension, etc.). To allow models to change based on state, item models can specify a property to keep track of and select a model based on that condition. There are three different types of property models: range dispatch, select, and conditional. Each of these act as a expression for some float, switch case, and boolean respectively. + +### Range Dispatch Models + +Range dispatch models have the type define some `RangeSelectItemModelProperty` to get some float to switch the model on. Each entry then has some threshold value which the float must be greater than to render. The model chosen is the one with the closest threshold value that is not over the property value (e.g., if the property values is `4` with thresholds `3` and `5`, then the model associated with `3` will be rendered, and if the value was `6`, then the model associated with `5` would be rendered). The available `RangeSelectItemModelProperty`s to use can be found in `RangeSelectItemModelProperties`. + + + + +```json5 +// For some item 'examplemod:example_item' +// JSON at 'assets/examplemod/items/example_item.json' +{ + "model": { + "type": "minecraft:range_dispatch", + + // The `RangeSelectItemModelProperty` to use + "property": "minecraft:count", + // A scalar to multiply to the computed property value + // If count was 0.3 and scale was 0.2, then the threshold checked would be 0.3*0.2=0.06 + "scale": 1, + "fallback": { + // The fallback model to use if no threshold matches + // Can be any unbaked model type + "type": "minecraft:model", + // Points to 'assets/examplemod/models/item/example_item.json' + "model": "examplemod:item/example_item" + }, + + // Properties defined by `Count` + // When true, normalizes the count using its max stack size + "normalize": true, + + // Entries with threshold information + "entries": [ + { + // When the count is a third of its current max stack size + "threshold": 0.33, + "model": { + // Can be any unbaked model type + "type": "minecraft:model", + // Points to 'assets/examplemod/models/item/example_item_1.json' + "model": "examplemod:item/example_item_1" + } + }, + { + // When the count is two thirds of its current max stack size + "threshold": 0.66, + "model": { + // Can be any unbaked model type + "type": "minecraft:model", + // Points to 'assets/examplemod/models/item/example_item_2.json' + "model": "examplemod:item/example_item_2" + } + } + ] + } +} +``` + + + + + +```java +// Assume there is some DeferredItem EXAMPLE_ITEM +// Within an extended ModelProvider +@Override +protected void registerModels(BlockModelGenerators blockModels, ItemModelGenerators itemModels) { + itemModels.itemModelOutput.accept( + EXAMPLE_ITEM.get(), + new RangeSelectItemModel.Unbaked( + new Count( + // When true, normalizes the count using its max stack size + true + ), + // A scalar to multiply to the computed property value + // If count was 0.3 and scale was 0.2, then the threshold checked would be 0.3*0.2=0.06 + 1, + // Entries with threshold information + List.of( + new RangeSelectItemModel.Entry( + // When the count is a third of its current max stack size + 0.33, + // Can be any unbaked model type + new BlockModelWrapper.Unbaked( + // Points to 'assets/examplemod/models/item/example_item_1.json' + ResourceLocation.fromNamespaceAndPath("examplemod", "item/example_item_1"), + // A list of tints to apply + Collections.emptyList() + ) + ), + new RangeSelectItemModel.Entry( + // When the count is two thirds of its current max stack size + 0.66, + // Can be any unbaked model type + new BlockModelWrapper.Unbaked( + // Points to 'assets/examplemod/models/item/example_item_2.json' + ResourceLocation.fromNamespaceAndPath("examplemod", "item/example_item_2"), + // A list of tints to apply + Collections.emptyList() + ) + ) + ), + // The fallback model to use if no threshold matches + Optional.of( + new BlockModelWrapper.Unbaked( + // Points to 'assets/examplemod/models/item/example_item.json' + ModelUtils.getModelLocation(EXAMPLE_ITEM.get()), + // A list of tints to apply + Collections.emptyList() + ) + ) + ) + ); +} +``` + + + + +Creating your own `RangeSelectItemModelProperty` is similar to any other codec-based registry object. You make a class that implements `RangeSelectItemModelProperty`, create a `MapCodec` to encode and decode the object, and register the codec to its registry via `RegisterRangeSelectItemModelPropertyEvent` on the [mod event bus][modbus]. The `RangeSelectItemModelProperty` only contains one method `get`, which takes in the current `ItemStack`, the level the stack is in, the entity holding the stack, and some seeded value to return an arbitrary float to be interpreted by the ranged dispatch model. + +```java +public record AppliedEnchantments() implements RangeSelectItemModelProperty { + + public static final MapCodec MAP_CODEC = MapCodec.unit(new AppliedEnchantments()); + + @Override + public float get(ItemStack stack, @Nullable ClientLevel level, @Nullable LivingEntity entity, int seed) { + return (float) stack.getEnchantments().size(); + } + + @Override + public MapCodec type() { + return MAP_CODEC; + } +} + +// In some client class where the event is registered to the mod event bus +@SubscribeEvent +public static void registerRangeProperties(RegisterRangeSelectItemModelPropertyEvent event) { + event.register( + // The name to reference as the type + ResourceLocation.fromNamespaceAndPath("examplemod", "applied_enchantments"), + // The map codec + AppliedEnchantments.MAP_CODEC + ) +} +``` + + + + +```json5 +// For some item 'examplemod:example_item' +// JSON at 'assets/examplemod/items/example_item.json' +{ + "model": { + "type": "minecraft:range_dispatch", + + // The `RangeSelectItemModelProperty` to use + "property": "examplemod:applied_enchantments", + // A scalar to multiply to the computed property value + // If count was 0.3 and scale was 0.2, then the threshold checked would be 0.3*0.2=0.06 + "scale": 0.5, + "fallback": { + // The fallback model to use if no threshold matches + // Can be any unbaked model type + "type": "minecraft:model", + // Points to 'assets/examplemod/models/item/example_item.json' + "model": "examplemod:item/example_item" + }, + + // Entries with threshold information + "entries": [ + { + // When there is at least one enchantment present + // Since 1 * the scale 0.5 = 0.5 + "threshold": 0.5, + "model": { + // Can be any unbaked model type + "type": "minecraft:model", + // Points to 'assets/examplemod/models/item/example_item_1.json' + "model": "examplemod:item/example_item_1" + } + }, + { + // When there are at least two enchantments present + // Since 2 * the scale 0.5 = 1 + "threshold": 1, + "model": { + // Can be any unbaked model type + "type": "minecraft:model", + // Points to 'assets/examplemod/models/item/example_item_2.json' + "model": "examplemod:item/example_item_2" + } + } + ] + } +} +``` + + + + + +```java +// Assume there is some DeferredItem EXAMPLE_ITEM +// Within an extended ModelProvider +@Override +protected void registerModels(BlockModelGenerators blockModels, ItemModelGenerators itemModels) { + itemModels.itemModelOutput.accept( + EXAMPLE_ITEM.get(), + new RangeSelectItemModel.Unbaked( + new AppliedEnchantments(), + // A scalar to multiply to the computed property value + // If count was 0.3 and scale was 0.2, then the threshold checked would be 0.3*0.2=0.06 + 0.5, + // Entries with threshold information + List.of( + new RangeSelectItemModel.Entry( + // When there is at least one enchantment present + 0.5, + // Can be any unbaked model type + new BlockModelWrapper.Unbaked( + // Points to 'assets/examplemod/models/item/example_item_1.json' + ResourceLocation.fromNamespaceAndPath("examplemod", "item/example_item_1"), + // A list of tints to apply + Collections.emptyList() + ) + ), + new RangeSelectItemModel.Entry( + // When there are at least two enchantments present + 1, + // Can be any unbaked model type + new BlockModelWrapper.Unbaked( + // Points to 'assets/examplemod/models/item/example_item_2.json' + ResourceLocation.fromNamespaceAndPath("examplemod", "item/example_item_2"), + // A list of tints to apply + Collections.emptyList() + ) + ) + ), + // The fallback model to use if no threshold matches + Optional.of( + new BlockModelWrapper.Unbaked( + // Points to 'assets/examplemod/models/item/example_item.json' + ModelUtils.getModelLocation(EXAMPLE_ITEM.get()), + // A list of tints to apply + Collections.emptyList() + ) + ) + ) + ); +} +``` + + + + +### Select Models + +Select models are similar to range dispatch models, but they change switch based on some value defined by a `SelectItemModelProperty`, like a switch statement for an enum. The model chosen is the property which exactly matches the value in the switch case. The available `SelectItemModelProperty`s to use can be found in `SelectItemModelProperties`. + + + + +```json5 +// For some item 'examplemod:example_item' +// JSON at 'assets/examplemod/items/example_item.json' +{ + "model": { + "type": "minecraft:select", + + // The `SelectItemModelProperty` to use + "property": "minecraft:display_context", + "fallback": { + // The fallback model to use if no case matches + // Can be any unbaked model type + "type": "minecraft:model", + "model": "examplemod:item/example_item" + }, + + // Switch cases based on Selectable Property + "cases": [ + { + // When the display context is `ItemDisplayContext#GUI` + "when": "gui", + "model": { + // Can be any unbaked model type + "type": "minecraft:model", + // Points to 'assets/examplemod/models/item/example_item_1.json' + "model": "examplemod:item/example_item_1" + } + }, + { + // When the display context is `ItemDisplayContext#FIRST_PERSON_RIGHT_HAND` + "when": "firstperson_righthand", + "model": { + // Can be any unbaked model type + "type": "minecraft:model", + // Points to 'assets/examplemod/models/item/example_item_2.json' + "model": "examplemod:item/example_item_2" + } + } + ] + } +} +``` + + + + + +```java +// Assume there is some DeferredItem EXAMPLE_ITEM +// Within an extended ModelProvider +@Override +protected void registerModels(BlockModelGenerators blockModels, ItemModelGenerators itemModels) { + itemModels.itemModelOutput.accept( + EXAMPLE_ITEM.get(), + new SelectItemModel.Unbaked( + new SelectItemModel.UnbakedSwitch( + // The `SelectItemModelProperty` to use + new DisplayContext(), + // Switch cases based on selectable property + List.of( + new SelectItemModel.SwitchCase( + // The list of cases to match for this model + List.of(ItemDisplayContext.GUI), + // Can be any unbaked model type + new BlockModelWrapper.Unbaked( + // Points to 'assets/examplemod/models/item/example_item_1.json' + ResourceLocation.fromNamespaceAndPath("examplemod", "item/example_item_1"), + // A list of tints to apply + Collections.emptyList() + ) + ), + new SelectItemModel.SwitchCase( + // The list of cases to match for this model + List.of(ItemDisplayContext.FIRST_PERSON_RIGHT_HAND), + // Can be any unbaked model type + new BlockModelWrapper.Unbaked( + // Points to 'assets/examplemod/models/item/example_item_2.json' + ResourceLocation.fromNamespaceAndPath("examplemod", "item/example_item_2"), + // A list of tints to apply + Collections.emptyList() + ) + ) + ) + ), + // The fallback model to use if no case matches + Optional.of( + new BlockModelWrapper.Unbaked( + // Points to 'assets/examplemod/models/item/example_item.json' + ModelUtils.getModelLocation(EXAMPLE_ITEM.get()), + // A list of tints to apply + Collections.emptyList() + ) + ) + ) + ); +} +``` + + + + +Creating your own `SelectItemModelProperty` is similar to a codec-based registry object. You make a class that implements `SelectItemModelProperty`, create a `Codec` to serialize and deserialize the property value, create a `MapCodec` to encode and decode the object, and register the codec to its registry via `RegisterSelectItemModelPropertyEvent` on the [mod event bus][modbus]. The `SelectItemModelProperty` has a generic `T` that represents the value to switch on. It only contains one method `get`, which takes in the current `ItemStack`, the level the stack is in, the entity holding the stack, some seeded value, and the display context of the item to return an arbitrary `T` to be interpreted by the select model. + +```java +// The select property class +public record StackRarity() implements SelectItemModelProperty { + + // The object to register that contains the relevant codecs + public static final SelectItemModelProperty.Type TYPE = SelectItemModelProperty.Type.create( + // The map codec for this property + MapCodec.unit(new StackRarity()), + // The codec for the object being selected + // Used to serialize the case entries ("when": ) + Rarity.CODEC + ); + + @Nullable + @Override + public Rarity get(ItemStack stack, @Nullable ClientLevel level, @Nullable LivingEntity entity, int seed, ItemDisplayContext displayContext) { + // When null, uses the fallback model + return stack.get(DataComponents.RARITY); + } + + @Override + public SelectItemModelProperty.Type type() { + return TYPE; + } +} + +// In some client class where the event is registered to the mod event bus +@SubscribeEvent +public static void registerSelectProperties(RegisterSelectItemModelPropertyEvent event) { + event.register( + // The name to reference as the type + ResourceLocation.fromNamespaceAndPath("examplemod", "rarity"), + // The property type + StackRarity.TYPE + ) +} +``` + + + + +```json5 +// For some item 'examplemod:example_item' +// JSON at 'assets/examplemod/items/example_item.json' +{ + "model": { + "type": "minecraft:select", + + // The `SelectItemModelProperty` to use + "property": "examplemod:rarity", + "fallback": { + // The fallback model to use if no case matches + // Can be any unbaked model type + "type": "minecraft:model", + "model": "examplemod:item/example_item" + }, + + // Switch cases based on Selectable Property + "cases": [ + { + // When the rarity is `Rarity#UNCOMMON` + "when": "uncommon", + "model": { + // Can be any unbaked model type + "type": "minecraft:model", + // Points to 'assets/examplemod/models/item/example_item_1.json' + "model": "examplemod:item/example_item_1" + } + }, + { + // When the rarity is `Rarity#RARE` + "when": "rare", + "model": { + // Can be any unbaked model type + "type": "minecraft:model", + // Points to 'assets/examplemod/models/item/example_item_2.json' + "model": "examplemod:item/example_item_2" + } + } + ] + } +} +``` + + + + + +```java +// Assume there is some DeferredItem EXAMPLE_ITEM +// Within an extended ModelProvider +@Override +protected void registerModels(BlockModelGenerators blockModels, ItemModelGenerators itemModels) { + itemModels.itemModelOutput.accept( + EXAMPLE_ITEM.get(), + new SelectItemModel.Unbaked( + new SelectItemModel.UnbakedSwitch( + // The `SelectItemModelProperty` to use + new StackRarity(), + // Switch cases based on selectable property + List.of( + new SelectItemModel.SwitchCase( + // The list of cases to match for this model + List.of(Rarity.UNCOMMON), + // Can be any unbaked model type + new BlockModelWrapper.Unbaked( + // Points to 'assets/examplemod/models/item/example_item_1.json' + ResourceLocation.fromNamespaceAndPath("examplemod", "item/example_item_1"), + // A list of tints to apply + Collections.emptyList() + ) + ), + new SelectItemModel.SwitchCase( + // The list of cases to match for this model + List.of(Rarity.RARE), + // Can be any unbaked model type + new BlockModelWrapper.Unbaked( + // Points to 'assets/examplemod/models/item/example_item_2.json' + ResourceLocation.fromNamespaceAndPath("examplemod", "item/example_item_2"), + // A list of tints to apply + Collections.emptyList() + ) + ) + ) + ), + // The fallback model to use if no case matches + Optional.of( + new BlockModelWrapper.Unbaked( + // Points to 'assets/examplemod/models/item/example_item.json' + ModelUtils.getModelLocation(EXAMPLE_ITEM.get()), + // A list of tints to apply + Collections.emptyList() + ) + ) + ) + ); +} +``` + + + + +### Conditional Models + +Conditional models are the simplest out of the three. The type defines some `ConditionalItemModelProperty` to get a boolean to switch the model on. The model chosen based on whether the returned boolean is true or false. The available `ConditionalItemModelProperty`s to use can be found in `ConditionalItemModelProperties`. + + + + +```json5 +// For some item 'examplemod:example_item' +// JSON at 'assets/examplemod/items/example_item.json' +{ + "model": { + "type": "minecraft:condition", + + // The `ConditionalItemModelProperty` to use + "property": "minecraft:damaged", + + // What the boolean outcome is + "on_true": { + // Can be any unbaked model type + "type": "minecraft:model", + // Points to 'assets/examplemod/models/item/example_item_1.json' + "model": "examplemod:item/example_item_1" + + }, + "on_false": { + // Can be any unbaked model type + "type": "minecraft:model", + // Points to 'assets/examplemod/models/item/example_item_2.json' + "model": "examplemod:item/example_item_2" + } + } +} +``` + + + + + +```java +// Assume there is some DeferredItem EXAMPLE_ITEM +// Within an extended ModelProvider +@Override +protected void registerModels(BlockModelGenerators blockModels, ItemModelGenerators itemModels) { + itemModels.itemModelOutput.accept( + EXAMPLE_ITEM.get(), + new ConditionalItemModel.Unbaked( + // The property to check + new Damaged(), + // When the boolean is true + new BlockModelWrapper.Unbaked( + // Points to 'assets/examplemod/models/item/example_item_1.json' + ResourceLocation.fromNamespaceAndPath("examplemod", "item/example_item_1"), + // A list of tints to apply + Collections.emptyList() + ), + // When the boolean is false + new BlockModelWrapper.Unbaked( + // Points to 'assets/examplemod/models/item/example_item_2.json' + ResourceLocation.fromNamespaceAndPath("examplemod", "item/example_item_2"), + // A list of tints to apply + Collections.emptyList() + ) + ) + ); +} +``` + + + + +Creating your own `ConditionalItemModelProperty` is similar to any other codec-based registry object. You make a class that implements `ConditionalItemModelProperty`, create a `MapCodec` to encode and decode the object, and register the codec to its registry via `RegisterConditionalItemModelPropertyEvent` on the [mod event bus][modbus]. The `RangeSelectItemModelProperty` only contains one method `get`, which takes in the current `ItemStack`, the level the stack is in, the entity holding the stack, some seeded value, and the display context of the item to return an arbitrary boolean to be interpreted by the conditional model (`on_true` or `on_false`). + +```java +public record BarVisible() implements ConditionalItemModelProperty { + + public static final MapCodec MAP_CODEC = MapCodec.unit(new BarVisible()); + + @Override + public boolean get(ItemStack stack, @Nullable ClientLevel level, @Nullable LivingEntity entity, int seed, ItemDisplayContext context) { + return stack.isBarVisible(); + } + + @Override + public MapCodec type() { + return MAP_CODEC; + } +} + +// In some client class where the event is registered to the mod event bus +@SubscribeEvent +public static void registerConditionalProperties(RegisterConditionalItemModelPropertyEvent event) { + event.register( + // The name to reference as the type + ResourceLocation.fromNamespaceAndPath("examplemod", "bar_visible"), + // The map codec + BarVisible.MAP_CODEC + ) +} +``` + + + + +```json5 +// For some item 'examplemod:example_item' +// JSON at 'assets/examplemod/items/example_item.json' +{ + "model": { + "type": "minecraft:condition", + + // The `ConditionalItemModelProperty` to use + "property": "examplemod:bar_visible", + + // What the boolean outcome is + "on_true": { + // Can be any unbaked model type + "type": "minecraft:model", + // Points to 'assets/examplemod/models/item/example_item_1.json' + "model": "examplemod:item/example_item_1" + + }, + "on_false": { + // Can be any unbaked model type + "type": "minecraft:model", + // Points to 'assets/examplemod/models/item/example_item_2.json' + "model": "examplemod:item/example_item_2" + } + } +} +``` + + + + + +```java +// Assume there is some DeferredItem EXAMPLE_ITEM +// Within an extended ModelProvider +@Override +protected void registerModels(BlockModelGenerators blockModels, ItemModelGenerators itemModels) { + itemModels.itemModelOutput.accept( + EXAMPLE_ITEM.get(), + new ConditionalItemModel.Unbaked( + // The property to check + new BarVisible(), + // When the boolean is true + new BlockModelWrapper.Unbaked( + // Points to 'assets/examplemod/models/item/example_item_1.json' + ResourceLocation.fromNamespaceAndPath("examplemod", "item/example_item_1"), + // A list of tints to apply + Collections.emptyList() + ), + // When the boolean is false + new BlockModelWrapper.Unbaked( + // Points to 'assets/examplemod/models/item/example_item_2.json' + ResourceLocation.fromNamespaceAndPath("examplemod", "item/example_item_2"), + // A list of tints to apply + Collections.emptyList() + ) + ) + ); +} +``` + + + + +## Special Models + +Not all models can be rendered using the basic model JSON. Some models can be dynamically rendered, or use existing models created for a [`BlockEntityRenderer`][ber]. In these instances, there is a special model type which allows the user to define their own rendering logic to use. These are known as `SpecialModelRenderer`s, which are defined within `SpecialModelRenderers`. + + + + +```json5 +// For some item 'examplemod:example_item' +// JSON at 'assets/examplemod/items/example_item.json' +{ + "model": { + "type": "minecraft:special", + + // The parent model to read the particle texture and display transformation from + // Points to 'assets/minecraft/models/item/template_skull.json' + "base": "minecraft:item/template_skull", + "model": { + // The special model renderer to use + "type": "minecraft:head", + + // Properties defined by `SkullSpecialRenderer.Unbaked` + // The type of the skull block + "kind": "wither_skeleton", + // The texture to use when rendering the head + // Points to 'assets/examplemod/textures/entity/heads/skeleton_override.png' + "texture": "examplemod:heads/skeleton_override", + // The animation float used to animate the head model + "animation": 0.5 + } + } +} +``` + + + + + +```java +// Assume there is some DeferredItem EXAMPLE_ITEM +// Within an extended ModelProvider +@Override +protected void registerModels(BlockModelGenerators blockModels, ItemModelGenerators itemModels) { + itemModels.itemModelOutput.accept( + EXAMPLE_ITEM.get(), + new SpecialModelWrapper.Unbaked( + // The parent model to read the particle texture and display transformation from + // Points to 'assets/minecraft/models/item/template_skull.json' + ResourceLocation.fromNamespaceAndPath("minecraft", "item/template_skull"), + // The special model renderer to use + new SkullSpecialRenderer.Unbaked( + // The type of the skull block + SkullBlock.Types.WITHER_SKELETON, + // The texture to use when rendering the head + // Points to 'assets/examplemod/textures/entity/heads/skeleton_override.png' + Optional.of( + ResourceLocation.fromNamespaceAndPath("examplemod", "heads/skeleton_override") + ), + // The animation float used to animate the head model + 0.5f + ) + ) + ); +} +``` + + + + +Creating your own `SpecialModelRenderer` is broken into three parts: the `SpecialModelRenderer` instance used to render the item, the `SpecialModelRenderer.Unbaked` instance used to read and write to JSON, and the registration to use the renderer when as an item or, if necessary, when as a block. + +First, there is the `SpecialModelRenderer`. This works similarly to any other renderer class (e.g. block entity renderers, entity renderers). It should take in the static data used during the rendering process (e.g., the `Model` instance, the `Material` of the texture, etc.). There are two methods to be aware of. First, there is `extractArgument`. This is used to limit the amount of data available to the `render` method by only supplying what is necessary from the `ItemStack`. + +:::note +If you don't know what data you may need, you can just have this return the `ItemStack` in question. If you need no data from the stack, you can instead use `NoDataSpecialModelRenderer`, which implements this method for you. +::: + +Next is the `render` method. This takes in value returned from `extractArgument`, the display context of the item, the pose stack to render in, the buffer sources available to use, the packed light, the overlay texture, and a boolean if the stack is foiled (e.g. enchanted). All rendering should happen in this method. + +```java +public record ExampleSpecialRenderer(Model model, Material material) implements SpecialModelRenderer { + + @Nullable + public Boolean extractArgument(ItemStack stack) { + // Extract the data to be used + return stack.isBarVisible(); + } + + // Render the model + @Override + public void render(@Nullable Boolean barVisible, ItemDisplayContext displayContext, PoseStack pose, MultiBufferSource bufferSource, int light, int overlay, boolean hasFoil) { + this.model.renderToBuffer(pose, this.material.buffer(bufferSource, barVisible ? RenderType::entityCutout : RenderType::entitySolid), light, overlay); + } +} +``` + +Next is the `SpecialModelRenderer.Unbaked` instance. This should contain data that can be read from a file to determine what to pass into the special renderer. This also contains two methods: `bake`, which is used to construct the special renderer instance; and `type`, which defines the `MapCodec` to use for encoding/decoding to file. + +```java +public record ExampleSpecialRenderer(Model model, Material material) implements SpecialModelRenderer { + + // ... + + public record Unbaked(ResourceLocation texture) implements SpecialModelRenderer.Unbaked { + + public static final MapCodec MAP_CODEC = ResourceLocation.CODEC.fieldOf("texture") + .xmap(ExampleSpecialRenderer.Unbaked::new, ExampleSpecialRenderer.Unbaked::texture); + + @Override + public MapCodec type() { + return MAP_CODEC; + } + + @Override + public SpecialModelRenderer bake(EntityModelSet modelSet) { + // Resolve resource location to absolute path + ResourceLocation textureLoc = this.texture.withPath(path -> "textures/entity/" + path + ".png"); + + // Get the model and the material to render + return new ExampleSpecialRenderer(...); + } + } +} +``` + +Finally, we register the objects to their necessary locations. For the client items, this is done via `RegisterSpecialModelRendererEvent` on the [mod event bus][modbus]. If the special renderer should also be used as part of a `BlockEntityRenderer`, such as when rendering in some item-like context (e.g., enderman holding the block), then an `Unbaked` version for the block should be registered via `RegisterSpecialBlockModelRendererEvent` on the [mod event bus][modbus]. + +```java +// In some client class where the event is registered to the mod event bus +@SubscribeEvent +public static void registerSpecialRenderers(RegisterSpecialModelRendererEvent event) { + event.register( + // The name to reference as the type + ResourceLocation.fromNamespaceAndPath("examplemod", "example_special"), + // The map codec + ExampleSpecialRenderer.Unbaked.MAP_CODEC + ) +} + +// For rendering a block in an item-like context +// Assume some DeferredBlock EXAMPLE_BLOCK +@SubscribeEvent +public static void registerSpecialBlockRenderers(RegisterSpecialBlockModelRendererEvent event) { + event.register( + // The block to render for + EXAMPLE_BLOCK.get() + // The unbaked instance to use + new ExampleSpecialRenderer.Unbaked(ResourceLocation.fromNamespaceAndPath("examplemod", "entity/example_special")) + ) +} +``` + + + + +```json5 +// For some item 'examplemod:example_item' +// JSON at 'assets/examplemod/items/example_item.json' +{ + "model": { + "type": "minecraft:special", + + // The parent model to read the particle texture and display transformation from + // Points to 'assets/minecraft/models/item/template_skull.json' + "base": "minecraft:item/template_skull", + "model": { + // The special model renderer to use + "type": "examplemod:example_special", + + // Properties defined by `ExampleSpecialRenderer.Unbaked` + // The texture to use when rendering + // Points to 'assets/examplemod/textures/entity/example/example_texture.png' + "texture": "examplemod:example/example_texture" + } + } +} +``` + + + + + +```java +// Assume there is some DeferredItem EXAMPLE_ITEM +// Within an extended ModelProvider +@Override +protected void registerModels(BlockModelGenerators blockModels, ItemModelGenerators itemModels) { + itemModels.itemModelOutput.accept( + EXAMPLE_ITEM.get(), + new SpecialModelWrapper.Unbaked( + // The parent model to read the particle texture and display transformation from + // Points to 'assets/minecraft/models/item/template_skull.json' + ResourceLocation.fromNamespaceAndPath("minecraft", "item/template_skull"), + // The special model renderer to use + new ExampleSpecialRenderer.Unbaked( + // The texture to use when rendering + // Points to 'assets/examplemod/textures/entity/example/example_texture.png' + ResourceLocation.fromNamespaceAndPath("examplemod", "example/example_texture") + ) + ) + ); +} +``` + + + + +## Manually Rendering an Item + +If you need to render an item yourself, such as in some `BlockEntityRenderer` or `EntityRenderer`, it can be achieved through three steps. First, the renderer in question creates an `ItemStackRenderState` to hold the rendering information of the stack. Then, the `ItemModelResolver` updates the `ItemStackRenderState` using one of its methods to update the state to the current item to render. Finally, the item is rendered using the render state's `render` method. + +The `ItemStackRenderState` keeps track of the data used to render. Each 'model' is given its own `ItemStackRenderState.LayerRenderState`, which contains the `BakedModel` to render, along with its render type, foil status, tint information, and any special renderers used. Layers are created using the `newLayer` method, and cleared for rendering using the `clear` method. If a predefined number of layers is used, then `ensureCapacity` is used to make sure there are the necessary number of `LayerRenderStates` to render properly. The `ItemStackRenderState` also contains some methods to get the `BakedModel` properties for the first layer except for `pickParticleIcon`, which gets the particle texture for a random layer. + +`ItemModelResolver` is responsible for updating the `ItemStackRenderState`. This is done through either `updateForLiving` for items held by living entities, `updateForNonLiving` for items held by other kinds of entities, and `updateForTopItem` for all other cases. These methods take in the render state, stack to render, and current display context. The other parameters update information about the held hand, level, entity, and seeded value. Each method calls `ItemStackRenderState#clear` before calling `update` on the `ItemModel` obtained from `DataComponents#ITEM_MODEL`. The `ItemModelResolver` can always be obtained via `Minecraft#getItemModelResolver` if you are not within some renderer context (e.g., `BlockEntityRenderer`, `EntityRenderer`). + +## Custom Item Model Defintions + +Creating your own `ItemModel` is broken into three parts: the `ItemModel` instance used update the render state, the `ItemModel.Unbaked` instance used to read and write to JSON, and the registration to use the `ItemModel`. + +:::warning +Please make sure to check that your required item model can not be created with the existing systems above. In most cases, it is not necessary to create a custom `ItemModel`. +::: + +First, there is the `ItemModel`. This is responsible for updating the `ItemStackRenderState` such that the item is rendered correctly. It should take in the static data used during the rendering process (e.g., the `BakedModel` instance, property information, etc.). The only method is `update`, which takes in the render state, stack, model resolver, display context, level, holding entity, and some seeded value to update the `ItemStackRenderState`. `ItemStackRenderState` should be the only parameter modified, with the rest treated as read-only data. + +```java +public record RenderTypeModelWrapper(BakedModel model, RenderType type) implements ItemModel { + + // Update the render state + @Override + public void update(ItemStackRenderState state, ItemStack stack, ItemModelResolver resolver, ItemDisplayContext displayContext, @Nullable ClientLevel level, @Nullable LivingEntity entity, int seed) { + // Create a new layer to render the model in + ItemStackRenderState.LayerRenderState layerState = state.newLayer(); + if (stack.hasFoil()) { + layerState.setFoilType(ItemStackRenderState.FoilType.STANDARD); + } + layerState.setupBlockModel(this.model, this.type); + } +} +``` + +Next is the `ItemModel.Unbaked` instance. This should contain data that can be read from a file to determine what to pass into the item model. This also contains two methods: `bake`, which is used to construct the `ItemModel` instance; and `type`, which defines the `MapCodec` to use for encoding/decoding to file. + +```java +public record RenderTypeModelWrapper(BakedModel model, RenderType type) implements ItemModel { + + // ... + + public record Unbaked(ResourceLocation model, RenderType type) implements ItemModel.Unbaked { + // Create a render type map for the codec + private static final BiMap RENDER_TYPES = Util.make(HashBiMap.create(), map -> { + map.put("translucent_item", Sheets.translucentItemSheet()); + map.put("cutout_block", Sheets.cutoutBlockSheet()); + }); + private static final Codec RENDER_TYPE_CODEC = ExtraCodecs.idResolverCodec(Codec.STRING, RENDER_TYPES::get, RENDER_TYPES.inverse()::get); + + // The map codec to register + public static final MapCodec MAP_CODEC = RecordCodecBuilder.mapCodec(instance -> + instance.group( + ResourceLocation.CODEC.fieldOf("model").forGetter(RenderTypeModelWrapper.Unbaked::model), + RENDER_TYPE_CODEC.fieldOf("render_type").forGetter(RenderTypeModelWrapper.Unbaked::type) + ) + .apply(instance, RenderTypeModelWrapper.Unbaked::new) + ); + + @Override + public void resolveDependencies(ResolvableModel.Resolver resolver) { + // Resolve model dependencies, so pass in all known resource locations + resolver.resolve(this.model); + } + + @Override + public ItemModel bake(ItemModel.BakingContext context) { + // Get the baked model and return + BakedModel baked = context.bake(this.model); + return new RenderTypeModelWrapper(baked, this.type); + } + + @Override + public MapCodec type() { + return MAP_CODEC; + } + } +} +``` + +Then, we register the map codec via `RegisterItemModelsEvent` on the [mod event bus][modbus]. + +```java +// In some client class where the event is registered to the mod event bus +@SubscribeEvent +public static void registerItemModels(RegisterItemModelsEvent event) { + event.register( + // The name to reference as the type + ResourceLocation.fromNamespaceAndPath("examplemod", "render_type"), + // The map codec + RenderTypeModelWrapper.Unbaked.MAP_CODEC + ) +} +``` + +Finally, we can use the `ItemModel` in our JSON or as part of the datagen process. + + + + +```json5 +// For some item 'examplemod:example_item' +// JSON at 'assets/examplemod/items/example_item.json' +{ + "model": { + "type": "examplemod:render_type", + // Points to 'assets/examplemod/models/item/example_item.json' + "model": "examplemod:item/example_item", + // Set the render type to use when rendering + "render_type": "cutout_block" + } +} +``` + + + + + +```java +// Assume there is some DeferredItem EXAMPLE_ITEM +// Within an extended ModelProvider +@Override +protected void registerModels(BlockModelGenerators blockModels, ItemModelGenerators itemModels) { + itemModels.itemModelOutput.accept( + EXAMPLE_ITEM.get(), + new RenderTypeModelWrapper.Unbaked( + // Points to 'assets/examplemod/models/item/example_item.json' + ModelUtils.getModelLocation(EXAMPLE_ITEM.get()), + // Set the render type to use when rendering + Sheets.cutoutBlockSheet() + ) + ); +} +``` + + + + +[assets]: ../../index.md#assets +[bakedmodels]: ../models/bakedmodel.md +[ber]: ../../../blockentities/ber.md +[composite]: modelloaders.md#composite-model +[itemmodel]: #manually-rendering-an-item +[modbus]: ../../../concepts/events.md#event-buses +[rl]: ../../../misc/resourcelocation.md diff --git a/docs/resources/client/models/modelloaders.md b/docs/resources/client/models/modelloaders.md index 6b3874060..531e607bb 100644 --- a/docs/resources/client/models/modelloaders.md +++ b/docs/resources/client/models/modelloaders.md @@ -46,69 +46,10 @@ Then, we can disable and enable individual parts in a child model of `examplemod To [datagen][modeldatagen] this model, use the custom loader class `CompositeModelBuilder`. -### Dynamic Fluid Container Model - -The dynamic fluid container model, also called dynamic bucket model after its most common use case, is used for items that represent a fluid container (such as a bucket or a tank) and want to show the fluid within the model. This only works if there is a fixed amount of fluids (e.g. only lava and powder snow) that can be used, use a [`BlockEntityWithoutLevelRenderer`][bewlr] instead if the fluid is arbitrary. - -```json5 -{ - "loader": "neoforge:fluid_container", - // Required. Must be a valid fluid id. - "fluid": "minecraft:water", - // The loader generally expects two textures: base and fluid. - "textures": { - // The base container texture, i.e. the equivalent of an empty bucket. - "base": "examplemod:item/custom_container", - // The fluid texture, i.e. the equivalent of water in a bucket. - "fluid": "examplemod:item/custom_container_fluid" - }, - // Optional, defaults to false. Whether to flip the model upside down, for gaseous fluids. - "flip_gas": true, - // Optional, defaults to true. Whether to have the cover act as the mask. - "cover_is_mask": false, - // Optional, defaults to true. Whether to apply the fluid's luminosity to the item model. - "apply_fluid_luminosity": false, -} -``` - -:::note -If you would like to apply a tint to the fluid texture, you will need to [register an `ItemColor`][tint]. An instance with the fluid tinting logic can be created from `DynamicFluidContainerModel.Colors`: - -```java -// Client-side mod bus event handler -@SubscribeEvent -public static void registerItemColorHandlers(RegisterColorHandlersEvent.Item event) { - event.register(new DynamicFluidContainerModel.Colors(), EXAMPLE_BUCKET.get(), ...); -} -``` +:::warning +The composite model loader should not be used for models used by [client items][citems]. Instead, they should use the [composite model][itemcomposite] provided in the definition itself. ::: -Very often, dynamic fluid container models will directly use the bucket model. This is done by specifying the `neoforge:item_bucket` parent model, like so: - -```json5 -{ - "loader": "neoforge:fluid_container", - "parent": "neoforge:item/bucket", - // Replace with your own fluid. - "fluid": "minecraft:water" - // Optional properties here. Note that the textures are handled by the parent. -} -``` - -To [datagen][modeldatagen] this model, use the custom loader class `DynamicFluidContainerModelBuilder`. - -### Elements Model - -An elements model consists of block model [elements][elements] and an optional [root transform][transform]. Intended mainly for usage outside regular model rendering, for example within a [BER][ber]. - -```json5 -{ - "loader": "neoforge:elements", - "elements": [...], - "transform": {...} -} -``` - ### Empty Model An empty model just renders nothing at all. @@ -119,36 +60,6 @@ An empty model just renders nothing at all. } ``` -### Item Layer Model - -Item layer models are a variant of the standard `item/generated` model that offer the following additional features: - -- Unlimited amount of layers (instead of the default 5) -- Per-layer [render types][rendertype] - -```json5 -{ - "loader": "neoforge:item_layers", - "textures": { - "layer0": "...", - "layer1": "...", - "layer2": "...", - "layer3": "...", - "layer4": "...", - "layer5": "...", - }, - "render_types": { - // Map render types to layer numbers. For example, layers 0, 2 and 4 will use cutout. - "minecraft:cutout": [0, 2, 4], - "minecraft:cutout_mipped": [1, 3], - "minecraft:translucent": [5] - }, - // other stuff the default loader allows here -} -``` - -To [datagen][modeldatagen] this model, use the custom loader class `ItemLayerModelBuilder`. - ### OBJ Model The OBJ model loader allows you to use Wavefront `.obj` 3D models in the game, allowing for arbitrary shapes (including triangles, circles, etc.) to be included in a model. The `.obj` model must be placed in the `models` folder (or a subfolder thereof), and a `.mtl` file with the same name must be provided (or set manually), so for example, an OBJ model at `models/block/example.obj` must have a corresponding MTL file at `models/block/example.mtl`. @@ -182,166 +93,90 @@ The OBJ model loader allows you to use Wavefront `.obj` 3D models in the game, a To [datagen][modeldatagen] this model, use the custom loader class `ObjModelBuilder`. -### Separate Transforms Model - -A separate transforms model can be used to switch between different models based on the perspective. The perspectives are the same as for the `display` block in a [normal model][model]. This works by specifying a base model (as a fallback) and then specifying per-perspective override models. Note that each of these can be fully-fledged models if you so desire, but it is usually easiest to just refer to another model by using a child model of that model, like so: - -```json5 -{ - "loader": "neoforge:separate_transforms", - // Use the cobblestone model normally. - "base": { - "parent": "minecraft:block/cobblestone" - }, - // Use the stone model only when dropped. - "perspectives": { - "ground": { - "parent": "minecraft:block/stone" - } - } -} -``` - -To [datagen][modeldatagen] this model, use the custom loader class `SeparateTransformsModelBuilder`. - ## Creating Custom Model Loaders To create your own model loader, you need three classes, plus an event handler: -- A geometry loader class -- A geometry class -- A dynamic [baked model][bakedmodel] class -- A [client-side][sides] [event handler][event] for `ModelEvent.RegisterGeometryLoaders` that registers the geometry loader +- An `UnbakedModelLoader` class +- An `UnbakedModel` class +- A [baked model][bakedmodel] class, usually a `SimpleBakedModel` instance, or `IDynamicBakedModel` if the `ModelData` is required +- A [client-side][sides] [event handler][event] for `ModelEvent.RegisterLoaders` that registers the unbaked model loader +- Optional: A [client-side][sides] [event handler][event] for `RegisterClientReloadListenersEvent` for model loaders that cache data about what is being loaded To illustrate how these classes are connected, we will follow a model being loaded: -- During model loading, a model JSON with the `loader` property set to your loader is passed to your geometry loader. The geometry loader then reads the model JSON and returns a geometry object using the model JSON's properties. -- During model baking, the geometry is baked, returning a dynamic baked model. -- During model rendering, the dynamic baked model is used for rendering. +- During model loading, a model JSON with the `loader` property set to your loader is passed to your unbaked model loader. The loader then reads the model JSON and returns an unbaked object using the model JSON's properties. +- During model baking, the object is baked, returning a baked model. +- During model rendering, the baked model is used for rendering. -Let's illustrate this further through a basic class setup. The geometry loader class is named `MyGeometryLoader`, the geometry class is named `MyGeometry`, and the dynamic baked model class is named `MyDynamicModel`: +:::note +If you are creating a custom model loader for a model used by an item, depending on the use case, it would be better to create a new `ItemModel` instead. For example, a model that uses or generates `BakedModel`s would make more sense as an `ItemModel` while a model that renders a different data format (like `.obj`) should create a new model loader. +::: + +Let's illustrate this further through a basic class setup. The loader class is named `MyUnbakedModelLoader`, the unbaked class is named `MyUnbakedModel`, and we construct a `SimpleBakedModel` instance. We will also assume that the model loader requires some cache: ```java -public class MyGeometryLoader implements IGeometryLoader { - // It is highly recommended to use a singleton pattern for geometry loaders, as all models can be loaded through one loader. - public static final MyGeometryLoader INSTANCE = new MyGeometryLoader(); +public class MyUnbakedModelLoader implements UnbakedModelLoader, ResourceManagerReloadListener { + // It is highly recommended to use a singleton pattern for unbaked model loaders, as all models can be loaded through one loader. + public static final MyUnbakedModelLoader INSTANCE = new MyUnbakedModelLoader(); // The id we will use to register this loader. Also used in the loader datagen class. public static final ResourceLocation ID = ResourceLocation.fromNamespaceAndPath("examplemod", "my_custom_loader"); // In accordance with the singleton pattern, make the constructor private. - private MyGeometryLoader() {} + private MyUnbakedModelLoader() {} + + @Override + public void onResourceManagerReload(ResourceManager resourceManager) { + // Handle any cache clearing logic + } @Override - public MyGeometry read(JsonObject jsonObject, JsonDeserializationContext context) throws JsonParseException { + public MyUnbakedModel read(JsonObject jsonObject, JsonDeserializationContext context) throws JsonParseException { // Use the given JsonObject and, if needed, the JsonDeserializationContext to get properties from the model JSON. - // The MyGeometry constructor may have constructor parameters (see below). - return new MyGeometry(); + // The MyUnbakedModel constructor may have constructor parameters (see below). + return new MyUnbakedModel(); } } -public class MyGeometry implements IUnbakedGeometry { +// AbstractUnbakedModel is used as the base unbaked model for custom models. +// Adds support for vanilla and NeoForge properties but leaves the bake method +// for modder implementation. +public class MyUnbakedModel extends AbstractUnbakedModel { // The constructor may have any parameters you need, and store them in fields for further usage below. - // If the constructor has parameters, the constructor call in MyGeometryLoader#read must match them. - public MyGeometry() {} - - // Method responsible for model baking, returning our dynamic model. Parameters in this method are: - // - The geometry baking context. Contains many properties that we will pass into the model, e.g. light and ao values. - // - The model baker. Can be used for baking sub-models. - // - The sprite getter. Maps materials (= texture variables) to TextureAtlasSprites. Materials can be obtained from the context. - // For example, to get a model's particle texture, call spriteGetter.apply(context.getMaterial("particle")); + // If the constructor has parameters, the constructor call in MyUnbakedModelLoader#read must match them. + public MyUnbakedModel() {} + + // Method responsible for model baking, returning our baked model. Parameters in this method are: + // - The map of texture names to their associated materials. + // - The model baker. Can be used for baking sub-models and getting sprites from the texture slots. // - The model state. This holds the properties from the blockstate file, e.g. rotations and the uvlock boolean. - // - The item overrides. This is the code representation of an "overrides" block in an item model. + // - A boolean of whether to use ambient occlusion when rendering the model. + // - A boolean of whether to use the block light when rendering a model. + // - The item transforms associated with how this model should be displayed in a given ItemDisplayContext. + // - A ContextMap of settings provided by NeoForge. See the 'NeoForgeModelProperties' class for all available properties. @Override - public BakedModel bake(IGeometryBakingContext context, ModelBaker baker, Function spriteGetter, ModelState modelState, List overrides) { - // See info on the parameters below. - return new MyDynamicModel(context.useAmbientOcclusion(), context.isGui3d(), context.useBlockLight(), - spriteGetter.apply(context.getMaterial("particle")), new BakedOverrides(baker, overrides)); + public BakedModel bake(TextureSlots textures, ModelBaker baker, ModelState modelState, boolean useAmbientOcclusion, boolean usesBlockLight, ItemTransforms itemTransforms, ContextMap additionalProperties) { + // The true boolean represents if the model is in 3D within the GUI + var builder = new SimpleBakedModel.Builder(useAmbientOcclusion, usesBlockLight, true, itemTransforms); + // Sets the particle texture + builder.particle(baker.findSprite(textures, TextureSlot.PARTICLE.getId())); + // Add the baked quads (call as many times as necessary) + builder.addUnculledFace(...) // or addCulledFace(Direction, BakedQuad) + // Create the baked model + return builder.build(additionalProperties.getOrDefault(NeoForgeModelProperties.RENDER_TYPE, RenderTypeGroup.EMPTY)); } // Method responsible for correctly resolving parent properties. Required if this model loads any nested models or reuses the vanilla loader on itself (see below). @Override - public void resolveDependencies(UnbakedModel.Resolver resolver, IGeometryBakingContext context) { - // UnbakedModel#resolveDependencies - } - - // Gets the name of the components that can be configured via IGeometryBakingContext - // Usually empty unless there are child components - @Override - public Set getConfigurableComponentNames() { - return Set.of(); - } -} - -// BakedModelWrapper can be used as well to return default values for most methods, allowing you to only override what actually needs to be overridden. -public class MyDynamicModel implements IDynamicBakedModel { - // Material of the missing texture. Its sprite can be used as a fallback when needed. - private static final Material MISSING_TEXTURE = - new Material(TextureAtlas.LOCATION_BLOCKS, MissingTextureAtlasSprite.getLocation()); - - // Attributes for use in the methods below. Optional, the methods may also use constant values if applicable. - private final boolean useAmbientOcclusion; - private final boolean isGui3d; - private final boolean usesBlockLight; - private final TextureAtlasSprite particle; - private final BakedOverrides overrides; - - // The constructor does not require any parameters other than the ones for instantiating the final fields. - // It may specify any additional parameters to store in fields you deem necessary for your model to work. - public MyDynamicModel(boolean useAmbientOcclusion, boolean isGui3d, boolean usesBlockLight, TextureAtlasSprite particle, BakedOverrides overrides) { - this.useAmbientOcclusion = useAmbientOcclusion; - this.isGui3d = isGui3d; - this.usesBlockLight = usesBlockLight; - this.particle = particle; - this.overrides = overrides; - } - - // Use our attributes. Refer to the article on baked models for more information on the method's effects. - @Override - public boolean useAmbientOcclusion() { - return useAmbientOcclusion; - } - - @Override - public boolean isGui3d() { - return isGui3d; - } - - @Override - public boolean usesBlockLight() { - return usesBlockLight; - } - - @Override - public TextureAtlasSprite getParticleIcon() { - // Return MISSING_TEXTURE.sprite() if you don't need a particle, e.g. when in an item model context. - return particle; - } - - @Override - public BakedOverrides overrides() { - // Return BakedOverrides.EMPTY when in a block model context. - return overrides; - } - - // Override this to true if you want to use a custom block entity renderer instead of the default renderer. - @Override - public boolean isCustomRenderer() { - return false; + public void resolveDependencies(ResolvableModel.Resolver resolver) { + // ResolvableModel.Resolver#resolve } - // This is where the magic happens. Return a list of the quads to render here. Parameters are: - // - The blockstate being rendered. May be null if rendering an item. - // - The side being culled against. May be null, which means quads that cannot be occluded should be returned. - // - A client-bound random source you can use for randomizing stuff. - // - The extra data to use. Originates from a block entity (if present), or from BakedModel#getModelData(). - // - The render type for which quads are being requested. - // NOTE: This may be called many times in quick succession, up to several times per block. - // This should be as fast as possible and use caching wherever applicable. + // Add properties to the context map used for baking @Override - public List getQuads(@Nullable BlockState state, @Nullable Direction side, RandomSource rand, ModelData extraData, @Nullable RenderType renderType) { - List quads = new ArrayList<>(); - // add elements to the quads list as needed here - return quads; + public void fillAdditionalProperties(ContextMap.Builder propertiesBuilder) { + super.fillAdditionalProperties(propertiesBuilder); + // Add additional properties below by calling withParameter(ContextKey, T) } } ``` @@ -351,8 +186,15 @@ When all is done, don't forget to actually register your loader, otherwise all t ```java // Client-side mod bus event handler @SubscribeEvent -public static void registerGeometryLoaders(ModelEvent.RegisterGeometryLoaders event) { - event.register(MyGeometryLoader.ID, MyGeometryLoader.INSTANCE); +public static void registerGeometryLoaders(ModelEvent.RegisterLoaders event) { + event.register(MyUnbakedModelLoader.ID, MyUnbakedModelLoader.INSTANCE); +} + +// If you are caching data in the model loader: +// Client-side mod bus event handler +@SubscribeEvent +public static void onRegisterReloadListeners(RegisterClientReloadListenersEvent event) { + event.registerReloadListener(MyUnbakedModelLoader.INSTANCE); } ``` @@ -361,23 +203,26 @@ public static void registerGeometryLoaders(ModelEvent.RegisterGeometryLoaders ev Of course, we can also [datagen] our models. To do so, we need a class that extends `CustomLoaderBuilder`: ```java -// This assumes a block model. Use ItemModelBuilder as the generic parameter instead -// if you're making a custom item model. -public class MyLoaderBuilder extends CustomLoaderBuilder { - public MyLoaderBuilder(BlockModelBuilder parent, ExistingFileHelper existingFileHelper) { +public class MyLoaderBuilder extends CustomLoaderBuilder { + public MyLoaderBuilder() { super( // Your model loader's id. - MyGeometryLoader.ID, - // The parent builder we use. This is always the first constructor parameter. - parent, - // The existing file helper we use. This is always the second constructor parameter. - existingFileHelper, + MyUnbakedModelLoader.ID, // Whether the loader allows inline vanilla elements as a fallback if the loader is absent. false ); } // Add fields and setters for the fields here. The fields can then be used below. + + @Override + protected CustomLoaderBuilder copyInternal() { + // Create a new instance of your loader builder and copy the properties from this builder + // to the new instance. + MyLoaderBuilder builder = new MyLoaderBuilder(); + // builder. = this.; + return builder; + } // Serialize the model to JSON. @Override @@ -392,65 +237,84 @@ public class MyLoaderBuilder extends CustomLoaderBuilder { To use this loader builder, do the following during block (or item) [model datagen][modeldatagen]: ```java -// This assumes a BlockStateProvider. Use getBuilder("my_cool_block") directly in an ItemModelProvider. -// The parameter for customLoader() is a BiFunction. The parameters of the BiFunction -// are the result of the getBuilder() call and the provider's ExistingFileHelper. -MyLoaderBuilder loaderBuilder = models().getBuilder("my_cool_block").customLoader(MyLoaderBuilder::new); +// This assumes an extension of ModelProvider and a DeferredBlock EXAMPLE_BLOCK. +// The parameter for customLoader() is a Supplier to construct the builder and a Consumer to set to associated properties. +@Override +protected void registerModels(BlockModelGenerators blockModels, ItemModelGenerators itemModels) { + blockModels.createTrivialBlock( + // The block to generate the model for + EXAMPLE_BLOCK.get(), + TexturedModel.createDefault( + // A mapping used to get the textures + block -> new TextureMapping().put( + TextureSlot.ALL, TextureMapping.getBlockTexture(block) + ), + // The model template builder used to create the JSON + ExtendedModelTemplateBuilder.builder() + // Say we are using a custom model loader + .customLoader(MyLoaderBuilder::new, loader -> { + // Set any required fields here + }) + // Textures required by the model + .requiredTextureSlot(TextureSlot.ALL) + // Call build once complete + .build() + ) + ) +} ``` -Then, call your field setters on the `loaderBuilder`. - #### Visibility -The default implementation of `CustomLoaderBuilder` holds methods for applying visibility. You may choose to use or ignore the `visibility` property in your model loader. Currently, only the [composite model loader][composite] makes use of this property. +The default implementation of `CustomLoaderBuilder` holds methods for applying visibility. You may choose to use or ignore the `visibility` property in your model loader. Currently, only the [composite model loader][composite] and [OBJ loader][obj] make use of this property. ### Reusing the Default Model Loader -In some contexts, it makes sense to reuse the vanilla model loader and just building your model logic on top of that instead of outright replacing it. We can do so using a neat trick: In the model loader, we simply remove the `loader` property and send it back to the model deserializer, tricking it into thinking that it is a regular model now. We then pass it to the geometry, bake the model geometry there (like the default geometry handler would) and pass it along to the dynamic model, where we can then use the model's quads in whatever way we want: +In some contexts, it makes sense to reuse the vanilla model loader and just building your model logic on top of that instead of outright replacing it. We can do so using a neat trick: in the model loader, we simply remove the `loader` property and send it back to the model deserializer, tricking it into thinking that it is a regular unbaked model now. Then, we bake the model during the baking process to pass along to the baked model, where we can the use the model's quads in whatever way we want. + +:::note +The following example should only be used if the file only contains a single model JSON, whether on the top-level or nested within some object. If multiple models need to be loaded, then the JSON should either contain references to the other JSON files, or the children objects should be deserialized into `UnbakedModel`s and baked via `UnbakedModel#bakeWithTopModelValues`. Using references is the recommended method. +::: ```java -public class MyGeometryLoader implements IGeometryLoader { - public static final MyGeometryLoader INSTANCE = new MyGeometryLoader(); - public static final ResourceLocation ID = ResourceLocation.fromNamespaceAndPath(...); - - private MyGeometryLoader() {} +public class MyUnbakedModelLoader implements UnbakedModelLoader { + public static final MyUnbakedModelLoader INSTANCE = new MyUnbakedModelLoader(); + public static final ResourceLocation ID = ResourceLocation.fromNamespaceAndPath("examplemod", "my_custom_loader"); + private MyUnbakedModelLoader() {} + @Override - public MyGeometry read(JsonObject jsonObject, JsonDeserializationContext context) throws JsonParseException { - // Trick the deserializer into thinking this is a normal model by removing the loader field and then passing it back into the deserializer. + public MyUnbakedModel read(JsonObject jsonObject, JsonDeserializationContext context) throws JsonParseException { + // Trick the deserializer into thinking this is a normal model by removing the loader field + // Then, pass it to the deserializer. jsonObject.remove("loader"); - BlockModel base = context.deserialize(jsonObject, BlockModel.class); - // other stuff here if needed - return new MyGeometry(base); + UnbakedModel model = context.deserialize(jsonObject, UnbakedModel.class); + return new MyUnbakedModel(model, /* other parameters here */); } } -public class MyGeometry implements IUnbakedGeometry { - private final BlockModel base; +// We extend the delegate class as that stores the wrapped model +public class MyUnbakedModel extends DelegateUnbakedModel { - // Store the block model for usage below. - public MyGeometry(BlockModel base) { - this.base = base; + // Store the model for use below + public MyUnbakedModel(UnbakedModel model, /* other parameters here */) { + super(model); } @Override - public BakedModel bake(IGeometryBakingContext context, ModelBaker baker, Function spriteGetter, ModelState modelState, List overrides) { - BakedModel bakedBase = this.base.bake(baker, spriteGetter, modelState); - return new MyDynamicModel(bakedBase, /* other parameters here */); - } - - @Override - public void resolveDependencies(UnbakedModel.Resolver resolver, IGeometryBakingContext context) { - this.base.resolveDependencies(resolver); + public BakedModel bake(TextureSlots textures, ModelBaker baker, ModelState modelState, boolean useAmbientOcclusion, boolean usesBlockLight, ItemTransforms itemTransforms, ContextMap additionalProperties) { + BakedModel base = super.bake(textures, baker, modelState, useAmbientOcclusion, usesBlockLight, itemTransforms, additionalProperties); + return new MyBakedModel(base, /* other parameters here */); } } -public class MyDynamicModel implements IDynamicBakedModel { - private final BakedModel base; +// We extend the delegate class as that stores the wrapped model +public class MyDynamicModel extends DelegateBakedModel { + // other fields here public MyDynamicModel(BakedModel base, /* other parameters here */) { - this.base = base; + super(base); // set other fields here } @@ -460,29 +324,19 @@ public class MyDynamicModel implements IDynamicBakedModel { public List getQuads(@Nullable BlockState state, @Nullable Direction side, RandomSource rand, ModelData extraData, @Nullable RenderType renderType) { List quads = new ArrayList<>(); // Add the base model's quads. Can also do something different with the quads here, depending on what you need. - quads.addAll(this.base.getQuads(state, side, rand, extraData, renderType)); + quads.addAll(this.parent.getQuads(state, side, rand, extraData, renderType)); // add other elements to the quads list as needed here return quads; } - - // Apply the base model's transforms to our model as well. - @Override - public BakedModel applyTransform(ItemDisplayContext transformType, PoseStack poseStack, boolean applyLeftHandTransform) { - return this.base.applyTransform(transformType, poseStack, applyLeftHandTransform); - } } ``` [bakedmodel]: bakedmodel.md -[ber]: ../../../blockentities/ber.md -[bewlr]: ../../../blockentities/ber.md#blockentitywithoutlevelrenderer +[citems]: items.md [composite]: #composite-model [datagen]: ../../index.md#data-generation -[elements]: index.md#elements [event]: ../../../concepts/events.md#registering-an-event-handler -[model]: index.md#specification +[itemcomposite]: #TODO [modeldatagen]: datagen.md -[rendertype]: index.md#render-types +[obj]: #obj-model [sides]: ../../../concepts/sides.md -[tint]: index.md#tinting -[transform]: index.md#root-transforms diff --git a/docs/resources/client/particles.md b/docs/resources/client/particles.md index 51ec1145e..c060f7975 100644 --- a/docs/resources/client/particles.md +++ b/docs/resources/client/particles.md @@ -127,7 +127,7 @@ A mismatched list of sprite set particle factories and particle definition files ::: :::note -While particle descriptions must have providers registered a certain way, they are only used if the `ParticleRenderType` (set via `Particle#getRenderType`) uses the `TextureAtlas#LOCATION_PARTICLES` as the shader texture. For vanilla render types, these are `PARTICLE_SHEET_OPAQUE` and `PARTICLE_SHEET_TRANSLUCENT`. +While particle descriptions must have providers registered a certain way, they are only used if the `RenderType` of the `ParticleRenderType` (set via `Particle#getRenderType`) uses `TextureAtlas#LOCATION_PARTICLES` for the shader texture. For vanilla particle render types, these are `PARTICLE_SHEET_OPAQUE` and `PARTICLE_SHEET_TRANSLUCENT`. ::: ### Datagen @@ -137,8 +137,8 @@ Particle definition files can also be [datagenned][datagen] by extending `Partic ```java public class MyParticleDescriptionProvider extends ParticleDescriptionProvider { // Get the parameters from GatherDataEvent. - public AMParticleDefinitionsProvider(PackOutput output, ExistingFileHelper existingFileHelper) { - super(output, existingFileHelper); + public MyParticleDescriptionProvider(PackOutput output) { + super(output); } // Assumes that all the referenced particles actually exists. Replace "examplemod" with your mod id. @@ -173,12 +173,11 @@ Don't forget to add the provider to the `GatherDataEvent`: public static void gatherData(GatherDataEvent event) { DataGenerator generator = event.getGenerator(); PackOutput output = generator.getPackOutput(); - ExistingFileHelper existingFileHelper = event.getExistingFileHelper(); // other providers here generator.addProvider( event.includeClient(), - new MyParticleDescriptionProvider(output, existingFileHelper) + new MyParticleDescriptionProvider(output) ); } ``` diff --git a/docs/resources/client/sounds.md b/docs/resources/client/sounds.md index fc2781338..cc622497a 100644 --- a/docs/resources/client/sounds.md +++ b/docs/resources/client/sounds.md @@ -247,9 +247,9 @@ Sound files themselves can of course not be [datagenned][datagen], but `sounds.j ```java public class MySoundDefinitionsProvider extends SoundDefinitionsProvider { // Parameters can be obtained from GatherDataEvent. - public MySoundDefinitionsProvider(PackOutput output, ExistingFileHelper existingFileHelper) { + public MySoundDefinitionsProvider(PackOutput output) { // Use your actual mod id instead of "examplemod". - super(output, "examplemod", existingFileHelper); + super(output, "examplemod"); } @Override @@ -294,12 +294,11 @@ As with every data provider, don't forget to register the provider to the event: public static void gatherData(GatherDataEvent event) { DataGenerator generator = event.getGenerator(); PackOutput output = generator.getPackOutput(); - ExistingFileHelper existingFileHelper = event.getExistingFileHelper(); // other providers here generator.addProvider( event.includeClient(), - new MySoundDefinitionsProvider(output, existingFileHelper) + new MySoundDefinitionsProvider(output) ); } ``` diff --git a/docs/resources/index.md b/docs/resources/index.md index a74519b6c..40522d182 100644 --- a/docs/resources/index.md +++ b/docs/resources/index.md @@ -16,7 +16,23 @@ Assets, or client-side resources, are all resources that are only relevant on th NeoForge automatically collects all mod resource packs into the `Mod resources` pack, which sits at the bottom of the Selected Packs side in the resource packs menu. It is currently not possible to disable the `Mod resources` pack. However, resource packs that sit above the `Mod resources` pack override resources defined in a resource pack below them. This mechanic allows resource pack makers to override your mod's resources, and also allows mod developers to override Minecraft resources if needed. -Resource packs can contain [models][models], [blockstate files][bsfile], [textures][textures], [sounds][sounds], [particle definitions][particles] and [translation files][translations]. +Resource packs may contain folders with files affecting the following things: + +| Folder Name | Contents | +|---------------|-----------------------------------------| +| `atlases` | Texture Atlas Sources | +| `blockstates` | [Blockstate Files][bsfile] | +| `equipment` | [Equipment Info][equipment] | +| `font` | Font Definitions | +| `items` | [Client Items][citems] | +| `lang` | [Translation Files][translations] | +| `models` | [Models][models] | +| `particles` | [Particle Definitions][particles] | +| `post_effect` | Post Processing Screen Effects | +| `shaders` | Metadata, Fragement, and Vertex Shaders | +| `sounds` | [Sound Files][sounds] | +| `texts` | Miscellaneous Text files | +| `textures` | [Textures][textures] | ## Data @@ -32,7 +48,7 @@ There is currently no built-in way to apply a set of custom data packs to every Data packs may contain folders with files affecting the following things: -| Folder name | Contents | +| Folder Name | Contents | |-----------------------------------------------------------------------------------|------------------------------| | `advancement` | [Advancements][advancements] | | `damage_type` | [Damage types][damagetypes] | @@ -68,8 +84,7 @@ All data providers extend the `DataProvider` interface and usually require one m | Class | Method | Generates | Side | Notes | |------------------------------------------------------|----------------------------------|-------------------------------------------------------------------------|--------|-----------------------------------------------------------------------------------------------------------------| -| [`BlockStateProvider`][blockstateprovider] | `registerStatesAndModels()` | Blockstate files, block models | Client | | -| [`ItemModelProvider`][itemmodelprovider] | `registerModels()` | Item models | Client | | +| [`ModelProvider`][modelprovider] | `registerModels()` | Models, Blockstate Files, Client Items | Client | | | [`LanguageProvider`][langprovider] | `addTranslations()` | Translations | Client | Also requires passing the language in the constructor. | | [`ParticleDescriptionProvider`][particleprovider] | `addDescriptions()` | Particle definitions | Client | | | [`SoundDefinitionsProvider`][soundprovider] | `registerSounds()` | Sound definitions | Client | | @@ -105,7 +120,6 @@ public class MyDatagenHandler { // See below for more details on each of these. DataGenerator generator = event.getGenerator(); PackOutput output = generator.getPackOutput(); - ExistingFileHelper existingFileHelper = event.getExistingFileHelper(); CompletableFuture lookupProvider = event.getLookupProvider(); // Register the provider. @@ -128,7 +142,7 @@ The event offers some context for you to use: - `event.getGenerator()` returns the `DataGenerator` that you register the providers to. - `event.getPackOutput()` returns a `PackOutput` that is used by some providers to determine their file output location. -- `event.getExistingFileHelper()` returns an `ExistingFileHelper` that is used by providers for things that can reference other files (for example block models, which can specify a parent file). +- `event.getResourceManager(PackType)` returns a `ResourceManager` that can be used by providers to check for already existing files. - `event.getLookupProvider()` returns a `CompletableFuture` that is mainly used by tags and datagen registries to reference other, potentially not yet existing elements. - `event.includeClient()`, `event.includeServer()`, `event.includeDev()` and `event.includeReports()` are `boolean` methods that allow you to check whether specific command line arguments (see below) are enabled. @@ -176,21 +190,21 @@ runs { [advancementprovider]: server/advancements.md#data-generation [advancements]: server/advancements.md -[blockstateprovider]: client/models/datagen.md#block-model-datagen [bsfile]: client/models/index.md#blockstate-files [chattype]: https://minecraft.wiki/w/Chat_type +[citems]: client/models/items.md [codec]: ../datastorage/codecs.md [damagetypes]: server/damagetypes.md [datamap]: server/datamaps/index.md [datamapprovider]: server/datamaps/index.md#data-generation [datapackcmd]: https://minecraft.wiki/w/Commands/datapack [datapackprovider]: ../concepts/registries.md#data-generation-for-datapack-registries +[equipment]: ../items/armor.md#equipment-models [event]: ../concepts/events.md [eventhandler]: ../concepts/events.md#registering-an-event-handler [function]: https://minecraft.wiki/w/Function_(Java_Edition) [glm]: server/loottables/glm.md [glmprovider]: server/loottables/glm.md#datagen -[itemmodelprovider]: client/models/datagen.md#item-model-datagen [itemmodifier]: https://minecraft.wiki/w/Item_modifier [langprovider]: client/i18n.md#datagen [lifecycle]: ../concepts/events.md#the-mod-lifecycle @@ -200,6 +214,7 @@ runs { [mcwiki]: https://minecraft.wiki [mcwikidatapacks]: https://minecraft.wiki/w/Data_pack [mcwikiresourcepacks]: https://minecraft.wiki/w/Resource_pack +[modelprovider]: client/models/datagen.md [models]: client/models/index.md [packmcmeta]: #packmcmeta [packmcmetadatapack]: https://minecraft.wiki/w/Data_pack#pack.mcmeta diff --git a/docs/resources/server/advancements.md b/docs/resources/server/advancements.md index 3ca5143fe..c2ea736b0 100644 --- a/docs/resources/server/advancements.md +++ b/docs/resources/server/advancements.md @@ -152,11 +152,7 @@ public void performExampleAction(ServerPlayer player, additionalContextParameter ## Data Generation -Advancements can be [datagenned][datagen] using an `AdvancementProvider`. An `AdvancementProvider` accepts a list of `AdvancementGenerator`s, which actually generate the advancements using `Advancement.Builder`. - -:::warning -Both Minecraft and NeoForge provide a class named `AdvancementProvider`, located at `net.minecraft.data.advancements.AdvancementProvider` and `net.neoforged.neoforge.common.data.AdvancementProvider`, respectively. The NeoForge class is an improvement on the one Minecraft provides, and should always be used in favor of the Minecraft one. The following documentation always assumes usage of the NeoForge `AdvancementProvider` class. -::: +Advancements can be [datagenned][datagen] using an `AdvancementProvider`. An `AdvancementProvider` accepts a list of `AdavancementSubProviders`s, which actually generate the advancements using `Advancement.Builder`. To start, create an instance of `AdvancementProvider` within `GatherDataEvent`: @@ -166,12 +162,11 @@ public static void gatherData(GatherDataEvent event) { DataGenerator generator = event.getGenerator(); PackOutput output = generator.getPackOutput(); CompletableFuture lookupProvider = event.getLookupProvider(); - ExistingFileHelper existingFileHelper = event.getExistingFileHelper(); generator.addProvider( event.includeServer(), new AdvancementProvider( - output, lookupProvider, existingFileHelper, + output, lookupProvider, // Add generators here List.of(...) ) @@ -185,10 +180,10 @@ Now, the next step is to fill the list with our generators. To do so, we can eit ```java // Class example -public class MyAdvancementGenerator extends AdvancementProvider.AdvancementGenerator { +public class MyAdvancementGenerator implements AdvancementSubProvider { @Override - public void generate(HolderLookup.Provider registries, Consumer saver, ExistingFileHelper existingFileHelper) { + public void generate(HolderLookup.Provider registries, Consumer saver) { // Generate your advancements here. } } @@ -196,8 +191,8 @@ public class MyAdvancementGenerator extends AdvancementProvider.AdvancementGener // Method Example public class ExampleClass { - // Matches the parameters provided by AdvancementProvider.AdvancementGenerator#generate - public static void generateExampleAdvancements(HolderLookup.Provider registries, Consumer saver, ExistingFileHelper existingFileHelper) { + // Matches the parameters provided by AdvancementSubProvider#generate + public static void generateExampleAdvancements(HolderLookup.Provider registries, Consumer saver) { // Generate your advancements here. } } @@ -206,7 +201,7 @@ public class ExampleClass { generator.addProvider( event.includeServer(), new AdvancementProvider( - output, lookupProvider, existingFileHelper, + output, lookupProvider, // Add generators here List.of( // Add an instance of our generator to the list parameter. This can be done as many times as you want. @@ -276,7 +271,7 @@ builder.requirements(AdvancementRequirements.allOf(List.of("pickup_dirt"))); // Save the advancement to disk, using the given resource location. This returns an AdvancementHolder, // which may be stored in a variable and used as a parent by other advancement builders. -builder.save(saver, ResourceLocation.fromNamespaceAndPath("examplemod", "example_advancement"), existingFileHelper); +builder.save(saver, ResourceLocation.fromNamespaceAndPath("examplemod", "example_advancement")); ``` [codec]: ../../datastorage/codecs.md diff --git a/docs/resources/server/conditions.md b/docs/resources/server/conditions.md index bc00f36be..4f3c3e912 100644 --- a/docs/resources/server/conditions.md +++ b/docs/resources/server/conditions.md @@ -19,6 +19,17 @@ Most JSON files can optionally declare a `neoforge:conditions` block in the root } ``` +:::note +If the value to load is not a map/object, it is stored within `neoforge:value`: + +```json5 +{ + "neoforge:conditions": [ /* ...*/ ], + "neoforge:value": 2 // The value to load +} +``` +::: + For example, if we want to only load our file if a mod with id `examplemod` is present, our file would look something like this: ```json5 @@ -42,22 +53,22 @@ Most vanilla files have been patched to use conditions using the `ConditionalCod ## Built-In Conditions -### `neoforge:true` and `neoforge:false` +### `neoforge:always` and `neoforge:never` These consist of no data and return the expected value. ```json5 { - // Will always return true (or false for "neoforge:false") - "type": "neoforge:true" + // Will always return true (or false for "neoforge:never") + "type": "neoforge:always" } ``` :::tip -Using the `neoforge:false` condition very cleanly allows disabling any data file. Simply place a file with the following contents at the needed location: +Using the `neoforge:never` condition very cleanly allows disabling any data file. Simply place a file with the following contents at the needed location: ```json5 -{"neoforge:conditions":[{"type":"neoforge:false"}]} +{"neoforge:conditions":[{"type":"neoforge:never"}]} ``` Disabling files this way will **not** cause log spam. @@ -108,46 +119,66 @@ This condition returns true if a mod with the given mod id is loaded, and false } ``` -### `neoforge:item_exists` +### `neoforge:registered` -This condition returns true if an item with the given registry name has been registered, and false otherwise. +This condition returns true if an object in a specific registry with the given registry name has been registered, and false otherwise. ```json5 { - "type": "neoforge:item_exists", + "type": "neoforge:registered", + // The registry to check the value for + // Defaults to `minecraft:item` + "registry": "minecraft:item", // Returns true if "examplemod:example_item" has been registered - "item": "examplemod:example_item" + "value": "examplemod:example_item" } ``` ### `neoforge:tag_empty` -This condition returns true if the given item [tag] is empty, and false otherwise. +This condition returns true if the given registry [tag] is empty, and false otherwise. ```json5 { "type": "neoforge:tag_empty", - // Returns true if "examplemod:example_tag" is an empty item tag + // The registry to check the tag for + // Defaults to `minecraft:item` + "registry": "minecraft:item", + // Returns true if "examplemod:example_tag" is an empty tag "tag": "examplemod:example_tag" } ``` +### `neoforge:feature_flags_enabled` + +This condition returns true if the provided [feature flags][flags] are enabled, and false otherwise. + +```json5 +{ + "type": "neoforge:feature_flags_enabled", + // Returns true if the "examplemod:example_feature" is enabled + "flags": [ + "examplemod:example_feature" + ] +} +``` + ## Creating Custom Conditions Custom conditions can be created by implementing `ICondition` and its `#test(IContext)` method, as well as creating a [map codec][codec] for it. The `IContext` parameter in `#test` has access to some parts of the game state. Currently, this only allows you to query tags from registries. Some objects with conditions may be loaded earlier than tags, in which case the context will be `IContext.EMPTY` and not contain any tag information at all. -For example, let's assume we want to reimplement the `tag_empty` condition, but for entity type tags instead of item tags, then our condition would look something like this: +For example, let's assume we want to implement an `xor` condition, then our condition would look something like this: ```java -// This class is basically a boiled-down copy of TagEmptyCondition, adjusted for entity types instead of items. -public record EntityTagEmptyCondition(TagKey> tag) implements ICondition { - public static final MapCodec CODEC = RecordCodecBuilder.mapCodec(inst -> inst.group( - TagKey.codec(Registries.ENTITY_TYPE).fieldOf("tag").forGetter(EntityTagEmptyCondition::tag) - ).apply(inst, EntityTagEmptyCondition::new)); +public record XorCondition(ICondition first, ICondition second) implements ICondition { + public static final MapCodec CODEC = RecordCodecBuilder.mapCodec(inst -> inst.group( + ICondition.CODEC.fieldOf("first").forGetter(XorCondition::first), + ICondition.CODEC.fieldOf("second").forGetter(XorCondition::second) + ).apply(inst, XorCondition::new)); @Override public boolean test(ICondition.IContext context) { - return !context.isTagLoaded(this.tag()); + return this.first.test(context) ^ this.second.test(context); } @Override @@ -163,8 +194,8 @@ Conditions are a registry of codecs. As such, we need to [register] our codec, l public static final DeferredRegister> CONDITION_CODECS = DeferredRegister.create(NeoForgeRegistries.Keys.CONDITION_CODECS, ExampleMod.MOD_ID); -public static final Supplier> ENTITY_TAG_EMPTY = - CONDITION_CODECS.register("entity_tag_empty", () -> EntityTagEmptyCondition.CODEC); +public static final Supplier> XOR = + CONDITION_CODECS.register("xor", () -> XorCondition.CODEC); ``` And then, we can use our condition in some data file (assuming we registered the condition under the `examplemod` namespace): @@ -173,8 +204,15 @@ And then, we can use our condition in some data file (assuming we registered the { "neoforge:conditions": [ { - "type": "examplemod:entity_tag_empty", - "tag": "minecraft:zombies" + "type": "examplemod:xor", + "first": { + // Either this condition is true + "type": "..." + }, + "second": { + // Or this condition, not both! + "type": "..." + } } ], // The rest of the data file @@ -190,11 +228,12 @@ While any datapack JSON file can use load conditions, only a few [data providers - [`DataMapProvider`][datamapprovider] - [`GlobalLootModifierProvider`][glmprovider] -For the conditions themselves, the `IConditionBuilder` interface provides static helpers for each of the built-in condition types that return the corresponding `ICondition`s. +For the conditions themselves, the `NeoForgeConditions` class provides static helpers for each of the built-in condition types that return the corresponding `ICondition`s. [codec]: ../../datastorage/codecs [datagen]: ../index.md#data-generation [datamapprovider]: datamaps/index.md#data-generation +[flags]: ../../advanced/featureflags.md [glmprovider]: loottables/glm.md#datagen [loottable]: loottables/index.md [recipeprovider]: recipes/index.md#data-generation diff --git a/docs/resources/server/datamaps/builtin.md b/docs/resources/server/datamaps/builtin.md index 8ae8b90df..9aba8cdb1 100644 --- a/docs/resources/server/datamaps/builtin.md +++ b/docs/resources/server/datamaps/builtin.md @@ -102,7 +102,7 @@ Example: ## `neoforge:oxidizables` -Allows configuring oxidation stages, as a replacement for `WeatheringCopper#NEXT_BY_BLOCK` (which will be ignored in 1.21.2). This data map is also used to build a reverse deoxidation map (for scraping with an axe). It is located at `neoforge/data_maps/block/oxidizables.json` and its objects have the following structure: +Allows configuring oxidation stages, as a replacement for `WeatheringCopper#NEXT_BY_BLOCK`. This data map is also used to build a reverse deoxidation map (for scraping with an axe). It is located at `neoforge/data_maps/block/oxidizables.json` and its objects have the following structure: ```json5 { @@ -202,7 +202,7 @@ Example: ## `neoforge:waxables` -Allows configuring the block a block will turn into when waxed (right clicked with a honeycomb), as a replacement for `HoneycombItem#WAXABLES` (which will be ignored in 1.21.2). This data map is also used to build a reverse dewaxing map (for scraping with an axe). It is located at `neoforge/data_maps/block/waxables.json` and its objects have the following structure: +Allows configuring the block a block will turn into when waxed (right clicked with a honeycomb), as a replacement for `HoneycombItem#WAXABLES`. This data map is also used to build a reverse dewaxing map (for scraping with an axe). It is located at `neoforge/data_maps/block/waxables.json` and its objects have the following structure: ```json5 { diff --git a/docs/resources/server/loottables/lootfunctions.md b/docs/resources/server/loottables/lootfunctions.md index 0cc4df8cf..30d35aa94 100644 --- a/docs/resources/server/loottables/lootfunctions.md +++ b/docs/resources/server/loottables/lootfunctions.md @@ -769,17 +769,48 @@ It is currently not possible to create this function during datagen. ## `minecraft:set_custom_model_data` -Sets the custom model data of the result item stack. +Sets the custom model data of the resulting item stack to use during rendering. ```json5 { "function": "minecraft:set_custom_model_data", - // The custom model data value to use. This can also be a number provider. - "value": 4 + // The float used during item model selection for the specified index + // for a client item with a `minecraft:custom_model_data` range property. + "floats": [ + // Will select the model where the property is less than 0.5 when "index": 0 + 0.5, + // Will select the model where the property is less than 0.25 when "index": 1 + 0.25 + ], + // The boolean used during item model selection for the specified index + // for a client item with a `minecraft:custom_model_data` condition property. + "flags": [ + // Will select the model where the condition is true when "index": 0 + true, + // Will select the model where the condition is false when "index": 1 + false + ], + // The string used during item model selection for the specified index + // for a client item with a `minecraft:custom_model_data` select property. + "strings": [ + // Will select the model with the "dummy" case when "index": 0 + "dummy", + // Will select the model with the "example" case when "index": 1 + "example" + ], + // The tint color to use for the specified index for a client item + // with a `minecraft:custom_model_data` tint source. + // 0xFF000000 is ORed with this value for an opaque color. + "colors": [ + // Blue when "index": 0 + 255, + // Green when "index": 1 + 65280 + ] } ``` -It is currently not possible to create this function during datagen. +During datagen, call `new SetCustomModelDataFunction()` with the list of conditions, optional number providers, booleans, strings, and number providers to construct the associated object. ## `minecraft:filtered` diff --git a/docs/resources/server/recipes/custom.md b/docs/resources/server/recipes/custom.md index 81c34dafc..e9ca30468 100644 --- a/docs/resources/server/recipes/custom.md +++ b/docs/resources/server/recipes/custom.md @@ -86,7 +86,7 @@ public class RightClickBlockRecipe implements Recipe { ## Recipe Book Categories -A `RecipeBookCategory` simply defines a group to display this recipe within in a recipe book. For example, an iron pickaxe crafting recipe would show up in the `RecipeBookCategories#CARFTING_EQUIPMENT` while a cooked cod recipe would show up in `#FURNANCE_FOOD` or `#SMOKER_FOOD`. Each recipe has one associated `RecipeBookCategory`. The vanilla categories can be found in `RecipeBookCategories`. +A `RecipeBookCategory` simply defines a group to display this recipe within in a recipe book. For example, an iron pickaxe crafting recipe would show up in the `RecipeBookCategories#CRAFTING_EQUIPMENT` while a cooked cod recipe would show up in `#FURNANCE_FOOD` or `#SMOKER_FOOD`. Each recipe has one associated `RecipeBookCategory`. The vanilla categories can be found in `RecipeBookCategories`. :::note There are two cooked cod recipes, one for the furnance and one for the smoker. The furnace and smoker recipes have different book categories. diff --git a/docs/resources/server/tags.md b/docs/resources/server/tags.md index 7b013db02..5222507a5 100644 --- a/docs/resources/server/tags.md +++ b/docs/resources/server/tags.md @@ -146,8 +146,8 @@ For the sake of example, let's assume that we want to generate block tags. (All ```java public class MyBlockTagsProvider extends BlockTagsProvider { // Get parameters from GatherDataEvent. - public MyBlockTagsProvider(PackOutput output, CompletableFuture lookupProvider, ExistingFileHelper existingFileHelper) { - super(output, lookupProvider, ExampleMod.MOD_ID, existingFileHelper); + public MyBlockTagsProvider(PackOutput output, CompletableFuture lookupProvider) { + super(output, lookupProvider, ExampleMod.MOD_ID); } // Add your tag entries here. @@ -155,30 +155,30 @@ public class MyBlockTagsProvider extends BlockTagsProvider { protected void addTags(HolderLookup.Provider lookupProvider) { // Create a tag builder for our tag. This could also be e.g. a vanilla or NeoForge tag. this.tag(MY_TAG) - // Add entries. This is a vararg parameter. - // Non-intrinsic providers must provide ResourceKeys here instead of the actual objects. - .add(Blocks.DIRT, Blocks.COBBLESTONE) - // Add optional entries that will be ignored if absent. This example uses Botania's Pure Daisy. - // Unlike #add, this is not a vararg parameter. - .addOptional(ResourceLocation.fromNamespaceAndPath("botania", "pure_daisy")) - // Add a tag entry. - .addTag(BlockTags.PLANKS) - // Add multiple tag entries. This is a vararg parameter. - // Can cause unchecked warnings that can safely be suppressed. - .addTags(BlockTags.LOGS, BlockTags.WOODEN_SLABS) - // Add an optional tag entry that will be ignored if absent. - .addOptionalTag(ResourceLocation.fromNamespaceAndPath("c", "ingots/tin")) - // Add multiple optional tag entries. This is a vararg parameter. - // Can cause unchecked warnings that can safely be suppressed. - .addOptionalTags(ResourceLocation.fromNamespaceAndPath("c", "nuggets/tin"), ResourceLocation.fromNamespaceAndPath("c", "storage_blocks/tin")) - // Set the replace property to true. - .replace() - // Set the replace property back to false. - .replace(false) - // Remove entries. This is a vararg parameter. Accepts either resource locations, resource keys, - // tag keys, or (intrinsic providers only) direct values. - // Can cause unchecked warnings that can safely be suppressed. - .remove(ResourceLocation.fromNamespaceAndPath("minecraft", "crimson_slab"), ResourceLocation.fromNamespaceAndPath("minecraft", "warped_slab")); + // Add entries. This is a vararg parameter. + // Non-intrinsic providers must provide ResourceKeys here instead of the actual objects. + .add(Blocks.DIRT, Blocks.COBBLESTONE) + // Add optional entries that will be ignored if absent. This example uses Botania's Pure Daisy. + // Unlike #add, this is not a vararg parameter. + .addOptional(ResourceLocation.fromNamespaceAndPath("botania", "pure_daisy")) + // Add a tag entry. + .addTag(BlockTags.PLANKS) + // Add multiple tag entries. This is a vararg parameter. + // Can cause unchecked warnings that can safely be suppressed. + .addTags(BlockTags.LOGS, BlockTags.WOODEN_SLABS) + // Add an optional tag entry that will be ignored if absent. + .addOptionalTag(ResourceLocation.fromNamespaceAndPath("c", "ingots/tin")) + // Add multiple optional tag entries. This is a vararg parameter. + // Can cause unchecked warnings that can safely be suppressed. + .addOptionalTags(ResourceLocation.fromNamespaceAndPath("c", "nuggets/tin"), ResourceLocation.fromNamespaceAndPath("c", "storage_blocks/tin")) + // Set the replace property to true. + .replace() + // Set the replace property back to false. + .replace(false) + // Remove entries. This is a vararg parameter. Accepts either resource locations, resource keys, + // tag keys, or (intrinsic providers only) direct values. + // Can cause unchecked warnings that can safely be suppressed. + .remove(ResourceLocation.fromNamespaceAndPath("minecraft", "crimson_slab"), ResourceLocation.fromNamespaceAndPath("minecraft", "warped_slab")); } } ``` @@ -222,14 +222,13 @@ Like all data providers, add each tag provider to the `GatherDataEvent`: ```java @SubscribeEvent public static void gatherData(GatherDataEvent event) { - PackOutput output = generator.getPackOutput(); + PackOutput output = event.getGenerator().getPackOutput(); CompletableFuture lookupProvider = event.getLookupProvider(); - ExistingFileHelper existingFileHelper = event.getExistingFileHelper(); // other providers here event.getGenerator().addProvider( event.includeServer(), - new MyBlockTagsProvider(output, lookupProvider, existingFileHelper) + new MyBlockTagsProvider(output, lookupProvider) ); } ``` @@ -248,9 +247,9 @@ To create a custom tag provider for a custom [registry], or for a vanilla or Neo ```java public class MyRecipeTypeTagsProvider extends TagsProvider> { // Get parameters from GatherDataEvent. - public MyRecipeTypeTagsProvider(PackOutput output, CompletableFuture lookupProvider, ExistingFileHelper existingFileHelper) { + public MyRecipeTypeTagsProvider(PackOutput output, CompletableFuture lookupProvider) { // Second parameter is the registry key we are generating the tags for. - super(output, Registries.RECIPE_TYPE, lookupProvider, ExampleMod.MOD_ID, existingFileHelper); + super(output, Registries.RECIPE_TYPE, lookupProvider, ExampleMod.MOD_ID); } @Override @@ -261,16 +260,16 @@ public class MyRecipeTypeTagsProvider extends TagsProvider> { If desirable and applicable, you can also extend `IntrinsicHolderTagsProvider` instead of `TagsProvider`, allowing you to pass in objects directly rather than just their resource keys. This additionally requires a function parameter that returns a resource key for a given object. Using attribute tags as an example: ```java -public class MyAttributeTagsProvider extends TagsProvider { +public class MyAttributeTagsProvider extends IntrinsicHolderTagsProvider { // Get parameters from GatherDataEvent. - public MyAttributeTagsProvider(PackOutput output, CompletableFuture lookupProvider, ExistingFileHelper existingFileHelper) { + public MyAttributeTagsProvider(PackOutput output, CompletableFuture lookupProvider) { super(output, - Registries.ATTRIBUTE, - lookupProvider, - // A function that, given an Attribute, returns a ResourceKey. - attribute -> BuiltInRegistries.ATTRIBUTE.getResourceKey(attribute).orElseThrow(), - ExampleMod.MOD_ID, - existingFileHelper); + Registries.ATTRIBUTE, + lookupProvider, + // A function that, given an Attribute, returns a ResourceKey. + attribute -> BuiltInRegistries.ATTRIBUTE.getResourceKey(attribute).orElseThrow(), + ExampleMod.MOD_ID + ); } // Attributes can now be used here directly, instead of just their resource keys. diff --git a/docs/worldgen/biomemodifier.md b/docs/worldgen/biomemodifier.md index e4aa98d59..9034534c3 100644 --- a/docs/worldgen/biomemodifier.md +++ b/docs/worldgen/biomemodifier.md @@ -685,21 +685,21 @@ There may be times when a biome modifier needs to target a biome that is not alw ```json5 { - "replace": false, - "values": [ - { - "id": "minecraft:pale_garden", - "required": false - } - ] + "replace": false, + "values": [ + { + "id": "minecraft:pale_garden", + "required": false + } + ] } ``` -Using that biome tag for a biome modifier will now not crash if the biome is not registered. A use case for this is the Pale Garden biome in 1.21.3. That biome is only created when you turn on the Winter Drop datapack in-game. Otherwise, the biome does not exist in the biome registry at all. Another use case can be to target modded biomes while still functioning when the mods adding these biomes are not present. +Using that biome tag for a biome modifier will now not crash if the biome is not registered. One such use case is the Pale Garden biome, which is only created in 1.21.3 when the Winter Drop datapack is turned on. Otherwise, the biome does not exist in the biome registry at all. Another use case can be to target modded biomes while still functioning when the mods adding these biomes are not present. To datagen optional entries for biome tags, the datagen code would look something along these lines: -```json5 +```java // In a TagsProvider subclass // Assume we have some example TagKey OPTIONAL_BIOMES_TAG @Override diff --git a/docusaurus.config.js b/docusaurus.config.js index 1196920f9..befefbf43 100644 --- a/docusaurus.config.js +++ b/docusaurus.config.js @@ -47,6 +47,9 @@ const config = { includeCurrentVersion: true, versions: { current: { + label: "1.21.4", + }, + "1.21.3": { label: "1.21.2 - 1.21.3", }, "1.21.1": { diff --git a/versioned_docs/version-1.20.4/concepts/registries.md b/versioned_docs/version-1.20.4/concepts/registries.md index 135d30095..c053ccb07 100644 --- a/versioned_docs/version-1.20.4/concepts/registries.md +++ b/versioned_docs/version-1.20.4/concepts/registries.md @@ -39,7 +39,7 @@ We can then add our registry entries as static final fields (see [the article on ```java public static final DeferredHolder EXAMPLE_BLOCK = BLOCKS.register( - "example_block" // Our registry name. + "example_block", // Our registry name. () -> new Block(...) // A supplier of the object we want to register. ); ``` @@ -50,7 +50,7 @@ The class `DeferredHolder` holds our object. The type parameter ```java public static final Supplier EXAMPLE_BLOCK = BLOCKS.register( - "example_block" // Our registry name. + "example_block", // Our registry name. () -> new Block(...) // A supplier of the object we want to register. ); ``` diff --git a/versioned_docs/version-1.20.4/resources/client/models/index.md b/versioned_docs/version-1.20.4/resources/client/models/index.md index 968b8e4d1..4d3db2ee9 100644 --- a/versioned_docs/version-1.20.4/resources/client/models/index.md +++ b/versioned_docs/version-1.20.4/resources/client/models/index.md @@ -109,11 +109,13 @@ Using the custom `neoforge:item_layers` loader, you can also specify extra face "layer1": "minecraft:item/glowstone_dust" }, "neoforge_data": { - "1": { - "color": "0xFFFF0000", - "block_light": 15, - "sky_light": 15, - "ambient_occlusion": false + "layers": { + "1": { + "color": "0xFFFF0000", + "block_light": 15, + "sky_light": 15, + "ambient_occlusion": false + } } } } diff --git a/versioned_docs/version-1.20.6/concepts/registries.md b/versioned_docs/version-1.20.6/concepts/registries.md index 196b8c383..45842a388 100644 --- a/versioned_docs/version-1.20.6/concepts/registries.md +++ b/versioned_docs/version-1.20.6/concepts/registries.md @@ -42,7 +42,7 @@ We can then add our registry entries as static final fields (see [the article on ```java public static final DeferredHolder EXAMPLE_BLOCK = BLOCKS.register( - "example_block" // Our registry name. + "example_block", // Our registry name. () -> new Block(...) // A supplier of the object we want to register. ); ``` @@ -53,7 +53,7 @@ The class `DeferredHolder` holds our object. The type parameter ```java public static final Supplier EXAMPLE_BLOCK = BLOCKS.register( - "example_block" // Our registry name. + "example_block", // Our registry name. () -> new Block(...) // A supplier of the object we want to register. ); ``` diff --git a/versioned_docs/version-1.20.6/resources/client/models/index.md b/versioned_docs/version-1.20.6/resources/client/models/index.md index 360d8ab9b..74b11ba68 100644 --- a/versioned_docs/version-1.20.6/resources/client/models/index.md +++ b/versioned_docs/version-1.20.6/resources/client/models/index.md @@ -109,11 +109,13 @@ Using the custom `neoforge:item_layers` loader, you can also specify extra face "layer1": "minecraft:item/glowstone_dust" }, "neoforge_data": { - "1": { - "color": "0xFFFF0000", - "block_light": 15, - "sky_light": 15, - "ambient_occlusion": false + "layers": { + "1": { + "color": "0xFFFF0000", + "block_light": 15, + "sky_light": 15, + "ambient_occlusion": false + } } } } diff --git a/versioned_docs/version-1.21.1/concepts/registries.md b/versioned_docs/version-1.21.1/concepts/registries.md index 7a1e68956..62a3e8e96 100644 --- a/versioned_docs/version-1.21.1/concepts/registries.md +++ b/versioned_docs/version-1.21.1/concepts/registries.md @@ -42,7 +42,7 @@ We can then add our registry entries as static final fields (see [the article on ```java public static final DeferredHolder EXAMPLE_BLOCK = BLOCKS.register( - "example_block" // Our registry name. + "example_block", // Our registry name. () -> new Block(...) // A supplier of the object we want to register. ); ``` @@ -53,7 +53,7 @@ The class `DeferredHolder` holds our object. The type parameter ```java public static final Supplier EXAMPLE_BLOCK = BLOCKS.register( - "example_block" // Our registry name. + "example_block", // Our registry name. () -> new Block(...) // A supplier of the object we want to register. ); ``` diff --git a/versioned_docs/version-1.21.1/resources/client/models/index.md b/versioned_docs/version-1.21.1/resources/client/models/index.md index 079ade3b5..5b83d0ccd 100644 --- a/versioned_docs/version-1.21.1/resources/client/models/index.md +++ b/versioned_docs/version-1.21.1/resources/client/models/index.md @@ -109,11 +109,13 @@ Using the custom `neoforge:item_layers` loader, you can also specify extra face "layer1": "minecraft:item/glowstone_dust" }, "neoforge_data": { - "1": { - "color": "0xFFFF0000", - "block_light": 15, - "sky_light": 15, - "ambient_occlusion": false + "layers": { + "1": { + "color": "0xFFFF0000", + "block_light": 15, + "sky_light": 15, + "ambient_occlusion": false + } } } } diff --git a/versioned_docs/version-1.21.3/advanced/_category_.json b/versioned_docs/version-1.21.3/advanced/_category_.json new file mode 100644 index 000000000..2161114e5 --- /dev/null +++ b/versioned_docs/version-1.21.3/advanced/_category_.json @@ -0,0 +1,4 @@ +{ + "label": "Advanced Topics", + "position": 12 +} \ No newline at end of file diff --git a/versioned_docs/version-1.21.3/advanced/accesstransformers.md b/versioned_docs/version-1.21.3/advanced/accesstransformers.md new file mode 100644 index 000000000..838b759de --- /dev/null +++ b/versioned_docs/version-1.21.3/advanced/accesstransformers.md @@ -0,0 +1,147 @@ +# 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`: + +Access Transformers need to be declared in `build.gradle`. AT files can be specified anywhere as long as they are copied to the `resources` output directory on compilation. + +```groovy +// In build.gradle: +// This block is where your mappings version is also specified +minecraft { + accessTransformers { + file('src/main/resources/META-INF/accesstransformer.cfg') + } +} +``` + +By default, NeoForge will search for `META-INF/accesstransformer.cfg`. If the `build.gradle` specifies access transformers in any other location, then their location needs to be defined within `neoforge.mods.toml`: + +```toml +# In neoforge.mods.toml: +[[accessTransformers]] +## The file is relative to the output directory of the resources, or the root path inside the jar when compiled +## The 'resources' directory represents the root output directory of the resources +file="META-INF/accesstransformer.cfg" +``` + +Additionally, multiple AT files can be specified and will be applied in order. This can be useful for larger mods with multiple packages. + +```groovy +// In build.gradle: +minecraft { + accessTransformers { + file('src/main/resources/accesstransformer_main.cfg') + file('src/additions/resources/accesstransformer_additions.cfg') + } +} +``` + +```toml +# In neoforge.mods.toml +[[accessTransformers]] +file="accesstransformer_main.cfg" + +[[accessTransformers]] +file="accesstransformer_additions.cfg" +``` + +After adding or modifying any Access Transformer, the Gradle project must be refreshed for the transformations to take effect. + +## The Access Transformer Specification + +### 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 + +#### 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 21, 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 random + +# Makes public the 'makeExecutor' method in Util, +# accepting a String and returns a TracingExecutor +public net.minecraft.Util makeExecutor(Ljava/lang/String;)Lnet/minecraft/TracingExecutor; + +# Makes public the 'leastMostToIntArray' method in UUIDUtil, +# accepting two longs and returning an int[] +public net.minecraft.core.UUIDUtil leastMostToIntArray(JJ)[I +``` + +[specs]: https://github.com/NeoForged/AccessTransformers/blob/main/FMLAT.md +[jvmdescriptors]: https://docs.oracle.com/javase/specs/jvms/se21/html/jvms-4.html#jvms-4.3.2 diff --git a/versioned_docs/version-1.21.3/advanced/extensibleenums.md b/versioned_docs/version-1.21.3/advanced/extensibleenums.md new file mode 100644 index 000000000..d35323040 --- /dev/null +++ b/versioned_docs/version-1.21.3/advanced/extensibleenums.md @@ -0,0 +1,148 @@ +# Extensible Enums + +Extensible Enums are an enhancement of specific Vanilla enums to allow new entries to be added. This is done by modifying the compiled bytecode of the enum at runtime to add the elements. + +## `IExtensibleEnum` + +All enums that can have new entries implement the `IExtensibleEnum` interface. This interface acts as a marker to allow the `RuntimeEnumExtender` launch plugin service to know what enums should be transformed. + +:::warning +You should **not** be implementing this interface on your own enums. Use maps or registries instead depending on your usecase. +Enums which are not patched to implement the interface cannot have the interface added to them via mixins or coremods due to the order the transformers run in. +::: + +### Creating an Enum Entry + +To create new enum entries, a JSON file needs to be created and referenced in the `neoforge.mods.toml` with the `enumExtensions` entry of a `[[mods]]` block. The specified path must be relative to the `resources` directory: +```toml +# In neoforge.mods.toml: +[[mods]] +## The file is relative to the output directory of the resources, or the root path inside the jar when compiled +## The 'resources' directory represents the root output directory of the resources +enumExtensions="META-INF/enumextensions.json" +``` + +The definition of the entry consists of the target enum's class name, the new field's name (must be prefixed with the mod ID), the descriptor of the constructor to use for constructing the entry and the parameters to be passed to said constructor. + +```json5 +{ + "entries": [ + { + // The enum class the entry should be added to + "enum": "net/minecraft/world/item/ItemDisplayContext", + // The field name of the new entry, must be prefixed with the mod ID + "name": "EXAMPLEMOD_STANDING", + // The constructor to be used + "constructor": "(ILjava/lang/String;Ljava/lang/String;)V", + // Constant parameters provided directly. + "parameters": [ -1, "examplemod:standing", null ] + }, + { + "enum": "net/minecraft/world/item/Rarity", + "name": "EXAMPLEMOD_CUSTOM", + "constructor": "(ILjava/lang/String;Ljava/util/function/UnaryOperator;)V", + // The parameters to be used, provided as a reference to an EnumProxy field in the given class + "parameters": { + "class": "example/examplemod/MyEnumParams", + "field": "CUSTOM_RARITY_ENUM_PROXY" + } + }, + { + "enum": "net/minecraft/world/damagesource/DamageEffects", + "name": "EXAMPLEMOD_TEST", + "constructor": "(Ljava/lang/String;Ljava/util/function/Supplier;)V", + // The parameters to be used, provided as a reference to a method in the given class + "parameters": { + "class": "example/examplemod/MyEnumParams", + "method": "getTestDamageEffectsParameter" + } + } + ] +} +``` + +```java +public class MyEnumParams { + public static final EnumProxy CUSTOM_RARITY_ENUM_PROXY = new EnumProxy<>( + Rarity.class, -1, "examplemod:custom", (UnaryOperator