Archived
0

Merge branch 'world-loader-anvil' into proto_1.12.2

This commit is contained in:
2019-01-29 11:43:51 +03:00
51 changed files with 2811 additions and 287 deletions

View File

@@ -0,0 +1,73 @@
package com.flowpowered.nbt;
import java.util.Arrays;
public class LongArrayTag extends Tag<long[]> {
/**
* The value.
*/
private final long[] value;
/**
* Creates the tag.
*
* @param name The name.
* @param value The value.
*/
public LongArrayTag(String name, long[] value) {
super(TagType.TAG_LONG_ARRAY, name);
this.value = value;
}
@Override
public long[] getValue() {
return value;
}
@Override
public String toString() {
StringBuilder hex = new StringBuilder();
for (long s : value) {
String hexDigits = Long.toHexString(s).toUpperCase();
if (hexDigits.length() == 1) {
hex.append("0");
}
hex.append(hexDigits).append(" ");
}
String name = getName();
String append = "";
if (name != null && !name.equals("")) {
append = "(\"" + this.getName() + "\")";
}
return "TAG_Long_Array" + append + ": " + hex.toString();
}
@Override
public LongArrayTag clone() {
long[] clonedArray = cloneArray(value);
return new LongArrayTag(getName(), clonedArray);
}
@Override
public boolean equals(Object other) {
if (!(other instanceof IntArrayTag)) {
return false;
}
LongArrayTag tag = (LongArrayTag) other;
return Arrays.equals(value, tag.value) && getName().equals(tag.getName());
}
private long[] cloneArray(long[] longArray) {
if (longArray == null) {
return null;
} else {
int length = longArray.length;
byte[] newArray = new byte[length];
System.arraycopy(longArray, 0, newArray, 0, length);
return longArray;
}
}
}

View File

@@ -0,0 +1,38 @@
package com.flowpowered.nbt;
import java.nio.charset.Charset;
/**
* A class which holds constant values.
*/
public final class NBTConstants {
/**
* The character set used by NBT (UTF-8).
*/
public static final Charset CHARSET = Charset.forName("UTF-8");
/**
* Tag type constants.
*/
@Deprecated
public static final int TYPE_END = TagType.TAG_END.getId(),
TYPE_BYTE = TagType.TAG_BYTE.getId(),
TYPE_SHORT = TagType.TAG_SHORT.getId(),
TYPE_INT = TagType.TAG_INT.getId(),
TYPE_LONG = TagType.TAG_LONG.getId(),
TYPE_FLOAT = TagType.TAG_FLOAT.getId(),
TYPE_DOUBLE = TagType.TAG_DOUBLE.getId(),
TYPE_BYTE_ARRAY = TagType.TAG_BYTE_ARRAY.getId(),
TYPE_STRING = TagType.TAG_STRING.getId(),
TYPE_LIST = TagType.TAG_LIST.getId(),
TYPE_COMPOUND = TagType.TAG_COMPOUND.getId(),
TYPE_INT_ARRAY = TagType.TAG_INT_ARRAY.getId(),
TYPE_SHORT_ARRAY = TagType.TAG_SHORT_ARRAY.getId(),
TYPE_LONG_ARRAY = TagType.TAG_LONG_ARRAY.getId();
/**
* Default private constructor.
*/
private NBTConstants() {
}
}

View File

