From 9ce0058026974e2c015f8913a57b3f6df488bd26 Mon Sep 17 00:00:00 2001 From: Duncan McKay Date: Thu, 21 May 2020 14:03:07 -0400 Subject: [PATCH 1/9] Added isFoodEdible to IAppleCoreAccessor --- java/squeek/applecore/api/IAppleCoreAccessor.java | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/java/squeek/applecore/api/IAppleCoreAccessor.java b/java/squeek/applecore/api/IAppleCoreAccessor.java index 23e9719..7bac019 100644 --- a/java/squeek/applecore/api/IAppleCoreAccessor.java +++ b/java/squeek/applecore/api/IAppleCoreAccessor.java @@ -22,6 +22,16 @@ public interface IAppleCoreAccessor */ boolean isFood(@Nonnull ItemStack food); + /** + * Check if the given ItemStack can be eaten, taking into account their max hunger, and if this food item is always edible + * + *
+ * In particular, this method will always return {@code true} if {@link net.minecraft.util.FoodStats#getFoodLevel} {@code <} {@link #getMaxHunger} + * or if this ItemStack's Item is an instance of ItemFood and has its alwaysEdible field set. + * @return {@code true} if that player is able to eat this food item, {@code false} otherwise. + */ + boolean isFoodEdible(@Nonnull ItemStack food, @Nonnull EntityPlayer player); + /** * Get player-agnostic food values. * From b420c4919e1a64ddad5984ac49a1e026d3fa5ae2 Mon Sep 17 00:00:00 2001 From: Duncan McKay Date: Thu, 21 May 2020 14:04:13 -0400 Subject: [PATCH 2/9] Began implementaion of isFoodEdible. First check complete. Second check is the hard part --- .../applecore/api_impl/AppleCoreAccessorMutatorImpl.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/java/squeek/applecore/api_impl/AppleCoreAccessorMutatorImpl.java b/java/squeek/applecore/api_impl/AppleCoreAccessorMutatorImpl.java index 49ece50..1d34317 100644 --- a/java/squeek/applecore/api_impl/AppleCoreAccessorMutatorImpl.java +++ b/java/squeek/applecore/api_impl/AppleCoreAccessorMutatorImpl.java @@ -51,6 +51,12 @@ private boolean isEdible(@Nonnull ItemStack food) // assume Block-based foods are edible return AppleCoreAPI.registry.getEdibleBlockFromItem(food.getItem()) != null; } + + @Override + public boolean isFoodEdible(@Nonnull ItemStack food, @Nonnull EntityPlayer player) + { + return player.getFoodStats().getFoodLevel() < getMaxHunger(player) || false; + } @Override public FoodValues getUnmodifiedFoodValues(@Nonnull ItemStack food) From 88913df458166aa6218312a970fe112f8480801b Mon Sep 17 00:00:00 2001 From: Duncan McKay Date: Thu, 21 May 2020 15:55:17 -0400 Subject: [PATCH 3/9] Began some ASM work. I need to look into this more --- .../applecore/asm/module/ModuleItemFood.java | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 java/squeek/applecore/asm/module/ModuleItemFood.java diff --git a/java/squeek/applecore/asm/module/ModuleItemFood.java b/java/squeek/applecore/asm/module/ModuleItemFood.java new file mode 100644 index 0000000..88ef2c0 --- /dev/null +++ b/java/squeek/applecore/asm/module/ModuleItemFood.java @@ -0,0 +1,43 @@ +package squeek.applecore.asm.module; + +import org.objectweb.asm.tree.AbstractInsnNode; +import org.objectweb.asm.tree.ClassNode; +import org.objectweb.asm.tree.FieldInsnNode; +import org.objectweb.asm.tree.InsnList; +import org.objectweb.asm.tree.MethodNode; + +import squeek.applecore.asm.IClassTransformerModule; +import squeek.asmhelper.ObfHelper; +import squeek.asmhelper.applecore.ASMHelper; + +import static org.objectweb.asm.Opcodes.*; + +public class ModuleItemFood implements IClassTransformerModule +{ + @Override + public String[] getClassesToTransform() + { + return new String[] {ASMConstants.ITEM_FOOD}; + } + + @Override + public byte[] transform(String name, String transformedName, byte[] basicClass) + { + if(transformedName.equals(ASMConstants.ITEM_FOOD)) + { + ClassNode classNode = ASMHelper.readClassFromBytes(bytes); + + MethodNode methodNode = ASMHelper.findMethodNodeOfClass(classNode, "", null); + patchItemFoodInit(methodNode); + } + return basicClass; + } + + private void patchItemFoodInit(MethodNode method) + { + InsnList insn = new InsnList(); + insn.add(new VarInsnNode); + insn.add(new FieldInsnNode(PUTFIELD, ObfHelper.getInternalClassName(ASMConstants.ITEM_FOOD), ObfHelper.isObfuscated() ? "field_77852_b" : "alwaysEdible", "Z")); + method.instructions.insert(insn); + } +} From 8aa1d7e586c3021789fd519a3e2f2b2cc4055d73 Mon Sep 17 00:00:00 2001 From: Duncan McKay Date: Thu, 21 May 2020 17:06:27 -0400 Subject: [PATCH 4/9] ASM not needed, using reflection. --- .../AppleCoreAccessorMutatorImpl.java | 39 ++++++++++++++++- .../applecore/asm/module/ModuleItemFood.java | 43 ------------------- 2 files changed, 38 insertions(+), 44 deletions(-) delete mode 100644 java/squeek/applecore/asm/module/ModuleItemFood.java diff --git a/java/squeek/applecore/api_impl/AppleCoreAccessorMutatorImpl.java b/java/squeek/applecore/api_impl/AppleCoreAccessorMutatorImpl.java index 1d34317..b77cf9a 100644 --- a/java/squeek/applecore/api_impl/AppleCoreAccessorMutatorImpl.java +++ b/java/squeek/applecore/api_impl/AppleCoreAccessorMutatorImpl.java @@ -6,6 +6,8 @@ import net.minecraft.item.ItemFood; import net.minecraft.item.ItemStack; import net.minecraftforge.common.MinecraftForge; +import net.minecraftforge.fml.common.ObfuscationReflectionHelper; +import net.minecraftforge.fml.relauncher.ReflectionHelper.UnableToFindFieldException; import squeek.applecore.api.AppleCoreAPI; import squeek.applecore.api.IAppleCoreAccessor; import squeek.applecore.api.IAppleCoreMutator; @@ -18,6 +20,8 @@ import squeek.applecore.api.hunger.StarvationEvent; import squeek.applecore.asm.util.IAppleCoreFoodStats; +import java.lang.reflect.Field; + import javax.annotation.Nonnull; public enum AppleCoreAccessorMutatorImpl implements IAppleCoreAccessor, IAppleCoreMutator @@ -55,7 +59,40 @@ private boolean isEdible(@Nonnull ItemStack food) @Override public boolean isFoodEdible(@Nonnull ItemStack food, @Nonnull EntityPlayer player) { - return player.getFoodStats().getFoodLevel() < getMaxHunger(player) || false; + return player.getFoodStats().getFoodLevel() < getMaxHunger(player) || isAlwaysEdible(food); + } + + private boolean isAlwaysEdible(@Nonnull ItemStack food) + { + if(food == ItemStack.EMPTY || !(food.getItem() instanceof ItemFood)) + return false; + Field edibility = null; + try + { + edibility = ObfuscationReflectionHelper.findField(ItemFood.class, "field_77852_bZ"); + } + catch(UnableToFindFieldException e) + { + //perhaps the field is deobfuscated? + try + { + edibility = ObfuscationReflectionHelper.findField(ItemFood.class, "alwaysEdible"); + } + catch(UnableToFindFieldException f) + { + return false; + } + } + try + { + //Should be a safe cast from the predicates checked. + ItemFood item = (ItemFood) food.getItem(); + return edibility != null ? (boolean) edibility.getBoolean(item) : false; + } + catch(IllegalAccessException e) + { + return false; + } } @Override diff --git a/java/squeek/applecore/asm/module/ModuleItemFood.java b/java/squeek/applecore/asm/module/ModuleItemFood.java deleted file mode 100644 index 88ef2c0..0000000 --- a/java/squeek/applecore/asm/module/ModuleItemFood.java +++ /dev/null @@ -1,43 +0,0 @@ -package squeek.applecore.asm.module; - -import org.objectweb.asm.tree.AbstractInsnNode; -import org.objectweb.asm.tree.ClassNode; -import org.objectweb.asm.tree.FieldInsnNode; -import org.objectweb.asm.tree.InsnList; -import org.objectweb.asm.tree.MethodNode; - -import squeek.applecore.asm.IClassTransformerModule; -import squeek.asmhelper.ObfHelper; -import squeek.asmhelper.applecore.ASMHelper; - -import static org.objectweb.asm.Opcodes.*; - -public class ModuleItemFood implements IClassTransformerModule -{ - @Override - public String[] getClassesToTransform() - { - return new String[] {ASMConstants.ITEM_FOOD}; - } - - @Override - public byte[] transform(String name, String transformedName, byte[] basicClass) - { - if(transformedName.equals(ASMConstants.ITEM_FOOD)) - { - ClassNode classNode = ASMHelper.readClassFromBytes(bytes); - - MethodNode methodNode = ASMHelper.findMethodNodeOfClass(classNode, "", null); - patchItemFoodInit(methodNode); - } - return basicClass; - } - - private void patchItemFoodInit(MethodNode method) - { - InsnList insn = new InsnList(); - insn.add(new VarInsnNode); - insn.add(new FieldInsnNode(PUTFIELD, ObfHelper.getInternalClassName(ASMConstants.ITEM_FOOD), ObfHelper.isObfuscated() ? "field_77852_b" : "alwaysEdible", "Z")); - method.instructions.insert(insn); - } -} From 244a8b50087390f267be9bb41dec64ac8dc218f0 Mon Sep 17 00:00:00 2001 From: Duncan McKay Date: Thu, 21 May 2020 17:40:08 -0400 Subject: [PATCH 5/9] Used correct ObfuscationReflectionHelper methods --- .../api_impl/AppleCoreAccessorMutatorImpl.java | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) diff --git a/java/squeek/applecore/api_impl/AppleCoreAccessorMutatorImpl.java b/java/squeek/applecore/api_impl/AppleCoreAccessorMutatorImpl.java index b77cf9a..67f7e80 100644 --- a/java/squeek/applecore/api_impl/AppleCoreAccessorMutatorImpl.java +++ b/java/squeek/applecore/api_impl/AppleCoreAccessorMutatorImpl.java @@ -66,33 +66,25 @@ private boolean isAlwaysEdible(@Nonnull ItemStack food) { if(food == ItemStack.EMPTY || !(food.getItem() instanceof ItemFood)) return false; - Field edibility = null; + Boolean edibility = null; + ItemFood item = (ItemFood) food.getItem(); try { - edibility = ObfuscationReflectionHelper.findField(ItemFood.class, "field_77852_bZ"); + edibility = ObfuscationReflectionHelper.getPrivateValue(ItemFood.class, item, "field_77852_bZ");//.findField(ItemFood.class, "field_77852_bZ"); } catch(UnableToFindFieldException e) { //perhaps the field is deobfuscated? try { - edibility = ObfuscationReflectionHelper.findField(ItemFood.class, "alwaysEdible"); + edibility = (Boolean) ObfuscationReflectionHelper.getPrivateValue(ItemFood.class, item, "alwaysEdible"); } catch(UnableToFindFieldException f) { return false; } } - try - { - //Should be a safe cast from the predicates checked. - ItemFood item = (ItemFood) food.getItem(); - return edibility != null ? (boolean) edibility.getBoolean(item) : false; - } - catch(IllegalAccessException e) - { - return false; - } + return edibility.booleanValue(); } @Override From 7729d0dcced90939f0546120aeacb162c7b10d85 Mon Sep 17 00:00:00 2001 From: Duncan McKay Date: Thu, 21 May 2020 17:44:01 -0400 Subject: [PATCH 6/9] Removed unneeded imports --- .../applecore/api_impl/AppleCoreAccessorMutatorImpl.java | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/java/squeek/applecore/api_impl/AppleCoreAccessorMutatorImpl.java b/java/squeek/applecore/api_impl/AppleCoreAccessorMutatorImpl.java index 67f7e80..1dc0ba9 100644 --- a/java/squeek/applecore/api_impl/AppleCoreAccessorMutatorImpl.java +++ b/java/squeek/applecore/api_impl/AppleCoreAccessorMutatorImpl.java @@ -1,5 +1,7 @@ package squeek.applecore.api_impl; +import javax.annotation.Nonnull; + import net.minecraft.block.Block; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.item.EnumAction; @@ -20,10 +22,6 @@ import squeek.applecore.api.hunger.StarvationEvent; import squeek.applecore.asm.util.IAppleCoreFoodStats; -import java.lang.reflect.Field; - -import javax.annotation.Nonnull; - public enum AppleCoreAccessorMutatorImpl implements IAppleCoreAccessor, IAppleCoreMutator { INSTANCE; From 8317d2764276a9a61a146df54c7f1e1eaec054e0 Mon Sep 17 00:00:00 2001 From: Ryan Liptak Date: Thu, 21 May 2020 14:59:01 -0700 Subject: [PATCH 7/9] Simplify alwaysEdible field reflection --- .../AppleCoreAccessorMutatorImpl.java | 34 +++++++------------ 1 file changed, 13 insertions(+), 21 deletions(-) diff --git a/java/squeek/applecore/api_impl/AppleCoreAccessorMutatorImpl.java b/java/squeek/applecore/api_impl/AppleCoreAccessorMutatorImpl.java index 1dc0ba9..6b66fdc 100644 --- a/java/squeek/applecore/api_impl/AppleCoreAccessorMutatorImpl.java +++ b/java/squeek/applecore/api_impl/AppleCoreAccessorMutatorImpl.java @@ -1,15 +1,12 @@ package squeek.applecore.api_impl; -import javax.annotation.Nonnull; - import net.minecraft.block.Block; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.item.EnumAction; import net.minecraft.item.ItemFood; import net.minecraft.item.ItemStack; import net.minecraftforge.common.MinecraftForge; -import net.minecraftforge.fml.common.ObfuscationReflectionHelper; -import net.minecraftforge.fml.relauncher.ReflectionHelper.UnableToFindFieldException; +import net.minecraftforge.fml.relauncher.ReflectionHelper; import squeek.applecore.api.AppleCoreAPI; import squeek.applecore.api.IAppleCoreAccessor; import squeek.applecore.api.IAppleCoreMutator; @@ -22,6 +19,9 @@ import squeek.applecore.api.hunger.StarvationEvent; import squeek.applecore.asm.util.IAppleCoreFoodStats; +import javax.annotation.Nonnull; +import java.lang.reflect.Field; + public enum AppleCoreAccessorMutatorImpl implements IAppleCoreAccessor, IAppleCoreMutator { INSTANCE; @@ -53,36 +53,28 @@ private boolean isEdible(@Nonnull ItemStack food) // assume Block-based foods are edible return AppleCoreAPI.registry.getEdibleBlockFromItem(food.getItem()) != null; } - + @Override public boolean isFoodEdible(@Nonnull ItemStack food, @Nonnull EntityPlayer player) { return player.getFoodStats().getFoodLevel() < getMaxHunger(player) || isAlwaysEdible(food); } - + + protected static final Field itemFoodAlwaysEdible = ReflectionHelper.findField(ItemFood.class, "alwaysEdible", "field_77852_bZ", "e"); + private boolean isAlwaysEdible(@Nonnull ItemStack food) { - if(food == ItemStack.EMPTY || !(food.getItem() instanceof ItemFood)) + if (food == ItemStack.EMPTY || !(food.getItem() instanceof ItemFood)) return false; - Boolean edibility = null; - ItemFood item = (ItemFood) food.getItem(); + try { - edibility = ObfuscationReflectionHelper.getPrivateValue(ItemFood.class, item, "field_77852_bZ");//.findField(ItemFood.class, "field_77852_bZ"); + return itemFoodAlwaysEdible.getBoolean(food.getItem()); } - catch(UnableToFindFieldException e) + catch (IllegalAccessException e) { - //perhaps the field is deobfuscated? - try - { - edibility = (Boolean) ObfuscationReflectionHelper.getPrivateValue(ItemFood.class, item, "alwaysEdible"); - } - catch(UnableToFindFieldException f) - { - return false; - } + throw new RuntimeException(e); } - return edibility.booleanValue(); } @Override From 55605d600e001a279b82dba6826af7c05d736249 Mon Sep 17 00:00:00 2001 From: Ryan Liptak Date: Thu, 21 May 2020 15:26:27 -0700 Subject: [PATCH 8/9] Rename isFoodEdible -> canPlayerEatFood and update doc comment --- .../applecore/api/IAppleCoreAccessor.java | 17 +++++++++++------ .../api_impl/AppleCoreAccessorMutatorImpl.java | 2 +- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/java/squeek/applecore/api/IAppleCoreAccessor.java b/java/squeek/applecore/api/IAppleCoreAccessor.java index 7bac019..eeb7d5d 100644 --- a/java/squeek/applecore/api/IAppleCoreAccessor.java +++ b/java/squeek/applecore/api/IAppleCoreAccessor.java @@ -23,14 +23,19 @@ public interface IAppleCoreAccessor boolean isFood(@Nonnull ItemStack food); /** - * Check if the given ItemStack can be eaten, taking into account their max hunger, and if this food item is always edible - * + * Check if the given ItemStack can currently be eaten by the player, taking into account their + * max hunger, and if the food item is always edible.
+ *
+ * In particular, this method will always return {@code true} if + * {@link net.minecraft.util.FoodStats#getFoodLevel} {@code <} {@link #getMaxHunger} + * or if this ItemStack's Item is an instance of ItemFood and has its alwaysEdible field set.
*
- * In particular, this method will always return {@code true} if {@link net.minecraft.util.FoodStats#getFoodLevel} {@code <} {@link #getMaxHunger} - * or if this ItemStack's Item is an instance of ItemFood and has its alwaysEdible field set. - * @return {@code true} if that player is able to eat this food item, {@code false} otherwise. + * Note: {@link ItemStack#EMPTY} can be passed to this function in order to check whether + * the player's hunger is currently below maximum. + * + * @return {@code true} if the player is currently able to eat the food item, {@code false} otherwise. */ - boolean isFoodEdible(@Nonnull ItemStack food, @Nonnull EntityPlayer player); + boolean canPlayerEatFood(@Nonnull ItemStack food, @Nonnull EntityPlayer player); /** * Get player-agnostic food values. diff --git a/java/squeek/applecore/api_impl/AppleCoreAccessorMutatorImpl.java b/java/squeek/applecore/api_impl/AppleCoreAccessorMutatorImpl.java index 6b66fdc..2605cb5 100644 --- a/java/squeek/applecore/api_impl/AppleCoreAccessorMutatorImpl.java +++ b/java/squeek/applecore/api_impl/AppleCoreAccessorMutatorImpl.java @@ -55,7 +55,7 @@ private boolean isEdible(@Nonnull ItemStack food) } @Override - public boolean isFoodEdible(@Nonnull ItemStack food, @Nonnull EntityPlayer player) + public boolean canPlayerEatFood(@Nonnull ItemStack food, @Nonnull EntityPlayer player) { return player.getFoodStats().getFoodLevel() < getMaxHunger(player) || isAlwaysEdible(food); } From 2dd3c79b320baa65bd7e63b102ec3d339e63573d Mon Sep 17 00:00:00 2001 From: Ryan Liptak Date: Thu, 21 May 2020 15:27:38 -0700 Subject: [PATCH 9/9] Add canPlayerEatFood usage to example mod The rest of the example mod kind of makes it hard to use in practice, since hunger is basically always below maximum, but oh well --- java/squeek/applecore/example/FoodValuesTooltipHandler.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/java/squeek/applecore/example/FoodValuesTooltipHandler.java b/java/squeek/applecore/example/FoodValuesTooltipHandler.java index bb7b822..daf7a8c 100644 --- a/java/squeek/applecore/example/FoodValuesTooltipHandler.java +++ b/java/squeek/applecore/example/FoodValuesTooltipHandler.java @@ -25,6 +25,12 @@ public void onItemTooltip(ItemTooltipEvent event) event.getToolTip().add("- Player-specific: " + playerValues.hunger + " : " + playerValues.saturationModifier + " (+" + DF.format(playerValues.getSaturationIncrement(event.getEntityPlayer())) + ")"); event.getToolTip().add("- Player-agnostic: " + modifiedValues.hunger + " : " + modifiedValues.saturationModifier + " (+" + DF.format(modifiedValues.getSaturationIncrement(event.getEntityPlayer())) + ")"); event.getToolTip().add("- Unmodified: " + unmodifiedValues.hunger + " : " + unmodifiedValues.saturationModifier + " (+" + DF.format(unmodifiedValues.getSaturationIncrement(event.getEntityPlayer())) + ")"); + + if (event.getEntityPlayer() != null) + { + boolean isCurrentlyEdible = AppleCoreAPI.accessor.canPlayerEatFood(event.getItemStack(), event.getEntityPlayer()); + event.getToolTip().add(isCurrentlyEdible ? "Can currently be eaten" : "Can not currently be eaten"); + } } } } \ No newline at end of file