Jump to content

Recommended Posts

Posted

Hey there! So I have a custom entity that I'm spawning when a player left clicks in the air. I'm doing this via a custom packet sent to the server telling it to spawn that entity. That's all good and well. However, the entity spawns in, doesn't do anything, then despawns after a few seconds. I have no idea where I'm going wrong. I believe I've done the packet handling, entity class, and AI correctly. Here's the respective code:

 

Packet and IMessageHandler classes:

public class PacketEntityRockSpawn implements IMessage {

    //sent from client to server telling it to spawn an EntityRock
    //Don't need to return anything because we'll keep rock count on server side

    private String entityID;

    public PacketEntityRockSpawn(){}

    public PacketEntityRockSpawn(String id){
        this.entityID = id;
    }

    @Override
    public void fromBytes(ByteBuf buf) {
        entityID = ByteBufUtils.readUTF8String(buf);
    }

    @Override
    public void toBytes(ByteBuf buf) { ByteBufUtils.writeUTF8String(buf, entityID);
    }

    public static class HandlerEntityRockSpawn implements IMessageHandler<PacketEntityRockSpawn, IMessage> {

        public HandlerEntityRockSpawn(){}

        @Override
        public IMessage onMessage(PacketEntityRockSpawn message, MessageContext ctx) {

            //received packet from client
            EntityPlayer player = ctx.getServerHandler().playerEntity;
            Entity entity = EntityList.createEntityByIDFromName(Test.MODID + "." + message.entityID, player.worldObj);
            World worldIn = player.worldObj;

            if (entity instanceof EntityRock && player.worldObj instanceof WorldServer)
            {
                ((WorldServer)player.worldObj).addScheduledTask(new Runnable() {
                    @Override
                    public void run() {
                        EntityRock entityRock = (EntityRock) entity;
                        entityRock.setOwner(player);
                        entityRock.onInitialSpawn(worldIn.getDifficultyForLocation(new BlockPos(entityRock)), (IEntityLivingData) null);
                        worldIn.spawnEntityInWorld(entity);
                        entityRock.playLivingSound();
                        PlayerHandler.getClientInfo(player).rocks.add(entityRock);
                        System.out.println("" + PlayerHandler.getClientInfo(player).rocks.size());
                    }
                });
            }

            return null;
        }
    }
}

 

Custom Entity Class:

public class EntityRock extends EntityAbilityBase{
    
    private EntityLivingBase owner;
    private double maxTravelDistance;
    private BlockPos startPosition;

    private final int HOME_RADIUS = 10;

    private static final DataParameter<Boolean> DATA_FOLLOWING = EntityDataManager.createKey(EntityRock.class, DataSerializers.BOOLEAN);
    private static final DataParameter<Boolean> DATA_LAUNCHED = EntityDataManager.createKey(EntityRock.class, DataSerializers.BOOLEAN);

    public EntityRock(World worldIn) {
        super(worldIn);

        this.setSize(1f, 1f);
        this.experienceValue = 200;
        this.stepHeight = 1;
    }

    public void setOwner(EntityLivingBase base){
        this.owner = base;
        this.setOwnerId(owner.getUniqueID());
        this.posX = owner.posX + this.rand.nextInt(3);
        this.posY = owner.posY + owner.getEyeHeight();
        this.posZ = owner.posZ + this.rand.nextInt(3);
        setHomePosAndDistance(getOwner().getPosition(), HOME_RADIUS);
    }

    @Override
    protected void entityInit(){
        super.entityInit();

        dataManager.register(DATA_FOLLOWING, true);
        dataManager.register(DATA_LAUNCHED, false);
    }

    @Override
    protected void initEntityAI(){
        System.out.println("TASK ADDED");
        this.tasks.addTask(0, new EntityAIFollowBender(this, 1.0, 2.0));
    }

    @Override
    protected void applyEntityAttributes(){
        super.applyEntityAttributes();

        this.getEntityAttribute(SharedMonsterAttributes.MAX_HEALTH).setBaseValue(200.0D); //100 full hearts
        this.getEntityAttribute(SharedMonsterAttributes.MOVEMENT_SPEED).setBaseValue(1.0D);
    }

    @Override
    protected PathNavigate getNewNavigator(World world){
        return new PathNavigateFlying(this, world);
    }

    /**
     * Called frequently so the entity can update its state every tick as required. For example, zombies and skeletons
     * use this to react to sunlight and start to burn.
     */
    public void onLivingUpdate()
    {
        if(isServerWorld() && getOwner() != null && !getOwner().isDead){
            if(dataManager.get(DATA_FOLLOWING)){
                setHomePosAndDistance(getOwner().getPosition(), HOME_RADIUS);
            } else if(dataManager.get(DATA_LAUNCHED)){
                setHomePosAndDistance(this.getPosition(), HOME_RADIUS);
            }
        }
        super.onLivingUpdate();
    }