@@ -0,0 +1,94 @@
package com.flowpowered.nbt;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public enum TagType {
TAG_END(EndTag.class, "TAG_End", 0),
TAG_BYTE(ByteTag.class, "TAG_Byte", 1),
TAG_SHORT(ShortTag.class, "TAG_Short", 2),
TAG_INT(IntTag.class, "TAG_Int", 3),
TAG_LONG(LongTag.class, "TAG_Long", 4),
TAG_FLOAT(FloatTag.class, "TAG_Float", 5),
TAG_DOUBLE(DoubleTag.class, "TAG_Double", 6),
TAG_BYTE_ARRAY(ByteArrayTag.class, "TAG_Byte_Array", 7),
TAG_STRING(StringTag.class, "TAG_String", 8),
@SuppressWarnings("unchecked")
TAG_LIST((Class) ListTag.class, "TAG_List", 9),
// Java generics, y u so suck
TAG_COMPOUND(CompoundTag.class, "TAG_Compound", 10),
TAG_INT_ARRAY(IntArrayTag.class, "TAG_Int_Array", 11),
TAG_LONG_ARRAY(LongArrayTag.class, "TAG_Long_Array", 12),
TAG_SHORT_ARRAY(ShortArrayTag.class, "TAG_Short_Array", 100),;
private static final Map<Class<? extends Tag<?>>, TagType> BY_CLASS = new HashMap<Class<? extends Tag<?>>, TagType>();
private static final Map<String, TagType> BY_NAME = new HashMap<String, TagType>();
private static final TagType[] BY_ID;
static {
BY_ID = new TagType[BaseData.maxId + 1];
for (TagType type : TagType.values()) {
BY_CLASS.put(type.getTagClass(), type);
BY_NAME.put(type.getTypeName(), type);
BY_ID[type.getId()] = type;
}
}
private final Class<? extends Tag<?>> tagClass;
private final String typeName;
private final int id;
private TagType(Class<? extends Tag<?>> tagClass, String typeName, int id) {
this.tagClass = tagClass;
this.typeName = typeName;
this.id = id;
// Such a hack, shame that Java makes this such a pain
if (this.id > BaseData.maxId) {
BaseData.maxId = this.id;
}
}
public Class<? extends Tag<?>> getTagClass() {
return tagClass;
}
public String getTypeName() {
return typeName;
}
public int getId() {
return id;
}
public static TagType getByTagClass(Class<? extends Tag<?>> clazz) {
TagType ret = BY_CLASS.get(clazz);
if (ret == null) {
throw new IllegalArgumentException("Tag type " + clazz + " is unknown!");
}
return ret;
}
public static TagType getByTypeName(String typeName) {
TagType ret = BY_NAME.get(typeName);
if (ret == null) {
throw new IllegalArgumentException("Tag type " + typeName + " is unknown!");
}
return ret;
}
public static TagType getById(int id) {
if (id >= 0 && id < BY_ID.length) {
TagType ret = BY_ID[id];
if (ret == null) {
throw new IllegalArgumentException("Tag type id " + id + " is unknown!");
}
return ret;
} else {
throw new IndexOutOfBoundsException("Tag type id " + id + " is out of bounds!");
}
}
private static class BaseData {
private static int maxId = 0;
}
}

View File

