Jump to content

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


Recommended Posts

Posted

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.

 

Posted

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;
    }
}

Posted

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

Posted

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...

Posted

@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

Posted

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

Posted

Exactly, and anyways I am currently using it but I need to disable or gain access to the healing of the player. The player is still being healed somehow and I have no idea how

Posted

I want to conditionally disable player health regen hence why I wanted to replace the food stats instance with my own so that the player doesnt regen HP overtime and instead replace the regen with my own conditional regen code.

Posted

Why would the players hp still regen if I replace the object inside EntityPlayer with my own? Where As you can see by my class  I clearly removed any reference to calling

player.heal(1.0F);

Posted

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...

Posted

I do think that the best event to use would be the

 

@SubscribeEvent
public void onEntityJoinWorld(EntityJoinWorldEvent event) {}

as this is called whenever the player joins the world (and yes when you die and respawn this does get called.)

Posted

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.

Posted

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.

Posted

On the FoodStats, I think you'd better wrap existing foodstats as I mentioned earlier.

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

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

Posted

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.

Posted

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.

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



  • Recently Browsing

    • No registered users viewing this page.
  • Posts

    • Version 1.19 - Forge 41.0.63 I want to create a wolf entity that I can ride, so far it seems to be working, but the problem is that when I get on the wolf, I can’t control it. I then discovered that the issue is that the server doesn’t detect that I’m riding the wolf, so I’m struggling with synchronization. However, it seems to not be working properly. As I understand it, the server receives the packet but doesn’t register it correctly. I’m a bit new to Java, and I’ll try to provide all the relevant code and prints *The comments and prints are translated by chatgpt since they were originally in Spanish* Thank you very much in advance No player is mounted, or the passenger is not a player. No player is mounted, or the passenger is not a player. No player is mounted, or the passenger is not a player. No player is mounted, or the passenger is not a player. No player is mounted, or the passenger is not a player. MountableWolfEntity package com.vals.valscraft.entity; import com.vals.valscraft.network.MountSyncPacket; import com.vals.valscraft.network.NetworkHandler; import net.minecraft.client.Minecraft; import net.minecraft.network.syncher.EntityDataAccessor; import net.minecraft.network.syncher.EntityDataSerializers; import net.minecraft.network.syncher.SynchedEntityData; import net.minecraft.server.MinecraftServer; import net.minecraft.server.level.ServerPlayer; import net.minecraft.world.entity.EntityType; import net.minecraft.world.entity.Mob; import net.minecraft.world.entity.ai.attributes.AttributeSupplier; import net.minecraft.world.entity.ai.attributes.Attributes; import net.minecraft.world.entity.animal.Wolf; import net.minecraft.world.entity.player.Player; import net.minecraft.world.entity.Entity; import net.minecraft.world.InteractionHand; import net.minecraft.world.InteractionResult; import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.Items; import net.minecraft.world.level.Level; import net.minecraft.world.phys.Vec3; import net.minecraftforge.event.TickEvent; import net.minecraftforge.eventbus.api.SubscribeEvent; import net.minecraftforge.network.PacketDistributor; public class MountableWolfEntity extends Wolf { private boolean hasSaddle; private static final EntityDataAccessor<Byte> DATA_ID_FLAGS = SynchedEntityData.defineId(MountableWolfEntity.class, EntityDataSerializers.BYTE); public MountableWolfEntity(EntityType<? extends Wolf> type, Level level) { super(type, level); this.hasSaddle = false; } @Override protected void defineSynchedData() { super.defineSynchedData(); this.entityData.define(DATA_ID_FLAGS, (byte)0); } public static AttributeSupplier.Builder createAttributes() { return Wolf.createAttributes() .add(Attributes.MAX_HEALTH, 20.0) .add(Attributes.MOVEMENT_SPEED, 0.3); } @Override public InteractionResult mobInteract(Player player, InteractionHand hand) { ItemStack itemstack = player.getItemInHand(hand); if (itemstack.getItem() == Items.SADDLE && !this.hasSaddle()) { if (!player.isCreative()) { itemstack.shrink(1); } this.setSaddle(true); return InteractionResult.SUCCESS; } else if (!level.isClientSide && this.hasSaddle()) { player.startRiding(this); MountSyncPacket packet = new MountSyncPacket(true); // 'true' means the player is mounted NetworkHandler.CHANNEL.sendToServer(packet); // Ensure the server handles the packet return InteractionResult.SUCCESS; } return InteractionResult.PASS; } @Override public void travel(Vec3 travelVector) { if (this.isVehicle() && this.getControllingPassenger() instanceof Player) { System.out.println("The wolf has a passenger."); System.out.println("The passenger is a player."); Player player = (Player) this.getControllingPassenger(); // Ensure the player is the controller this.setYRot(player.getYRot()); this.yRotO = this.getYRot(); this.setXRot(player.getXRot() * 0.5F); this.setRot(this.getYRot(), this.getXRot()); this.yBodyRot = this.getYRot(); this.yHeadRot = this.yBodyRot; float forward = player.zza; float strafe = player.xxa; if (forward <= 0.0F) { forward *= 0.25F; } this.flyingSpeed = this.getSpeed() * 0.1F; this.setSpeed((float) this.getAttributeValue(Attributes.MOVEMENT_SPEED) * 1.5F); this.setDeltaMovement(new Vec3(strafe, travelVector.y, forward).scale(this.getSpeed())); this.calculateEntityAnimation(this, false); } else { // The wolf does not have a passenger or the passenger is not a player System.out.println("No player is mounted, or the passenger is not a player."); super.travel(travelVector); } } public boolean hasSaddle() { return this.hasSaddle; } public void setSaddle(boolean hasSaddle) { this.hasSaddle = hasSaddle; } @Override protected void dropEquipment() { super.dropEquipment(); if (this.hasSaddle()) { this.spawnAtLocation(Items.SADDLE); this.setSaddle(false); } } @SubscribeEvent public static void onServerTick(TickEvent.ServerTickEvent event) { if (event.phase == TickEvent.Phase.START) { MinecraftServer server = net.minecraftforge.server.ServerLifecycleHooks.getCurrentServer(); if (server != null) { for (ServerPlayer player : server.getPlayerList().getPlayers()) { if (player.isPassenger() && player.getVehicle() instanceof MountableWolfEntity) { MountableWolfEntity wolf = (MountableWolfEntity) player.getVehicle(); System.out.println("Tick: " + player.getName().getString() + " is correctly mounted on " + wolf); } } } } } private boolean lastMountedState = false; @Override public void tick() { super.tick(); if (!this.level.isClientSide) { // Only on the server boolean isMounted = this.isVehicle() && this.getControllingPassenger() instanceof Player; // Only print if the state changed if (isMounted != lastMountedState) { if (isMounted) { Player player = (Player) this.getControllingPassenger(); // Verify the passenger is a player System.out.println("Server: Player " + player.getName().getString() + " is now mounted."); } else { System.out.println("Server: The wolf no longer has a passenger."); } lastMountedState = isMounted; } } } @Override public void addPassenger(Entity passenger) { super.addPassenger(passenger); if (passenger instanceof Player) { Player player = (Player) passenger; if (!this.level.isClientSide && player instanceof ServerPlayer) { // Send the packet to the server to indicate the player is mounted NetworkHandler.CHANNEL.send(PacketDistributor.PLAYER.with(() -> (ServerPlayer) player), new MountSyncPacket(true)); } } } @Override public void removePassenger(Entity passenger) { super.removePassenger(passenger); if (passenger instanceof Player) { Player player = (Player) passenger; if (!this.level.isClientSide && player instanceof ServerPlayer) { // Send the packet to the server to indicate the player is no longer mounted NetworkHandler.CHANNEL.send(PacketDistributor.PLAYER.with(() -> (ServerPlayer) player), new MountSyncPacket(false)); } } } @Override public boolean isControlledByLocalInstance() { Entity entity = this.getControllingPassenger(); return entity instanceof Player; } @Override public void positionRider(Entity passenger) { if (this.hasPassenger(passenger)) { double xOffset = Math.cos(Math.toRadians(this.getYRot() + 90)) * 0.4; double zOffset = Math.sin(Math.toRadians(this.getYRot() + 90)) * 0.4; passenger.setPos(this.getX() + xOffset, this.getY() + this.getPassengersRidingOffset() + passenger.getMyRidingOffset(), this.getZ() + zOffset); } } } MountSyncPacket package com.vals.valscraft.network; import com.vals.valscraft.entity.MountableWolfEntity; import net.minecraft.network.FriendlyByteBuf; import net.minecraft.server.level.ServerLevel; import net.minecraft.server.level.ServerPlayer; import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.player.Player; import net.minecraftforge.network.NetworkEvent; import java.util.function.Supplier; public class MountSyncPacket { private final boolean isMounted; public MountSyncPacket(boolean isMounted) { this.isMounted = isMounted; } public void encode(FriendlyByteBuf buffer) { buffer.writeBoolean(isMounted); } public static MountSyncPacket decode(FriendlyByteBuf buffer) { return new MountSyncPacket(buffer.readBoolean()); } public void handle(NetworkEvent.Context context) { context.enqueueWork(() -> { ServerPlayer player = context.getSender(); // Get the player from the context if (player != null) { // Verifies if the player has dismounted if (!isMounted) { Entity vehicle = player.getVehicle(); if (vehicle instanceof MountableWolfEntity wolf) { // Logic to remove the player as a passenger wolf.removePassenger(player); System.out.println("Server: Player " + player.getName().getString() + " is no longer mounted."); } } } }); context.setPacketHandled(true); // Marks the packet as handled } } networkHandler package com.vals.valscraft.network; import com.vals.valscraft.valscraft; import net.minecraft.resources.ResourceLocation; import net.minecraftforge.network.NetworkRegistry; import net.minecraftforge.network.simple.SimpleChannel; import net.minecraftforge.network.NetworkEvent; import java.util.function.Supplier; public class NetworkHandler { private static final String PROTOCOL_VERSION = "1"; public static final SimpleChannel CHANNEL = NetworkRegistry.newSimpleChannel( new ResourceLocation(valscraft.MODID, "main"), () -> PROTOCOL_VERSION, PROTOCOL_VERSION::equals, PROTOCOL_VERSION::equals ); public static void init() { int packetId = 0; // Register the mount synchronization packet CHANNEL.registerMessage( packetId++, MountSyncPacket.class, MountSyncPacket::encode, MountSyncPacket::decode, (msg, context) -> msg.handle(context.get()) // Get the context with context.get() ); } }  
    • Do you use features of inventory profiles next (ipnext) or is there a change without it?
    • Remove rubidium - you are already using embeddium, which is a fork of rubidium
  • Topics

×
×
  • Create New...

Important Information

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