Skip to content

Commit

Permalink
Add support for 1.9 health regen and add events to API for controllin…
Browse files Browse the repository at this point in the history
…g it

 - Add HealthRegenEvent.AllowSaturatedRegen, HealthRegenEvent.GetSaturatedRegenTickPeriod, and HealthRegenEvent.SaturatedRegen events
 - Add getSaturatedHealthRegenTickPeriod method to IAppleCoreAccessor
 - Make the FoodStats.onUpdate modifications much lighter by moving the AppleCore logic into Hooks and returning early in FoodStats.onUpdate instead
 - Use dummy interface instead of reflection in AppleCoreAccessorMutatorImpl
 - Closes #65
  • Loading branch information
squeek502 committed Jun 10, 2016
1 parent 416be68 commit 2969273
Show file tree
Hide file tree
Showing 9 changed files with 296 additions and 404 deletions.
4 changes: 4 additions & 0 deletions java/squeek/applecore/api/IAppleCoreAccessor.java
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
78 changes: 77 additions & 1 deletion java/squeek/applecore/api/hunger/HealthRegenEvent.java
Original file line number Diff line number Diff line change
Expand Up @@ -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}.<br>
* <br>
Expand Down Expand Up @@ -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)
{
Expand Down Expand Up @@ -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}.<br>
* <br>
* This event is not {@link Cancelable}.<br>
* <br>
* This event uses the {@link Result}. {@link HasResult}<br>
* {@link Result#DEFAULT} will use the vanilla conditionals.<br>
* {@link Result#ALLOW} will allow regen without condition.<br>
* {@link Result#DENY} will deny regen without condition.<br>
*/
@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}.<br>
* <br>
* {@link #regenTickPeriod} contains the number of ticks between each saturated regen.<br>
* <br>
* This event is not {@link Cancelable}.<br>
* <br>
* This event does not have a {@link Result}. {@link HasResult}<br>
*/
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}.<br>
* <br>
* {@link #deltaHealth} contains the delta to be applied to health.<br>
* {@link #deltaExhaustion} contains the delta to be applied to exhaustion level.<br>
* <br>
* This event is {@link Cancelable}.<br>
* If this event is canceled, it will skip applying the delta values to health and exhaustion.<br>
* <br>
* This event does not have a {@link Result}. {@link HasResult}<br>
*/
@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;
}
}
}
46 changes: 17 additions & 29 deletions java/squeek/applecore/api_impl/AppleCoreAccessorMutatorImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -104,7 +105,7 @@ public float getExhaustion(EntityPlayer player)
{
try
{
return foodExhaustion.getFloat(player.getFoodStats());
return getAppleCoreFoodStats(player).getExhaustion();
}
catch (RuntimeException e)
{
Expand Down Expand Up @@ -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
*/
Expand All @@ -148,7 +157,7 @@ public void setExhaustion(EntityPlayer player, float exhaustion)
{
try
{
foodExhaustion.setFloat(player.getFoodStats(), exhaustion);
getAppleCoreFoodStats(player).setExhaustion(exhaustion);
}
catch (RuntimeException e)
{
Expand All @@ -163,26 +172,15 @@ 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
public void setSaturation(EntityPlayer player, float saturation)
{
try
{
foodSaturationLevel.setFloat(player.getFoodStats(), saturation);
getAppleCoreFoodStats(player).setSaturation(saturation);
}
catch (RuntimeException e)
{
Expand All @@ -199,7 +197,7 @@ public void setHealthRegenTickCounter(EntityPlayer player, int tickCounter)
{
try
{
foodTimer.setInt(player.getFoodStats(), tickCounter);
getAppleCoreFoodStats(player).setFoodTimer(tickCounter);
}
catch (RuntimeException e)
{
Expand All @@ -216,7 +214,7 @@ public void setStarveDamageTickCounter(EntityPlayer player, int tickCounter)
{
try
{
starveTimer.setInt(player.getFoodStats(), tickCounter);
getAppleCoreFoodStats(player).setStarveTimer(tickCounter);
}
catch (RuntimeException e)
{
Expand All @@ -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();
}
}
1 change: 1 addition & 0 deletions java/squeek/applecore/asm/ASMConstants.java
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand Down
108 changes: 108 additions & 0 deletions java/squeek/applecore/asm/Hooks.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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);
Expand Down Expand Up @@ -104,18 +193,37 @@ 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);
MinecraftForge.EVENT_BUS.post(event);
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);
Expand Down
Loading

0 comments on commit 2969273

Please sign in to comment.