@@ -0,0 +1,210 @@
package com.flowpowered.nbt.stream;
import java.io.Closeable;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteOrder;
import java.util.ArrayList;
import java.util.List;
import java.util.zip.GZIPInputStream;
import com.flowpowered.nbt.*;
/**
* This class reads NBT, or Named Binary Tag streams, and produces an object graph of subclasses of the {@link Tag} object. <p /> The NBT format was created by Markus Persson, and the specification
* may be found at <a href="https://flowpowered.com/nbt/spec.txt"> https://flowpowered.com/nbt/spec.txt</a>.
*/
public final class NBTInputStream implements Closeable {
/**
* The data input stream.
*/
private final EndianSwitchableInputStream is;
/**
* Creates a new {@link NBTInputStream}, which will source its data from the specified input stream. This assumes the stream is compressed.
*
* @param is The input stream.
* @throws java.io.IOException if an I/O error occurs.
*/
public NBTInputStream(InputStream is) throws IOException {
this(is, true, ByteOrder.BIG_ENDIAN);
}
/**
* Creates a new {@link NBTInputStream}, which sources its data from the specified input stream. A flag must be passed which indicates if the stream is compressed with GZIP or not. This assumes the
* stream uses big endian encoding.
*
* @param is The input stream.
* @param compressed A flag indicating if the stream is compressed.
* @throws java.io.IOException if an I/O error occurs.
*/
public NBTInputStream(InputStream is, boolean compressed) throws IOException {
this(is, compressed, ByteOrder.BIG_ENDIAN);
}
/**
* Creates a new {@link NBTInputStream}, which sources its data from the specified input stream. A flag must be passed which indicates if the stream is compressed with GZIP or not.
*
* @param is The input stream.
* @param compressed A flag indicating if the stream is compressed.
* @param endianness Whether to read numbers from the InputStream with little endian encoding.
* @throws java.io.IOException if an I/O error occurs.
*/
public NBTInputStream(InputStream is, boolean compressed, ByteOrder endianness) throws IOException {
this.is = new EndianSwitchableInputStream(compressed ? new GZIPInputStream(is) : is, endianness);
}
/**
* Reads an NBT {@link Tag} from the stream.
*
* @return The tag that was read.
* @throws java.io.IOException if an I/O error occurs.
*/
public Tag readTag() throws IOException {
return readTag(0);
}
/**
* Reads an NBT {@link Tag} from the stream.
*
* @param depth The depth of this tag.
* @return The tag that was read.
* @throws java.io.IOException if an I/O error occurs.
*/
private Tag readTag(int depth) throws IOException {
int typeId = is.readByte() & 0xFF;
TagType type = TagType.getById(typeId);
String name;
if (type != TagType.TAG_END) {
int nameLength = is.readShort() & 0xFFFF;
byte[] nameBytes = new byte[nameLength];
is.readFully(nameBytes);
name = new String(nameBytes, NBTConstants.CHARSET.name());
} else {
name = "";
}
return readTagPayload(type, name, depth);
}
/**
* Reads the payload of a {@link Tag}, given the name and type.
*
* @param type The type.
* @param name The name.
* @param depth The depth.
* @return The tag.
* @throws java.io.IOException if an I/O error occurs.
*/
@SuppressWarnings ({"unchecked", "rawtypes"})
private Tag readTagPayload(TagType type, String name, int depth) throws IOException {
switch (type) {
case TAG_END:
if (depth == 0) {
throw new IOException("TAG_End found without a TAG_Compound/TAG_List tag preceding it.");
} else {
return new EndTag();
}
case TAG_BYTE:
return new ByteTag(name, is.readByte());
case TAG_SHORT:
return new ShortTag(name, is.readShort());
case TAG_INT:
return new IntTag(name, is.readInt());
case TAG_LONG:
return new LongTag(name, is.readLong());
case TAG_FLOAT:
return new FloatTag(name, is.readFloat());
case TAG_DOUBLE:
return new DoubleTag(name, is.readDouble());
case TAG_BYTE_ARRAY:
int length = is.readInt();
byte[] bytes = new byte[length];
is.readFully(bytes);
return new ByteArrayTag(name, bytes);
case TAG_STRING:
length = is.readShort();
bytes = new byte[length];
is.readFully(bytes);
return new StringTag(name, new String(bytes, NBTConstants.CHARSET.name()));
case TAG_LIST:
TagType childType = TagType.getById(is.readByte());
length = is.readInt();
Class<? extends Tag> clazz = childType.getTagClass();
List<Tag> tagList = new ArrayList<Tag>(length);
for (int i = 0; i < length; i++) {
Tag tag = readTagPayload(childType, "", depth + 1);
if (tag instanceof EndTag) {
throw new IOException("TAG_End not permitted in a list.");
} else if (!clazz.isInstance(tag)) {
throw new IOException("Mixed tag types within a list.");
}
tagList.add(tag);
}
return new ListTag(name, clazz, tagList);
case TAG_COMPOUND:
CompoundMap compoundTagList = new CompoundMap();
while (true) {
Tag tag = readTag(depth + 1);
if (tag instanceof EndTag) {
break;
} else {
compoundTagList.put(tag);
}
}
return new CompoundTag(name, compoundTagList);
case TAG_INT_ARRAY:
length = is.readInt();
int[] ints = new int[length];
for (int i = 0; i < length; i++) {
ints[i] = is.readInt();
}
return new IntArrayTag(name, ints);
case TAG_SHORT_ARRAY:
length = is.readInt();
short[] shorts = new short[length];
for (int i = 0; i < length; i++) {
shorts[i] = is.readShort();
}
return new ShortArrayTag(name, shorts);
case TAG_LONG_ARRAY:
length = is.readInt();
long[] longs = new long[length];
for (int i = 0; i < length; i++) {
longs[i] = is.readLong();
}
return new LongArrayTag(name, longs);
default:
throw new IOException("Invalid tag type: " + type + ".");
}
}
public void close() throws IOException {
is.close();
}
/**
* @return whether this NBTInputStream reads numbers in little-endian format.
*/
public ByteOrder getByteOrder() {
return is.getEndianness();
}
}

View File

