Skip to content

Commit

Permalink
Enchantment API (FabricMC#3627)
Browse files Browse the repository at this point in the history
* ALLOW_ENCHANTING event

* Intrinsic enchantments + testmod

also fixed a bug in the testmod that prevented the custom damage handler from ever working

* Item-based override mechanism

* Replaces part of the use cases of the event with a convenient method to override in FabricItem.
* Updated and tested the testmod.

* javadoc

* Move event logic to FabricItemStack

* oops

* Simplify mixin

* Replace ActionResult with TriState

* Use TriState in testmod

* requests

* Clarify jdoc

* Ship without intrinsic enchantments at first

* Checkstyle

* Checkstyle

---------

Co-authored-by: modmuss50 <[email protected]>
  • Loading branch information
Syst3ms and modmuss50 authored Apr 10, 2024
1 parent 6793dde commit 8f5205a
Show file tree
Hide file tree
Showing 12 changed files with 390 additions and 14 deletions.
Original file line number Diff line number Diff line change
@@ -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
}
Original file line number Diff line number Diff line change
@@ -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}.
*
* <p>This should only be used to modify the behavior of <em>external</em> items with regards to <em>external</em> 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.</p>
*
* <p>To modify the behavior of your own modded <em>enchantments</em>, use {@link Enchantment#isAcceptableItem(ItemStack)} instead.
* To modify the behavior of your own modded <em>items</em>, use {@link FabricItem#canBeEnchantedWith(ItemStack, Enchantment, EnchantingContext)} instead.
* Note that this event triggers <em>before</em> {@link FabricItem#canBeEnchantedWith(ItemStack, Enchantment, EnchantingContext)},
* and that method will only be called if no listeners override it.</p>
*
* <p>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.</p>
*
* @see AllowEnchanting#allowEnchanting(Enchantment, ItemStack, EnchantingContext)
* @see Enchantment#isAcceptableItem(ItemStack)
* @see FabricItem#canBeEnchantedWith(ItemStack, Enchantment, EnchantingContext)
*/
public static final Event<AllowEnchanting> ALLOW_ENCHANTING = EventFactory.createArrayBacked(
AllowEnchanting.class,
callbacks -> (enchantment, target, context) -> {
for (AllowEnchanting callback : callbacks) {
TriState result = callback.allowEnchanting(enchantment, target, context);

if (result != TriState.DEFAULT) {
return result;
}
}

return TriState.DEFAULT;
}
);

@FunctionalInterface
public interface AllowEnchanting {
/**
* Checks whether an {@link Enchantment} should be applied to a given {@link ItemStack}.
*
* @param enchantment the enchantment that may be applied
* @param target the target item
* @param enchantingContext the enchanting context in which this check is made
* @return {@link TriState#TRUE} if the enchantment may be applied, {@link TriState#FALSE} if it
* may not, {@link TriState#DEFAULT} to fall back to other callbacks/vanilla behavior
* @see EnchantingContext
*/
TriState allowEnchanting(
Enchantment enchantment,
ItemStack target,
EnchantingContext enchantingContext
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
package net.fabricmc.fabric.api.item.v1;

import net.minecraft.component.type.AttributeModifiersComponent;
import net.minecraft.enchantment.Enchantment;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
Expand Down Expand Up @@ -107,6 +108,26 @@ default ItemStack getRecipeRemainder(ItemStack stack) {
return ((Item) this).hasRecipeRemainder() ? ((Item) this).getRecipeRemainder().getDefaultStack() : ItemStack.EMPTY;
}

/**
* Determines if the item is allowed to receive an {@link Enchantment}. This can be used to manually override what
* enchantments a modded item is able to receive.
*
* <p>For 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.</p>
*
* <p>Note that this method is only called <em>after</em> the {@link EnchantmentEvents#ALLOW_ENCHANTING} event, and
* only if none of the listeners to that event override the result.</p>
*
* @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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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}.
*
* <p>When checking whether an enchantment can be applied to an {@link ItemStack}, use this method instead of
* {@link Enchantment#isAcceptableItem(ItemStack)}</p>
*
* @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));
}
}
Original file line number Diff line number Diff line change
@@ -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);
}
}
Original file line number Diff line number Diff line change
@@ -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);
}
}
Original file line number Diff line number Diff line change
@@ -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);
}
}
Original file line number Diff line number Diff line change
@@ -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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,11 @@
"compatibilityLevel": "JAVA_17",
"mixins": [
"AbstractFurnaceBlockEntityMixin",
"AnvilScreenHandlerMixin",
"BrewingStandBlockEntityMixin",
"EnchantCommandMixin",
"EnchantmentHelperMixin",
"EnchantRandomlyLootFunctionMixin",
"ItemMixin",
"ItemSettingsMixin",
"ItemStackMixin",
Expand Down
Loading

0 comments on commit 8f5205a

Please sign in to comment.