diff --git a/patches/net/minecraft/advancements/critereon/ItemEnchantmentsPredicate.java.patch b/patches/net/minecraft/advancements/critereon/ItemEnchantmentsPredicate.java.patch index 6007136e46..f0fc6271ca 100644 --- a/patches/net/minecraft/advancements/critereon/ItemEnchantmentsPredicate.java.patch +++ b/patches/net/minecraft/advancements/critereon/ItemEnchantmentsPredicate.java.patch @@ -1,14 +1,19 @@ --- a/net/minecraft/advancements/critereon/ItemEnchantmentsPredicate.java +++ b/net/minecraft/advancements/critereon/ItemEnchantmentsPredicate.java -@@ -48,6 +_,11 @@ - super(p_333967_); +@@ -52,6 +_,16 @@ + public DataComponentType componentType() { + return DataComponents.ENCHANTMENTS; } - -+ @Override // Neo: use getAllEnchantments for enchantments ++ ++ // Neo: use IItemExtension#getAllEnchantments for enchantments when testing this predicate. ++ @Override + public boolean matches(ItemStack p_333958_) { -+ return matches(p_333958_, p_333958_.getAllEnchantments()); ++ var lookup = net.neoforged.neoforge.common.CommonHooks.resolveLookup(net.minecraft.core.registries.Registries.ENCHANTMENT); ++ if (lookup != null) { ++ return matches(p_333958_, p_333958_.getAllEnchantments(lookup)); ++ } ++ return super.matches(p_333958_); + } -+ - @Override - public DataComponentType componentType() { - return DataComponents.ENCHANTMENTS; + } + + public static class StoredEnchantments extends ItemEnchantmentsPredicate { diff --git a/patches/net/minecraft/core/Holder.java.patch b/patches/net/minecraft/core/Holder.java.patch index 43ab5b2270..56c0ad8e75 100644 --- a/patches/net/minecraft/core/Holder.java.patch +++ b/patches/net/minecraft/core/Holder.java.patch @@ -5,26 +5,10 @@ import net.minecraft.tags.TagKey; -public interface Holder { -+public interface Holder extends net.neoforged.neoforge.registries.datamaps.IWithData { ++public interface Holder extends net.neoforged.neoforge.common.extensions.IHolderExtension { T value(); boolean isBound(); -@@ -41,6 +_,15 @@ - return this.unwrapKey().map(p_316542_ -> p_316542_.location().toString()).orElse("[unregistered]"); - } - -+ /** -+ * {@return the holder that this holder wraps} -+ * -+ * Useful for holders that delegate to another holder. -+ */ -+ default Holder getDelegate() { -+ return this; -+ } -+ - static Holder direct(T p_205710_) { - return new Holder.Direct<>(p_205710_); - } @@ -220,6 +_,14 @@ } } @@ -40,11 +24,12 @@ public void bindTags(Collection> p_205770_) { this.tags = Set.copyOf(p_205770_); } -@@ -232,6 +_,18 @@ - @Override +@@ -233,6 +_,28 @@ public String toString() { return "Reference{" + this.key + "=" + this.value + "}"; -+ } + } ++ ++ // Neo Start + + // Neo: Add DeferredHolder-compatible hashCode() and equals() overrides + @Override @@ -56,6 +41,15 @@ + public boolean equals(Object obj) { + if (this == obj) return true; + return obj instanceof Holder h && h.kind() == Kind.REFERENCE && h.unwrapKey().orElseThrow() == this.key(); - } ++ } ++ ++ @Override ++ @org.jetbrains.annotations.Nullable ++ public HolderLookup.RegistryLookup unwrapLookup() { ++ return this.owner instanceof HolderLookup.RegistryLookup rl ? rl : null; ++ } ++ ++ // Neo End public static enum Type { + STAND_ALONE, diff --git a/patches/net/minecraft/world/item/ArrowItem.java.patch b/patches/net/minecraft/world/item/ArrowItem.java.patch index 38380c0556..615f0130b9 100644 --- a/patches/net/minecraft/world/item/ArrowItem.java.patch +++ b/patches/net/minecraft/world/item/ArrowItem.java.patch @@ -1,13 +1,21 @@ --- a/net/minecraft/world/item/ArrowItem.java +++ b/net/minecraft/world/item/ArrowItem.java -@@ -24,4 +_,10 @@ +@@ -24,4 +_,18 @@ arrow.pickup = AbstractArrow.Pickup.ALLOWED; return arrow; } + -+ public boolean isInfinite(ItemStack stack, ItemStack bow, net.minecraft.world.entity.LivingEntity livingEntity) { -+ // TODO 1.21 - probably needs to decide based on tags/other datapack logic -+ int enchant = net.minecraft.world.item.enchantment.EnchantmentHelper.getItemEnchantmentLevel(livingEntity.registryAccess().registryOrThrow(net.minecraft.core.registries.Registries.ENCHANTMENT).getHolderOrThrow(net.minecraft.world.item.enchantment.Enchantments.INFINITY), bow); -+ return enchant > 0 && this.getClass() == net.minecraft.world.item.ArrowItem.class; ++ /** ++ * Called to determine if this arrow will be infinite when fired. If an arrow is infinite, then the arrow will never be consumed (regardless of enchantments). ++ *

