--- a/net/minecraft/server/World.java +++ b/net/minecraft/server/World.java @@ -21,6 +21,23 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +// CraftBukkit start +import com.google.common.collect.Maps; +import java.util.Map; +import org.bukkit.Bukkit; +import org.bukkit.block.BlockState; +import org.bukkit.craftbukkit.CraftServer; +import org.bukkit.craftbukkit.CraftWorld; +import org.bukkit.craftbukkit.block.CraftBlockState; +import org.bukkit.craftbukkit.block.data.CraftBlockData; +import org.bukkit.craftbukkit.event.CraftEventFactory; +import org.bukkit.craftbukkit.util.CraftMagicNumbers; +import org.bukkit.event.block.BlockCanBuildEvent; +import org.bukkit.event.block.BlockPhysicsEvent; +import org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason; +import org.bukkit.generator.ChunkGenerator; +// CraftBukkit end + public abstract class World implements IEntityAccess, GeneratorAccess, IIBlockAccess, AutoCloseable { protected static final Logger e = LogManager.getLogger(); @@ -62,7 +79,52 @@ private final WorldBorder K; int[] E; - protected World(IDataManager idatamanager, @Nullable PersistentCollection persistentcollection, WorldData worlddata, WorldProvider worldprovider, MethodProfiler methodprofiler, boolean flag) { + // CraftBukkit start Added the following + private final CraftWorld world; + public boolean pvpMode; + public boolean keepSpawnInMemory = true; + public ChunkGenerator generator; + + public boolean captureBlockStates = false; + public boolean captureTreeGeneration = false; + public ArrayList<CraftBlockState> capturedBlockStates = new ArrayList<CraftBlockState>() { + @Override + public boolean add(CraftBlockState blockState) { + Iterator<CraftBlockState> blockStateIterator = this.iterator(); + while (blockStateIterator.hasNext()) { + BlockState blockState1 = blockStateIterator.next(); + if (blockState1.getLocation().equals(blockState.getLocation())) { + return false; + } + } + + return super.add(blockState); + } + }; + public List<EntityItem> captureDrops; + public long ticksPerAnimalSpawns; + public long ticksPerMonsterSpawns; + public boolean populating; + private int tickPosition; + + public CraftWorld getWorld() { + return this.world; + } + + public CraftServer getServer() { + return (CraftServer) Bukkit.getServer(); + } + + public Chunk getChunkIfLoaded(int x, int z) { + return ((ChunkProviderServer) this.chunkProvider).getChunkAt(x, z, false, false); + } + + protected World(IDataManager idatamanager, @Nullable PersistentCollection persistentcollection, WorldData worlddata, WorldProvider worldprovider, MethodProfiler methodprofiler, boolean flag, ChunkGenerator gen, org.bukkit.World.Environment env) { + this.generator = gen; + this.world = new CraftWorld((WorldServer) this, gen, env); + this.ticksPerAnimalSpawns = this.getServer().getTicksPerAnimalSpawns(); // CraftBukkit + this.ticksPerMonsterSpawns = this.getServer().getTicksPerMonsterSpawns(); // CraftBukkit + // CraftBukkit end this.v = Lists.newArrayList(new IWorldAccess[] { this.u}); this.allowMonsters = true; this.allowAnimals = true; @@ -74,6 +136,36 @@ this.worldProvider = worldprovider; this.isClientSide = flag; this.K = worldprovider.getWorldBorder(); + // CraftBukkit start + getWorldBorder().world = (WorldServer) this; + // From PlayerList.setPlayerFileData + getWorldBorder().a(new IWorldBorderListener() { + public void a(WorldBorder worldborder, double d0) { + getServer().getHandle().sendAll(new PacketPlayOutWorldBorder(worldborder, PacketPlayOutWorldBorder.EnumWorldBorderAction.SET_SIZE), worldborder.world); + } + + public void a(WorldBorder worldborder, double d0, double d1, long i) { + getServer().getHandle().sendAll(new PacketPlayOutWorldBorder(worldborder, PacketPlayOutWorldBorder.EnumWorldBorderAction.LERP_SIZE), worldborder.world); + } + + public void a(WorldBorder worldborder, double d0, double d1) { + getServer().getHandle().sendAll(new PacketPlayOutWorldBorder(worldborder, PacketPlayOutWorldBorder.EnumWorldBorderAction.SET_CENTER), worldborder.world); + } + + public void a(WorldBorder worldborder, int i) { + getServer().getHandle().sendAll(new PacketPlayOutWorldBorder(worldborder, PacketPlayOutWorldBorder.EnumWorldBorderAction.SET_WARNING_TIME), worldborder.world); + } + + public void b(WorldBorder worldborder, int i) { + getServer().getHandle().sendAll(new PacketPlayOutWorldBorder(worldborder, PacketPlayOutWorldBorder.EnumWorldBorderAction.SET_WARNING_BLOCKS), worldborder.world); + } + + public void b(WorldBorder worldborder, double d0) {} + + public void c(WorldBorder worldborder, double d0) {} + }); + this.getServer().addWorld(this.world); + // CraftBukkit end } public BiomeBase getBiome(BlockPosition blockposition) { @@ -148,6 +240,26 @@ } public boolean setTypeAndData(BlockPosition blockposition, IBlockData iblockdata, int i) { + // CraftBukkit start - tree generation + if (this.captureTreeGeneration) { + CraftBlockState blockstate = null; + Iterator<CraftBlockState> it = capturedBlockStates.iterator(); + while (it.hasNext()) { + CraftBlockState previous = it.next(); + if (previous.getX() == blockposition.getX() && previous.getY() == blockposition.getY() && previous.getZ() == blockposition.getZ()) { + blockstate = previous; + it.remove(); + break; + } + } + if (blockstate == null) { + blockstate = org.bukkit.craftbukkit.block.CraftBlockState.getBlockState(this, blockposition, i); + } + blockstate.setData(iblockdata); + this.capturedBlockStates.add(blockstate); + return true; + } + // CraftBukkit end if (k(blockposition)) { return false; } else if (!this.isClientSide && this.worldData.getType() == WorldType.DEBUG_ALL_BLOCK_STATES) { @@ -155,9 +267,23 @@ } else { Chunk chunk = this.getChunkAtWorldCoords(blockposition); Block block = iblockdata.getBlock(); - IBlockData iblockdata1 = chunk.setType(blockposition, iblockdata, (i & 64) != 0); + + // CraftBukkit start - capture blockstates + CraftBlockState blockstate = null; + if (this.captureBlockStates) { + blockstate = org.bukkit.craftbukkit.block.CraftBlockState.getBlockState(this, blockposition, i); + this.capturedBlockStates.add(blockstate); + } + // CraftBukkit end + + IBlockData iblockdata1 = chunk.setType(blockposition, iblockdata, (i & 64) != 0, (i & 1024) == 0); // CraftBukkit custom NO_PLACE flag if (iblockdata1 == null) { + // CraftBukkit start - remove blockstate if failed + if (this.captureBlockStates) { + this.capturedBlockStates.remove(blockstate); + } + // CraftBukkit end return false; } else { IBlockData iblockdata2 = this.getType(blockposition); @@ -168,6 +294,7 @@ this.methodProfiler.e(); } + /* if (iblockdata2 == iblockdata) { if (iblockdata1 != iblockdata2) { this.a(blockposition, blockposition); @@ -192,12 +319,63 @@ iblockdata.b(this, blockposition, j); } } + */ + + // CraftBukkit start + if (!this.captureBlockStates) { // Don't notify clients or update physics while capturing blockstates + // Modularize client and physic updates + notifyAndUpdatePhysics(blockposition, chunk, iblockdata1, iblockdata, iblockdata2, i); + } + // CraftBukkit end return true; } } } + // CraftBukkit start - Split off from above in order to directly send client and physic updates + public void notifyAndUpdatePhysics(BlockPosition blockposition, Chunk chunk, IBlockData oldBlock, IBlockData newBlock, IBlockData actualBlock, int i) { + IBlockData iblockdata = newBlock; + IBlockData iblockdata1 = oldBlock; + IBlockData iblockdata2 = actualBlock; + if (iblockdata2 == iblockdata) { + if (iblockdata1 != iblockdata2) { + this.a(blockposition, blockposition); + } + + if ((i & 2) != 0 && (!this.isClientSide || (i & 4) == 0) && (chunk == null || chunk.isReady())) { // allow chunk to be null here as chunk.isReady() is false when we send our notification during block placement + this.notify(blockposition, iblockdata1, iblockdata, i); + } + + if (!this.isClientSide && (i & 1) != 0) { + this.update(blockposition, iblockdata1.getBlock()); + if (iblockdata.isComplexRedstone()) { + this.updateAdjacentComparators(blockposition, newBlock.getBlock()); + } + } + + if ((i & 16) == 0) { + int j = i & -2; + + // CraftBukkit start + iblockdata1.b(this, blockposition, j); // Don't call an event for the old block to limit event spam + CraftWorld world = ((WorldServer) this).getWorld(); + if (world != null) { + BlockPhysicsEvent event = new BlockPhysicsEvent(world.getBlockAt(blockposition.getX(), blockposition.getY(), blockposition.getZ()), CraftBlockData.fromData(iblockdata)); + this.getServer().getPluginManager().callEvent(event); + + if (event.isCancelled()) { + return; + } + } + // CraftBukkit end + iblockdata.a((GeneratorAccess) this, blockposition, j); + iblockdata.b(this, blockposition, j); + } + } + } + // CraftBukkit end + public boolean setAir(BlockPosition blockposition) { Fluid fluid = this.b(blockposition); @@ -234,6 +412,11 @@ public void update(BlockPosition blockposition, Block block) { if (this.worldData.getType() != WorldType.DEBUG_ALL_BLOCK_STATES) { + // CraftBukkit start + if (populating) { + return; + } + // CraftBukkit end this.applyPhysics(blockposition, block); } @@ -309,6 +492,17 @@ IBlockData iblockdata = this.getType(blockposition); try { + // CraftBukkit start + CraftWorld world = ((WorldServer) this).getWorld(); + if (world != null) { + BlockPhysicsEvent event = new BlockPhysicsEvent(world.getBlockAt(blockposition.getX(), blockposition.getY(), blockposition.getZ()), CraftBlockData.fromData(iblockdata)); + this.getServer().getPluginManager().callEvent(event); + + if (event.isCancelled()) { + return; + } + } + // CraftBukkit end iblockdata.doPhysics(this, blockposition, block, blockposition1); } catch (Throwable throwable) { CrashReport crashreport = CrashReport.a(throwable, "Exception while updating neighbours"); @@ -317,7 +511,7 @@ crashreportsystemdetails.a("Source block type", () -> { try { return String.format("ID #%s (%s // %s)", new Object[] { IRegistry.BLOCK.getKey(block), block.m(), block.getClass().getCanonicalName()}); - } catch (Throwable throwable) { + } catch (Throwable throwablex) { // CraftBukkit - decompile error return "ID #" + IRegistry.BLOCK.getKey(block); } }); @@ -403,6 +597,17 @@ } public IBlockData getType(BlockPosition blockposition) { + // CraftBukkit start - tree generation + if (captureTreeGeneration) { + Iterator<CraftBlockState> it = capturedBlockStates.iterator(); + while (it.hasNext()) { + CraftBlockState previous = it.next(); + if (previous.getX() == blockposition.getX() && previous.getY() == blockposition.getY() && previous.getZ() == blockposition.getZ()) { + return previous.getHandle(); + } + } + } + // CraftBukkit end if (k(blockposition)) { return Blocks.VOID_AIR.getBlockData(); } else { @@ -649,6 +854,49 @@ } public boolean addEntity(Entity entity) { + // CraftBukkit start - Used for entities other than creatures + return addEntity(entity, SpawnReason.DEFAULT); + } + + public boolean addEntity(Entity entity, SpawnReason spawnReason) { // Changed signature, added SpawnReason + if (entity == null) return false; + + org.bukkit.event.Cancellable event = null; + if (entity instanceof EntityLiving && !(entity instanceof EntityPlayer)) { + boolean isAnimal = entity instanceof EntityAnimal || entity instanceof EntityWaterAnimal || entity instanceof EntityGolem; + boolean isMonster = entity instanceof EntityMonster || entity instanceof EntityGhast || entity instanceof EntitySlime; + boolean isNpc = entity instanceof NPC; + + if (spawnReason != SpawnReason.CUSTOM) { + if (isAnimal && !allowAnimals || isMonster && !allowMonsters || isNpc && !getServer().getServer().getSpawnNPCs()) { + entity.dead = true; + return false; + } + } + + event = CraftEventFactory.callCreatureSpawnEvent((EntityLiving) entity, spawnReason); + } else if (entity instanceof EntityItem) { + event = CraftEventFactory.callItemSpawnEvent((EntityItem) entity); + } else if (entity.getBukkitEntity() instanceof org.bukkit.entity.Projectile) { + // Not all projectiles extend EntityProjectile, so check for Bukkit interface instead + event = CraftEventFactory.callProjectileLaunchEvent(entity); + } else if (entity.getBukkitEntity() instanceof org.bukkit.entity.Vehicle){ + event = CraftEventFactory.callVehicleCreateEvent(entity); + } + + if (event != null && (event.isCancelled() || entity.dead)) { + Entity vehicle = entity.getVehicle(); + if (vehicle != null) { + vehicle.dead = true; + } + for (Entity passenger : entity.getAllPassengers()) { + passenger.dead = true; + } + entity.dead = true; + return false; + } + // CraftBukkit end + int i = MathHelper.floor(entity.locX / 16.0D); int j = MathHelper.floor(entity.locZ / 16.0D); boolean flag = entity.attachedToPlayer; @@ -679,6 +927,7 @@ ((IWorldAccess) this.v.get(i)).a(entity); } + entity.valid = true; // CraftBukkit } protected void c(Entity entity) { @@ -686,6 +935,7 @@ ((IWorldAccess) this.v.get(i)).b(entity); } + entity.valid = false; // CraftBukkit } public void kill(Entity entity) { @@ -721,7 +971,15 @@ this.getChunkAt(i, j).b(entity); } - this.entityList.remove(entity); + // CraftBukkit start - Decrement loop variable field if we've already ticked this entity + int index = this.entityList.indexOf(entity); + if (index != -1) { + if (index <= this.tickPosition) { + this.tickPosition--; + } + this.entityList.remove(index); + } + // CraftBukkit end this.c(entity); } @@ -756,6 +1014,11 @@ for (i = 0; i < this.k.size(); ++i) { entity = (Entity) this.k.get(i); + // CraftBukkit start - Fixed an NPE + if (entity == null) { + continue; + } + // CraftBukkit end try { ++entity.ticksLived; @@ -804,8 +1067,10 @@ CrashReportSystemDetails crashreportsystemdetails1; CrashReport crashreport1; - for (i = 0; i < this.entityList.size(); ++i) { - entity = (Entity) this.entityList.get(i); + // CraftBukkit start - Use field for loop variable + for (this.tickPosition = 0; this.tickPosition < this.entityList.size(); ++this.tickPosition) { + entity = (Entity) this.entityList.get(this.tickPosition); + // CraftBukkit end Entity entity1 = entity.getVehicle(); if (entity1 != null) { @@ -838,7 +1103,7 @@ this.getChunkAt(j, l).b(entity); } - this.entityList.remove(i--); + this.entityList.remove(this.tickPosition--); // CraftBukkit - Use field for loop variable this.c(entity); } @@ -893,9 +1158,11 @@ TileEntity tileentity1 = (TileEntity) this.c.get(i1); if (!tileentity1.x()) { + /* CraftBukkit start - Order matters, moved down if (!this.tileEntityList.contains(tileentity1)) { this.a(tileentity1); } + // CraftBukkit end */ if (this.isLoaded(tileentity1.getPosition())) { Chunk chunk = this.getChunkAtWorldCoords(tileentity1.getPosition()); @@ -903,6 +1170,12 @@ chunk.a(tileentity1.getPosition(), tileentity1); this.notify(tileentity1.getPosition(), iblockdata, iblockdata, 3); + // CraftBukkit start + // From above, don't screw this up - SPIGOT-1746 + if (!this.tileEntityList.contains(tileentity1)) { + this.a(tileentity1); + } + // CraftBukkit end } } } @@ -956,15 +1229,13 @@ int i; int j; - if (!(entity instanceof EntityHuman)) { - i = MathHelper.floor(entity.locX); - j = MathHelper.floor(entity.locZ); - boolean flag1 = true; - - if (flag && !this.isAreaLoaded(i - 32, 0, j - 32, i + 32, 0, j + 32, true)) { - return; - } + // CraftBukkit start - check if chunks are loaded as done in previous versions + // TODO: Go back to Vanilla behaviour when comfortable + Chunk startingChunk = this.getChunkIfLoaded(MathHelper.floor(entity.locX) >> 4, MathHelper.floor(entity.locZ) >> 4); + if (flag && !(startingChunk != null && startingChunk.areNeighborsLoaded(2))) { + return; } + // CraftBukkit end entity.N = entity.locX; entity.O = entity.locY; @@ -980,6 +1251,7 @@ return IRegistry.ENTITY_TYPE.getKey(entity.P()).toString(); }); entity.tick(); + entity.postTick(); // CraftBukkit this.methodProfiler.e(); } } @@ -1310,11 +1582,18 @@ } } + public Map<BlockPosition, TileEntity> capturedTileEntities = Maps.newHashMap(); @Nullable public TileEntity getTileEntity(BlockPosition blockposition) { if (k(blockposition)) { return null; } else { + // CraftBukkit start + if (capturedTileEntities.containsKey(blockposition)) { + return capturedTileEntities.get(blockposition); + } + // CraftBukkit end + TileEntity tileentity = null; if (this.J) { @@ -1349,6 +1628,14 @@ public void setTileEntity(BlockPosition blockposition, @Nullable TileEntity tileentity) { if (!k(blockposition)) { if (tileentity != null && !tileentity.x()) { + // CraftBukkit start + if (captureBlockStates) { + tileentity.setWorld(this); + tileentity.setPosition(blockposition); + capturedTileEntities.put(blockposition, tileentity); + return; + } + // CraftBukkit end if (this.J) { tileentity.setPosition(blockposition); Iterator iterator = this.c.iterator(); @@ -1509,6 +1796,14 @@ } this.p = MathHelper.a(this.p, 0.0F, 1.0F); + + // CraftBukkit start + for (int idx = 0; idx < this.players.size(); ++idx) { + if (((EntityPlayer) this.players.get(idx)).world == this) { + ((EntityPlayer) this.players.get(idx)).tickWeather(); + } + } + // CraftBukkit end } } } @@ -1594,7 +1889,10 @@ } public boolean c(EnumSkyBlock enumskyblock, BlockPosition blockposition) { - if (!this.areChunksLoaded(blockposition, 17, false)) { + // CraftBukkit start - Use neighbor cache instead of looking up + Chunk chunk = this.getChunkIfLoaded(blockposition.getX() >> 4, blockposition.getZ() >> 4); + if (chunk == null || !chunk.areNeighborsLoaded(1) /*!this.areChunksLoaded(blockposition, 17, false)*/) { + // CraftBukkit end return false; } else { int i = 0; @@ -1737,7 +2035,7 @@ } public Stream<VoxelShape> a(@Nullable Entity entity, VoxelShape voxelshape, VoxelShape voxelshape1, Set<Entity> set) { - Stream stream = super.a(entity, voxelshape, voxelshape1, set); + Stream stream = IIBlockAccess.super.a(entity, voxelshape, voxelshape1, set); // CraftBukkit - decompile error return entity == null ? stream : Stream.concat(stream, this.a(entity, voxelshape, set)); } @@ -1767,7 +2065,7 @@ while (iterator.hasNext()) { Entity entity = (Entity) iterator.next(); - if (oclass.isAssignableFrom(entity.getClass()) && predicate.test(entity)) { + if (oclass.isAssignableFrom(entity.getClass()) && predicate.test((T) entity)) { arraylist.add(entity); } } @@ -1782,7 +2080,7 @@ while (iterator.hasNext()) { Entity entity = (Entity) iterator.next(); - if (oclass.isAssignableFrom(entity.getClass()) && predicate.test(entity)) { + if (oclass.isAssignableFrom(entity.getClass()) && predicate.test((T) entity)) { // CraftBukkit - fix decompile error arraylist.add(entity); } } @@ -1831,7 +2129,7 @@ } } - return entity; + return (T) entity; // CraftBukkit fix decompile error } @Nullable @@ -1852,8 +2150,16 @@ while (iterator.hasNext()) { Entity entity = (Entity) iterator.next(); + // CraftBukkit start - Split out persistent check, don't apply it to special persistent mobs + if (entity instanceof EntityInsentient) { + EntityInsentient entityinsentient = (EntityInsentient) entity; + if (entityinsentient.isTypeNotPersistent() && entityinsentient.isPersistent()) { + continue; + } + } - if (!(entity instanceof EntityInsentient) || !((EntityInsentient) entity).isPersistent()) { + if (true || !(entity instanceof EntityInsentient) || !((EntityInsentient) entity).isPersistent()) { + // CraftBukkit end if (oclass.isAssignableFrom(entity.getClass())) { ++j; } @@ -1972,6 +2278,11 @@ for (int i = 0; i < this.players.size(); ++i) { EntityHuman entityhuman1 = (EntityHuman) this.players.get(i); + // CraftBukkit start - Fixed an NPE + if (entityhuman1 == null || entityhuman1.dead) { + continue; + } + // CraftBukkit end if (predicate.test(entityhuman1)) { double d5 = entityhuman1.d(d0, d1, d2); @@ -2185,6 +2496,16 @@ public void everyoneSleeping() {} + // CraftBukkit start + // Calls the method that checks to see if players are sleeping + // Called by CraftPlayer.setPermanentSleeping() + public void checkSleepStatus() { + if (!this.isClientSide) { + this.everyoneSleeping(); + } + } + // CraftBukkit end + public float g(float f) { return (this.q + (this.r - this.q) * f) * this.i(f); } @@ -2346,7 +2667,7 @@ int l = j * 16 + 8 - blockposition.getZ(); boolean flag = true; - return k >= -128 && k <= 128 && l >= -128 && l <= 128; + return k >= -128 && k <= 128 && l >= -128 && l <= 128 && this.keepSpawnInMemory; // CraftBukkit - Added 'this.keepSpawnInMemory' } public LongSet ag() {