Merge branch 'world-loader-anvil' into proto_1.12.2
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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() {
|
||||
}
|
||||
}
|
||||
|
||||
94
anvil-loader/src/main/java/com/flowpowered/nbt/TagType.java
Normal file
94
anvil-loader/src/main/java/com/flowpowered/nbt/TagType.java
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
80
anvil-loader/src/main/java/mc/world/anvil/AnvilBlock.java
Normal file
80
anvil-loader/src/main/java/mc/world/anvil/AnvilBlock.java
Normal 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() +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
137
anvil-loader/src/main/java/mc/world/anvil/AnvilChunk.java
Normal file
137
anvil-loader/src/main/java/mc/world/anvil/AnvilChunk.java
Normal 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...
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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...
|
||||
}
|
||||
}
|
||||
153
anvil-loader/src/main/java/mc/world/anvil/EmptyChunk.java
Normal file
153
anvil-loader/src/main/java/mc/world/anvil/EmptyChunk.java
Normal 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) {
|
||||
}
|
||||
}
|
||||
}
|
||||
26
anvil-loader/src/main/java/mc/world/anvil/LevelInfo.java
Normal file
26
anvil-loader/src/main/java/mc/world/anvil/LevelInfo.java
Normal 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();
|
||||
}
|
||||
}
|
||||
108
anvil-loader/src/main/java/mc/world/anvil/Region.java
Normal file
108
anvil-loader/src/main/java/mc/world/anvil/Region.java
Normal 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();
|
||||
}
|
||||
}
|
||||
49
anvil-loader/src/main/java/mc/world/anvil/RegionManager.java
Normal file
49
anvil-loader/src/main/java/mc/world/anvil/RegionManager.java
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
213
anvil-loader/src/test/java/mc/world/anvil/RegionTest.java
Normal file
213
anvil-loader/src/test/java/mc/world/anvil/RegionTest.java
Normal 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);
|
||||
}
|
||||
}
|
||||
BIN
anvil-loader/src/test/resources/region/r.0.-1.mca
Normal file
BIN
anvil-loader/src/test/resources/region/r.0.-1.mca
Normal file
Binary file not shown.
BIN
anvil-loader/src/test/resources/region/r.0.0.mca
Normal file
BIN
anvil-loader/src/test/resources/region/r.0.0.mca
Normal file
Binary file not shown.
Reference in New Issue
Block a user