package com.boydti.fawe.jnbt.anvil; import com.boydti.fawe.FaweCache; import com.boydti.fawe.jnbt.NBTStreamer; import com.boydti.fawe.object.FaweChunk; import com.boydti.fawe.object.FaweQueue; import com.boydti.fawe.object.RunnableVal2; import com.boydti.fawe.object.io.FastByteArrayOutputStream; import com.boydti.fawe.object.number.MutableLong; import com.boydti.fawe.util.ArrayUtil; import com.boydti.fawe.util.MainUtil; import com.boydti.fawe.util.MathMan; import com.boydti.fawe.util.ReflectionUtils; import com.sk89q.jnbt.CompoundTag; import com.sk89q.jnbt.IntTag; import com.sk89q.jnbt.ListTag; import com.sk89q.jnbt.NBTConstants; import com.sk89q.jnbt.NBTInputStream; import com.sk89q.jnbt.NBTOutputStream; import com.sk89q.jnbt.Tag; import java.io.DataOutput; import java.io.DataOutputStream; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.Map; import java.util.Set; import java.util.UUID; public class MCAChunk extends FaweChunk { // ids: byte[16][4096] // data: byte[16][2048] // skylight: byte[16][2048] // blocklight: byte[16][2048] // entities: Map // tiles: List // biomes: byte[256] // compressedSize: int // modified: boolean // deleted: boolean public byte[][] ids; public byte[][] data; public byte[][] skyLight; public byte[][] blockLight; public byte[] biomes; public Map tiles = new HashMap<>(); public Map entities = new HashMap<>(); private long inhabitedTime; private long lastUpdate; private int[] heightMap; private int modified; private boolean deleted; public MCAChunk(FaweQueue queue, int x, int z) { super(queue, x, z); this.ids = new byte[16][]; this.data = new byte[16][]; this.skyLight = new byte[16][]; this.blockLight = new byte[16][]; this.biomes = new byte[256]; this.tiles = new HashMap<>(); this.entities = new HashMap<>(); this.lastUpdate = System.currentTimeMillis(); this.heightMap = new int[256]; this.setModified(); } public MCAChunk(MCAChunk parent, boolean shallow) { super(parent.getParent(), parent.getX(), parent.getZ()); if (shallow) { this.ids = parent.ids; this.data = parent.data; this.skyLight = parent.skyLight; this.blockLight = parent.blockLight; this.biomes = parent.biomes; this.tiles = parent.tiles; this.entities = parent.entities; this.inhabitedTime = parent.inhabitedTime; this.lastUpdate = parent.lastUpdate; this.heightMap = parent.heightMap; this.modified = parent.modified; this.deleted = parent.deleted; } else { this.ids = (byte[][]) MainUtil.copyNd(parent.ids); this.data = (byte[][]) MainUtil.copyNd(parent.data); this.skyLight = (byte[][]) MainUtil.copyNd(parent.skyLight); this.blockLight = (byte[][]) MainUtil.copyNd(parent.blockLight); this.biomes = parent.biomes.clone(); this.tiles = new HashMap<>(parent.tiles); this.entities = new HashMap<>(parent.entities); this.inhabitedTime = parent.inhabitedTime; this.lastUpdate = parent.lastUpdate; this.heightMap = parent.heightMap.clone(); this.modified = parent.modified; this.deleted = parent.deleted; } } public byte[] toBytes(byte[] buffer) throws IOException { if (buffer == null) { buffer = new byte[8192]; } FastByteArrayOutputStream buffered = new FastByteArrayOutputStream(buffer); DataOutputStream dataOut = new DataOutputStream(buffered); NBTOutputStream nbtOut = new NBTOutputStream((DataOutput) dataOut); nbtOut.writeNamedTagName("", NBTConstants.TYPE_COMPOUND); nbtOut.writeLazyCompoundTag("Level", new NBTOutputStream.LazyWrite() { @Override public void write(NBTOutputStream out) throws IOException { out.writeNamedTag("V", (byte) 1); out.writeNamedTag("xPos", getX()); out.writeNamedTag("zPos", getZ()); out.writeNamedTag("LightPopulated", (byte) 0); out.writeNamedTag("TerrainPopulated", (byte) 1); if (entities.isEmpty()) { out.writeNamedEmptyList("Entities"); } else { out.writeNamedTag("Entities", new ListTag(CompoundTag.class, new ArrayList(entities.values()))); } if (tiles.isEmpty()) { out.writeNamedEmptyList("TileEntities"); } else { out.writeNamedTag("TileEntities", new ListTag(CompoundTag.class, new ArrayList(tiles.values()))); } out.writeNamedTag("InhabitedTime", inhabitedTime); out.writeNamedTag("LastUpdate", lastUpdate); if (biomes != null) { out.writeNamedTag("Biomes", biomes); } out.writeNamedTag("HeightMap", heightMap); out.writeNamedTagName("Sections", NBTConstants.TYPE_LIST); dataOut.writeByte(NBTConstants.TYPE_COMPOUND); int len = 0; for (int layer = 0; layer < ids.length; layer++) { if (ids[layer] != null) len++; } dataOut.writeInt(len); for (int layer = 0; layer < ids.length; layer++) { byte[] idLayer = ids[layer]; if (idLayer == null) { continue; } out.writeNamedTag("Y", (byte) layer); out.writeNamedTag("BlockLight", blockLight[layer]); out.writeNamedTag("SkyLight", skyLight[layer]); out.writeNamedTag("Blocks", idLayer); out.writeNamedTag("Data", data[layer]); out.writeEndTag(); } } }); nbtOut.writeEndTag(); nbtOut.close(); return buffered.toByteArray(); } public long getInhabitedTime() { return inhabitedTime; } public long getLastUpdate() { return lastUpdate; } public void setInhabitedTime(long inhabitedTime) { this.inhabitedTime = inhabitedTime; } public void setLastUpdate(long lastUpdate) { this.lastUpdate = lastUpdate; } public void copyFrom(MCAChunk other, int minX, int maxX, int minY, int maxY, int minZ, int maxZ, int offsetX, int offsetY, int offsetZ) { minY = Math.max(-offsetY - minY, minY); maxY = Math.min(255 - offsetY, maxY); minZ = Math.max(-offsetZ - minZ, minZ); maxZ = Math.min(15 - offsetZ, maxZ); minX = Math.max(-offsetX - minX, minX); maxX = Math.min(15 - offsetX, maxX); if (minX > maxX || minZ > maxZ || minY > maxY) return; int startLayer = minY >> 4; int endLayer = maxY >> 4; for (int otherY = minY, thisY = minY + offsetY; otherY <= maxY; otherY++, thisY++) { int thisLayer = thisY >> 4; int otherLayer = otherY >> 4; byte[] thisIds = ids[thisLayer]; byte[] otherIds = other.ids[otherLayer]; if (otherIds == null) { if (thisIds != null) { int indexY = (thisY & 15) << 8; byte[] thisData = data[thisLayer]; byte[] thisSkyLight = skyLight[thisLayer]; byte[] thisBlockLight = blockLight[thisLayer]; for (int otherZ = minZ, thisZ = minZ + offsetZ; otherZ <= maxZ; otherZ++, thisZ++) { int startIndex = indexY + (thisZ << 4) + minX + offsetX; int endIndex = startIndex + maxX - minX; ArrayUtil.fill(thisIds, startIndex, endIndex + 1, (byte) 0); int startIndexShift = startIndex >> 1; int endIndexShift = endIndex >> 1; if ((startIndex & 1) != 0) { startIndexShift++; setNibble(startIndex, thisData, (byte) 0); setNibble(startIndex, thisSkyLight, (byte) 0); setNibble(startIndex, thisBlockLight, (byte) 0); } if ((endIndex & 1) != 1) { endIndexShift--; setNibble(endIndex, thisData, (byte) 0); setNibble(endIndex, thisSkyLight, (byte) 0); setNibble(endIndex, thisBlockLight, (byte) 0); } ArrayUtil.fill(thisData, startIndexShift, endIndexShift + 1, (byte) 0); ArrayUtil.fill(thisSkyLight, startIndexShift, endIndexShift + 1, (byte) 0); ArrayUtil.fill(thisBlockLight, startIndexShift, endIndexShift + 1, (byte) 0); } } continue; } else if (thisIds == null) { ids[thisLayer] = thisIds = new byte[4096]; data[thisLayer] = new byte[2048]; skyLight[thisLayer] = new byte[2048]; blockLight[thisLayer] = new byte[2048]; } int indexY = (thisY & 15) << 8; int otherIndexY = (otherY & 15) << 8; byte[] thisData = data[thisLayer]; byte[] thisSkyLight = skyLight[thisLayer]; byte[] thisBlockLight = blockLight[thisLayer]; byte[] otherData = other.data[otherLayer]; byte[] otherSkyLight = other.skyLight[otherLayer]; byte[] otherBlockLight = other.blockLight[otherLayer]; for (int otherZ = minZ, thisZ = minZ + offsetZ; otherZ <= maxZ; otherZ++, thisZ++) { int startIndex = indexY + (thisZ << 4) + minX + offsetX; int endIndex = startIndex + maxX - minX; int otherStartIndex = otherIndexY + (otherZ << 4) + minX; int otherEndIndex = otherStartIndex + maxX - minX; System.arraycopy(otherIds, otherStartIndex, thisIds, startIndex, endIndex - startIndex + 1); if ((startIndex & 1) == (otherStartIndex & 1)) { int startIndexShift = startIndex >> 1; int endIndexShift = endIndex >> 1; int otherStartIndexShift = otherStartIndex >> 1; int otherEndIndexShift = otherEndIndex >> 1; if ((startIndex & 1) != 0) { startIndexShift++; otherStartIndexShift++; setNibble(startIndex, thisData, getNibble(otherStartIndex, otherData)); setNibble(startIndex, thisSkyLight, getNibble(otherStartIndex, otherSkyLight)); setNibble(startIndex, thisBlockLight, getNibble(otherStartIndex, otherBlockLight)); } if ((endIndex & 1) != 1) { endIndexShift--; otherEndIndexShift--; setNibble(endIndex, thisData, getNibble(otherEndIndex, otherData)); setNibble(endIndex, thisSkyLight, getNibble(otherEndIndex, otherSkyLight)); setNibble(endIndex, thisBlockLight, getNibble(otherEndIndex, otherBlockLight)); } System.arraycopy(otherData, otherStartIndexShift, thisData, startIndexShift, endIndexShift - startIndexShift + 1); System.arraycopy(otherSkyLight, otherStartIndexShift, thisSkyLight, startIndexShift, endIndexShift - startIndexShift + 1); System.arraycopy(otherBlockLight, otherStartIndexShift, thisBlockLight, startIndexShift, endIndexShift - startIndexShift + 1); } else { for (int thisIndex = startIndex, otherIndex = otherStartIndex; thisIndex <= endIndex; thisIndex++, otherIndex++) { setNibble(thisIndex, thisData, getNibble(otherIndex, otherData)); setNibble(thisIndex, thisSkyLight, getNibble(otherIndex, otherSkyLight)); setNibble(thisIndex, thisBlockLight, getNibble(otherIndex, otherBlockLight)); } } } } if (!other.tiles.isEmpty()) { for (Map.Entry entry : other.tiles.entrySet()) { int key = entry.getKey(); int x = MathMan.untripleBlockCoordX(key); int y = MathMan.untripleBlockCoordY(key); int z = MathMan.untripleBlockCoordZ(key); if (x < minX || x > maxX) continue; if (z < minZ || z > maxZ) continue; if (y < minY || y > maxY) continue; x += offsetX; y += offsetY; z += offsetZ; short pair = MathMan.tripleBlockCoord(x, y, z); CompoundTag tag = entry.getValue(); Map map = ReflectionUtils.getMap(tag.getValue()); map.put("x", new IntTag(x + (getX() << 4))); map.put("y", new IntTag(y)); map.put("z", new IntTag(z + (getZ() << 4))); tiles.put(pair, tag); } } } public void copyFrom(MCAChunk other, int minY, int maxY, int offsetY) { minY = Math.max(-offsetY - minY, minY); maxY = Math.min(255 - offsetY, maxY); if (minY > maxY) return; if ((offsetY & 15) == 0) { int offsetLayer = offsetY >> 4; int startLayer = minY >> 4; int endLayer = maxY >> 4; for (int thisLayer = startLayer + offsetLayer, otherLayer = startLayer; thisLayer <= endLayer; thisLayer++, otherLayer++) { byte[] otherIds = other.ids[otherLayer]; byte[] currentIds = ids[thisLayer]; int by = otherLayer << 4; int ty = by + 15; if (by >= minY && ty <= maxY) { if (otherIds != null) { ids[thisLayer] = otherIds; data[thisLayer] = other.data[otherLayer]; skyLight[thisLayer] = other.skyLight[otherLayer]; blockLight[thisLayer] = other.blockLight[otherLayer]; } else { ids[thisLayer] = null; } } else { by = Math.max(by, minY) & 15; ty = Math.min(ty, maxY) & 15; int indexStart = by << 8; int indexEnd = 256 + (ty << 8); int indexStartShift = indexStart >> 1; int indexEndShift = indexEnd >> 1; if (otherIds == null) { if (currentIds != null) { ArrayUtil.fill(currentIds, indexStart, indexEnd, (byte) 0); ArrayUtil.fill(data[thisLayer], indexStartShift, indexEndShift, (byte) 0); ArrayUtil.fill(skyLight[thisLayer], indexStartShift, indexEndShift, (byte) 0); ArrayUtil.fill(blockLight[thisLayer], indexStartShift, indexEndShift, (byte) 0); } } else { if (currentIds == null) { currentIds = this.ids[thisLayer] = new byte[4096]; this.data[thisLayer] = new byte[2048]; this.skyLight[thisLayer] = new byte[2048]; this.blockLight[thisLayer] = new byte[2048]; } System.arraycopy(other.ids[otherLayer], indexStart, currentIds, indexStart, indexEnd - indexStart); System.arraycopy(other.data[otherLayer], indexStartShift, data[thisLayer], indexStartShift, indexEndShift - indexStartShift); System.arraycopy(other.skyLight[otherLayer], indexStartShift, skyLight[thisLayer], indexStartShift, indexEndShift - indexStartShift); System.arraycopy(other.blockLight[otherLayer], indexStartShift, blockLight[thisLayer], indexStartShift, indexEndShift - indexStartShift); } } } } else { for (int otherY = minY, thisY = minY + offsetY; otherY <= maxY; otherY++, thisY++) { int otherLayer = otherY >> 4; int thisLayer = thisY >> 4; byte[] thisIds = this.ids[thisLayer]; byte[] otherIds = other.ids[otherLayer]; int thisStartIndex = (thisY & 15) << 8; int thisStartIndexShift = thisStartIndex >> 1; if (otherIds == null) { if (thisIds == null) { continue; } ArrayUtil.fill(thisIds, thisStartIndex, thisStartIndex + 256, (byte) 0); ArrayUtil.fill(this.data[thisLayer], thisStartIndexShift, thisStartIndexShift + 128, (byte) 0); ArrayUtil.fill(this.skyLight[thisLayer], thisStartIndexShift, thisStartIndexShift + 128, (byte) 0); ArrayUtil.fill(this.blockLight[thisLayer], thisStartIndexShift, thisStartIndexShift + 128, (byte) 0); continue; } else if (thisIds == null) { ids[thisLayer] = thisIds = new byte[4096]; data[thisLayer] = new byte[2048]; skyLight[thisLayer] = new byte[2048]; blockLight[thisLayer] = new byte[2048]; } int otherStartIndex = (otherY & 15) << 8; int otherStartIndexShift = otherStartIndex >> 1; System.arraycopy(other.ids[otherLayer], otherStartIndex, thisIds, thisStartIndex, 256); System.arraycopy(other.data[otherLayer], otherStartIndexShift, data[thisLayer], thisStartIndexShift, 128); System.arraycopy(other.skyLight[otherLayer], otherStartIndexShift, skyLight[thisLayer], thisStartIndexShift, 128); System.arraycopy(other.blockLight[otherLayer], otherStartIndexShift, blockLight[thisLayer], thisStartIndexShift, 128); } } // Copy nbt int thisMinY = minY + offsetY; int thisMaxY = maxY + offsetY; if (!tiles.isEmpty()) { Iterator> iter = tiles.entrySet().iterator(); while (iter.hasNext()) { int y = MathMan.untripleBlockCoordY(iter.next().getKey()); if (y >= thisMinY && y <= thisMaxY) iter.remove(); } } if (!other.tiles.isEmpty()) { for (Map.Entry entry : other.tiles.entrySet()) { int key = entry.getKey(); int y = MathMan.untripleBlockCoordY(key); if (y >= minY && y <= maxY) { tiles.put((short) (key + offsetY), entry.getValue()); } } } if (!other.entities.isEmpty()) { for (Map.Entry entry : other.entities.entrySet()) { // TODO } } } public int getMinLayer() { for (int layer = 0; layer < ids.length; layer++) { if (ids[layer] != null) { return layer; } } return Integer.MAX_VALUE; } public int getMaxLayer() { for (int layer = ids.length - 1; layer >= 0; layer--) { if (ids[layer] != null) { return layer; } } return Integer.MIN_VALUE; } /** * Deprecated, use the toBytes method * * @return */ @Deprecated public CompoundTag toTag() { if (deleted) { return null; } // e.g. by precalculating the length HashMap level = new HashMap(); level.put("Entities", new ListTag(CompoundTag.class, new ArrayList(entities.values()))); level.put("TileEntities", new ListTag(CompoundTag.class, new ArrayList(tiles.values()))); level.put("InhabitedTime", inhabitedTime); level.put("LastUpdate", lastUpdate); level.put("LightPopulated", (byte) 0); level.put("TerrainPopulated", (byte) 1); level.put("V", (byte) 1); level.put("xPos", getX()); level.put("zPos", getZ()); if (biomes != null) { level.put("Biomes", biomes); } level.put("HeightMap", heightMap); ArrayList> sections = new ArrayList<>(); for (int layer = 0; layer < ids.length; layer++) { byte[] idLayer = ids[layer]; if (idLayer == null) { continue; } HashMap map = new HashMap(); map.put("Y", (byte) layer); map.put("BlockLight", blockLight[layer]); map.put("SkyLight", skyLight[layer]); map.put("Blocks", idLayer); map.put("Data", data[layer]); sections.add(map); } level.put("Sections", sections); HashMap root = new HashMap<>(); root.put("Level", level); return FaweCache.asTag(root); } public MCAChunk(NBTInputStream nis, FaweQueue parent, int x, int z, int compressedSize) throws IOException { super(parent, x, z); ids = new byte[16][]; data = new byte[16][]; skyLight = new byte[16][]; blockLight = new byte[16][]; NBTStreamer streamer = new NBTStreamer(nis); streamer.addReader(".Level.InhabitedTime", new RunnableVal2() { @Override public void run(Integer index, Long value) { inhabitedTime = value; } }); streamer.addReader(".Level.LastUpdate", new RunnableVal2() { @Override public void run(Integer index, Long value) { lastUpdate = value; } }); streamer.addReader(".Level.Sections.#", new RunnableVal2() { @Override public void run(Integer index, CompoundTag tag) { int layer = tag.getByte("Y"); ids[layer] = tag.getByteArray("Blocks"); data[layer] = tag.getByteArray("Data"); skyLight[layer] = tag.getByteArray("SkyLight"); blockLight[layer] = tag.getByteArray("BlockLight"); } }); streamer.addReader(".Level.TileEntities.#", new RunnableVal2() { @Override public void run(Integer index, CompoundTag tile) { int x = tile.getInt("x") & 15; int y = tile.getInt("y"); int z = tile.getInt("z") & 15; short pair = MathMan.tripleBlockCoord(x, y, z); tiles.put(pair, tile); } }); streamer.addReader(".Level.Entities.#", new RunnableVal2() { @Override public void run(Integer index, CompoundTag entityTag) { if (entities == null) { entities = new HashMap(); } long least = entityTag.getLong("UUIDLeast"); long most = entityTag.getLong("UUIDMost"); entities.put(new UUID(most, least), entityTag); } }); streamer.addReader(".Level.Biomes", new RunnableVal2() { @Override public void run(Integer index, byte[] value) { biomes = value; } }); streamer.addReader(".Level.HeightMap", new RunnableVal2() { @Override public void run(Integer index, int[] value) { heightMap = value; } }); streamer.readFully(); } public long filterBlocks(MutableMCABackedBaseBlock mutableBlock, MCAFilter filter) { MutableLong result = new MutableLong(); mutableBlock.setChunk(this); int bx = getX() << 4; int bz = getZ() << 4; int tx = bx + 15; int tz = bz + 15; for (int layer = 0; layer < ids.length; layer++) { if (doesSectionExist(layer)) { mutableBlock.setArrays(layer); int yStart = layer << 4; int yEnd = yStart + 15; for (int y = yStart, y0 = (yStart & 15); y <= yEnd; y++, y0++) { int yIndex = ((y0) << 8); mutableBlock.setY(y); for (int z = bz, z0 = bz & 15; z <= tz; z++, z0++) { int zIndex = yIndex + ((z0) << 4); mutableBlock.setZ(z); for (int x = bx, x0 = bx & 15; x <= tx; x++, x0++) { int xIndex = zIndex + x0; mutableBlock.setX(x); mutableBlock.setIndex(xIndex); filter.applyBlock(x, y, z, mutableBlock, result); } } } } } return result.get(); } public int[] getHeightMapArray() { return heightMap; } public void setDeleted(boolean deleted) { setModified(); this.deleted = deleted; } public boolean isDeleted() { return deleted; } public boolean isModified() { return modified != 0; } public int getModified() { return modified; } @Deprecated public final void setModified() { this.modified++; } @Override public int getBitMask() { int bitMask = 0; for (int section = 0; section < ids.length; section++) { if (ids[section] != null) { bitMask += 1 << section; } } return bitMask; } @Override public void setTile(int x, int y, int z, CompoundTag tile) { setModified(); short pair = MathMan.tripleBlockCoord(x, y, z); if (tile != null) { tiles.put(pair, tile); } else { tiles.remove(pair); } } @Override public void setEntity(CompoundTag entityTag) { setModified(); long least = entityTag.getLong("UUIDLeast"); long most = entityTag.getLong("UUIDMost"); entities.put(new UUID(most, least), entityTag); } @Override public void setBiome(int x, int z, byte biome) { setModified(); biomes[x + (z << 4)] = biome; } @Override public Set getEntities() { return new HashSet<>(entities.values()); } @Override public Map getTiles() { return tiles == null ? new HashMap() : tiles; } @Override public CompoundTag getTile(int x, int y, int z) { if (tiles == null || tiles.isEmpty()) { return null; } short pair = MathMan.tripleBlockCoord(x, y, z); return tiles.get(pair); } public boolean doesSectionExist(int cy) { return ids[cy] != null; } @Override public FaweChunk copy(boolean shallow) { return new MCAChunk(this, shallow); } @Override public int getBlockCombinedId(int x, int y, int z) { int layer = y >> 4; byte[] idLayer = ids[layer]; if (idLayer == null) { return 0; } int j = FaweCache.CACHE_J[y][z & 15][x & 15]; int id = idLayer[j] & 0xFF; if (FaweCache.hasData(id)) { byte[] dataLayer = data[layer]; if (dataLayer != null) { return (id << 4) + getNibble(j, dataLayer); } } return id << 4; } @Override public byte[] getBiomeArray() { return this.biomes; } @Override public Set getEntityRemoves() { return new HashSet<>(); } public void setSkyLight(int x, int y, int z, int value) { setModified(); int layer = y >> 4; byte[] skyLayer = skyLight[layer]; if (skyLayer == null) { return; } int index = FaweCache.CACHE_J[y][z & 15][x & 15]; setNibble(index, skyLayer, value); } public void setBlockLight(int x, int y, int z, int value) { setModified(); int layer = y >> 4; byte[] blockLayer = blockLight[layer]; if (blockLayer == null) { return; } int index = FaweCache.CACHE_J[y][z & 15][x & 15]; setNibble(index, blockLayer, value); } public int getSkyLight(int x, int y, int z) { int layer = y >> 4; byte[] skyLayer = skyLight[layer]; if (skyLayer == null) { return 0; } int index = FaweCache.CACHE_J[y][z & 15][x & 15]; return getNibble(index, skyLayer); } public int getBlockLight(int x, int y, int z) { int layer = y >> 4; byte[] blockLayer = blockLight[layer]; if (blockLayer == null) { return 0; } int index = FaweCache.CACHE_J[y][z & 15][x & 15]; return getNibble(index, blockLayer); } public void setFullbright() { setModified(); for (byte[] array : skyLight) { if (array != null) { Arrays.fill(array, (byte) 255); } } } public void removeLight() { for (int i = 0; i < skyLight.length; i++) { removeLight(i); } } public void removeLight(int i) { byte[] array1 = skyLight[i]; if (array1 == null) { return; } byte[] array2 = blockLight[i]; Arrays.fill(array1, (byte) 0); Arrays.fill(array2, (byte) 0); } public int getNibble(int index, byte[] array) { int indexShift = index >> 1; if ((index & 1) == 0) { return array[indexShift] & 15; } else { return array[indexShift] >> 4 & 15; } } public void setNibble(int index, byte[] array, int value) { int indexShift = index >> 1; byte existing = array[indexShift]; int valueShift = value << 4; if (existing == value + valueShift) { return; } if ((index & 1) == 0) { array[indexShift] = (byte) (existing & 240 | value); } else { array[indexShift] = (byte) (existing & 15 | valueShift); } } public void setIdUnsafe(byte[] idsLayer, int index, byte id) { idsLayer[index] = id; } public void setBlockUnsafe(byte[] idsLayer, byte[] dataLayer, int index, byte id, int data) { idsLayer[index] = id; setNibble(index, dataLayer, data); } @Override public void setBlock(int x, int y, int z, int id, int data) { setModified(); int layer = y >> 4; byte[] idsLayer = ids[layer]; if (idsLayer == null) { idsLayer = this.ids[layer] = new byte[4096]; this.data[layer] = new byte[2048]; this.skyLight[layer] = new byte[2048]; this.blockLight[layer] = new byte[2048]; } int j = FaweCache.CACHE_J[y][z & 15][x & 15]; idsLayer[j] = (byte) id; byte[] dataLayer = this.data[layer]; setNibble(j, dataLayer, data); } @Override public void setBiome(byte biome) { Arrays.fill(biomes, biome); } @Override public void removeEntity(UUID uuid) { setModified(); entities.remove(uuid); } private final boolean idsEqual(byte[] a, byte[] b) { // Assumes both are null, or none are (idsEqual - 2d array) // Assumes length is 4096 if (a == b) return true; for (char i = 0; i < 4096; i++) { if (a[i] != b[i]) return false; } return true; } private final boolean idsEqual(byte[][] a, byte[][] b, boolean matchNullToAir) { // Assumes length is 16 for (byte i = 0; i < 16; i++) { if ((a[i] == null) != (b[i] == null)) { if (matchNullToAir) { if (b[i] != null) { for (byte c : b[i]) { if (c != 0) return false; } } else if (a[i] != null) { for (byte c : a[i]) { if (c != 0) return false; } } } return false; } } // Check the chunks close to the ground first for (byte i = 4; i < 8; i++) { if (!idsEqual(a[i], b[i])) return false; } for (byte i = 3; i >= 0; i--) { if (!idsEqual(a[i], b[i])) return false; } for (byte i = 8; i < 16; i++) { if (!idsEqual(a[i], b[i])) return false; } return true; } /** * Check if the ids match the ids in the other chunk * @param other * @param matchNullToAir * @return */ public boolean idsEqual(MCAChunk other, boolean matchNullToAir) { return idsEqual(other.ids, this.ids, matchNullToAir); } @Override public Void getChunk() { throw new UnsupportedOperationException("Not applicable for this"); } @Override public FaweChunk call() { throw new UnsupportedOperationException("Not supported"); } }