diff --git a/java/squeek/applecore/api/IAppleCoreAccessor.java b/java/squeek/applecore/api/IAppleCoreAccessor.java index b52ed74..1942a12 100644 --- a/java/squeek/applecore/api/IAppleCoreAccessor.java +++ b/java/squeek/applecore/api/IAppleCoreAccessor.java @@ -65,4 +65,8 @@ public interface IAppleCoreAccessor */ public int getStarveDamageTickPeriod(EntityPlayer player); + /** + * @return The number of ticks between health being regenerated by the {@code player} when at full hunger and > 0 saturation. + */ + public int getSaturatedHealthRegenTickPeriod(EntityPlayer player); } diff --git a/java/squeek/applecore/api/hunger/HealthRegenEvent.java b/java/squeek/applecore/api/hunger/HealthRegenEvent.java index b971703..3d3517b 100644 --- a/java/squeek/applecore/api/hunger/HealthRegenEvent.java +++ b/java/squeek/applecore/api/hunger/HealthRegenEvent.java @@ -23,6 +23,8 @@ public HealthRegenEvent(EntityPlayer player) /** * Fired each FoodStats update to determine whether or not health regen from food is allowed for the {@link #player}. + * However, this event will not be fired if saturated regen occurs, as saturated health regen will take precedence + * over normal health regen (see {@link AllowSaturatedRegen}). * * This event is fired in {@link FoodStats#onUpdate}.
*
@@ -81,7 +83,7 @@ public GetRegenTickPeriod(EntityPlayer player) public static class Regen extends HealthRegenEvent { public float deltaHealth = 1f; - public float deltaExhaustion = 3f; + public float deltaExhaustion = 4f; public Regen(EntityPlayer player) { @@ -114,4 +116,78 @@ public PeacefulRegen(EntityPlayer player) super(player); } } + + /** + * Fired each FoodStats update to determine whether or health regen from full hunger + saturation is allowed for the {@link #player}. + * + * Saturated health regen will take precedence over normal health regen. + * + * This event is fired in {@link FoodStats#onUpdate}.
+ *
+ * This event is not {@link Cancelable}.
+ *
+ * This event uses the {@link Result}. {@link HasResult}
+ * {@link Result#DEFAULT} will use the vanilla conditionals.
+ * {@link Result#ALLOW} will allow regen without condition.
+ * {@link Result#DENY} will deny regen without condition.
+ */ + @HasResult + public static class AllowSaturatedRegen extends HealthRegenEvent + { + public AllowSaturatedRegen(EntityPlayer player) + { + super(player); + } + } + + /** + * Fired every time the saturated regen tick period is retrieved to allow control over its value. + * + * This event is fired in {@link FoodStats#onUpdate} and in {@link AppleCoreAPI}.
+ *
+ * {@link #regenTickPeriod} contains the number of ticks between each saturated regen.
+ *
+ * This event is not {@link Cancelable}.
+ *
+ * This event does not have a {@link Result}. {@link HasResult}
+ */ + public static class GetSaturatedRegenTickPeriod extends HealthRegenEvent + { + public int regenTickPeriod = 10; + + public GetSaturatedRegenTickPeriod(EntityPlayer player) + { + super(player); + } + } + + /** + * Fired once the ticks since last regen reaches regenTickPeriod (see {@link GetSaturatedRegenTickPeriod}), + * in order to control how regen affects health/exhaustion. + * + * By default, the amount of health restored depends on the player's current saturation level. + * + * This event is fired in {@link FoodStats#onUpdate}.
+ *
+ * {@link #deltaHealth} contains the delta to be applied to health.
+ * {@link #deltaExhaustion} contains the delta to be applied to exhaustion level.
+ *
+ * This event is {@link Cancelable}.
+ * If this event is canceled, it will skip applying the delta values to health and exhaustion.
+ *
+ * This event does not have a {@link Result}. {@link HasResult}
+ */ + @Cancelable + public static class SaturatedRegen extends HealthRegenEvent + { + public float deltaHealth; + public float deltaExhaustion; + + public SaturatedRegen(EntityPlayer player) + { + super(player); + this.deltaExhaustion = Math.min(player.getFoodStats().getSaturationLevel(), 4.0F); + this.deltaHealth = deltaExhaustion / 4.0F; + } + } } \ No newline at end of file diff --git a/java/squeek/applecore/api_impl/AppleCoreAccessorMutatorImpl.java b/java/squeek/applecore/api_impl/AppleCoreAccessorMutatorImpl.java index e5441cb..6cab11f 100644 --- a/java/squeek/applecore/api_impl/AppleCoreAccessorMutatorImpl.java +++ b/java/squeek/applecore/api_impl/AppleCoreAccessorMutatorImpl.java @@ -18,6 +18,7 @@ import squeek.applecore.api.hunger.ExhaustionEvent; import squeek.applecore.api.hunger.HealthRegenEvent; import squeek.applecore.api.hunger.StarvationEvent; +import squeek.applecore.asm.util.IAppleCoreFoodStats; import java.lang.reflect.Field; @@ -104,7 +105,7 @@ public float getExhaustion(EntityPlayer player) { try { - return foodExhaustion.getFloat(player.getFoodStats()); + return getAppleCoreFoodStats(player).getExhaustion(); } catch (RuntimeException e) { @@ -140,6 +141,14 @@ public int getStarveDamageTickPeriod(EntityPlayer player) return event.starveTickPeriod; } + @Override + public int getSaturatedHealthRegenTickPeriod(EntityPlayer player) + { + HealthRegenEvent.GetSaturatedRegenTickPeriod event = new HealthRegenEvent.GetSaturatedRegenTickPeriod(player); + MinecraftForge.EVENT_BUS.post(event); + return event.regenTickPeriod; + } + /* * IAppleCoreMutator implementation */ @@ -148,7 +157,7 @@ public void setExhaustion(EntityPlayer player, float exhaustion) { try { - foodExhaustion.setFloat(player.getFoodStats(), exhaustion); + getAppleCoreFoodStats(player).setExhaustion(exhaustion); } catch (RuntimeException e) { @@ -163,18 +172,7 @@ public void setExhaustion(EntityPlayer player, float exhaustion) @Override public void setHunger(EntityPlayer player, int hunger) { - try - { - foodLevel.setInt(player.getFoodStats(), hunger); - } - catch (RuntimeException e) - { - throw e; - } - catch (Exception e) - { - throw new RuntimeException(e); - } + player.getFoodStats().setFoodLevel(hunger); } @Override @@ -182,7 +180,7 @@ public void setSaturation(EntityPlayer player, float saturation) { try { - foodSaturationLevel.setFloat(player.getFoodStats(), saturation); + getAppleCoreFoodStats(player).setSaturation(saturation); } catch (RuntimeException e) { @@ -199,7 +197,7 @@ public void setHealthRegenTickCounter(EntityPlayer player, int tickCounter) { try { - foodTimer.setInt(player.getFoodStats(), tickCounter); + getAppleCoreFoodStats(player).setFoodTimer(tickCounter); } catch (RuntimeException e) { @@ -216,7 +214,7 @@ public void setStarveDamageTickCounter(EntityPlayer player, int tickCounter) { try { - starveTimer.setInt(player.getFoodStats(), tickCounter); + getAppleCoreFoodStats(player).setStarveTimer(tickCounter); } catch (RuntimeException e) { @@ -228,18 +226,8 @@ public void setStarveDamageTickCounter(EntityPlayer player, int tickCounter) } } - // reflection - static final Field foodLevel = ReflectionHelper.findField(FoodStats.class, "foodLevel", "field_75127_a", "a"); - static final Field foodSaturationLevel = ReflectionHelper.findField(FoodStats.class, "foodSaturationLevel", "field_75125_b", "b"); - static final Field foodExhaustion = ReflectionHelper.findField(FoodStats.class, "foodExhaustionLevel", "field_75126_c", "c"); - static final Field foodTimer = ReflectionHelper.findField(FoodStats.class, "foodTimer", "field_75123_d", "d"); - static final Field starveTimer = ReflectionHelper.findField(FoodStats.class, "starveTimer"); - static + public IAppleCoreFoodStats getAppleCoreFoodStats(EntityPlayer player) throws ClassCastException { - foodLevel.setAccessible(true); - foodSaturationLevel.setAccessible(true); - foodExhaustion.setAccessible(true); - foodTimer.setAccessible(true); - starveTimer.setAccessible(true); + return (IAppleCoreFoodStats) player.getFoodStats(); } } \ No newline at end of file diff --git a/java/squeek/applecore/asm/ASMConstants.java b/java/squeek/applecore/asm/ASMConstants.java index 02860f7..0711536 100644 --- a/java/squeek/applecore/asm/ASMConstants.java +++ b/java/squeek/applecore/asm/ASMConstants.java @@ -8,6 +8,7 @@ public class ASMConstants public static final String HOOKS_INTERNAL_CLASS = ASMHelper.toInternalClassName(HOOKS); public static final String FOOD_VALUES = "squeek.applecore.api.food.FoodValues"; public static final String IAPPLECOREFERTILIZABLE = "squeek.applecore.asm.util.IAppleCoreFertilizable"; + public static final String IAPPLECOREFOODSTATS = "squeek.applecore.asm.util.IAppleCoreFoodStats"; public static final class ExhaustionEvent { diff --git a/java/squeek/applecore/asm/Hooks.java b/java/squeek/applecore/asm/Hooks.java index f9450f2..262046f 100644 --- a/java/squeek/applecore/asm/Hooks.java +++ b/java/squeek/applecore/asm/Hooks.java @@ -9,6 +9,7 @@ import net.minecraft.item.EnumAction; import net.minecraft.item.ItemFood; import net.minecraft.item.ItemStack; +import net.minecraft.util.DamageSource; import net.minecraft.util.FoodStats; import net.minecraft.util.math.BlockPos; import net.minecraft.world.World; @@ -24,12 +25,100 @@ import squeek.applecore.api.hunger.StarvationEvent; import squeek.applecore.api.plants.FertilizationEvent; import squeek.applecore.asm.util.IAppleCoreFertilizable; +import squeek.applecore.asm.util.IAppleCoreFoodStats; import java.lang.reflect.Method; import java.util.Random; public class Hooks { + public static boolean onAppleCoreFoodStatsUpdate(FoodStats foodStats, EntityPlayer player) + { + if (!(foodStats instanceof IAppleCoreFoodStats)) + return false; + + IAppleCoreFoodStats appleCoreFoodStats = (IAppleCoreFoodStats) foodStats; + + appleCoreFoodStats.setPrevFoodLevel(foodStats.getFoodLevel()); + + Result allowExhaustionResult = Hooks.fireAllowExhaustionEvent(player); + float maxExhaustion = Hooks.fireExhaustionTickEvent(player, appleCoreFoodStats.getExhaustion()); + if (allowExhaustionResult == Result.ALLOW || (allowExhaustionResult == Result.DEFAULT && appleCoreFoodStats.getExhaustion() >= maxExhaustion)) + { + ExhaustionEvent.Exhausted exhaustedEvent = Hooks.fireExhaustionMaxEvent(player, maxExhaustion, appleCoreFoodStats.getExhaustion()); + + appleCoreFoodStats.setExhaustion(appleCoreFoodStats.getExhaustion() + exhaustedEvent.deltaExhaustion); + if (!exhaustedEvent.isCanceled()) + { + appleCoreFoodStats.setSaturation(Math.max(foodStats.getSaturationLevel() + exhaustedEvent.deltaSaturation, 0.0F)); + foodStats.setFoodLevel(Math.max(foodStats.getFoodLevel() + exhaustedEvent.deltaHunger, 0)); + } + } + + boolean hasNaturalRegen = player.worldObj.getGameRules().hasRule("naturalRegeneration"); + + Result allowSaturatedRegenResult = Hooks.fireAllowSaturatedRegenEvent(player); + boolean shouldDoSaturatedRegen = allowSaturatedRegenResult == Result.ALLOW || (allowSaturatedRegenResult == Result.DEFAULT && hasNaturalRegen && foodStats.getSaturationLevel() > 0.0F && player.shouldHeal() && foodStats.getFoodLevel() >= 20); + Result allowRegenResult = shouldDoSaturatedRegen ? Result.DENY : Hooks.fireAllowRegenEvent(player); + boolean shouldDoRegen = allowRegenResult == Result.ALLOW || (allowRegenResult == Result.DEFAULT && hasNaturalRegen && foodStats.getFoodLevel() >= 18 && player.shouldHeal()); + if (shouldDoSaturatedRegen) + { + appleCoreFoodStats.setFoodTimer(appleCoreFoodStats.getFoodTimer()+1); + + if (appleCoreFoodStats.getFoodTimer() >= Hooks.fireSaturatedRegenTickEvent(player)) + { + HealthRegenEvent.SaturatedRegen saturatedRegenEvent = Hooks.fireSaturatedRegenEvent(player); + if (!saturatedRegenEvent.isCanceled()) + { + player.heal(saturatedRegenEvent.deltaHealth); + foodStats.addExhaustion(saturatedRegenEvent.deltaExhaustion); + } + appleCoreFoodStats.setFoodTimer(0); + } + } + else if (shouldDoRegen) + { + appleCoreFoodStats.setFoodTimer(appleCoreFoodStats.getFoodTimer()+1); + + if (appleCoreFoodStats.getFoodTimer() >= Hooks.fireRegenTickEvent(player)) + { + HealthRegenEvent.Regen regenEvent = Hooks.fireRegenEvent(player); + if (!regenEvent.isCanceled()) + { + player.heal(regenEvent.deltaHealth); + foodStats.addExhaustion(regenEvent.deltaExhaustion); + } + appleCoreFoodStats.setFoodTimer(0); + } + } + else + { + appleCoreFoodStats.setFoodTimer(0); + } + + Result allowStarvationResult = Hooks.fireAllowStarvation(player); + if (allowStarvationResult == Result.ALLOW || (allowStarvationResult == Result.DEFAULT && foodStats.getFoodLevel() <= 0)) + { + appleCoreFoodStats.setStarveTimer(appleCoreFoodStats.getStarveTimer()+1); + + if (appleCoreFoodStats.getStarveTimer() >= Hooks.fireStarvationTickEvent(player)) + { + StarvationEvent.Starve starveEvent = Hooks.fireStarveEvent(player); + if (!starveEvent.isCanceled()) + { + player.attackEntityFrom(DamageSource.starve, starveEvent.starveDamage); + } + appleCoreFoodStats.setStarveTimer(0); + } + } + else + { + appleCoreFoodStats.setStarveTimer(0); + } + + return true; + } + public static FoodValues onFoodStatsAdded(FoodStats foodStats, ItemFood itemFood, ItemStack itemStack, EntityPlayer player) { return AppleCoreAPI.accessor.getFoodValuesForPlayer(itemStack, player); @@ -104,11 +193,23 @@ public static Result fireAllowRegenEvent(EntityPlayer player) return event.getResult(); } + public static Result fireAllowSaturatedRegenEvent(EntityPlayer player) + { + HealthRegenEvent.AllowSaturatedRegen event = new HealthRegenEvent.AllowSaturatedRegen(player); + MinecraftForge.EVENT_BUS.post(event); + return event.getResult(); + } + public static int fireRegenTickEvent(EntityPlayer player) { return AppleCoreAPI.accessor.getHealthRegenTickPeriod(player); } + public static int fireSaturatedRegenTickEvent(EntityPlayer player) + { + return AppleCoreAPI.accessor.getSaturatedHealthRegenTickPeriod(player); + } + public static HealthRegenEvent.Regen fireRegenEvent(EntityPlayer player) { HealthRegenEvent.Regen event = new HealthRegenEvent.Regen(player); @@ -116,6 +217,13 @@ public static HealthRegenEvent.Regen fireRegenEvent(EntityPlayer player) return event; } + public static HealthRegenEvent.SaturatedRegen fireSaturatedRegenEvent(EntityPlayer player) + { + HealthRegenEvent.SaturatedRegen event = new HealthRegenEvent.SaturatedRegen(player); + MinecraftForge.EVENT_BUS.post(event); + return event; + } + public static HealthRegenEvent.PeacefulRegen firePeacefulRegenEvent(EntityPlayer player) { HealthRegenEvent.PeacefulRegen event = new HealthRegenEvent.PeacefulRegen(player); diff --git a/java/squeek/applecore/asm/module/ModuleFoodStats.java b/java/squeek/applecore/asm/module/ModuleFoodStats.java index de6fa10..c682de2 100644 --- a/java/squeek/applecore/asm/module/ModuleFoodStats.java +++ b/java/squeek/applecore/asm/module/ModuleFoodStats.java @@ -1,5 +1,7 @@ package squeek.applecore.asm.module; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Type; import org.objectweb.asm.tree.*; import squeek.applecore.asm.ASMConstants; import squeek.applecore.asm.IClassTransformerModule; @@ -11,6 +13,7 @@ public class ModuleFoodStats implements IClassTransformerModule { public static String foodStatsPlayerField = "entityplayer"; + public static String foodStatsStarveTimerField = "starveTimer"; @Override public String[] getClassesToTransform() @@ -41,6 +44,21 @@ public byte[] transform(String name, String transformedName, byte[] basicClass) injectFoodStatsPlayerField(classNode); injectFoodStatsConstructor(classNode); + // add starveTimer field + classNode.fields.add(new FieldNode(ACC_PUBLIC, foodStatsStarveTimerField, "I", null, null)); + + // IAppleCoreFoodStats implementation + classNode.interfaces.add(ASMHelper.toInternalClassName(ASMConstants.IAPPLECOREFOODSTATS)); + tryAddFieldGetter(classNode, "getFoodTimer", ObfHelper.isObfuscated() ? "field_75123_d" : "foodTimer", "I"); + tryAddFieldSetter(classNode, "setFoodTimer", ObfHelper.isObfuscated() ? "field_75123_d" : "foodTimer", "I"); + tryAddFieldGetter(classNode, "getStarveTimer", foodStatsStarveTimerField, "I"); + tryAddFieldSetter(classNode, "setStarveTimer", foodStatsStarveTimerField, "I"); + tryAddFieldGetter(classNode, "getPlayer", foodStatsPlayerField, ASMHelper.toDescriptor(ASMConstants.PLAYER)); + tryAddFieldSetter(classNode, "setPrevFoodLevel", ObfHelper.isObfuscated() ? "field_75124_e" : "prevFoodLevel", "I"); + tryAddFieldGetter(classNode, "getExhaustion", ObfHelper.isObfuscated() ? "field_75126_c" : "foodExhaustionLevel", "F"); + tryAddFieldSetter(classNode, "setExhaustion", ObfHelper.isObfuscated() ? "field_75126_c" : "foodExhaustionLevel", "F"); + tryAddFieldSetter(classNode, "setSaturation", ObfHelper.isObfuscated() ? "field_75125_b" : "foodSaturationLevel", "F"); + MethodNode addStatsMethodNode = ASMHelper.findMethodNodeOfClass(classNode, "func_75122_a", "addStats", ASMHelper.toMethodDescriptor("V", "I", "F")); if (addStatsMethodNode != null) { @@ -60,9 +78,7 @@ public byte[] transform(String name, String transformedName, byte[] basicClass) MethodNode updateMethodNode = ASMHelper.findMethodNodeOfClass(classNode, "func_75118_a", "onUpdate", ASMHelper.toMethodDescriptor("V", ASMConstants.PLAYER)); if (updateMethodNode != null) { - hookHealthRegen(classNode, updateMethodNode); - hookExhaustion(classNode, updateMethodNode); - hookStarvation(classNode, updateMethodNode); + hookUpdate(classNode, updateMethodNode); } else throw new RuntimeException("FoodStats: onUpdate method not found"); @@ -266,332 +282,48 @@ private void hookFoodStatsAddition(ClassNode classNode, MethodNode method) method.instructions.insertBefore(targetNode, ifCanceled); } - private void hookExhaustion(ClassNode classNode, MethodNode method) + private void hookUpdate(ClassNode classNode, MethodNode updateMethodNode) { - - String internalFoodStatsName = ASMHelper.toInternalClassName(classNode.name); - LabelNode endLabel = ASMHelper.findEndLabel(method); + LabelNode ifSkipReturn = new LabelNode(); InsnList toInject = new InsnList(); - - AbstractInsnNode injectPoint = ASMHelper.findFirstInstructionWithOpcode(method, PUTFIELD); - AbstractInsnNode foodExhaustionIf = ASMHelper.findFirstInstructionWithOpcode(method, IFLE); - LabelNode foodExhaustionBlockEndLabel = ((JumpInsnNode) foodExhaustionIf).label; - - // remove the entire exhaustion block - ASMHelper.removeFromInsnListUntil(method.instructions, injectPoint.getNext(), foodExhaustionBlockEndLabel); - - // create allowExhaustionResult variable - LabelNode allowExhaustionResultStart = new LabelNode(); - LocalVariableNode allowExhaustionResult = new LocalVariableNode("allowExhaustionResult", ASMHelper.toDescriptor(ASMConstants.EVENT_RESULT), null, allowExhaustionResultStart, endLabel, method.maxLocals); - method.maxLocals += 1; - method.localVariables.add(allowExhaustionResult); - - // Result allowExhaustionResult = Hooks.fireAllowExhaustionEvent(player); - toInject.add(new VarInsnNode(ALOAD, 1)); - toInject.add(new MethodInsnNode(INVOKESTATIC, ASMConstants.HOOKS_INTERNAL_CLASS, "fireAllowExhaustionEvent", ASMHelper.toMethodDescriptor(ASMConstants.EVENT_RESULT, ASMConstants.PLAYER), false)); - toInject.add(new VarInsnNode(ASTORE, allowExhaustionResult.index)); - toInject.add(allowExhaustionResultStart); - - // create maxExhaustion variable - LabelNode maxExhaustionStart = new LabelNode(); - LocalVariableNode maxExhaustion = new LocalVariableNode("maxExhaustion", "F", null, maxExhaustionStart, endLabel, method.maxLocals); - method.maxLocals += 1; - method.localVariables.add(maxExhaustion); - - // float maxExhaustion = Hooks.fireExhaustionTickEvent(player, foodExhaustionLevel); - toInject.add(new VarInsnNode(ALOAD, 1)); - toInject.add(new VarInsnNode(ALOAD, 0)); - toInject.add(new FieldInsnNode(GETFIELD, internalFoodStatsName, ObfHelper.isObfuscated() ? "field_75126_c" : "foodExhaustionLevel", "F")); - toInject.add(new MethodInsnNode(INVOKESTATIC, ASMConstants.HOOKS_INTERNAL_CLASS, "fireExhaustionTickEvent", ASMHelper.toMethodDescriptor("F", ASMConstants.PLAYER, "F"), false)); - toInject.add(new VarInsnNode(FSTORE, maxExhaustion.index)); - toInject.add(maxExhaustionStart); - - // if (allowExhaustionResult == Result.ALLOW || (allowExhaustionResult == Result.DEFAULT && this.foodExhaustionLevel >= maxExhaustion)) - toInject.add(new VarInsnNode(ALOAD, allowExhaustionResult.index)); - toInject.add(new FieldInsnNode(GETSTATIC, ASMHelper.toInternalClassName(ASMConstants.EVENT_RESULT), "ALLOW", ASMHelper.toDescriptor(ASMConstants.EVENT_RESULT))); - LabelNode ifAllowed = new LabelNode(); - toInject.add(new JumpInsnNode(IF_ACMPEQ, ifAllowed)); - toInject.add(new VarInsnNode(ALOAD, allowExhaustionResult.index)); - toInject.add(new FieldInsnNode(GETSTATIC, ASMHelper.toInternalClassName(ASMConstants.EVENT_RESULT), "DEFAULT", ASMHelper.toDescriptor(ASMConstants.EVENT_RESULT))); - toInject.add(new JumpInsnNode(IF_ACMPNE, foodExhaustionBlockEndLabel)); toInject.add(new VarInsnNode(ALOAD, 0)); - toInject.add(new FieldInsnNode(GETFIELD, internalFoodStatsName, ObfHelper.isObfuscated() ? "field_75126_c" : "foodExhaustionLevel", "F")); - toInject.add(new VarInsnNode(FLOAD, maxExhaustion.index)); - toInject.add(new InsnNode(FCMPL)); - toInject.add(new JumpInsnNode(IFLT, foodExhaustionBlockEndLabel)); - toInject.add(ifAllowed); - - // create exhaustedEvent variable - LabelNode exhaustedEventStart = new LabelNode(); - LocalVariableNode exhaustedEvent = new LocalVariableNode("exhaustionMaxEvent", ASMHelper.toDescriptor(ASMConstants.ExhaustionEvent.EXHAUSTED), null, exhaustedEventStart, foodExhaustionBlockEndLabel, method.maxLocals); - method.maxLocals += 1; - method.localVariables.add(exhaustedEvent); - - // FoodEvent.Exhaustion.MaxReached exhaustionMaxEvent = Hooks.fireExhaustionMaxEvent(player, exhaustionTickEvent.maxExhaustionLevel, foodExhaustionLevel); toInject.add(new VarInsnNode(ALOAD, 1)); - toInject.add(new VarInsnNode(FLOAD, maxExhaustion.index)); - toInject.add(new VarInsnNode(ALOAD, 0)); - toInject.add(new FieldInsnNode(GETFIELD, internalFoodStatsName, ObfHelper.isObfuscated() ? "field_75126_c" : "foodExhaustionLevel", "F")); - toInject.add(new MethodInsnNode(INVOKESTATIC, ASMConstants.HOOKS_INTERNAL_CLASS, "fireExhaustionMaxEvent", ASMHelper.toMethodDescriptor(ASMConstants.ExhaustionEvent.EXHAUSTED, ASMConstants.PLAYER, "F", "F"), false)); - toInject.add(new VarInsnNode(ASTORE, exhaustedEvent.index)); - toInject.add(exhaustedEventStart); + toInject.add(new MethodInsnNode(INVOKESTATIC, ASMHelper.toInternalClassName(ASMConstants.HOOKS), "onAppleCoreFoodStatsUpdate", ASMHelper.toMethodDescriptor("Z", ASMConstants.FOOD_STATS, ASMConstants.PLAYER), false)); + toInject.add(new JumpInsnNode(IFEQ, ifSkipReturn)); + toInject.add(new LabelNode()); + toInject.add(new InsnNode(RETURN)); + toInject.add(ifSkipReturn); - // this.foodExhaustionLevel += exhaustionMaxEvent.deltaExhaustion; - toInject.add(new VarInsnNode(ALOAD, 0)); - toInject.add(new InsnNode(DUP)); - toInject.add(new FieldInsnNode(GETFIELD, internalFoodStatsName, ObfHelper.isObfuscated() ? "field_75126_c" : "foodExhaustionLevel", "F")); - toInject.add(new VarInsnNode(ALOAD, exhaustedEvent.index)); - toInject.add(new FieldInsnNode(GETFIELD, ASMHelper.toInternalClassName(ASMConstants.ExhaustionEvent.EXHAUSTED), "deltaExhaustion", "F")); - toInject.add(new InsnNode(FADD)); - toInject.add(new FieldInsnNode(PUTFIELD, internalFoodStatsName, ObfHelper.isObfuscated() ? "field_75126_c" : "foodExhaustionLevel", "F")); - - // if (!exhaustionMaxEvent.isCanceled()) - toInject.add(new VarInsnNode(ALOAD, exhaustedEvent.index)); - toInject.add(new MethodInsnNode(INVOKEVIRTUAL, ASMHelper.toInternalClassName(ASMConstants.ExhaustionEvent.EXHAUSTED), "isCanceled", ASMHelper.toMethodDescriptor("Z"), false)); - toInject.add(new JumpInsnNode(IFNE, foodExhaustionBlockEndLabel)); - - // this.foodSaturationLevel = Math.max(this.foodSaturationLevel + exhaustionMaxEvent.deltaSaturation, 0.0F); - toInject.add(new VarInsnNode(ALOAD, 0)); - toInject.add(new VarInsnNode(ALOAD, 0)); - toInject.add(new FieldInsnNode(GETFIELD, internalFoodStatsName, ObfHelper.isObfuscated() ? "field_75125_b" : "foodSaturationLevel", "F")); - toInject.add(new VarInsnNode(ALOAD, exhaustedEvent.index)); - toInject.add(new FieldInsnNode(GETFIELD, ASMHelper.toInternalClassName(ASMConstants.ExhaustionEvent.EXHAUSTED), "deltaSaturation", "F")); - toInject.add(new InsnNode(FADD)); - toInject.add(new InsnNode(FCONST_0)); - toInject.add(new MethodInsnNode(INVOKESTATIC, "java/lang/Math", "max", "(FF)F", false)); - toInject.add(new FieldInsnNode(PUTFIELD, internalFoodStatsName, ObfHelper.isObfuscated() ? "field_75125_b" : "foodSaturationLevel", "F")); - - // this.foodLevel = Math.max(this.foodLevel + exhaustionMaxEvent.deltaHunger, 0); - toInject.add(new VarInsnNode(ALOAD, 0)); - toInject.add(new VarInsnNode(ALOAD, 0)); - toInject.add(new FieldInsnNode(GETFIELD, internalFoodStatsName, ObfHelper.isObfuscated() ? "field_75127_a" : "foodLevel", "I")); - toInject.add(new VarInsnNode(ALOAD, exhaustedEvent.index)); - toInject.add(new FieldInsnNode(GETFIELD, ASMHelper.toInternalClassName(ASMConstants.ExhaustionEvent.EXHAUSTED), "deltaHunger", "I")); - toInject.add(new InsnNode(IADD)); - toInject.add(new InsnNode(ICONST_0)); - toInject.add(new MethodInsnNode(INVOKESTATIC, "java/lang/Math", "max", ASMHelper.toMethodDescriptor("I", "I", "I"), false)); - toInject.add(new FieldInsnNode(PUTFIELD, internalFoodStatsName, ObfHelper.isObfuscated() ? "field_75127_a" : "foodLevel", "I")); - - method.instructions.insert(injectPoint, toInject); + updateMethodNode.instructions.insertBefore(ASMHelper.findFirstInstruction(updateMethodNode), toInject); } - private void hookHealthRegen(ClassNode classNode, MethodNode method) + private boolean tryAddFieldGetter(ClassNode classNode, String methodName, String fieldName, String fieldDescriptor) { - String internalFoodStatsName = ASMHelper.toInternalClassName(classNode.name); - LabelNode endLabel = ASMHelper.findEndLabel(method); - - InsnList toInject = new InsnList(); - - AbstractInsnNode entryPoint = ASMHelper.find(method.instructions, new LdcInsnNode("naturalRegeneration")); - AbstractInsnNode injectPoint = entryPoint.getPrevious().getPrevious().getPrevious().getPrevious(); - AbstractInsnNode healthBlockJumpToEnd = ASMHelper.findNextInstructionWithOpcode(entryPoint, GOTO); - LabelNode healthBlockEndLabel = ((JumpInsnNode) healthBlockJumpToEnd).label; - - // remove the entire health regen block - ASMHelper.removeFromInsnListUntil(method.instructions, injectPoint.getNext(), healthBlockEndLabel); - - // create allowRegenResult variable - LabelNode allowRegenResultStart = new LabelNode(); - LocalVariableNode allowRegenResult = new LocalVariableNode("allowRegenResult", ASMHelper.toDescriptor(ASMConstants.EVENT_RESULT), null, allowRegenResultStart, endLabel, method.maxLocals); - method.maxLocals += 1; - method.localVariables.add(allowRegenResult); - - // Result allowRegenResult = Hooks.fireAllowRegenEvent(player); - toInject.add(new VarInsnNode(ALOAD, 1)); - toInject.add(new MethodInsnNode(INVOKESTATIC, ASMConstants.HOOKS_INTERNAL_CLASS, "fireAllowRegenEvent", ASMHelper.toMethodDescriptor(ASMConstants.EVENT_RESULT, ASMConstants.PLAYER), false)); - toInject.add(new VarInsnNode(ASTORE, allowRegenResult.index)); - toInject.add(allowRegenResultStart); - - // if (allowRegenResult == Result.ALLOW || (allowRegenResult == Result.DEFAULT && player.worldObj.getGameRules().getGameRules("naturalRegeneration") && this.foodLevel >= 18 && player.shouldHeal())) - toInject.add(new VarInsnNode(ALOAD, allowRegenResult.index)); - toInject.add(new FieldInsnNode(GETSTATIC, ASMHelper.toInternalClassName(ASMConstants.EVENT_RESULT), "ALLOW", ASMHelper.toDescriptor(ASMConstants.EVENT_RESULT))); - LabelNode ifAllowed = new LabelNode(); - toInject.add(new JumpInsnNode(IF_ACMPEQ, ifAllowed)); - toInject.add(new VarInsnNode(ALOAD, allowRegenResult.index)); - toInject.add(new FieldInsnNode(GETSTATIC, ASMHelper.toInternalClassName(ASMConstants.EVENT_RESULT), "DEFAULT", ASMHelper.toDescriptor(ASMConstants.EVENT_RESULT))); - LabelNode elseStart = new LabelNode(); - toInject.add(new JumpInsnNode(IF_ACMPNE, elseStart)); - toInject.add(new VarInsnNode(ALOAD, 1)); - toInject.add(new FieldInsnNode(GETFIELD, ObfHelper.getInternalClassName(ASMConstants.PLAYER), ObfHelper.isObfuscated() ? "field_70170_p" : "worldObj", ASMHelper.toDescriptor(ASMConstants.WORLD))); - toInject.add(new MethodInsnNode(INVOKEVIRTUAL, ObfHelper.getInternalClassName(ASMConstants.WORLD), ObfHelper.isObfuscated() ? "func_82736_K" : "getGameRules", ASMHelper.toMethodDescriptor(ASMConstants.GAME_RULES), false)); - toInject.add(new LdcInsnNode("naturalRegeneration")); - toInject.add(new MethodInsnNode(INVOKEVIRTUAL, ObfHelper.getInternalClassName(ASMConstants.GAME_RULES), ObfHelper.isObfuscated() ? "func_82766_b" : "getBoolean", ASMHelper.toMethodDescriptor("Z", ASMConstants.STRING), false)); - toInject.add(new JumpInsnNode(IFEQ, elseStart)); - toInject.add(new VarInsnNode(ALOAD, 0)); - toInject.add(new FieldInsnNode(GETFIELD, internalFoodStatsName, ObfHelper.isObfuscated() ? "field_75127_a" : "foodLevel", "I")); - toInject.add(new IntInsnNode(BIPUSH, 18)); - toInject.add(new JumpInsnNode(IF_ICMPLT, elseStart)); - toInject.add(new VarInsnNode(ALOAD, 1)); - toInject.add(new MethodInsnNode(INVOKEVIRTUAL, ObfHelper.getInternalClassName("net.minecraft.entity.player.EntityPlayer"), ObfHelper.isObfuscated() ? "func_70996_bM" : "shouldHeal", ASMHelper.toMethodDescriptor("Z"), false)); - toInject.add(new JumpInsnNode(IFEQ, elseStart)); - toInject.add(ifAllowed); - - // ++this.foodTimer; - toInject.add(new VarInsnNode(ALOAD, 0)); - toInject.add(new InsnNode(DUP)); - toInject.add(new FieldInsnNode(GETFIELD, internalFoodStatsName, ObfHelper.isObfuscated() ? "field_75123_d" : "foodTimer", "I")); - toInject.add(new InsnNode(ICONST_1)); - toInject.add(new InsnNode(IADD)); - toInject.add(new FieldInsnNode(PUTFIELD, internalFoodStatsName, ObfHelper.isObfuscated() ? "field_75123_d" : "foodTimer", "I")); - - // if (this.foodTimer >= Hooks.fireRegenTickEvent(player)) - toInject.add(new VarInsnNode(ALOAD, 0)); - toInject.add(new FieldInsnNode(GETFIELD, internalFoodStatsName, ObfHelper.isObfuscated() ? "field_75123_d" : "foodTimer", "I")); - toInject.add(new VarInsnNode(ALOAD, 1)); - toInject.add(new MethodInsnNode(INVOKESTATIC, ASMConstants.HOOKS_INTERNAL_CLASS, "fireRegenTickEvent", ASMHelper.toMethodDescriptor("I", ASMConstants.PLAYER), false)); - toInject.add(new JumpInsnNode(IF_ICMPLT, healthBlockEndLabel)); - - // create regenEvent variable - LabelNode regenEventStart = new LabelNode(); - LabelNode regenEventEnd = new LabelNode(); - LocalVariableNode regenEvent = new LocalVariableNode("regenEvent", ASMHelper.toDescriptor(ASMConstants.HealthRegenEvent.REGEN), null, regenEventStart, regenEventEnd, method.maxLocals); - method.maxLocals += 1; - method.localVariables.add(regenEvent); - - // FoodEvent.RegenHealth.Regen regenEvent = Hooks.fireRegenEvent(player); - toInject.add(new VarInsnNode(ALOAD, 1)); - toInject.add(new MethodInsnNode(INVOKESTATIC, ASMConstants.HOOKS_INTERNAL_CLASS, "fireRegenEvent", ASMHelper.toMethodDescriptor(ASMConstants.HealthRegenEvent.REGEN, ASMConstants.PLAYER), false)); - toInject.add(new VarInsnNode(ASTORE, regenEvent.index)); - toInject.add(regenEventStart); - - // if (!regenEvent.isCanceled()) - toInject.add(new VarInsnNode(ALOAD, regenEvent.index)); - toInject.add(new MethodInsnNode(INVOKEVIRTUAL, ASMHelper.toInternalClassName(ASMConstants.HealthRegenEvent.REGEN), "isCanceled", ASMHelper.toMethodDescriptor("Z"), false)); - LabelNode ifCanceled = new LabelNode(); - toInject.add(new JumpInsnNode(IFNE, ifCanceled)); - - // player.heal(regenEvent.deltaHealth); - toInject.add(new VarInsnNode(ALOAD, 1)); - toInject.add(new VarInsnNode(ALOAD, regenEvent.index)); - toInject.add(new FieldInsnNode(GETFIELD, ASMHelper.toInternalClassName(ASMConstants.HealthRegenEvent.REGEN), "deltaHealth", "F")); - toInject.add(new MethodInsnNode(INVOKEVIRTUAL, ObfHelper.getInternalClassName(ASMConstants.ENTITY_LIVING), ObfHelper.isObfuscated() ? "func_70691_i" : "heal", ASMHelper.toMethodDescriptor("V", "F"), false)); - - // this.addExhaustion(regenEvent.deltaExhaustion); - toInject.add(new VarInsnNode(ALOAD, 0)); - toInject.add(new VarInsnNode(ALOAD, regenEvent.index)); - toInject.add(new FieldInsnNode(GETFIELD, ASMHelper.toInternalClassName(ASMConstants.HealthRegenEvent.REGEN), "deltaExhaustion", "F")); - toInject.add(new MethodInsnNode(INVOKEVIRTUAL, internalFoodStatsName, ObfHelper.isObfuscated() ? "func_75113_a" : "addExhaustion", ASMHelper.toMethodDescriptor("V", "F"), false)); - - // this.foodTimer = 0; - toInject.add(ifCanceled); - toInject.add(new VarInsnNode(ALOAD, 0)); - toInject.add(new InsnNode(ICONST_0)); - toInject.add(new FieldInsnNode(PUTFIELD, internalFoodStatsName, ObfHelper.isObfuscated() ? "field_75123_d" : "foodTimer", "I")); - toInject.add(regenEventEnd); - toInject.add(new JumpInsnNode(GOTO, healthBlockEndLabel)); - - // else - toInject.add(elseStart); - - // this.foodTimer = 0; - toInject.add(new VarInsnNode(ALOAD, 0)); - toInject.add(new InsnNode(ICONST_0)); - toInject.add(new FieldInsnNode(PUTFIELD, internalFoodStatsName, ObfHelper.isObfuscated() ? "field_75123_d" : "foodTimer", "I")); - - method.instructions.insert(injectPoint, toInject); + String methodDescriptor = ASMHelper.toMethodDescriptor(fieldDescriptor); + if (ASMHelper.findMethodNodeOfClass(classNode, methodName, methodDescriptor) != null) + return false; + + MethodVisitor mv = classNode.visitMethod(ACC_PUBLIC, methodName, methodDescriptor, null, null); + mv.visitVarInsn(ALOAD, 0); + mv.visitFieldInsn(GETFIELD, ASMHelper.toInternalClassName(classNode.name), fieldName, fieldDescriptor); + mv.visitInsn(Type.getType(fieldDescriptor).getOpcode(IRETURN)); + mv.visitMaxs(0, 0); + return true; } - private void hookStarvation(ClassNode classNode, MethodNode method) + private boolean tryAddFieldSetter(ClassNode classNode, String methodName, String fieldName, String fieldDescriptor) { - // add starveTimer field - classNode.fields.add(new FieldNode(ACC_PUBLIC, "starveTimer", "I", null, null)); - - String internalFoodStatsName = ASMHelper.toInternalClassName(classNode.name); - AbstractInsnNode lastReturn = ASMHelper.findLastInstructionWithOpcode(method, RETURN); - - InsnList toInject = new InsnList(); - - // create allowStarvationResult variable - LabelNode allowStarvationResultStart = new LabelNode(); - LabelNode beforeReturn = new LabelNode(); - // for whatever reason, the end label of this variable cant be the actual end label of the method - // it was throwing ArrayIndexOutOfBoundException in the ClassReader in obfuscated environments - // not sure why that is the case, but this workaround seems to avoid the issue - LocalVariableNode allowStarvationResult = new LocalVariableNode("allowStarvationResult", ASMHelper.toDescriptor(ASMConstants.EVENT_RESULT), null, allowStarvationResultStart, beforeReturn, method.maxLocals); - method.maxLocals += 1; - method.localVariables.add(allowStarvationResult); - - // Result allowStarvationResult = Hooks.fireAllowStarvation(player); - toInject.add(new VarInsnNode(ALOAD, 1)); - toInject.add(new MethodInsnNode(INVOKESTATIC, ASMConstants.HOOKS_INTERNAL_CLASS, "fireAllowStarvation", ASMHelper.toMethodDescriptor(ASMConstants.EVENT_RESULT, ASMConstants.PLAYER), false)); - toInject.add(new VarInsnNode(ASTORE, allowStarvationResult.index)); - toInject.add(allowStarvationResultStart); - - // if (allowStarvationResult == Result.ALLOW || (allowStarvationResult == Result.DEFAULT && this.foodLevel <= 0)) - toInject.add(new VarInsnNode(ALOAD, allowStarvationResult.index)); - toInject.add(new FieldInsnNode(GETSTATIC, ASMHelper.toInternalClassName(ASMConstants.EVENT_RESULT), "ALLOW", ASMHelper.toDescriptor(ASMConstants.EVENT_RESULT))); - LabelNode ifAllowed = new LabelNode(); - toInject.add(new JumpInsnNode(IF_ACMPEQ, ifAllowed)); - toInject.add(new VarInsnNode(ALOAD, allowStarvationResult.index)); - LabelNode elseStart = new LabelNode(); - toInject.add(new FieldInsnNode(GETSTATIC, ASMHelper.toInternalClassName(ASMConstants.EVENT_RESULT), "DEFAULT", ASMHelper.toDescriptor(ASMConstants.EVENT_RESULT))); - toInject.add(new JumpInsnNode(IF_ACMPNE, elseStart)); - toInject.add(new VarInsnNode(ALOAD, 0)); - toInject.add(new FieldInsnNode(GETFIELD, internalFoodStatsName, ObfHelper.isObfuscated() ? "field_75127_a" : "foodLevel", "I")); - toInject.add(new JumpInsnNode(IFGT, elseStart)); - toInject.add(ifAllowed); - - // ++this.starveTimer; - toInject.add(new VarInsnNode(ALOAD, 0)); - toInject.add(new InsnNode(DUP)); - toInject.add(new FieldInsnNode(GETFIELD, internalFoodStatsName, "starveTimer", "I")); - toInject.add(new InsnNode(ICONST_1)); - toInject.add(new InsnNode(IADD)); - toInject.add(new FieldInsnNode(PUTFIELD, internalFoodStatsName, "starveTimer", "I")); - - // if (this.starveTimer >= Hooks.fireStarvationTickEvent(player)) - toInject.add(new VarInsnNode(ALOAD, 0)); - toInject.add(new FieldInsnNode(GETFIELD, internalFoodStatsName, "starveTimer", "I")); - toInject.add(new VarInsnNode(ALOAD, 1)); - toInject.add(new MethodInsnNode(INVOKESTATIC, ASMConstants.HOOKS_INTERNAL_CLASS, "fireStarvationTickEvent", ASMHelper.toMethodDescriptor("I", ASMConstants.PLAYER), false)); - toInject.add(new JumpInsnNode(IF_ICMPLT, beforeReturn)); - - // create starveEvent variable - LabelNode starveEventStart = new LabelNode(); - LabelNode starveEventEnd = new LabelNode(); - LocalVariableNode starveEvent = new LocalVariableNode("starveEvent", ASMHelper.toDescriptor(ASMConstants.StarvationEvent.STARVE), null, starveEventStart, starveEventEnd, method.maxLocals); - method.maxLocals += 1; - method.localVariables.add(starveEvent); - - // FoodEvent.Starvation.Starve starveEvent = Hooks.fireStarveEvent(player); - toInject.add(new VarInsnNode(ALOAD, 1)); - toInject.add(new MethodInsnNode(INVOKESTATIC, ASMConstants.HOOKS_INTERNAL_CLASS, "fireStarveEvent", ASMHelper.toMethodDescriptor(ASMConstants.StarvationEvent.STARVE, ASMConstants.PLAYER), false)); - toInject.add(new VarInsnNode(ASTORE, starveEvent.index)); - toInject.add(starveEventStart); - - // if (!starveEvent.isCanceled()) - toInject.add(new VarInsnNode(ALOAD, starveEvent.index)); - toInject.add(new MethodInsnNode(INVOKEVIRTUAL, ASMHelper.toInternalClassName(ASMConstants.StarvationEvent.STARVE), "isCanceled", ASMHelper.toMethodDescriptor("Z"), false)); - LabelNode ifCanceled = new LabelNode(); - toInject.add(new JumpInsnNode(IFNE, ifCanceled)); - - // player.attackEntityFrom(DamageSource.starve, starveEvent.starveDamage); - toInject.add(new VarInsnNode(ALOAD, 1)); - toInject.add(new FieldInsnNode(GETSTATIC, ObfHelper.getInternalClassName(ASMConstants.DAMAGE_SOURCE), ObfHelper.isObfuscated() ? "field_76366_f" : "starve", ASMHelper.toDescriptor(ASMConstants.DAMAGE_SOURCE))); - toInject.add(new VarInsnNode(ALOAD, starveEvent.index)); - toInject.add(new FieldInsnNode(GETFIELD, ASMHelper.toInternalClassName(ASMConstants.StarvationEvent.STARVE), "starveDamage", "F")); - toInject.add(new MethodInsnNode(INVOKEVIRTUAL, ObfHelper.getInternalClassName(ASMConstants.PLAYER), ObfHelper.isObfuscated() ? "func_70097_a" : "attackEntityFrom", ASMHelper.toMethodDescriptor("Z", ASMConstants.DAMAGE_SOURCE, "F"), false)); - toInject.add(new InsnNode(POP)); - - // this.starveTimer = 0; - toInject.add(ifCanceled); - toInject.add(new VarInsnNode(ALOAD, 0)); - toInject.add(new InsnNode(ICONST_0)); - toInject.add(new FieldInsnNode(PUTFIELD, internalFoodStatsName, "starveTimer", "I")); - toInject.add(starveEventEnd); - toInject.add(new JumpInsnNode(GOTO, beforeReturn)); - - // else - toInject.add(elseStart); - - // this.starveTimer = 0; - toInject.add(new VarInsnNode(ALOAD, 0)); - toInject.add(new InsnNode(ICONST_0)); - toInject.add(new FieldInsnNode(PUTFIELD, internalFoodStatsName, "starveTimer", "I")); - - toInject.add(beforeReturn); - - method.instructions.insertBefore(lastReturn, toInject); + String methodDescriptor = ASMHelper.toMethodDescriptor("V", fieldDescriptor); + if (ASMHelper.findMethodNodeOfClass(classNode, methodName, methodDescriptor) != null) + return false; + + MethodVisitor mv = classNode.visitMethod(ACC_PUBLIC, methodName, methodDescriptor, null, null); + mv.visitVarInsn(ALOAD, 0); + mv.visitVarInsn(Type.getType(fieldDescriptor).getOpcode(ILOAD), 1); + mv.visitFieldInsn(PUTFIELD, ASMHelper.toInternalClassName(classNode.name), fieldName, fieldDescriptor); + mv.visitInsn(RETURN); + mv.visitMaxs(0, 0); + return true; } } \ No newline at end of file diff --git a/java/squeek/applecore/asm/reference/FoodStatsModifications.java b/java/squeek/applecore/asm/reference/FoodStatsModifications.java index 9ae2ab7..df0c063 100644 --- a/java/squeek/applecore/asm/reference/FoodStatsModifications.java +++ b/java/squeek/applecore/asm/reference/FoodStatsModifications.java @@ -54,66 +54,14 @@ public void addStats(ItemFood food, ItemStack stack) Hooks.onPostFoodStatsAdded(this, food, stack, modifiedFoodValues, this.foodLevel - prevFoodLevel, this.foodSaturationLevel - prevSaturationLevel, this.player); } - // heavily modified method @Override public void onUpdate(EntityPlayer player) { - this.prevFoodLevel = this.foodLevel; - - Result allowExhaustionResult = Hooks.fireAllowExhaustionEvent(player); - float maxExhaustion = Hooks.fireExhaustionTickEvent(player, foodExhaustionLevel); - if (allowExhaustionResult == Result.ALLOW || (allowExhaustionResult == Result.DEFAULT && this.foodExhaustionLevel >= maxExhaustion)) - { - ExhaustionEvent.Exhausted exhaustedEvent = Hooks.fireExhaustionMaxEvent(player, maxExhaustion, foodExhaustionLevel); - - this.foodExhaustionLevel += exhaustedEvent.deltaExhaustion; - if (!exhaustedEvent.isCanceled()) - { - this.foodSaturationLevel = Math.max(this.foodSaturationLevel + exhaustedEvent.deltaSaturation, 0.0F); - this.foodLevel = Math.max(this.foodLevel + exhaustedEvent.deltaHunger, 0); - } - } - - Result allowRegenResult = Hooks.fireAllowRegenEvent(player); - if (allowRegenResult == Result.ALLOW || (allowRegenResult == Result.DEFAULT && player.worldObj.getGameRules().hasRule("naturalRegeneration") && this.foodLevel >= 18 && player.shouldHeal())) - { - ++this.foodTimer; - - if (this.foodTimer >= Hooks.fireRegenTickEvent(player)) - { - HealthRegenEvent.Regen regenEvent = Hooks.fireRegenEvent(player); - if (!regenEvent.isCanceled()) - { - player.heal(regenEvent.deltaHealth); - this.addExhaustion(regenEvent.deltaExhaustion); - } - this.foodTimer = 0; - } - } - else - { - this.foodTimer = 0; - } - - Result allowStarvationResult = Hooks.fireAllowStarvation(player); - if (allowStarvationResult == Result.ALLOW || (allowStarvationResult == Result.DEFAULT && this.foodLevel <= 0)) - { - ++this.starveTimer; + // added lines + if (Hooks.onAppleCoreFoodStatsUpdate(this, player)) + return; - if (this.starveTimer >= Hooks.fireStarvationTickEvent(player)) - { - StarvationEvent.Starve starveEvent = Hooks.fireStarveEvent(player); - if (!starveEvent.isCanceled()) - { - player.attackEntityFrom(DamageSource.starve, starveEvent.starveDamage); - } - this.starveTimer = 0; - } - } - else - { - this.starveTimer = 0; - } + // the body of the base function } // start unmodified diff --git a/java/squeek/applecore/asm/util/IAppleCoreFoodStats.java b/java/squeek/applecore/asm/util/IAppleCoreFoodStats.java new file mode 100644 index 0000000..602c0e2 --- /dev/null +++ b/java/squeek/applecore/asm/util/IAppleCoreFoodStats.java @@ -0,0 +1,16 @@ +package squeek.applecore.asm.util; + +import net.minecraft.entity.player.EntityPlayer; + +public interface IAppleCoreFoodStats +{ + int getFoodTimer(); + void setFoodTimer(int value); + int getStarveTimer(); + void setStarveTimer(int value); + EntityPlayer getPlayer(); + void setPrevFoodLevel(int value); + float getExhaustion(); + void setExhaustion(float value); + void setSaturation(float value); +} diff --git a/java/squeek/applecore/example/HealthRegenModifier.java b/java/squeek/applecore/example/HealthRegenModifier.java index 30dfbae..44a2aec 100644 --- a/java/squeek/applecore/example/HealthRegenModifier.java +++ b/java/squeek/applecore/example/HealthRegenModifier.java @@ -32,4 +32,23 @@ public void onPeacefulRegen(HealthRegenEvent.PeacefulRegen event) event.deltaHealth = 0f; } + @SubscribeEvent + public void allowSaturatedHealthRegen(HealthRegenEvent.AllowSaturatedRegen event) + { + event.setResult(Result.DEFAULT); + } + + @SubscribeEvent + public void onSaturatedRegenTick(HealthRegenEvent.GetSaturatedRegenTickPeriod event) + { + event.regenTickPeriod = 20; + } + + @SubscribeEvent + public void onSaturatedRegen(HealthRegenEvent.SaturatedRegen event) + { + event.deltaHealth = 1; + event.deltaExhaustion = 1f; + } + } \ No newline at end of file