diff --git a/fabric-item-api-v1/src/main/java/net/fabricmc/fabric/api/item/v1/FabricItem.java b/fabric-item-api-v1/src/main/java/net/fabricmc/fabric/api/item/v1/FabricItem.java index fbd5e6e00..547cb644c 100644 --- a/fabric-item-api-v1/src/main/java/net/fabricmc/fabric/api/item/v1/FabricItem.java +++ b/fabric-item-api-v1/src/main/java/net/fabricmc/fabric/api/item/v1/FabricItem.java @@ -16,7 +16,13 @@ package net.fabricmc.fabric.api.item.v1; +import com.google.common.collect.Multimap; + +import net.minecraft.block.BlockState; import net.minecraft.client.network.ClientPlayerEntity; +import net.minecraft.entity.EquipmentSlot; +import net.minecraft.entity.attribute.EntityAttribute; +import net.minecraft.entity.attribute.EntityAttributeModifier; import net.minecraft.entity.player.PlayerEntity; import net.minecraft.item.Item; import net.minecraft.item.ItemStack; @@ -25,9 +31,7 @@ import net.minecraft.util.Hand; /** * General-purpose Fabric-provided extensions for {@link Item} subclasses. * - * <p>Note: This interface is automatically implemented on all items via Mixin, - * however it has to be implemented explicitly by modders to be able to override functions. - * In the future, it is planned that {@code public class Item implements FabricItem} will be visible in a development environment. + * <p>Note: This interface is automatically implemented on all items via Mixin and interface injection. * * <p>Note to maintainers: Functions should only be added to this interface if they are general-purpose enough, * to be evaluated on a case-by-case basis. Otherwise they are better suited for more specialized APIs. @@ -61,4 +65,30 @@ public interface FabricItem { default boolean allowContinuingBlockBreaking(PlayerEntity player, ItemStack oldStack, ItemStack newStack) { return false; } + + /** + * Return the attribute modifiers to apply when this stack is worn in a living entity equipment slot. + * Stack-aware version of {@link Item#getAttributeModifiers(EquipmentSlot)}. + * + * <p>Note that attribute modifiers are only updated when the stack changes, i.e. when {@code ItemStack.areEqual(old, new)} is false. + * + * @param stack the current stack + * @param slot the equipment slot this stack is in + * @return the attribute modifiers + */ + default Multimap<EntityAttribute, EntityAttributeModifier> getAttributeModifiers(ItemStack stack, EquipmentSlot slot) { + return ((Item) this).getAttributeModifiers(slot); + } + + /** + * Determines if mining with this item allows drops to be harvested from the specified block state. + * Stack-aware version of {@link Item#isSuitableFor(BlockState)}. + * + * @param stack the current stack + * @param state the block state of the targeted block + * @return true if drops can be harvested + */ + default boolean isSuitableFor(ItemStack stack, BlockState state) { + return ((Item) this).isSuitableFor(state); + } } diff --git a/fabric-item-api-v1/src/main/java/net/fabricmc/fabric/mixin/item/ItemStackMixin.java b/fabric-item-api-v1/src/main/java/net/fabricmc/fabric/mixin/item/ItemStackMixin.java index cd38ef4f8..e0b559dc4 100644 --- a/fabric-item-api-v1/src/main/java/net/fabricmc/fabric/mixin/item/ItemStackMixin.java +++ b/fabric-item-api-v1/src/main/java/net/fabricmc/fabric/mixin/item/ItemStackMixin.java @@ -18,15 +18,21 @@ package net.fabricmc.fabric.mixin.item; import java.util.function.Consumer; +import com.google.common.collect.Multimap; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Shadow; import org.spongepowered.asm.mixin.Unique; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.ModifyArg; +import org.spongepowered.asm.mixin.injection.Redirect; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +import net.minecraft.block.BlockState; +import net.minecraft.entity.EquipmentSlot; import net.minecraft.entity.LivingEntity; +import net.minecraft.entity.attribute.EntityAttribute; +import net.minecraft.entity.attribute.EntityAttributeModifier; import net.minecraft.item.Item; import net.minecraft.item.ItemStack; @@ -65,4 +71,26 @@ public abstract class ItemStackMixin { this.fabric_damagingEntity = null; this.fabric_breakCallback = null; } + + @Redirect( + method = "getAttributeModifiers", + at = @At( + value = "INVOKE", + target = "Lnet/minecraft/item/Item;getAttributeModifiers(Lnet/minecraft/entity/EquipmentSlot;)Lcom/google/common/collect/Multimap;" + ) + ) + public Multimap<EntityAttribute, EntityAttributeModifier> hookGetAttributeModifiers(Item item, EquipmentSlot slot) { + return item.getAttributeModifiers((ItemStack) (Object) this, slot); + } + + @Redirect( + method = "isSuitableFor", + at = @At( + value = "INVOKE", + target = "Lnet/minecraft/item/Item;isSuitableFor(Lnet/minecraft/block/BlockState;)Z" + ) + ) + public boolean hookIsSuitableFor(Item item, BlockState state) { + return item.isSuitableFor((ItemStack) (Object) this, state); + } } diff --git a/fabric-item-api-v1/src/main/java/net/fabricmc/fabric/mixin/item/client/ClientPlayerInteractionManagerMixin.java b/fabric-item-api-v1/src/main/java/net/fabricmc/fabric/mixin/item/client/ClientPlayerInteractionManagerMixin.java index b16e05bcf..61f2de56f 100644 --- a/fabric-item-api-v1/src/main/java/net/fabricmc/fabric/mixin/item/client/ClientPlayerInteractionManagerMixin.java +++ b/fabric-item-api-v1/src/main/java/net/fabricmc/fabric/mixin/item/client/ClientPlayerInteractionManagerMixin.java @@ -27,8 +27,6 @@ import net.minecraft.client.network.ClientPlayerInteractionManager; import net.minecraft.item.ItemStack; import net.minecraft.util.math.BlockPos; -import net.fabricmc.fabric.api.item.v1.FabricItem; - @Mixin(ClientPlayerInteractionManager.class) public class ClientPlayerInteractionManagerMixin { @Shadow @@ -58,7 +56,7 @@ public class ClientPlayerInteractionManagerMixin { ItemStack oldStack = this.selectedStack; ItemStack newStack = this.client.player.getMainHandStack(); - if (oldStack.isOf(newStack.getItem()) && ((FabricItem) oldStack.getItem()).allowContinuingBlockBreaking(this.client.player, oldStack, newStack)) { + if (oldStack.isOf(newStack.getItem()) && oldStack.getItem().allowContinuingBlockBreaking(this.client.player, oldStack, newStack)) { stackUnchanged = true; } } diff --git a/fabric-item-api-v1/src/main/java/net/fabricmc/fabric/mixin/item/client/HeldItemRendererMixin.java b/fabric-item-api-v1/src/main/java/net/fabricmc/fabric/mixin/item/client/HeldItemRendererMixin.java index 55416a36f..3f10a0e21 100644 --- a/fabric-item-api-v1/src/main/java/net/fabricmc/fabric/mixin/item/client/HeldItemRendererMixin.java +++ b/fabric-item-api-v1/src/main/java/net/fabricmc/fabric/mixin/item/client/HeldItemRendererMixin.java @@ -51,7 +51,7 @@ public class HeldItemRendererMixin { ItemStack newMainStack = client.player.getMainHandStack(); if (mainHand.getItem() == newMainStack.getItem()) { - if (!((FabricItem) mainHand.getItem()).allowNbtUpdateAnimation(client.player, Hand.MAIN_HAND, mainHand, newMainStack)) { + if (!mainHand.getItem().allowNbtUpdateAnimation(client.player, Hand.MAIN_HAND, mainHand, newMainStack)) { mainHand = newMainStack; } } @@ -60,7 +60,7 @@ public class HeldItemRendererMixin { ItemStack newOffStack = client.player.getOffHandStack(); if (offHand.getItem() == newOffStack.getItem()) { - if (!((FabricItem) offHand.getItem()).allowNbtUpdateAnimation(client.player, Hand.OFF_HAND, offHand, newOffStack)) { + if (!offHand.getItem().allowNbtUpdateAnimation(client.player, Hand.OFF_HAND, offHand, newOffStack)) { offHand = newOffStack; } } 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 6a2e17f7c..7798a4d0d 100644 --- a/fabric-item-api-v1/src/main/resources/fabric.mod.json +++ b/fabric-item-api-v1/src/main/resources/fabric.mod.json @@ -24,6 +24,9 @@ }, "description": "Hooks for items", "custom": { - "fabric-api:module-lifecycle": "stable" + "fabric-api:module-lifecycle": "stable", + "loom:injected_interfaces": { + "net/minecraft/class_1792": ["net/fabricmc/fabric/api/item/v1/FabricItem"] + } } } diff --git a/fabric-item-api-v1/src/testmod/java/net/fabricmc/fabric/test/item/UpdatingItem.java b/fabric-item-api-v1/src/testmod/java/net/fabricmc/fabric/test/item/UpdatingItem.java index b994fe962..44c295c02 100644 --- a/fabric-item-api-v1/src/testmod/java/net/fabricmc/fabric/test/item/UpdatingItem.java +++ b/fabric-item-api-v1/src/testmod/java/net/fabricmc/fabric/test/item/UpdatingItem.java @@ -16,7 +16,15 @@ package net.fabricmc.fabric.test.item; +import com.google.common.collect.ImmutableMultimap; +import com.google.common.collect.Multimap; + +import net.minecraft.block.BlockState; import net.minecraft.entity.Entity; +import net.minecraft.entity.EquipmentSlot; +import net.minecraft.entity.attribute.EntityAttribute; +import net.minecraft.entity.attribute.EntityAttributeModifier; +import net.minecraft.entity.attribute.EntityAttributes; import net.minecraft.entity.player.PlayerEntity; import net.minecraft.item.Item; import net.minecraft.item.ItemGroup; @@ -25,9 +33,10 @@ import net.minecraft.nbt.NbtCompound; import net.minecraft.util.Hand; import net.minecraft.world.World; -import net.fabricmc.fabric.api.item.v1.FabricItem; +public class UpdatingItem extends Item { + private static final EntityAttributeModifier PLUS_FIVE = new EntityAttributeModifier( + ATTACK_DAMAGE_MODIFIER_ID, "updating item", 5, EntityAttributeModifier.Operation.ADDITION); -public class UpdatingItem extends Item implements FabricItem { private final boolean allowUpdateAnimation; public UpdatingItem(boolean allowUpdateAnimation) { @@ -52,4 +61,30 @@ public class UpdatingItem extends Item implements FabricItem { public boolean allowContinuingBlockBreaking(PlayerEntity player, ItemStack oldStack, ItemStack newStack) { return true; // set to false and you won't be able to break a block in survival with this item } + + // True for 15 seconds every 30 seconds + private boolean isEnabled(ItemStack stack) { + return !stack.hasNbt() || stack.getNbt().getLong("ticks") % 600 < 300; + } + + @Override + public Multimap<EntityAttribute, EntityAttributeModifier> getAttributeModifiers(ItemStack stack, EquipmentSlot slot) { + // Give + 5 attack damage for 15 seconds every 30 seconds. + if (slot == EquipmentSlot.MAINHAND && isEnabled(stack)) { + return ImmutableMultimap.of(EntityAttributes.GENERIC_ATTACK_DAMAGE, PLUS_FIVE); + } else { + return ImmutableMultimap.of(); + } + } + + @Override + public boolean isSuitableFor(ItemStack stack, BlockState state) { + // Suitable for everything for 15 seconds every 30 seconds. + return isEnabled(stack); + } + + @Override + public float getMiningSpeedMultiplier(ItemStack stack, BlockState state) { + return isEnabled(stack) ? 20 : super.getMiningSpeedMultiplier(stack, state); + } }