@@ -0,0 +1,80 @@
package mc.world.anvil;
import com.flowpowered.nbt.CompoundTag;
import lombok.extern.slf4j.Slf4j;
import mc.core.world.block.Block;
import mc.core.world.block.BlockLocation;
import mc.core.world.block.BlockType;
@Slf4j
public class AnvilBlock implements Block {
private final AnvilChunkSection chunkSection;
private final BlockLocation location;
private BlockLocation globalLocation;
public AnvilBlock(AnvilChunkSection chunkSection, int x, int y, int z) {
this.chunkSection = chunkSection;
this.location = new BlockLocation(x, y, z);
}
@Override
public int getLight() {
return chunkSection.getBlockLight().get(location);
}
@Override
public void setLight(int light) {
// nope...
}
@Override
public BlockType getType() {
final byte id = chunkSection.getBlocks().get((location.getY() << 8) + (location.getZ() << 4) + location.getX());
final int meta = chunkSection.getBlocksMeta().get(location);
BlockType type = BlockType.getByIdMeta(id & 0xFF, meta);
if (type.equals(BlockType.BEDROCK) && id != 7) {
log.warn("ChunkSection: {},{},{} | Block: {}",
chunkSection.getParent().getX(),
chunkSection.getY(),
chunkSection.getParent().getZ(),
location.toString());
}
return type;
}
@Override
public BlockLocation getLocation() {
if (globalLocation == null) {
globalLocation = new BlockLocation(
(chunkSection.getParent().getX() << 4) + location.getX(),
(chunkSection.getY() << 4) + location.getY(),
(chunkSection.getParent().getZ() << 4) + location.getZ()
);
}
return globalLocation;
}
@Override
public CompoundTag getNBTData() {
CompoundTag compoundTag = ((AnvilChunk)chunkSection.getParent()).getNbtByGlobalXYZ(
(chunkSection.getParent().getX() << 4) + location.getX(),
(chunkSection.getY() << 4) + location.getY(),
(chunkSection.getParent().getZ() << 4) + location.getZ()
);
if (compoundTag != null) {
compoundTag.getValue().remove("Items");
compoundTag.getValue().remove("Lock");
}
return compoundTag;
}
@Override
public String toString() {
return "AnvilBlock{" +
"location=" + getLocation() +
", type=" + getType() +
'}';
}
}

View File

@@ -0,0 +1,137 @@
package mc.world.anvil;
import com.flowpowered.nbt.ByteArrayTag;
import com.flowpowered.nbt.ByteTag;
import com.flowpowered.nbt.CompoundMap;
import com.flowpowered.nbt.CompoundTag;
import com.flowpowered.nbt.IntTag;
import com.flowpowered.nbt.ListTag;
import gnu.trove.list.TByteList;
import gnu.trove.list.array.TByteArrayList;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import mc.core.utils.NibbleArray;
import mc.core.world.Biome;
import mc.core.world.block.Block;
import mc.core.world.chunk.Chunk;
import mc.core.world.chunk.ChunkSection;
import java.util.ArrayList;
import java.util.List;
@Slf4j
@Getter
public class AnvilChunk implements Chunk {
private int x;
private int z;
private TByteList biomes = new TByteArrayList(256);
private List<ChunkSection> sections;
private ListTag<CompoundTag> tileEntities;
@SuppressWarnings("unchecked")
AnvilChunk(CompoundTag chunkTag) {
CompoundMap levelTagMap = ((CompoundTag) chunkTag.getValue().get("Level")).getValue();
this.x = ((IntTag) levelTagMap.get("xPos")).getValue();
this.z = ((IntTag) levelTagMap.get("zPos")).getValue();
biomes.add(((ByteArrayTag) levelTagMap.get("Biomes")).getValue());
tileEntities = (ListTag<CompoundTag>) levelTagMap.get("TileEntities");
List<CompoundTag> sections = ((ListTag<CompoundTag>) levelTagMap.get("Sections")).getValue();
this.sections = new ArrayList<>(sections.size());
for (CompoundTag sectionTag : sections) {
CompoundMap sectionTagValue = sectionTag.getValue();
AnvilChunkSection chunkSection = new AnvilChunkSection();
chunkSection.setParent(this);
chunkSection.setY(((ByteTag) sectionTagValue.get("Y")).getValue());
chunkSection.setBlockLight(new NibbleArray(((ByteArrayTag) sectionTagValue.get("BlockLight")).getValue()));
chunkSection.setSkyLight(new NibbleArray(((ByteArrayTag) sectionTagValue.get("SkyLight")).getValue()));
chunkSection.getBlocks().add(((ByteArrayTag) sectionTagValue.get("Blocks")).getValue());
chunkSection.setBlocksMeta(new NibbleArray(((ByteArrayTag) sectionTagValue.get("Data")).getValue()));
this.sections.add(chunkSection);
}
}
CompoundTag getNbtByGlobalXYZ(int x, int y, int z) {
for (CompoundTag compoundTag : tileEntities.getValue()) {
CompoundMap compoundMap = compoundTag.getValue();
if (((IntTag)compoundMap.get("x")).getValue() == x
&& ((IntTag)compoundMap.get("y")).getValue() == y
&& ((IntTag)compoundMap.get("z")).getValue() == z) {
return compoundTag;
}
}
return null;
}
@Override
public ChunkSection getChunkSection(int height) {
if (height > sections.size()-1) return null;
return sections.get(height);
}
@Override
public void setChunkSection(int height, ChunkSection chunkSection) {
// nope...
}
@Override
public Block getBlock(int x, int y, int z) {
final int height = y >> 4;
return sections.get(height).getBlock(
x - getX() << 4,
y - height << 4,
z - getZ() << 4
);
}
@Override
public void setBlock(Block block) {
// nope...
}
@Override
public int getSkyLight(int x, int y, int z) {
final int height = y >> 4;
return sections.get(height).getSkyLight(
x - getX() << 4,
y - height << 4,
z - getZ() << 4
);
}
@Override
public void setSkyLight(int x, int y, int z, int lightLevel) {
// nope...
}
@Override
public int getAddition(int x, int y, int z) {
final int height = y >> 4;
return sections.get(height).getAddition(
x - getX() << 4,
y - height << 4,
z - getZ() << 4
);
}
@Override
public void setAddition(int x, int y, int z, int value) {
// nope...
}
@Override
public Biome getBiome(int x, int z) {
return Biome.getById(biomes.get((z >> 4) << 4 | (x >> 4)) & 255);
}
@Override
public void setBiome(int x, int z, Biome biome) {
// nope...
}
}

