Jump to content

[SOLVED] [1.8] Strange behavior when custom arrow/projectile is in ground.


Recommended Posts

Posted

I have a spear that acts like an arrow, but when i shoot it in a block it has a strange behavior. It "moves" up after it hit the block. Here is a video to show you what i mean

https://www.youtube.com/watch?v=RUIcAT-zqTc&feature=youtu.be

 

This is my entity class

package blaze.entities;

import blaze.core.BLItems;
import io.netty.buffer.ByteBuf;
import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.entity.projectile.EntityArrow;
import net.minecraft.item.ItemStack;
import net.minecraft.util.MathHelper;
import net.minecraft.world.World;
import net.minecraftforge.fml.common.registry.IEntityAdditionalSpawnData;

public class EntityOniceSpear extends EntityArrow implements IEntityAdditionalSpawnData 
{
    private boolean inGround;

    public EntityOniceSpear(World worldIn)
    {
        super(worldIn);
    }

    public EntityOniceSpear(World worldIn, EntityLivingBase shooter, EntityLivingBase par3, float par4, float par5)
    {
        super(worldIn, shooter, par3, par4, par5);
    }
    
    public EntityOniceSpear(World worldIn, EntityLivingBase shooter, float par3)
    {
        super(worldIn, shooter, par3);
    }

/**
     * Called by a player entity when they collide with an entity
     */
    @Override
    public void onCollideWithPlayer(EntityPlayer entityIn)
    {
        if (!this.worldObj.isRemote && this.inGround && this.arrowShake <= 0)
        {
            boolean flag = this.canBePickedUp == 1 || this.canBePickedUp == 2 && entityIn.capabilities.isCreativeMode;

            if (this.canBePickedUp == 1 && !entityIn.inventory.addItemStackToInventory(new ItemStack(BLItems.onice_spear, 1)))
            {
                flag = false;
            }

            if (flag)
            {
                this.playSound("random.pop", 0.2F, ((this.rand.nextFloat() - this.rand.nextFloat()) * 0.7F + 1.0F) * 2.0F);
                entityIn.onItemPickup(this, 1);
                this.setDead();
            }
        }
    }

    @Override
public void writeSpawnData(ByteBuf buffer) {
	buffer.writeInt(shootingEntity != null ? shootingEntity.getEntityId() : -1);
}

@Override
public void readSpawnData(ByteBuf buffer) {
	Entity shooter = worldObj.getEntityByID(buffer.readInt());
	if (shooter instanceof EntityLivingBase) {
		shootingEntity = (EntityLivingBase) shooter;
	}
}
}

 

And this is how i register the entity

EntityRegistry.registerModEntity(EntityMalachiteSpear.class, "malachiteSpear", 2 , BL.instance, 64, 20, true);

The value 64 and 20 has been taken from the registration of the EntityArrow class in EntityTracker (here it also set false the last value, but if i do the arrow doesn't do the moving animation)

 

So why has this behavior? :/ Thanks in advance to all who will help me :)

Don't blame me if i always ask for your help. I just want to learn to be better :)

Posted

Usually that is caused by the position being out of sync between server and client, and the 'movement' is caused by the client getting the real position from the server.

 

I remember having a similar issue long ago and thought it was solved by using the same tracking values as a real EntityArrow, but you already do, so I'm not too sure.

 

There is one thing that your entity will not have that arrows do, however, and that is the initial spawn packet of arrows sends information about the shooter to the client. This happens in NetHandlerPlayClient#handleSpawnObject, which you can replicate by implementing IEntityAdditionalSpawnData for your spear:

@Override
public void writeSpawnData(ByteBuf buffer) {
	buffer.writeInt(shootingEntity != null ? shootingEntity.getEntityId() : -1);
}

@Override
public void readSpawnData(ByteBuf buffer) {
	Entity shooter = worldObj.getEntityByID(buffer.readInt());
	if (shooter instanceof EntityLivingBase) {
		shootingEntity = (EntityLivingBase) shooter;
	}
}

I doubt that will fix your issue directly, but it will at least make your spear more closely mimic vanilla arrows.

Posted

Thanks for your reply :) I've added these methods but now the entity is not rendering ad eclipse gives me this error :/

