Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Stack aware getFoodComponent #3295

Closed
wants to merge 23 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,14 @@
package net.fabricmc.fabric.api.item.v1;

import com.google.common.collect.Multimap;
import org.jetbrains.annotations.Nullable;

import net.minecraft.block.BlockState;
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.FoodComponent;
import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
import net.minecraft.util.Hand;
Expand Down Expand Up @@ -122,4 +124,15 @@ default boolean isSuitableFor(ItemStack stack, BlockState state) {
default ItemStack getRecipeRemainder(ItemStack stack) {
return ((Item) this).hasRecipeRemainder() ? ((Item) this).getRecipeRemainder().getDefaultStack() : ItemStack.EMPTY;
}

/**
* This is a stack-aware version of {@link Item#getFoodComponent()}.
* Note that simple food component can also be set via {@link Item.Settings#food(FoodComponent)}.
* If you want to get a food component for a stack, is <strong>recommended</strong> to use the stack version of this method: {@link FabricItemStack#getFoodComponent()}.
*
* @return this item's {@link FoodComponent}, or {@code null} if none was set
*/
default @Nullable FoodComponent getFoodComponent(ItemStack stack) {
return ((Item) this).getFoodComponent();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@

package net.fabricmc.fabric.api.item.v1;

import org.jetbrains.annotations.Nullable;

import net.minecraft.item.FoodComponent;
import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;

Expand All @@ -36,4 +39,14 @@ public interface FabricItemStack {
default ItemStack getRecipeRemainder() {
return ((ItemStack) this).getItem().getRecipeRemainder((ItemStack) this);
}

/**
* Stack-aware version of {@link Item#getFoodComponent()}.
* See {@link FabricItem#getFoodComponent(ItemStack)} for a more in depth description.
*
* @return this item stack's {@link FoodComponent}, or {@code null} if none was set
*/
default @Nullable FoodComponent getFoodComponent() {
return ((ItemStack) this).getItem().getFoodComponent(((ItemStack) this));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/*
* 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;
Phoupraw marked this conversation as resolved.
Show resolved Hide resolved

import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Redirect;

import net.minecraft.item.BlockItem;
import net.minecraft.item.ItemUsageContext;

@Mixin(BlockItem.class)
class BlockItemMixin {
@Redirect(method = "useOnBlock", at = @At(value = "INVOKE", target = "Lnet/minecraft/item/BlockItem;isFood()Z"))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could a lot of these mixins be improved by using WrapOperation?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

At that moment IJ Idea and Minecraft Dev plugin doesn't support mixin extra well, so I use redirect instead to avoid potential a mass of errors.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

MCDev has had full mixinextras support for a long time now.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah? I had a try just now, and found that it still suggests incorrect return type wrapping putting field.
For example, for the following wrap:

@WrapOperation(method = "<clinit>", at = @At(value = "FIELD", target = "Lnet/fabricmc/fabric/api/transfer/v1/fluid/FluidStorage;ITEM:Lnet/fabricmc/fabric/api/lookup/v1/item/ItemApiLookup;", opcode = Opcodes.PUTSTATIC))

the correct handler method signature should be

private static void customField(ItemApiLookup<Storage<FluidVariant>, ContainerItemContext> value, Operation<ItemApiLookup<Storage<FluidVariant>, ContainerItemContext>> original)

but minecraft dev plugin suggests that it should be

private static ItemApiLookup<Storage<FluidVariant>, ContainerItemContext> customField(ItemApiLookup<Storage<FluidVariant>, ContainerItemContext> value, Operation<ItemApiLookup<Storage<FluidVariant>, ContainerItemContext>> original)

The latter causes an error on game launching.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok you might be right about that one… will fix
Don’t know of any other such cases

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just found that fabric api project still remains loader 0.14.21... And fabric item api still depends on fabricloader >=0.4.0 in fabric.mod.json... Shall we update them?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We had the same problem in #3403 (comment) and decided on using the @reDIrect's in 1.20.1/.2 and then for .4 using ME. Quite happy to leave this as-is and when cherry-picking to 1.20.4 I can change this to use ME.

private boolean isStackAwareFood(BlockItem instance, ItemUsageContext context) {
return context.getStack().isFood();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/*
* 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.entity.passive.CatEntity;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.item.FoodComponent;
import net.minecraft.item.Item;
import net.minecraft.util.Hand;

@Mixin(CatEntity.class)
class CatEntityMixin {
@Redirect(method = "interactMob", at = @At(value = "INVOKE", target = "Lnet/minecraft/item/Item;getFoodComponent()Lnet/minecraft/item/FoodComponent;"))
private @Nullable FoodComponent getStackAwareFoodComponent(Item instance, PlayerEntity player, Hand hand) {
return player.getStackInHand(hand).getFoodComponent();
}

@Redirect(method = "interactMob", at = @At(value = "INVOKE", target = "Lnet/minecraft/item/Item;isFood()Z"))
private boolean isStackAwareFood(Item instance, PlayerEntity player, Hand hand) {
return player.getStackInHand(hand).isFood();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/*
* 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.data.server.recipe.CookingRecipeJsonBuilder;
import net.minecraft.item.Item;

@Mixin(CookingRecipeJsonBuilder.class)
class CookingRecipeJsonBuilderMixin {
@Redirect(method = "getSmeltingRecipeCategory", at = @At(value = "INVOKE", target = "Lnet/minecraft/item/Item;isFood()Z"))
private static boolean isStackAwareFood(Item instance) {
return instance.getDefaultStack().isFood();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/*
* 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;
Phoupraw marked this conversation as resolved.
Show resolved Hide resolved

import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Redirect;

import net.minecraft.entity.EntityType;
import net.minecraft.entity.EquipmentSlot;
import net.minecraft.entity.passive.AnimalEntity;
import net.minecraft.entity.passive.FoxEntity;
import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
import net.minecraft.world.World;

@Mixin(FoxEntity.class)
abstract class FoxEntityMixin extends AnimalEntity {
protected FoxEntityMixin(EntityType<? extends AnimalEntity> entityType, World world) {
super(entityType, world);
}

@Redirect(method = {"canEat", "canPickupItem"}, at = @At(value = "INVOKE", target = "Lnet/minecraft/item/Item;isFood()Z", ordinal = 0))
private boolean isStackAwareFood(Item instance, ItemStack stack) {
return stack.isFood();
}

@Redirect(method = {"canPickupItem"}, at = @At(value = "INVOKE", target = "Lnet/minecraft/item/Item;isFood()Z", ordinal = 1))
private boolean isEquippedStackAwareFood(Item instance) {
return this.getEquippedStack(EquipmentSlot.MAINHAND).isFood();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/*
* 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.entity.player.HungerManager;
import net.minecraft.item.FoodComponent;
import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;

@Mixin(HungerManager.class)
class HungerManagerMixin {
@Redirect(method = "eat", at = @At(value = "INVOKE", target = "Lnet/minecraft/item/Item;getFoodComponent()Lnet/minecraft/item/FoodComponent;"))
private @Nullable FoodComponent getStackAwareFoodComponent(Item instance, Item item, ItemStack stack) {
return stack.getFoodComponent();
}

@Redirect(method = "eat", at = @At(value = "INVOKE", target = "Lnet/minecraft/item/Item;isFood()Z"))
private boolean isStackAwareFood(Item instance, Item item, ItemStack stack) {
return stack.isFood();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,16 @@
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.Redirect;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;

import net.minecraft.entity.LivingEntity;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.item.FoodComponent;
import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
import net.minecraft.util.Hand;
import net.minecraft.world.World;

import net.fabricmc.fabric.api.item.v1.CustomDamageHandler;
import net.fabricmc.fabric.api.item.v1.EquipmentSlotProvider;
Expand Down Expand Up @@ -67,4 +74,29 @@ public CustomDamageHandler fabric_getCustomDamageHandler() {
public void fabric_setCustomDamageHandler(@Nullable CustomDamageHandler handler) {
this.customDamageHandler = handler;
}

@Redirect(method = "use", at = @At(value = "INVOKE", target = "Lnet/minecraft/item/Item;getFoodComponent()Lnet/minecraft/item/FoodComponent;"))
private @Nullable FoodComponent getStackAwareFoodComponent(Item instance, World world, PlayerEntity user, Hand hand) {
return user.getStackInHand(hand).getFoodComponent();
}

@Redirect(method = "getMaxUseTime", at = @At(value = "INVOKE", target = "Lnet/minecraft/item/Item;getFoodComponent()Lnet/minecraft/item/FoodComponent;"))
private @Nullable FoodComponent getStackAwareFoodComponent(Item instance, ItemStack stack) {
return stack.getFoodComponent();
}

@Redirect(method = {"getMaxUseTime", "getUseAction"}, at = @At(value = "INVOKE", target = "Lnet/minecraft/item/Item;isFood()Z"))
private boolean isStackAwareFood(Item instance, ItemStack stack) {
return stack.isFood();
}

@Redirect(method = "use", at = @At(value = "INVOKE", target = "Lnet/minecraft/item/Item;isFood()Z"))
private boolean isStackAwareFood(Item instance, World world, PlayerEntity user, Hand hand) {
return user.getStackInHand(hand).isFood();
}

@Redirect(method = "finishUsing", at = @At(value = "INVOKE", target = "Lnet/minecraft/item/Item;isFood()Z"))
private boolean isStackAwareFood(Item instance, ItemStack stack, World world, LivingEntity user) {
return stack.isFood();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
import org.spongepowered.asm.mixin.injection.ModifyArg;
import org.spongepowered.asm.mixin.injection.Redirect;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;

import net.minecraft.block.BlockState;
import net.minecraft.entity.EquipmentSlot;
Expand All @@ -44,7 +45,8 @@

@Mixin(ItemStack.class)
public abstract class ItemStackMixin implements FabricItemStack {
@Shadow public abstract Item getItem();
@Shadow
public abstract Item getItem();

@Unique
private LivingEntity fabric_damagingEntity;
Expand Down Expand Up @@ -100,4 +102,9 @@ public Multimap<EntityAttribute, EntityAttributeModifier> hookGetAttributeModifi
public boolean hookIsSuitableFor(Item item, BlockState state) {
return item.isSuitableFor((ItemStack) (Object) this, state);
}

@Inject(method = "isFood", at = @At("HEAD"), cancellable = true)
public void isStackAwareFood(CallbackInfoReturnable<Boolean> cir) {
cir.setReturnValue(this.getFoodComponent() != null);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,20 +16,29 @@

package net.fabricmc.fabric.mixin.item;

import org.jetbrains.annotations.Nullable;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.Redirect;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;

import net.minecraft.entity.EquipmentSlot;
import net.minecraft.entity.LivingEntity;
import net.minecraft.item.FoodComponent;
import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
import net.minecraft.world.World;

import net.fabricmc.fabric.api.item.v1.EquipmentSlotProvider;
import net.fabricmc.fabric.impl.item.ItemExtensions;

@Mixin(LivingEntity.class)
abstract class LivingEntityMixin {
@Shadow
protected ItemStack activeItemStack;

@Inject(method = "getPreferredEquipmentSlot", at = @At(value = "HEAD"), cancellable = true)
private static void onGetPreferredEquipmentSlot(ItemStack stack, CallbackInfoReturnable<EquipmentSlot> info) {
EquipmentSlotProvider equipmentSlotProvider = ((ItemExtensions) stack.getItem()).fabric_getEquipmentSlotProvider();
Expand All @@ -38,4 +47,19 @@ private static void onGetPreferredEquipmentSlot(ItemStack stack, CallbackInfoRet
info.setReturnValue(equipmentSlotProvider.getPreferredEquipmentSlot(stack));
}
}

@Redirect(method = "shouldSpawnConsumptionEffects", at = @At(value = "INVOKE", target = "Lnet/minecraft/item/Item;getFoodComponent()Lnet/minecraft/item/FoodComponent;"))
private @Nullable FoodComponent getStackAwareFoodComponent(Item instance) {
return this.activeItemStack.getFoodComponent();
}

@Redirect(method = "applyFoodEffects", at = @At(value = "INVOKE", target = "Lnet/minecraft/item/Item;getFoodComponent()Lnet/minecraft/item/FoodComponent;"))
private @Nullable FoodComponent getStackAwareFoodComponent(Item instance, ItemStack stack, World world, LivingEntity targetEntity) {
return stack.getFoodComponent();
}

@Redirect(method = "applyFoodEffects", at = @At(value = "INVOKE", target = "Lnet/minecraft/item/Item;isFood()Z"))
private boolean isStackAwareFood(Item instance, ItemStack stack, World world, LivingEntity targetEntity) {
return stack.isFood();
}
}
Loading