FastAsyncWorldedit/core/src/main/java/com/boydti/fawe/jnbt/anvil/MCAChunk.java

870 lines
34 KiB
Java

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<Void> {
// ids: byte[16][4096]
// data: byte[16][2048]
// skylight: byte[16][2048]
// blocklight: byte[16][2048]
// entities: Map<Short, CompoundTag>
// tiles: List<CompoundTag>
// 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<Short, CompoundTag> tiles = new HashMap<>();
public Map<UUID, CompoundTag> 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<CompoundTag>(entities.values())));
}
if (tiles.isEmpty()) {
out.writeNamedEmptyList("TileEntities");
} else {
out.writeNamedTag("TileEntities", new ListTag(CompoundTag.class, new ArrayList<CompoundTag>(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<Short, CompoundTag> 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<String, Tag> 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<Map.Entry<Short, CompoundTag>> 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<Short, CompoundTag> 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<UUID, CompoundTag> 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<String, Object> level = new HashMap<String, Object>();
level.put("Entities", new ListTag(CompoundTag.class, new ArrayList<CompoundTag>(entities.values())));
level.put("TileEntities", new ListTag(CompoundTag.class, new ArrayList<CompoundTag>(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<HashMap<String, Object>> sections = new ArrayList<>();
for (int layer = 0; layer < ids.length; layer++) {
byte[] idLayer = ids[layer];
if (idLayer == null) {
continue;
}
HashMap<String, Object> map = new HashMap<String, Object>();
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<String, Object> 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<Integer, Long>() {
@Override
public void run(Integer index, Long value) {
inhabitedTime = value;
}
});
streamer.addReader(".Level.LastUpdate", new RunnableVal2<Integer, Long>() {
@Override
public void run(Integer index, Long value) {
lastUpdate = value;
}
});
streamer.addReader(".Level.Sections.#", new RunnableVal2<Integer, CompoundTag>() {
@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<Integer, CompoundTag>() {
@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<Integer, CompoundTag>() {
@Override
public void run(Integer index, CompoundTag entityTag) {
if (entities == null) {
entities = new HashMap<UUID, CompoundTag>();
}
long least = entityTag.getLong("UUIDLeast");
long most = entityTag.getLong("UUIDMost");
entities.put(new UUID(most, least), entityTag);
}
});
streamer.addReader(".Level.Biomes", new RunnableVal2<Integer, byte[]>() {
@Override
public void run(Integer index, byte[] value) {
biomes = value;
}
});
streamer.addReader(".Level.HeightMap", new RunnableVal2<Integer, int[]>() {
@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<CompoundTag> getEntities() {
return new HashSet<>(entities.values());
}
@Override
public Map<Short, CompoundTag> getTiles() {
return tiles == null ? new HashMap<Short, CompoundTag>() : 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<Void> 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<UUID> 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");
}
}