[22:27:36] [Client thread/ERROR] [FML]: A severe problem occurred during the spawning of an entity at ( 558.0625,5.5, -549.84375)
java.lang.NoSuchMethodException: blaze.entities.EntityOniceSpear.<init>(net.minecraft.world.World)
at java.lang.Class.getConstructor0(Unknown Source) ~[?:1.8.0_51]
at java.lang.Class.getConstructor(Unknown Source) ~[?:1.8.0_51]
at net.minecraftforge.fml.common.network.internal.EntitySpawnHandler.spawnEntity(EntitySpawnHandler.java:98) [EntitySpawnHandler.class:?]
at net.minecraftforge.fml.common.network.internal.EntitySpawnHandler.process(EntitySpawnHandler.java:56) [EntitySpawnHandler.class:?]
at net.minecraftforge.fml.common.network.internal.EntitySpawnHandler.access$000(EntitySpawnHandler.java:31) [EntitySpawnHandler.class:?]
at net.minecraftforge.fml.common.network.internal.EntitySpawnHandler$1.run(EntitySpawnHandler.java:46) [EntitySpawnHandler$1.class:?]
at java.util.concurrent.Executors$RunnableAdapter.call(Unknown Source) [?:1.8.0_51]
at java.util.concurrent.FutureTask.run(Unknown Source) [?:1.8.0_51]
at net.minecraftforge.fml.common.FMLCommonHandler.callFuture(FMLCommonHandler.java:709) [FMLCommonHandler.class:?]
at net.minecraft.client.Minecraft.runGameLoop(Minecraft.java:1070) [Minecraft.class:?]
at net.minecraft.client.Minecraft.run(Minecraft.java:376) [Minecraft.class:?]
at net.minecraft.client.main.Main.main(Main.java:117) [Main.class:?]
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[?:1.8.0_51]
at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source) ~[?:1.8.0_51]
at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source) ~[?:1.8.0_51]
at java.lang.reflect.Method.invoke(Unknown Source) ~[?:1.8.0_51]
at net.minecraft.launchwrapper.Launch.launch(Launch.java:135) [launchwrapper-1.11.jar:?]
at net.minecraft.launchwrapper.Launch.main(Launch.java:28) [launchwrapper-1.11.jar:?]
at net.minecraftforge.gradle.GradleStartCommon.launch(Unknown Source) [start/:?]
at GradleStart.main(Unknown Source) [start/:?]
[22:27:36] [Client thread/FATAL] [FML]: Exception caught executing FutureTask: java.util.concurrent.ExecutionException: java.lang.RuntimeException: java.lang.NoSuchMethodException: blaze.entities.EntityOniceSpear.<init>(net.minecraft.world.World)
java.util.concurrent.ExecutionException: java.lang.RuntimeException: java.lang.NoSuchMethodException: blaze.entities.EntityOniceSpear.<init>(net.minecraft.world.World)
at java.util.concurrent.FutureTask.report(Unknown Source) ~[?:1.8.0_51]
at java.util.concurrent.FutureTask.get(Unknown Source) ~[?:1.8.0_51]
at net.minecraftforge.fml.common.FMLCommonHandler.callFuture(FMLCommonHandler.java:710) [FMLCommonHandler.class:?]
at net.minecraft.client.Minecraft.runGameLoop(Minecraft.java:1070) [Minecraft.class:?]
at net.minecraft.client.Minecraft.run(Minecraft.java:376) [Minecraft.class:?]
at net.minecraft.client.main.Main.main(Main.java:117) [Main.class:?]
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[?:1.8.0_51]
at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source) ~[?:1.8.0_51]
at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source) ~[?:1.8.0_51]
at java.lang.reflect.Method.invoke(Unknown Source) ~[?:1.8.0_51]
at net.minecraft.launchwrapper.Launch.launch(Launch.java:135) [launchwrapper-1.11.jar:?]
at net.minecraft.launchwrapper.Launch.main(Launch.java:28) [launchwrapper-1.11.jar:?]
at net.minecraftforge.gradle.GradleStartCommon.launch(Unknown Source) [start/:?]
at GradleStart.main(Unknown Source) [start/:?]
Caused by: java.lang.RuntimeException: java.lang.NoSuchMethodException: blaze.entities.EntityOniceSpear.<init>(net.minecraft.world.World)
at com.google.common.base.Throwables.propagate(Throwables.java:160) ~[guava-17.0.jar:?]
at net.minecraftforge.fml.common.network.internal.EntitySpawnHandler.spawnEntity(EntitySpawnHandler.java:147) ~[EntitySpawnHandler.class:?]
at net.minecraftforge.fml.common.network.internal.EntitySpawnHandler.process(EntitySpawnHandler.java:56) ~[EntitySpawnHandler.class:?]
at net.minecraftforge.fml.common.network.internal.EntitySpawnHandler.access$000(EntitySpawnHandler.java:31) ~[EntitySpawnHandler.class:?]
at net.minecraftforge.fml.common.network.internal.EntitySpawnHandler$1.run(EntitySpawnHandler.java:46) ~[EntitySpawnHandler$1.class:?]
at java.util.concurrent.Executors$RunnableAdapter.call(Unknown Source) ~[?:1.8.0_51]
at java.util.concurrent.FutureTask.run(Unknown Source) ~[?:1.8.0_51]
at net.minecraftforge.fml.common.FMLCommonHandler.callFuture(FMLCommonHandler.java:709) ~[FMLCommonHandler.class:?]
... 11 more
Caused by: java.lang.NoSuchMethodException: blaze.entities.EntityOniceSpear.<init>(net.minecraft.world.World)
at java.lang.Class.getConstructor0(Unknown Source) ~[?:1.8.0_51]
at java.lang.Class.getConstructor(Unknown Source) ~[?:1.8.0_51]
at net.minecraftforge.fml.common.network.internal.EntitySpawnHandler.spawnEntity(EntitySpawnHandler.java:98) ~[EntitySpawnHandler.class:?]
at net.minecraftforge.fml.common.network.internal.EntitySpawnHandler.process(EntitySpawnHandler.java:56) ~[EntitySpawnHandler.class:?]
at net.minecraftforge.fml.common.network.internal.EntitySpawnHandler.access$000(EntitySpawnHandler.java:31) ~[EntitySpawnHandler.class:?]
at net.minecraftforge.fml.common.network.internal.EntitySpawnHandler$1.run(EntitySpawnHandler.java:46) ~[EntitySpawnHandler$1.class:?]
at java.util.concurrent.Executors$RunnableAdapter.call(Unknown Source) ~[?:1.8.0_51]
at java.util.concurrent.FutureTask.run(Unknown Source) ~[?:1.8.0_51]
at net.minecraftforge.fml.common.FMLCommonHandler.callFuture(FMLCommonHandler.java:709) ~[FMLCommonHandler.class:?]
... 11 more

 