View File

@@ -0,0 +1,50 @@
package mc.world.anvil;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import mc.core.world.chunk.Chunk;
import mc.core.world.chunk.ChunkProvider;
import org.springframework.stereotype.Component;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
@Slf4j
@Component
@NoArgsConstructor
@Setter
public class AnvilChunkProvider implements ChunkProvider {
private RegionManager regionManager;
public AnvilChunkProvider(String mapPath) {
Path pathMap = Paths.get(mapPath);
if (Files.exists(pathMap)) {
log.info("Use Anvil map from \"{}\"", pathMap);
this.setRegionManager(new RegionManager(pathMap.resolve("region")));
} else {
log.error("Anvil map: path \"{}\" not found!!!", pathMap);
}
}
@Override
public Chunk getChunk(int x, int z) {
Region region = regionManager.getRegion(x >> 5, z >> 5);
if (region == null) {
return new EmptyChunk(x, z);
} else {
return region.getChunk(x, z);
}
}
@Override
public void saveChunk(Chunk chunk) {
// nope
}
@Override
public void saveChunk(Chunk... chunks) {
// nope
}
}

View File

@@ -0,0 +1,57 @@
package mc.world.anvil;
import gnu.trove.list.TByteList;
import gnu.trove.list.linked.TByteLinkedList;
import lombok.Getter;
import lombok.Setter;
import mc.core.utils.NibbleArray;
import mc.core.world.block.Block;
import mc.core.world.chunk.Chunk;
import mc.core.world.chunk.ChunkSection;
@Getter
public class AnvilChunkSection implements ChunkSection {
@Setter
private Chunk parent;
@Setter
private int y;
private TByteList blocks = new TByteLinkedList();
@Setter
private NibbleArray blocksMeta;
@Setter
private NibbleArray blockLight;
@Setter
private NibbleArray skyLight;
@Override
public Block getBlock(int x, int y, int z) {
return new AnvilBlock(this, x, y, z);
}
@Override
public void setBlock(Block block) {
// nope...
}
@Override
public int getSkyLight(int x, int y, int z) {
return skyLight.get(x, y, z);
}
@Override
public void setSkyLight(int x, int y, int z, int lightLevel) {
// nope...
}
@Override
public int getAddition(int x, int y, int z) {
return 0;
}
@Override
public void setAddition(int x, int y, int z, int value) {
// nope...
}
}

View File

