Jump to content

[Solved] [1.7.10] How would you replace the existing FoodStats Instance


Thornack

Recommended Posts

Hi everyone,

 

So EntityPlayer has

 protected FoodStats foodStats = new FoodStats();

I want to replace this instance with my own so that I can control the food behaviour completely. I also have created my own FoodStats Class and made it extend the vanilla FoodStats class. This is the class where I will implement custom food behaviour.

 

Now I know that to get the protected field I have to use

ObfuscationReflectionHelper.getPrivateValue(EntityPlayer.class, (EntityPlayer)event.entity, "foodStats", "field_71100_bB");

 

I also know that to set a protected field I can use

ObfuscationReflectionHelper.setPrivateValue(EntityPlayer.class, (EntityPlayer)event.entity, customFieldValue, "foodStats", "field_71100_bB");			

 

But I am unsure how to actually do the replacement so that when the player begins gameplay the vanilla FoodStats Instance is thrown away and mine is put in its place.

 

I have my EntityConstructing event, my IEEP class, a PlayerLoggedInEvent and a EntityJoinWorldEvent working and created but I am unsure what to do next.

 

Any help is appreciated.

 

Link to comment
Share on other sites

Im particularily interested in changing the onUpdate Method to be my own.  This is my code

 

The Event

@SubscribeEvent
public void onEntityConstructing(EntityConstructing event) {

	if (event.entity instanceof EntityPlayer) {

		ObfuscationReflectionHelper.setPrivateValue(EntityPlayer.class, (EntityPlayer)event.entity, new CustomFoodStats(), "foodStats", "field_71100_bB");

	}
}

 

