/*
* 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 java.awt.*;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import static java.util.stream.Collectors.toList;
import static org.pepsoft.minecraft.Constants.*;
/**
* An "MCRegion" chunk.
*
* @author pepijn
*/
public final class ChunkImpl extends AbstractNBTItem implements Chunk {
public ChunkImpl(int xPos, int zPos, int maxHeight) {
super(new CompoundTag(TAG_LEVEL, new HashMap<>()));
this.xPos = xPos;
this.zPos = zPos;
this.maxHeight = maxHeight;
blocks = new byte[256 * maxHeight];
data = new byte[128 * maxHeight];
skyLight = new byte[128 * maxHeight];
blockLight = new byte[128 * maxHeight];
heightMap = new byte[256];
entities = new ArrayList<>();
tileEntities = new ArrayList<>();
readOnly = false;
}
public ChunkImpl(CompoundTag tag, int maxHeight) {
this(tag, maxHeight, false);
}
public ChunkImpl(CompoundTag tag, int maxHeight, boolean readOnly) {
super((CompoundTag) tag.getTag(TAG_LEVEL));
this.maxHeight = maxHeight;
this.readOnly = readOnly;
blocks = getByteArray(TAG_BLOCKS);
data = getByteArray(TAG_DATA);
skyLight = getByteArray(TAG_SKY_LIGHT);
blockLight = getByteArray(TAG_BLOCK_LIGHT);
heightMap = getByteArray(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);
}
@Override
public Tag toNBT() {
setByteArray(TAG_BLOCKS, blocks);
setByteArray(TAG_DATA, data);
setByteArray(TAG_SKY_LIGHT, skyLight);
setByteArray(TAG_BLOCK_LIGHT, blockLight);
setByteArray(TAG_HEIGHT_MAP, heightMap);
List<Tag> entityTags = new ArrayList<>(entities.size());
entityTags.addAll(entities.stream().map(Entity::toNBT).collect(toList()));
setList(TAG_ENTITIES, CompoundTag.class, entityTags);
List<Tag> tileEntityTags = new ArrayList<>(entities.size());
tileEntityTags.addAll(tileEntities.stream().map(TileEntity::toNBT).collect(toList()));
setList(TAG_TILE_ENTITIES, CompoundTag.class, tileEntityTags);
setLong(TAG_LAST_UPDATE, System.currentTimeMillis());
setInt(TAG_X_POS, xPos);
setInt(TAG_Z_POS, zPos);
setBoolean(TAG_TERRAIN_POPULATED, terrainPopulated);
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) {
return blocks[blockOffset(x, y, z)] & 0xFF;
}
@Override
public void setBlockType(int x, int y, int z, int blockType) {
if (readOnly) {
return;
}
blocks[blockOffset(x, y, z)] = (byte) blockType;
}
@Override
public int getDataValue(int x, int y, int z) {
return getDataByte(data, x, y, z);
}
@Override
public void setDataValue(int x, int y, int z, int dataValue) {
if (readOnly) {
return;
}
setDataByte(data, x, y, z, dataValue);
}
@Override
public int getSkyLightLevel(int x, int y, int z) {
return getDataByte(skyLight, x, y, z);
}
@Override
public void setSkyLightLevel(int x, int y, int z, int skyLightLevel) {
if (readOnly) {
return;
}
setDataByte(skyLight, x, y, z, skyLightLevel);
}
@Override
public int getBlockLightLevel(int x, int y, int z) {
return getDataByte(blockLight, x, y, z);
}
@Override
public void setBlockLightLevel(int x, int y, int z, int blockLightLevel) {
if (readOnly) {
return;
}
setDataByte(blockLight, x, y, z, blockLightLevel);
}
@Override
public boolean isBiomesAvailable() {
throw new UnsupportedOperationException("Not supported");
}
@Override
public int getBiome(int x, int z) {
throw new UnsupportedOperationException("Not supported");
}
@Override
public void setBiome(int x, int z, int biome) {
throw new UnsupportedOperationException("Not supported");
}
@Override
public int getHeight(int x, int z) {
return heightMap[x + z * 16] & 0xFF;
}
@Override
public void setHeight(int x, int z, int height) {
if (readOnly) {
return;
}
heightMap[x + z * 16] = (byte) (Math.min(height, 255));
}
@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) {
return Material.get(getBlockType(x, y, z), getDataValue(x, y, z));
}
@Override
public void setMaterial(int x, int y, int z, Material material) {
setBlockType(x, y, z, material.blockType);
setDataValue(x, y, z, material.data);
}
@Override
public boolean isReadOnly() {
return readOnly;
}
@Override
public boolean isLightPopulated() {
return false;
}
@Override
public void setLightPopulated(boolean lightPopulated) {
// Do nothing
}
@Override
public long getInhabitedTime() {
return 0;
}
@Override
public void setInhabitedTime(long inhabitedTime) {
// Do nothing
}
@Override
public int getHighestNonAirBlock(int x, int z) {
final int base = blockOffset(x, 0, z);
for (int i = blockOffset(x, maxHeight - 1, z); i >= base; i--) {
if (blocks[i] != 0) {
return i - base;
}
}
return -1;
}
@Override
public int getHighestNonAirBlock() {
for (int y = maxHeight - 1; y >= 0; y--) {
for (int i = 0; i < blocks.length; i += maxHeight) {
if (blocks[i | y] != 0) {
return y;
}
}
}
return -1;
}
@Override
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final ChunkImpl other = (ChunkImpl) obj;
if (this.xPos != other.xPos) {
return false;
}
if (this.zPos != other.zPos) {
return false;
}
return true;
}
@Override
public int hashCode() {
int hash = 7;
hash = 67 * hash + this.xPos;
hash = 67 * hash + this.zPos;
return hash;
}
/**
* @throws UnsupportedOperationException
*/
@Override
public ChunkImpl clone() {
throw new UnsupportedOperationException("ChunkImlp.clone() not supported");
}
private int getDataByte(byte[] array, int x, int y, int z) {
byte dataByte = array[blockOffset(x, y, z) / 2];
if (blockOffset(x, y, z) % 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 y + (z + x * 16) * maxHeight;
}
public final boolean readOnly;
final byte[] blocks;
final byte[] data;
final byte[] skyLight;
final byte[] blockLight;
final byte[] heightMap;
final int xPos, zPos;
boolean terrainPopulated;
final List<Entity> entities;
final List<TileEntity> tileEntities;
final int maxHeight;
private static final long serialVersionUID = 1L;
}