++ * Only called on the logical server. ++ * ++ * @param ammo The ammo stack (containing this item) ++ * @param bow The bow stack ++ * @param livingEntity The entity who is firing the bow ++ * @return True if the arrow is infinite ++ */ ++ public boolean isInfinite(ItemStack ammo, ItemStack bow, net.minecraft.world.entity.LivingEntity livingEntity) { ++ return false; + } } diff --git a/patches/net/minecraft/world/item/ItemStack.java.patch b/patches/net/minecraft/world/item/ItemStack.java.patch index 5c73aeb83d..074261d353 100644 --- a/patches/net/minecraft/world/item/ItemStack.java.patch +++ b/patches/net/minecraft/world/item/ItemStack.java.patch @@ -140,6 +140,24 @@ return list; } } +@@ -897,6 +_,17 @@ + return !this.getOrDefault(DataComponents.ENCHANTMENTS, ItemEnchantments.EMPTY).isEmpty(); + } + ++ /** ++ * Gets all enchantments from NBT. Use {@link ItemStack#getAllEnchantments} for gameplay logic. ++ */ ++ public ItemEnchantments getTagEnchantments() { ++ return getEnchantments(); ++ } ++ ++ /** ++ * @deprecated Neo: Use {@link #getTagEnchantments()} for NBT enchantments, or {@link #getAllEnchantments} for gameplay. ++ */ ++ @Deprecated + public ItemEnchantments getEnchantments() { + return this.getOrDefault(DataComponents.ENCHANTMENTS, ItemEnchantments.EMPTY); + } @@ -933,6 +_,8 @@ } diff --git a/patches/net/minecraft/world/item/ProjectileWeaponItem.java.patch b/patches/net/minecraft/world/item/ProjectileWeaponItem.java.patch index 01cd5e6e88..f8f0773159 100644 --- a/patches/net/minecraft/world/item/ProjectileWeaponItem.java.patch +++ b/patches/net/minecraft/world/item/ProjectileWeaponItem.java.patch @@ -9,12 +9,13 @@ } protected static List draw(ItemStack p_331565_, ItemStack p_330406_, LivingEntity p_330823_) { -@@ -115,7 +_,7 @@ +@@ -115,7 +_,8 @@ } protected static ItemStack useAmmo(ItemStack p_331207_, ItemStack p_331434_, LivingEntity p_330302_, boolean p_330934_) { - int i = !p_330934_ && !p_330302_.hasInfiniteMaterials() && p_330302_.level() instanceof ServerLevel serverlevel -+ int i = !p_330934_ && !(p_330302_.hasInfiniteMaterials() || (p_331434_.getItem() instanceof ArrowItem && ((ArrowItem) p_331434_.getItem()).isInfinite(p_331434_, p_331207_, p_330302_))) && p_330302_.level() instanceof ServerLevel serverlevel ++ // Neo: Adjust this check to respect ArrowItem#isInfinite, bypassing processAmmoUse if true. ++ int i = !p_330934_ && p_330302_.level() instanceof ServerLevel serverlevel && !(p_330302_.hasInfiniteMaterials() || (p_331434_.getItem() instanceof ArrowItem ai && ai.isInfinite(p_331434_, p_331207_, p_330302_))) ? EnchantmentHelper.processAmmoUse(serverlevel, p_331207_, p_331434_, 1) : 0; if (i > p_331434_.getCount()) { diff --git a/patches/net/minecraft/world/item/enchantment/Enchantment.java.patch b/patches/net/minecraft/world/item/enchantment/Enchantment.java.patch index 7277a14558..939f9a5229 100644 --- a/patches/net/minecraft/world/item/enchantment/Enchantment.java.patch +++ b/patches/net/minecraft/world/item/enchantment/Enchantment.java.patch @@ -1,21 +1,22 @@ --- a/net/minecraft/world/item/enchantment/Enchantment.java +++ b/net/minecraft/world/item/enchantment/Enchantment.java -@@ -504,6 +_,26 @@ - return new Enchantment.Builder(p_345873_); +@@ -132,6 +_,10 @@ + return this.definition.slots().stream().anyMatch(p_345027_ -> p_345027_.test(p_345146_)); } -+ // TODO 1.21: Make IEnchantmentExtension additions data-driven along with these methods: ++ /** ++ * @deprecated Neo: Use {@link ItemStack#isPrimaryItemFor(Holder)} ++ */ ++ @Deprecated + public boolean isPrimaryItem(ItemStack p_336088_) { + return this.isSupportedItem(p_336088_) && (this.definition.primaryItems.isEmpty() || p_336088_.is(this.definition.primaryItems.get())); + } +@@ -503,6 +_,15 @@ + public static Enchantment.Builder enchantment(Enchantment.EnchantmentDefinition p_345873_) { + return new Enchantment.Builder(p_345873_); + } + -+// /** -+// * This applies specifically to applying at the enchanting table. The other method {@link #canEnchant(ItemStack)} -+// * applies for all possible enchantments. -+// * @param stack -+// * @return -+// */ -+// public boolean canApplyAtEnchantingTable(ItemStack stack) { -+// return stack.canApplyAtEnchantingTable(this); -+// } -+// ++// TODO: Reimplement. Not sure if we want to patch EnchantmentDefinition or hack this in as an EnchantmentEffectComponent. +// /** +// * Is this enchantment allowed to be enchanted on books via Enchantment Table +// * @return false to disable the vanilla feature @@ -23,7 +24,6 @@ +// public boolean isAllowedOnBooks() { +// return true; +// } -+ + public static class Builder { private final Enchantment.EnchantmentDefinition definition; - private HolderSet exclusiveSet = HolderSet.direct(); diff --git a/patches/net/minecraft/world/item/enchantment/EnchantmentHelper.java.patch b/patches/net/minecraft/world/item/enchantment/EnchantmentHelper.java.patch index d48c8c0d34..be15334ec6 100644 --- a/patches/net/minecraft/world/item/enchantment/EnchantmentHelper.java.patch +++ b/patches/net/minecraft/world/item/enchantment/EnchantmentHelper.java.patch @@ -1,33 +1,62 @@ --- a/net/minecraft/world/item/enchantment/EnchantmentHelper.java +++ b/net/minecraft/world/item/enchantment/EnchantmentHelper.java -@@ -48,8 +_,7 @@ +@@ -47,7 +_,19 @@ + import org.apache.commons.lang3.mutable.MutableObject; public class EnchantmentHelper { ++ /** ++ * @deprecated Neo: Use {@link #getTagEnchantmentLevel(Holder, ItemStack)} for NBT enchantments, or {@link ItemStack#getEnchantmentLevel(Holder)} for gameplay. ++ */ ++ @Deprecated public static int getItemEnchantmentLevel(Holder p_346179_, ItemStack p_44845_) { -- ItemEnchantments itemenchantments = p_44845_.getOrDefault(DataComponents.ENCHANTMENTS, ItemEnchantments.EMPTY); -- return itemenchantments.getLevel(p_346179_); ++ // Neo: To reduce patch size, update this method to always check gameplay enchantments, and add getTagEnchantmentLevel as a helper for mods. + return p_44845_.getEnchantmentLevel(p_346179_); ++ } ++ ++ /** ++ * Gets the level of an enchantment from NBT. Use {@link ItemStack#getEnchantmentLevel(Holder)} for gameplay logic. ++ */ ++ public static int getTagEnchantmentLevel(Holder p_346179_, ItemStack p_44845_) { + ItemEnchantments itemenchantments = p_44845_.getOrDefault(DataComponents.ENCHANTMENTS, ItemEnchantments.EMPTY); + return itemenchantments.getLevel(p_346179_); } - - public static ItemEnchantments updateEnchantments(ItemStack p_331034_, Consumer p_332031_) { -@@ -120,7 +_,7 @@ - } - +@@ -122,6 +_,12 @@ private static void runIterationOnItem(ItemStack p_345425_, EnchantmentHelper.EnchantmentVisitor p_345023_) { -- ItemEnchantments itemenchantments = p_345425_.getOrDefault(DataComponents.ENCHANTMENTS, ItemEnchantments.EMPTY); -+ ItemEnchantments itemenchantments = p_345425_.getAllEnchantments(); // Neo: Allow gameplay enchantments to run too. + ItemEnchantments itemenchantments = p_345425_.getOrDefault(DataComponents.ENCHANTMENTS, ItemEnchantments.EMPTY); ++ // Neo: Respect gameplay-only enchantments when doing iterations ++ var lookup = net.neoforged.neoforge.common.CommonHooks.resolveLookup(net.minecraft.core.registries.Registries.ENCHANTMENT); ++ if (lookup != null) { ++ itemenchantments = p_345425_.getAllEnchantments(lookup); ++ } ++ for (Entry> entry : itemenchantments.entrySet()) { p_345023_.accept(entry.getKey(), entry.getIntValue()); -@@ -131,7 +_,7 @@ - ItemStack p_44852_, EquipmentSlot p_345566_, LivingEntity p_345792_, EnchantmentHelper.EnchantmentInSlotVisitor p_345683_ + } +@@ -132,6 +_,10 @@ ) { if (!p_44852_.isEmpty()) { -- ItemEnchantments itemenchantments = p_44852_.get(DataComponents.ENCHANTMENTS); -+ ItemEnchantments itemenchantments = p_44852_.getAllEnchantments(); // Neo: Allow gameplay enchantments to run too. + ItemEnchantments itemenchantments = p_44852_.get(DataComponents.ENCHANTMENTS); ++ ++ // Neo: Respect gameplay-only enchantments when doing iterations ++ itemenchantments = p_44852_.getAllEnchantments(p_345792_.registryAccess().lookupOrThrow(net.minecraft.core.registries.Registries.ENCHANTMENT)); ++ if (itemenchantments != null && !itemenchantments.isEmpty()) { EnchantedItemInUse enchantediteminuse = new EnchantedItemInUse(p_44852_, p_345566_, p_345792_); +@@ -417,6 +_,12 @@ + public static boolean hasTag(ItemStack p_345665_, TagKey p_345928_) { + ItemEnchantments itemenchantments = p_345665_.getOrDefault(DataComponents.ENCHANTMENTS, ItemEnchantments.EMPTY); + ++ // Neo: Respect gameplay-only enchantments when enchantment effect tag checks ++ var lookup = net.neoforged.neoforge.common.CommonHooks.resolveLookup(net.minecraft.core.registries.Registries.ENCHANTMENT); ++ if (lookup != null) { ++ itemenchantments = p_345665_.getAllEnchantments(lookup); ++ } ++ + for (Entry> entry : itemenchantments.entrySet()) { + Holder holder = entry.getKey(); + if (holder.is(p_345928_)) { @@ -484,7 +_,7 @@ public static int getEnchantmentCost(RandomSource p_220288_, int p_220289_, int p_220290_, ItemStack p_220291_) { @@ -46,3 +75,14 @@ if (i <= 0) { return list; } else { +@@ -575,7 +_,9 @@ + public static List getAvailableEnchantmentResults(int p_44818_, ItemStack p_44819_, Stream> p_345348_) { + List list = Lists.newArrayList(); + boolean flag = p_44819_.is(Items.BOOK); +- p_345348_.filter(p_344529_ -> p_344529_.value().isPrimaryItem(p_44819_) || flag).forEach(p_344478_ -> { ++ // Neo: Rewrite filter logic to call isPrimaryItemFor instead of hardcoded vanilla logic. ++ // The original logic is recorded in the default implementation of IItemExtension#isPrimaryItemFor. ++ p_345348_.filter(p_44819_::isPrimaryItemFor).forEach(p_344478_ -> { + Enchantment enchantment = p_344478_.value(); + + for (int i = enchantment.getMaxLevel(); i >= enchantment.getMinLevel(); i--) { diff --git a/src/main/java/net/neoforged/neoforge/client/ClientHooks.java b/src/main/java/net/neoforged/neoforge/client/ClientHooks.java index 1f56c480e9..d4fa3aeeff 100644 --- a/src/main/java/net/neoforged/neoforge/client/ClientHooks.java +++ b/src/main/java/net/neoforged/neoforge/client/ClientHooks.java @@ -90,12 +90,15 @@ import net.minecraft.client.sounds.SoundEngine; import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; +import net.minecraft.core.HolderLookup.RegistryLookup; +import net.minecraft.core.Registry; import net.minecraft.locale.Language; import net.minecraft.network.Connection; import net.minecraft.network.chat.ChatType; import net.minecraft.network.chat.Component; import net.minecraft.network.chat.FormattedText; import net.minecraft.network.chat.PlayerChatMessage; +import net.minecraft.resources.ResourceKey; import net.minecraft.resources.ResourceLocation; import net.minecraft.server.packs.resources.ReloadableResourceManager; import net.minecraft.util.Mth; @@ -1048,4 +1051,14 @@ public static void fireClientTickPre() { public static void fireClientTickPost() { NeoForge.EVENT_BUS.post(new ClientTickEvent.Post()); } + + @Nullable + @SuppressWarnings("resource") + public static RegistryLookup resolveLookup(ResourceKey> key) { + ClientLevel level = Minecraft.getInstance().level; + if (level != null) { + return level.registryAccess().lookup(key).orElse(null); + } + return null; + } } diff --git a/src/main/java/net/neoforged/neoforge/common/CommonHooks.java b/src/main/java/net/neoforged/neoforge/common/CommonHooks.java index c75cda7c7a..d59c272a0e 100644 --- a/src/main/java/net/neoforged/neoforge/common/CommonHooks.java +++ b/src/main/java/net/neoforged/neoforge/common/CommonHooks.java @@ -42,6 +42,7 @@ import net.minecraft.core.Direction; import net.minecraft.core.Holder; import net.minecraft.core.HolderLookup; +import net.minecraft.core.HolderLookup.RegistryLookup; import net.minecraft.core.HolderSet; import net.minecraft.core.Registry; import net.minecraft.core.SectionPos; @@ -63,6 +64,7 @@ import net.minecraft.network.syncher.EntityDataSerializer; import net.minecraft.resources.ResourceKey; import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.MinecraftServer; import net.minecraft.server.level.ServerLevel; import net.minecraft.server.level.ServerPlayer; import net.minecraft.server.packs.PackType; @@ -124,6 +126,7 @@ import net.minecraft.world.level.block.Blocks; import net.minecraft.world.level.block.GameMasterBlock; import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.block.state.BlockBehaviour; import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.block.state.pattern.BlockInWorld; import net.minecraft.world.level.chunk.ChunkAccess; @@ -142,6 +145,8 @@ import net.neoforged.fml.ModList; import net.neoforged.fml.ModLoader; import net.neoforged.fml.i18n.MavenVersionTranslator; +import net.neoforged.fml.loading.FMLEnvironment; +import net.neoforged.neoforge.client.ClientHooks; import net.neoforged.neoforge.common.conditions.ConditionalOps; import net.neoforged.neoforge.common.extensions.IEntityExtension; import net.neoforged.neoforge.common.loot.IGlobalLootModifier; @@ -193,6 +198,7 @@ import net.neoforged.neoforge.fluids.FluidType; import net.neoforged.neoforge.registries.NeoForgeRegistries; import net.neoforged.neoforge.resource.ResourcePackLoader; +import net.neoforged.neoforge.server.ServerLifecycleHooks; import net.neoforged.neoforge.server.permission.PermissionAPI; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -1380,4 +1386,24 @@ public static boolean canMobEffectBeApplied(LivingEntity entity, MobEffectInstan var event = new MobEffectEvent.Applicable(entity, effect); return NeoForge.EVENT_BUS.post(event).getApplicationResult(); } + + /** + * Attempts to resolve a {@link RegistryLookup} using the current global state. + *

+ * Prioritizes the server's lookup, only attempting to retrieve it from the client if the server is unavailable. + * + * @param The type of registry being looked up + * @param key The resource key for the target registry + * @return A registry access, if one was available. + */ + @Nullable + public static RegistryLookup resolveLookup(ResourceKey> key) { + MinecraftServer server = ServerLifecycleHooks.getCurrentServer(); + if (server != null) { + return server.registryAccess().lookup(key).orElse(null); + } else if (FMLEnvironment.dist.isClient()) { + return ClientHooks.resolveLookup(key); + } + return null; + } } diff --git a/src/main/java/net/neoforged/neoforge/common/extensions/IHolderExtension.java b/src/main/java/net/neoforged/neoforge/common/extensions/IHolderExtension.java new file mode 100644 index 0000000000..5da03f58bf --- /dev/null +++ b/src/main/java/net/neoforged/neoforge/common/extensions/IHolderExtension.java @@ -0,0 +1,36 @@ +/* + * Copyright (c) NeoForged and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ + +package net.neoforged.neoforge.common.extensions; + +import net.minecraft.core.Holder; +import net.minecraft.core.HolderLookup; +import net.minecraft.core.Registry; +import net.neoforged.neoforge.registries.datamaps.IWithData; +import org.jetbrains.annotations.Nullable; + +/** + * Extension for {@link Holder} + */ +public interface IHolderExtension extends IWithData { + /** + * {@return the holder that this holder wraps} + * + * Used by {@link Registry#safeCastToReference} to resolve the underlying {@link Holder.Reference} for delegating holders. + */ + default Holder getDelegate() { + return (Holder) this; + } + + /** + * Attempts to resolve the underlying {@link HolderLookup.RegistryLookup} from a {@link Holder}. + *

+ * This will only succeed if the underlying holder is a {@link Holder.Reference}. + */ + @Nullable + default HolderLookup.RegistryLookup unwrapLookup() { + return null; + } +} diff --git a/src/main/java/net/neoforged/neoforge/common/extensions/IItemExtension.java b/src/main/java/net/neoforged/neoforge/common/extensions/IItemExtension.java index 575a436143..4f7d03ef2f 100644 --- a/src/main/java/net/neoforged/neoforge/common/extensions/IItemExtension.java +++ b/src/main/java/net/neoforged/neoforge/common/extensions/IItemExtension.java @@ -11,6 +11,7 @@ import java.util.function.Consumer; import net.minecraft.core.BlockPos; import net.minecraft.core.Holder; +import net.minecraft.core.HolderLookup.RegistryLookup; import net.minecraft.core.component.DataComponentMap; import net.minecraft.core.component.DataComponentType; import net.minecraft.core.component.DataComponents; @@ -411,52 +412,56 @@ default int getEnchantmentValue(ItemStack stack) { } /** - * Checks whether an item can be enchanted with a certain enchantment. This - * applies specifically to enchanting an item in the enchanting table and is - * called when retrieving the list of possible enchantments for an item. - * Enchantments may additionally (or exclusively) be doing their own checks in - * {@link Enchantment#canApplyAtEnchantingTable(ItemStack)}; - * check the individual implementation for reference. By default this will check - * if the enchantment type is valid for this item type. + * Checks if an item should be treated as a primary item for a given enchantment. + *

+ * Primary items are those that are able to receive the enchantment during enchanting, + * either from the enchantment table or other random enchantment mechanisms. + * As a special case, books are primary items for every enchantment. + *

+ * Other application mechanisms, such as the anvil, check {@link Enchantment#isSupportedItem(ItemStack)} instead. + * If you want those mechanisms to be able to apply an enchantment, you will need to add your item to the relevant tag. * * @param stack the item stack to be enchanted * @param enchantment the enchantment to be applied - * @return true if the enchantment can be applied to this item + * @return true if this item should be treated as a primary item for the enchantment + * @apiNote Call via {@link IItemStackExtension#isPrimaryItemFor(Holder)} */ - default boolean canApplyAtEnchantingTable(ItemStack stack, Enchantment enchantment) { - return enchantment.getSupportedItems().contains(stack.getItem().builtInRegistryHolder()); + @ApiStatus.OverrideOnly + default boolean isPrimaryItemFor(ItemStack stack, Holder enchantment) { + return stack.getItem() == Items.BOOK || enchantment.value().isPrimaryItem(stack); } /** * Gets the level of the enchantment currently present on the stack. By default, returns the enchantment level present in NBT. * Most enchantment implementations rely upon this method. - * The returned value must be the same as getting the enchantment from {@link #getAllEnchantments(ItemStack)} + * The returned value must be the same as getting the enchantment from {@link #getAllEnchantments} * * @param stack The item stack being checked * @param enchantment The enchantment being checked for * @return Level of the enchantment, or 0 if not present - * @see #getAllEnchantments(ItemStack) - * @apiNote Call via {@link IItemStackExtension#getEnchantmentLevel(Enchantment)}. + * @see #getAllEnchantments + * @apiNote Call via {@link IItemStackExtension#getEnchantmentLevel}. */ @ApiStatus.OverrideOnly default int getEnchantmentLevel(ItemStack stack, Holder enchantment) { - ItemEnchantments itemenchantments = stack.getOrDefault(DataComponents.ENCHANTMENTS, ItemEnchantments.EMPTY); + ItemEnchantments itemenchantments = stack.getTagEnchantments(); return itemenchantments.getLevel(enchantment); } /** * Gets a map of all enchantments present on the stack. By default, returns the enchantments present in NBT. * Used in several places in code including armor enchantment hooks. - * The returned value(s) must have the same level as {@link #getEnchantmentLevel(ItemStack, Enchantment)}. + * The returned value(s) must have the same level as {@link #getEnchantmentLevel}. * - * @param stack The item stack being checked + * @param stack The item stack being checked + * @param lookup A registry lookup, used to resolve enchantment {@link Holder}s. * @return Map of all enchantments on the stack, empty if no enchantments are present - * @see #getEnchantmentLevel(ItemStack, Enchantment) - * @apiNote Call via {@link IItemStackExtension#getAllEnchantments()}. + * @see #getEnchantmentLevel + * @apiNote Call via {@link IItemStackExtension#getAllEnchantments}. */ @ApiStatus.OverrideOnly - default ItemEnchantments getAllEnchantments(ItemStack stack) { - return stack.getOrDefault(DataComponents.ENCHANTMENTS, ItemEnchantments.EMPTY); + default ItemEnchantments getAllEnchantments(ItemStack stack, RegistryLookup lookup) { + return stack.getTagEnchantments(); } /** diff --git a/src/main/java/net/neoforged/neoforge/common/extensions/IItemStackExtension.java b/src/main/java/net/neoforged/neoforge/common/extensions/IItemStackExtension.java index fec7137877..5d4673c752 100644 --- a/src/main/java/net/neoforged/neoforge/common/extensions/IItemStackExtension.java +++ b/src/main/java/net/neoforged/neoforge/common/extensions/IItemStackExtension.java @@ -9,6 +9,7 @@ import com.google.common.collect.Multimap; import net.minecraft.core.BlockPos; import net.minecraft.core.Holder; +import net.minecraft.core.HolderLookup.RegistryLookup; import net.minecraft.core.component.DataComponents; import net.minecraft.core.registries.BuiltInRegistries; import net.minecraft.network.chat.Component; @@ -137,34 +138,22 @@ default boolean shouldCauseBlockBreakReset(ItemStack newStack) { } /** - * Checks whether an item can be enchanted with a certain enchantment. This - * applies specifically to enchanting an item in the enchanting table and is - * called when retrieving the list of possible enchantments for an item. - * Enchantments may additionally (or exclusively) be doing their own checks in - * {@link Enchantment#canApplyAtEnchantingTable(ItemStack)}; - * check the individual implementation for reference. By default this will check - * if the enchantment type is valid for this item type. - * - * @param enchantment the enchantment to be applied - * @return true if the enchantment can be applied to this item + * @see {@link IItemExtension#isPrimaryItemFor(ItemStack, Holder)} */ - default boolean canApplyAtEnchantingTable(Enchantment enchantment) { - return self().getItem().canApplyAtEnchantingTable(self(), enchantment); + default boolean isPrimaryItemFor(Holder enchantment) { + return self().getItem().isPrimaryItemFor(self(), enchantment); } /** * Gets the gameplay level of the target enchantment on this stack. *

- * Equivalent to calling {@link EnchantmentHelper#getItemEnchantmentLevel(Enchantment, ItemStack)}. - *

- * Use in place of {@link EnchantmentHelper#getItemEnchantmentLevel(Enchantment, ItemStack)} for gameplay logic. + * Use in place of {@link EnchantmentHelper#getTagEnchantmentLevel} for gameplay logic. *

- * Use {@link DataComponents#ENCHANTMENTS} instead when modifying the item's enchantments. + * Use {@link EnchantmentHelper#getEnchantmentsForCrafting} and {@link EnchantmentHelper#setEnchantments} when modifying the item's enchantments. * * @param enchantment The enchantment being checked for. * @return The level of the enchantment, or 0 if not present. - * @see #getAllEnchantments() - * @see DataComponents#ENCHANTMENTS + * @see {@link #getAllEnchantments} to get all gameplay enchantments */ default int getEnchantmentLevel(Holder enchantment) { int level = self().getItem().getEnchantmentLevel(self(), enchantment); @@ -174,17 +163,16 @@ default int getEnchantmentLevel(Holder enchantment) { /** * Gets the gameplay level of all enchantments on this stack. *

- * Use in place of {@link DataComponents#ENCHANTMENTS} for gameplay logic. + * Use in place of {@link ItemStack#getTagEnchantments()} for gameplay logic. *

- * Use {@link DataComponents#ENCHANTMENTS} instead when modifying the item's enchantments. + * Use {@link EnchantmentHelper#getEnchantmentsForCrafting} and {@link EnchantmentHelper#setEnchantments} when modifying the item's enchantments. * * @return Map of all enchantments on the stack, or an empty map if no enchantments are present. - * @see #getEnchantmentLevel(Enchantment) - * @see DataComponents#ENCHANTMENTS + * @see {@link #getEnchantmentLevel} to get the level of a single enchantment for gameplay purposes */ - default ItemEnchantments getAllEnchantments() { - var enchantments = self().getItem().getAllEnchantments(self()); - return EventHooks.getEnchantmentLevel(enchantments, self()); + default ItemEnchantments getAllEnchantments(RegistryLookup lookup) { + var enchantments = self().getItem().getAllEnchantments(self(), lookup); + return EventHooks.getAllEnchantmentLevels(enchantments, self(), lookup); } /** diff --git a/src/main/java/net/neoforged/neoforge/event/EventHooks.java b/src/main/java/net/neoforged/neoforge/event/EventHooks.java index 906a762506..36a3023286 100644 --- a/src/main/java/net/neoforged/neoforge/event/EventHooks.java +++ b/src/main/java/net/neoforged/neoforge/event/EventHooks.java @@ -25,6 +25,7 @@ import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; import net.minecraft.core.Holder; +import net.minecraft.core.HolderLookup.RegistryLookup; import net.minecraft.core.NonNullList; import net.minecraft.core.RegistryAccess; import net.minecraft.network.chat.Component; @@ -1036,9 +1037,14 @@ public static boolean onEffectRemoved(LivingEntity entity, MobEffectInstance eff * @return The new level of the enchantment. */ public static int getEnchantmentLevelSpecific(int level, ItemStack stack, Holder ench) { + RegistryLookup lookup = ench.unwrapLookup(); + if (lookup == null) { // Pretty sure this is never null, but I can't *prove* that it isn't. + return level; + } + var enchantments = new ItemEnchantments.Mutable(ItemEnchantments.EMPTY); enchantments.set(ench, level); - var event = new GetEnchantmentLevelEvent(stack, enchantments, ench); + var event = new GetEnchantmentLevelEvent(stack, enchantments, ench, ench.unwrapLookup()); NeoForge.EVENT_BUS.post(event); return enchantments.getLevel(ench); } @@ -1050,9 +1056,9 @@ public static int getEnchantmentLevelSpecific(int level, ItemStack stack, Holder * @param stack The stack being queried against. * @return The new enchantment map. */ - public static ItemEnchantments getEnchantmentLevel(ItemEnchantments enchantments, ItemStack stack) { + public static ItemEnchantments getAllEnchantmentLevels(ItemEnchantments enchantments, ItemStack stack, RegistryLookup lookup) { var mutableEnchantments = new ItemEnchantments.Mutable(enchantments); - var event = new GetEnchantmentLevelEvent(stack, mutableEnchantments, null); + var event = new GetEnchantmentLevelEvent(stack, mutableEnchantments, null, lookup); NeoForge.EVENT_BUS.post(event); return mutableEnchantments.toImmutable(); } diff --git a/src/main/java/net/neoforged/neoforge/event/enchanting/GetEnchantmentLevelEvent.java b/src/main/java/net/neoforged/neoforge/event/enchanting/GetEnchantmentLevelEvent.java index c0929bee65..f902fce284 100644 --- a/src/main/java/net/neoforged/neoforge/event/enchanting/GetEnchantmentLevelEvent.java +++ b/src/main/java/net/neoforged/neoforge/event/enchanting/GetEnchantmentLevelEvent.java @@ -5,7 +5,9 @@ package net.neoforged.neoforge.event.enchanting; +import java.util.Optional; import net.minecraft.core.Holder; +import net.minecraft.core.HolderLookup.RegistryLookup; import net.minecraft.resources.ResourceKey; import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.enchantment.Enchantment; @@ -25,11 +27,13 @@ public class GetEnchantmentLevelEvent extends Event { protected final ItemEnchantments.Mutable enchantments; @Nullable protected final Holder targetEnchant; + protected final RegistryLookup lookup; - public GetEnchantmentLevelEvent(ItemStack stack, ItemEnchantments.Mutable enchantments, @Nullable Holder targetEnchant) { + public GetEnchantmentLevelEvent(ItemStack stack, ItemEnchantments.Mutable enchantments, @Nullable Holder targetEnchant, RegistryLookup lookup) { this.stack = stack; this.enchantments = enchantments; this.targetEnchant = targetEnchant; + this.lookup = lookup; } /** @@ -81,4 +85,22 @@ public boolean isTargetting(Holder ench) { public boolean isTargetting(ResourceKey ench) { return this.targetEnchant == null || this.targetEnchant.is(ench); } + + /** + * Attempts to resolve a {@link Holder.Reference} for a target enchantment. + * Since enchantments are data, they are not guaranteed to exist. + * + * @param key The target resource key + * @return If the holder was available, an Optional containing it; otherwise an empty Optional. + */ + public Optional> getHolder(ResourceKey key) { + return this.lookup.get(key); + } + + /** + * {@return the underlying registry lookup, which can be used to access enchantment Holders} + */ + public RegistryLookup getLookup() { + return lookup; + } } diff --git a/tests/src/main/java/net/neoforged/neoforge/debug/enchantment/EnchantmentLevelTests.java b/tests/src/main/java/net/neoforged/neoforge/debug/enchantment/EnchantmentLevelTests.java index 70d5fad57f..848332d1a8 100644 --- a/tests/src/main/java/net/neoforged/neoforge/debug/enchantment/EnchantmentLevelTests.java +++ b/tests/src/main/java/net/neoforged/neoforge/debug/enchantment/EnchantmentLevelTests.java @@ -33,16 +33,20 @@ static void getEnchLevelEvent(final DynamicTest test, final RegistrationHelper r ItemEnchantments.Mutable enchants = e.getEnchantments(); // Increase the level of sharpness by 1 in all cases. - if (e.isTargetting(Enchantments.SHARPNESS)) { - enchants.set(e.getTargetEnchant(), enchants.getLevel(e.getTargetEnchant()) + 1); - } + e.getHolder(Enchantments.SHARPNESS).ifPresent(holder -> { + if (e.isTargetting(holder)) { + enchants.set(holder, enchants.getLevel(holder) + 1); + } + }); // Increase the level of fire aspect by 1 if the stack contains specific NBT. - if (e.isTargetting(Enchantments.FIRE_ASPECT)) { - if (e.getStack().getOrDefault(DataComponents.CUSTOM_DATA, CustomData.EMPTY).contains("boost_fire_aspect")) { - enchants.set(e.getTargetEnchant(), enchants.getLevel(e.getTargetEnchant()) + 1); + e.getHolder(Enchantments.FIRE_ASPECT).ifPresent(holder -> { + if (e.isTargetting(holder)) { + if (e.getStack().getOrDefault(DataComponents.CUSTOM_DATA, CustomData.EMPTY).contains("boost_fire_aspect")) { + enchants.set(holder, enchants.getLevel(holder) + 1); + } } - } + }); }); test.onGameTest(helper -> { diff --git a/tests/src/main/java/net/neoforged/neoforge/debug/loot/GlobalLootModifiersTest.java b/tests/src/main/java/net/neoforged/neoforge/debug/loot/GlobalLootModifiersTest.java index 6fa3d0a1e7..52d66b557c 100644 --- a/tests/src/main/java/net/neoforged/neoforge/debug/loot/GlobalLootModifiersTest.java +++ b/tests/src/main/java/net/neoforged/neoforge/debug/loot/GlobalLootModifiersTest.java @@ -41,7 +41,6 @@ import net.minecraft.world.item.crafting.RecipeType; import net.minecraft.world.item.crafting.SingleRecipeInput; import net.minecraft.world.item.enchantment.Enchantment; -import net.minecraft.world.item.enchantment.EnchantmentHelper; import net.minecraft.world.item.enchantment.Enchantments; import net.minecraft.world.level.GameType; import net.minecraft.world.level.block.Blocks; @@ -137,8 +136,8 @@ public SilkTouchTestModifier(LootItemCondition[] conditionsIn) { public ObjectArrayList doApply(ObjectArrayList generatedLoot, LootContext context) { ItemStack ctxTool = context.getParamOrNull(LootContextParams.TOOL); var reg = context.getLevel().registryAccess().registryOrThrow(Registries.ENCHANTMENT); - //return early if silk-touch is already applied (otherwise we'll get stuck in an infinite loop). - if (ctxTool == null || EnchantmentHelper.getItemEnchantmentLevel(reg.getHolderOrThrow(Enchantments.SILK_TOUCH), ctxTool) > 0) + // return early if silk-touch is already applied (otherwise we'll get stuck in an infinite loop). + if (ctxTool == null || ctxTool.getEnchantmentLevel(reg.getHolderOrThrow(Enchantments.SILK_TOUCH)) > 0) return generatedLoot; ItemStack fakeTool = ctxTool.copy(); fakeTool.enchant(reg.getHolderOrThrow(Enchantments.SILK_TOUCH), 1);