    public void launch(EntityPlayer player) {
        int yaw = (int)player.rotationYaw;

        if(yaw<0) {            //due to the yaw running a -360 to positive 360
            yaw += 360;
        }        //not sure why it's that way
        yaw+=22;     //centers coordinates you may want to drop this line
        yaw%=360;  //and this one if you want a strict interpretation of the zones

        int facing = yaw/45;   //  360degrees divided by 45 == 8 zones (4 normal direction, 4 diagonal)

        //-----------------------
        RayTraceResult result = player.worldObj.rayTraceBlocks(player.getPositionEyes(1.0f), player.getLookVec());
        //remove task of following owner
        //add task of going to target
        //turn step height to 0
        //mark start position
        //play sound

        if(result == null)
            return;

        /*if(result.typeOfHit != RayTraceResult.Type.MISS)
            this.tasks.removeTask(followTask);

        if(result.typeOfHit == RayTraceResult.Type.BLOCK) {
            BlockPos pos = new BlockPos(result.hitVec);
            this.tasks.addTask(0, launhTask = new EntityAITargetBlock(this, 2.0D, Integer.MAX_VALUE, pos));
        }
        else if(result.typeOfHit == RayTraceResult.Type.ENTITY && result.entityHit instanceof EntityLivingBase)
            this.tasks.addTask(0, entityLaunchTask = new EntityAITargetEntity(this, false, (EntityLivingBase) result.entityHit));

        this.stepHeight = 0;
        this.startPosition = new BlockPos(this.getPositionVector());*/

    }

    @Override
    public boolean processInteract(EntityPlayer player, EnumHand hand, ItemStack stack){
        if(hand.equals(EnumHand.MAIN_HAND)){

        }
        //remove task of following owner
        //add task of going to target
        //turn step height to 0
        //mark start position
        //play sound
        return true;
    }


    @Override
    public void writeEntityToNBT(NBTTagCompound compound){
        super.writeEntityToNBT(compound);
    }

    @Override
    public void readFromNBT(NBTTagCompound compound){
        super.readFromNBT(compound);
    }

    @Override
    public EntityAgeable createChild(EntityAgeable ageable) {
        return null;
    }

    @Override
    public boolean canBePushed(){
        return false;
    }

    @Override
    public void fall(float distance, float damageMultiplier)
    {
    }

    @Override
    public boolean isAIDisabled(){return false;}

    protected SoundEvent getAmbientSound(){
        return TestSounds.Guard;
    }
    protected SoundEvent getHurtSound(){return TestSounds.Guard;}
    protected SoundEvent getDeathSound(){return TestSounds.Guard;}


 

The "base" class it extends:

public class EntityAbilityBase extends EntityTameable {

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

    @Override
    public EntityAgeable createChild(EntityAgeable ageable) {
        return null;
    }

    /**
     * Checks if this entity is running on a client.
     *
     * Required since MCP's isClientWorld returns the exact opposite...
     *
     * @return true if the entity runs on a client or false if it runs on a server
     */
    public final boolean isClient() {
        return worldObj.isRemote;
    }

    /**
     * Checks if this entity is running on a server.
     *
     * @return true if the entity runs on a server or false if it runs on a client
     */
    public final boolean isServer() {
        return !worldObj.isRemote;
    }
}

 

The AI task that it gets:

public class EntityAIFollowBender  extends EntityAIAbilityBase
{
    private EntityLivingBase owner;
    private double minRadius;
    public EntityAIFollowBender(EntityAbilityBase ability, double speed, double minRadius) {
        super(ability, speed);
        this.minRadius = minRadius;
    }

    @Override
    public boolean shouldExecute(){
        owner = ability.getOwner();
        if(owner == null || owner.isDead || (owner != null && owner.getPositionVector().distanceTo(ability.getPositionVector()) <= minRadius)){
            System.out.println("NOT EXECUTE");
            return false;
        }
        System.out.println("EXECUTE");
        return true;
    }

    @Override
    public void updateTask(){
        //if(ability.getNavigator() instanceof PathNavigateFlying){
          //  ((PathNavigateFlying) ability.getNavigator()).tryMoveToBender(owner, speed);
        //} else {
            ability.getNavigator().tryMoveToEntityLiving(owner, speed);
        //}
    }
}

 

The base class that the AI task extends:

public class EntityAIAbilityBase extends EntityAIBase {

