/*
* To change this template, choose Tools | Templates
* and open the template in the editor.
*/
package org.pepsoft.minecraft;
import org.jnbt.CompoundTag;
import org.jnbt.Tag;
import org.pepsoft.worldpainter.exporting.MinecraftWorld;
import java.awt.*;
import java.util.*;
import java.util.List;
import static java.util.stream.Collectors.toList;
import static org.pepsoft.minecraft.Constants.*;
/**
* An "Anvil" chunk.
*
* @author pepijn
*/
public final class ChunkImpl2 extends AbstractNBTItem implements Chunk, MinecraftWorld {
public ChunkImpl2(int xPos, int zPos, int maxHeight) {
super(new CompoundTag(TAG_LEVEL, new HashMap<>()));
this.xPos = xPos;
this.zPos = zPos;
this.maxHeight = maxHeight;
sections = new Section[maxHeight >> 4];
heightMap = new int[256];
entities = new ArrayList<>();
tileEntities = new ArrayList<>();
readOnly = false;
lightPopulated = true;
}
public ChunkImpl2(CompoundTag tag, int maxHeight) {
this(tag, maxHeight, false);
}
public ChunkImpl2(CompoundTag tag, int maxHeight, boolean readOnly) {
super((CompoundTag) tag.getTag(TAG_LEVEL));
this.maxHeight = maxHeight;
this.readOnly = readOnly;
sections = new Section[maxHeight >> 4];
List<CompoundTag> sectionTags = getList(TAG_SECTIONS);
for (CompoundTag sectionTag: sectionTags) {
Section section = new Section(sectionTag);
sections[section.level] = section;
}
// for (Section section: sections) {
// if (section != null) {
// for (byte skyLightLevelByte: section.skyLight) {
// int skyLightLevel = skyLightLevelByte & 0x0F;
// if ((skyLightLevel < 0) || (skyLightLevel > 15)) {
// throw new IllegalStateException("skyLightLevel " + skyLightLevel + " in section " + section.level);
// }
// skyLightLevel = (skyLightLevelByte >> 4) & 0x0F;
// if ((skyLightLevel < 0) || (skyLightLevel > 15)) {
// throw new IllegalStateException("skyLightLevel " + skyLightLevel + " in section " + section.level);
// }
// }
// }
// }
biomes = getByteArray(TAG_BIOMES);
heightMap = getIntArray(TAG_HEIGHT_MAP);
List<CompoundTag> entityTags = getList(TAG_ENTITIES);
entities = new ArrayList<>(entityTags.size());
entities.addAll(entityTags.stream().map(Entity::fromNBT).collect(toList()));
List<CompoundTag> tileEntityTags = getList(TAG_TILE_ENTITIES);
tileEntities = new ArrayList<>(tileEntityTags.size());
tileEntities.addAll(tileEntityTags.stream().map(TileEntity::fromNBT).collect(toList()));
// TODO: last update is ignored, is that correct?
xPos = getInt(TAG_X_POS);
zPos = getInt(TAG_Z_POS);
terrainPopulated = getBoolean(TAG_TERRAIN_POPULATED);
lightPopulated = getBoolean(TAG_LIGHT_POPULATED);
inhabitedTime = getLong(TAG_INHABITED_TIME);
}
public boolean isSectionPresent(int y) {
return sections[y] != null;
}
public Section[] getSections() {
return sections;
}
@Override
public Tag toNBT() {
List<Tag> sectionTags = new ArrayList<>(maxHeight >> 4);
for (Section section: sections) {
// if (section != null) {
// for (byte skyLightLevelByte: section.skyLight) {
// int skyLightLevel = skyLightLevelByte & 0x0F;
// if ((skyLightLevel < 0) || (skyLightLevel > 15)) {
// throw new IllegalStateException("skyLightLevel " + skyLightLevel + " in section " + section.level);
// }
// skyLightLevel = (skyLightLevelByte >> 4) & 0x0F;
// if ((skyLightLevel < 0) || (skyLightLevel > 15)) {
// throw new IllegalStateException("skyLightLevel " + skyLightLevel + " in section " + section.level);
// }
// }
// }
if ((section != null) && (! section.isEmpty())) {
sectionTags.add(section.toNBT());
}
}
setList(TAG_SECTIONS, CompoundTag.class, sectionTags);
if (biomes != null) {
setByteArray(TAG_BIOMES, biomes);
}
setIntArray(TAG_HEIGHT_MAP, heightMap);
List<Tag> entityTags = new ArrayList<>(entities.size());
entities.stream().map(Entity::toNBT).forEach(entityTags::add);
setList(TAG_ENTITIES, CompoundTag.class, entityTags);
List<Tag> tileEntityTags = new ArrayList<>(entities.size());
tileEntities.stream().map(TileEntity::toNBT).forEach(tileEntityTags::add);
setList(TAG_TILE_ENTITIES, CompoundTag.class, tileEntityTags);
setLong(TAG_LAST_UPDATE, System.currentTimeMillis()); // TODO: is this correct?
setInt(TAG_X_POS, xPos);
setInt(TAG_Z_POS, zPos);
setBoolean(TAG_TERRAIN_POPULATED, terrainPopulated);
setBoolean(TAG_LIGHT_POPULATED, lightPopulated);
if (inhabitedTime != 0) {
setLong(TAG_INHABITED_TIME, inhabitedTime);
}
return new CompoundTag("", Collections.singletonMap("", super.toNBT()));
}
@Override
public int getMaxHeight() {
return maxHeight;
}
@Override
public int getxPos() {
return xPos;
}
@Override
public int getzPos() {
return zPos;
}
@Override
public Point getCoords() {
return new Point(xPos, zPos);
}
@Override
public int getBlockType(int x, int y, int z) {
Section section = sections[y >> 4];
if (section == null) {
return 0;
} else {
if (section.add != null) {
return (section.blocks[blockOffset(x, y, z)] & 0xFF) | (getDataByte(section.add, x, y, z) << 8);
} else {
return section.blocks[blockOffset(x, y, z)] & 0xFF;
}
}
}
@Override
public void setBlockType(int x, int y, int z, int blockType) {
if (readOnly) {
return;
}
int level = y >> 4;
Section section = sections[level];
if (section == null) {
if (blockType == BLK_AIR) {
return;
}
section = new Section((byte) level);
sections[level] = section;
}
section.blocks[blockOffset(x, y, z)] = (byte) blockType;
if (blockType > 255) {
if (section.add == null) {
section.add = new byte[128 * 16];
}
setDataByte(section.add, x, y, z, blockType >> 8);
} else if (section.add != null) {
// An extended block might have been set earlier, so zero out the
// high portion
setDataByte(section.add, x, y, z, 0);
}
}
@Override
public int getDataValue(int x, int y, int z) {
int level = y >> 4;
if (sections[level] == null) {
return 0;
} else {
return getDataByte(sections[level].data, x, y, z);
}
}
@Override
public void setDataValue(int x, int y, int z, int dataValue) {
// if ((dataValue < 0) || (dataValue > 15)) {
// throw new IllegalArgumentException("dataValue " + dataValue);
// }
if (readOnly) {
return;
}
int level = y >> 4;
Section section = sections[level];
if (section == null) {
if (dataValue == 0) {
return;
}
section = new Section((byte) level);
sections[level] = section;
}
setDataByte(section.data, x, y, z, dataValue);
}
@Override
public int getSkyLightLevel(int x, int y, int z) {
int level = y >> 4;
if (sections[level] == null) {
return 15;
} else {
return getDataByte(sections[level].skyLight, x, y, z);
}
}
@Override
public void setSkyLightLevel(int x, int y, int z, int skyLightLevel) {
// if ((skyLightLevel < 0) || (skyLightLevel > 15)) {
// throw new IllegalArgumentException("skyLightLevel " + skyLightLevel);
// }
if (readOnly) {
return;
}
int level = y >> 4;
Section section = sections[level];
if (section == null) {
if (skyLightLevel == 15) {
return;
}
section = new Section((byte) level);
sections[level] = section;
}
setDataByte(section.skyLight, x, y, z, skyLightLevel);
}
@Override
public int getBlockLightLevel(int x, int y, int z) {
int level = y >> 4;
if (sections[level] == null) {
return 0;
} else {
return getDataByte(sections[level].blockLight, x, y, z);
}
}
@Override
public void setBlockLightLevel(int x, int y, int z, int blockLightLevel) {
// if ((blockLightLevel < 0) || (blockLightLevel > 15)) {
// throw new IllegalArgumentException("blockLightLevel " + blockLightLevel);
// }
if (readOnly) {
return;
}
int level = y >> 4;
Section section = sections[level];
if (section == null) {
if (blockLightLevel == 0) {
return;
}
section = new Section((byte) level);
sections[level] = section;
}
setDataByte(section.blockLight, x, y, z, blockLightLevel);
}
@Override
public int getHeight(int x, int z) {
return heightMap[x + z * 16];
}
@Override
public void setHeight(int x, int z, int height) {
if (readOnly) {
return;
}
heightMap[x + z * 16] = height;
}
@Override
public boolean isBiomesAvailable() {
return biomes != null;
}
@Override
public int getBiome(int x, int z) {
return biomes[x + z * 16] & 0xFF;
}
@Override
public void setBiome(int x, int z, int biome) {
if (readOnly) {
return;
}
if (biomes == null) {
biomes = new byte[256];
}
biomes[x + z * 16] = (byte) biome;
}
@Override
public boolean isTerrainPopulated() {
return terrainPopulated;
}
@Override
public void setTerrainPopulated(boolean terrainPopulated) {
if (readOnly) {
return;
}
this.terrainPopulated = terrainPopulated;
}
@Override
public List<Entity> getEntities() {
return entities;
}
@Override
public List<TileEntity> getTileEntities() {
return tileEntities;
}
@Override
public Material getMaterial(int x, int y, int z) {
Section section = sections[y >> 4];
if (section == null) {
return Material.AIR;
} else {
if (section.add != null) {
return Material.get((section.blocks[blockOffset(x, y, z)] & 0xFF) | (getDataByte(section.add, x, y, z) << 8), getDataByte(section.data, x, y, z));
} else {
return Material.get(section.blocks[blockOffset(x, y, z)] & 0xFF, getDataByte(section.data, x, y, z));
}
}
}
@Override
public void setMaterial(int x, int y, int z, Material material) {
if (readOnly) {
return;
}
int level = y >> 4;
Section section = sections[level];
if (section == null) {
if (material == Material.AIR) {
return;
}
section = new Section((byte) level);
sections[level] = section;
}
int blockType = material.blockType;
section.blocks[blockOffset(x, y, z)] = (byte) blockType;
if (blockType > 255) {
if (section.add == null) {
section.add = new byte[128 * 16];
}
setDataByte(section.add, x, y, z, blockType >> 8);
} else if (section.add != null) {
// An extended block might have been set earlier, so zero out the
// high portion
setDataByte(section.add, x, y, z, 0);
}
setDataByte(section.data, x, y, z, material.data);
}
@Override
public boolean isReadOnly() {
return readOnly;
}
@Override
public boolean isLightPopulated() {
return lightPopulated;
}
@Override
public void setLightPopulated(boolean lightPopulated) {
this.lightPopulated = lightPopulated;
}
@Override
public long getInhabitedTime() {
return inhabitedTime;
}
@Override
public void setInhabitedTime(long inhabitedTime) {
this.inhabitedTime = inhabitedTime;
}
@Override
public int getHighestNonAirBlock(int x, int z) {
for (int yy = sections.length - 1; yy >= 0; yy--) {
if (sections[yy] != null) {
final byte[] blocks = sections[yy].blocks;
final int base = blockOffset(x, 0, z);
for (int i = blockOffset(x, 15, z); i >= base; i -= 256) {
if (blocks[i] != 0) {
return (yy << 4) | ((i - base) >> 8);
}
}
}
}
return -1;
}
@Override
public int getHighestNonAirBlock() {
for (int yy = sections.length - 1; yy >= 0; yy--) {
if (sections[yy] != null) {
final byte[] blocks = sections[yy].blocks;
for (int i = blocks.length - 1; i >= 0; i--) {
if (blocks[i] != 0) {
return (yy << 4) | (i >> 8);
}
}
}
}
return -1;
}
// MinecraftWorld
@Override
public int getBlockTypeAt(int x, int y, int height) {
return getBlockType(x, height, y);
}
@Override
public int getDataAt(int x, int y, int height) {
return getDataValue(x, height, y);
}
@Override
public Material getMaterialAt(int x, int y, int height) {
return getMaterial(x, height, y);
}
@Override
public void setBlockTypeAt(int x, int y, int height, int blockType) {
setBlockType(x, height, y, blockType);
}
@Override
public void setDataAt(int x, int y, int height, int data) {
setDataValue(x, height, y, data);
}
@Override
public void setMaterialAt(int x, int y, int height, Material material) {
setMaterial(x, height, y, material);
}
@Override
public boolean isChunkPresent(int x, int y) {
return ((x == xPos) && (y == zPos));
}
@Override
public void addChunk(Chunk chunk) {
throw new UnsupportedOperationException();
}
@Override
public void addEntity(int x, int y, int height, Entity entity) {
entity = (Entity) entity.clone();
entity.setPos(new double[] {x, height, y});
getEntities().add(entity);
}
@Override
public void addEntity(double x, double y, double height, Entity entity) {
entity = (Entity) entity.clone();
entity.setPos(new double[] {x, height, y});
getEntities().add(entity);
}
@Override
public void addTileEntity(int x, int y, int height, TileEntity tileEntity) {
tileEntity = (TileEntity) tileEntity.clone();
tileEntity.setX(x);
tileEntity.setZ(y);
tileEntity.setY(height);
getTileEntities().add(tileEntity);
}
// ChunkProvider
@Override
public Chunk getChunk(int x, int z) {
if ((x == xPos) && (z == zPos)) {
return this;
} else {
return null;
}
}
@Override
public Chunk getChunkForEditing(int x, int z) {
return getChunk(x, z);
}
// Object
@Override
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final ChunkImpl2 other = (ChunkImpl2) obj;
if (this.xPos != other.xPos) {
return false;
}
if (this.zPos != other.zPos) {
return false;
}
return true;
}
@Override
public int hashCode() {
int hash = 3;
hash = 37 * hash + this.xPos;
hash = 37 * hash + this.zPos;
return hash;
}
/**
* @throws UnsupportedOperationException
*/
@Override
public ChunkImpl clone() {
throw new UnsupportedOperationException("ChunkImlp2.clone() not supported");
}
private int getDataByte(byte[] array, int x, int y, int z) {
int blockOffset = blockOffset(x, y, z);
byte dataByte = array[blockOffset / 2];
if (blockOffset % 2 == 0) {
// Even byte -> least significant bits
return dataByte & 0x0F;
} else {
// Odd byte -> most significant bits
return (dataByte & 0xF0) >> 4;
}
}
private void setDataByte(byte[] array, int x, int y, int z, int dataValue) {
int blockOffset = blockOffset(x, y, z);
int offset = blockOffset / 2;
byte dataByte = array[offset];
if (blockOffset % 2 == 0) {
// Even byte -> least significant bits
dataByte &= 0xF0;
dataByte |= (dataValue & 0x0F);
} else {
// Odd byte -> most significant bits
dataByte &= 0x0F;
dataByte |= ((dataValue & 0x0F) << 4);
}
array[offset] = dataByte;
}
private int blockOffset(int x, int y, int z) {
return x | ((z | ((y & 0xF) << 4)) << 4);
}
public final boolean readOnly;
final Section[] sections;
final int[] heightMap;
final int xPos, zPos;
byte[] biomes;
boolean terrainPopulated, lightPopulated;
final List<Entity> entities;
final List<TileEntity> tileEntities;
final int maxHeight;
long inhabitedTime;
private static final long serialVersionUID = 1L;
public static class Section extends AbstractNBTItem {
Section(CompoundTag tag) {
super(tag);
level = getByte(TAG_Y2);
blocks = getByteArray(TAG_BLOCKS);
if (containsTag(TAG_ADD)) {
add = getByteArray(TAG_ADD);
}
data = getByteArray(TAG_DATA);
skyLight = getByteArray(TAG_SKY_LIGHT);
blockLight = getByteArray(TAG_BLOCK_LIGHT);
}
Section(byte level) {
super(new CompoundTag("", new HashMap<>()));
this.level = level;
blocks = new byte[256 * 16];
data = new byte[128 * 16];
skyLight = new byte[128 * 16];
Arrays.fill(skyLight, (byte) 0xff);
blockLight = new byte[128 * 16];
}
@Override
public Tag toNBT() {
setByte(TAG_Y2, level);
setByteArray(TAG_BLOCKS, blocks);
if (add != null) {
for (byte b: add) {
if (b != 0) {
setByteArray(TAG_ADD, add);
break;
}
}
}
setByteArray(TAG_DATA, data);
setByteArray(TAG_SKY_LIGHT, skyLight);
setByteArray(TAG_BLOCK_LIGHT, blockLight);
return super.toNBT();
}
/**
* Indicates whether the section is empty, meaning all block ID's, data
* values and block light values are 0, and all sky light values are 15.
*
* @return <code>true</code> if the section is empty
*/
boolean isEmpty() {
for (byte b: blocks) {
if (b != (byte) 0) {
return false;
}
}
if (add != null) {
for (byte b: add) {
if (b != (byte) 0) {
return false;
}
}
}
for (byte b: skyLight) {
if (b != (byte) -1) {
return false;
}
}
for (byte b: blockLight) {
if (b != (byte) 0) {
return false;
}
}
for (byte b: data) {
if (b != (byte) 0) {
return false;
}
}
return true;
}
final byte level;
final byte[] blocks;
final byte[] data;
final byte[] skyLight;
final byte[] blockLight;
byte[] add;
private static final long serialVersionUID = 1L;
}
}