--- a/net/minecraft/server/Entity.java +++ b/net/minecraft/server/Entity.java @@ -20,8 +20,55 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +// CraftBukkit start +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.Server; +import org.bukkit.block.BlockFace; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Hanging; +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Vehicle; +import org.bukkit.event.entity.EntityCombustByEntityEvent; +import org.bukkit.event.hanging.HangingBreakByEntityEvent; +import org.bukkit.event.vehicle.VehicleBlockCollisionEvent; +import org.bukkit.event.vehicle.VehicleEnterEvent; +import org.bukkit.event.vehicle.VehicleExitEvent; +import org.bukkit.craftbukkit.CraftWorld; +import org.bukkit.craftbukkit.entity.CraftEntity; +import org.bukkit.craftbukkit.entity.CraftPlayer; +import org.bukkit.craftbukkit.event.CraftEventFactory; +import org.bukkit.event.entity.EntityAirChangeEvent; +import org.bukkit.event.entity.EntityCombustEvent; +import org.bukkit.event.entity.EntityDropItemEvent; +import org.bukkit.event.entity.EntityPortalEvent; +import org.bukkit.event.player.PlayerTeleportEvent; +import org.bukkit.plugin.PluginManager; +// CraftBukkit end + public abstract class Entity implements INamableTileEntity, ICommandListener { + // CraftBukkit start + private static final int CURRENT_LEVEL = 2; + static boolean isLevelAtLeast(NBTTagCompound tag, int level) { + return tag.hasKey("Bukkit.updateLevel") && tag.getInt("Bukkit.updateLevel") >= level; + } + + protected CraftEntity bukkitEntity; + + public CraftEntity getBukkitEntity() { + if (bukkitEntity == null) { + bukkitEntity = CraftEntity.getEntity(world.getServer(), this); + } + return bukkitEntity; + } + + @Override + public CommandSender getBukkitSender(CommandListenerWrapper wrapper) { + return getBukkitEntity(); + } + // CraftBukkit end + protected static final Logger LOGGER = LogManager.getLogger(); private static final AtomicInteger entityCount = new AtomicInteger(); private static final List c = Collections.emptyList(); @@ -106,6 +153,16 @@ private long aH; private EntitySize size; private float headHeight; + // CraftBukkit start + public boolean persist = true; + public boolean valid; + public org.bukkit.projectiles.ProjectileSource projectileSource; // For projectiles only + public boolean forceExplosionKnockback; // SPIGOT-949 + + public float getBukkitYaw() { + return this.yaw; + } + // CraftBukkit end public Entity(EntityTypes entitytypes, World world) { this.id = Entity.entityCount.incrementAndGet(); @@ -212,6 +269,33 @@ } protected void setYawPitch(float f, float f1) { + // CraftBukkit start - yaw was sometimes set to NaN, so we need to set it back to 0 + if (Float.isNaN(f)) { + f = 0; + } + + if (f == Float.POSITIVE_INFINITY || f == Float.NEGATIVE_INFINITY) { + if (this instanceof EntityPlayer) { + this.world.getServer().getLogger().warning(this.getName() + " was caught trying to crash the server with an invalid yaw"); + ((CraftPlayer) this.getBukkitEntity()).kickPlayer("Infinite yaw (Hacking?)"); + } + f = 0; + } + + // pitch was sometimes set to NaN, so we need to set it back to 0 + if (Float.isNaN(f1)) { + f1 = 0; + } + + if (f1 == Float.POSITIVE_INFINITY || f1 == Float.NEGATIVE_INFINITY) { + if (this instanceof EntityPlayer) { + this.world.getServer().getLogger().warning(this.getName() + " was caught trying to crash the server with an invalid pitch"); + ((CraftPlayer) this.getBukkitEntity()).kickPlayer("Infinite pitch (Hacking?)"); + } + f1 = 0; + } + // CraftBukkit end + this.yaw = f % 360.0F; this.pitch = f1 % 360.0F; } @@ -224,6 +308,7 @@ float f1 = this.size.height; this.a(new AxisAlignedBB(d0 - (double) f, d1, d2 - (double) f, d0 + (double) f, d1 + (double) f1, d2 + (double) f)); + if (valid) ((WorldServer) world).chunkCheck(this); // CraftBukkit } public void tick() { @@ -234,6 +319,15 @@ this.entityBaseTick(); } + // CraftBukkit start + public void postTick() { + // No clean way to break out of ticking once the entity has been copied to a new world, so instead we move the portalling later in the tick cycle + if (!(this instanceof EntityPlayer)) { + this.doPortalTick(); + } + } + // CraftBukkit end + public void entityBaseTick() { this.world.getMethodProfiler().enter("entityBaseTick"); if (this.isPassenger() && this.getVehicle().dead) { @@ -250,7 +344,7 @@ this.lastZ = this.locZ; this.lastPitch = this.pitch; this.lastYaw = this.yaw; - this.doPortalTick(); + if (this instanceof EntityPlayer) this.doPortalTick(); // CraftBukkit - // Moved up to postTick this.az(); this.m(); if (this.world.isClientSide) { @@ -300,12 +394,44 @@ protected void burnFromLava() { if (!this.isFireProof()) { - this.setOnFire(15); + // CraftBukkit start - Fallen in lava TODO: this event spams! + if (this instanceof EntityLiving && fireTicks <= 0) { + // not on fire yet + // TODO: shouldn't be sending null for the block + org.bukkit.block.Block damager = null; // ((WorldServer) this.l).getWorld().getBlockAt(i, j, k); + org.bukkit.entity.Entity damagee = this.getBukkitEntity(); + EntityCombustEvent combustEvent = new org.bukkit.event.entity.EntityCombustByBlockEvent(damager, damagee, 15); + this.world.getServer().getPluginManager().callEvent(combustEvent); + + if (!combustEvent.isCancelled()) { + this.setOnFire(combustEvent.getDuration(), false); + } + } else { + // This will be called every single tick the entity is in lava, so don't throw an event + this.setOnFire(15, false); + } + // CraftBukkit end - we also don't throw an event unless the object in lava is living, to save on some event calls this.damageEntity(DamageSource.LAVA, 4.0F); } } public void setOnFire(int i) { + // CraftBukkit start + this.setOnFire(i, true); + } + + public void setOnFire(int i, boolean callEvent) { + if (callEvent) { + EntityCombustEvent event = new EntityCombustEvent(this.getBukkitEntity(), i); + this.world.getServer().getPluginManager().callEvent(event); + + if (event.isCancelled()) { + return; + } + + i = event.getDuration(); + } + // CraftBukkit end int j = i * 20; if (this instanceof EntityLiving) { @@ -401,6 +527,28 @@ block1.a((IBlockAccess) this.world, this); } + // CraftBukkit start + if (positionChanged && getBukkitEntity() instanceof Vehicle) { + Vehicle vehicle = (Vehicle) this.getBukkitEntity(); + org.bukkit.block.Block bl = this.world.getWorld().getBlockAt(MathHelper.floor(this.locX), MathHelper.floor(this.locY), MathHelper.floor(this.locZ)); + + if (vec3d1.x > vec3d.x) { + bl = bl.getRelative(BlockFace.EAST); + } else if (vec3d.x < vec3d.x) { + bl = bl.getRelative(BlockFace.WEST); + } else if (vec3d1.z > vec3d.z) { + bl = bl.getRelative(BlockFace.SOUTH); + } else if (vec3d1.z < vec3d.z) { + bl = bl.getRelative(BlockFace.NORTH); + } + + if (bl.getType() != org.bukkit.Material.AIR) { + VehicleBlockCollisionEvent event = new VehicleBlockCollisionEvent(vehicle, bl); + world.getServer().getPluginManager().callEvent(event); + } + } + // CraftBukkit end + if (this.playStepSound() && (!this.onGround || !this.isSneaking() || !(this instanceof EntityHuman)) && !this.isPassenger()) { double d0 = vec3d1.x; double d1 = vec3d1.y; @@ -454,7 +602,14 @@ if (!flag) { ++this.fireTicks; if (this.fireTicks == 0) { - this.setOnFire(8); + // CraftBukkit start + EntityCombustEvent event = new org.bukkit.event.entity.EntityCombustByBlockEvent(null, getBukkitEntity(), 8); + world.getServer().getPluginManager().callEvent(event); + + if (!event.isCancelled()) { + this.setOnFire(event.getDuration(), false); + } + // CraftBukkit end } } @@ -565,7 +720,7 @@ VoxelShape voxelshape = this.world.getWorldBorder().a(); Stream stream = VoxelShapes.c(voxelshape, VoxelShapes.a(axisalignedbb.shrink(1.0E-7D)), OperatorBoolean.AND) ? Stream.empty() : Stream.of(voxelshape); AxisAlignedBB axisalignedbb1 = axisalignedbb.a(vec3d).g(1.0E-7D); - Stream stream1 = this.world.getEntities(this, axisalignedbb1).stream().filter((entity) -> { + Stream stream1 = this.world.getEntities(this, axisalignedbb1).stream().filter((entity) -> { // CraftBukkit - decompile error return !this.x(entity); }).flatMap((entity) -> { return Stream.of(entity.ap(), this.j(entity)); @@ -649,6 +804,7 @@ this.locX = (axisalignedbb.minX + axisalignedbb.maxX) / 2.0D; this.locY = axisalignedbb.minY; this.locZ = (axisalignedbb.minZ + axisalignedbb.maxZ) / 2.0D; + if (valid) ((WorldServer) world).chunkCheck(this); // CraftBukkit } protected SoundEffect getSoundSwim() { @@ -820,7 +976,7 @@ return null; } - protected void burn(int i) { + protected void burn(float i) { // CraftBukkit - int -> float if (!this.isFireProof()) { this.damageEntity(DamageSource.FIRE, (float) i); } @@ -1053,6 +1209,13 @@ } public void spawnIn(World world) { + // CraftBukkit start + if (world == null) { + die(); + this.world = ((CraftWorld) Bukkit.getServer().getWorlds().get(0)).getHandle(); + return; + } + // CraftBukkit end this.world = world; } @@ -1078,6 +1241,7 @@ this.lastYaw -= 360.0F; } + world.getChunkAt((int) Math.floor(this.locX) >> 4, (int) Math.floor(this.locZ) >> 4); // CraftBukkit this.setPosition(this.locX, this.locY, this.locZ); this.setYawPitch(f, f1); } @@ -1246,7 +1410,7 @@ public boolean c(NBTTagCompound nbttagcompound) { String s = this.getSaveID(); - if (!this.dead && s != null) { + if (this.persist && !this.dead && s != null) { // CraftBukkit - persist flag nbttagcompound.setString("id", s); this.save(nbttagcompound); return true; @@ -1265,6 +1429,18 @@ Vec3D vec3d = this.getMot(); nbttagcompound.set("Motion", this.a(vec3d.x, vec3d.y, vec3d.z)); + + // CraftBukkit start - Checking for NaN pitch/yaw and resetting to zero + // TODO: make sure this is the best way to address this. + if (Float.isNaN(this.yaw)) { + this.yaw = 0; + } + + if (Float.isNaN(this.pitch)) { + this.pitch = 0; + } + // CraftBukkit end + nbttagcompound.set("Rotation", this.a(this.yaw, this.pitch)); nbttagcompound.setFloat("FallDistance", this.fallDistance); nbttagcompound.setShort("Fire", (short) this.fireTicks); @@ -1274,6 +1450,12 @@ nbttagcompound.setBoolean("Invulnerable", this.invulnerable); nbttagcompound.setInt("PortalCooldown", this.portalCooldown); nbttagcompound.a("UUID", this.getUniqueID()); + // CraftBukkit start + // PAIL: Check above UUID reads 1.8 properly, ie: UUIDMost / UUIDLeast + nbttagcompound.setLong("WorldUUIDLeast", ((WorldServer) this.world).getDataManager().getUUID().getLeastSignificantBits()); + nbttagcompound.setLong("WorldUUIDMost", ((WorldServer) this.world).getDataManager().getUUID().getMostSignificantBits()); + nbttagcompound.setInt("Bukkit.updateLevel", CURRENT_LEVEL); + // CraftBukkit end IChatBaseComponent ichatbasecomponent = this.getCustomName(); if (ichatbasecomponent != null) { @@ -1331,6 +1513,11 @@ } } + // CraftBukkit start - stores eventually existing bukkit values + if (this.bukkitEntity != null) { + this.bukkitEntity.storeBukkitValues(nbttagcompound); + } + // CraftBukkit end return nbttagcompound; } catch (Throwable throwable) { CrashReport crashreport = CrashReport.a(throwable, "Saving entity NBT"); @@ -1414,6 +1601,42 @@ } else { throw new IllegalStateException("Entity has invalid position"); } + + // CraftBukkit start + if (this instanceof EntityLiving) { + EntityLiving entity = (EntityLiving) this; + + // Reset the persistence for tamed animals + if (entity instanceof EntityTameableAnimal && !isLevelAtLeast(nbttagcompound, 2) && !nbttagcompound.getBoolean("PersistenceRequired")) { + EntityInsentient entityinsentient = (EntityInsentient) entity; + entityinsentient.persistent = !entityinsentient.isTypeNotPersistent(0); + } + } + // CraftBukkit end + + // CraftBukkit start - Reset world + if (this instanceof EntityPlayer) { + Server server = Bukkit.getServer(); + org.bukkit.World bworld = null; + + // TODO: Remove World related checks, replaced with WorldUID + String worldName = nbttagcompound.getString("world"); + + if (nbttagcompound.hasKey("WorldUUIDMost") && nbttagcompound.hasKey("WorldUUIDLeast")) { + UUID uid = new UUID(nbttagcompound.getLong("WorldUUIDMost"), nbttagcompound.getLong("WorldUUIDLeast")); + bworld = server.getWorld(uid); + } else { + bworld = server.getWorld(worldName); + } + + if (bworld == null) { + bworld = ((org.bukkit.craftbukkit.CraftServer) server).getServer().getWorldServer(DimensionManager.OVERWORLD).getWorld(); + } + + spawnIn(bworld == null? null : ((CraftWorld) bworld).getHandle()); + } + // CraftBukkit end + } catch (Throwable throwable) { CrashReport crashreport = CrashReport.a(throwable, "Loading entity NBT"); CrashReportSystemDetails crashreportsystemdetails = crashreport.a("Entity being loaded"); @@ -1489,9 +1712,22 @@ } else if (this.world.isClientSide) { return null; } else { + // CraftBukkit start - Capture drops for death event + if (this instanceof EntityLiving && !((EntityLiving) this).forceDrops) { + ((EntityLiving) this).drops.add(org.bukkit.craftbukkit.inventory.CraftItemStack.asBukkitCopy(itemstack)); + return null; + } + // CraftBukkit end EntityItem entityitem = new EntityItem(this.world, this.locX, this.locY + (double) f, this.locZ, itemstack); entityitem.defaultPickupDelay(); + // CraftBukkit start + EntityDropItemEvent event = new EntityDropItemEvent(this.getBukkitEntity(), (org.bukkit.entity.Item) entityitem.getBukkitEntity()); + Bukkit.getPluginManager().callEvent(event); + if (event.isCancelled()) { + return null; + } + // CraftBukkit end this.world.addEntity(entityitem); return entityitem; } @@ -1595,7 +1831,7 @@ } this.vehicle = entity; - this.vehicle.addPassenger(this); + if (!this.vehicle.addPassenger(this)) this.vehicle = null; // CraftBukkit return true; } } @@ -1620,15 +1856,36 @@ Entity entity = this.vehicle; this.vehicle = null; - entity.removePassenger(this); + if (!entity.removePassenger(this)) this.vehicle = entity; // CraftBukkit } } - protected void addPassenger(Entity entity) { + protected boolean addPassenger(Entity entity) { // CraftBukkit if (entity.getVehicle() != this) { throw new IllegalStateException("Use x.startRiding(y), not y.addPassenger(x)"); } else { + // CraftBukkit start + com.google.common.base.Preconditions.checkState(!entity.passengers.contains(this), "Circular entity riding! %s %s", this, entity); + + CraftEntity craft = (CraftEntity) entity.getBukkitEntity().getVehicle(); + Entity orig = craft == null ? null : craft.getHandle(); + if (getBukkitEntity() instanceof Vehicle && entity.getBukkitEntity() instanceof LivingEntity) { + VehicleEnterEvent event = new VehicleEnterEvent( + (Vehicle) getBukkitEntity(), + entity.getBukkitEntity() + ); + // Suppress during worldgen + if (this.valid) { + Bukkit.getPluginManager().callEvent(event); + } + CraftEntity craftn = (CraftEntity) entity.getBukkitEntity().getVehicle(); + Entity n = craftn == null ? null : craftn.getHandle(); + if (event.isCancelled() || n != orig) { + return false; + } + } + // CraftBukkit end if (!this.world.isClientSide && entity instanceof EntityHuman && !(this.getRidingPassenger() instanceof EntityHuman)) { this.passengers.add(0, entity); } else { @@ -1636,15 +1893,33 @@ } } + return true; // CraftBukkit } - protected void removePassenger(Entity entity) { + protected boolean removePassenger(Entity entity) { // CraftBukkit if (entity.getVehicle() == this) { throw new IllegalStateException("Use x.stopRiding(y), not y.removePassenger(x)"); } else { + // CraftBukkit start + CraftEntity craft = (CraftEntity) entity.getBukkitEntity().getVehicle(); + Entity orig = craft == null ? null : craft.getHandle(); + if (getBukkitEntity() instanceof Vehicle && entity.getBukkitEntity() instanceof LivingEntity) { + VehicleExitEvent event = new VehicleExitEvent( + (Vehicle) getBukkitEntity(), + (LivingEntity) entity.getBukkitEntity() + ); + Bukkit.getPluginManager().callEvent(event); + CraftEntity craftn = (CraftEntity) entity.getBukkitEntity().getVehicle(); + Entity n = craftn == null ? null : craftn.getHandle(); + if (event.isCancelled() || n != orig) { + return false; + } + } + // CraftBukkit end this.passengers.remove(entity); entity.j = 60; } + return true; // CraftBukkit } protected boolean q(Entity entity) { @@ -1687,11 +1962,17 @@ int i = this.ab(); if (this.ai) { - if (this.world.getMinecraftServer().getAllowNether() && !this.isPassenger() && this.aj++ >= i) { + if ((true || this.world.getMinecraftServer().getAllowNether()) && !this.isPassenger() && this.aj++ >= i) { // CraftBukkit this.world.getMethodProfiler().enter("portal"); this.aj = i; this.portalCooldown = this.aW(); - this.a(this.world.worldProvider.getDimensionManager() == DimensionManager.NETHER ? DimensionManager.OVERWORLD : DimensionManager.NETHER); + // CraftBukkit start + if (this instanceof EntityPlayer) { + ((EntityPlayer) this).a(this.world.worldProvider.getDimensionManager() == DimensionManager.NETHER ? DimensionManager.OVERWORLD : DimensionManager.NETHER, PlayerTeleportEvent.TeleportCause.NETHER_PORTAL); + } else { + this.a(this.world.worldProvider.getDimensionManager() == DimensionManager.NETHER ? DimensionManager.OVERWORLD : DimensionManager.NETHER); + } + // CraftBukkit end this.world.getMethodProfiler().exit(); } @@ -1771,6 +2052,13 @@ } public void setSwimming(boolean flag) { + // CraftBukkit start + if (this.isSwimming() != flag && this instanceof EntityLiving) { + if (CraftEventFactory.callToggleSwimEvent((EntityLiving) this, flag).isCancelled()) { + return; + } + } + // CraftBukkit end this.setFlag(4, flag); } @@ -1831,16 +2119,56 @@ } public void setAirTicks(int i) { - this.datawatcher.set(Entity.AIR_TICKS, i); + // CraftBukkit start + EntityAirChangeEvent event = new EntityAirChangeEvent(this.getBukkitEntity(), i); + // Suppress during worldgen + if (this.valid) { + event.getEntity().getServer().getPluginManager().callEvent(event); + } + if (event.isCancelled()) { + return; + } + this.datawatcher.set(Entity.AIR_TICKS, event.getAmount()); + // CraftBukkit end } public void onLightningStrike(EntityLightning entitylightning) { ++this.fireTicks; + // CraftBukkit start + final org.bukkit.entity.Entity thisBukkitEntity = this.getBukkitEntity(); + final org.bukkit.entity.Entity stormBukkitEntity = entitylightning.getBukkitEntity(); + final PluginManager pluginManager = Bukkit.getPluginManager(); + // CraftBukkit end + if (this.fireTicks == 0) { - this.setOnFire(8); + // CraftBukkit start - Call a combust event when lightning strikes + EntityCombustByEntityEvent entityCombustEvent = new EntityCombustByEntityEvent(stormBukkitEntity, thisBukkitEntity, 8); + pluginManager.callEvent(entityCombustEvent); + if (!entityCombustEvent.isCancelled()) { + this.setOnFire(entityCombustEvent.getDuration(), false); + } + // CraftBukkit end + } + + // CraftBukkit start + if (thisBukkitEntity instanceof Hanging) { + HangingBreakByEntityEvent hangingEvent = new HangingBreakByEntityEvent((Hanging) thisBukkitEntity, stormBukkitEntity); + pluginManager.callEvent(hangingEvent); + + if (hangingEvent.isCancelled()) { + return; + } } - this.damageEntity(DamageSource.LIGHTNING, 5.0F); + if (this.isFireProof()) { + return; + } + CraftEventFactory.entityDamage = entitylightning; + if (!this.damageEntity(DamageSource.LIGHTNING, 5.0F)) { + CraftEventFactory.entityDamage = null; + return; + } + // CraftBukkit end } public void j(boolean flag) { @@ -1988,20 +2316,33 @@ @Nullable public Entity a(DimensionManager dimensionmanager) { + // CraftBukkit start + return teleportTo(dimensionmanager, null); + } + + @Nullable + public Entity teleportTo(DimensionManager dimensionmanager, BlockPosition location) { + // CraftBukkit end if (!this.world.isClientSide && !this.dead) { this.world.getMethodProfiler().enter("changeDimension"); MinecraftServer minecraftserver = this.getMinecraftServer(); DimensionManager dimensionmanager1 = this.dimension; WorldServer worldserver = minecraftserver.getWorldServer(dimensionmanager1); WorldServer worldserver1 = minecraftserver.getWorldServer(dimensionmanager); + // CraftBukkit start + if (worldserver1 == null){ + return null; + } - this.dimension = dimensionmanager; - this.decouple(); + // this.dimension = dimensionmanager; + // this.decouple(); + // CraftBukkit end this.world.getMethodProfiler().enter("reposition"); Vec3D vec3d = this.getMot(); float f = 0.0F; - BlockPosition blockposition; + BlockPosition blockposition = location; // CraftBukkit + if (blockposition == null) { // CraftBukkit if (dimensionmanager1 == DimensionManager.THE_END && dimensionmanager == DimensionManager.OVERWORLD) { blockposition = worldserver1.getHighestBlockYAt(HeightMap.Type.MOTION_BLOCKING_NO_LEAVES, worldserver1.getSpawn()); } else if (dimensionmanager == DimensionManager.THE_END) { @@ -2039,6 +2380,25 @@ vec3d = shapedetector_c.b; f = (float) shapedetector_c.c; } + } // CraftBukkit + + // CraftBukkit start + Location enter = this.getBukkitEntity().getLocation(); + Location exit = new Location(worldserver1.getWorld(), blockposition.getX(), blockposition.getY(), blockposition.getZ()); + + EntityPortalEvent event = new EntityPortalEvent(this.getBukkitEntity(), enter, exit); + event.getEntity().getServer().getPluginManager().callEvent(event); + if (event.isCancelled() || event.getTo() == null || event.getTo().getWorld() == null || !this.isAlive()) { + return null; + } + + exit = event.getTo(); + worldserver1 = ((CraftWorld) exit.getWorld()).getHandle(); + blockposition = new BlockPosition(exit.getX(), exit.getY(), exit.getZ()); + + this.dimension = dimensionmanager; + this.decouple(); + // CraftBukkit end this.world.getMethodProfiler().exitEnter("reloading"); Entity entity = this.getEntityType().a((World) worldserver1); @@ -2048,6 +2408,14 @@ entity.setPositionRotation(blockposition, entity.yaw + f, entity.pitch); entity.setMot(vec3d); worldserver1.addEntityTeleport(entity); + // CraftBukkit start - Forward the CraftEntity to the new entity + this.getBukkitEntity().setHandle(entity); + entity.bukkitEntity = this.getBukkitEntity(); + + if (this instanceof EntityInsentient) { + ((EntityInsentient)this).unleash(true, false); // Unleash to prevent duping of leads. + } + // CraftBukkit end } this.dead = true; @@ -2239,7 +2607,26 @@ } public void a(AxisAlignedBB axisalignedbb) { - this.boundingBox = axisalignedbb; + // CraftBukkit start - block invalid bounding boxes + double minX = axisalignedbb.minX, + minY = axisalignedbb.minY, + minZ = axisalignedbb.minZ, + maxX = axisalignedbb.maxX, + maxY = axisalignedbb.maxY, + maxZ = axisalignedbb.maxZ; + double len = axisalignedbb.maxX - axisalignedbb.minX; + if (len < 0) maxX = minX; + if (len > 64) maxX = minX + 64.0; + + len = axisalignedbb.maxY - axisalignedbb.minY; + if (len < 0) maxY = minY; + if (len > 64) maxY = minY + 64.0; + + len = axisalignedbb.maxZ - axisalignedbb.minZ; + if (len < 0) maxZ = minZ; + if (len > 64) maxZ = minZ + 64.0; + this.boundingBox = new AxisAlignedBB(minX, minY, minZ, maxX, maxY, maxZ); + // CraftBukkit end } protected float getHeadHeight(EntityPose entitypose, EntitySize entitysize) {