QUICK UPDATE: i've solved that problem by adding the other constructors, but now i can't pickup the spear when on ground

public EntityOniceSpear(World worldIn)
    {
        super(worldIn);
    }

    public EntityOniceSpear(World worldIn, double x, double y, double z)
    {
        super(worldIn);
    }

    public EntityOniceSpear(World worldIn, EntityLivingBase shooter, EntityLivingBase p_i1755_3_, float p_i1755_4_, float p_i1755_5_)
    {
        super(worldIn);
    }

Don't blame me if i always ask for your help. I just want to learn to be better :)

Posted

yes i was able to pickup before, and i didn't change anything about that part (the function onCollideWithPlayer is the same)

Don't blame me if i always ask for your help. I just want to learn to be better :)

Posted

Oh, I see why (at least if you are using this constructor):

public EntityOniceSpear(World worldIn, EntityLivingBase shooter, EntityLivingBase p_i1755_3_, float p_i1755_4_, float p_i1755_5_)
    {
        super(worldIn);
    }

You are calling the wrong super method - all of the extra processing that gets done when there is a shooter is not being done.

 

Again, can you show the code that actually spawns the entity? E.g. in your Item class #onItemRightClick or wherever?

Posted

This is the item class from where i shoot the spear

package blaze.items;

import blaze.core.BLTabs;
import blaze.entities.EntityMalachiteSpear;
import blaze.entities.EntityOniceSpear;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.init.Items;
import net.minecraft.item.Item;
import net.minecraft.item.ItemBow;
import net.minecraft.item.ItemStack;
import net.minecraft.stats.StatList;
import net.minecraft.world.World;
import net.minecraftforge.fml.relauncher.Side;
import net.minecraftforge.fml.relauncher.SideOnly;

