--- a/net/minecraft/server/Entity.java +++ b/net/minecraft/server/Entity.java @@ -16,8 +16,48 @@ 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.TravelAgent; +import org.bukkit.block.BlockFace; +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.EntityPortalEvent; +import org.bukkit.plugin.PluginManager; +// CraftBukkit end + public abstract class Entity implements 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; + } + // CraftBukikt end + private static final Logger a = LogManager.getLogger(); private static final List b = Collections.emptyList(); private static final AxisAlignedBB c = new AxisAlignedBB(0.0D, 0.0D, 0.0D, 0.0D, 0.0D, 0.0D); @@ -101,6 +141,9 @@ private boolean aH; private double[] aI; private long aJ; + public boolean valid; // CraftBukkit + public org.bukkit.projectiles.ProjectileSource projectileSource; // CraftBukkit - For projectiles only + public boolean forceExplosionKnockback; // CraftBukkit - SPIGOT-949 public Entity(World world) { this.id = Entity.entityCount++; @@ -206,6 +249,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("Nope"); + } + 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("Nope"); + } + f1 = 0; + } + // CraftBukkit end + this.yaw = f % 360.0F; this.pitch = f1 % 360.0F; } @@ -249,7 +319,7 @@ if (this.ak) { MinecraftServer minecraftserver = this.world.getMinecraftServer(); - if (minecraftserver.getAllowNether()) { + if (true || minecraftserver.getAllowNether()) { // CraftBukkit if (!this.isPassenger()) { int i = this.V(); @@ -334,6 +404,27 @@ protected void burnFromLava() { if (!this.fireProof) { this.damageEntity(DamageSource.LAVA, 4.0F); + + // CraftBukkit start - Fallen in lava TODO: this event spams! + if (this instanceof EntityLiving) { + if (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()); + } + } else { + // This will be called every single tick the entity is in lava, so don't throw an event + this.setOnFire(15); + } + return; + } + // CraftBukkit end - we also don't throw an event unless the object in lava is living, to save on some event calls this.setOnFire(15); } } @@ -374,6 +465,22 @@ this.a(this.getBoundingBox().d(d0, d1, d2)); this.recalcPosition(); } else { + // CraftBukkit start - Don't do anything if we aren't moving + // We need to do this regardless of whether or not we are moving thanks to portals + try { + this.checkBlockCollisions(); + } catch (Throwable throwable) { + CrashReport crashreport = CrashReport.a(throwable, "Checking entity block collision"); + CrashReportSystemDetails crashreportsystemdetails = crashreport.a("Entity being checked for collision"); + + this.appendEntityCrashDetails(crashreportsystemdetails); + throw new ReportedException(crashreport); + } + // Check if we're moving + if (d0 == 0 && d1 == 0 && d2 == 0 && this.isVehicle() && this.isPassenger()) { + return; + } + // CraftBukkit end if (enummovetype == EnumMoveType.PISTON) { long i = this.world.getTime(); @@ -515,7 +622,7 @@ } } - boolean flag = this.onGround || d1 != d1 && d1 < 0.0D; + boolean flag = this.onGround || d1 != d8 && d1 < 0.0D; // CraftBukkit - decompile error double d11; if (this.P > 0.0F && flag && (d7 != d0 || d9 != d2)) { @@ -613,7 +720,7 @@ this.world.methodProfiler.a("rest"); this.recalcPosition(); this.positionChanged = d7 != d0 || d9 != d2; - this.B = d1 != d1; + this.B = d1 != d8; // CraftBukkit - decompile error this.onGround = this.B && d8 < 0.0D; this.C = this.positionChanged || this.B; l = MathHelper.floor(this.locX); @@ -648,6 +755,28 @@ block1.a(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 (d6 > d0) { + bl = bl.getRelative(BlockFace.EAST); + } else if (d6 < d0) { + bl = bl.getRelative(BlockFace.WEST); + } else if (d8 > d2) { + bl = bl.getRelative(BlockFace.SOUTH); + } else if (d8 < d2) { + 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 d22 = this.locX - d4; double d23 = this.locY - d5; @@ -681,6 +810,8 @@ } } + // CraftBukkit start - Move to the top of the method + /* try { this.checkBlockCollisions(); } catch (Throwable throwable) { @@ -690,6 +821,8 @@ this.appendEntityCrashDetails(crashreportsystemdetails); throw new ReportedException(crashreport); } + */ + // CraftBukkit end boolean flag1 = this.ai(); @@ -698,7 +831,14 @@ if (!flag1) { ++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()); + } + // CraftBukkit end } } } else if (this.fireTicks <= 0) { @@ -819,7 +959,7 @@ return null; } - protected void burn(int i) { + protected void burn(float i) { // CraftBukkit - int -> float if (!this.fireProof) { this.damageEntity(DamageSource.FIRE, (float) i); } @@ -987,6 +1127,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; } @@ -1219,6 +1366,18 @@ try { nbttagcompound.set("Pos", this.a(new double[] { this.locX, this.locY, this.locZ})); nbttagcompound.set("Motion", this.a(new double[] { this.motX, this.motY, this.motZ})); + + // 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(new float[] { this.yaw, this.pitch})); nbttagcompound.setFloat("FallDistance", this.fallDistance); nbttagcompound.setShort("Fire", (short) this.fireTicks); @@ -1228,6 +1387,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", this.world.getDataManager().getUUID().getLeastSignificantBits()); + nbttagcompound.setLong("WorldUUIDMost", this.world.getDataManager().getUUID().getMostSignificantBits()); + nbttagcompound.setInt("Bukkit.updateLevel", CURRENT_LEVEL); + // CraftBukkit end if (this.hasCustomName()) { nbttagcompound.setString("CustomName", this.getCustomName()); } @@ -1303,6 +1468,8 @@ this.motX = nbttaglist1.e(0); this.motY = nbttaglist1.e(1); this.motZ = nbttaglist1.e(2); + + /* CraftBukkit start - Moved section down if (Math.abs(this.motX) > 10.0D) { this.motX = 0.0D; } @@ -1314,6 +1481,7 @@ if (Math.abs(this.motZ) > 10.0D) { this.motZ = 0.0D; } + // CraftBukkit end */ this.locX = nbttaglist.e(0); this.locY = nbttaglist.e(1); @@ -1371,6 +1539,58 @@ this.setPosition(this.locX, this.locY, this.locZ); } + // 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(); + } + } + // CraftBukkit end + + // CraftBukkit start - Exempt Vehicles from notch's sanity check + if (!(getBukkitEntity() instanceof Vehicle)) { + if (Math.abs(this.motX) > 10.0D) { + this.motX = 0.0D; + } + + if (Math.abs(this.motY) > 10.0D) { + this.motY = 0.0D; + } + + if (Math.abs(this.motZ) > 10.0D) { + this.motZ = 0.0D; + } + } + // 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) { + EntityPlayer entityPlayer = (EntityPlayer) this; + bworld = ((org.bukkit.craftbukkit.CraftServer) server).getServer().getWorldServer(entityPlayer.dimension).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"); @@ -1438,6 +1658,12 @@ if (itemstack.isEmpty()) { 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.q(); @@ -1563,6 +1789,24 @@ if (entity.bB() != 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 && entity.world.isChunkLoaded((int) entity.locX >> 4, (int) entity.locZ >> 4, false)) { // Boolean not used + VehicleEnterEvent event = new VehicleEnterEvent( + (Vehicle) getBukkitEntity(), + 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; + } + } + // CraftBukkit end if (!this.world.isClientSide && entity instanceof EntityHuman && !(this.bw() instanceof EntityHuman)) { this.passengers.add(0, entity); } else { @@ -1576,6 +1820,22 @@ if (entity.bB() == 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; + } + } + // CraftBukkit end this.passengers.remove(entity); entity.j = 60; } @@ -1715,14 +1975,48 @@ } public void setAirTicks(int i) { - this.datawatcher.set(Entity.az, Integer.valueOf(i)); + // CraftBukkit start + EntityAirChangeEvent event = new EntityAirChangeEvent(this.getBukkitEntity(), i); + if (event.isCancelled()) { + return; + } + this.datawatcher.set(Entity.az, Integer.valueOf(event.getAmount())); + // CraftBukkit end } public void onLightningStrike(EntityLightning entitylightning) { - this.damageEntity(DamageSource.LIGHTNING, 5.0F); + // CraftBukkit start + final org.bukkit.entity.Entity thisBukkitEntity = this.getBukkitEntity(); + final org.bukkit.entity.Entity stormBukkitEntity = entitylightning.getBukkitEntity(); + final PluginManager pluginManager = Bukkit.getPluginManager(); + + if (thisBukkitEntity instanceof Hanging) { + HangingBreakByEntityEvent hangingEvent = new HangingBreakByEntityEvent((Hanging) thisBukkitEntity, stormBukkitEntity); + pluginManager.callEvent(hangingEvent); + + if (hangingEvent.isCancelled()) { + return; + } + } + + if (this.fireProof) { + return; + } + CraftEventFactory.entityDamage = entitylightning; + if (!this.damageEntity(DamageSource.LIGHTNING, 5.0F)) { + CraftEventFactory.entityDamage = null; + return; + } + // CraftBukkit end ++this.fireTicks; 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()); + } + // CraftBukkit end } } @@ -1867,19 +2161,70 @@ if (!this.world.isClientSide && !this.dead) { this.world.methodProfiler.a("changeDimension"); MinecraftServer minecraftserver = this.B_(); - int j = this.dimension; - WorldServer worldserver = minecraftserver.getWorldServer(j); - WorldServer worldserver1 = minecraftserver.getWorldServer(i); + // CraftBukkit start - Move logic into new function "teleportTo(Location,boolean)" + // int j = this.dimension; + // WorldServer worldserver = minecraftserver.getWorldServer(j); + // WorldServer worldserver1 = minecraftserver.getWorldServer(i); + WorldServer exitWorld = null; + if (this.dimension < CraftWorld.CUSTOM_DIMENSION_OFFSET) { // Plugins must specify exit from custom Bukkit worlds + // Only target existing worlds (compensate for allow-nether/allow-end as false) + for (WorldServer world : minecraftserver.worlds) { + if (world.dimension == i) { + exitWorld = world; + } + } + } + + BlockPosition blockposition = null; // PAIL: CHECK + Location enter = this.getBukkitEntity().getLocation(); + Location exit; + if (exitWorld != null) { + if (blockposition != null) { + exit = new Location(exitWorld.getWorld(), blockposition.getX(), blockposition.getY(), blockposition.getZ()); + } else { + exit = minecraftserver.getPlayerList().calculateTarget(enter, minecraftserver.getWorldServer(i)); + } + } + else { + exit = null; + } + boolean useTravelAgent = exitWorld != null && !(this.dimension == 1 && exitWorld.dimension == 1); // don't use agent for custom worlds or return from THE_END + + TravelAgent agent = exit != null ? (TravelAgent) ((CraftWorld) exit.getWorld()).getHandle().getTravelAgent() : org.bukkit.craftbukkit.CraftTravelAgent.DEFAULT; // return arbitrary TA to compensate for implementation dependent plugins + EntityPortalEvent event = new EntityPortalEvent(this.getBukkitEntity(), enter, exit, agent); + event.useTravelAgent(useTravelAgent); + event.getEntity().getServer().getPluginManager().callEvent(event); + if (event.isCancelled() || event.getTo() == null || event.getTo().getWorld() == null || !this.isAlive()) { + return null; + } + exit = event.useTravelAgent() ? event.getPortalTravelAgent().findOrCreate(event.getTo()) : event.getTo(); + // Need to make sure the profiler state is reset afterwards (but we still want to time the call) + Entity entity = this.teleportTo(exit, true); + this.world.methodProfiler.b(); + return entity; + } + return null; + } + + public Entity teleportTo(Location exit, boolean portal) { + if (true) { + WorldServer worldserver = ((CraftWorld) getBukkitEntity().getLocation().getWorld()).getHandle(); + WorldServer worldserver1 = ((CraftWorld) exit.getWorld()).getHandle(); + int i = worldserver1.dimension; + // CraftBukkit end this.dimension = i; + /* CraftBukkit start - TODO: Check if we need this if (j == 1 && i == 1) { worldserver1 = minecraftserver.getWorldServer(0); this.dimension = 0; } + // CraftBukkit end */ this.world.kill(this); this.dead = false; this.world.methodProfiler.a("reposition"); + /* CraftBukkit start - Handled in calculateTarget BlockPosition blockposition; if (i == 1) { @@ -1908,12 +2253,18 @@ blockposition = new BlockPosition(this); } - worldserver.entityJoinedWorld(this, false); + // CraftBukkit end */ + // CraftBukkit start - Ensure chunks are loaded in case TravelAgent is not used which would initially cause chunks to load during find/create + // minecraftserver.getPlayerList().changeWorld(this, j, worldserver, worldserver1); + worldserver1.getMinecraftServer().getPlayerList().repositionEntity(this, exit, portal); + // worldserver.entityJoinedWorld(this, false); // Handled in repositionEntity + // CraftBukkit end this.world.methodProfiler.c("reloading"); Entity entity = EntityTypes.a(this.getClass(), (World) worldserver1); if (entity != null) { entity.a(this); + /* CraftBukkit start - We need to do this... if (j == 1 && i == 1) { BlockPosition blockposition1 = worldserver1.q(worldserver1.getSpawn()); @@ -1921,6 +2272,7 @@ } else { entity.setPositionRotation(blockposition, entity.yaw, entity.pitch); } + // CraftBukkit end */ boolean flag = entity.attachedToPlayer; @@ -1928,13 +2280,21 @@ worldserver1.addEntity(entity); entity.attachedToPlayer = flag; worldserver1.entityJoinedWorld(entity, false); + // 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; this.world.methodProfiler.b(); worldserver.m(); worldserver1.m(); - this.world.methodProfiler.b(); + // this.world.methodProfiler.b(); // CraftBukkit: Moved up to keep balanced return entity; } else { return null; @@ -2038,6 +2398,11 @@ } public void setCustomName(String s) { + // CraftBukkit start - Add a sane limit for name length + if (s.length() > 256) { + s = s.substring(0, 256); + } + // CraftBukkit end this.datawatcher.set(Entity.aA, s); } @@ -2095,7 +2460,26 @@ } public void a(AxisAlignedBB axisalignedbb) { - this.boundingBox = axisalignedbb; + // CraftBukkit start - block invalid bounding boxes + double a = axisalignedbb.a, + b = axisalignedbb.b, + c = axisalignedbb.c, + d = axisalignedbb.d, + e = axisalignedbb.e, + f = axisalignedbb.f; + double len = axisalignedbb.d - axisalignedbb.a; + if (len < 0) d = a; + if (len > 64) d = a + 64.0; + + len = axisalignedbb.e - axisalignedbb.b; + if (len < 0) e = b; + if (len > 64) e = b + 64.0; + + len = axisalignedbb.f - axisalignedbb.c; + if (len < 0) f = c; + if (len > 64) f = c + 64.0; + this.boundingBox = new AxisAlignedBB(a, b, c, d, e, f); + // CraftBukkit end } public float getHeadHeight() { @@ -2269,7 +2653,7 @@ for (Iterator iterator = this.bx().iterator(); iterator.hasNext(); entity.a(oclass, set)) { entity = (Entity) iterator.next(); if (oclass.isAssignableFrom(entity.getClass())) { - set.add(entity); + set.add((T) entity); // CraftBukkit - decompile error } }