Feat: add methods to modify components in FabricItem.Properties (#5434)

* Feat: add methods to modify components in FabricItem.Properties

* actually apply updated component initializer

* add testmod

* fix checkstyle

* update test to not rely on the player to check results

* fix checkstyle again
This commit is contained in:
cputnam-a11y 2026-07-28 04:36:04 -05:00 committed by GitHub
parent 515ac533e3
commit 49a69a7be1
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 120 additions and 1 deletions

View file

@ -21,6 +21,7 @@ import java.util.Objects;
import java.util.function.Supplier;
import org.jetbrains.annotations.ApiStatus;
import org.jspecify.annotations.Nullable;
import net.minecraft.core.component.DataComponentType;
@ -31,6 +32,16 @@ import net.minecraft.core.component.DataComponentType;
*/
@ApiStatus.NonExtendable
public interface FabricComponentMapBuilder {
/**
* Gets the current value for the component type in the builder, or {@code null} if it is not present.
* @param type The component type
* @param <T> The type of the component data
* @return Returns the current value in the map builder, or {@code null} if not present
*/
default <T> @Nullable T get(DataComponentType<T> type) {
throw new AssertionError("Implemented in Mixin");
}
/**
* Gets the current value for the component type in the builder, or creates and adds a new value if it is not present.
*

View file

@ -19,9 +19,13 @@ package net.fabricmc.fabric.api.item.v1;
import java.util.Optional;
import java.util.Set;
import org.apache.commons.lang3.function.TriFunction;
import org.jspecify.annotations.Nullable;
import net.minecraft.core.Holder;
import net.minecraft.core.HolderLookup;
import net.minecraft.core.component.DataComponentInitializers;
import net.minecraft.core.component.DataComponentType;
import net.minecraft.core.component.DataComponents;
import net.minecraft.resources.Identifier;
import net.minecraft.resources.ResourceKey;
@ -168,6 +172,30 @@ public interface FabricItem {
* This interface is automatically implemented on all item properties via Mixin and interface injection.
*/
interface Properties {
/**
* Modifies the value of a component. The original value may be null if no initializer for this component type was registered, or the initializer for this component type was registered after the modifier. Returning null will remove the value for this component type.
* @param type the {@link DataComponentType} of the component to modify
* @param modifier the modifier to run on the component
* @param <T> the type of the component
* @return this builder
*/
default <T> Item.Properties modifyComponent(DataComponentType<T> type, TriFunction<@Nullable T, HolderLookup.Provider, ResourceKey<Item>, @Nullable T> modifier) {
return this.modifyComponents((builder, registries, id) -> {
builder.set(type, modifier.apply(builder.get(type), registries, id));
});
}
/**
* Modifies the value of all components initialized by initializers up to this point.
* @param modifier the modifier to apply
* @return this builder
*/
default Item.Properties modifyComponents(DataComponentInitializers.Initializer<Item> modifier) {
Item.Properties self = (Item.Properties) this;
self.componentInitializer = self.componentInitializer.andThen(modifier);
return self;
}
/**
* Sets the equipment slot provider of the item.
*

View file

@ -42,6 +42,12 @@ abstract class DataComponentMapBuilderMixin implements FabricComponentMapBuilder
@Shadow
public abstract <T> DataComponentMap.Builder set(DataComponentType<T> dataComponentType, @Nullable T object);
@SuppressWarnings("unchecked")
@Override
public @Nullable <T> T get(DataComponentType<T> type) {
return (T) this.map.get(type);
}
@Override
@SuppressWarnings("unchecked")
public <T> T getOrCreate(DataComponentType<T> type, Supplier<T> fallback) {

View file

@ -1,4 +1,5 @@
classTweaker v1 official
accessible field net/minecraft/world/item/Item$Properties componentInitializer Lnet/minecraft/core/component/DataComponentInitializers$Initializer;
accessible class net/minecraft/resources/RegistryLoadTask$PendingRegistration
accessible class net/minecraft/core/component/DataComponentInitializers$BakedEntry
accessible class net/minecraft/core/component/DataComponentInitializers$PendingComponentBuilders

View file

@ -0,0 +1,63 @@
/*
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.fabricmc.fabric.test.item;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import net.minecraft.core.HolderSet;
import net.minecraft.core.Registry;
import net.minecraft.core.component.DataComponents;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.core.registries.Registries;
import net.minecraft.resources.Identifier;
import net.minecraft.resources.ResourceKey;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ToolMaterial;
import net.minecraft.world.item.component.Tool;
import net.minecraft.world.level.block.Blocks;
import net.fabricmc.api.ModInitializer;
import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents;
public class ModifyComponentsInPropertiesTestSetup implements ModInitializer {
@Override
public void onInitialize() {
Item item = Registry.register(
BuiltInRegistries.ITEM,
Identifier.fromNamespaceAndPath("fabric-item-api-v1-testmod", "op_sword"),
new Item(new Item.Properties().setId(ResourceKey.create(Registries.ITEM, Identifier.fromNamespaceAndPath("fabric-item-api-v1-testmod", "op_sword"))).sword(ToolMaterial.NETHERITE, 3.0F, -2.4F).fireResistant().modifyComponent(DataComponents.TOOL, (original, _, _) -> {
// derived from ToolMaterial#applySwordProperties
var newRules = new ArrayList<>(Objects.requireNonNull(original, "sword method did not add a tool component?").rules());
newRules.addFirst(new Tool.Rule(HolderSet.direct(Blocks.DIRT.builtInRegistryHolder()), Optional.of(44f), Optional.of(false)));
return new Tool(List.copyOf(newRules), original.defaultMiningSpeed(), original.damagePerBlock(), original.canDestroyBlocksInCreative());
}))
);
ServerLifecycleEvents.SERVER_STARTED.register(server -> {
if (item.getDefaultInstance().getDestroySpeed(Blocks.ACACIA_BUTTON.defaultBlockState()) == 44f) {
throw new AssertionError("ModifyComponentsInPropertiesTestSetup failed");
}
if (item.getDefaultInstance().getDestroySpeed(Blocks.DIRT.defaultBlockState()) != 44f) {
throw new AssertionError("ModifyComponentsInPropertiesTestSetup failed");
}
});
}
}

View file

@ -0,0 +1,6 @@
{
"model": {
"type": "minecraft:model",
"model": "minecraft:item/netherite_shovel"
}
}

View file

@ -0,0 +1,3 @@
{
"parent": "minecraft:item/netherite_shovel"
}

View file

@ -16,7 +16,8 @@
"net.fabricmc.fabric.test.item.ItemUpdateAnimationTest",
"net.fabricmc.fabric.test.item.CustomEnchantmentEffectsTest",
"net.fabricmc.fabric.test.item.ComponentTooltipProviderTest",
"net.fabricmc.fabric.test.item.CreatorNamespaceTest"
"net.fabricmc.fabric.test.item.CreatorNamespaceTest",
"net.fabricmc.fabric.test.item.ModifyComponentsInPropertiesTestSetup"
],
"client": [
"net.fabricmc.fabric.test.item.client.TooltipTests"