    protected final EntityAbilityBase ability;
    protected final World world;
    protected final Random random;
    protected final double speed;

    public EntityAIAbilityBase(EntityAbilityBase ability, double speed){
        this.ability = ability;
        this.world = ability.worldObj;
        this.random = ability.getRNG();
        this.speed = speed;
    }

    @Override
    public boolean shouldExecute() {
        return true;
    }

    protected boolean tryMoveToBlockPos(BlockPos pos){
        return ability.getNavigator().tryMoveToXYZ(pos.getX() + 0.5, pos.getY() + 0.5, pos.getZ() + 0.5, speed);
    }
}

 

The custom PathNavigate:

public class PathNavigateFlying extends PathNavigateSwimmer {

    private Random rand = new Random();
    private double posX, posY, posZ;

    public PathNavigateFlying(EntityLiving entitylivingIn, World worldIn) {
        super(entitylivingIn, worldIn);
    }

    @Override
    protected PathFinder getPathFinder(){
        return new PathFinder(new NodeProcessorFlying());
    }

    @Override
    protected boolean canNavigate(){
        return true;
    }

    public boolean tryMoveToBender(Entity entity, double speed){
        this.posX = entity.posX + Offsets.values()[this.rand.nextInt(3)].getOutput();
        this.posY = entity.posY + entity.getEyeHeight();
        this.posZ = entity.posZ + Offsets.values()[this.rand.nextInt(3)].getOutput();
        boolean a = super.tryMoveToXYZ(posX, posY, posZ, speed);
        System.out.println("move: " + String.valueOf(this.currentPath == null)
                + " " + String.valueOf(this.currentPath.getCurrentPathLength() == 0));
        return a;
    }

    private enum Offsets{
        ONE(0),
        TWO(0.5),
        THREE(1.0);

        private double output;

        Offsets(double in){output = in;}

        public double getOutput() {
            return output;
        }
    }
}

 

And finally the custom node processor:

public class NodeProcessorFlying extends SwimNodeProcessor {

    /**
     * Returns PathPoint for given coordinates
     */
    @Override
    public PathPoint getPathPointToCoords(double x, double y, double target){
        return openPoint(
                MathHelper.floor_double(x - (entity.width / 2.0)),
                MathHelper.floor_double(y + 0.5),
                MathHelper.floor_double(target - (entity.width / 2.0))
        );
    }

    @Override
    public int findPathOptions(PathPoint[] pathOptions, PathPoint currentPoint, PathPoint targetPoint, float maxDistance){
        int i = 0;

        for(EnumFacing facing: EnumFacing.values()){
            PathPoint pathPoint = getSafePoint(entity,
                    currentPoint.xCoord + facing.getFrontOffsetX(),
                    currentPoint.yCoord + facing.getFrontOffsetY(),
                    currentPoint.zCoord + facing.getFrontOffsetZ()
            );

            if(pathPoint != null && !pathPoint.visited && pathPoint.distanceTo(targetPoint) < maxDistance){
                pathOptions[i++] = pathPoint;
            }
        }

        return i;
    }

    /**
     * Returns a point that the entity can safely move to
     */
    private PathPoint getSafePoint(Entity entityIn, int x, int y, int z){
        BlockPos pos = new BlockPos(x, y, z);

        entitySizeX = MathHelper.floor_float(entityIn.width + 1);
        entitySizeY = MathHelper.floor_float(entityIn.height + 1);
        entitySizeZ = MathHelper.floor_float(entityIn.width + 1);

        for(int ix = 0; ix < entitySizeX; ix++){
            for(int iy = 0; iy < entitySizeY; iy++){
                for(int iz = 0; iz < entitySizeZ; iz++){
                    IBlockState state = blockaccess.getBlockState(pos.add(ix, iy, iz));
                    if(state.getMaterial() != Material.AIR){
                        return null;
                    }
                }
            }
        }

        return openPoint(x, y, z);
    }

 

I know this is a lot to look at, so any help is greatly appreciated! :D

Posted

Hi

 

I think the first place to start is to see if you can figure out why the entity despawns.

 

Try adding

System.out.println("XXX:side=" + (this.worldObj.isRemote ? "client" : "server"));

to suitable places in your entity code (for example onUpdate(), setDead()).  Or breakpoints.

 

-TGG

Posted

Hi

 

I think the first place to start is to see if you can figure out why the entity despawns.

 

Try adding

System.out.println("XXX:side=" + (this.worldObj.isRemote ? "client" : "server"));

to suitable places in your entity code (for example onUpdate(), setDead()).  Or breakpoints.

 

-TGG

 

Hey, so I just tested that. It's immediately despawning server-side right after it's spawned in, however, the client-representation isn't "despawned" for a few seconds after, which I find weird. Unfortunately, I have no idea where to go from here xD Any direction is greatly appreciated!

Posted

Okay, so I think I've kinda narrowed it down. I've added printing stacktraces to "despawnEntity()" and "setDead()". The results seem to be this:

 

- Entity spawns

- Immediately despawns (despawnEntity()) server side

- Client isn't "updated" about this because I still get "onLivingUpdate()" calls client-side

- Entity eventually is setDead() client side after a few seconds.

 

I have absolutely no idea where to go from here. There seems to be a break in server -> client synchronicity. That's the best guess I have right now xD

 

Here's the entity class that I'm using:

public class EntityAbilityBase extends EntityTameable {

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