My Custom FoodStats Class (based on Vanilla

public class CustomFoodStats extends FoodStats
{
    /** The player's food level. */
    private int foodLevel = 20;
    /** The player's food saturation. */
    private float foodSaturationLevel = 5.0F;
    /** The player's food exhaustion. */
    private float foodExhaustionLevel;
    /** The player's food timer value. */
    private int foodTimer;
    private int prevFoodLevel = 20;
   
    /**
     * Args: int foodLevel, float foodSaturationModifier
     */
    
    public void addStats(int min, float max)
    {	this.foodLevel = Math.min(min + this.foodLevel, 20);
        this.foodSaturationLevel = Math.min(this.foodSaturationLevel + (float)min * max * 2.0F, (float)this.foodLevel);
    }
    
    public void func_151686_a(ItemFood food, ItemStack itemStack)
    { 	
        this.addStats(food.func_150905_g(itemStack), food.func_150906_h(itemStack));
    }

    /**
     * Handles the food game logic.
     */
   
    public void onUpdate(EntityPlayer player)
    {  
    System.out.println("OVERRIDDEN");
    }
    

    /**
     * Reads food stats from an NBT object.
     */
   
    public void readNBT(NBTTagCompound nbt)
    {	if (nbt.hasKey("foodLevel", 99))
        {
            this.foodLevel = nbt.getInteger("foodLevel");
            this.foodTimer = nbt.getInteger("foodTickTimer");
            this.foodSaturationLevel = nbt.getFloat("foodSaturationLevel");
            this.foodExhaustionLevel = nbt.getFloat("foodExhaustionLevel");
        }
    }

    /**
     * Writes food stats to an NBT object.
     */
   
    public void writeNBT(NBTTagCompound nbt)
    {	
        nbt.setInteger("foodLevel", this.foodLevel);
        nbt.setInteger("foodTickTimer", this.foodTimer);
        nbt.setFloat("foodSaturationLevel", this.foodSaturationLevel);
        nbt.setFloat("foodExhaustionLevel", this.foodExhaustionLevel);
    }

    /**
     * Get the player's food level.
     */
    
    public int getFoodLevel()
    {	return this.foodLevel;
    }

    
    @SideOnly(Side.CLIENT)
    public int getPrevFoodLevel()
    {	
        return this.prevFoodLevel;
    }

    /**
     * If foodLevel is not max.
     */
    
    public boolean needFood()
    {	
        return this.foodLevel < 20;
    }

    /**
     * adds input to foodExhaustionLevel to a max of 40
     */
   
    public void addExhaustion(float amount)
    {	 this.foodExhaustionLevel = Math.min(this.foodExhaustionLevel + amount, 40.0F);
    }

    /**
     * Get the player's food saturation level.
     */
    
    public float getSaturationLevel()
    {	 return this.foodSaturationLevel;
    }

   
    @SideOnly(Side.CLIENT)
    public void setFoodLevel(int amount)
    {	
        this.foodLevel = amount;
    }

   
    @SideOnly(Side.CLIENT)
    public void setFoodSaturationLevel(float amount)
    {	
        this.foodSaturationLevel = amount;
    }
}

Link to comment
Share on other sites

public class CustomFoodStats extends FoodStats
{
   
   @Override
    public void onUpdate(EntityPlayer player)
    {  super.onUpdate(player);
    System.out.println("OVERRIDDEN");
    }
     
}

 

for now thats all I want to change but eventually I want to change all of it

Link to comment
Share on other sites

But if I do

 

@SubscribeEvent
public void playerLogIn(PlayerLoggedInEvent event) {
ObfuscationReflectionHelper.setPrivateValue(EntityPlayer.class,	(EntityPlayer) event.player, new CustomFoodStats(),	"foodStats", "field_71100_bB");
}

 

Then I get "OVERRIDDEN" outprinted to consol (from my custom class). But my player still gains HP like he would normally due to the regular food stats onUpdate method...

Link to comment
Share on other sites

@SubscribeEvent
public void onEntityConstructing(EntityConstructing event) {

	if (event.entity instanceof EntityPlayer) {
		System.out.println("CAAAAAAAAAALLLLLLLLLLLLLLLEEEEEEEEEEEEEDDDDDDDDDDDDDDDDDD");
		ObfuscationReflectionHelper.setPrivateValue(EntityPlayer.class,	(EntityPlayer) event.entity, new CustomFoodStats(),	"foodStats", "field_71100_bB");
	}
}

 

I see the print line so yes it is being called but I dont see the "OVERRIDDEN" being printed

 

public class CustomFoodStats extends FoodStats
{
    @Override
    public void onUpdate(EntityPlayer player)
    { 
    	System.out.println("OVERRIDDEN");
    }
     
}

 

And I should. I deleted the //super.onUpdate(player); but my player still gains health over time.... AND he shouldnt

Link to comment
Share on other sites

Funny Thing is if I move the refletion code to

@SubscribeEvent
public void playerLogIn(PlayerLoggedInEvent event) {
ObfuscationReflectionHelper.setPrivateValue(EntityPlayer.class,	(EntityPlayer) event.player, new CustomFoodStats(),	"foodStats", "field_71100_bB");
}

 

Then I do get Overridden printed to the consol but again the player still regens hp... And he shouldnt

Link to comment
Share on other sites

I found the following.

in vanilla FoodStats onUpdate

if (this.foodTimer >= 80)
            {
                p_75118_1_.heal(1.0F);
                this.addExhaustion(3.0F);
                this.foodTimer = 0;
            }

 

inEntityPlayer #onLivingUpdate

if (this.worldObj.difficultySetting == EnumDifficulty.PEACEFUL && this.getHealth() < this.getMaxHealth() && this.worldObj.getGameRules().getGameRuleBooleanValue("naturalRegeneration") && this.ticksExisted % 20 * 12 == 0)
        {
            this.heal(1.0F);
        }

 

and there is the issue that Failender Pointed out with the food stats not being replaced in all cases if I use Login event...

 

ugh... This altering of healing behaviour is a pain...

Link to comment
Share on other sites

So did you tested it on peaceful?

It is completely separate mechanism as shown in the code.

To change that, you should patch/replace player which would have more possibility of breaking compatibility.

I. Stellarium for Minecraft: Configurable Universe for Minecraft! (WIP)

II. Stellar Sky, Better Star Rendering&Sky Utility mod, had separated from Stellarium.

Link to comment
Share on other sites

Ok So I kind of have a fix but it kinda sucks haha. To overcome the problem with PEACEFUL Mode I simply decriment the players health everytime the onUpdate method incriments it. Kinda hacky n shitty but it works.

 

My custom FoodStats Class

public class CustomFoodStats extends FoodStats
{
/** The player's food amountl. */
    private int foodAmount = 15;
    /** The player's food saturation. */
    private float foodSaturation = 2.50F;
    /** The player's food exhaustion. */
    private float foodExhaustionAmount;
    /** The player's food bar timer value. */
    private int foodBarTimer;
    private int prevFoodAmount = 15;
       
    @Override
    public void onUpdate(EntityPlayer player)
    { 
    	 if (player.worldObj.difficultySetting == EnumDifficulty.PEACEFUL && player.getHealth() < player.getMaxHealth() && player.worldObj.getGameRules().getGameRuleBooleanValue("naturalRegeneration") && player.ticksExisted % 20 * 12 == 0)
         {
             player.heal(-1.0F);//Need to do this because the onLivingUpdate in EntityPlayer heals the player in peaceful mode.
         }
    	
        EnumDifficulty enumdifficulty = player.worldObj.difficultySetting;
        this.prevFoodAmount = this.foodAmount;

        if (this.foodExhaustionAmount > 4.0F)
        {
            this.foodExhaustionAmount -= 4.0F;

            if (this.foodSaturation > 0.0F)
            {
                this.foodSaturation = Math.max(this.foodSaturation - 1.0F, 0.0F);
            }
            else if (enumdifficulty != EnumDifficulty.PEACEFUL)
            {
                this.foodAmount = Math.max(this.foodAmount - 1, 0);
            }
        }

        if (player.worldObj.getGameRules().getGameRuleBooleanValue("naturalRegeneration") && this.foodAmount >= 18 && player.shouldHeal())
        {
            ++this.foodBarTimer;

            if (this.foodBarTimer >= 80)
            {
            	if(condition = true){
                //Do Custom Healing
                }else{
                player.heal(1.0F);//You can cancel healing by commenting this out
                }
                this.addExhaustion(3.0F);
                this.foodBarTimer = 0;
            }
        }
        else if (this.foodAmount <= 0)
        {
            ++this.foodBarTimer;

            if (this.foodBarTimer >= 80)
            {
                if (player.getHealth() > 10.0F || enumdifficulty == EnumDifficulty.HARD || player.getHealth() > 1.0F && enumdifficulty == EnumDifficulty.NORMAL)
                {
                    player.attackEntityFrom(DamageSource.starve, 1.0F);
                }

                this.foodBarTimer = 0;
            }
        }
        else
        {
            this.foodBarTimer = 0;
        }
    }
     
}

 

And my event which makes it all possible

@SubscribeEvent
public void onEntityJoinWorld(EntityJoinWorldEvent event) {

	if (event.entity instanceof EntityPlayer && !event.entity.worldObj.isRemote) {

	ObfuscationReflectionHelper.setPrivateValue(EntityPlayer.class,(EntityPlayer) event.entity, new CustomFoodStats(),"foodStats", "field_71100_bB");

	}
}

 

If you know of a better way to stop healing from happening I would love to hear about it. I can now just use my condition to trigger this and keep default behavior if my condition isnt true.

Link to comment
Share on other sites

I sometimes wonder if people have any level of creativity. Just because I said "You can probably use EntityConstructing event" doesn't mean you have to do it and it's the only way.

 

Yes you can do replacement whenever you want, but you can't do it in EntityConstructing - java won't allow you (seems like it's too soon :x).

 

Anyway:

public class ExtendedFoodStats extends FoodStats
{
FoodStats wrapped;

public ExtendedFoodStats(FoodStats wrapped)
{
	this.wrapped = wrapped;
}

@Override
public void onUpdate(EntityPlayer player)
{
	// Wrapping allows you to refere to parent stats with not only super,
	// which would only refere to super-class, but to ANY class that you've replaced.
	// So if there are two mods modifying FoodStats you (or other mod) would actually wrap around its predecessor.
	// Which wraps which depends on order of calling.
	// Eg. this.wrapped.onUpdate() will call object that was there previously. It can be vanilla one, or custom one you've replaced.
	System.out.println("FOOOOD");
}
}

 

What is so hard to understand about object wrapping? And yes - you shouldn't use it in your case - too big of a mod, won't work well with anything else so why bother.

 

public void onEntityJoined(EntityJoinWorldEvent event)
{
	if (event.entity instanceof EntityPlayer)
	{
		FoodStats foodStats = ((EntityPlayer) event.entity).getFoodStats();
		if (!(foodStats instanceof ExtendedFoodStats))
		{
			foodStats = new ExtendedFoodStats(foodStats);
			ObfuscationReflectionHelper.setPrivateValue(EntityPlayer.class, (EntityPlayer) event.entity, foodStats, "foodStats", "field_71100_bB");
		}
	}
}

 

EDIT:

Also, lol me - I didn't read second page of thread, seems like it was resolved :x

1.7.10 is no longer supported by forge, you are on your own.

Link to comment
Share on other sites

And yes - you shouldn't use it in your case - too big of a mod, won't work well with anything else so why bother.

Why? There are some compatibility issues.

I. Stellarium for Minecraft: Configurable Universe for Minecraft! (WIP)

II. Stellar Sky, Better Star Rendering&Sky Utility mod, had separated from Stellarium.

Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Unfortunately, your content contains terms that we do not allow. Please edit your content to remove the highlighted words below.
Reply to this topic...

×   Pasted as rich text.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Announcements



×
×
  • Create New...

Important Information

By using this site, you agree to our Terms of Use.