From d5debaed0eca08cac9450317d46810a9194c22dd 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
---
 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.
+	 *
+	 * <p>This should only be used to modify the behavior of <em>external</em> 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
+	 * <a href="https://minecraft.wiki/w/Enchantment_definition">Enchantment Definition page</a> on the Minecraft Wiki
+	 * for more information.
+	 *
+	 * <p>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> 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<Enchantment> 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.
+	 *
+	 * <p>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.
+	 *
+	 * <p>{@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}.
+ *
+ * <p>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 <T>      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> T getOrCreate(ComponentType<T> 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 <T>          The type of the component data
+	 * @return Returns the current value in the map builder, or the default value if not present
+	 */
+	default <T> T getOrDefault(ComponentType<T> 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 <T>  The type of the component entry data
+	 * @return Returns a mutable list of values for the type.
+	 */
+	default <T> List<T> getOrEmpty(ComponentType<List<T>> 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<Enchantment> 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<List<Object>>) 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<ComponentType<?>, Object> components;
+
+	@Shadow
+	public abstract <T> ComponentMap.Builder add(ComponentType<T> type, @Nullable T value);
+
+	@Override
+	@SuppressWarnings("unchecked")
+	public <T> T getOrCreate(ComponentType<T> 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 <T> List<T> getOrEmpty(ComponentType<List<T>> type) {
+		// creating a new array list guarantees that the list in the map is mutable
+		List<T> 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<Enchantment> getExclusiveSet();
+
+	@Accessor("effectMap")
+	ComponentMap.Builder getEffectMap();
+
+	@Invoker("getEffectsList")
+	<E> List<E> invokeGetEffectsList(ComponentType<List<E>> 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 <T> RegistryEntry.Reference<T> enchantmentKey(
+			MutableRegistry<T> instance,
+			RegistryKey<T> objectKey,
+			Object object,
+			RegistryEntryInfo registryEntryInfo,
+			Operation<RegistryEntry.Reference<T>> original,
+			MutableRegistry<T> registry,
+			Decoder<T> decoder,
+			RegistryOps<JsonElement> ops,
+			RegistryKey<T> registryKey,
+			Resource resource,
+			RegistryEntryInfo entryInfo
+	) {
+		if (object instanceof Enchantment enchantment) {
+			object = EnchantmentUtil.modify((RegistryKey<Enchantment>) 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 845c30509..997badd92 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 57c971e29..d1d360a3a 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<Enchantment> 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<RegistryEntry.Reference<Enchantment>> 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<EnchantmentEffectEntry<EnchantmentValueEffect>> 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<Enchantment> 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"