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