public class ItemSpear extends ItemBow
{

private int type;

public ItemSpear(int type)
{
	this.setMaxDamage(0);
	this.setMaxStackSize(16);
	this.type = type;
	this.setCreativeTab(BLTabs.tabCombat);
}

/**
     * Called whenever this item is equipped and the right mouse button is pressed. Args: itemStack, world, entityPlayer
     */
@Override
    public ItemStack onItemRightClick(ItemStack itemStackIn, World worldIn, EntityPlayer playerIn)
    {
        net.minecraftforge.event.entity.player.ArrowNockEvent event = new net.minecraftforge.event.entity.player.ArrowNockEvent(playerIn, itemStackIn);
        if (net.minecraftforge.common.MinecraftForge.EVENT_BUS.post(event)) return event.result;
        playerIn.setItemInUse(itemStackIn, this.getMaxItemUseDuration(itemStackIn));

        return itemStackIn;
    }

/**
 * How long it takes to use or consume an item
 */
@Override
public int getMaxItemUseDuration(ItemStack stack)
{
	return 72000;
}

/**
 * Called when the player stops using an Item (stops holding the right mouse button).
 *  
 * @param timeLeft The amount of ticks left before the using would have been complete
 */
@Override
public void onPlayerStoppedUsing(ItemStack stack, World worldIn, EntityPlayer playerIn, int timeLeft)
{
	int j = this.getMaxItemUseDuration(stack) - timeLeft;
	net.minecraftforge.event.entity.player.ArrowLooseEvent event = new net.minecraftforge.event.entity.player.ArrowLooseEvent(playerIn, stack, j);
	if (net.minecraftforge.common.MinecraftForge.EVENT_BUS.post(event)) return;
	j = event.charge;

	boolean flag = playerIn.capabilities.isCreativeMode;
		float f = (float)j / 20.0F;
		f = (f * f + f * 2.0F) / 3.0F;

		if ((double)f < 0.1D)
		{
			return;
		}

		if (f > 1.0F)
		{
			f = 1.0F;
		}

		if(this.type == 0)
		{
			EntityOniceSpear entityspear = new EntityOniceSpear(worldIn, playerIn, f * 2.0F);
			if (flag)
			{
				entityspear.canBePickedUp = 2;
			}
			else
			{
				playerIn.inventory.consumeInventoryItem(this);
			}

			playerIn.triggerAchievement(StatList.objectUseStats[item.getIdFromItem(this)]);

			if (!worldIn.isRemote)
			{
				worldIn.spawnEntityInWorld(entityspear);
			}
		}
		else
		{
			EntityMalachiteSpear entityspear = new EntityMalachiteSpear(worldIn, playerIn, f * 2.0F);
			if (flag)
			{
				entityspear.canBePickedUp = 2;
			}
			else
			{
				playerIn.inventory.consumeInventoryItem(this);
			}

			playerIn.triggerAchievement(StatList.objectUseStats[item.getIdFromItem(this)]);

			if (!worldIn.isRemote)
			{
				worldIn.spawnEntityInWorld(entityspear);
			}
		}

}

@Override
@SideOnly(Side.CLIENT)
public boolean isFull3D()
{
	return true;
}
}

Don't blame me if i always ask for your help. I just want to learn to be better :)

Posted

Of course, here is the entity class :)

package blaze.entities;

import blaze.core.BLItems;
import io.netty.buffer.ByteBuf;
import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.entity.projectile.EntityArrow;
import net.minecraft.item.ItemStack;
import net.minecraft.util.MathHelper;
import net.minecraft.world.World;
import net.minecraftforge.fml.common.registry.IEntityAdditionalSpawnData;

public class EntityOniceSpear extends EntityArrow implements IEntityAdditionalSpawnData 
{
    private boolean inGround;

    public EntityOniceSpear(World worldIn)
    {
        super(worldIn);
    }

    public EntityOniceSpear(World worldIn, EntityLivingBase shooter, EntityLivingBase par3, float par4, float par5)
    {
        super(worldIn, shooter, par3, par4, par5);
    }
    
    public EntityOniceSpear(World worldIn, EntityLivingBase shooter, float par3)
    {
        super(worldIn, shooter, par3);
    }

/**
     * Called by a player entity when they collide with an entity
     */
    @Override
    public void onCollideWithPlayer(EntityPlayer entityIn)
    {
        if (!this.worldObj.isRemote && this.inGround && this.arrowShake <= 0)
        {
            boolean flag = this.canBePickedUp == 1 || this.canBePickedUp == 2 && entityIn.capabilities.isCreativeMode;

            if (this.canBePickedUp == 1 && !entityIn.inventory.addItemStackToInventory(new ItemStack(BLItems.onice_spear, 1)))
            {
                flag = false;
            }

            if (flag)
            {
                this.playSound("random.pop", 0.2F, ((this.rand.nextFloat() - this.rand.nextFloat()) * 0.7F + 1.0F) * 2.0F);
                entityIn.onItemPickup(this, 1);
                this.setDead();
            }
        }
    }

