# 24w09a/10a + Item Components 101 Here comes the (extremely belated) post on the two recent snapshots. Note that this post includes both 09a and 10a stuff, without distinguishing the two. ## Fabric API Breaking changes were made to Data Generation API (now v17), Item API (now v5), Recipe API (now v4), and Transfer API (now v5). ### Data Generation The following methods now take `RegistryWrapper.WrapperLookup registryLookup` arguments: - `FabricAdvancementProvider#generateAdvancements` - `FabricLanguageProvider#generateTranslations` `FabricLanguageProvider` constructor now also requires passing `CompletableFuture<RegistryWrapper.WrapperLookup>`, like other providers. ### Item API `FabricItemSettings` was removed (after deprecation during 1.20.5 cycle). Use vanilla `Item.Settings` instead. `ModifyItemAttributeModifiersCallback` was also removed without deprecation or replacement (yet). `allowNbtUpdateAnimations` method of `FabricItem` was renamed to `allowComponentsUpdateAnimations`. ### Recipe API `DefaultCustomIngredients#nbt` and support for NBT ingredients were removed, as the game no longer uses NBT to store item stack-specific data. To check for custom name, enchantments, etc that were previously kept inside the NBT but are now recorded in the components (see below for details), use the new `components` ingredient. To check for custom data component (a NBT data not used by the game but can be used by data packs or commands), use the `customData` ingredient that will be released shortly. ### Transfer API `TransferVariant` (such as `FluidVariant` or `ItemVariant`) is now a pair of the object and the components, instead of the object and NBT. `getNbt`, `hasNbt`, and `nbtMatches` methods were replaced with `getComponents`, `hasComponents`, and `componentsMatch` methods. The following methods were removed: `copyNbt`, `copyOrCreateNbt`, `toNbt`, and `toPacket`. `FluidVariant#of` and `ItemVariant#of` now takes `ComponentChanges` instead of `@Nullable NbtCompound`. `TransferVariant` is now serialized using codecs and packet codecs. Therefore, `fromNbt` and `fromPacket` static methods were removed. `SingleVariantStorage#writeNbt` instance method was removed; subclasses now provide separate methods named `writeNbt`. Both `readNbt` and `writeNbt` now require passing a `RegistryWrapper.WrapperLookup` instance; they should be available in the methods from which they are called (or you can use the world's `DynamicRegistryManager` instance). ## Minecraft changes ### Item Components We skip the general description of the item components system, which you can check in the slicedlime's video: [News in Data Pack Version 33 (24w09a): Item Components!](https://www.youtube.com/watch?v=iY9OHAd4Aco). Now, we begin the journey into the implementation of item components - note that while this is currently used only by item stacks, nothing stops them from reusing this in other contexts (especially block entities): There are five main objects in the item components: - `DataComponentType`, a type of components, serving as keys; - Component classes (can be any object), serving as values; - `ComponentMap`, a read-only view of components; - `ComponentMapImpl`, which is a `ComponentMap` that can be modified, and internally a pair of unmodifiable base `ComponentMap` and the "overrides" that are modified; and - `ComponentChanges`, a map of component type to the changes for the value (either setting it to a specific value or removing it). This can be used as a diff applied to `ItemStack`; you apply an instance of `ComponentChanges` to `ItemStack`/`ComponentMapImpl`, so that the overrides part of the map reflects the changes. In other parts of the code, `ComponentChanges` is just used as `ComponentMapImpl` minus the base. So, how does this work? Each item has the base components. In addition to the base components common to all items, like having empty `EnchantmentsComponent`, some items provide additional base components. A notable example is `DataComponentTypes#DAMAGE` for armors, tools, and weapons. A component type that exists in the base has a corresponding default value; here the default damage is `0`. An `ItemStack` can either 1) add an additional component not present in the base; 2) change the components from the one in the base; or 3) remove a component that exists in the base. The final components of `ItemStack`, obtainable as `ComponentMap` from `ItemStack#getComponents`, reflects all three. **(Note: the folloing note and table is not necessary for understanding how modders use it, but might be interesting for some.)** Both `ComponentMapImpl` and `ComponentChanges` utilize `@Nullable Optional` for the values. Here, `null` means "we don't change this", while `Optional.empty()` means "remove this" (either from the base, or the existing `ComponentMapImpl` "overrides" if there is no base). Here is the table explaining each possible situation. Looks confusing, but Eureka moment is waiting for you: | Base for the `Item` | Current override of the base (saved in `ItemStack`) | Applied `ComponentChanges.Builder` | Result | Effective value from `get` call | |---------------------------|-----------------------------------------------------|------------------------------------|-------------------------------------------------------------------------------------------------|---------------------------------| | Missing (`get() == null`) | Missing | Missing/`remove()` | Nothing occurs | `null` | | Missing | Missing | `add(T)` | "Current override" set to T | T | | Missing | `Optional.of(T)` | Missing/`add(T)` | Nothing occurs | T | | Missing | `Optional.of(T)` | `remove()` | T removed, "Current override" is now missing | `null` | | Missing | `Optional.of(T)` | `add(T2)` | "Current override" is now T2 | T2 | | Default (T) | Missing | Missing | Nothing occurs | T | | Default (T) | Missing | `remove()` | Overridden so that the base component is removed for the stack only | `null` | | Default (T) | Missing | `add(T)` | Because the default value is added, "Current override" continues to be missing | T | | Default (T) | Missing | `add(T2)` | "Current override" set to T2 | T2 | | Default (T) | `Optional.of(T2)` | Missing/`add(T2)` | Nothing occurs | T2 | | Default (T) | `Optional.of(T2)` | `add(T)` | Because the default value is added, "Current override" is now missing | T | | Default (T) | `Optional.of(T2)` | `remove()` | Base component is removed | `null` | | Default (T) | `Optional.empty()` | Missing/`remove()` | Nothing occurs | `null` | | Default (T) | `Optional.empty()` | `add(T)` | Because the default value is added, the component is back and "Current override" is now missing | T | | Default (T) | `Optional.empty()` | `add(T2)` | "Current override" set to T2 | T2 | ### Using Item Components Getting/setting the custom name of an item (note that `Text` is directly used as the component value): ```java @Nullable Text name = stack.get(DataComponentTypes.CUSTOM_NAME); // Like Map#put, returns old value @Nullable Text oldName = stack.set(DataComponentTypes.CUSTOM_NAME, Text.literal("Lorem ipsum")); // get with default value. This does not modify the stack. name = stack.getOrDefault(DataComponentTypes.CUSTOM_NAME, Text.empty()); // Remove custom name; does nothing when there is no custom name @Nullable Text removedName = stack.remove(DataComponentTypes.CUSTOM_NAME); // apply() works like Map#compute. // Here, we change the color of the item name. // (Note: stack#getName already checks for custom name, so in reality this does not need apply() call.) stack.apply(DataComponentTypes.CUSTOM_NAME, stack.getName(), current -> current.copy().formatted(Formatting.RED)) ``` Getting/setting the custom data (NBT). This is useful for datapack makers and server-side modders, because custom item components (see below) need to be synced to the clients, while custom data remains an unparsed NBT. Unlike the previous example, this uses a component record `NbtComponent`. Note: **component values are supposed to be immutable** - even if you can modify it, don't. Always copy and `set()`. ```java NbtCompound nbt = ...; NbtComponent component = NbtComponent.of(nbt); // Setting stack.set(DataComponentTypes.CUSTOM_DATA, component); // Getting a copy @Nullable var data = stack.get(DataComponentTypes.CUSTOM_DATA); if (data != null) { NbtCompound value = data.copyNbt(); } // Using NbtComponent#apply, which copies automatically, to modify the NBT // Note: you might want to use the static method NbtComponent#set instead, // which makes this a bit shorter stack.apply(DataComponentTypes.CUSTOM_DATA, NbtComponent.DEFAULT, comp -> comp.apply(currentNbt -> { currentNbt.putInt("key", 0); })); // Checking if a certain NBT is a subset of the stack NBT // (also known as: "non-strict matching") NbtCompound requiredNbt = ...; boolean match = stack.getOrDefault(DataComponentTypes.CUSTOM_DATA, NbtComponent.DEFAULT).matches(requiredNbt); // use createPredicate to make Predicate<ItemStack> ``` Using `ComponentChanges` to mass-modify the components of `ItemStack`: ```java // stack is a damageable item. // Repair the item and remove custom name. // Note that the resulting stack has no components because 0 damage is the default from the base components. var changes = ComponentChanges.builder().add(DataComponentTypes.DAMAGE, 0).remove(DataComponentTypes.CUSTOM_NAME).build(); stack.applyChanges(changes); ``` Making an item with base components: ```java Item item = new Item(new Item.Settings().component(DataComponentTypes.CUSTOM_NAME, Text.literal("Hello"))); // Call #component multiple times for multiple base components. // Note: calling maxDamage automatically adds DAMAGE component. ``` Making a custom component. Note that like many registered entries, these must be present in both the client and the server. ```java public static final DataComponentType<Integer> WEIRDNESS = DataComponentType.builder().codec(Codec.INT).packetCodec(PacketCodecs.VAR_INT).build(); // in the initializer Registry.register(Registries.DATA_COMPONENT_TYPE, new Identifier("example", "weirdness"), WEIRDNESS); ``` ### Some caveats - Component values should not be modified directly. Using `Record` or other immutable object is highly recommended. Always copy, modify, then set. - Setting an invalid value for a component does not cause an error immediately, but will lead to a crash during save, and possible data corruption. A common example is `NONNEGATIVE_INT` being used for an incrementing value; when it overflows and the value is set to negatives, saving it would crash. ### Other Item changes - As noted in the video, an empty item stack is now serialized as omitting the field or an empty object. `ItemStack#fromNbt` now returns `Optional<ItemStack>` while `fromNbtOrEmpty` supports empty NBT object. Both now require passing the registries. - `writeNbt` method was renamed to `encode`. The one with `NbtCompound` argument allows adding to the existing compound, like `writeNbt`. - The methods in `ItemStack` that reference `Nbt` are generally renamed to reference `Components` instead. - `Item#isNbtSynced` was removed, specify custom packet codecs instead. - `Item#getBreakSound` was added. - Attack damage and mining speed of `MiningToolItem` and `SwordItem` are now specified in item settings via `attributeModifiers` setting. ### BlockEntity interaction with components Although `BlockEntity` does not fully use components yet, there are some mechanisms allowing it to interact with item stacks for the block (like shulker boxes). A block entity should use components to store data in the corresponding item stacks, either dropped or Pick-Blocked. This can be done by: - Setting the block entity's fields in `readComponents`, - Writing the block entity's fields to the components in `addComponents`, and - Removing fields from the serialized NBT that are now encoded using components with `removeFromCopiedStackNbt`. Here is an example (note that `LockableContainerBlockEntity` already provides this, if you're using it): ```java private final DefaultedList<ItemStack> inventory = ...; public void readComponents(ComponentMap components) { components.getOrDefault(DataComponentTypes.CONTAINER, ContainerComponent.DEFAULT).copyTo(this.inventory); } public void addComponents(ComponentMap.Builder builder) { builder.add(DataComponentTypes.CONTAINER, ContainerComponent.fromStacks(this.inventory)); } public void removeFromCopiedStackNbt(NbtCompound nbt) { nbt.remove("Items"); } ``` ### Other changes - Pathfinding cache was added; and with this change `AbstractBlock#canPathfindThrough` no longer takes the `world` and `pos` arguments. - Serialization of `Text` now requires passing registries. In contexts where registry is unavailable, use `DynamicRegistryManager.EMPTY`. - Some string-related methods were moved to `StringHelper`.