@@ -0,0 +1,153 @@
package mc.world.anvil;
import lombok.Getter;
import lombok.NoArgsConstructor;
import mc.core.world.Biome;
import mc.core.world.block.Block;
import mc.core.world.block.BlockLocation;
import mc.core.world.block.BlockType;
import mc.core.world.chunk.Chunk;
import mc.core.world.chunk.ChunkSection;
import java.util.LinkedList;
import java.util.List;
@Getter
public class EmptyChunk implements Chunk {
private int x;
private int z;
private List<ChunkSection> $sections = new LinkedList<>();
public EmptyChunk(int x, int z) {
this.x = x;
this.z = z;
for (int i = 0; i < 16; i++) {
this.$sections.add(null);
}
}
@Override
public ChunkSection getChunkSection(int height) {
ChunkSection section;
if ((section = $sections.get(height)) == null) {
section = new EmptySection(height);
$sections.set(height, section);
}
return section;
}
@Override
public void setChunkSection(int height, ChunkSection chunkSection) {
}
@Override
public Block getBlock(int x, int y, int z) {
return getChunkSection(y >> 4).getBlock(
x - getX() << 4,
y - (y >> 4) << 4,
z - getZ() << 4
);
}
@Override
public void setBlock(Block block) {
}
@Override
public int getSkyLight(int x, int y, int z) {
return getChunkSection(y >> 4).getSkyLight(x, y, z);
}
@Override
public void setSkyLight(int x, int y, int z, int lightLevel) {
}
@Override
public int getAddition(int x, int y, int z) {
return getChunkSection(y >> 4).getAddition(x, y, z);
}
@Override
public void setAddition(int x, int y, int z, int value) {
}
@Override
public Biome getBiome(int x, int z) {
return Biome.PLAINS;
}
@Override
public void setBiome(int x, int z, Biome biome) {
}
@NoArgsConstructor
@Getter
public class EmptySection implements ChunkSection {
private int y;
EmptySection(int y) {
this.y = y;
}
@Override
public Chunk getParent() {
return EmptyChunk.this;
}
@Override
public void setParent(Chunk chunk) {
}
@Override
public Block getBlock(int localX, int localY, int localZ) {
return new Block() {
@Override
public int getLight() {
return 15;
}
@Override
public void setLight(int light) {
}
@Override
public BlockType getType() {
return BlockType.AIR;
}
@Override
public BlockLocation getLocation() {
return new BlockLocation(
(getParent().getX() << 4) + localX,
(getY() << 4) + localY,
(getParent().getZ() << 4) + localZ
);
}
};
}
@Override
public void setBlock(Block block) {
}
@Override
public int getSkyLight(int localX, int localY, int localZ) {
return 15;
}
@Override
public void setSkyLight(int localX, int localY, int localZ, int lightLevel) {
}
@Override
public int getAddition(int localX, int localY, int localZ) {
return 0;
}
@Override
public void setAddition(int localX, int localY, int localZ, int value) {
}
}
}

View File

@@ -0,0 +1,26 @@
package mc.world.anvil;
import com.flowpowered.nbt.*;
import lombok.Getter;
import lombok.ToString;
import mc.core.world.block.BlockLocation;
@Getter
@ToString
class LevelInfo {
private long seed;
private BlockLocation spawn;
private int version;
LevelInfo(CompoundTag levelDatTag) {
CompoundMap dataMapTag = ((CompoundTag) levelDatTag.getValue().get("Data")).getValue();
seed = ((LongTag) dataMapTag.get("RandomSeed")).getValue();
spawn = new BlockLocation(
((IntTag) dataMapTag.get("SpawnX")).getValue(),
((IntTag) dataMapTag.get("SpawnY")).getValue(),
((IntTag) dataMapTag.get("SpawnZ")).getValue()
);
version = ((IntTag) dataMapTag.get("version")).getValue();
}
}

View File

@@ -0,0 +1,108 @@
package mc.world.anvil;
import com.flowpowered.nbt.CompoundTag;
import com.flowpowered.nbt.Tag;
import com.flowpowered.nbt.stream.NBTInputStream;
import gnu.trove.list.TByteList;
import gnu.trove.list.array.TByteArrayList;
import lombok.extern.slf4j.Slf4j;
import mc.core.world.chunk.Chunk;
import javax.annotation.Nullable;
import java.io.*;
import java.util.zip.InflaterInputStream;
@Slf4j
class Region implements Closeable {
private static final byte BYTE_TRUE = 1,
BYTE_FALSE = 0;
private RandomAccessFile file;
private TByteList sectorFree;
private final int[] offsets = new int[1024];
Region(File file) throws IOException {
this.file = new RandomAccessFile(file, "rw");
int sizeOfSectorFree = (int)this.file.length() / 4096;
sectorFree = new TByteArrayList(sizeOfSectorFree);
sectorFree.add(BYTE_FALSE);
sectorFree.add(BYTE_FALSE);
for (int i = 0; i < sizeOfSectorFree-2; i++) {
sectorFree.add(BYTE_TRUE);
}
for (int i = 0; i < offsets.length; ++i) {
int read = this.file.readInt();
offsets[i] = read;
if (read != 0 && (read >> 8) + (read & 255) <= this.sectorFree.size()) {
for (int j = 0; j < (read & 255); ++j) {
this.sectorFree.set((read >> 8) + j, BYTE_FALSE);
}
}
}
this.file.skipBytes(1024);
}
@Nullable
Chunk getChunk(int x, int z) {
int offset;
try {
offset = getOffset(x & 31, z & 31);
} catch (Exception e) {
return new EmptyChunk(x, z);
}
if (offset == 0) {
return new EmptyChunk(x, z);
}
int v1 = offset >> 8;
int v2 = offset & 255;
if (v1 + v2 > sectorFree.size()) {
return new EmptyChunk(x, z);
}
try {
file.seek((long) (v1 * 4096));
int read = file.readInt();
if (read <= 0 || read > 4096 * v2) {
return new EmptyChunk(x, z);
}
boolean gzippedData = (file.readByte() == 0x01);
if (gzippedData) {
log.warn("GZipped");
} else {
byte[] buffer = new byte[read - 1];
file.read(buffer);
InflaterInputStream inputStream = new InflaterInputStream(new ByteArrayInputStream(buffer));
NBTInputStream nbtInputStream = new NBTInputStream(inputStream, false);
Tag rootTag = nbtInputStream.readTag();
nbtInputStream.close();
return new AnvilChunk((CompoundTag) rootTag);
}
} catch (IOException e) {
log.error("Get chunk", e);
return null;
}
return null;
}
private int getOffset(int x, int z) {
return offsets[x + z * 32];
}
@Override
public void close() throws IOException {
if (file != null) file.close();
}
}

