diff --git a/fabric-item-api-v1/src/main/java/net/fabricmc/fabric/api/item/v1/DefaultItemComponentEvents.java b/fabric-item-api-v1/src/main/java/net/fabricmc/fabric/api/item/v1/DefaultItemComponentEvents.java new file mode 100644 index 000000000..f1cd536dc --- /dev/null +++ b/fabric-item-api-v1/src/main/java/net/fabricmc/fabric/api/item/v1/DefaultItemComponentEvents.java @@ -0,0 +1,84 @@ +/* + * 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.api.item.v1; + +import java.util.Collection; +import java.util.function.BiConsumer; +import java.util.function.Consumer; +import java.util.function.Predicate; + +import net.minecraft.component.ComponentMap; +import net.minecraft.item.Item; + +import net.fabricmc.fabric.api.event.Event; +import net.fabricmc.fabric.api.event.EventFactory; + +/** + * Events to modify the default {@link ComponentMap} of items. + */ +public final class DefaultItemComponentEvents { + /** + * Event used to add or remove data components to known items. + */ + public static final Event MODIFY = EventFactory.createArrayBacked(ModifyCallback.class, listeners -> context -> { + for (ModifyCallback listener : listeners) { + listener.modify(context); + } + }); + + private DefaultItemComponentEvents() { + } + + public interface ModifyContext { + /** + * Modify the default data components of the specified item. + * + * @param itemPredicate A predicate to match items to modify + * @param builderConsumer A consumer that provides a {@link ComponentMap.Builder} to modify the item's components. + */ + void modify(Predicate itemPredicate, BiConsumer builderConsumer); + + /** + * Modify the default data components of the specified item. + * + * @param item The item to modify + * @param builderConsumer A consumer that provides a {@link ComponentMap.Builder} to modify the item's components. + */ + default void modify(Item item, Consumer builderConsumer) { + modify(Predicate.isEqual(item), (builder, _item) -> builderConsumer.accept(builder)); + } + + /** + * Modify the default data components of the specified items. + * @param items The items to modify + * @param builderConsumer A consumer that provides a {@link ComponentMap.Builder} to modify the item's components. + */ + default void modify(Collection items, BiConsumer builderConsumer) { + modify(items::contains, builderConsumer); + } + } + + @FunctionalInterface + public interface ModifyCallback { + /** + * Modify the default data components of items using the provided {@link ModifyContext} instance. + * + * @param context The context to modify items + */ + void modify(ModifyContext context); + } +} diff --git a/fabric-item-api-v1/src/main/java/net/fabricmc/fabric/impl/item/DefaultItemComponentImpl.java b/fabric-item-api-v1/src/main/java/net/fabricmc/fabric/impl/item/DefaultItemComponentImpl.java new file mode 100644 index 000000000..4091e56d2 --- /dev/null +++ b/fabric-item-api-v1/src/main/java/net/fabricmc/fabric/impl/item/DefaultItemComponentImpl.java @@ -0,0 +1,51 @@ +/* + * 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.impl.item; + +import java.util.function.BiConsumer; +import java.util.function.Predicate; + +import net.minecraft.component.ComponentMap; +import net.minecraft.item.Item; +import net.minecraft.registry.Registries; + +import net.fabricmc.fabric.api.item.v1.DefaultItemComponentEvents; +import net.fabricmc.fabric.mixin.item.ItemAccessor; + +public class DefaultItemComponentImpl { + public static void modifyItemComponents() { + DefaultItemComponentEvents.MODIFY.invoker().modify(ModifyContextImpl.INSTANCE); + } + + static class ModifyContextImpl implements DefaultItemComponentEvents.ModifyContext { + private static final ModifyContextImpl INSTANCE = new ModifyContextImpl(); + + private ModifyContextImpl() { + } + + @Override + public void modify(Predicate itemPredicate, BiConsumer builderConsumer) { + for (Item item : Registries.ITEM) { + if (itemPredicate.test(item)) { + ComponentMap.Builder builder = ComponentMap.builder().addAll(item.getComponents()); + builderConsumer.accept(builder, item); + ((ItemAccessor) item).setComponents(builder.build()); + } + } + } + } +} diff --git a/fabric-item-api-v1/src/main/java/net/fabricmc/fabric/mixin/item/ItemAccessor.java b/fabric-item-api-v1/src/main/java/net/fabricmc/fabric/mixin/item/ItemAccessor.java new file mode 100644 index 000000000..69db4ef77 --- /dev/null +++ b/fabric-item-api-v1/src/main/java/net/fabricmc/fabric/mixin/item/ItemAccessor.java @@ -0,0 +1,31 @@ +/* + * 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.mixin.item; + +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Mutable; +import org.spongepowered.asm.mixin.gen.Accessor; + +import net.minecraft.component.ComponentMap; +import net.minecraft.item.Item; + +@Mixin(Item.class) +public interface ItemAccessor { + @Accessor + @Mutable + void setComponents(ComponentMap components); +} diff --git a/fabric-item-api-v1/src/main/java/net/fabricmc/fabric/mixin/item/RegistriesMixin.java b/fabric-item-api-v1/src/main/java/net/fabricmc/fabric/mixin/item/RegistriesMixin.java new file mode 100644 index 000000000..19c3a5b85 --- /dev/null +++ b/fabric-item-api-v1/src/main/java/net/fabricmc/fabric/mixin/item/RegistriesMixin.java @@ -0,0 +1,34 @@ +/* + * 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.mixin.item; + +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +import net.minecraft.registry.Registries; + +import net.fabricmc.fabric.impl.item.DefaultItemComponentImpl; + +@Mixin(Registries.class) +public abstract class RegistriesMixin { + @Inject(method = "freezeRegistries", at = @At("HEAD")) + private static void modifyDefaultItemComponents(CallbackInfo ci) { + DefaultItemComponentImpl.modifyItemComponents(); + } +} diff --git a/fabric-item-api-v1/src/main/resources/fabric-item-api-v1.mixins.json b/fabric-item-api-v1/src/main/resources/fabric-item-api-v1.mixins.json index 0a0e6e667..845c30509 100644 --- a/fabric-item-api-v1/src/main/resources/fabric-item-api-v1.mixins.json +++ b/fabric-item-api-v1/src/main/resources/fabric-item-api-v1.mixins.json @@ -9,11 +9,13 @@ "EnchantCommandMixin", "EnchantmentHelperMixin", "EnchantRandomlyLootFunctionMixin", + "ItemAccessor", "ItemMixin", "ItemSettingsMixin", "ItemStackMixin", "LivingEntityMixin", - "RecipeMixin" + "RecipeMixin", + "RegistriesMixin" ], "injectors": { "defaultRequire": 1 diff --git a/fabric-item-api-v1/src/testmod/java/net/fabricmc/fabric/test/item/DefaultItemComponentTest.java b/fabric-item-api-v1/src/testmod/java/net/fabricmc/fabric/test/item/DefaultItemComponentTest.java new file mode 100644 index 000000000..230ebfc28 --- /dev/null +++ b/fabric-item-api-v1/src/testmod/java/net/fabricmc/fabric/test/item/DefaultItemComponentTest.java @@ -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.List; + +import it.unimi.dsi.fastutil.ints.IntList; + +import net.minecraft.component.DataComponentTypes; +import net.minecraft.component.type.FireworkExplosionComponent; +import net.minecraft.component.type.FireworksComponent; +import net.minecraft.item.Items; +import net.minecraft.text.Text; +import net.minecraft.util.Formatting; +import net.minecraft.util.Identifier; + +import net.fabricmc.api.ModInitializer; +import net.fabricmc.fabric.api.event.Event; +import net.fabricmc.fabric.api.item.v1.DefaultItemComponentEvents; + +public class DefaultItemComponentTest implements ModInitializer { + @Override + public void onInitialize() { + Identifier latePhase = new Identifier("fabric-item-api-v1-testmod", "late"); + DefaultItemComponentEvents.MODIFY.addPhaseOrdering(Event.DEFAULT_PHASE, latePhase); + + DefaultItemComponentEvents.MODIFY.register(context -> { + context.modify(Items.GOLD_INGOT, builder -> { + builder.add(DataComponentTypes.ITEM_NAME, Text.literal("Fool's Gold").formatted(Formatting.GOLD)); + }); + context.modify(Items.GOLD_NUGGET, builder -> { + builder.add(DataComponentTypes.FIREWORKS, new FireworksComponent(1, List.of( + new FireworkExplosionComponent(FireworkExplosionComponent.Type.STAR, IntList.of(0x32a852), IntList.of(0x32a852), true, true) + ))); + }); + context.modify(Items.BEEF, builder -> { + // Remove the food component from beef + builder.add(DataComponentTypes.FOOD, null); + }); + }); + + // Make all fireworks glint + DefaultItemComponentEvents.MODIFY.register(latePhase, context -> { + context.modify(item -> item.getComponents().contains(DataComponentTypes.FIREWORKS), (builder, item) -> { + builder.add(DataComponentTypes.ENCHANTMENT_GLINT_OVERRIDE, true); + }); + }); + } +} diff --git a/fabric-item-api-v1/src/testmod/java/net/fabricmc/fabric/test/item/gametest/DefaultItemComponentGameTest.java b/fabric-item-api-v1/src/testmod/java/net/fabricmc/fabric/test/item/gametest/DefaultItemComponentGameTest.java new file mode 100644 index 000000000..f6856bad0 --- /dev/null +++ b/fabric-item-api-v1/src/testmod/java/net/fabricmc/fabric/test/item/gametest/DefaultItemComponentGameTest.java @@ -0,0 +1,76 @@ +/* + * 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.gametest; + +import java.util.function.Consumer; + +import net.minecraft.component.DataComponentTypes; +import net.minecraft.component.type.FireworksComponent; +import net.minecraft.item.ItemStack; +import net.minecraft.item.Items; +import net.minecraft.test.GameTest; +import net.minecraft.test.GameTestException; +import net.minecraft.test.TestContext; +import net.minecraft.text.Text; + +import net.fabricmc.fabric.api.gametest.v1.FabricGameTest; + +public class DefaultItemComponentGameTest implements FabricGameTest { + @GameTest(templateName = EMPTY_STRUCTURE) + public void modify(TestContext context) { + Consumer checkText = text -> { + if (text == null) { + throw new GameTestException("Item name component not found on gold ingot"); + } + + if (!"Fool's Gold".equals(text.getString())) { + throw new GameTestException("Item name component on gold ingot is not set"); + } + }; + + Text text = Items.GOLD_INGOT.getComponents().get(DataComponentTypes.ITEM_NAME); + checkText.accept(text); + + text = new ItemStack(Items.GOLD_INGOT).getComponents().get(DataComponentTypes.ITEM_NAME); + checkText.accept(text); + + boolean isBeefFood = Items.BEEF.getComponents().contains(DataComponentTypes.FOOD); + + if (isBeefFood) { + throw new GameTestException("Food component not removed from beef"); + } + + context.complete(); + } + + @GameTest(templateName = EMPTY_STRUCTURE) + public void afterModify(TestContext context) { + FireworksComponent fireworksComponent = Items.GOLD_NUGGET.getComponents().get(DataComponentTypes.FIREWORKS); + + if (fireworksComponent == null) { + throw new GameTestException("Fireworks component not found on gold nugget"); + } + + Boolean enchantGlint = Items.GOLD_NUGGET.getComponents().get(DataComponentTypes.ENCHANTMENT_GLINT_OVERRIDE); + + if (enchantGlint != Boolean.TRUE) { + throw new GameTestException("Enchantment glint override not set on gold nugget"); + } + + context.complete(); + } +} diff --git a/fabric-item-api-v1/src/testmod/resources/fabric.mod.json b/fabric-item-api-v1/src/testmod/resources/fabric.mod.json index 627c17dd5..e0c6ab03e 100644 --- a/fabric-item-api-v1/src/testmod/resources/fabric.mod.json +++ b/fabric-item-api-v1/src/testmod/resources/fabric.mod.json @@ -11,6 +11,7 @@ "entrypoints": { "main": [ "net.fabricmc.fabric.test.item.CustomDamageTest", + "net.fabricmc.fabric.test.item.DefaultItemComponentTest", "net.fabricmc.fabric.test.item.ItemUpdateAnimationTest", "net.fabricmc.fabric.test.item.ArmorKnockbackResistanceTest" ], @@ -19,6 +20,7 @@ ], "fabric-gametest" : [ "net.fabricmc.fabric.test.item.gametest.BrewingStandGameTest", + "net.fabricmc.fabric.test.item.gametest.DefaultItemComponentGameTest", "net.fabricmc.fabric.test.item.gametest.FurnaceGameTest", "net.fabricmc.fabric.test.item.gametest.RecipeGameTest" ]