|
| 1 | +--- |
| 2 | +slug: /dev/data-component-api |
| 3 | +description: A guide to the ItemStack data component API. |
| 4 | +--- |
| 5 | + |
| 6 | +# Data Component API |
| 7 | + |
| 8 | +:::danger[Experimental] |
| 9 | + |
| 10 | +The data component API is currently experimental, and is additionally subject to change across versions. |
| 11 | + |
| 12 | +::: |
| 13 | + |
| 14 | +The data component API provides a version-specific interface for accessing and manipulating item data that is otherwise not representable by the `ItemMeta` API. |
| 15 | +Through this API, you can read and modify properties of an item, so called data components, in a stable and object-oriented manner. |
| 16 | + |
| 17 | + |
| 18 | +## Introduction |
| 19 | + |
| 20 | +### What is a data component? |
| 21 | +A data component represents a piece of data associated with an item. Vanilla items can have properties such as custom model data, container loot contents, banner patterns, or potion effects. |
| 22 | + |
| 23 | +### Structure |
| 24 | + |
| 25 | +For implementation details, [click here](#example-cool-sword). |
| 26 | + |
| 27 | +#### The prototype (default values) |
| 28 | +Items come with an initial set of components that we call the prototype. |
| 29 | +These components are defined on the `ItemType` of the `ItemStack`. They control the base behavior |
| 30 | +of the item, representing a brand new item without any modifications. |
| 31 | + |
| 32 | +The prototype gives items their initial properties such as if they are food, a tool, a weapon, etc. |
| 33 | + |
| 34 | +#### The patch |
| 35 | +The patch represents the modifications made to the item. This may include giving it a custom name, |
| 36 | +modifying the enchantments, damaging it, or adding to the lore. The patch is applied ontop of the prototype, |
| 37 | +allowing us to make modifications to an item. |
| 38 | + |
| 39 | +The patch also allows for removing components that were previously in the prototype. This is shown by |
| 40 | +the `minecraft:tool` example in red. We are removing this component, so this sword item will no longer |
| 41 | +break cobweb or other sword blocks faster. |
| 42 | + |
| 43 | +We can also add new components, as seen from the new `minecraft:enchantment_glint_override` component, which |
| 44 | +allows us to make it appear as if it were enchanted. |
| 45 | + |
| 46 | + |
| 47 | +## Differences compared to `ItemMeta` |
| 48 | + |
| 49 | +The `ItemMeta` API provides methods to modify `ItemStack`s in a hierarchical manner, such as `CompassMeta`, which allows you to modify the components of a `minecraft:compass`. |
| 50 | +While `ItemMeta` is still very useful, it does not properly represent the prototype/patch relationship that Minecraft items use. |
| 51 | + |
| 52 | +### Key differences |
| 53 | + |
| 54 | +#### Expanded data model |
| 55 | +The data component API exposes a much broader and more detailed set of item properties than `ItemMeta`. |
| 56 | +Data components allow the entire item to be modified in a fashion that better represents how Minecraft does item modifications. |
| 57 | + |
| 58 | +#### Version-specific |
| 59 | +The data component API is designed to adapt to version changes. The data component API may experience breaking changes on version updates as Minecraft makes changes to components. |
| 60 | +Backwards compatibility is not promised. |
| 61 | + |
| 62 | +Because `ItemMeta` is represented in a different format, breaking changes made to components by Mojang may not result in breaking changes to `ItemMeta`. |
| 63 | + |
| 64 | +#### Builders and immutability |
| 65 | +Many complex data components require a builder approach for construction and editing. All data types that are returned by the api are also immutable, so they will not directly modify the component. |
| 66 | + |
| 67 | +#### Patch-only |
| 68 | +`ItemMeta` only represents the patch of an `ItemStack`. This means that you cannot get the original properties (prototype) of the `ItemStack`, such as its default |
| 69 | +durability or default attributes. |
| 70 | + |
| 71 | +#### No snapshots |
| 72 | +Currently, `ItemMeta` represents a **snapshot** of an `ItemStack`'s patched map. |
| 73 | +This is expensive as it requires the entire patch to be read, even values that you may not be using. |
| 74 | + |
| 75 | +The data component API integrates directly with `ItemStack`. Although conceptually similar, the data component API focuses on explicit, strongly typed data retrieval and updates without this additional overhead. |
| 76 | + |
| 77 | +### When should I use `DataComponents` or `ItemMeta`? |
| 78 | + |
| 79 | +You would want to use `ItemMeta` if you: |
| 80 | +- Are doing only simple changes to `ItemStack`s |
| 81 | +- Want to keep cross-version compatibility with your plugin |
| 82 | + |
| 83 | +You would want to use data components if you: |
| 84 | +- Want more complicated `ItemStack` modifications |
| 85 | +- Do not care about cross-version compatibility |
| 86 | +- Want to access default (prototype) values |
| 87 | +- Want to remove components from an `ItemStack`'s prototype |
| 88 | + |
| 89 | + |
| 90 | +## Basic usage |
| 91 | +The data component API will fetch values according to the behavior seen in game. So, if the patch removes the `minecraft:tool` component, |
| 92 | +trying to get that component will return null. |
| 93 | + |
| 94 | +### Retrieving a prototype value |
| 95 | + |
| 96 | +```java |
| 97 | +// Get the default durability of diamond sword |
| 98 | +int defaultDurability = Material.DIAMOND_SWORD.getDefaultData(DataComponentTypes.MAX_DAMAGE) |
| 99 | +``` |
| 100 | + |
| 101 | +### Checking for a data component |
| 102 | + |
| 103 | +```java |
| 104 | +// Check if this item has a custom name data component |
| 105 | +boolean hasCustomName = stack.hasData(DataComponentTypes.CUSTOM_NAME); |
| 106 | +logger.info("Has custom name? " + hasCustomName); |
| 107 | +``` |
| 108 | + |
| 109 | +### Reading a valued data component |
| 110 | + |
| 111 | +```java |
| 112 | +// The damage of an item can be null, so we require a null check |
| 113 | +Integer damageValue = stack.getData(DataComponentTypes.DAMAGE); |
| 114 | +if (damageValue != null) { |
| 115 | + logger.info("Current damage: " + damageValue); |
| 116 | +} else { |
| 117 | + logger.info("This item doesn't have a damage component set."); |
| 118 | +} |
| 119 | + |
| 120 | +// Certain components, like the max stack size, will always be present on an item |
| 121 | +Integer maxStackSize = stack.getData(DataComponentTypes.MAX_STACK_SIZE); |
| 122 | +``` |
| 123 | + |
| 124 | +### Setting a valued data component |
| 125 | + |
| 126 | +```java |
| 127 | +// Set a custom model data value on this item |
| 128 | +stack.setData(DataComponentTypes.CUSTOM_MODEL_DATA, CustomModelData.customModelData() |
| 129 | + .addFloat(0.5f) |
| 130 | + .addFlag(true) |
| 131 | + .build() |
| 132 | +); |
| 133 | +``` |
| 134 | + |
| 135 | +### Removing or resetting a data component |
| 136 | + |
| 137 | +```java |
| 138 | +// Remove an existing component (e.g. tool) |
| 139 | +stack.unsetData(DataComponentTypes.TOOL); |
| 140 | + |
| 141 | +// Reset a component to the default (prototype) value for its item type (e.g. max stack size) |
| 142 | +stack.resetData(DataComponentTypes.MAX_STACK_SIZE); |
| 143 | +``` |
| 144 | + |
| 145 | +### Non-valued data components |
| 146 | + |
| 147 | +Some components are only flags and don't carry any sort of value: |
| 148 | + |
| 149 | +```java |
| 150 | +// Make the item unbreakable |
| 151 | +stack.setData(DataComponentTypes.UNBREAKABLE); |
| 152 | + |
| 153 | +// Remove the unbreakable flag |
| 154 | +stack.unsetData(DataComponentTypes.UNBREAKABLE); |
| 155 | +``` |
| 156 | + |
| 157 | +## Advanced usage with builders |
| 158 | + |
| 159 | +Many data components have complex structures that require builders. |
| 160 | + |
| 161 | +### Modifying prototype component values |
| 162 | + |
| 163 | +```java |
| 164 | +ItemStack itemStack = ItemStack.of(Material.DIAMOND_HELMET); |
| 165 | +// Get the equippable component for this item, and make it a builder. |
| 166 | +// Note: Not all types have .toBuilder() methods |
| 167 | +// This is the prototype value of the diamond helmet. |
| 168 | +Equippable.Builder builder = itemStack.getData(DataComponentTypes.EQUIPPABLE).toBuilder(); |
| 169 | + |
| 170 | +// Make the helmet look like netherite |
| 171 | +// We get the prototype equippable value from NETHERITE_HELMET |
| 172 | +builder.assetId(Material.NETHERITE_HELMET.getDefaultData(DataComponentTypes.EQUIPPABLE).assetId()); |
| 173 | +// And give it a spooky sound when putting it on |
| 174 | +builder.equipSound(Registry.SOUNDS.getKeyOrThrow(Sound.ENTITY_GHAST_HURT)); |
| 175 | + |
| 176 | +// Set our new item |
| 177 | +itemStack.setData(DataComponentTypes.EQUIPPABLE, builder); |
| 178 | +``` |
| 179 | +This will create a diamond helmet that looks like a netherite helmet and plays a spooky ghast sound when equipped. |
| 180 | + |
| 181 | +### Example: Written book |
| 182 | + |
| 183 | +```java |
| 184 | +ItemStack writtenBook = ItemStack.of(Material.WRITTEN_BOOK); |
| 185 | +WrittenBookContent.Builder bookBuilder = WrittenBookContent.writtenBookContent("My Book", "AuthorName"); |
| 186 | + |
| 187 | +// Add a page |
| 188 | +bookBuilder.addPage(Component.text("This is a new page!")); |
| 189 | + |
| 190 | +// Add a page that shows differently for people who have swear filtering on |
| 191 | +// Players who have disabled filtering, will see "I hate Paper!", while those with filtering on will see the "I love Paper!". |
| 192 | +bookBuilder.addFilteredPage( |
| 193 | + Filtered.of(Component.text("I hate Paper!"), Component.text("I love Paper!")) |
| 194 | +); |
| 195 | + |
| 196 | +// Change generation |
| 197 | +bookBuilder.generation(1); |
| 198 | + |
| 199 | +// Apply changes |
| 200 | +writtenBook.setData(DataComponentTypes.WRITTEN_BOOK_CONTENT, bookBuilder.build()); |
| 201 | +``` |
| 202 | + |
| 203 | +### Example: Cool sword |
| 204 | + |
| 205 | +```java |
| 206 | +ItemStack itemStack = ItemStack.of(Material.DIAMOND_SWORD); |
| 207 | +itemStack.setData(DataComponentTypes.LORE, ItemLore.lore().addLine(Component.text("Cool sword!")).build()); |
| 208 | +itemStack.setData(DataComponentTypes.ENCHANTMENTS, ItemEnchantments.itemEnchantments().add(Enchantment.SHARPNESS, 10).build()); |
| 209 | +itemStack.setData(DataComponentTypes.RARITY, ItemRarity.RARE); |
| 210 | + |
| 211 | +itemStack.unsetData(DataComponentTypes.TOOL); // Remove the tool component |
| 212 | + |
| 213 | +itemStack.setData(DataComponentTypes.MAX_DAMAGE, 10); |
| 214 | +itemStack.setData(DataComponentTypes.ENCHANTMENT_GLINT_OVERRIDE, true); // Make it glow! |
| 215 | +``` |
| 216 | + |
| 217 | +## Matching items without certain data components |
| 218 | + |
| 219 | +When comparing items, you sometimes want to ignore certain values. For this, we can use the |
| 220 | +<Javadoc name="org.bukkit.inventory.ItemStack#matchesWithoutData(org.bukkit.inventory.ItemStack,java.util.Set)">`ItemStack#matchesWithoutData`</Javadoc> |
| 221 | +method. |
| 222 | + |
| 223 | +For example, here we compare two diamond swords whilst ignoring their durability: |
| 224 | + |
| 225 | +```java |
| 226 | +ItemStack originalSword = new ItemStack(Material.DIAMOND_SWORD); |
| 227 | +ItemStack damagedSword = new ItemStack(Material.DIAMOND_SWORD); |
| 228 | +damagedSword.setData(DataComponentTypes.DAMAGE, 100); |
| 229 | + |
| 230 | +boolean match = damagedSword.matchesWithoutData(originalSword, Set.of(DataComponentTypes.DAMAGE), false); |
| 231 | +logger.info("Do the sword match? " + match); // true |
| 232 | +``` |
0 commit comments