Files
AkarinMC/src/main/java/net/minecraft/server/Chunk.java
2019-03-26 15:50:48 +08:00

1531 lines
56 KiB
Java

package net.minecraft.server;
// Paper start
import com.destroystokyo.paper.PaperWorldConfig.DuplicateUUIDMode;
import java.util.Arrays;
import java.util.HashMap;
import java.util.UUID;
// Paper end
import com.destroystokyo.paper.exception.ServerInternalException;
import com.google.common.collect.Maps;
import com.google.common.collect.Queues;
import com.google.common.collect.Sets;
import it.unimi.dsi.fastutil.longs.LongOpenHashSet;
import it.unimi.dsi.fastutil.longs.LongSet;
import it.unimi.dsi.fastutil.shorts.ShortList;
import it.unimi.dsi.fastutil.shorts.ShortListIterator;
import java.util.BitSet;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Predicate;
import javax.annotation.Nullable;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
// CraftBukkit start
import com.google.common.collect.Lists;
import java.util.LinkedList;
import org.bukkit.craftbukkit.event.CraftEventFactory;
import org.bukkit.event.entity.CreatureSpawnEvent;
// CraftBukkit end
public class Chunk implements IChunkAccess {
private static final Logger d = LogManager.getLogger();
public static final ChunkSection a = null; public static final ChunkSection EMPTY_CHUNK_SECTION = Chunk.a; // Paper - OBFHELPER
private final ChunkSection[] sections;
private final BiomeBase[] f;
private final boolean[] g;
private final Map<BlockPosition, NBTTagCompound> h;
private boolean i;public boolean isLoaded() { return i; } // Paper - OBFHELPER
public final World world;
public final Map<HeightMap.Type, HeightMap> heightMap;
public Long scheduledForUnload; // Paper - delay chunk unloads
private static final Logger logger = LogManager.getLogger(); // Paper
public final int locX;
public final int locZ;
private boolean l;
private final ChunkConverter m;
public final Map<BlockPosition, TileEntity> tileEntities;
public final List<Entity>[] entitySlices; // Spigot
private final Map<String, StructureStart> p;
private final Map<String, LongSet> q;
private final ShortList[] r;
private final TickList<Block> s;
private final TickList<FluidType> t;
private boolean u;
private boolean v;public boolean hasEntities() { return v; } // Paper - OBFHELPER
private long lastSaved;
private boolean x; public boolean isModified() { return x; } // Paper - OBFHELPER
private int y;
private long z;
private int A;
private final ConcurrentLinkedQueue<BlockPosition> B;
private ChunkStatus C;
private int D;
private final AtomicInteger E;
private final ChunkCoordIntPair F;
// CraftBukkit start - Neighbor loaded cache for chunk lighting and entity ticking
private int neighbors = 0x1 << 12;
public long chunkKey;
// Paper start
public final co.aikar.util.Counter<String> entityCounts = new co.aikar.util.Counter<>();
public final co.aikar.util.Counter<String> tileEntityCounts = new co.aikar.util.Counter<>();
private class TileEntityHashMap extends java.util.HashMap<BlockPosition, TileEntity> {
@Override
public TileEntity put(BlockPosition key, TileEntity value) {
TileEntity replaced = super.put(key, value);
if (replaced != null) {
replaced.setCurrentChunk(null);
tileEntityCounts.decrement(replaced.getMinecraftKeyString());
}
if (value != null) {
value.setCurrentChunk(Chunk.this);
tileEntityCounts.increment(value.getMinecraftKeyString());
}
return replaced;
}
@Override
public TileEntity remove(Object key) {
TileEntity removed = super.remove(key);
if (removed != null) {
removed.setCurrentChunk(null);
tileEntityCounts.decrement(removed.getMinecraftKeyString());
}
return removed;
}
}
final PaperLightingQueue.LightingQueue lightingQueue = new PaperLightingQueue.LightingQueue(this);
// 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
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, BiomeBase[] abiomebase, ChunkConverter chunkconverter, TickList<Block> ticklist, TickList<FluidType> ticklist1, long k) {
this.sections = new ChunkSection[16];
this.g = new boolean[256];
this.h = Maps.newHashMap();
this.heightMap = Maps.newEnumMap(HeightMap.Type.class);
this.tileEntities = new TileEntityHashMap(); // Paper
this.p = Maps.newHashMap();
this.q = Maps.newHashMap();
this.r = new ShortList[16];
this.A = 4096;
this.B = Queues.newConcurrentLinkedQueue();
this.C = ChunkStatus.EMPTY;
this.E = new AtomicInteger();
this.entitySlices = (List[]) (new List[16]); // Spigot
this.world = world;
this.locX = i;
this.locZ = j;
this.F = new ChunkCoordIntPair(i, j);
this.m = chunkconverter;
HeightMap.Type[] aheightmap_type = HeightMap.Type.values();
int l = aheightmap_type.length;
for (int i1 = 0; i1 < l; ++i1) {
HeightMap.Type heightmap_type = aheightmap_type[i1];
if (heightmap_type.c() == HeightMap.Use.LIVE_WORLD) {
this.heightMap.put(heightmap_type, new HeightMap(this, heightmap_type));
}
}
for (int j1 = 0; j1 < this.entitySlices.length; ++j1) {
this.entitySlices[j1] = new org.bukkit.craftbukkit.util.UnsafeList(); // Spigot
}
this.f = abiomebase;
this.s = ticklist;
this.t = ticklist1;
this.z = k;
// CraftBukkit start
this.bukkitChunk = new org.bukkit.craftbukkit.CraftChunk(this);
this.chunkKey = ChunkCoordIntPair.a(this.locX, this.locZ);
}
public org.bukkit.Chunk bukkitChunk;
public boolean mustSave;
private boolean needsDecoration;
// CraftBukkit end
public Chunk(World world, ProtoChunk protochunk, int i, int j) {
this(world, i, j, protochunk.getBiomeIndex(), protochunk.v(), protochunk.k(), protochunk.l(), protochunk.m());
int k;
for (k = 0; k < this.sections.length; ++k) {
this.sections[k] = protochunk.getSections()[k];
if (this.sections[k] != null) this.sections[k].disableLocks(); // Paper - Async Chunks - disable locks used during world gen
}
Iterator iterator = protochunk.s().iterator();
while (iterator.hasNext()) {
NBTTagCompound nbttagcompound = (NBTTagCompound) iterator.next();
ChunkRegionLoader.a(nbttagcompound, world, this);
}
iterator = protochunk.r().values().iterator();
while (iterator.hasNext()) {
TileEntity tileentity = (TileEntity) iterator.next();
this.a(tileentity);
}
this.h.putAll(protochunk.w());
for (k = 0; k < protochunk.u().length; ++k) {
this.r[k] = protochunk.u()[k];
}
this.a(protochunk.e());
this.b(protochunk.f());
iterator = protochunk.t().iterator();
while (iterator.hasNext()) {
HeightMap.Type heightmap_type = (HeightMap.Type) iterator.next();
if (heightmap_type.c() == HeightMap.Use.LIVE_WORLD) {
((HeightMap) this.heightMap.computeIfAbsent(heightmap_type, (heightmap_type1) -> {
return new HeightMap(this, heightmap_type1);
})).a(protochunk.b(heightmap_type).b());
}
}
this.x = true;
this.a(ChunkStatus.FULLCHUNK);
this.needsDecoration = true; // CraftBukkit
}
public Set<BlockPosition> t() {
Set<BlockPosition> set = Sets.newHashSet(this.h.keySet());
set.addAll(this.tileEntities.keySet());
return set;
}
public boolean a(int i, int j) {
return i == this.locX && j == this.locZ;
}
public ChunkSection[] getSections() {
return this.sections;
}
public void initLighting() {
int i = this.b();
this.y = Integer.MAX_VALUE;
Iterator iterator = this.heightMap.values().iterator();
while (iterator.hasNext()) {
HeightMap heightmap = (HeightMap) iterator.next();
heightmap.a();
}
for (int j = 0; j < 16; ++j) {
for (int k = 0; k < 16; ++k) {
if (this.world.worldProvider.g()) {
int 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)); // Akarin
}
}
--i1;
} while (i1 > 0 && l > 0);
}
}
}
this.x = true;
}
private void c(int i, int j) {
this.g[i + j * 16] = true;
this.l = true;
}
private void g(boolean flag) {
//this.world.methodProfiler.enter("recheckGaps"); // Akarin - remove caller
if (this.areNeighborsLoaded(1)) { // Paper
for (int i = 0; i < 16; ++i) {
for (int j = 0; j < 16; ++j) {
if (this.g[i + j * 16]) {
this.g[i + j * 16] = false;
int k = this.a(HeightMap.Type.LIGHT_BLOCKING, i, j);
int l = this.locX * 16 + i;
int i1 = this.locZ * 16 + j;
int j1 = Integer.MAX_VALUE;
EnumDirection enumdirection;
Iterator iterator;
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.c(l, i1, j1);
iterator = EnumDirection.EnumDirectionLimit.HORIZONTAL.iterator();
while (iterator.hasNext()) {
enumdirection = (EnumDirection) iterator.next();
this.c(l + enumdirection.getAdjacentX(), i1 + enumdirection.getAdjacentZ(), k);
}
if (flag) {
//this.world.methodProfiler.exit(); // Akarin - remove caller
return;
}
}
}
}
this.l = false;
}
//this.world.methodProfiler.exit(); // Akarin - remove caller
}
private void c(int i, int j, int k) {
int l = this.world.getHighestBlockYAt(HeightMap.Type.MOTION_BLOCKING, 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.areNeighborsLoaded(1)) { // Paper
for (int i1 = k; i1 < l; ++i1) {
this.world.updateBrightness(EnumSkyBlock.SKY, new BlockPosition(i, i1, j), this); // Paper
}
this.x = true;
}
}
private void a(int i, int j, int k, IBlockData iblockdata) {
HeightMap heightmap = (HeightMap) this.heightMap.get(HeightMap.Type.LIGHT_BLOCKING);
int l = heightmap.a(i & 15, k & 15) & 255;
if (heightmap.a(i, j, k, iblockdata)) {
int i1 = heightmap.a(i & 15, k & 15);
int j1 = this.locX * 16 + i;
int k1 = this.locZ * 16 + k;
this.world.a(j1, k1, i1, l);
int l1;
int i2;
int j2;
if (this.world.worldProvider.g()) {
l1 = Math.min(l, i1);
i2 = Math.max(l, i1);
j2 = i1 < l ? 15 : 0;
int k2;
for (k2 = l1; k2 < i2; ++k2) {
ChunkSection chunksection = this.sections[k2 >> 4];
if (chunksection != Chunk.a) {
chunksection.a(i, k2 & 15, k, j2);
//this.world.m(new BlockPosition((this.locX << 4) + i, k2, (this.locZ << 4) + k)); // Akarin
}
}
k2 = 15;
while (i1 > 0 && k2 > 0) {
--i1;
int l2 = this.d(i, i1, k);
l2 = l2 == 0 ? 1 : l2;
k2 -= l2;
k2 = Math.max(0, k2);
ChunkSection chunksection1 = this.sections[i1 >> 4];
if (chunksection1 != Chunk.a) {
chunksection1.a(i, i1 & 15, k, k2);
}
}
}
if (i1 < this.y) {
this.y = i1;
}
if (this.world.worldProvider.g()) {
l1 = heightmap.a(i & 15, k & 15);
i2 = Math.min(l, l1);
j2 = Math.max(l, l1);
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.x = true;
}
}
private int d(int i, int j, int k) {
return this.getBlockData(i, j, k).b(this.world, new BlockPosition(i, j, k));
}
// Paper start - Optimize getBlockData to reduce instructions
public final IBlockData getBlockData(BlockPosition pos) { return getBlockData(pos.getX(), pos.getY(), pos.getZ()); } // Paper
public final IBlockData getType(BlockPosition blockposition) {
return this.getBlockData(blockposition.getX(), blockposition.getY(), blockposition.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 getBlockData_unused(int i, int j, int k) {
// Paper end
if (this.world.S() == WorldType.DEBUG_ALL_BLOCK_STATES) {
IBlockData iblockdata = null;
if (j == 60) {
iblockdata = Blocks.BARRIER.getBlockData();
}
if (j == 70) {
iblockdata = ChunkProviderDebug.b(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", () -> {
return CrashReportSystemDetails.a(i, j, k);
});
throw new ReportedException(crashreport);
}
}
}
public Fluid getFluid(BlockPosition blockposition) {
return this.b(blockposition.getX(), blockposition.getY(), blockposition.getZ());
}
public Fluid b(int i, int j, int k) {
try {
if (j >= 0 && j >> 4 < this.sections.length) {
ChunkSection chunksection = this.sections[j >> 4];
if (chunksection != Chunk.a) {
return chunksection.b(i & 15, j & 15, k & 15);
}
}
return FluidTypes.EMPTY.i();
} catch (Throwable throwable) {
CrashReport crashreport = CrashReport.a(throwable, "Getting fluid state");
CrashReportSystemDetails crashreportsystemdetails = crashreport.a("Block being got");
crashreportsystemdetails.a("Location", () -> {
return CrashReportSystemDetails.a(i, j, k);
});
throw new ReportedException(crashreport);
}
}
// CraftBukkit start
@Nullable
public IBlockData setType(BlockPosition blockposition, IBlockData iblockdata, boolean flag) {
return this.setType(blockposition, iblockdata, flag, true);
}
@Nullable
public IBlockData setType(BlockPosition blockposition, IBlockData iblockdata, boolean flag, boolean doPlace) {
// CraftBukkit end
int i = blockposition.getX() & 15;
int j = blockposition.getY();
int k = blockposition.getZ() & 15;
int l = ((HeightMap) this.heightMap.get(HeightMap.Type.LIGHT_BLOCKING)).a(i, k);
IBlockData iblockdata1 = this.getType(blockposition);
if (iblockdata1 == iblockdata) {
return null;
} else {
Block block = iblockdata.getBlock();
Block block1 = iblockdata1.getBlock();
ChunkSection chunksection = this.sections[j >> 4];
boolean flag1 = false;
if (chunksection == Chunk.a) {
if (iblockdata.isAir()) {
return null;
}
chunksection = new ChunkSection(j >> 4 << 4, this.world.worldProvider.g(), this, this.world, true); // Paper - Anti-Xray
this.sections[j >> 4] = chunksection;
flag1 = j >= l;
}
chunksection.setType(i, j & 15, k, iblockdata);
((HeightMap) this.heightMap.get(HeightMap.Type.MOTION_BLOCKING)).a(i, j, k, iblockdata);
((HeightMap) this.heightMap.get(HeightMap.Type.MOTION_BLOCKING_NO_LEAVES)).a(i, j, k, iblockdata);
((HeightMap) this.heightMap.get(HeightMap.Type.OCEAN_FLOOR)).a(i, j, k, iblockdata);
((HeightMap) this.heightMap.get(HeightMap.Type.WORLD_SURFACE)).a(i, j, k, iblockdata);
if (!this.world.isClientSide) {
iblockdata1.remove(this.world, blockposition, iblockdata, flag);
} else if (block1 != block && block1 instanceof ITileEntity) {
this.world.n(blockposition);
}
if (false && chunksection.getType(i, j & 15, k).getBlock() != block) { // Paper - don't need to recheck this - this would only fail due to non main thread writes which are not supported
return null;
} else {
if (flag1) {
this.initLighting();
} else if (!world.paperConfig.optimizeLight || block != block1) { // Paper - Optimize light recalculations
this.runOrQueueLightUpdate(() -> { // Paper - Queue light update
int i1 = iblockdata.b(this.world, blockposition);
int j1 = iblockdata1.b(this.world, blockposition);
this.a(i, j, k, iblockdata);
if (i1 != j1 && (i1 < j1 || this.getBrightness(EnumSkyBlock.SKY, blockposition) > 0 || this.getBrightness(EnumSkyBlock.BLOCK, blockposition) > 0)) {
this.c(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 && doPlace && (!this.world.captureBlockStates || block instanceof BlockTileEntity)) {
iblockdata.onPlace(this.world, blockposition, iblockdata1);
}
if (block instanceof ITileEntity) {
tileentity = this.a(blockposition, Chunk.EnumTileEntityState.CHECK);
if (tileentity == null) {
tileentity = ((ITileEntity) block).a(this.world);
this.world.setTileEntity(blockposition, tileentity);
} else {
tileentity.invalidateBlockCache();
}
}
this.x = true;
return iblockdata1;
}
}
}
public int getBrightness(EnumSkyBlock enumskyblock, BlockPosition blockposition) {
return this.a(enumskyblock, blockposition, this.world.o().g());
}
public int a(EnumSkyBlock enumskyblock, BlockPosition blockposition, boolean flag) {
int i = blockposition.getX() & 15;
int j = blockposition.getY();
int k = blockposition.getZ() & 15;
int l = j >> 4;
if (l >= 0 && l <= this.sections.length - 1) {
ChunkSection chunksection = this.sections[l];
return chunksection == Chunk.a ? (this.c(blockposition) ? enumskyblock.c : 0) : (enumskyblock == EnumSkyBlock.SKY ? (!flag ? 0 : chunksection.c(i, j & 15, k)) : (enumskyblock == EnumSkyBlock.BLOCK ? chunksection.d(i, j & 15, k) : enumskyblock.c));
} else {
return (enumskyblock != EnumSkyBlock.SKY || !flag) && enumskyblock != EnumSkyBlock.BLOCK ? 0 : enumskyblock.c;
}
}
public void a(EnumSkyBlock enumskyblock, BlockPosition blockposition, int i) {
this.a(enumskyblock, this.world.o().g(), blockposition, i);
}
public void a(EnumSkyBlock enumskyblock, boolean flag, BlockPosition blockposition, int i) {
int j = blockposition.getX() & 15;
int k = blockposition.getY();
int l = blockposition.getZ() & 15;
int i1 = k >> 4;
if (i1 < 16 && i1 >= 0) {
ChunkSection chunksection = this.sections[i1];
if (chunksection == Chunk.a) {
if (i == enumskyblock.c) {
return;
}
chunksection = new ChunkSection(i1 << 4, flag, this, this.world, true); // Paper - Anti-Xray
this.sections[i1] = chunksection;
this.initLighting();
}
if (enumskyblock == EnumSkyBlock.SKY) {
if (this.world.worldProvider.g()) {
chunksection.a(j, k & 15, l, i);
}
} else if (enumskyblock == EnumSkyBlock.BLOCK) {
chunksection.b(j, k & 15, l, i);
}
this.x = true;
}
}
public final int getLightSubtracted(BlockPosition blockposition, int i) { return this.a(blockposition, i); } // Paper - OBFHELPER
public int a(BlockPosition blockposition, int i) {
return this.a(blockposition, i, this.world.o().g());
}
public int a(BlockPosition blockposition, int i, boolean flag) {
int j = blockposition.getX() & 15;
int k = blockposition.getY();
int l = blockposition.getZ() & 15;
int i1 = k >> 4;
if (i1 >= 0 && i1 <= this.sections.length - 1) {
ChunkSection chunksection = this.sections[i1];
if (chunksection == Chunk.a) {
return flag && i < EnumSkyBlock.SKY.c ? EnumSkyBlock.SKY.c - i : 0;
} else {
int j1 = flag ? chunksection.c(j, k & 15, l) : 0;
j1 -= i;
int k1 = chunksection.d(j, k & 15, l);
if (k1 > j1) {
j1 = k1;
}
return j1;
}
} else {
return 0;
}
}
public void a(Entity entity) {
this.v = 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.d.warn("Wrong location! ({}, {}) should be ({}, {}), {}", i, j, this.locX, this.locZ, entity);
entity.die();
return; // Paper
}
int k = MathHelper.floor(entity.locY / 16.0D);
if (k < 0) {
k = 0;
}
if (k >= this.entitySlices.length) {
k = this.entitySlices.length - 1;
}
entity.inChunk = true;
entity.chunkX = this.locX;
entity.chunkY = k;
entity.chunkZ = this.locZ;
// Paper start
List<Entity> entitySlice = this.entitySlices[k];
boolean inThis = entitySlice.contains(entity);
List<Entity> currentSlice = entity.entitySlice;
if (inThis || (currentSlice != null && currentSlice.contains(entity))) {
if (currentSlice == entitySlice || inThis) {
return;
} else {
Chunk chunk = entity.getCurrentChunk();
if (chunk != null) {
chunk.removeEntity(entity);
} else {
removeEntity(entity);
}
currentSlice.remove(entity); // Just incase the above did not remove from this target slice
}
}
entity.entitySlice = entitySlice;
entitySlice.add(entity);
this.markDirty();
if (entity instanceof EntityItem) {
itemCounts[k]++;
} else if (entity instanceof IInventory) {
inventoryEntityCounts[k]++;
}
entity.setCurrentChunk(this);
entityCounts.increment(entity.getMinecraftKeyString());
// Paper end
}
public void a(HeightMap.Type heightmap_type, long[] along) {
((HeightMap) this.heightMap.get(heightmap_type)).a(along);
}
public void removeEntity(Entity entity) { b(entity); } // Paper - OBFHELPER
public void b(Entity entity) {
this.a(entity, entity.chunkY);
}
public void a(Entity entity, int i) {
if (i < 0) {
i = 0;
}
if (i >= this.entitySlices.length) {
i = this.entitySlices.length - 1;
}
// Paper start
if (entity.entitySlice == null || !entity.entitySlice.contains(entity) || entitySlices[i] == entity.entitySlice) {
entity.entitySlice = null;
}
if (!this.entitySlices[i].remove(entity)) { return; }
this.markDirty();
if (entity instanceof EntityItem) {
itemCounts[i]--;
} else if (entity instanceof IInventory) {
inventoryEntityCounts[i]--;
}
entity.setCurrentChunk(null);
entityCounts.decrement(entity.getMinecraftKeyString());
// Paper end
}
public boolean c(BlockPosition blockposition) {
int i = blockposition.getX() & 15;
int j = blockposition.getY();
int k = blockposition.getZ() & 15;
return j >= ((HeightMap) this.heightMap.get(HeightMap.Type.LIGHT_BLOCKING)).a(i, k);
}
public int a(HeightMap.Type heightmap_type, int i, int j) {
return ((HeightMap) this.heightMap.get(heightmap_type)).a(i & 15, j & 15) - 1;
}
@Nullable
private TileEntity j(BlockPosition blockposition) {
IBlockData iblockdata = this.getType(blockposition);
Block block = iblockdata.getBlock();
return !block.isTileEntity() ? null : ((ITileEntity) block).a(this.world);
}
@Nullable
public TileEntity getTileEntity(BlockPosition blockposition) {
return this.a(blockposition, Chunk.EnumTileEntityState.CHECK);
}
@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 = world.capturedTileEntities.get(blockposition);
if (tileentity == null) {
tileentity = (TileEntity) this.tileEntities.get(blockposition);
}
// CraftBukkit end
if (tileentity == null) {
NBTTagCompound nbttagcompound = (NBTTagCompound) this.h.remove(blockposition);
if (nbttagcompound != null) {
TileEntity tileentity1 = this.a(blockposition, nbttagcompound);
if (tileentity1 != null) {
return tileentity1;
}
}
}
if (tileentity == null) {
if (chunk_enumtileentitystate == Chunk.EnumTileEntityState.IMMEDIATE) {
tileentity = this.j(blockposition);
this.world.setTileEntity(blockposition, tileentity);
} else if (chunk_enumtileentitystate == Chunk.EnumTileEntityState.QUEUED) {
this.B.add(blockposition);
}
} else if (tileentity.x()) {
this.tileEntities.remove(blockposition);
return null;
}
return tileentity;
}
public void a(TileEntity tileentity) {
this.a(tileentity.getPosition(), tileentity);
if (this.i) {
this.world.a(tileentity);
}
}
public void a(BlockPosition blockposition, TileEntity tileentity) {
tileentity.setWorld(this.world);
tileentity.setPosition(blockposition);
if (this.getType(blockposition).getBlock() instanceof ITileEntity) {
if (this.tileEntities.containsKey(blockposition)) {
((TileEntity) this.tileEntities.get(blockposition)).y();
}
tileentity.z();
this.tileEntities.put(blockposition.h(), tileentity);
// CraftBukkit start
// Paper start - Remove invalid mob spawner tile entities
} else if (tileentity instanceof TileEntityMobSpawner && !(getBlockData(blockposition.getX(), blockposition.getY(), blockposition.getZ()).getBlock() instanceof BlockMobSpawner)) {
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()
+ " (" + getBlockData(blockposition) + ") 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());
this.markDirty();
org.bukkit.Bukkit.getLogger().info("Removing corrupt tile entity");
}
// Paper end
// CraftBukkit end
}
}
public void a(NBTTagCompound nbttagcompound) {
this.h.put(new BlockPosition(nbttagcompound.getInt("x"), nbttagcompound.getInt("y"), nbttagcompound.getInt("z")), nbttagcompound);
}
public void removeTileEntity(BlockPosition blockposition) { this.d(blockposition); } // Paper - OBFHELPER
public void d(BlockPosition blockposition) {
if (this.i) {
TileEntity tileentity = (TileEntity) this.tileEntities.remove(blockposition);
if (tileentity != null) {
tileentity.y();
}
}
}
public void addEntities() {
this.i = true;
this.world.a(this.tileEntities.values());
List[] aentityslice = this.entitySlices; // Spigot
int i = aentityslice.length;
for (int j = 0; j < i; ++j) {
List<Entity> entityslice = aentityslice[j]; // Spigot
// Paper start
DuplicateUUIDMode mode = world.paperConfig.duplicateUUIDMode;
if (mode == DuplicateUUIDMode.WARN || mode == DuplicateUUIDMode.DELETE || mode == DuplicateUUIDMode.SAFE_REGEN) {
Map<UUID, Entity> thisChunk = new HashMap<>();
for (Iterator<Entity> iterator = ((List<Entity>) entityslice).iterator(); iterator.hasNext(); ) {
Entity entity = iterator.next();
if (entity.dead || entity.valid) continue;
Entity other = ((WorldServer) world).entitiesByUUID.get(entity.uniqueID);
if (other == null || other.dead || world.getEntityUnloadQueue().contains(other)) {
other = thisChunk.get(entity.uniqueID);
}
if (mode == DuplicateUUIDMode.SAFE_REGEN && other != null && !other.dead &&
!world.getEntityUnloadQueue().contains(other)
&& java.util.Objects.equals(other.getSaveID(), entity.getSaveID())
&& entity.getBukkitEntity().getLocation().distance(other.getBukkitEntity().getLocation()) < world.paperConfig.duplicateUUIDDeleteRange
) {
if (World.DEBUG_ENTITIES) logger.warn("[DUPE-UUID] Duplicate UUID found used by " + other + ", deleted entity " + entity + " because it was near the duplicate and likely an actual duplicate. See https://github.com/PaperMC/Paper/issues/1223 for discussion on what this is about.");
entity.die();
iterator.remove();
continue;
}
if (other != null && !other.dead) {
switch (mode) {
case SAFE_REGEN: {
entity.setUUID(UUID.randomUUID());
if (World.DEBUG_ENTITIES) logger.warn("[DUPE-UUID] Duplicate UUID found used by " + other + ", regenerated UUID for " + entity + ". See https://github.com/PaperMC/Paper/issues/1223 for discussion on what this is about.");
break;
}
case DELETE: {
if (World.DEBUG_ENTITIES) logger.warn("[DUPE-UUID] Duplicate UUID found used by " + other + ", deleted entity " + entity + ". See https://github.com/PaperMC/Paper/issues/1223 for discussion on what this is about.");
entity.die();
iterator.remove();
break;
}
default:
if (World.DEBUG_ENTITIES) logger.warn("[DUPE-UUID] Duplicate UUID found used by " + other + ", doing nothing to " + entity + ". See https://github.com/PaperMC/Paper/issues/1223 for discussion on what this is about.");
break;
}
}
thisChunk.put(entity.uniqueID, entity);
}
}
// Paper end
// CraftBukkit start
this.world.addChunkEntities(entityslice.stream() // Paper - add all at same time to avoid entities adding to world modifying slice state, skip already added entities (not normal, but can happen)
// Paper start - Inline event into stream
.filter((entity) -> {
if (!this.needsDecoration) {
return true;
}
return CraftEventFactory.doEntityAddEventCalling(this.world, entity, CreatureSpawnEvent.SpawnReason.CHUNK_GEN);
})
// Paper end - Inline event into stream
.filter((entity) -> !(entity instanceof EntityHuman || entity.valid))); // Paper - add all at same time to avoid entities adding to world modifying slice state, skip already added entities (not normal, but can happen)
// CraftBukkit end
}
// CraftBukkit start
org.bukkit.Server server = this.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(this.bukkitChunk, this.needsDecoration));
if (this.needsDecoration) {
this.world.timings.syncChunkLoadPopulateTimer.startTimingUnsafe(); // Paper
BlockSand.instaFall = true;
java.util.Random random = new java.util.Random();
random.setSeed(world.getSeed());
long xRand = random.nextLong() / 2L * 2L + 1L;
long zRand = random.nextLong() / 2L * 2L + 1L;
random.setSeed((long) locX * xRand + (long) 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;
server.getPluginManager().callEvent(new org.bukkit.event.world.ChunkPopulateEvent(bukkitChunk));
this.world.timings.syncChunkLoadPopulateTimer.stopTimingUnsafe(); // Paper
}
}
// CraftBukkit end
}
public void removeEntities() {
this.i = 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((List<org.bukkit.entity.HumanEntity>) ( (IInventory) tileentity ).getViewers() ) )
{
if ( h instanceof org.bukkit.craftbukkit.entity.CraftHumanEntity )
{
( (org.bukkit.craftbukkit.entity.CraftHumanEntity) h).getHandle().closeInventory(org.bukkit.event.inventory.InventoryCloseEvent.Reason.UNLOADED); // Paper
}
}
}
// 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( (List<org.bukkit.entity.HumanEntity>) ( (IInventory) entity ).getViewers() ) )
{
if ( h instanceof org.bukkit.craftbukkit.entity.CraftHumanEntity )
{
( (org.bukkit.craftbukkit.entity.CraftHumanEntity) h).getHandle().closeInventory(org.bukkit.event.inventory.InventoryCloseEvent.Reason.UNLOADED); // Paper
}
}
}
// Spigot End
entity.setCurrentChunk(null); // Paper
entity.entitySlice = null; // Paper
// 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.b((Collection) newList);
// CraftBukkit end
}
}
public void markDirty() {
this.x = true;
}
public void a(@Nullable Entity entity, AxisAlignedBB axisalignedbb, List<Entity> list, Predicate<? super Entity> predicate) {
int i = MathHelper.floor((axisalignedbb.minY - 2.0D) / 16.0D);
int j = MathHelper.floor((axisalignedbb.maxY + 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.isInventory() && 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.test(entity1)) {
list.add(entity1);
}
Entity[] aentity = entity1.bi();
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.test(entity2))) {
list.add(entity2);
}
}
}
}
}
}
}
}
public <T extends Entity> void a(Class<? extends T> oclass, AxisAlignedBB axisalignedbb, List<T> list, @Nullable Predicate<? super T> predicate) {
int i = MathHelper.floor((axisalignedbb.minY - 2.0D) / 16.0D);
int j = MathHelper.floor((axisalignedbb.maxY + 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()) {
T t0 = (T) iterator.next(); // CraftBukkit - decompile error
if (oclass.isInstance(t0) && t0.getBoundingBox().c(axisalignedbb) && (predicate == null || predicate.test(t0))) { // Spigot - instance check
list.add(t0);
}
}
}
}
public boolean c(boolean flag) {
if (flag) {
if (this.v && this.world.getTime() != this.lastSaved || this.x) {
return true;
}
}
// Paper start - Make world configurable and incremental
// This !flag section should say if isModified or hasEntities, then check auto save
return ((isModified() || hasEntities()) && this.world.getTime() >= this.lastSaved + world.paperConfig.autoSavePeriod);
// Paper end
}
public boolean isEmpty() {
return false;
}
public void d(boolean flag) {
if (this.l && this.world.worldProvider.g() && !flag) {
this.g(this.world.isClientSide);
}
this.u = true;
while (!this.B.isEmpty()) {
BlockPosition blockposition = (BlockPosition) this.B.poll();
if (this.a(blockposition, Chunk.EnumTileEntityState.CHECK) == null && this.getType(blockposition).getBlock().isTileEntity()) {
TileEntity tileentity = this.j(blockposition);
this.world.setTileEntity(blockposition, tileentity);
//this.world.a(blockposition, blockposition); // Akarin
}
}
}
public boolean isReady() {
return true; // Paper - Always send chunks
}
public boolean v() {
return this.u;
}
public ChunkCoordIntPair getPos() {
return this.F;
}
public boolean b(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.d.warn("Could not set level chunk sections, array length is {} instead of {}", achunksection.length, this.sections.length);
} else {
System.arraycopy(achunksection, 0, this.sections, 0, this.sections.length);
}
}
public BiomeBase getBiome(BlockPosition blockposition) {
int i = blockposition.getX() & 15;
int j = blockposition.getZ() & 15;
return this.f[j << 4 | i];
}
public BiomeBase[] getBiomeIndex() {
return this.f;
}
public void x() {
if (this.A < 4096) {
BlockPosition blockposition = new BlockPosition(this.locX << 4, 0, this.locZ << 4);
for (int i = 0; i < 8; ++i) {
if (this.A >= 4096) {
return;
}
int j = this.A % 16;
int k = this.A / 16 % 16;
int l = this.A / 256;
++this.A;
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).isAir()) {
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).e() > 0) {
this.world.r(blockposition2);
}
}
this.world.r(blockposition1);
}
}
}
}
}
public boolean y() {
return this.i;
}
public World getWorld() {
return this.world;
}
public Set<HeightMap.Type> A() {
return this.heightMap.keySet();
}
public HeightMap b(HeightMap.Type heightmap_type) {
return (HeightMap) this.heightMap.get(heightmap_type);
}
public Map<BlockPosition, TileEntity> getTileEntities() {
return this.tileEntities;
}
public List<Entity>[] getEntitySlices() { // Spigot
return this.entitySlices;
}
public NBTTagCompound g(BlockPosition blockposition) {
return (NBTTagCompound) this.h.get(blockposition);
}
public TickList<Block> k() {
return this.s;
}
public TickList<FluidType> l() {
return this.t;
}
public BitSet a(WorldGenStage.Features worldgenstage_features) {
throw new RuntimeException("Not yet implemented");
}
public void a(boolean flag) {
this.x = flag;
}
public void f(boolean flag) {
this.v = flag;
}
public void setLastSaved(long i) {
this.lastSaved = i;
}
@Nullable
public StructureStart a(String s) {
return (StructureStart) this.p.get(s);
}
public void a(String s, StructureStart structurestart) {
this.p.put(s, structurestart);
}
public Map<String, StructureStart> e() {
return this.p;
}
public void a(Map<String, StructureStart> map) {
this.p.clear();
this.p.putAll(map);
}
@Nullable
public LongSet b(String s) {
return (LongSet) this.q.computeIfAbsent(s, (s1) -> {
return new LongOpenHashSet();
});
}
public void a(String s, long i) {
((LongSet) this.q.computeIfAbsent(s, (s1) -> {
return new LongOpenHashSet();
})).add(i);
}
public Map<String, LongSet> f() {
return this.q;
}
public void b(Map<String, LongSet> map) {
this.q.clear();
this.q.putAll(map);
}
public int D() {
return this.y;
}
public long m() {
return world.paperConfig.fixedInhabitedTime < 0 ? this.z : world.paperConfig.fixedInhabitedTime; // Paper
}
public void b(long i) {
this.z = i;
}
public void E() {
if (!this.C.a(ChunkStatus.POSTPROCESSED) && this.D == 8) {
ChunkCoordIntPair chunkcoordintpair = this.getPos();
for (int i = 0; i < this.r.length; ++i) {
if (this.r[i] != null) {
ShortListIterator shortlistiterator = this.r[i].iterator();
while (shortlistiterator.hasNext()) {
Short oshort = (Short) shortlistiterator.next();
BlockPosition blockposition = ProtoChunk.a(oshort, i, chunkcoordintpair);
IBlockData iblockdata = this.world.getType(blockposition);
IBlockData iblockdata1 = Block.b(iblockdata, this.world, blockposition);
this.world.setTypeAndData(blockposition, iblockdata1, 20);
}
this.r[i].clear();
}
}
if (this.s instanceof ProtoChunkTickList) {
((ProtoChunkTickList<Block>) this.s).a(this.world.getBlockTickList(), (blockposition1) -> { // CraftBukkit - decompile error
return this.world.getType(blockposition1).getBlock();
});
}
if (this.t instanceof ProtoChunkTickList) {
((ProtoChunkTickList<FluidType>) this.t).a(this.world.getFluidTickList(), (blockposition1) -> { // CraftBukkit - decompile error
return this.world.getFluid(blockposition1).c();
});
}
Iterator iterator = (new HashSet(this.h.keySet())).iterator();
while (iterator.hasNext()) {
BlockPosition blockposition1 = (BlockPosition) iterator.next();
this.getTileEntity(blockposition1);
}
this.h.clear();
this.a(ChunkStatus.POSTPROCESSED);
this.m.a(this);
// Paper start - resend chunk after post process
PlayerChunk playerChunk = ((WorldServer) world).getPlayerChunkMap().getChunk(locX, locZ);
if (playerChunk != null) {
playerChunk.done = false;
playerChunk.sendAll();
}
// Paper end
}
}
@Nullable
private TileEntity a(BlockPosition blockposition, NBTTagCompound nbttagcompound) {
TileEntity tileentity;
if ("DUMMY".equals(nbttagcompound.getString("id"))) {
Block block = this.getType(blockposition).getBlock();
if (block instanceof ITileEntity) {
tileentity = ((ITileEntity) block).a(this.world);
} else {
tileentity = null;
Chunk.d.warn("Tried to load a DUMMY block entity @ {} but found not block entity block {} at location", blockposition, this.getType(blockposition));
}
} else {
tileentity = TileEntity.create(nbttagcompound);
}
if (tileentity != null) {
tileentity.setPosition(blockposition);
this.a(tileentity);
} else {
Chunk.d.warn("Tried to load a block entity for block {} but failed at location {}", this.getType(blockposition), blockposition);
}
return tileentity;
}
public ChunkConverter F() {
return this.m;
}
public ShortList[] G() {
return this.r;
}
public void a(short short0, int i) {
ProtoChunk.a(this.r, i).add(short0);
}
public ChunkStatus i() {
return this.C;
}
public void a(ChunkStatus chunkstatus) {
this.C = chunkstatus;
}
public void c(String s) {
this.a(ChunkStatus.a(s));
}
public void H() {
++this.D;
if (this.D > 8) {
throw new RuntimeException("Error while adding chunk to cache. Too many neighbors");
} else {
if (this.J()) {
((IAsyncTaskHandler) this.world).postToMainThread(this::E);
}
}
}
public void I() {
--this.D;
if (this.D < 0) {
throw new RuntimeException("Error while removing chunk from cache. Not enough neighbors");
}
}
public boolean J() {
return this.D == 8;
}
// Paper start
public void runOrQueueLightUpdate(Runnable runnable) {
if (this.world.paperConfig.queueLightUpdates) {
lightingQueue.add(runnable);
} else {
runnable.run();
}
}
// Paper end
public static enum EnumTileEntityState {
IMMEDIATE, QUEUED, CHECK;
private EnumTileEntityState() {}
}
}