In 1.21, ArmorMaterial became a data-driven record registered in the game's built-in registry rather than an enum. Each material declares per-slot defence values, enchantability, an equip sound, a repair ingredient, and one or more texture layers used for the 3D equipped rendering. The inventory icons are separate item textures, just like any other flat item.
ItemRegistry.Armour Material
Create a new class called ArmourMaterialRegistry in your common registry package. Declare a RegistrationProvider<ArmorMaterial> and register your material:
The seven arguments to ArmorMaterial are:
- defence map: damage reduction points per slot (iron totals 15: 2+6+5+2).
- enchantmentValue: enchantability (iron is 9, gold is 25, diamond is 10).
- equipSound: the
Holder<SoundEvent>played on equip. - repairIngredient: item accepted by an anvil to repair the armour.
- layers: list of texture layers shown on the player model when equipped.
- toughness: extra damage reduction for heavy armour (diamond: 2, netherite: 4, iron: 0).
- knockbackResistance: fraction of knockback absorbed (netherite: 0.1, others: 0).
Call ArmourMaterialRegistry.init() from CommonClass.init() before the item registry so the material is registered before the armour items that reference it:
Registering Armour Items
ArmorItem takes a Holder<ArmorMaterial>, not the material object directly. Retrieve it with Holder.direct() inside the item supplier, which is safe because the supplier is only evaluated after the material registry has been populated. Also, let's set the max stack size for the ItemStack to 1 by calling.stacksTo(1) on getItemProperties():
Holder.direct() creates an unkeyed holder, which works at runtime but means the material cannot be referenced by data packs or other mods by its registry key. For full interoperability, retrieve the keyed holder after registration using BuiltInRegistries.ARMOR_MATERIAL.getHolder(ResourceKey...) and store it in astatic Holder<ArmorMaterial> field initialised in a RegisterEvent or equivalent post-registration hook.Armour Textures
The 3D equipped model uses two texture files named after the layer identifier you declared. For the ArmorMaterial.Layer created with ResourceLocation.fromNamespaceAndPath(MOD_ID, "example"), Minecraft looks for:
assets/examplemod/textures/models/armor/example_layer_1.png: worn on the helmet, chestplate, and boots model.assets/examplemod/textures/models/armor/example_layer_2.png: worn on the leggings model.
Both files follow the vanilla armour UV layout. The easiest way to create them is to copy a vanilla armour texture from the Minecraft assets as a starting point and recolour or redraw from there.
Creative Tab and Language File
Add all four pieces to your creative tab and add translation keys to en_us.json:
Datagen
Armour items use the standard flat 2D inventory model, so each piece needs a basicItem call in ExampleItemModelProvider:
Create a 16×16 PNG inventory icon for each piece under src/main/resources/assets/examplemod/textures/item/ and run NeoForge Data to generate the model files.
Testing In-Game
Launch the client and equip each piece from your creative tab. Confirm:
- The inventory icon for each slot shows your item texture.
- The equipped 3D model on the player uses your layer textures.
- The armour bar (chestplate icon at the top of the hotbar) fills according to your defence values.
- Placing the pieces in an anvil with Iron Sticks repairs their durability.
If the equipped model appears as the default leather armour texture or shows missing textures, double-check the layer texture file names. The number suffix (_layer_1, _layer_2) must be present and the folder must be textures/models/armor/ not textures/armor/.
You can find the source for this tutorial here:
View Source on GitHubBlock Entities (MultiLoader 1.21+)
Attach persistent data and ticking logic to a block using BlockEntityType, implement saveAdditional and loadAdditional for NBT persistence, and sync state to clients with getUpdatePacket.
Continue →