package net.minecraft.server;
import com.destroystokyo.paper.exception.ServerInternalException;
import com.google.common.base.Predicate;
import com.google.common.collect.Maps;
import com.google.common.collect.Queues;
import com.koloboke.collect.map.hash.HashObjIntMap;
import com.koloboke.collect.map.hash.HashObjIntMaps;
import com.koloboke.collect.map.hash.HashObjObjMaps;
import java.util.Arrays;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.concurrent.ConcurrentLinkedQueue;
import javax.annotation.Nullable;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import com.google.common.collect.Lists; // CraftBukkit
import org.bukkit.Server; // CraftBukkit
import org.bukkit.craftbukkit.util.CraftMagicNumbers; // Paper
public class Chunk {
private static final Logger e = LogManager.getLogger();
public static final ChunkSection a = null;
private final ChunkSection[] sections;
private final byte[] g;
private final int[] h;
private final boolean[] i;
private boolean j;
public final World world;
public final int[] heightMap;
public Long scheduledForUnload; // Paper - delay chunk unloads
public final int locX;
public final int locZ;
private boolean m;
public final Map<BlockPosition, TileEntity> tileEntities;
public final List<Entity>[] entitySlices; // Spigot
public final PaperLightingQueue.LightingQueue lightingQueue = new PaperLightingQueue.LightingQueue(this); // Paper
private boolean done;
private boolean lit;
private boolean r;
private boolean s;
private boolean t;
private long lastSaved;
private int v;
private long w;
private int x;
private final ConcurrentLinkedQueue<BlockPosition> y;
public boolean d; public void setShouldUnload(boolean unload) { this.d = unload; } public boolean isUnloading() { return d; } // Paper - OBFHELPER
public HashObjIntMap<Class> entityCount = HashObjIntMaps.getDefaultFactory().newMutableMap(); // Spigot
// Paper start
// Track the number of minecarts and items
// Keep this synced with entitySlices.add() and entitySlices.remove()
private final int[] itemCounts = new int[16];
private final int[] inventoryEntityCounts = new int[16];
// Paper end
// CraftBukkit start - Neighbor loaded cache for chunk lighting and entity ticking
private int neighbors = 0x1 << 12;
public long chunkKey;
public boolean areNeighborsLoaded(final int radius) {
switch (radius) {
case 2:
return this.neighbors == Integer.MAX_VALUE >> 6;
case 1:
final int mask =
// x z offset x z offset x z offset
(0x1 << (1 * 5 + 1 + 12)) | (0x1 << (0 * 5 + 1 + 12)) | (0x1 << (-1 * 5 + 1 + 12)) |
(0x1 << (1 * 5 + 0 + 12)) | (0x1 << (0 * 5 + 0 + 12)) | (0x1 << (-1 * 5 + 0 + 12)) |
(0x1 << (1 * 5 + -1 + 12)) | (0x1 << (0 * 5 + -1 + 12)) | (0x1 << (-1 * 5 + -1 + 12));
return (this.neighbors & mask) == mask;
default:
throw new UnsupportedOperationException(String.valueOf(radius));
}
}
public void setNeighborLoaded(final int x, final int z) {
this.neighbors |= 0x1 << (x * 5 + 12 + z);
}
public void setNeighborUnloaded(final int x, final int z) {
this.neighbors &= ~(0x1 << (x * 5 + 12 + z));
}
// CraftBukkit end
public Chunk(World world, int i, int j) {
this.sections = new ChunkSection[16];
this.g = new byte[256];
this.h = new int[256];
this.i = new boolean[256];
this.tileEntities = HashObjObjMaps.newMutableMap();
this.x = 4096;
this.y = Queues.newConcurrentLinkedQueue();
this.entitySlices = (new List[16]); // Spigot
this.world = world;
this.locX = i;
this.locZ = j;
this.heightMap = new int[256];
for (int k = 0; k < this.entitySlices.length; ++k) {
this.entitySlices[k] = new org.bukkit.craftbukkit.util.UnsafeList(); // Spigot
}
Arrays.fill(this.h, -999);
Arrays.fill(this.g, (byte) -1);
// CraftBukkit start
this.bukkitChunk = new org.bukkit.craftbukkit.CraftChunk(this);
this.chunkKey = ChunkCoordIntPair.chunkXZ2Int(this.locX, this.locZ);
}
public org.bukkit.Chunk bukkitChunk;
public boolean mustSave;
// CraftBukkit end
public Chunk(World world, ChunkSnapshot chunksnapshot, int i, int j) {
this(world, i, j);
boolean flag = true;
boolean flag1 = world.worldProvider.m();
for (int k = 0; k < 16; ++k) {
for (int l = 0; l < 16; ++l) {
for (int i1 = 0; i1 < 256; ++i1) {
IBlockData iblockdata = chunksnapshot.a(k, i1, l);
if (iblockdata.getMaterial() != Material.AIR) {
int j1 = i1 >> 4;
if (this.sections[j1] == Chunk.a) {
this.sections[j1] = new ChunkSection(j1 << 4, flag1);
}
this.sections[j1].setType(k, i1 & 15, l, iblockdata);
}
}
}
}
}
public boolean a(int i, int j) {
return i == this.locX && j == this.locZ;
}
public int e(BlockPosition blockposition) {
return this.b(blockposition.getX() & 15, blockposition.getZ() & 15);
}
public int b(int i, int j) {
return this.heightMap[j << 4 | i];
}
@Nullable
private ChunkSection y() {
for (int i = this.sections.length - 1; i >= 0; --i) {
if (this.sections[i] != Chunk.a) {
return this.sections[i];
}
}
return null;
}
public int findFilledTop() { return this.g(); } // OBFHELPER
public int g() {
ChunkSection section = this.y();
return section == null ? 0 : section.getYPosition();
}
public ChunkSection[] getSections() {
return this.sections;
}
public void initLighting() {
int i = this.g();
this.v = Integer.MAX_VALUE;
for (int j = 0; j < 16; ++j) {
int k = 0;
while (k < 16) {
this.h[j + (k << 4)] = -999;
int l = i + 16;
while (true) {
if (l > 0) {
if (this.d(j, l - 1, k) == 0) {
--l;
continue;
}
this.heightMap[k << 4 | j] = l;
if (l < this.v) {
this.v = l;
}
}
if (this.world.worldProvider.m()) {
l = 15;
int i1 = i + 16 - 1;
do {
int j1 = this.d(j, i1, k);
if (j1 == 0 && l != 15) {
j1 = 1;
}
l -= j1;
if (l > 0) {
ChunkSection chunksection = this.sections[i1 >> 4];
if (chunksection != Chunk.a) {
chunksection.a(j, i1 & 15, k, l);
this.world.m(new BlockPosition((this.locX << 4) + j, i1, (this.locZ << 4) + k));
}
}
--i1;
} while (i1 > 0 && l > 0);
}
++k;
break;
}
}
}
this.s = true;
}
private void d(int i, int j) {
this.i[i + j * 16] = true;
this.m = true;
}
private void h(boolean flag) {
this.world.methodProfiler.a("recheckGaps");
if (this.world.areChunksLoaded(new BlockPosition(this.locX * 16 + 8, 0, this.locZ * 16 + 8), 16)) {
lightingQueue.add(() -> recheckGaps(flag)); // Paper - Queue light update
}
}
private void recheckGaps(boolean flag) {
if (true) {
// Paper end
for (int i = 0; i < 16; ++i) {
for (int j = 0; j < 16; ++j) {
if (this.i[i + j * 16]) {
this.i[i + j * 16] = false;
int k = this.b(i, j);
int l = this.locX * 16 + i;
int i1 = this.locZ * 16 + j;
int j1 = Integer.MAX_VALUE;
Iterator iterator;
EnumDirection enumdirection;
for (iterator = EnumDirection.EnumDirectionLimit.HORIZONTAL.iterator(); iterator.hasNext(); j1 = Math.min(j1, this.world.d(l + enumdirection.getAdjacentX(), i1 + enumdirection.getAdjacentZ()))) {
enumdirection = (EnumDirection) iterator.next();
}
this.b(l, i1, j1);
iterator = EnumDirection.EnumDirectionLimit.HORIZONTAL.iterator();
while (iterator.hasNext()) {
enumdirection = (EnumDirection) iterator.next();
this.b(l + enumdirection.getAdjacentX(), i1 + enumdirection.getAdjacentZ(), k);
}
if (flag) {
return;
}
}
}
}
this.m = false;
}
}
private void b(int i, int j, int k) {
int l = this.world.getHighestBlockYAt(new BlockPosition(i, 0, j)).getY();
if (l > k) {
this.a(i, j, k, l + 1);
} else if (l < k) {
this.a(i, j, l, k + 1);
}
}
private void a(int i, int j, int k, int l) {
if (l > k && this.world.areChunksLoaded(new BlockPosition(i, 0, j), 16)) {
for (int i1 = k; i1 < l; ++i1) {
this.world.c(EnumSkyBlock.SKY, new BlockPosition(i, i1, j));
}
this.s = true;
}
}
private void c(int i, int j, int k) {
int l = this.heightMap[k << 4 | i] & 255;
int i1 = l;
if (j > l) {
i1 = j;
}
while (i1 > 0 && this.d(i, i1 - 1, k) == 0) {
--i1;
}
if (i1 != l) {
this.world.a(i + this.locX * 16, k + this.locZ * 16, i1, l);
this.heightMap[k << 4 | i] = i1;
int j1 = this.locX * 16 + i;
int k1 = this.locZ * 16 + k;
int l1;
int i2;
if (this.world.worldProvider.m()) {
ChunkSection chunksection;
if (i1 < l) {
for (l1 = i1; l1 < l; ++l1) {
chunksection = this.sections[l1 >> 4];
if (chunksection != Chunk.a) {
chunksection.a(i, l1 & 15, k, 15);
this.world.m(new BlockPosition((this.locX << 4) + i, l1, (this.locZ << 4) + k));
}
}
} else {
for (l1 = l; l1 < i1; ++l1) {
chunksection = this.sections[l1 >> 4];
if (chunksection != Chunk.a) {
chunksection.a(i, l1 & 15, k, 0);
this.world.m(new BlockPosition((this.locX << 4) + i, l1, (this.locZ << 4) + k));
}
}
}
l1 = 15;
while (i1 > 0 && l1 > 0) {
--i1;
i2 = this.d(i, i1, k);
if (i2 == 0) {
i2 = 1;
}
l1 -= i2;
if (l1 < 0) {
l1 = 0;
}
ChunkSection chunksection1 = this.sections[i1 >> 4];
if (chunksection1 != Chunk.a) {
chunksection1.a(i, i1 & 15, k, l1);
}
}
}
l1 = this.heightMap[k << 4 | i];
i2 = l;
int j2 = l1;
if (l1 < l) {
i2 = l1;
j2 = l;
}
if (l1 < this.v) {
this.v = l1;
}
if (this.world.worldProvider.m()) {
Iterator iterator = EnumDirection.EnumDirectionLimit.HORIZONTAL.iterator();
while (iterator.hasNext()) {
EnumDirection enumdirection = (EnumDirection) iterator.next();
this.a(j1 + enumdirection.getAdjacentX(), k1 + enumdirection.getAdjacentZ(), i2, j2);
}
this.a(j1, k1, i2, j2);
}
this.s = true;
}
}
public int b(BlockPosition blockposition) {
return this.getBlockData(blockposition).c();
}
private int d(int i, int j, int k) {
return this.a(i, j, k).c();
}
// Paper start - Optimize getBlockData to reduce instructions
public final IBlockData getBlockData(final BlockPosition pos) {
return getBlockData(pos.getX(), pos.getY(), pos.getZ());
}
public final IBlockData getBlockData(final int x, final int y, final int z) {
// Method body / logic copied from below
final int i = y >> 4;
if (y >= 0 && i < this.sections.length && this.sections[i] != null) {
// Inlined ChunkSection.getType() and DataPaletteBlock.a(int,int,int)
return this.sections[i].blockIds.a((y & 15) << 8 | (z & 15) << 4 | x & 15);
}
return Blocks.AIR.getBlockData();
}
public IBlockData a(final int i, final int j, final int k) {
return getBlockData(i, j, k);
}
public IBlockData unused(final int i, final int j, final int k) {
// Paper end
if (this.world.L() == WorldType.DEBUG_ALL_BLOCK_STATES) {
IBlockData iblockdata = null;
if (j == 60) {
iblockdata = Blocks.BARRIER.getBlockData();
}
if (j == 70) {
iblockdata = ChunkProviderDebug.c(i, k);
}
return iblockdata == null ? Blocks.AIR.getBlockData() : iblockdata;
} else {
try {
if (j >= 0 && j >> 4 < this.sections.length) {
ChunkSection chunksection = this.sections[j >> 4];
if (chunksection != Chunk.a) {
return chunksection.getType(i & 15, j & 15, k & 15);
}
}
return Blocks.AIR.getBlockData();
} catch (Throwable throwable) {
CrashReport crashreport = CrashReport.a(throwable, "Getting block state");
CrashReportSystemDetails crashreportsystemdetails = crashreport.a("Block being got");
crashreportsystemdetails.a("Location", new CrashReportCallable() {
public String a() throws Exception {
return CrashReportSystemDetails.a(i, j, k);
}
@Override
public Object call() throws Exception {
return this.a();
}
});
throw new ReportedException(crashreport);
}
}
}
@Nullable
public IBlockData a(BlockPosition blockposition, IBlockData iblockdata) {
int i = blockposition.getX() & 15;
int j = blockposition.getY();
int k = blockposition.getZ() & 15;
int l = k << 4 | i;
if (j >= this.h[l] - 1) {
this.h[l] = -999;
}
int i1 = this.heightMap[l];
IBlockData iblockdata1 = this.getBlockData(blockposition);
if (iblockdata1 == iblockdata) {
return null;
} else {
Block block = iblockdata.getBlock();
Block block1 = iblockdata1.getBlock();
ChunkSection chunksection = this.sections[j >> 4];
boolean flag = false;
if (chunksection == Chunk.a) {
if (block == Blocks.AIR) {
return null;
}
chunksection = new ChunkSection(j >> 4 << 4, this.world.worldProvider.m());
this.sections[j >> 4] = chunksection;
flag = j >= i1;
}
chunksection.setType(i, j & 15, k, iblockdata);
if (block1 != block) {
if (!this.world.isClientSide) {
block1.remove(this.world, blockposition, iblockdata1);
} else if (block1 instanceof ITileEntity) {
this.world.s(blockposition);
}
}
if (chunksection.getType(i, j & 15, k).getBlock() != block) {
return null;
} else {
if (flag) {
this.initLighting();
} else { lightingQueue.add(() -> { // Paper - Queue light update
int j1 = iblockdata.c();
int k1 = iblockdata1.c();
if (j1 > 0) {
if (j >= i1) {
this.c(i, j + 1, k);
}
} else if (j == i1 - 1) {
this.c(i, j, k);
}
if (j1 != k1 && (j1 < k1 || this.getBrightness(EnumSkyBlock.SKY, blockposition) > 0 || this.getBrightness(EnumSkyBlock.BLOCK, blockposition) > 0)) {
this.d(i, k);
}
}); // Paper
}
TileEntity tileentity;
if (block1 instanceof ITileEntity) {
tileentity = this.a(blockposition, Chunk.EnumTileEntityState.CHECK);
if (tileentity != null) {
tileentity.invalidateBlockCache();
}
}
// CraftBukkit - Don't place while processing the BlockPlaceEvent, unless it's a BlockContainer. Prevents blocks such as TNT from activating when cancelled.
if (!this.world.isClientSide && block1 != block && (!this.world.captureBlockStates || block instanceof BlockTileEntity)) {
block.onPlace(this.world, blockposition, iblockdata);
}
if (block instanceof ITileEntity) {
tileentity = this.a(blockposition, Chunk.EnumTileEntityState.CHECK);
if (tileentity == null) {
tileentity = ((ITileEntity) block).a(this.world, block.toLegacyData(iblockdata));
this.world.setTileEntity(blockposition, tileentity);
}
if (tileentity != null) {
tileentity.invalidateBlockCache();
}
}
this.s = true;
return iblockdata1;
}
}
}
public int getBrightness(EnumSkyBlock enumskyblock, BlockPosition blockposition) {
int i = blockposition.getX() & 15;
int j = blockposition.getY();
int k = blockposition.getZ() & 15;
ChunkSection chunksection = this.sections[j >> 4];
return chunksection == Chunk.a ? (this.c(blockposition) ? enumskyblock.c : 0) : (enumskyblock == EnumSkyBlock.SKY ? (!this.world.worldProvider.m() ? 0 : chunksection.b(i, j & 15, k)) : (enumskyblock == EnumSkyBlock.BLOCK ? chunksection.c(i, j & 15, k) : enumskyblock.c));
}
public void a(EnumSkyBlock enumskyblock, BlockPosition blockposition, int i) {
int j = blockposition.getX() & 15;
int k = blockposition.getY();
int l = blockposition.getZ() & 15;
ChunkSection chunksection = this.sections[k >> 4];
if (chunksection == Chunk.a) {
chunksection = new ChunkSection(k >> 4 << 4, this.world.worldProvider.m());
this.sections[k >> 4] = chunksection;
this.initLighting();
}
this.s = true;
if (enumskyblock == EnumSkyBlock.SKY) {
if (this.world.worldProvider.m()) {
chunksection.a(j, k & 15, l, i);
}
} else if (enumskyblock == EnumSkyBlock.BLOCK) {
chunksection.b(j, k & 15, l, i);
}
}
public int a(BlockPosition blockposition, int i) {
int j = blockposition.getX() & 15;
int k = blockposition.getY();
int l = blockposition.getZ() & 15;
ChunkSection chunksection = this.sections[k >> 4];
if (chunksection == Chunk.a) {
return this.world.worldProvider.m() && i < EnumSkyBlock.SKY.c ? EnumSkyBlock.SKY.c - i : 0;
} else {
int i1 = !this.world.worldProvider.m() ? 0 : chunksection.b(j, k & 15, l);
i1 -= i;
int j1 = chunksection.c(j, k & 15, l);
if (j1 > i1) {
i1 = j1;
}
return i1;
}
}
public void a(Entity entity) {
this.t = true;
int i = MathHelper.floor(entity.locX / 16.0D);
int j = MathHelper.floor(entity.locZ / 16.0D);
if (i != this.locX || j != this.locZ) {
Chunk.e.warn("Wrong location! ({}, {}) should be ({}, {}), {}", new Object[] { Integer.valueOf(i), Integer.valueOf(j), Integer.valueOf(this.locX), Integer.valueOf(this.locZ), entity});
entity.die();
}
int k = MathHelper.floor(entity.locY / 16.0D);
if (k < 0) {
k = 0;
}
if (k >= this.entitySlices.length) {
k = this.entitySlices.length - 1;
}
entity.aa = true;
entity.ab = this.locX;
entity.ac = k;
entity.ad = this.locZ;
this.entitySlices[k].add(entity);
// Paper start - update count
if (entity instanceof EntityItem) {
itemCounts[k]++;
} else if (entity instanceof IInventory) {
inventoryEntityCounts[k]++;
}
// Paper end
// Spigot start - increment creature type count
// Keep this synced up with World.a(Class)
if (entity instanceof EntityInsentient) {
EntityInsentient entityinsentient = (EntityInsentient) entity;
if (entityinsentient.isTypeNotPersistent() && entityinsentient.isPersistent()) {
return;
}
}
for ( EnumCreatureType creatureType : EnumCreatureType.values() )
{
if ( creatureType.a().isAssignableFrom( entity.getClass() ) )
{
this.entityCount.addValue(creatureType.a(), 1, 1);
}
}
// Spigot end
}
public void b(Entity entity) {
this.a(entity, entity.ac);
}
public void a(Entity entity, int i) {
if (i < 0) {
i = 0;
}
if (i >= this.entitySlices.length) {
i = this.entitySlices.length - 1;
}
if (!this.entitySlices[i].remove(entity)) { return; } // Paper
// Paper start - update counts
if (entity instanceof EntityItem) {
itemCounts[i]--;
} else if (entity instanceof IInventory) {
inventoryEntityCounts[i]--;
}
// Paper end
// Spigot start - decrement creature type count
// Keep this synced up with World.a(Class)
if (entity instanceof EntityInsentient) {
EntityInsentient entityinsentient = (EntityInsentient) entity;
if (entityinsentient.isTypeNotPersistent() && entityinsentient.isPersistent()) {
return;
}
}
for ( EnumCreatureType creatureType : EnumCreatureType.values() )
{
if ( creatureType.a().isAssignableFrom( entity.getClass() ) )
{
this.entityCount.addValue(creatureType.a(), -1);
}
}
// Spigot end
}
public boolean c(BlockPosition blockposition) {
int i = blockposition.getX() & 15;
int j = blockposition.getY();
int k = blockposition.getZ() & 15;
return j >= this.heightMap[k << 4 | i];
}
@Nullable
private TileEntity g(BlockPosition blockposition) {
IBlockData iblockdata = this.getBlockData(blockposition);
Block block = iblockdata.getBlock();
return !block.isTileEntity() ? null : ((ITileEntity) block).a(this.world, iblockdata.getBlock().toLegacyData(iblockdata));
}
@Nullable public final TileEntity getTileEntityImmediately(BlockPosition pos) { return this.a(pos, EnumTileEntityState.IMMEDIATE); } // Paper - OBFHELPER
@Nullable
public TileEntity a(BlockPosition blockposition, Chunk.EnumTileEntityState chunk_enumtileentitystate) {
// CraftBukkit start
TileEntity tileentity = null;
if (world.captureBlockStates) {
tileentity = world.capturedTileEntities.get(blockposition);
}
if (tileentity == null) {
tileentity = this.tileEntities.get(blockposition);
}
// CraftBukkit end
if (tileentity == null) {
if (chunk_enumtileentitystate == Chunk.EnumTileEntityState.IMMEDIATE) {
tileentity = this.g(blockposition);
this.world.setTileEntity(blockposition, tileentity);
} else if (chunk_enumtileentitystate == Chunk.EnumTileEntityState.QUEUED) {
this.y.add(blockposition);
}
} else if (tileentity.y()) {
this.tileEntities.remove(blockposition);
return null;
}
return tileentity;
}
public void a(TileEntity tileentity) {
this.a(tileentity.getPosition(), tileentity);
if (this.j) {
this.world.a(tileentity);
}
}
public void a(BlockPosition blockposition, TileEntity tileentity) {
tileentity.a(this.world);
tileentity.setPosition(blockposition);
if (this.getBlockData(blockposition).getBlock() instanceof ITileEntity) {
if (this.tileEntities.containsKey(blockposition)) {
this.tileEntities.get(blockposition).z();
}
tileentity.A();
this.tileEntities.put(blockposition, tileentity);
// CraftBukkit start
// Paper start - Remove invalid mob spawner tile entities
} else if (tileentity instanceof TileEntityMobSpawner && org.bukkit.craftbukkit.util.CraftMagicNumbers.getMaterial(getBlockData(blockposition).getBlock()) != org.bukkit.Material.MOB_SPAWNER) {
this.tileEntities.remove(blockposition);
// Paper end
} else {
// Paper start
ServerInternalException e = new ServerInternalException(
"Attempted to place a tile entity (" + tileentity + ") at " + tileentity.position.getX() + ","
+ tileentity.position.getY() + "," + tileentity.position.getZ()
+ " (" + CraftMagicNumbers.getMaterial(getBlockData(blockposition).getBlock()) + ") where there was no entity tile!\n" +
"Chunk coordinates: " + (this.locX * 16) + "," + (this.locZ * 16));
e.printStackTrace();
ServerInternalException.reportInternalException(e);
if (this.world.paperConfig.removeCorruptTEs) {
this.removeTileEntity(tileentity.getPosition());
org.bukkit.Bukkit.getLogger().info("Removing corrupt tile entity");
}
// Paper end
// CraftBukkit end
}
}
public void removeTileEntity(BlockPosition blockposition) { this.d(blockposition); } // Paper - OBFHELPER
public void d(BlockPosition blockposition) {
if (this.j) {
TileEntity tileentity = this.tileEntities.remove(blockposition);
if (tileentity != null) {
tileentity.z();
}
}
}
public void addEntities() {
this.j = true;
this.world.b(this.tileEntities.values());
List[] aentityslice = this.entitySlices; // Spigot
int i = aentityslice.length;
for (int j = 0; j < i; ++j) {
List entityslice = aentityslice[j]; // Spigot
this.world.a(entityslice);
}
}
public void removeEntities() {
this.j = false;
Iterator iterator = this.tileEntities.values().iterator();
while (iterator.hasNext()) {
TileEntity tileentity = (TileEntity) iterator.next();
// Spigot Start
if ( tileentity instanceof IInventory )
{
for ( org.bukkit.entity.HumanEntity h : Lists.<org.bukkit.entity.HumanEntity>newArrayList(( (IInventory) tileentity ).getViewers() ) )
{
if ( h instanceof org.bukkit.craftbukkit.entity.CraftHumanEntity )
{
( (org.bukkit.craftbukkit.entity.CraftHumanEntity) h).getHandle().closeInventory();
}
}
}
// Spigot End
this.world.b(tileentity);
}
List[] aentityslice = this.entitySlices; // Spigot
int i = aentityslice.length;
for (int j = 0; j < i; ++j) {
// CraftBukkit start
List<Entity> newList = Lists.newArrayList(aentityslice[j]);
java.util.Iterator<Entity> iter = newList.iterator();
while (iter.hasNext()) {
Entity entity = iter.next();
// Spigot Start
if ( entity instanceof IInventory )
{
for ( org.bukkit.entity.HumanEntity h : Lists.<org.bukkit.entity.HumanEntity>newArrayList( ( (IInventory) entity ).getViewers() ) )
{
if ( h instanceof org.bukkit.craftbukkit.entity.CraftHumanEntity )
{
( (org.bukkit.craftbukkit.entity.CraftHumanEntity) h).getHandle().closeInventory();
}
}
}
// Spigot End
// Do not pass along players, as doing so can get them stuck outside of time.
// (which for example disables inventory icon updates and prevents block breaking)
if (entity instanceof EntityPlayer) {
iter.remove();
}
}
this.world.c(newList);
// CraftBukkit end
}
}
public void e() {
this.s = true;
}
public void a(@Nullable Entity entity, AxisAlignedBB axisalignedbb, List<Entity> list, Predicate<? super Entity> predicate) {
int i = MathHelper.floor((axisalignedbb.b - 2.0D) / 16.0D);
int j = MathHelper.floor((axisalignedbb.e + 2.0D) / 16.0D);
i = MathHelper.clamp(i, 0, this.entitySlices.length - 1);
j = MathHelper.clamp(j, 0, this.entitySlices.length - 1);
for (int k = i; k <= j; ++k) {
if (!this.entitySlices[k].isEmpty()) {
Iterator iterator = this.entitySlices[k].iterator();
// Paper start - Don't search for inventories if we have none, and that is all we want
/*
* We check if they want inventories by seeing if it is the static `IEntitySelector.c`
*
* Make sure the inventory selector stays in sync.
* It should be the one that checks `var1 instanceof IInventory && var1.isAlive()`
*/
if (predicate == IEntitySelector.c && inventoryEntityCounts[k] <= 0) continue;
// Paper end
while (iterator.hasNext()) {
Entity entity1 = (Entity) iterator.next();
if (entity1.getBoundingBox().c(axisalignedbb) && entity1 != entity) {
if (predicate == null || predicate.apply(entity1)) {
list.add(entity1);
}
Entity[] aentity = entity1.aT();
if (aentity != null) {
Entity[] aentity1 = aentity;
int l = aentity.length;
for (int i1 = 0; i1 < l; ++i1) {
Entity entity2 = aentity1[i1];
if (entity2 != entity && entity2.getBoundingBox().c(axisalignedbb) && (predicate == null || predicate.apply(entity2))) {
list.add(entity2);
}
}
}
}
}
}
}
}
public <T extends Entity> void a(Class<? extends T> oclass, AxisAlignedBB axisalignedbb, List<T> list, Predicate<? super T> predicate) {
int i = MathHelper.floor((axisalignedbb.b - 2.0D) / 16.0D);
int j = MathHelper.floor((axisalignedbb.e + 2.0D) / 16.0D);
i = MathHelper.clamp(i, 0, this.entitySlices.length - 1);
j = MathHelper.clamp(j, 0, this.entitySlices.length - 1);
// Paper start
int[] counts;
if (EntityItem.class.isAssignableFrom(oclass)) {
counts = itemCounts;
} else if (IInventory.class.isAssignableFrom(oclass)) {
counts = inventoryEntityCounts;
} else {
counts = null;
}
// Paper end
for (int k = i; k <= j; ++k) {
if (counts != null && counts[k] <= 0) continue; // Paper - Don't check a chunk if it doesn't have the type we are looking for
Iterator iterator = this.entitySlices[k].iterator(); // Spigot
while (iterator.hasNext()) {
Entity entity = (Entity) iterator.next();
if (oclass.isInstance(entity) && entity.getBoundingBox().c(axisalignedbb) && (predicate == null || predicate.apply((T) entity))) { // CraftBukkit - fix decompile error // Spigot - instance check
list.add((T) entity); // Fix decompile error
}
}
}
}
public boolean a(boolean flag) {
if (flag) {
if (this.t && this.world.getTime() != this.lastSaved || this.s) {
return true;
}
}
// This !flag section should say if s(isModified) or t(hasEntities), then check auto save
return ((this.s || this.t) && this.world.getTime() >= this.lastSaved + world.paperConfig.autoSavePeriod); // Paper - Make world configurable and incremental
}
public Random a(long i) {
return new Random(this.world.getSeed() + this.locX * this.locX * 4987142 + this.locX * 5947611 + this.locZ * this.locZ * 4392871L + this.locZ * 389711 ^ i);
}
public boolean isEmpty() {
return false;
}
// CraftBukkit start
public void loadNearby(IChunkProvider ichunkprovider, ChunkGenerator chunkgenerator, boolean newChunk) {
world.timings.syncChunkLoadPostTimer.startTiming(); // Paper
Server server = world.getServer();
if (server != null) {
/*
* If it's a new world, the first few chunks are generated inside
* the World constructor. We can't reliably alter that, so we have
* no way of creating a CraftWorld/CraftServer at that point.
*/
server.getPluginManager().callEvent(new org.bukkit.event.world.ChunkLoadEvent(bukkitChunk, newChunk));
}
// Update neighbor counts
for (int x = -2; x < 3; x++) {
for (int z = -2; z < 3; z++) {
if (x == 0 && z == 0) {
continue;
}
Chunk neighbor = getWorld().getChunkIfLoaded(locX + x, locZ + z);
if (neighbor != null) {
neighbor.setNeighborLoaded(-x, -z);
setNeighborLoaded(x, z);
}
}
}
// CraftBukkit end
world.timings.syncChunkLoadPostTimer.stopTiming(); // Paper
world.timings.syncChunkLoadPopulateNeighbors.startTiming(); // Paper
Chunk chunk = MCUtil.getLoadedChunkWithoutMarkingActive(ichunkprovider, this.locX, this.locZ - 1); // Paper
Chunk chunk1 = MCUtil.getLoadedChunkWithoutMarkingActive(ichunkprovider, this.locX + 1, this.locZ); // Paper
Chunk chunk2 = MCUtil.getLoadedChunkWithoutMarkingActive(ichunkprovider, this.locX, this.locZ + 1); // Paper
Chunk chunk3 = MCUtil.getLoadedChunkWithoutMarkingActive(ichunkprovider, this.locX - 1, this.locZ); // Paper
if (chunk1 != null && chunk2 != null && MCUtil.getLoadedChunkWithoutMarkingActive(ichunkprovider,this.locX + 1, this.locZ + 1) != null) { // Paper
this.a(chunkgenerator);
}
if (chunk3 != null && chunk2 != null && MCUtil.getLoadedChunkWithoutMarkingActive(ichunkprovider,this.locX - 1, this.locZ + 1) != null) { // Paper
chunk3.a(chunkgenerator);
}
if (chunk != null && chunk1 != null && MCUtil.getLoadedChunkWithoutMarkingActive(ichunkprovider,this.locX + 1, this.locZ - 1) != null) { // Paper
chunk.a(chunkgenerator);
}
if (chunk != null && chunk3 != null) {
Chunk chunk4 = MCUtil.getLoadedChunkWithoutMarkingActive(ichunkprovider,this.locX - 1, this.locZ - 1); // Paper
if (chunk4 != null) {
chunk4.a(chunkgenerator);
}
}
world.timings.syncChunkLoadPopulateNeighbors.stopTiming(); // Paper
}
protected void a(ChunkGenerator chunkgenerator) {
if (this.isDone()) {
if (chunkgenerator.a(this, this.locX, this.locZ)) {
this.e();
}
} else {
this.o();
chunkgenerator.recreateStructures(this.locX, this.locZ);
// CraftBukkit start
BlockSand.instaFall = true;
Random random = new Random();
random.setSeed(world.getSeed());
long xRand = random.nextLong() / 2L * 2L + 1L;
long zRand = random.nextLong() / 2L * 2L + 1L;
random.setSeed(locX * xRand + locZ * zRand ^ world.getSeed());
org.bukkit.World world = this.world.getWorld();
if (world != null) {
this.world.populating = true;
try {
for (org.bukkit.generator.BlockPopulator populator : world.getPopulators()) {
populator.populate(world, random, bukkitChunk);
}
} finally {
this.world.populating = false;
}
}
BlockSand.instaFall = false;
this.world.getServer().getPluginManager().callEvent(new org.bukkit.event.world.ChunkPopulateEvent(bukkitChunk));
// CraftBukkit end
this.e();
}
}
public BlockPosition f(BlockPosition blockposition) {
int i = blockposition.getX() & 15;
int j = blockposition.getZ() & 15;
int k = i | j << 4;
BlockPosition blockposition1 = new BlockPosition(blockposition.getX(), this.h[k], blockposition.getZ());
if (blockposition1.getY() == -999) {
int l = this.g() + 15;
blockposition1 = new BlockPosition(blockposition.getX(), l, blockposition.getZ());
int i1 = -1;
while (blockposition1.getY() > 0 && i1 == -1) {
IBlockData iblockdata = this.getBlockData(blockposition1);
Material material = iblockdata.getMaterial();
if (!material.isSolid() && !material.isLiquid()) {
blockposition1 = blockposition1.down();
} else {
i1 = blockposition1.getY() + 1;
}
}
this.h[k] = i1;
}
return new BlockPosition(blockposition.getX(), this.h[k], blockposition.getZ());
}
public void b(boolean flag) {
if (this.m && this.world.worldProvider.m() && !flag) {
this.h(this.world.isClientSide);
}
this.r = true;
if (!this.lit && this.done && this.world.spigotConfig.randomLightUpdates) { // Spigot - also use random light updates setting to determine if we should relight
this.o();
}
BlockPosition blockposition;
while ((blockposition = this.y.poll()) != null) {
if (this.a(blockposition, Chunk.EnumTileEntityState.CHECK) == null && this.getBlockData(blockposition).getBlock().isTileEntity()) {
TileEntity tileentity = this.g(blockposition);
this.world.setTileEntity(blockposition, tileentity);
this.world.b(blockposition, blockposition);
}
}
}
public boolean isReady() {
// Spigot Start
/*
* As of 1.7, Mojang added a check to make sure that only chunks which have been lit are sent to the client.
* Unfortunately this interferes with our modified chunk ticking algorithm, which will only tick chunks distant from the player on a very infrequent basis.
* We cannot unfortunately do this lighting stage during chunk gen as it appears to put a lot more noticeable load on the server, than when it is done at play time.
* For now at least we will simply send all chunks, in accordance with pre 1.7 behaviour.
*/
return true;
// Spigot End
}
public boolean j() {
return this.r;
}
public ChunkCoordIntPair k() {
return new ChunkCoordIntPair(this.locX, this.locZ);
}
public boolean c(int i, int j) {
if (i < 0) {
i = 0;
}
if (j >= 256) {
j = 255;
}
for (int k = i; k <= j; k += 16) {
ChunkSection chunksection = this.sections[k >> 4];
if (chunksection != Chunk.a && !chunksection.a()) {
return false;
}
}
return true;
}
public void a(ChunkSection[] achunksection) {
if (this.sections.length != achunksection.length) {
Chunk.e.warn("Could not set level chunk sections, array length is {} instead of {}", new Object[] { Integer.valueOf(achunksection.length), Integer.valueOf(this.sections.length)});
} else {
System.arraycopy(achunksection, 0, this.sections, 0, this.sections.length);
}
}
public BiomeBase getBiome(BlockPosition blockposition, WorldChunkManager worldchunkmanager) {
int i = blockposition.getX() & 15;
int j = blockposition.getZ() & 15;
int k = this.g[j << 4 | i] & 255;
BiomeBase biomebase;
if (k == 255) {
biomebase = worldchunkmanager.getBiome(blockposition, Biomes.c);
k = BiomeBase.a(biomebase);
this.g[j << 4 | i] = (byte) (k & 255);
}
biomebase = BiomeBase.getBiome(k);
return biomebase == null ? Biomes.c : biomebase;
}
public byte[] getBiomeIndex() {
return this.g;
}
public void a(byte[] abyte) {
if (this.g.length != abyte.length) {
Chunk.e.warn("Could not set level chunk biomes, array length is {} instead of {}", new Object[] { Integer.valueOf(abyte.length), Integer.valueOf(this.g.length)});
} else {
System.arraycopy(abyte, 0, this.g, 0, this.g.length);
}
}
public void m() {
this.x = 0;
}
public void n() {
if (this.x < 4096) {
BlockPosition blockposition = new BlockPosition(this.locX << 4, 0, this.locZ << 4);
for (int i = 0; i < 8; ++i) {
if (this.x >= 4096) {
return;
}
int j = this.x % 16;
int k = this.x / 16 % 16;
int l = this.x / 256;
++this.x;
for (int i1 = 0; i1 < 16; ++i1) {
BlockPosition blockposition1 = blockposition.a(k, (j << 4) + i1, l);
boolean flag = i1 == 0 || i1 == 15 || k == 0 || k == 15 || l == 0 || l == 15;
if (this.sections[j] == Chunk.a && flag || this.sections[j] != Chunk.a && this.sections[j].getType(k, i1, l).getMaterial() == Material.AIR) {
EnumDirection[] aenumdirection = EnumDirection.values();
int j1 = aenumdirection.length;
for (int k1 = 0; k1 < j1; ++k1) {
EnumDirection enumdirection = aenumdirection[k1];
BlockPosition blockposition2 = blockposition1.shift(enumdirection);
if (this.world.getType(blockposition2).d() > 0) {
this.world.w(blockposition2);
}
}
this.world.w(blockposition1);
}
}
}
}
}
public void o() {
world.timings.lightChunk.startTiming(); // Paper
this.done = true;
this.lit = true;
BlockPosition blockposition = new BlockPosition(this.locX << 4, 0, this.locZ << 4);
if (this.world.worldProvider.m()) {
if (this.world.areChunksLoadedBetween(blockposition.a(-1, 0, -1), blockposition.a(16, this.world.K(), 16))) {
label42:
for (int i = 0; i < 16; ++i) {
for (int j = 0; j < 16; ++j) {
if (!this.e(i, j)) {
this.lit = false;
break label42;
}
}
}
if (this.lit) {
Iterator iterator = EnumDirection.EnumDirectionLimit.HORIZONTAL.iterator();
while (iterator.hasNext()) {
EnumDirection enumdirection = (EnumDirection) iterator.next();
int k = enumdirection.c() == EnumDirection.EnumAxisDirection.POSITIVE ? 16 : 1;
this.world.getChunkAtWorldCoords(blockposition.shift(enumdirection, k)).a(enumdirection.opposite());
}
this.z();
}
} else {
this.lit = false;
}
}
world.timings.lightChunk.stopTiming(); // Paper
}
private void z() {
for (int i = 0; i < this.i.length; ++i) {
this.i[i] = true;
}
this.h(false);
}
private void a(EnumDirection enumdirection) {
if (this.done) {
int i;
if (enumdirection == EnumDirection.EAST) {
for (i = 0; i < 16; ++i) {
this.e(15, i);
}
} else if (enumdirection == EnumDirection.WEST) {
for (i = 0; i < 16; ++i) {
this.e(0, i);
}
} else if (enumdirection == EnumDirection.SOUTH) {
for (i = 0; i < 16; ++i) {
this.e(i, 15);
}
} else if (enumdirection == EnumDirection.NORTH) {
for (i = 0; i < 16; ++i) {
this.e(i, 0);
}
}
}
}
private boolean e(int i, int j) {
int k = this.g();
boolean flag = false;
boolean flag1 = false;
BlockPosition.MutableBlockPosition blockposition_mutableblockposition = new BlockPosition.MutableBlockPosition((this.locX << 4) + i, 0, (this.locZ << 4) + j);
int l;
for (l = k + 16 - 1; l > this.world.K() || l > 0 && !flag1; --l) {
blockposition_mutableblockposition.c(blockposition_mutableblockposition.getX(), l, blockposition_mutableblockposition.getZ());
int i1 = this.b(blockposition_mutableblockposition);
if (i1 == 255 && blockposition_mutableblockposition.getY() < this.world.K()) {
flag1 = true;
}
if (!flag && i1 > 0) {
flag = true;
} else if (flag && i1 == 0 && !this.world.w(blockposition_mutableblockposition)) {
return false;
}
}
for (l = blockposition_mutableblockposition.getY(); l > 0; --l) {
blockposition_mutableblockposition.c(blockposition_mutableblockposition.getX(), l, blockposition_mutableblockposition.getZ());
if (this.getBlockData(blockposition_mutableblockposition).d() > 0) {
this.world.w(blockposition_mutableblockposition);
}
}
return true;
}
public boolean p() {
return this.j;
}
public World getWorld() {
return this.world;
}
public int[] r() {
return this.heightMap;
}
public void a(int[] aint) {
if (this.heightMap.length != aint.length) {
Chunk.e.warn("Could not set level chunk heightmap, array length is {} instead of {}", new Object[] { Integer.valueOf(aint.length), Integer.valueOf(this.heightMap.length)});
} else {
System.arraycopy(aint, 0, this.heightMap, 0, this.heightMap.length);
}
}
public Map<BlockPosition, TileEntity> getTileEntities() {
return this.tileEntities;
}
public List<Entity>[] getEntitySlices() {
return this.entitySlices;
}
public boolean isDone() {
return this.done;
}
public void d(boolean flag) {
this.done = flag;
}
public boolean v() {
return this.lit;
}
public void e(boolean flag) {
this.lit = flag;
}
public void f(boolean flag) {
this.s = flag;
}
public void g(boolean flag) {
this.t = flag;
}
public void setLastSaved(long i) {
this.lastSaved = i;
}
public int w() {
return this.v;
}
public long x() {
return world.paperConfig.useInhabitedTime ? this.w : 0; // Paper
}
public void c(long i) {
this.w = i;
}
public static enum EnumTileEntityState {
IMMEDIATE, QUEUED, CHECK;
private EnumTileEntityState() {}
}
}