Jimmeh Posted November 27, 2016 Posted November 27, 2016 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! Quote
TheGreyGhost Posted November 28, 2016 Posted November 28, 2016 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 Quote
Jimmeh Posted November 29, 2016 Author Posted November 29, 2016 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 Any direction is greatly appreciated! Quote
Jimmeh Posted December 1, 2016 Author Posted December 1, 2016 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 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 Thanks! Quote
TheGreyGhost Posted December 1, 2016 Posted December 1, 2016 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 Quote
Elix_x Posted December 1, 2016 Posted December 1, 2016 As TGG said, run dedicated server from your IDE and it will clear all obfuscated names. Quote Check out my mods: BTAM Armor sets Avoid Exploding Creepers Tools compressor Anti Id Conflict Key bindings overhaul Colourfull blocks Invisi Zones
Jimmeh Posted December 3, 2016 Author Posted December 3, 2016 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 Quote
TheGreyGhost Posted December 5, 2016 Posted December 5, 2016 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 Quote
Jimmeh Posted December 7, 2016 Author Posted December 7, 2016 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! Quote
Elix_x Posted December 9, 2016 Posted December 9, 2016 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. Quote Check out my mods: BTAM Armor sets Avoid Exploding Creepers Tools compressor Anti Id Conflict Key bindings overhaul Colourfull blocks Invisi Zones
Recommended Posts
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.