From ebc0bc7d186ada60ce26eb9b0e6a27c1495e861e Mon Sep 17 00:00:00 2001 From: TheDeathlyCow <53499406+TheDeathlyCow@users.noreply.github.com> Date: Tue, 24 Sep 2024 05:46:27 +1200 Subject: [PATCH] Modify Enchantment and Fabric Component Map Builder Extensions (#4085) * modify effects event * give impaling fire aspect * add fabric component map builder * change interface name to match event * gametests for weird impaling enchantment * fix checkstyle issues * fabric map builder javadoc * modify effects javadoc * fix checkstyle issues * prefer extension methods over add * add enchantment source * fix missing asterisk on fabricitemstack javadoc * switch to enchantment builder * fix effects list * fix checkstyle * add note on exclusive set to javadoc * add fabric component builder extensions to default component testmod * remove threadlocal usage from mixin * remove modid prefix from accessors * remove unused import * fix recursive invoker * add test to automatically check modified item name (cherry picked from commit d5debaed0eca08cac9450317d46810a9194c22dd) --- fabric-item-api-v1/build.gradle | 2 +- .../fabric/api/item/v1/EnchantmentEvents.java | 38 +++++ .../fabric/api/item/v1/EnchantmentSource.java | 55 +++++++ .../item/v1/FabricComponentMapBuilder.java | 74 ++++++++++ .../fabric/api/item/v1/FabricItemStack.java | 2 +- .../fabric/impl/item/EnchantmentUtil.java | 80 +++++++++++ .../mixin/item/ComponentMapBuilderMixin.java | 65 +++++++++ .../item/EnchantmentBuilderAccessor.java | 43 ++++++ .../mixin/item/RegistryLoaderMixin.java | 66 +++++++++ .../resources/fabric-item-api-v1.mixins.json | 5 +- .../src/main/resources/fabric.mod.json | 3 +- .../item/CustomEnchantmentEffectsTest.java | 76 ++++++++++ .../test/item/DefaultItemComponentTest.java | 13 ++ .../CustomEnchantmentEffectsGameTest.java | 91 ++++++++++++ .../DefaultItemComponentGameTest.java | 18 +++ .../enchantment/weird_impaling.json | 42 ++++++ .../gametest/structure/bedrock_platform.snbt | 136 ++++++++++++++++++ .../src/testmod/resources/fabric.mod.json | 4 +- 18 files changed, 808 insertions(+), 5 deletions(-) create mode 100644 fabric-item-api-v1/src/main/java/net/fabricmc/fabric/api/item/v1/EnchantmentSource.java create mode 100644 fabric-item-api-v1/src/main/java/net/fabricmc/fabric/api/item/v1/FabricComponentMapBuilder.java create mode 100644 fabric-item-api-v1/src/main/java/net/fabricmc/fabric/impl/item/EnchantmentUtil.java create mode 100644 fabric-item-api-v1/src/main/java/net/fabricmc/fabric/mixin/item/ComponentMapBuilderMixin.java create mode 100644 fabric-item-api-v1/src/main/java/net/fabricmc/fabric/mixin/item/EnchantmentBuilderAccessor.java create mode 100644 fabric-item-api-v1/src/main/java/net/fabricmc/fabric/mixin/item/RegistryLoaderMixin.java create mode 100644 fabric-item-api-v1/src/testmod/java/net/fabricmc/fabric/test/item/CustomEnchantmentEffectsTest.java create mode 100644 fabric-item-api-v1/src/testmod/java/net/fabricmc/fabric/test/item/gametest/CustomEnchantmentEffectsGameTest.java create mode 100644 fabric-item-api-v1/src/testmod/resources/data/fabric-item-api-v1-testmod/enchantment/weird_impaling.json create mode 100644 fabric-item-api-v1/src/testmod/resources/data/fabric-item-api-v1-testmod/gametest/structure/bedrock_platform.snbt diff --git a/fabric-item-api-v1/build.gradle b/fabric-item-api-v1/build.gradle index e54321e6e..4c7c582bc 100644 --- a/fabric-item-api-v1/build.gradle +++ b/fabric-item-api-v1/build.gradle @@ -1,6 +1,6 @@ version = getSubprojectVersion(project) -moduleDependencies(project, ['fabric-api-base']) +moduleDependencies(project, ['fabric-api-base', 'fabric-resource-loader-v0']) testDependencies(project, [ ':fabric-content-registries-v0', diff --git a/fabric-item-api-v1/src/main/java/net/fabricmc/fabric/api/item/v1/EnchantmentEvents.java b/fabric-item-api-v1/src/main/java/net/fabricmc/fabric/api/item/v1/EnchantmentEvents.java index 83cc8c3c8..4c6cac1f0 100644 --- a/fabric-item-api-v1/src/main/java/net/fabricmc/fabric/api/item/v1/EnchantmentEvents.java +++ b/fabric-item-api-v1/src/main/java/net/fabricmc/fabric/api/item/v1/EnchantmentEvents.java @@ -18,6 +18,7 @@ package net.fabricmc.fabric.api.item.v1; import net.minecraft.enchantment.Enchantment; import net.minecraft.item.ItemStack; +import net.minecraft.registry.RegistryKey; import net.minecraft.registry.entry.RegistryEntry; import net.fabricmc.fabric.api.event.Event; @@ -64,6 +65,27 @@ public final class EnchantmentEvents { } ); + /** + * An event that allows an {@link Enchantment} to be modified without needing to fully override an enchantment. + * + *

This should only be used to modify the behavior of external enchantments, where 'external' means + * either vanilla or from another mod. For instance, a mod might add a bleed effect to Sharpness (and only Sharpness). + * For your own enchantments, you should simply define them in your mod's data pack. See the + * Enchantment Definition page on the Minecraft Wiki + * for more information. + * + *

Note: If you wish to modify the exclusive set of the enchantment, consider extending the + * {@linkplain net.minecraft.registry.tag.EnchantmentTags relevant tag} through your mod's data pack instead. + */ + public static final Event MODIFY = EventFactory.createArrayBacked( + Modify.class, + callbacks -> (key, builder, source) -> { + for (Modify callback : callbacks) { + callback.modify(key, builder, source); + } + } + ); + @FunctionalInterface public interface AllowEnchanting { /** @@ -82,4 +104,20 @@ public final class EnchantmentEvents { EnchantingContext enchantingContext ); } + + @FunctionalInterface + public interface Modify { + /** + * Modifies the effects of an {@link Enchantment}. + * + * @param key The ID of the enchantment + * @param builder The enchantment builder + * @param source The source of the enchantment + */ + void modify( + RegistryKey key, + Enchantment.Builder builder, + EnchantmentSource source + ); + } } diff --git a/fabric-item-api-v1/src/main/java/net/fabricmc/fabric/api/item/v1/EnchantmentSource.java b/fabric-item-api-v1/src/main/java/net/fabricmc/fabric/api/item/v1/EnchantmentSource.java new file mode 100644 index 000000000..81db2eb69 --- /dev/null +++ b/fabric-item-api-v1/src/main/java/net/fabricmc/fabric/api/item/v1/EnchantmentSource.java @@ -0,0 +1,55 @@ +/* + * 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; + +/** + * Determines where an enchantment has been loaded from. + */ +public enum EnchantmentSource { + /** + * An enchantment loaded from the vanilla data pack. + */ + VANILLA(true), + /** + * An enchantment loaded from mods' bundled resources. + * + *

This includes the additional builtin data packs registered by mods + * with Fabric Resource Loader. + */ + MOD(true), + /** + * An enchantment loaded from an external data pack. + */ + DATA_PACK(false); + + private final boolean builtin; + + EnchantmentSource(boolean builtin) { + this.builtin = builtin; + } + + /** + * Returns whether this enchantment source is builtin and bundled in the vanilla or mod resources. + * + *

{@link #VANILLA} and {@link #MOD} are builtin. + * + * @return {@code true} if builtin, {@code false} otherwise + */ + public boolean isBuiltin() { + return builtin; + } +} diff --git a/fabric-item-api-v1/src/main/java/net/fabricmc/fabric/api/item/v1/FabricComponentMapBuilder.java b/fabric-item-api-v1/src/main/java/net/fabricmc/fabric/api/item/v1/FabricComponentMapBuilder.java new file mode 100644 index 000000000..d911a80a1 --- /dev/null +++ b/fabric-item-api-v1/src/main/java/net/fabricmc/fabric/api/item/v1/FabricComponentMapBuilder.java @@ -0,0 +1,74 @@ +/* + * 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.List; +import java.util.Objects; +import java.util.function.Supplier; + +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.NotNull; + +import net.minecraft.component.ComponentType; + +/** + * Fabric-provided extensions for {@link net.minecraft.component.ComponentMap.Builder}. + * + *

Note: This interface is automatically implemented on all component map builders via Mixin and interface injection. + */ +@ApiStatus.NonExtendable +public interface FabricComponentMapBuilder { + /** + * Gets the current value for the component type in the builder, or creates and adds a new value if it is not present. + * + * @param type The component type + * @param fallback The supplier for the default data value if the type is not in this map yet. The value given by this supplier + * may not be null. + * @param The type of the component data + * @return Returns the current value in the map builder, or the default value provided by the fallback if not present + * @see #getOrEmpty(ComponentType) + */ + default T getOrCreate(ComponentType type, Supplier<@NotNull T> fallback) { + 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. + * + * @param type The component type + * @param defaultValue The default data value if the type is not in this map yet + * @param The type of the component data + * @return Returns the current value in the map builder, or the default value if not present + */ + default T getOrDefault(ComponentType type, @NotNull T defaultValue) { + Objects.requireNonNull(defaultValue, "Cannot insert null values to component map builder"); + return getOrCreate(type, () -> defaultValue); + } + + /** + * For list component types specifically, returns a mutable list of values currently held in the builder for the given + * component type. If the type is not registered to this builder yet, this will create and add a new empty list to the builder + * for the type, and return that. + * + * @param type The component type. The component must be a list-type. + * @param The type of the component entry data + * @return Returns a mutable list of values for the type. + */ + default List getOrEmpty(ComponentType> type) { + throw new AssertionError("Implemented in Mixin"); + } +} diff --git a/fabric-item-api-v1/src/main/java/net/fabricmc/fabric/api/item/v1/FabricItemStack.java b/fabric-item-api-v1/src/main/java/net/fabricmc/fabric/api/item/v1/FabricItemStack.java index 1be59c035..4fb71479e 100644 --- a/fabric-item-api-v1/src/main/java/net/fabricmc/fabric/api/item/v1/FabricItemStack.java +++ b/fabric-item-api-v1/src/main/java/net/fabricmc/fabric/api/item/v1/FabricItemStack.java @@ -23,7 +23,7 @@ import net.minecraft.registry.entry.RegistryEntry; import net.fabricmc.fabric.api.util.TriState; -/* +/** * Fabric-provided extensions for {@link ItemStack}. * This interface is automatically implemented on all item stacks via Mixin and interface injection. */ diff --git a/fabric-item-api-v1/src/main/java/net/fabricmc/fabric/impl/item/EnchantmentUtil.java b/fabric-item-api-v1/src/main/java/net/fabricmc/fabric/impl/item/EnchantmentUtil.java new file mode 100644 index 000000000..defbb3b1b --- /dev/null +++ b/fabric-item-api-v1/src/main/java/net/fabricmc/fabric/impl/item/EnchantmentUtil.java @@ -0,0 +1,80 @@ +/* + * 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.List; + +import net.minecraft.component.ComponentType; +import net.minecraft.enchantment.Enchantment; +import net.minecraft.registry.RegistryKey; +import net.minecraft.resource.Resource; +import net.minecraft.resource.ResourcePackSource; + +import net.fabricmc.fabric.api.item.v1.EnchantmentEvents; +import net.fabricmc.fabric.api.item.v1.EnchantmentSource; +import net.fabricmc.fabric.impl.resource.loader.BuiltinModResourcePackSource; +import net.fabricmc.fabric.impl.resource.loader.FabricResource; +import net.fabricmc.fabric.impl.resource.loader.ModResourcePackCreator; +import net.fabricmc.fabric.mixin.item.EnchantmentBuilderAccessor; + +public class EnchantmentUtil { + @SuppressWarnings("unchecked") + public static Enchantment modify(RegistryKey key, Enchantment originalEnchantment, EnchantmentSource source) { + Enchantment.Builder builder = Enchantment.builder(originalEnchantment.definition()); + EnchantmentBuilderAccessor accessor = (EnchantmentBuilderAccessor) builder; + + builder.exclusiveSet(originalEnchantment.exclusiveSet()); + accessor.getEffectMap().addAll(originalEnchantment.effects()); + + originalEnchantment.effects().stream() + .forEach(component -> { + if (component.value() instanceof List valueList) { + // component type cast is checked by the value + accessor.invokeGetEffectsList((ComponentType>) component.type()) + .addAll(valueList); + } + }); + + EnchantmentEvents.MODIFY.invoker().modify(key, builder, source); + + return new Enchantment( + originalEnchantment.description(), + accessor.getDefinition(), + accessor.getExclusiveSet(), + accessor.getEffectMap().build() + ); + } + + public static EnchantmentSource determineSource(Resource resource) { + if (resource != null) { + ResourcePackSource packSource = ((FabricResource) resource).getFabricPackSource(); + + if (packSource == ResourcePackSource.BUILTIN) { + return EnchantmentSource.VANILLA; + } else if (packSource == ModResourcePackCreator.RESOURCE_PACK_SOURCE || packSource instanceof BuiltinModResourcePackSource) { + return EnchantmentSource.MOD; + } + } + + // If not builtin or mod, assume external data pack. + // It might also be a virtual enchantment injected via mixin instead of being loaded + // from a resource, but we can't determine that here. + return EnchantmentSource.DATA_PACK; + } + + private EnchantmentUtil() { } +} diff --git a/fabric-item-api-v1/src/main/java/net/fabricmc/fabric/mixin/item/ComponentMapBuilderMixin.java b/fabric-item-api-v1/src/main/java/net/fabricmc/fabric/mixin/item/ComponentMapBuilderMixin.java new file mode 100644 index 000000000..f2fc2a83b --- /dev/null +++ b/fabric-item-api-v1/src/main/java/net/fabricmc/fabric/mixin/item/ComponentMapBuilderMixin.java @@ -0,0 +1,65 @@ +/* + * 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 java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Objects; +import java.util.function.Supplier; + +import it.unimi.dsi.fastutil.objects.Reference2ObjectMap; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; + +import net.minecraft.component.ComponentMap; +import net.minecraft.component.ComponentType; + +import net.fabricmc.fabric.api.item.v1.FabricComponentMapBuilder; + +@Mixin(ComponentMap.Builder.class) +abstract class ComponentMapBuilderMixin implements FabricComponentMapBuilder { + @Shadow + @Final + private Reference2ObjectMap, Object> components; + + @Shadow + public abstract ComponentMap.Builder add(ComponentType type, @Nullable T value); + + @Override + @SuppressWarnings("unchecked") + public T getOrCreate(ComponentType type, Supplier<@NotNull T> fallback) { + if (!this.components.containsKey(type)) { + T defaultValue = fallback.get(); + Objects.requireNonNull(defaultValue, "Cannot insert null values to component map builder"); + this.add(type, defaultValue); + } + + return (T) this.components.get(type); + } + + @Override + public List getOrEmpty(ComponentType> type) { + // creating a new array list guarantees that the list in the map is mutable + List existing = new ArrayList<>(this.getOrCreate(type, Collections::emptyList)); + this.add(type, existing); + return existing; + } +} diff --git a/fabric-item-api-v1/src/main/java/net/fabricmc/fabric/mixin/item/EnchantmentBuilderAccessor.java b/fabric-item-api-v1/src/main/java/net/fabricmc/fabric/mixin/item/EnchantmentBuilderAccessor.java new file mode 100644 index 000000000..b0ef4e514 --- /dev/null +++ b/fabric-item-api-v1/src/main/java/net/fabricmc/fabric/mixin/item/EnchantmentBuilderAccessor.java @@ -0,0 +1,43 @@ +/* + * 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 java.util.List; + +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; +import org.spongepowered.asm.mixin.gen.Invoker; + +import net.minecraft.component.ComponentMap; +import net.minecraft.component.ComponentType; +import net.minecraft.enchantment.Enchantment; +import net.minecraft.registry.entry.RegistryEntryList; + +@Mixin(Enchantment.Builder.class) +public interface EnchantmentBuilderAccessor { + @Accessor("definition") + Enchantment.Definition getDefinition(); + + @Accessor("exclusiveSet") + RegistryEntryList getExclusiveSet(); + + @Accessor("effectMap") + ComponentMap.Builder getEffectMap(); + + @Invoker("getEffectsList") + List invokeGetEffectsList(ComponentType> type); +} diff --git a/fabric-item-api-v1/src/main/java/net/fabricmc/fabric/mixin/item/RegistryLoaderMixin.java b/fabric-item-api-v1/src/main/java/net/fabricmc/fabric/mixin/item/RegistryLoaderMixin.java new file mode 100644 index 000000000..fd2e0930e --- /dev/null +++ b/fabric-item-api-v1/src/main/java/net/fabricmc/fabric/mixin/item/RegistryLoaderMixin.java @@ -0,0 +1,66 @@ +/* + * 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 com.google.gson.JsonElement; +import com.llamalad7.mixinextras.injector.wrapoperation.Operation; +import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; +import com.mojang.serialization.Decoder; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; + +import net.minecraft.enchantment.Enchantment; +import net.minecraft.registry.MutableRegistry; +import net.minecraft.registry.RegistryKey; +import net.minecraft.registry.RegistryLoader; +import net.minecraft.registry.RegistryOps; +import net.minecraft.registry.entry.RegistryEntry; +import net.minecraft.registry.entry.RegistryEntryInfo; +import net.minecraft.resource.Resource; + +import net.fabricmc.fabric.impl.item.EnchantmentUtil; + +@Mixin(RegistryLoader.class) +abstract class RegistryLoaderMixin { + @WrapOperation( + method = "parseAndAdd", + at = @At( + value = "INVOKE", + target = "Lnet/minecraft/registry/MutableRegistry;add(Lnet/minecraft/registry/RegistryKey;Ljava/lang/Object;Lnet/minecraft/registry/entry/RegistryEntryInfo;)Lnet/minecraft/registry/entry/RegistryEntry$Reference;" + ) + ) + @SuppressWarnings("unchecked") + private static RegistryEntry.Reference enchantmentKey( + MutableRegistry instance, + RegistryKey objectKey, + Object object, + RegistryEntryInfo registryEntryInfo, + Operation> original, + MutableRegistry registry, + Decoder decoder, + RegistryOps ops, + RegistryKey registryKey, + Resource resource, + RegistryEntryInfo entryInfo + ) { + if (object instanceof Enchantment enchantment) { + object = EnchantmentUtil.modify((RegistryKey) objectKey, enchantment, EnchantmentUtil.determineSource(resource)); + } + + return original.call(instance, registryKey, object, registryEntryInfo); + } +} 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 920ff6492..72c8d11d0 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 @@ -6,6 +6,8 @@ "AbstractFurnaceBlockEntityMixin", "AnvilScreenHandlerMixin", "BrewingStandBlockEntityMixin", + "ComponentMapBuilderMixin", + "EnchantmentBuilderAccessor", "EnchantCommandMixin", "EnchantmentHelperMixin", "EnchantRandomlyLootFunctionMixin", @@ -15,7 +17,8 @@ "ItemStackMixin", "LivingEntityMixin", "RecipeMixin", - "RegistriesMixin" + "RegistriesMixin", + "RegistryLoaderMixin" ], "injectors": { "defaultRequire": 1 diff --git a/fabric-item-api-v1/src/main/resources/fabric.mod.json b/fabric-item-api-v1/src/main/resources/fabric.mod.json index aa9d95d37..f3c0a889e 100644 --- a/fabric-item-api-v1/src/main/resources/fabric.mod.json +++ b/fabric-item-api-v1/src/main/resources/fabric.mod.json @@ -32,7 +32,8 @@ "loom:injected_interfaces": { "net/minecraft/class_1792": ["net/fabricmc/fabric/api/item/v1/FabricItem"], "net/minecraft/class_1792\u0024class_1793": ["net/fabricmc/fabric/api/item/v1/FabricItem\u0024Settings"], - "net/minecraft/class_1799": ["net/fabricmc/fabric/api/item/v1/FabricItemStack"] + "net/minecraft/class_1799": ["net/fabricmc/fabric/api/item/v1/FabricItemStack"], + "net/minecraft/class_9323\u0024class_9324": ["net/fabricmc/fabric/api/item/v1/FabricComponentMapBuilder"] } } } diff --git a/fabric-item-api-v1/src/testmod/java/net/fabricmc/fabric/test/item/CustomEnchantmentEffectsTest.java b/fabric-item-api-v1/src/testmod/java/net/fabricmc/fabric/test/item/CustomEnchantmentEffectsTest.java new file mode 100644 index 000000000..f1ac0e6e3 --- /dev/null +++ b/fabric-item-api-v1/src/testmod/java/net/fabricmc/fabric/test/item/CustomEnchantmentEffectsTest.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; + +import net.minecraft.component.EnchantmentEffectComponentTypes; +import net.minecraft.enchantment.Enchantment; +import net.minecraft.enchantment.EnchantmentLevelBasedValue; +import net.minecraft.enchantment.effect.EnchantmentEffectTarget; +import net.minecraft.enchantment.effect.entity.IgniteEnchantmentEffect; +import net.minecraft.enchantment.effect.value.AddEnchantmentEffect; +import net.minecraft.entity.EntityType; +import net.minecraft.loot.condition.DamageSourcePropertiesLootCondition; +import net.minecraft.loot.condition.EntityPropertiesLootCondition; +import net.minecraft.loot.context.LootContext; +import net.minecraft.predicate.entity.DamageSourcePredicate; +import net.minecraft.predicate.entity.EntityPredicate; +import net.minecraft.predicate.entity.EntityTypePredicate; +import net.minecraft.registry.RegistryKey; +import net.minecraft.registry.RegistryKeys; +import net.minecraft.util.Identifier; + +import net.fabricmc.api.ModInitializer; +import net.fabricmc.fabric.api.item.v1.EnchantmentEvents; + +public class CustomEnchantmentEffectsTest implements ModInitializer { + // weird impaling is a copy of impaling used for testing (just in case minecraft changes impaling for some reason) + public static final RegistryKey WEIRD_IMPALING = RegistryKey.of( + RegistryKeys.ENCHANTMENT, + Identifier.of("fabric-item-api-v1-testmod", "weird_impaling") + ); + + @Override + public void onInitialize() { + EnchantmentEvents.MODIFY.register( + (key, builder, source) -> { + if (source.isBuiltin() && key == WEIRD_IMPALING) { + // make impaling set things on fire + builder.addEffect( + EnchantmentEffectComponentTypes.POST_ATTACK, + EnchantmentEffectTarget.ATTACKER, + EnchantmentEffectTarget.VICTIM, + new IgniteEnchantmentEffect(EnchantmentLevelBasedValue.linear(4.0f)), + DamageSourcePropertiesLootCondition.builder( + DamageSourcePredicate.Builder.create().isDirect(true) + ) + ); + + // add bonus impaling damage to zombie + builder.addEffect( + EnchantmentEffectComponentTypes.DAMAGE, + new AddEnchantmentEffect(EnchantmentLevelBasedValue.linear(2.5f)), + EntityPropertiesLootCondition.builder( + LootContext.EntityTarget.THIS, + EntityPredicate.Builder.create() + .type(EntityTypePredicate.create(EntityType.ZOMBIE)) + ) + ); + } + } + ); + } +} 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 index 3dc51c75d..b77db958b 100644 --- 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 @@ -51,6 +51,14 @@ public class DefaultItemComponentTest implements ModInitializer { // Remove the food component from beef builder.add(DataComponentTypes.FOOD, null); }); + // add a word to the start of diamond pickaxe name + context.modify(Items.DIAMOND_PICKAXE, builder -> { + Text baseName = builder.getOrCreate( + DataComponentTypes.ITEM_NAME, + Items.DIAMOND_PICKAXE::getName + ); + builder.add(DataComponentTypes.ITEM_NAME, prependModifiedLiteral(baseName)); + }); }); // Make all fireworks glint @@ -60,4 +68,9 @@ public class DefaultItemComponentTest implements ModInitializer { }); }); } + + public static Text prependModifiedLiteral(Text name) { + return Text.literal("Modified ") + .append(name); + } } diff --git a/fabric-item-api-v1/src/testmod/java/net/fabricmc/fabric/test/item/gametest/CustomEnchantmentEffectsGameTest.java b/fabric-item-api-v1/src/testmod/java/net/fabricmc/fabric/test/item/gametest/CustomEnchantmentEffectsGameTest.java new file mode 100644 index 000000000..f2dd926ef --- /dev/null +++ b/fabric-item-api-v1/src/testmod/java/net/fabricmc/fabric/test/item/gametest/CustomEnchantmentEffectsGameTest.java @@ -0,0 +1,91 @@ +/* + * 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.List; +import java.util.Optional; + +import net.minecraft.component.EnchantmentEffectComponentTypes; +import net.minecraft.enchantment.Enchantment; +import net.minecraft.enchantment.effect.EnchantmentEffectEntry; +import net.minecraft.enchantment.effect.EnchantmentValueEffect; +import net.minecraft.entity.Entity; +import net.minecraft.entity.EntityType; +import net.minecraft.entity.mob.CreeperEntity; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.item.ItemStack; +import net.minecraft.item.Items; +import net.minecraft.registry.DynamicRegistryManager; +import net.minecraft.registry.Registry; +import net.minecraft.registry.RegistryKeys; +import net.minecraft.registry.entry.RegistryEntry; +import net.minecraft.test.GameTest; +import net.minecraft.test.GameTestException; +import net.minecraft.test.TestContext; +import net.minecraft.util.Hand; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.GameMode; + +import net.fabricmc.fabric.api.gametest.v1.FabricGameTest; +import net.fabricmc.fabric.test.item.CustomEnchantmentEffectsTest; + +public class CustomEnchantmentEffectsGameTest implements FabricGameTest { + @GameTest(templateName = "fabric-item-api-v1-testmod:bedrock_platform") + public void weirdImpalingSetsFireToTargets(TestContext context) { + BlockPos pos = new BlockPos(3, 3, 3); + CreeperEntity creeper = context.spawnEntity(EntityType.CREEPER, pos); + PlayerEntity player = context.createMockPlayer(GameMode.CREATIVE); + + ItemStack trident = Items.TRIDENT.getDefaultStack(); + Optional> impaling = getEnchantmentRegistry(context) + .getEntry(CustomEnchantmentEffectsTest.WEIRD_IMPALING); + if (impaling.isEmpty()) { + throw new GameTestException("Weird Impaling enchantment is not present"); + } + + trident.addEnchantment(impaling.get(), 1); + + player.setStackInHand(Hand.MAIN_HAND, trident); + + context.expectEntityWithData(pos, EntityType.CREEPER, Entity::isOnFire, false); + player.attack(creeper); + context.expectEntityWithDataEnd(pos, EntityType.CREEPER, Entity::isOnFire, true); + } + + @GameTest(templateName = EMPTY_STRUCTURE) + public void weirdImpalingHasTwoDamageEffects(TestContext context) { + Enchantment impaling = getEnchantmentRegistry(context).get(CustomEnchantmentEffectsTest.WEIRD_IMPALING); + + if (impaling == null) { + throw new GameTestException("Weird Impaling enchantment is not present"); + } + + List> damageEffects = impaling + .getEffect(EnchantmentEffectComponentTypes.DAMAGE); + + context.assertTrue( + damageEffects.size() == 2, + String.format("Weird Impaling has %d damage effect(s), not the expected 2", damageEffects.size()) + ); + context.complete(); + } + + private static Registry getEnchantmentRegistry(TestContext context) { + DynamicRegistryManager registryManager = context.getWorld().getRegistryManager(); + return registryManager.get(RegistryKeys.ENCHANTMENT); + } +} 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 index f6856bad0..c467042dd 100644 --- 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 @@ -20,6 +20,7 @@ import java.util.function.Consumer; import net.minecraft.component.DataComponentTypes; import net.minecraft.component.type.FireworksComponent; +import net.minecraft.item.Item; import net.minecraft.item.ItemStack; import net.minecraft.item.Items; import net.minecraft.test.GameTest; @@ -28,6 +29,7 @@ import net.minecraft.test.TestContext; import net.minecraft.text.Text; import net.fabricmc.fabric.api.gametest.v1.FabricGameTest; +import net.fabricmc.fabric.test.item.DefaultItemComponentTest; public class DefaultItemComponentGameTest implements FabricGameTest { @GameTest(templateName = EMPTY_STRUCTURE) @@ -73,4 +75,20 @@ public class DefaultItemComponentGameTest implements FabricGameTest { context.complete(); } + + @GameTest(templateName = EMPTY_STRUCTURE) + public void diamondPickaxeIsRenamed(TestContext context) { + Item testItem = Items.DIAMOND_PICKAXE; + ItemStack stack = testItem.getDefaultStack(); + + Text itemName = stack.getOrDefault(DataComponentTypes.ITEM_NAME, Text.literal("")); + Text expectedName = DefaultItemComponentTest.prependModifiedLiteral(testItem.getName()); + + String errorMessage = "Expected '%s' to be contained in '%s', but it was not!"; + + // if they contain each other, then they are equal + context.assertTrue(itemName.contains(expectedName), errorMessage.formatted(expectedName, itemName)); + context.assertTrue(expectedName.contains(itemName), errorMessage.formatted(itemName, expectedName)); + context.complete(); + } } diff --git a/fabric-item-api-v1/src/testmod/resources/data/fabric-item-api-v1-testmod/enchantment/weird_impaling.json b/fabric-item-api-v1/src/testmod/resources/data/fabric-item-api-v1-testmod/enchantment/weird_impaling.json new file mode 100644 index 000000000..e091a2db8 --- /dev/null +++ b/fabric-item-api-v1/src/testmod/resources/data/fabric-item-api-v1-testmod/enchantment/weird_impaling.json @@ -0,0 +1,42 @@ +{ + "description": { + "text": "Weird Impaling" + }, + "exclusive_set": "#minecraft:exclusive_set/damage", + "supported_items": "#minecraft:enchantable/trident", + "weight": 2, + "max_level": 5, + "min_cost": { + "base": 1, + "per_level_above_first": 8 + }, + "max_cost": { + "base": 21, + "per_level_above_first": 8 + }, + "anvil_cost": 4, + "slots": [ + "mainhand" + ], + "effects": { + "minecraft:damage": [ + { + "effect": { + "type": "minecraft:add", + "value": { + "type": "minecraft:linear", + "base": 2.5, + "per_level_above_first": 2.5 + } + }, + "requirements": { + "condition": "minecraft:entity_properties", + "entity": "this", + "predicate": { + "type": "#minecraft:sensitive_to_impaling" + } + } + } + ] + } +} diff --git a/fabric-item-api-v1/src/testmod/resources/data/fabric-item-api-v1-testmod/gametest/structure/bedrock_platform.snbt b/fabric-item-api-v1/src/testmod/resources/data/fabric-item-api-v1-testmod/gametest/structure/bedrock_platform.snbt new file mode 100644 index 000000000..4bb24ae7e --- /dev/null +++ b/fabric-item-api-v1/src/testmod/resources/data/fabric-item-api-v1-testmod/gametest/structure/bedrock_platform.snbt @@ -0,0 +1,136 @@ +{ + DataVersion: 3955, + size: [5, 5, 5], + data: [ + {pos: [0, 0, 0], state: "minecraft:bedrock"}, + {pos: [0, 0, 1], state: "minecraft:bedrock"}, + {pos: [0, 0, 2], state: "minecraft:bedrock"}, + {pos: [0, 0, 3], state: "minecraft:bedrock"}, + {pos: [0, 0, 4], state: "minecraft:bedrock"}, + {pos: [1, 0, 0], state: "minecraft:bedrock"}, + {pos: [1, 0, 1], state: "minecraft:bedrock"}, + {pos: [1, 0, 2], state: "minecraft:bedrock"}, + {pos: [1, 0, 3], state: "minecraft:bedrock"}, + {pos: [1, 0, 4], state: "minecraft:bedrock"}, + {pos: [2, 0, 0], state: "minecraft:bedrock"}, + {pos: [2, 0, 1], state: "minecraft:bedrock"}, + {pos: [2, 0, 2], state: "minecraft:bedrock"}, + {pos: [2, 0, 3], state: "minecraft:bedrock"}, + {pos: [2, 0, 4], state: "minecraft:bedrock"}, + {pos: [3, 0, 0], state: "minecraft:bedrock"}, + {pos: [3, 0, 1], state: "minecraft:bedrock"}, + {pos: [3, 0, 2], state: "minecraft:bedrock"}, + {pos: [3, 0, 3], state: "minecraft:bedrock"}, + {pos: [3, 0, 4], state: "minecraft:bedrock"}, + {pos: [4, 0, 0], state: "minecraft:bedrock"}, + {pos: [4, 0, 1], state: "minecraft:bedrock"}, + {pos: [4, 0, 2], state: "minecraft:bedrock"}, + {pos: [4, 0, 3], state: "minecraft:bedrock"}, + {pos: [4, 0, 4], state: "minecraft:bedrock"}, + {pos: [0, 1, 0], state: "minecraft:air"}, + {pos: [0, 1, 1], state: "minecraft:air"}, + {pos: [0, 1, 2], state: "minecraft:air"}, + {pos: [0, 1, 3], state: "minecraft:air"}, + {pos: [0, 1, 4], state: "minecraft:air"}, + {pos: [1, 1, 0], state: "minecraft:air"}, + {pos: [1, 1, 1], state: "minecraft:air"}, + {pos: [1, 1, 2], state: "minecraft:air"}, + {pos: [1, 1, 3], state: "minecraft:air"}, + {pos: [1, 1, 4], state: "minecraft:air"}, + {pos: [2, 1, 0], state: "minecraft:air"}, + {pos: [2, 1, 1], state: "minecraft:air"}, + {pos: [2, 1, 2], state: "minecraft:air"}, + {pos: [2, 1, 3], state: "minecraft:air"}, + {pos: [2, 1, 4], state: "minecraft:air"}, + {pos: [3, 1, 0], state: "minecraft:air"}, + {pos: [3, 1, 1], state: "minecraft:air"}, + {pos: [3, 1, 2], state: "minecraft:air"}, + {pos: [3, 1, 3], state: "minecraft:air"}, + {pos: [3, 1, 4], state: "minecraft:air"}, + {pos: [4, 1, 0], state: "minecraft:air"}, + {pos: [4, 1, 1], state: "minecraft:air"}, + {pos: [4, 1, 2], state: "minecraft:air"}, + {pos: [4, 1, 3], state: "minecraft:air"}, + {pos: [4, 1, 4], state: "minecraft:air"}, + {pos: [0, 2, 0], state: "minecraft:air"}, + {pos: [0, 2, 1], state: "minecraft:air"}, + {pos: [0, 2, 2], state: "minecraft:air"}, + {pos: [0, 2, 3], state: "minecraft:air"}, + {pos: [0, 2, 4], state: "minecraft:air"}, + {pos: [1, 2, 0], state: "minecraft:air"}, + {pos: [1, 2, 1], state: "minecraft:air"}, + {pos: [1, 2, 2], state: "minecraft:air"}, + {pos: [1, 2, 3], state: "minecraft:air"}, + {pos: [1, 2, 4], state: "minecraft:air"}, + {pos: [2, 2, 0], state: "minecraft:air"}, + {pos: [2, 2, 1], state: "minecraft:air"}, + {pos: [2, 2, 2], state: "minecraft:air"}, + {pos: [2, 2, 3], state: "minecraft:air"}, + {pos: [2, 2, 4], state: "minecraft:air"}, + {pos: [3, 2, 0], state: "minecraft:air"}, + {pos: [3, 2, 1], state: "minecraft:air"}, + {pos: [3, 2, 2], state: "minecraft:air"}, + {pos: [3, 2, 3], state: "minecraft:air"}, + {pos: [3, 2, 4], state: "minecraft:air"}, + {pos: [4, 2, 0], state: "minecraft:air"}, + {pos: [4, 2, 1], state: "minecraft:air"}, + {pos: [4, 2, 2], state: "minecraft:air"}, + {pos: [4, 2, 3], state: "minecraft:air"}, + {pos: [4, 2, 4], state: "minecraft:air"}, + {pos: [0, 3, 0], state: "minecraft:air"}, + {pos: [0, 3, 1], state: "minecraft:air"}, + {pos: [0, 3, 2], state: "minecraft:air"}, + {pos: [0, 3, 3], state: "minecraft:air"}, + {pos: [0, 3, 4], state: "minecraft:air"}, + {pos: [1, 3, 0], state: "minecraft:air"}, + {pos: [1, 3, 1], state: "minecraft:air"}, + {pos: [1, 3, 2], state: "minecraft:air"}, + {pos: [1, 3, 3], state: "minecraft:air"}, + {pos: [1, 3, 4], state: "minecraft:air"}, + {pos: [2, 3, 0], state: "minecraft:air"}, + {pos: [2, 3, 1], state: "minecraft:air"}, + {pos: [2, 3, 2], state: "minecraft:air"}, + {pos: [2, 3, 3], state: "minecraft:air"}, + {pos: [2, 3, 4], state: "minecraft:air"}, + {pos: [3, 3, 0], state: "minecraft:air"}, + {pos: [3, 3, 1], state: "minecraft:air"}, + {pos: [3, 3, 2], state: "minecraft:air"}, + {pos: [3, 3, 3], state: "minecraft:air"}, + {pos: [3, 3, 4], state: "minecraft:air"}, + {pos: [4, 3, 0], state: "minecraft:air"}, + {pos: [4, 3, 1], state: "minecraft:air"}, + {pos: [4, 3, 2], state: "minecraft:air"}, + {pos: [4, 3, 3], state: "minecraft:air"}, + {pos: [4, 3, 4], state: "minecraft:air"}, + {pos: [0, 4, 0], state: "minecraft:air"}, + {pos: [0, 4, 1], state: "minecraft:air"}, + {pos: [0, 4, 2], state: "minecraft:air"}, + {pos: [0, 4, 3], state: "minecraft:air"}, + {pos: [0, 4, 4], state: "minecraft:air"}, + {pos: [1, 4, 0], state: "minecraft:air"}, + {pos: [1, 4, 1], state: "minecraft:air"}, + {pos: [1, 4, 2], state: "minecraft:air"}, + {pos: [1, 4, 3], state: "minecraft:air"}, + {pos: [1, 4, 4], state: "minecraft:air"}, + {pos: [2, 4, 0], state: "minecraft:air"}, + {pos: [2, 4, 1], state: "minecraft:air"}, + {pos: [2, 4, 2], state: "minecraft:air"}, + {pos: [2, 4, 3], state: "minecraft:air"}, + {pos: [2, 4, 4], state: "minecraft:air"}, + {pos: [3, 4, 0], state: "minecraft:air"}, + {pos: [3, 4, 1], state: "minecraft:air"}, + {pos: [3, 4, 2], state: "minecraft:air"}, + {pos: [3, 4, 3], state: "minecraft:air"}, + {pos: [3, 4, 4], state: "minecraft:air"}, + {pos: [4, 4, 0], state: "minecraft:air"}, + {pos: [4, 4, 1], state: "minecraft:air"}, + {pos: [4, 4, 2], state: "minecraft:air"}, + {pos: [4, 4, 3], state: "minecraft:air"}, + {pos: [4, 4, 4], state: "minecraft:air"} + ], + entities: [], + palette: [ + "minecraft:bedrock", + "minecraft:air" + ] +} 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 e0c6ab03e..e56615d39 100644 --- a/fabric-item-api-v1/src/testmod/resources/fabric.mod.json +++ b/fabric-item-api-v1/src/testmod/resources/fabric.mod.json @@ -13,13 +13,15 @@ "net.fabricmc.fabric.test.item.CustomDamageTest", "net.fabricmc.fabric.test.item.DefaultItemComponentTest", "net.fabricmc.fabric.test.item.ItemUpdateAnimationTest", - "net.fabricmc.fabric.test.item.ArmorKnockbackResistanceTest" + "net.fabricmc.fabric.test.item.ArmorKnockbackResistanceTest", + "net.fabricmc.fabric.test.item.CustomEnchantmentEffectsTest" ], "client": [ "net.fabricmc.fabric.test.item.client.TooltipTests" ], "fabric-gametest" : [ "net.fabricmc.fabric.test.item.gametest.BrewingStandGameTest", + "net.fabricmc.fabric.test.item.gametest.CustomEnchantmentEffectsGameTest", "net.fabricmc.fabric.test.item.gametest.DefaultItemComponentGameTest", "net.fabricmc.fabric.test.item.gametest.FurnaceGameTest", "net.fabricmc.fabric.test.item.gametest.RecipeGameTest"