diff --git a/fabric-item-api-v1/src/main/java/net/fabricmc/fabric/api/item/v1/EnchantingContext.java b/fabric-item-api-v1/src/main/java/net/fabricmc/fabric/api/item/v1/EnchantingContext.java new file mode 100644 index 0000000000..8b55209893 --- /dev/null +++ b/fabric-item-api-v1/src/main/java/net/fabricmc/fabric/api/item/v1/EnchantingContext.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; + +import net.minecraft.enchantment.EnchantmentHelper; +import net.minecraft.item.ItemStack; +import net.minecraft.util.math.random.Random; + +/* + * There is one context for each vanilla call to Enchantment#isAcceptableItem. The reason why RANDOM_ENCHANTMENT + * feels like a kitchen sink is because it corresponds to the one in EnchantmentHelper, which is shared across multiple + * uses. + * + * This also gets in the way of adding further context (nullable Player and BlockPos have been suggested + * in the past). It's not impossible to do so, but a probably a bit more brittle. + */ +/** + * An enum that describes the various contexts in which the game checks whether an enchantment can be applied to an item. + */ +public enum EnchantingContext { + /** + * When generating a random enchantment for the item. This includes the enchanting table, random + * mob equipment, and the {@code enchant_with_levels} loot function. + * + * @see EnchantmentHelper#generateEnchantments(Random, ItemStack, int, boolean) + */ + RANDOM_ENCHANTMENT, + /** + * When trying to apply an enchantment in an anvil. + */ + ANVIL, + /** + * When using the {@code /enchant} command. + */ + ENCHANT_COMMAND, + /** + * When randomly enchanting an item using the {@code enchant_randomly} loot function without a list of enchantments + * to choose from. + */ + LOOT_RANDOM_ENCHANTMENT +} 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 new file mode 100644 index 0000000000..5f624ff6ac --- /dev/null +++ b/fabric-item-api-v1/src/main/java/net/fabricmc/fabric/api/item/v1/EnchantmentEvents.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 net.minecraft.enchantment.Enchantment; +import net.minecraft.item.ItemStack; + +import net.fabricmc.fabric.api.event.Event; +import net.fabricmc.fabric.api.event.EventFactory; +import net.fabricmc.fabric.api.util.TriState; + +/** + * Events relating to enchantments, allowing for finer control of what enchantments can apply to different items. + */ +public final class EnchantmentEvents { + private EnchantmentEvents() { } + + /** + * An event that allows overriding whether an {@link Enchantment} can be applied to an {@link ItemStack}. + * + *
This should only be used to modify the behavior of external items with regards to external enchantments, + * where 'external' means either vanilla or from another mod. For instance, a mod might allow enchanting a pickaxe + * with Sharpness (and only Sharpness) under certain specific conditions.
+ * + *To modify the behavior of your own modded enchantments, use {@link Enchantment#isAcceptableItem(ItemStack)} instead. + * To modify the behavior of your own modded items, use {@link FabricItem#canBeEnchantedWith(ItemStack, Enchantment, EnchantingContext)} instead. + * Note that this event triggers before {@link FabricItem#canBeEnchantedWith(ItemStack, Enchantment, EnchantingContext)}, + * and that method will only be called if no listeners override it.
+ * + *Note that allowing an enchantment using this event does not guarantee the item will receive that enchantment, + * only that it isn't forbidden from doing so.
+ * + * @see AllowEnchanting#allowEnchanting(Enchantment, ItemStack, EnchantingContext) + * @see Enchantment#isAcceptableItem(ItemStack) + * @see FabricItem#canBeEnchantedWith(ItemStack, Enchantment, EnchantingContext) + */ + public static final EventFor example, one might want a modded item to be able to receive Unbreaking, but not Mending, which cannot be + * achieved with the vanilla tag system alone. Alternatively, one might want to do the same thing with enchantments + * from other mods, which don't have a similar tag system in general.
+ * + *Note that this method is only called after the {@link EnchantmentEvents#ALLOW_ENCHANTING} event, and + * only if none of the listeners to that event override the result.
+ * + * @param stack the current stack + * @param enchantment the enchantment to check + * @param context the context in which the enchantment is being checked + * @return whether the enchantment is allowed to apply to the stack + */ + default boolean canBeEnchantedWith(ItemStack stack, Enchantment enchantment, EnchantingContext context) { + return enchantment.isAcceptableItem(stack); + } + /** * Fabric-provided extensions for {@link Item.Settings}. * This interface is automatically implemented on all item settings via Mixin and interface injection. 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 0c646a6aad..50c3959a25 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 @@ -16,9 +16,12 @@ package net.fabricmc.fabric.api.item.v1; +import net.minecraft.enchantment.Enchantment; import net.minecraft.item.Item; import net.minecraft.item.ItemStack; +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. @@ -36,4 +39,24 @@ public interface FabricItemStack { default ItemStack getRecipeRemainder() { return ((ItemStack) this).getItem().getRecipeRemainder((ItemStack) this); } + + /** + * Determines whether this {@link ItemStack} can be enchanted with the given {@link Enchantment}. + * + *When checking whether an enchantment can be applied to an {@link ItemStack}, use this method instead of + * {@link Enchantment#isAcceptableItem(ItemStack)}
+ * + * @param enchantment the enchantment to check + * @param context the context in which the enchantment is being checked + * @return whether the enchantment is allowed to apply to the stack + * @see FabricItem#canBeEnchantedWith(ItemStack, Enchantment, EnchantingContext) + */ + default boolean canBeEnchantedWith(Enchantment enchantment, EnchantingContext context) { + TriState result = EnchantmentEvents.ALLOW_ENCHANTING.invoker().allowEnchanting( + enchantment, + (ItemStack) this, + context + ); + return result.orElseGet(() -> ((ItemStack) this).getItem().canBeEnchantedWith((ItemStack) this, enchantment, context)); + } } diff --git a/fabric-item-api-v1/src/main/java/net/fabricmc/fabric/mixin/item/AnvilScreenHandlerMixin.java b/fabric-item-api-v1/src/main/java/net/fabricmc/fabric/mixin/item/AnvilScreenHandlerMixin.java new file mode 100644 index 0000000000..15771f78ea --- /dev/null +++ b/fabric-item-api-v1/src/main/java/net/fabricmc/fabric/mixin/item/AnvilScreenHandlerMixin.java @@ -0,0 +1,50 @@ +/* + * 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.jetbrains.annotations.Nullable; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Redirect; + +import net.minecraft.enchantment.Enchantment; +import net.minecraft.entity.player.PlayerInventory; +import net.minecraft.item.ItemStack; +import net.minecraft.screen.AnvilScreenHandler; +import net.minecraft.screen.ForgingScreenHandler; +import net.minecraft.screen.ScreenHandlerContext; +import net.minecraft.screen.ScreenHandlerType; + +import net.fabricmc.fabric.api.item.v1.EnchantingContext; + +@Mixin(AnvilScreenHandler.class) +abstract class AnvilScreenHandlerMixin extends ForgingScreenHandler { + AnvilScreenHandlerMixin(@Nullable ScreenHandlerType> type, int syncId, PlayerInventory playerInventory, ScreenHandlerContext context) { + super(type, syncId, playerInventory, context); + } + + @Redirect( + method = "updateResult", + at = @At( + value = "INVOKE", + target = "Lnet/minecraft/enchantment/Enchantment;isAcceptableItem(Lnet/minecraft/item/ItemStack;)Z" + ) + ) + private boolean callAllowEnchantingEvent(Enchantment instance, ItemStack stack) { + return stack.canBeEnchantedWith(instance, EnchantingContext.ANVIL); + } +} diff --git a/fabric-item-api-v1/src/main/java/net/fabricmc/fabric/mixin/item/EnchantCommandMixin.java b/fabric-item-api-v1/src/main/java/net/fabricmc/fabric/mixin/item/EnchantCommandMixin.java new file mode 100644 index 0000000000..2f945c282e --- /dev/null +++ b/fabric-item-api-v1/src/main/java/net/fabricmc/fabric/mixin/item/EnchantCommandMixin.java @@ -0,0 +1,38 @@ +/* + * 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.Redirect; + +import net.minecraft.enchantment.Enchantment; +import net.minecraft.item.ItemStack; +import net.minecraft.server.command.EnchantCommand; + +import net.fabricmc.fabric.api.item.v1.EnchantingContext; + +@Mixin(EnchantCommand.class) +abstract class EnchantCommandMixin { + @Redirect( + method = "execute", + at = @At(value = "INVOKE", target = "Lnet/minecraft/enchantment/Enchantment;isAcceptableItem(Lnet/minecraft/item/ItemStack;)Z") + ) + private static boolean callAllowEnchantingEvent(Enchantment instance, ItemStack stack) { + return stack.canBeEnchantedWith(instance, EnchantingContext.ENCHANT_COMMAND); + } +} diff --git a/fabric-item-api-v1/src/main/java/net/fabricmc/fabric/mixin/item/EnchantRandomlyLootFunctionMixin.java b/fabric-item-api-v1/src/main/java/net/fabricmc/fabric/mixin/item/EnchantRandomlyLootFunctionMixin.java new file mode 100644 index 0000000000..d884bb01b3 --- /dev/null +++ b/fabric-item-api-v1/src/main/java/net/fabricmc/fabric/mixin/item/EnchantRandomlyLootFunctionMixin.java @@ -0,0 +1,38 @@ +/* + * 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.Redirect; + +import net.minecraft.enchantment.Enchantment; +import net.minecraft.item.ItemStack; +import net.minecraft.loot.function.EnchantRandomlyLootFunction; + +import net.fabricmc.fabric.api.item.v1.EnchantingContext; + +@Mixin(EnchantRandomlyLootFunction.class) +abstract class EnchantRandomlyLootFunctionMixin { + @Redirect( + method = "method_53327", + at = @At(value = "INVOKE", target = "Lnet/minecraft/enchantment/Enchantment;isAcceptableItem(Lnet/minecraft/item/ItemStack;)Z") + ) + private static boolean callAllowEnchantingEvent(Enchantment instance, ItemStack stack) { + return stack.canBeEnchantedWith(instance, EnchantingContext.LOOT_RANDOM_ENCHANTMENT); + } +} diff --git a/fabric-item-api-v1/src/main/java/net/fabricmc/fabric/mixin/item/EnchantmentHelperMixin.java b/fabric-item-api-v1/src/main/java/net/fabricmc/fabric/mixin/item/EnchantmentHelperMixin.java new file mode 100644 index 0000000000..b06fe3284d --- /dev/null +++ b/fabric-item-api-v1/src/main/java/net/fabricmc/fabric/mixin/item/EnchantmentHelperMixin.java @@ -0,0 +1,38 @@ +/* + * 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.Redirect; + +import net.minecraft.enchantment.Enchantment; +import net.minecraft.enchantment.EnchantmentHelper; +import net.minecraft.item.ItemStack; + +import net.fabricmc.fabric.api.item.v1.EnchantingContext; + +@Mixin(EnchantmentHelper.class) +abstract class EnchantmentHelperMixin { + @Redirect( + method = "getPossibleEntries", + at = @At(value = "INVOKE", target = "Lnet/minecraft/enchantment/Enchantment;isAcceptableItem(Lnet/minecraft/item/ItemStack;)Z") + ) + private static boolean useCustomEnchantingChecks(Enchantment instance, ItemStack stack) { + return stack.canBeEnchantedWith(instance, EnchantingContext.RANDOM_ENCHANTMENT); + } +} 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 a56c7c35b2..0a0e6e667c 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 @@ -4,7 +4,11 @@ "compatibilityLevel": "JAVA_17", "mixins": [ "AbstractFurnaceBlockEntityMixin", + "AnvilScreenHandlerMixin", "BrewingStandBlockEntityMixin", + "EnchantCommandMixin", + "EnchantmentHelperMixin", + "EnchantRandomlyLootFunctionMixin", "ItemMixin", "ItemSettingsMixin", "ItemStackMixin", diff --git a/fabric-item-api-v1/src/testmod/java/net/fabricmc/fabric/test/item/CustomDamageTest.java b/fabric-item-api-v1/src/testmod/java/net/fabricmc/fabric/test/item/CustomDamageTest.java index 1db6a5253a..1b39db7f48 100644 --- a/fabric-item-api-v1/src/testmod/java/net/fabricmc/fabric/test/item/CustomDamageTest.java +++ b/fabric-item-api-v1/src/testmod/java/net/fabricmc/fabric/test/item/CustomDamageTest.java @@ -17,8 +17,12 @@ package net.fabricmc.fabric.test.item; import net.minecraft.component.DataComponentType; +import net.minecraft.enchantment.Enchantment; +import net.minecraft.enchantment.EnchantmentHelper; +import net.minecraft.enchantment.Enchantments; import net.minecraft.item.Item; import net.minecraft.item.ItemStack; +import net.minecraft.item.Items; import net.minecraft.item.PickaxeItem; import net.minecraft.item.ToolMaterials; import net.minecraft.network.codec.PacketCodecs; @@ -30,21 +34,14 @@ import net.fabricmc.api.ModInitializer; import net.fabricmc.fabric.api.item.v1.CustomDamageHandler; +import net.fabricmc.fabric.api.item.v1.EnchantingContext; +import net.fabricmc.fabric.api.item.v1.EnchantmentEvents; import net.fabricmc.fabric.api.registry.FuelRegistry; +import net.fabricmc.fabric.api.util.TriState; public class CustomDamageTest implements ModInitializer { - public static final Item WEIRD_PICK = new WeirdPick(); public static final DataComponentType