    @Override
    public EntityAgeable createChild(EntityAgeable ageable) {
        return null;
    }

    /**
     * Checks if this entity is running on a client.
     *
     * Required since MCP's isClientWorld returns the exact opposite...
     *
     * @return true if the entity runs on a client or false if it runs on a server
     */
    public final boolean isClient() {
        return worldObj.isRemote;
    }

    /**
     * Checks if this entity is running on a server.
     *
     * @return true if the entity runs on a server or false if it runs on a client
     */
    public final boolean isServer() {
        return !worldObj.isRemote;
    }

    @Override
    protected void despawnEntity(){
        System.out.println("DESPAWNED " + this.worldObj.isRemote);
        for (StackTraceElement ste : Thread.currentThread().getStackTrace()) {
            System.out.println(ste);
        }
        super.despawnEntity();
    }

    @Override
    public void setDead(){
        for (StackTraceElement ste : Thread.currentThread().getStackTrace()) {
            System.out.println(ste);
        }
        super.setDead();
    }
}

 

Here's the server console log:

http://hastebin.com/ulijiloyuq.css

 

And here's the client console log, which finally has setDead() called seconds after the server despawns:

http://hastebin.com/aharugajuf.md

 

As always, any help is now beyond appreciated xD Thanks!

Posted

Hi

 

The lag between server and client isn't anything particular to worry about at this stage, I think.

 

I think you need to figure out what's causing your entity to despawn in such a hurry.

 

I can't tell what those obfuscated methods are (and MCPBot is not talking to me today, not sure why!), but in any case I would suggest you put a breakpoint in the server despawn instead.  Or run your test in an integrated server from Eclipse (or IDEA) instead of a built version in dedicated server.

 

-TGG

Posted

Alrighty. So I've managed to get the deobfuscated console logs for the client and server. I've sat here trying to trace it one method to the next to figure out why this thing keeps despawning, but I can't seem to figure it out. I've added breakpoints every where I can think to put them, especially within EntityLiving#despawnEntity(). I even added the LivingEntitySpawn.AllowDespawn event, setting that result to Result.DENY, and that did absolutely nothing (in fact, it didn't even fire). I'm completely lost on what to do D:

 

Here's the server console log:

http://hastebin.com/acidipizuw.css

 

Here's the EntityAbilityBase referred to in that log:

http://hastebin.com/rinejibele.java

 

And lastly, the EntityRock referred to as well:

http://hastebin.com/mudoquguwe.java

 

Once again, any help is greatly appreciated, because right now, I can't make custom entities :/

Posted

Hi

 

The name of the despawn method is actually a bit misleading.  It just checks for whether the entity needs to be despawned (i.e. because it's too far away from the player); usually it doesn't do anything.

 

Try putting the breakpoint into setDead() instead, and posting us the stack trace from there.

 

-TGG

 

 

Posted

Sorry about the late reply. I've been pretty busy! Okay, so I have the deobfuscated console logs of the server and client here. I've tried to "cross reference" them to figure things out, but I'm still having a difficult time. setDead() is only being called clientside, I've noticed.

 

Here's the server:

http://hastebin.com/pahiqamasu.css

 

Here's the console:

http://hastebin.com/ayihawusez.sql

 

And here's the class in which I've overridden the despawnEntity() and setDead() methods (I left in the whole class that way the line numbers would line up correctly):

http://hastebin.com/xehapoquyi.java

 

Once again, all help is greatly appreciated! Thank you!

Posted

Entity being killed onlyon client means that it's considered as "memory garbage" - it's too far, not in render or does not meet client stay conditions.

 

Try tracing origin of client entity remove packet and put a few breakpoints there to find which criteria is validated/invalidated.

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.