View File

@@ -0,0 +1,49 @@
package mc.world.anvil;
import gnu.trove.map.TIntObjectMap;
import gnu.trove.map.hash.TIntObjectHashMap;
import lombok.extern.slf4j.Slf4j;
import mc.core.utils.CompactedCoords;
import org.springframework.lang.Nullable;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
@Slf4j
public class RegionManager {
private final Path regionFilesPath;
private final TIntObjectMap<Region> regions = new TIntObjectHashMap<>();
public RegionManager(String regionFilesPath) {
this(Paths.get(regionFilesPath));
}
public RegionManager(Path regionFilesPath) {
this.regionFilesPath = regionFilesPath;
}
@Nullable
public Region getRegion(int x, int z) {
final int xz = CompactedCoords.compressXZ(x, z);
if (regions.containsKey(xz)) {
return regions.get(xz);
} else {
Path regionFilePath = regionFilesPath.resolve("r." + x + "." + z + ".mca");
if (Files.exists(regionFilePath)) {
try {
Region region = new Region(regionFilePath.toFile());
regions.put(xz, region);
return region;
} catch (IOException e) {
log.error("load region from file", e);
return null;
}
} else {
return null;
}
}
}
}

View File

@@ -0,0 +1,213 @@
package mc.world.anvil;
import com.flowpowered.nbt.CompoundMap;
import com.flowpowered.nbt.CompoundTag;
import com.flowpowered.nbt.IntTag;
import com.flowpowered.nbt.StringTag;
import lombok.SneakyThrows;
import mc.core.world.block.Block;
import mc.core.world.block.BlockType;
import mc.core.world.chunk.Chunk;
import mc.core.world.chunk.ChunkSection;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Stream;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
class RegionTest {
private static RegionManager regionManager;
@BeforeAll
@SneakyThrows
static void before() {
regionManager = new RegionManager(Paths.get(RegionTest.class.getResource("/region/").toURI()));
}
private static void assertZeroPlast(int x, int z, Block block, String msg) {
// @formatter:off
if (x == 0 && z == 0) assertEquals(BlockType.STONE, block.getType(), msg);
else if (x == 15 && z == 0) assertEquals(BlockType.GRANITE, block.getType(), msg);
else if (x == 0 && z == 15) assertEquals(BlockType.POLISHED_GRANITE, block.getType(), msg);
else if (x == 15 && z == 15) assertEquals(BlockType.DIORITE, block.getType(), msg);
else assertEquals(BlockType.BEDROCK, block.getType(), msg);
// @formatter:on
}
private static ChunkChecker chunkChecker00() {
return new ChunkChecker() {
private void checkSection0(ChunkSection chunkSection) {
for (int y = 0; y < 16; y++) {
for (int x = 0; x < 16; x++) {
for (int z = 0; z < 16; z++) {
Block block = chunkSection.getBlock(x, y, z);
String msg = String.format("coords: %d %d %d", x, y, z);
if (y == 0) {
assertZeroPlast(x, z, block, msg);
} else {
assertEquals(BlockType.STONE, block.getType(), msg);
}
}
}
}
}
private CompoundTag createExceptedNBT(Block block) {
CompoundMap compoundMap = new CompoundMap();
compoundMap.put(new IntTag("x", block.getLocation().getX()));
compoundMap.put(new IntTag("y", block.getLocation().getY()));
compoundMap.put(new IntTag("z", block.getLocation().getZ()));
compoundMap.put(new StringTag("id", block.getType().getNamedId()));
return new CompoundTag("", compoundMap);
}
private void checkSection1(ChunkSection chunkSection) {
for (int y = 0; y < 16; y++) {
for (int x = 0; x < 16; x++) {
for (int z = 0; z < 16; z++) {
Block block = chunkSection.getBlock(x, y, z);
String msg = String.format("coords: %d %d %d", x, y, z);
// @formatter:off
if (y == 0) assertEquals(BlockType.DIRT, block.getType(), msg);
else if (y == 1) assertEquals(BlockType.GRASS, block.getType(), msg);
else if (y == 2) {
if ((x == 2 || x == 4 || x == 5) && z == 1) {
assertEquals(BlockType.CHEST_NORTH, block.getType(), msg);
assertEquals(createExceptedNBT(block), block.getNBTData());
} else if ((x == 2 || x == 3 || x == 5) && z == 6) {
assertEquals(BlockType.CHEST_SOUTH, block.getType(), msg);
assertEquals(createExceptedNBT(block), block.getNBTData());
} else if (x == 1 && (z == 2 || z == 3 || z == 5)) {
assertEquals(BlockType.CHEST_WEST, block.getType(), msg);
assertEquals(createExceptedNBT(block), block.getNBTData());
} else if (x == 6 && (z == 2 || z == 4 || z == 5)) {
assertEquals(BlockType.CHEST_EAST, block.getType(), msg);
assertEquals(createExceptedNBT(block), block.getNBTData());
} else {
assertEquals(BlockType.AIR, block.getType(), msg);
}
}
else assertEquals(BlockType.AIR, block.getType(), msg);
// @formatter:on
}
}
}
}
@Override
public void check(Chunk chunk) {
ChunkSection chunkSection = chunk.getChunkSection(0);
assertNotNull(chunkSection);
checkSection0(chunkSection);
chunkSection = chunk.getChunkSection(1);
assertNotNull(chunkSection);
checkSection1(chunkSection);
}
};
}
private static ChunkChecker chunkChecker01() {
return new ChunkChecker() {
@Override
public void check(Chunk chunk) {
ChunkSection section = chunk.getChunkSection(0);
assertNotNull(section);
final List<BlockType> exceptedTypes = Arrays.asList(
BlockType.CLAY,
BlockType.ORE_REDSTONE,
BlockType.ORE_DIAMOND,
BlockType.OBSIDIAN,
BlockType.STONE_MOSS,
BlockType.SANDSTONE,
BlockType.ORE_LAPIS,
BlockType.WOOD_JUNGLE,
BlockType.WOOD_BIRCH,
BlockType.WOOD_SPRUCE,
BlockType.WOOD_OAK,
BlockType.ORE_COAL,
BlockType.ORE_IRON,
BlockType.ORE_GOLD,
BlockType.GRAVEL,
BlockType.SAND
);
for (int y = 0; y < 2; y++) {
for (int x = 0; x < 16; x++) {
for (int z = 0; z < 16; z++) {
Block block = section.getBlock(x, y, z);
String msg = String.format("coords: %d %d %d", x, y, z);
if (y == 0) {
assertZeroPlast(x, z, block, msg);
} else {
assertEquals(exceptedTypes.get(x), block.getType(), msg);
}
}
}
}
}
};
}
private static ChunkChecker chunkChecker0N1() {
return new ChunkChecker() {
@Override
public void check(Chunk chunk) {
ChunkSection section = chunk.getChunkSection(0);
assertNotNull(section);
for (int y = 0; y < 1; y++) {
for (int x = 0; x < 16; x++) {
for (int z = 0; z < 16; z++) {
assertZeroPlast(x, z,
section.getBlock(x, y, z),
String.format("coords: %d %d %d", x, y, z));
}
}
}
}
};
}
private static Stream<Arguments> streamArguments() {
return Stream.of(
Arguments.of(0, 0, chunkChecker00()),
Arguments.of(0, 1, chunkChecker01()),
Arguments.of(0, -1, chunkChecker0N1())
);
}
@DisplayName("testGetChunk")
@ParameterizedTest(name = "[{index}] chunk {0},{1}")
@MethodSource("streamArguments")
void testGetChunk(int chunkX, int chunkZ, ChunkChecker chunkChecker) {
final Region region = regionManager.getRegion(chunkX >> 5, chunkZ >> 5);
assertNotNull(region);
final Chunk chunk = region.getChunk(chunkX, chunkZ);
assertNotNull(chunk);
assertFalse(chunk instanceof EmptyChunk);
chunkChecker.check(chunk);
}
interface ChunkChecker {
void check(Chunk chunk);
}
}

Binary file not shown.

Binary file not shown.