    @Override
public void writeSpawnData(ByteBuf buffer) {
	buffer.writeInt(shootingEntity != null ? shootingEntity.getEntityId() : -1);
}

@Override
public void readSpawnData(ByteBuf buffer) {
	Entity shooter = worldObj.getEntityByID(buffer.readInt());
	if (shooter instanceof EntityLivingBase) {
		shootingEntity = (EntityLivingBase) shooter;
	}
}
}

Don't blame me if i always ask for your help. I just want to learn to be better :)

Posted

I have that constructor, is here

public EntityOniceSpear(World worldIn)
    {
        super(worldIn);
    }

Don't blame me if i always ask for your help. I just want to learn to be better :)

Posted

It is in the new code. Anyway i've updated the original post so you should see it now

Don't blame me if i always ask for your help. I just want to learn to be better :)

Posted

I see your problem now - you have duplicated the EntityArrow#onGround field, but you can't override class fields like that. EntityArrow's onGround is the one being used in all of the logic, and yours just sits there doing nothing.

 

Since it is private in 1.8, you can use reflection (recommended course of action) or find some other way to determine if it is in the ground (e.g. current velocity < 0.1 or something). You could even use ASM to change the vanilla code so that the field is public, if you wanted, but that's probably more work than it's worth.

Posted

I think i'll try the "velocity way". Unfortunately i don't know how to use asm (any link to a tutorial will be very appreciated) and for reflection i'm still learning how it works :) I'll let you know if checking it's velocity solve the problem ;)

Don't blame me if i always ask for your help. I just want to learn to be better :)

Posted

Alright, solved by doing this

public boolean isInGround()
    {
    	double x = this.posX - this.lastTickPosX;
    	double y = this.posY - this.lastTickPosY;
    	double z = this.posZ - this.lastTickPosZ;
    	
    	return (x == 0.0D && y == 0.0D && z == 0.0D);
    }
    
/**
     * Called by a player entity when they collide with an entity
     */
    @Override
    public void onCollideWithPlayer(EntityPlayer entityIn)
    {
        if (!this.worldObj.isRemote && this.isInGround() && this.arrowShake <= 0)
        {
            boolean flag = this.canBePickedUp == 1 || this.canBePickedUp == 2 && entityIn.capabilities.isCreativeMode;

            if (this.canBePickedUp == 1 && !entityIn.inventory.addItemStackToInventory(new ItemStack(BLItems.onice_spear, 1)))
            {
                flag = false;
            }

            if (flag)
            {
                this.playSound("random.pop", 0.2F, ((this.rand.nextFloat() - this.rand.nextFloat()) * 0.7F + 1.0F) * 2.0F);
                entityIn.onItemPickup(this, 1);
                this.setDead();
            }
        }
    }

 

Thanks for the help you gave me :D

Don't blame me if i always ask for your help. I just want to learn to be better :)

Posted

Nice, though double precision doesn't usually lend itself well to direct equality comparisons, it can still work out for the most part :P

 

If you wanted to go the Reflection route, Forge provides the handy ReflectionHelper class. Class fields are indexed starting from 0, so just count the fields in EntityArrow until you reach 'inGround' and you will have the correct index:

// you want field 5 (i.e. 'inGround' from the EntityArrow class) and using the current instance [the spear entity] to retrieve the value
Object o = ReflectionHelper.getPrivateValue(EntityArrow.class, this, 5);

// should print out 'true' when your spear is in the ground and 'false' otherwise
System.out.println("Value for field 5: " + o);

Pretty straightforward, for the most part, though that just scratches the surface ;)

Posted

Using reflection i think is the best way, so if i want to use it where should i put that code (i mean where should i set the onGround value)?

Don't blame me if i always ask for your help. I just want to learn to be better :)

Posted

I just use it when I want to get the value.

 

The ReflectionHelper sets the field to be accessible when you access it, and if it's already been made accessible I don't think that it has to do that step again so you shouldn't be incurring any extra performance penalty, but I haven't delved too deeply into the details of exactly how Reflection works so take all that with a grain of salt.

 

At any rate, since you will only be using Reflection in onCollideWithPlayer, even if it wasn't the most performance-friendly solution no one would ever notice because it is usually only called once ;)

Posted

Thank you, i've change the isInGround function to this

public boolean isInGround()
    {
    	return ReflectionHelper.getPrivateValue(EntityArrow.class, this, 5);
    }

and it's still working, so i think i'll leave this ;) Thanks really much for your help and explanations, i only i could i'll give you more than one thank you :D

Don't blame me if i always ask for your help. I just want to learn to be better :)

Guest
This topic is now closed to further replies.

Announcements



×
×
  • Create New...

Important Information

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