Merge branch 'world-loader-anvil' into proto_1.12.2
This commit is contained in:
9
anvil-loader/build.gradle
Normal file
9
anvil-loader/build.gradle
Normal file
@@ -0,0 +1,9 @@
|
||||
group 'mc'
|
||||
version '1.0-SNAPSHOT'
|
||||
|
||||
dependencies {
|
||||
/* Core */
|
||||
compile_excludeCopy project(':core')
|
||||
|
||||
compile (group: 'net.sf.trove4j', name: 'trove4j', version: '3.0.3')
|
||||
}
|
||||
@@ -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.
11
build.gradle
11
build.gradle
@@ -5,8 +5,8 @@ buildscript {
|
||||
repositories {
|
||||
maven { url "https://plugins.gradle.org/m2/" }
|
||||
}
|
||||
dependencies {
|
||||
classpath (group: 'org.sonarsource.scanner.gradle', name: 'sonarqube-gradle-plugin', version: '2.6.2')
|
||||
dependencies {
|
||||
classpath (group: 'org.sonarsource.scanner.gradle', name: 'sonarqube-gradle-plugin', version: '2.6.2')
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,7 +44,7 @@ subprojects {
|
||||
ext {
|
||||
slf4j_version = '1.7.25'
|
||||
spring_version = '5.1.0.RELEASE'
|
||||
lombok_version = '1.18.2'
|
||||
lombok_version = '1.18.4'
|
||||
junit_version = '5.3.1'
|
||||
}
|
||||
|
||||
@@ -65,13 +65,14 @@ subprojects {
|
||||
|
||||
/* Lombok */
|
||||
annotationProcessor (group: 'org.projectlombok', name: 'lombok', version: lombok_version)
|
||||
compileOnly (group: 'org.projectlombok', name: 'lombok', version: lombok_version)
|
||||
compile (group: 'org.projectlombok', name: 'lombok', version: lombok_version)
|
||||
testAnnotationProcessor (group: 'org.projectlombok', name: 'lombok', version: lombok_version)
|
||||
testCompileOnly (group: 'org.projectlombok', name: 'lombok', version: lombok_version)
|
||||
testCompile (group: 'org.projectlombok', name: 'lombok', version: lombok_version)
|
||||
|
||||
/* Testing */
|
||||
testImplementation (group: 'org.junit.jupiter', name: 'junit-jupiter-api', version: junit_version)
|
||||
testRuntimeOnly(group: 'org.junit.jupiter', name: 'junit-jupiter-engine', version: junit_version)
|
||||
testImplementation(group: 'org.junit.jupiter', name: 'junit-jupiter-params', version: junit_version)
|
||||
testCompile (group: 'org.slf4j', name: 'slf4j-simple', version: slf4j_version)
|
||||
testCompile (group: 'org.mockito', name: 'mockito-core', version: '1.10.19')
|
||||
testCompile (group: 'org.springframework', name: 'spring-test', version: spring_version)
|
||||
|
||||
@@ -23,9 +23,11 @@ public class CoreEventListener {
|
||||
public void handlerPlayerMoveEvent(CS_PlayerMoveEvent event) {
|
||||
Chunk chunk;
|
||||
chunk = event.getPlayer().getWorld().getChunk(event.getOldLocation().toBlockLocation()); // Old chunk
|
||||
if (chunk == null) return;
|
||||
int ccX = chunk.getX();
|
||||
int ccZ = chunk.getZ();
|
||||
chunk = event.getPlayer().getWorld().getChunk(event.getNewLocation().toBlockLocation()); // Next chunk
|
||||
if (chunk == null) return;
|
||||
int ncX = chunk.getX();
|
||||
int ncZ = chunk.getZ();
|
||||
|
||||
@@ -36,21 +38,6 @@ public class CoreEventListener {
|
||||
int cMinZ = chunk.getZ() - viewDistance;
|
||||
int cMaxZ = chunk.getZ() + viewDistance;
|
||||
|
||||
SC_ChunkUnloadEvent eventChunkUnload = new SC_ChunkUnloadEvent(event.getPlayer());
|
||||
Iterator<Integer> itr = event.getPlayer().getLoadedChunks().iterator();
|
||||
while(itr.hasNext()) {
|
||||
int compressXZ = itr.next();
|
||||
int[] xz = CompactedCoords.uncompressXZ(compressXZ);
|
||||
if (xz[0] > cMaxX || xz[0] < cMinX || xz[1] > cMaxZ || xz[1] < cMinZ) {
|
||||
eventChunkUnload.getNeedUnloadChunks().add(compressXZ);
|
||||
itr.remove();
|
||||
}
|
||||
}
|
||||
|
||||
if (!eventChunkUnload.getNeedUnloadChunks().isEmpty()) {
|
||||
EventBus.getInstance().post(eventChunkUnload);
|
||||
}
|
||||
|
||||
SC_ChunkLoadEvent eventChunkLoad = new SC_ChunkLoadEvent(event.getPlayer());
|
||||
for (int cZ = cMinZ; cZ <= cMaxZ; cZ++) {
|
||||
for (int cX = cMinX; cX <= cMaxX; cX++) {
|
||||
@@ -67,6 +54,21 @@ public class CoreEventListener {
|
||||
if (!eventChunkLoad.getNeedLoadChunks().isEmpty()) {
|
||||
EventBus.getInstance().post(eventChunkLoad);
|
||||
}
|
||||
|
||||
SC_ChunkUnloadEvent eventChunkUnload = new SC_ChunkUnloadEvent(event.getPlayer());
|
||||
Iterator<Integer> itr = event.getPlayer().getLoadedChunks().iterator();
|
||||
while(itr.hasNext()) {
|
||||
int compressXZ = itr.next();
|
||||
int[] xz = CompactedCoords.uncompressXZ(compressXZ);
|
||||
if (xz[0] > cMaxX || xz[0] < cMinX || xz[1] > cMaxZ || xz[1] < cMinZ) {
|
||||
eventChunkUnload.getNeedUnloadChunks().add(compressXZ);
|
||||
itr.remove();
|
||||
}
|
||||
}
|
||||
|
||||
if (!eventChunkUnload.getNeedUnloadChunks().isEmpty()) {
|
||||
EventBus.getInstance().post(eventChunkUnload);
|
||||
}
|
||||
}
|
||||
|
||||
event.getPlayer().getLocation().setXYZ(
|
||||
|
||||
@@ -4,7 +4,6 @@ import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import mc.core.world.block.BlockLocation;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package mc.core.network;
|
||||
|
||||
import com.flowpowered.nbt.Tag;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
@@ -30,6 +31,7 @@ public abstract class NetInputStream extends InputStream {
|
||||
public abstract double readDouble();
|
||||
public abstract String readString();
|
||||
public abstract UUID readUUID();
|
||||
public abstract Tag<?> readNBT();
|
||||
|
||||
public abstract void skipBytes(int count);
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package mc.core.network;
|
||||
|
||||
import com.flowpowered.nbt.Tag;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.util.UUID;
|
||||
@@ -20,6 +22,7 @@ public abstract class NetOutputStream extends OutputStream {
|
||||
public abstract void writeDouble(double value);
|
||||
public abstract void writeString(String value);
|
||||
public abstract void writeUUID(UUID uuid);
|
||||
public abstract void writeNBT(Tag<?> tag);
|
||||
|
||||
@Override
|
||||
public void write(int b) throws IOException {
|
||||
|
||||
@@ -14,15 +14,9 @@ public class CompactedCoords {
|
||||
}
|
||||
|
||||
public static int[] uncompressXZ(int compactValue) {
|
||||
//TODO не нравится мне такие костыли
|
||||
return new int[]{
|
||||
(int)(short) (compactValue >> 16),
|
||||
(int)(short) (compactValue | 0xFFFF0000)
|
||||
compactValue >> 16,
|
||||
(compactValue & 0x8000) > 0 ? compactValue | 0xFFFF0000 : compactValue & 0xFFFF
|
||||
};
|
||||
}
|
||||
|
||||
private static int floor_double(double value) {
|
||||
int i = (int)value;
|
||||
return value < (double)i ? i - 1 : i;
|
||||
}
|
||||
}
|
||||
|
||||
65
core/src/main/java/mc/core/utils/NibbleArray.java
Normal file
65
core/src/main/java/mc/core/utils/NibbleArray.java
Normal file
@@ -0,0 +1,65 @@
|
||||
package mc.core.utils;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import mc.core.world.block.BlockLocation;
|
||||
|
||||
/**
|
||||
* Сжатый массив значений 0-15
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
public class NibbleArray {
|
||||
private final byte[] data;
|
||||
|
||||
public NibbleArray(int capacity) {
|
||||
this.data = new byte[capacity];
|
||||
}
|
||||
|
||||
public NibbleArray() {
|
||||
this.data = new byte[2048];
|
||||
}
|
||||
|
||||
private int coordsToIndex(int x, int y, int z) {
|
||||
return y << 8 | z << 4 | x;
|
||||
}
|
||||
|
||||
private int nibbleIndex(int index) {
|
||||
return index >> 1;
|
||||
}
|
||||
|
||||
private boolean isLowerNibble(int index) {
|
||||
return (index & 1) == 0;
|
||||
}
|
||||
|
||||
public int get(BlockLocation location) {
|
||||
return get(location.getX(), location.getY(), location.getZ());
|
||||
}
|
||||
|
||||
public int get(int x, int y, int z) {
|
||||
final int idx = coordsToIndex(x, y, z);
|
||||
|
||||
final int ni = nibbleIndex(idx);
|
||||
return isLowerNibble(idx) ? this.data[ni] & 0x0F : this.data[ni] >> 4 & 0x0F;
|
||||
}
|
||||
|
||||
public void set(BlockLocation location, int value) {
|
||||
set(location.getX(), location.getY(), location.getZ(), value);
|
||||
}
|
||||
|
||||
public void set(int x, int y, int z, int value) {
|
||||
if (value < 0) value = 0;
|
||||
else if (value > 15) value = 15;
|
||||
|
||||
final int idx = coordsToIndex(x, y, z);
|
||||
final int ni = nibbleIndex(idx);
|
||||
|
||||
if (isLowerNibble(idx)) {
|
||||
this.data[ni] = (byte)(value);
|
||||
} else {
|
||||
this.data[ni] = (byte)(this.data[ni] | value << 4);
|
||||
}
|
||||
}
|
||||
|
||||
public byte[] getRawData() {
|
||||
return data;
|
||||
}
|
||||
}
|
||||
@@ -2,10 +2,78 @@ package mc.core.world;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
public enum Biome {
|
||||
PLAINS(1);
|
||||
OCEAN(0),
|
||||
PLAINS(1),
|
||||
DESERT(2),
|
||||
EXTREME_HILLS(3),
|
||||
FOREST(4),
|
||||
TAIGA(5),
|
||||
SWAMPLAND(6),
|
||||
RIVER(7),
|
||||
HELL(8),
|
||||
SKY(9),
|
||||
FROZEN_OCEAN(10),
|
||||
FROZEN_RIVER(11),
|
||||
ICE_PLAINS(12),
|
||||
ICE_MOUNTAINS(13),
|
||||
MUSHROOM_ISLAND(14),
|
||||
MUSHROOM_ISLAND_SHORE(15),
|
||||
BEACH(16),
|
||||
DESERT_HILLS(17),
|
||||
FOREST_HILLS(18),
|
||||
TAIGA_HILLS(19),
|
||||
EXTREME_HILLS_EDGE(20),
|
||||
JUNGLE(21),
|
||||
JUNGLE_HILLS(22),
|
||||
JUNGLE_EDGE(23),
|
||||
DEEP_OCEAN(24),
|
||||
STONE_BEACH(25),
|
||||
COLD_BEACH(26),
|
||||
BIRCH_FOREST(27),
|
||||
BIRCH_FOREST_HILLS(28),
|
||||
ROOFED_FOREST(29),
|
||||
TAIGA_COLD(30),
|
||||
TAIGA_COLD_HILLS(31),
|
||||
REDWOOD_TAIGA(32),
|
||||
REDWOOD_TAIGA_HILLS(33),
|
||||
EXTREME_HILLS_WITH_TREES(34),
|
||||
SAVANNA(35),
|
||||
SAVANNA_ROCK(36),
|
||||
MESA(37),
|
||||
MESA_ROCK(38),
|
||||
MESA_CLEAR_ROCK(39),
|
||||
VOID(127),
|
||||
MUTATED_PLAINS(129),
|
||||
MUTATED_DESERT(130),
|
||||
MUTATED_EXTREME_HILLS(131),
|
||||
MUTATED_FOREST(132),
|
||||
MUTATED_TAIGA(133),
|
||||
MUTATED_SWAMPLAND(134),
|
||||
MUTATED_ICE_FLATS(140),
|
||||
MUTATED_JUNGLE(149),
|
||||
MUTATED_JUNGLE_EDGE(151),
|
||||
MUTATED_BIRCH_FOREST(155),
|
||||
MUTATED_BIRCH_FOREST_HILLS(156),
|
||||
MUTATED_ROOFED_FOREST(157),
|
||||
MUTATED_TAIGA_COLD(158),
|
||||
MUTATED_REDWOOD_TAIGA(160),
|
||||
MUTATED_REDWOOD_TAIGA_HILLS(161),
|
||||
MUTATED_EXTREME_HILLS_WITH_TREES(162),
|
||||
MUTATED_SAVANNA(163),
|
||||
MUTATED_SAVANNA_ROCK(164),
|
||||
MUTATED_MESA(165),
|
||||
MUTATED_MESA_ROCK(166),
|
||||
MUTATED_MESA_CLEAR_ROCK(167);
|
||||
|
||||
public static Biome getById(final int id) {
|
||||
return Arrays.stream(Biome.values()).filter(biome -> biome.id == id).findFirst().orElse(Biome.PLAINS);
|
||||
}
|
||||
|
||||
@Getter
|
||||
private final int id;
|
||||
|
||||
@@ -25,7 +25,7 @@ public interface World {
|
||||
* Получить чанк по его координатам
|
||||
* @param x chunk X
|
||||
* @param z chunk Z
|
||||
* @return {@link Chunk}
|
||||
* @return {@link mc.core.world.chunk.Chunk}
|
||||
*/
|
||||
Chunk getChunk(int x, int z);
|
||||
|
||||
@@ -38,6 +38,12 @@ public interface World {
|
||||
return getChunk(location.getX() >> 4, location.getZ() >> 4);
|
||||
}
|
||||
|
||||
/**
|
||||
* Установить чанк по координатам
|
||||
* @param x глобальный X
|
||||
* @param z глобальный Z
|
||||
* @param chunk {@link mc.core.world.chunk.Chunk}
|
||||
*/
|
||||
void setChunk(int x, int z, Chunk chunk);
|
||||
|
||||
/**
|
||||
|
||||
@@ -3,13 +3,11 @@ package mc.core.world.block;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
@Getter
|
||||
public abstract class AbstractBlock implements Block {
|
||||
@Getter
|
||||
@Setter
|
||||
private BlockLocation location;
|
||||
@Getter
|
||||
private int light = 0;
|
||||
@Getter
|
||||
private final BlockType type;
|
||||
|
||||
protected AbstractBlock(BlockType type) {
|
||||
|
||||
@@ -1,8 +1,17 @@
|
||||
package mc.core.world.block;
|
||||
|
||||
import com.flowpowered.nbt.CompoundTag;
|
||||
|
||||
public interface Block {
|
||||
int getLight();
|
||||
void setLight(int light);
|
||||
BlockType getType();
|
||||
BlockLocation getLocation();
|
||||
|
||||
default CompoundTag getNBTData() {
|
||||
return null;
|
||||
}
|
||||
|
||||
default void setNBTData(CompoundTag nbtData) {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package mc.core.world.block;
|
||||
|
||||
//TODO избавится от этого "аппендикса"
|
||||
@Deprecated
|
||||
public class BlockFactory {
|
||||
|
||||
public Block create(BlockType blockType, int x, int y, int z) {
|
||||
|
||||
@@ -2,30 +2,514 @@ package mc.core.world.block;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
public enum BlockType {
|
||||
|
||||
AIR(0, 0),
|
||||
STONE(1, 0),
|
||||
|
||||
STONE (1, 0),
|
||||
STONE_MOSS(48, 0),
|
||||
|
||||
GRANITE (1, 1),
|
||||
POLISHED_GRANITE(1, 2),
|
||||
|
||||
DIORITE(1, 3),
|
||||
ANDESITE(1, 5),
|
||||
|
||||
GRASS(2, 0),
|
||||
PATH(208, 0),
|
||||
|
||||
DIRT(3, 0),
|
||||
|
||||
/** Farmland, Dry, Moisture 0 */
|
||||
FARMLAND (60, 0),
|
||||
/** Farmland, Dry, Moisture 1 */
|
||||
FARMLAND_1(60, 1),
|
||||
/** Farmland, Dry, Moisture 2 */
|
||||
FARMLAND_2(60, 2),
|
||||
/** Farmland, Dry, Moisture 3 */
|
||||
FARMLAND_3(60, 3),
|
||||
/** Farmland, Dry, Moisture 4 */
|
||||
FARMLAND_4(60, 4),
|
||||
/** Farmland, Dry, Moisture 5 */
|
||||
FARMLAND_5(60, 5),
|
||||
/** Farmland, Dry, Moisture 6 */
|
||||
FARMLAND_6(60, 6),
|
||||
/** Farmland, Dry, Moisture 7 */
|
||||
FARMLAND_7(60, 7),
|
||||
|
||||
COBBLESTONE(4, 0),
|
||||
BEDROCK(7, 0),
|
||||
WATER(9, 0),
|
||||
SAND(12, 0),
|
||||
SNOW(78, 0);
|
||||
|
||||
/** Water, flowing, Level 7 (Source) */
|
||||
WATER_FLOWING (8, 0),
|
||||
/** Water, flowing, Level 6 */
|
||||
WATER_FLOWING_1 (8, 1),
|
||||
/** Water, flowing, Level 5 */
|
||||
WATER_FLOWING_2 (8, 2),
|
||||
/** Water, flowing, Level 4 */
|
||||
WATER_FLOWING_3 (8, 3),
|
||||
/** Water, flowing, Level 3 */
|
||||
WATER_FLOWING_4 (8, 4),
|
||||
/** Water, flowing, Level 2 */
|
||||
WATER_FLOWING_5 (8, 5),
|
||||
/** Water, flowing, Level 1 */
|
||||
WATER_FLOWING_6 (8, 6),
|
||||
/** Water, flowing, Level 0 */
|
||||
WATER_FLOWING_7 (8, 7),
|
||||
/** Water, flowing, Level 15 */
|
||||
WATER_FLOWING_8 (8, 8),
|
||||
/** Water, flowing, Level 14 */
|
||||
WATER_FLOWING_9 (8, 9),
|
||||
/** Water, flowing, Level 13 */
|
||||
WATER_FLOWING_10(8, 10),
|
||||
/** Water, flowing, Level 12 */
|
||||
WATER_FLOWING_11(8, 11),
|
||||
/** Water, flowing, Level 11 */
|
||||
WATER_FLOWING_12(8, 12),
|
||||
/** Water, flowing, Level 10 */
|
||||
WATER_FLOWING_13(8, 13),
|
||||
/** Water, flowing, Level 9 */
|
||||
WATER_FLOWING_14(8, 14),
|
||||
/** Water, flowing, Level 8 */
|
||||
WATER_FLOWING_15(8, 15),
|
||||
|
||||
/** Water, still, Level 7 (Source) */
|
||||
WATER_STILL (9, 0),
|
||||
/** Water, still, Level 6 */
|
||||
WATER_STILL_1 (9, 1),
|
||||
/** Water, still, Level 5 */
|
||||
WATER_STILL_2 (9, 2),
|
||||
/** Water, still, Level 4 */
|
||||
WATER_STILL_3 (9, 3),
|
||||
/** Water, still, Level 3 */
|
||||
WATER_STILL_4 (9, 4),
|
||||
/** Water, still, Level 2 */
|
||||
WATER_STILL_5 (9, 5),
|
||||
/** Water, still, Level 1 */
|
||||
WATER_STILL_6 (9, 6),
|
||||
/** Water, still, Level 0 */
|
||||
WATER_STILL_7 (9, 7),
|
||||
/** Water, still, Level 15 */
|
||||
WATER_STILL_8 (9, 8),
|
||||
/** Water, still, Level 14 */
|
||||
WATER_STILL_9 (9, 9),
|
||||
/** Water, still, Level 13 */
|
||||
WATER_STILL_10(9, 10),
|
||||
/** Water, still, Level 12 */
|
||||
WATER_STILL_11(9, 11),
|
||||
/** Water, still, Level 11 */
|
||||
WATER_STILL_12(9, 12),
|
||||
/** Water, still, Level 10 */
|
||||
WATER_STILL_13(9, 13),
|
||||
/** Water, still, Level 9 */
|
||||
WATER_STILL_14(9, 14),
|
||||
/** Water, still, Level 8 */
|
||||
WATER_STILL_15(9, 15),
|
||||
|
||||
/** Lava, flowing, Level 7 (Source) */
|
||||
LAVA_FLOWING (10, 0),
|
||||
/** Lava, flowing, Level 6 */
|
||||
LAVA_FLOWING_1 (10, 1),
|
||||
/** Lava, flowing, Level 5 */
|
||||
LAVA_FLOWING_2 (10, 2),
|
||||
/** Lava, flowing, Level 4 */
|
||||
LAVA_FLOWING_3 (10, 3),
|
||||
/** Lava, flowing, Level 3 */
|
||||
LAVA_FLOWING_4 (10, 4),
|
||||
/** Lava, flowing, Level 2 */
|
||||
LAVA_FLOWING_5 (10, 5),
|
||||
/** Lava, flowing, Level 1 */
|
||||
LAVA_FLOWING_6 (10, 6),
|
||||
/** Lava, flowing, Level 0 */
|
||||
LAVA_FLOWING_7 (10, 7),
|
||||
/** Lava, flowing, Level 15 */
|
||||
LAVA_FLOWING_8 (10, 8),
|
||||
/** Lava, flowing, Level 14 */
|
||||
LAVA_FLOWING_9 (10, 9),
|
||||
/** Lava, flowing, Level 13 */
|
||||
LAVA_FLOWING_10(10, 10),
|
||||
/** Lava, flowing, Level 12 */
|
||||
LAVA_FLOWING_11(10, 11),
|
||||
/** Lava, flowing, Level 11 */
|
||||
LAVA_FLOWING_12(10, 12),
|
||||
/** Lava, flowing, Level 10 */
|
||||
LAVA_FLOWING_13(10, 13),
|
||||
/** Lava, flowing, Level 9 */
|
||||
LAVA_FLOWING_14(10, 14),
|
||||
/** Lava, flowing, Level 8 */
|
||||
LAVA_FLOWING_15(10, 15),
|
||||
|
||||
/** Lava, still, Level 7 (Source) */
|
||||
LAVA_STILL (11, 0),
|
||||
/** Lava, still, Level 6 */
|
||||
LAVA_STILL_1 (11, 1),
|
||||
/** Lava, still, Level 5 */
|
||||
LAVA_STILL_2 (11, 2),
|
||||
/** Lava, still, Level 4 */
|
||||
LAVA_STILL_3 (11, 3),
|
||||
/** Lava, still, Level 3 */
|
||||
LAVA_STILL_4 (11, 4),
|
||||
/** Lava, still, Level 2 */
|
||||
LAVA_STILL_5 (11, 5),
|
||||
/** Lava, still, Level 1 */
|
||||
LAVA_STILL_6 (11, 6),
|
||||
/** Lava, still, Level 0 */
|
||||
LAVA_STILL_7 (11, 7),
|
||||
/** Lava, still, Level 15 */
|
||||
LAVA_STILL_8 (11, 8),
|
||||
/** Lava, still, Level 14 */
|
||||
LAVA_STILL_9 (11, 9),
|
||||
/** Lava, still, Level 13 */
|
||||
LAVA_STILL_10(11, 10),
|
||||
/** Lava, still, Level 12 */
|
||||
LAVA_STILL_11(11, 11),
|
||||
/** Lava, still, Level 11 */
|
||||
LAVA_STILL_12(11, 12),
|
||||
/** Lava, still, Level 10 */
|
||||
LAVA_STILL_13(11, 13),
|
||||
/** Lava, still, Level 9 */
|
||||
LAVA_STILL_14(11, 14),
|
||||
/** Lava, still, Level 8 */
|
||||
LAVA_STILL_15(11, 15),
|
||||
|
||||
SAND (12, 0),
|
||||
SANDSTONE(24, 0),
|
||||
|
||||
GRAVEL(13, 0),
|
||||
|
||||
ORE_GOLD (14, 0),
|
||||
ORE_IRON (15, 0),
|
||||
ORE_COAL (16, 0),
|
||||
ORE_LAPIS (21, 0),
|
||||
ORE_DIAMOND (56, 0),
|
||||
ORE_REDSTONE (73, 0),
|
||||
ORE_GLOWING_REDSTONE(74, 0),
|
||||
ORE_EMERALD (129, 0),
|
||||
|
||||
// Upright
|
||||
WOOD_OAK (17, 0),
|
||||
WOOD_SPRUCE (17, 1),
|
||||
WOOD_BIRCH (17, 2),
|
||||
WOOD_JUNGLE (17, 3),
|
||||
WOOD_ACACIA (162, 0),
|
||||
WOOD_OAK_DARK(162, 1),
|
||||
|
||||
// East/West
|
||||
WOOD_OAK_EW (17, 4),
|
||||
WOOD_SPRUCE_EW (17, 5),
|
||||
WOOD_BIRCH_EW (17, 6),
|
||||
WOOD_JUNGLE_EW (17, 7),
|
||||
WOOD_ACACIA_EW (162, 4),
|
||||
WOOD_OAK_DARK_EW(162, 5),
|
||||
|
||||
// North/South
|
||||
WOOD_OAK_NS (17, 8),
|
||||
WOOD_SPRUCE_NS (17, 9),
|
||||
WOOD_BIRCH_NS (17, 10),
|
||||
WOOD_JUNGLE_NS (17, 11),
|
||||
WOOD_ACACIA_NS (162, 8),
|
||||
WOOD_OAK_DARK_NS(162, 9),
|
||||
|
||||
PLANK_WOOD_OAK (5, 0),
|
||||
PLANK_WOOD_SPRUCE (5, 1),
|
||||
PLANK_WOOD_BIRCH (5, 2),
|
||||
PLANK_WOOD_JUNGLE (5, 3),
|
||||
PLANK_WOOD_ACACIA (5, 4),
|
||||
PLANK_WOOD_OAK_DARK(5, 5),
|
||||
|
||||
DOOR_LOW_OAK_EAST(64, 0),
|
||||
DOOR_LOW_OAK_SOUTH(64, 1),
|
||||
DOOR_LOW_OAK_WEST(64, 2),
|
||||
DOOR_LOW_OAK_NORTH(64, 3),
|
||||
DOOR_LOW_OAK_EAST_OPENED(64, 4),
|
||||
DOOR_LOW_OAK_SOUTH_OPENED(64, 5),
|
||||
DOOR_LOW_OAK_WEST_OPENED(64, 6),
|
||||
DOOR_LOW_OAK_NORTH_OPENED(64, 7),
|
||||
|
||||
DOOR_UP_OAK_LEFT(64, 8),
|
||||
DOOR_UP_OAK_RIGHT(64, 9),
|
||||
DOOR_UP_OAK_LEFT_POWERED(64, 10),
|
||||
DOOR_UP_OAK_RIGHT_POWERED(64, 11),
|
||||
DOOR_UP_OAK_12(64, 12),
|
||||
DOOR_UP_OAK_13(64, 13),
|
||||
DOOR_UP_OAK_14(64, 14),
|
||||
DOOR_UP_OAK_15(64, 15),
|
||||
|
||||
FENCE_OAK(85, 0),
|
||||
|
||||
// Decay after Tree Update
|
||||
LEAVES_OAK (18, 0),
|
||||
LEAVES_SPRUCE (18, 1),
|
||||
LEAVES_BIRCH (18, 2),
|
||||
LEAVES_JUNGLE (18, 3),
|
||||
LEAVES_ACACIA (161, 0),
|
||||
LEAVES_OAK_DARK(161, 1),
|
||||
|
||||
// No Decay
|
||||
LEAVES_OAK2 (18, 4),
|
||||
LEAVES_SPRUCE2 (18, 5),
|
||||
LEAVES_BIRCH2 (18, 6),
|
||||
LEAVES_JUNGLE2 (18, 7),
|
||||
LEAVES_ACACIA2 (161, 4),
|
||||
LEAVES_OAK_DARK2(161, 5),
|
||||
|
||||
// Decay
|
||||
LEAVES_OAK3 (18, 8),
|
||||
LEAVES_SPRUCE3 (18, 9),
|
||||
LEAVES_BIRCH3 (18, 10),
|
||||
LEAVES_JUNGLE3 (18, 11),
|
||||
LEAVES_ACACIA3 (161, 8),
|
||||
LEAVES_OAK_DARK3(161, 9),
|
||||
|
||||
// No decay, unused
|
||||
@Deprecated
|
||||
LEAVES_OAK4 (18, 12),
|
||||
@Deprecated
|
||||
LEAVES_SPRUCE4 (18, 13),
|
||||
@Deprecated
|
||||
LEAVES_BIRCH4 (18, 14),
|
||||
@Deprecated
|
||||
LEAVES_JUNGLE4 (18, 15),
|
||||
@Deprecated
|
||||
LEAVES_ACACIA4 (161, 12),
|
||||
@Deprecated
|
||||
LEAVES_OAK_DARK4(161, 13),
|
||||
|
||||
COBWEB(30, 0),
|
||||
TALLGRASS(31, 1),
|
||||
DANDELION(37, 0),
|
||||
|
||||
FLOWER_POPPY (38, 0),
|
||||
FLOWER_BLUE_ORCHID (38, 1),
|
||||
FLOWER_ALLIUM (38, 2),
|
||||
FLOWER_AZURE_BLUET (38, 3),
|
||||
FLOWER_TULIP_RED (38, 4),
|
||||
FLOWER_TULIP_ORANGE(38, 5),
|
||||
FLOWER_TULIP_WHITE (38, 6),
|
||||
FLOWER_TULIP_PINK (38, 7),
|
||||
FLOWER_OXEYE_DAISY (38, 8),
|
||||
|
||||
MUSHROOM_BROWN(39, 0),
|
||||
MUSHROOM_RED (40, 0),
|
||||
|
||||
MUSHROOM_BLOCK_BROWN_ALL_INSIDE(99, 0),
|
||||
MUSHROOM_BLOCK_BROWN_NW (99, 1),
|
||||
MUSHROOM_BLOCK_BROWN_NORT (99, 2),
|
||||
MUSHROOM_BLOCK_BROWN_NE (99, 3),
|
||||
MUSHROOM_BLOCK_BROWN_WEST (99, 4),
|
||||
MUSHROOM_BLOCK_BROWN_CENTER (99, 5),
|
||||
MUSHROOM_BLOCK_BROWN_EAST (99, 6),
|
||||
MUSHROOM_BLOCK_BROWN_SW (99, 7),
|
||||
MUSHROOM_BLOCK_BROWN_SOUTH (99, 8),
|
||||
MUSHROOM_BLOCK_BROWN_SE (99, 9),
|
||||
MUSHROOM_BLOCK_BROWN_STEM (99, 10),
|
||||
MUSHROOM_BLOCK_BROWN_ALL_OUSIDE(99, 14),
|
||||
MUSHROOM_BLOCK_BROWN_ALL_STEM (99, 15),
|
||||
|
||||
MUSHROOM_BLOCK_RED_ALL_INSIDE(100, 0),
|
||||
MUSHROOM_BLOCK_RED_NW (100, 1),
|
||||
MUSHROOM_BLOCK_RED_NORT (100, 2),
|
||||
MUSHROOM_BLOCK_RED_NE (100, 3),
|
||||
MUSHROOM_BLOCK_RED_WEST (100, 4),
|
||||
MUSHROOM_BLOCK_RED_CENTER (100, 5),
|
||||
MUSHROOM_BLOCK_RED_EAST (100, 6),
|
||||
MUSHROOM_BLOCK_RED_SW (100, 7),
|
||||
MUSHROOM_BLOCK_RED_SOUTH (100, 8),
|
||||
MUSHROOM_BLOCK_RED_SE (100, 9),
|
||||
MUSHROOM_BLOCK_RED_STEM (100, 10),
|
||||
MUSHROOM_BLOCK_RED_ALL_OUSIDE(100, 14),
|
||||
MUSHROOM_BLOCK_RED_ALL_STEM (100, 15),
|
||||
|
||||
OBSIDIAN(49, 0),
|
||||
|
||||
TORCH_EAST (50, 1),
|
||||
TORCH_WEST (50, 2),
|
||||
TORCH_SOUTH(50, 3),
|
||||
TORCH_NORTH(50, 4),
|
||||
TORCH_UP (50, 5),
|
||||
|
||||
MONSTER_SPAWNER(52, 0),
|
||||
|
||||
CHEST_NORTH(54, 2, "minecraft:chest"),
|
||||
CHEST_SOUTH(54, 3, "minecraft:chest"),
|
||||
CHEST_WEST (54, 4, "minecraft:chest"),
|
||||
CHEST_EAST (54, 5, "minecraft:chest"),
|
||||
|
||||
RAIL_NS (66, 0),
|
||||
RAIL_EW (66, 1),
|
||||
RAIL_ASCENDING_EAST (66, 2),
|
||||
RAIL_ASCENDING_WEST (66, 3),
|
||||
RAIL_ASCENDING_NORTH(66, 4),
|
||||
RAIL_ASCENDING_SOUTH(66, 5),
|
||||
RAIL_CURVED_SE (66, 6),
|
||||
RAIL_CURVED_SW (66, 7),
|
||||
RAIL_CURVED_NW (66, 8),
|
||||
RAIL_CURVED_NE (66, 9),
|
||||
|
||||
SNOW(78, 0),
|
||||
|
||||
CLAY(82, 0),
|
||||
CLAY_HARDENED(172, 0),
|
||||
|
||||
/** Sugar canes (Age 0) */
|
||||
SUGAR_CANES(83, 0),
|
||||
/** Sugar canes (Age 1) */
|
||||
SUGAR_CANES_1(83, 1),
|
||||
/** Sugar canes (Age 2) */
|
||||
SUGAR_CANES_2(83, 2),
|
||||
/** Sugar canes (Age 3) */
|
||||
SUGAR_CANES_3(83, 3),
|
||||
/** Sugar canes (Age 4) */
|
||||
SUGAR_CANES_4(83, 4),
|
||||
/** Sugar canes (Age 5) */
|
||||
SUGAR_CANES_5(83, 5),
|
||||
/** Sugar canes (Age 6) */
|
||||
SUGAR_CANES_6(83, 6),
|
||||
/** Sugar canes (Age 7) */
|
||||
SUGAR_CANES_7(83, 7),
|
||||
/** Sugar canes (Age 8) */
|
||||
SUGAR_CANES_8(83, 8),
|
||||
/** Sugar canes (Age 9) */
|
||||
SUGAR_CANES_9(83, 9),
|
||||
/** Sugar canes (Age 10) */
|
||||
SUGAR_CANES_10(83, 10),
|
||||
/** Sugar canes (Age 11) */
|
||||
SUGAR_CANES_11(83, 11),
|
||||
/** Sugar canes (Age 12) */
|
||||
SUGAR_CANES_12(83, 12),
|
||||
/** Sugar canes (Age 13) */
|
||||
SUGAR_CANES_13(83, 13),
|
||||
/** Sugar canes (Age 14) */
|
||||
SUGAR_CANES_14(83, 14),
|
||||
/** Sugar canes (Age 15) */
|
||||
SUGAR_CANES_15(83, 15),
|
||||
|
||||
PUMPKIN_SOUTH(86, 0),
|
||||
PUMPKIN_WEST (86, 1),
|
||||
PUMPKIN_NORTH(86, 2),
|
||||
PUMPKIN_EAST (86, 3),
|
||||
|
||||
STONE_MONSTER_EGG(97, 0),
|
||||
|
||||
GLASS_PANE(102, 0),
|
||||
|
||||
VINE (106, 0),
|
||||
VINE_SOUTH(106, 1),
|
||||
VINE_WEST (106, 2),
|
||||
VINE_SW (106, 3),
|
||||
VINE_NORTH(106, 4),
|
||||
VINE_NS (106, 5),
|
||||
VINE_NW (106, 6),
|
||||
VINE_NSW (106, 7), // North, South, West
|
||||
VINE_EAST (106, 8),
|
||||
VINE_ES (106, 9),
|
||||
VINE_EW (106, 10),
|
||||
VINE_ESW (106, 11),
|
||||
VINE_EN (106, 12),
|
||||
VINE_ENS (106, 13),
|
||||
VINE_ENW (106, 14),
|
||||
VINE_ENSW (106, 14),
|
||||
|
||||
WATERLILY(111, 0),
|
||||
|
||||
LILAC(175, 1),
|
||||
DOUBLE_TALLGRASS(175, 2),
|
||||
ROSE_BUSH(175, 4),
|
||||
PEONY(175, 5),
|
||||
ROSE_BUSH_10(175, 10),
|
||||
|
||||
/** Wheat (Age 0) */
|
||||
WHEAT (59, 0),
|
||||
/** Wheat (Age 1) */
|
||||
WHEAT_1(59, 1),
|
||||
/** Wheat (Age 2) */
|
||||
WHEAT_2(59, 2),
|
||||
/** Wheat (Age 3) */
|
||||
WHEAT_3(59, 3),
|
||||
/** Wheat (Age 4) */
|
||||
WHEAT_4(59, 4),
|
||||
/** Wheat (Age 5) */
|
||||
WHEAT_5(59, 5),
|
||||
/** Wheat (Age 6) */
|
||||
WHEAT_6(59, 6),
|
||||
/** Wheat (Age 7) */
|
||||
WHEAT_7(59, 7),
|
||||
|
||||
/** Carrots (Age 0) */
|
||||
CARROTS(141, 0),
|
||||
/** Carrots (Age 1) */
|
||||
CARROTS_1(141, 1),
|
||||
/** Carrots (Age 2) */
|
||||
CARROTS_2(141, 2),
|
||||
/** Carrots (Age 3) */
|
||||
CARROTS_3(141, 3),
|
||||
/** Carrots (Age 4) */
|
||||
CARROTS_4(141, 4),
|
||||
/** Carrots (Age 5) */
|
||||
CARROTS_5(141, 5),
|
||||
/** Carrots (Age 6) */
|
||||
CARROTS_6(141, 6),
|
||||
/** Carrots (Age 7) */
|
||||
CARROTS_7(141, 7),
|
||||
|
||||
/** Potatoes (Age 0) */
|
||||
POTATOES (142, 0),
|
||||
/** Potatoes (Age 1) */
|
||||
POTATOES_1(142, 1),
|
||||
/** Potatoes (Age 2) */
|
||||
POTATOES_2(142, 2),
|
||||
/** Potatoes (Age 3) */
|
||||
POTATOES_3(142, 3),
|
||||
/** Potatoes (Age 4) */
|
||||
POTATOES_4(142, 4),
|
||||
/** Potatoes (Age 5) */
|
||||
POTATOES_5(142, 5),
|
||||
/** Potatoes (Age 6) */
|
||||
POTATOES_6(142, 6),
|
||||
/** Potatoes (Age 7) */
|
||||
POTATOES_7(142, 7),
|
||||
|
||||
/** Beetroot (Age 0) */
|
||||
BEETROOT (207, 0),
|
||||
/** Beetroot (Age 1) */
|
||||
BEETROOT_1(207, 1),
|
||||
/** Beetroot (Age 2) */
|
||||
BEETROOT_2(207, 2),
|
||||
/** Beetroot (Age 3) */
|
||||
BEETROOT_3(207, 3);
|
||||
|
||||
BlockType(int id, int meta) {
|
||||
this.id = id;
|
||||
this.meta = meta;
|
||||
this.namedId = null;
|
||||
}
|
||||
|
||||
public static BlockType getByIdMeta(int id, int meta) {
|
||||
if (id < 0) {
|
||||
log.warn("Incorrect id \"{}\"", id);
|
||||
return BEDROCK;
|
||||
}
|
||||
|
||||
Stream<BlockType> stream = Arrays.stream(BlockType.values());
|
||||
return stream.filter(blockType -> blockType.id == id && blockType.meta == meta)
|
||||
.findFirst()
|
||||
.orElse(BlockType.AIR);
|
||||
.orElseGet(() -> {
|
||||
log.warn("Unknow block type: {}:{}", id, meta);
|
||||
return BEDROCK;
|
||||
});
|
||||
}
|
||||
|
||||
@Getter
|
||||
private final int id;
|
||||
@Getter
|
||||
private final int meta;
|
||||
@Getter
|
||||
private final String namedId;
|
||||
}
|
||||
|
||||
@@ -3,11 +3,32 @@ package mc.core.world.chunk;
|
||||
import mc.core.world.Biome;
|
||||
import mc.core.world.block.Block;
|
||||
|
||||
/* 16x256x16 */
|
||||
public interface Chunk {
|
||||
/**
|
||||
* Глобальная координата X
|
||||
* @return
|
||||
*/
|
||||
int getX();
|
||||
|
||||
/**
|
||||
* Глобальная координата Z
|
||||
* @return
|
||||
*/
|
||||
int getZ();
|
||||
|
||||
/**
|
||||
* Получить секцию чанка
|
||||
* @param height высота (0-15)
|
||||
* @return {@link mc.core.world.chunk.ChunkSection}
|
||||
*/
|
||||
ChunkSection getChunkSection(int height);
|
||||
|
||||
/**
|
||||
* Установить секцию чанка
|
||||
* @param height высота (0-15)
|
||||
* @param chunkSection {@link mc.core.world.chunk.ChunkSection}
|
||||
*/
|
||||
void setChunkSection(int height, ChunkSection chunkSection);
|
||||
|
||||
/**
|
||||
@@ -30,8 +51,15 @@ public interface Chunk {
|
||||
* Получить тип биома по глобальным координатам
|
||||
* @param x global X
|
||||
* @param z global Z
|
||||
* @return
|
||||
* @return {@link mc.core.world.Biome}
|
||||
*/
|
||||
Biome getBiome(int x, int z);
|
||||
|
||||
/**
|
||||
* Указать данные по биому
|
||||
* @param x global X
|
||||
* @param z global Z
|
||||
* @param biome {@link mc.core.world.Biome}
|
||||
*/
|
||||
void setBiome(int x, int z, Biome biome);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,12 @@
|
||||
package mc.core.world.chunk;
|
||||
|
||||
public interface ChunkProvider {
|
||||
/**
|
||||
* Получить чанк по координатам
|
||||
* @param x глобальный X
|
||||
* @param z глобальный Z
|
||||
* @return {@link mc.core.world.chunk.Chunk}
|
||||
*/
|
||||
Chunk getChunk(int x , int z);
|
||||
|
||||
void saveChunk(Chunk chunk);
|
||||
|
||||
@@ -9,6 +9,10 @@ public interface ChunkSection {
|
||||
Chunk getParent();
|
||||
void setParent(Chunk chunk);
|
||||
|
||||
/**
|
||||
* Высота
|
||||
* @return
|
||||
*/
|
||||
int getY();
|
||||
|
||||
/**
|
||||
@@ -19,9 +23,29 @@ public interface ChunkSection {
|
||||
* @return {@link Block}
|
||||
*/
|
||||
Block getBlock(int localX, int localY, int localZ);
|
||||
|
||||
/**
|
||||
* Установить блок
|
||||
* @param block {@link mc.core.world.block.Block}
|
||||
*/
|
||||
void setBlock(Block block);
|
||||
|
||||
/**
|
||||
* Получить данные о естественной подсветке
|
||||
* @param localX локальный X (0-15)
|
||||
* @param localY локальный Y (0-15)
|
||||
* @param localZ локальный Z (0-15)
|
||||
* @return integer значение 0-15, где 0 - это света нет, а 15 - получает прямой солнечный свет
|
||||
*/
|
||||
int getSkyLight(int localX, int localY, int localZ);
|
||||
|
||||
/**
|
||||
* Указать данные о естественной подсветке
|
||||
* @param localX локальный X (0-15)
|
||||
* @param localY локальный Y (0-15)
|
||||
* @param localZ локальный Z (0-15)
|
||||
* @param lightLevel значение 0-15, где 0 - это света нет, а 15 - получает прямой солнечный свет
|
||||
*/
|
||||
void setSkyLight(int localX, int localY, int localZ, int lightLevel);
|
||||
|
||||
int getAddition(int localX, int localY, int localZ);
|
||||
|
||||
@@ -1,25 +1,37 @@
|
||||
package mc.core.utils;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.Arguments;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
|
||||
import java.util.concurrent.ThreadLocalRandom;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
class CompactedCoordsTest {
|
||||
@Test
|
||||
void compressXZ() {
|
||||
ThreadLocalRandom random = ThreadLocalRandom.current();
|
||||
private static Stream<Arguments> streamTestParams() {
|
||||
return Stream.of(
|
||||
Arguments.of(Short.MIN_VALUE, Short.MIN_VALUE),
|
||||
Arguments.of(Short.MIN_VALUE, Short.MAX_VALUE),
|
||||
Arguments.of(Short.MAX_VALUE, Short.MAX_VALUE),
|
||||
Arguments.of(Short.MAX_VALUE, Short.MIN_VALUE),
|
||||
Arguments.of(0, 0),
|
||||
Arguments.of(-1, -1),
|
||||
Arguments.of(-1, 1),
|
||||
Arguments.of(1, 1),
|
||||
Arguments.of(1, -1)
|
||||
);
|
||||
}
|
||||
|
||||
for (int i = 0; i < 100; i++) {
|
||||
final int x = random.nextInt(Short.MIN_VALUE, Short.MAX_VALUE);
|
||||
final int z = random.nextInt(Short.MIN_VALUE, Short.MAX_VALUE);
|
||||
@ParameterizedTest
|
||||
@MethodSource("streamTestParams")
|
||||
void testCompress(int x, int z) {
|
||||
final int compressXZ = CompactedCoords.compressXZ(x, z);
|
||||
int[] xz = CompactedCoords.uncompressXZ(compressXZ);
|
||||
|
||||
final int compressXZ = CompactedCoords.compressXZ(x, z);
|
||||
int[] xz = CompactedCoords.uncompressXZ(compressXZ);
|
||||
|
||||
assertEquals(x, xz[0]);
|
||||
assertEquals(z, xz[1]);
|
||||
}
|
||||
assertTrue(x == xz[0] && z == xz[1],
|
||||
String.format("x = %d, vx = %d; z = %d, vz = %d",
|
||||
x, xz[0],
|
||||
z, xz[1]));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,4 +6,5 @@ dependencies {
|
||||
|
||||
/* Components */
|
||||
compile (group: 'com.google.code.gson', name: 'gson', version: '2.8.5')
|
||||
compile (group: 'net.sf.trove4j', name: 'trove4j', version: '3.0.3')
|
||||
}
|
||||
|
||||
@@ -1,18 +1,19 @@
|
||||
/*
|
||||
* DmitriyMX <dimon550@gmail.com>
|
||||
* 2018-07-25
|
||||
*/
|
||||
package mc.core.network.proto_1_12_2;
|
||||
|
||||
import com.flowpowered.nbt.Tag;
|
||||
import com.flowpowered.nbt.stream.NBTInputStream;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import mc.core.network.NetInputStream;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
@Slf4j
|
||||
public abstract class NetInputStream_p340 extends NetInputStream {
|
||||
private NBTInputStream nbtInputStream;
|
||||
|
||||
@Override
|
||||
public int readVarInt(AtomicInteger countReadBytes) {
|
||||
int numRead = 0;
|
||||
@@ -59,4 +60,23 @@ public abstract class NetInputStream_p340 extends NetInputStream {
|
||||
public UUID readUUID() {
|
||||
return new UUID(readLong(), readLong());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Tag<?> readNBT() {
|
||||
if (nbtInputStream == null) {
|
||||
try {
|
||||
nbtInputStream = new NBTInputStream(this, false);
|
||||
} catch (IOException e) {
|
||||
log.error("Create NBT stream", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
return nbtInputStream.readTag();
|
||||
} catch (IOException e) {
|
||||
log.error("Read NBT", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,18 @@
|
||||
package mc.core.network.proto_1_12_2;
|
||||
|
||||
import com.flowpowered.nbt.Tag;
|
||||
import com.flowpowered.nbt.stream.NBTOutputStream;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import mc.core.network.NetOutputStream;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.UUID;
|
||||
|
||||
@Slf4j
|
||||
public abstract class NetOutputStream_p340 extends NetOutputStream {
|
||||
private NBTOutputStream nbtOutputStream;
|
||||
|
||||
@Override
|
||||
public void writeVarInt(int value) {
|
||||
while ((value & -128) != 0) {
|
||||
@@ -37,4 +42,22 @@ public abstract class NetOutputStream_p340 extends NetOutputStream {
|
||||
writeLong(uuid.getMostSignificantBits());
|
||||
writeLong(uuid.getLeastSignificantBits());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeNBT(Tag<?> tag) {
|
||||
if (nbtOutputStream == null) {
|
||||
try {
|
||||
nbtOutputStream = new NBTOutputStream(this, false);
|
||||
} catch (IOException e) {
|
||||
log.error("Create NBT stream", e);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
nbtOutputStream.writeTag(tag);
|
||||
} catch (IOException e) {
|
||||
log.error("Write NBT", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,16 +1,17 @@
|
||||
/*
|
||||
* DmitriyMX <dimon550@gmail.com>
|
||||
* 2018-07-21
|
||||
*/
|
||||
package mc.core.network.proto_1_12_2.packets;
|
||||
|
||||
import com.flowpowered.nbt.CompoundTag;
|
||||
import gnu.trove.list.TIntList;
|
||||
import gnu.trove.list.array.TIntArrayList;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import mc.core.network.NetOutputStream;
|
||||
import mc.core.network.SCPacket;
|
||||
import mc.core.network.proto_1_12_2.ByteArrayOutputNetStream;
|
||||
import mc.core.utils.NibbleArray;
|
||||
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;
|
||||
@@ -80,10 +81,6 @@ public class ChunkDataPacket implements SCPacket {
|
||||
private Chunk chunk;
|
||||
private List<ChunkSection> sectionList;
|
||||
|
||||
private int serializeBlockState(BlockType blockType) {
|
||||
return (blockType.getId() << 4) | blockType.getMeta();
|
||||
}
|
||||
|
||||
public void setChunk(Chunk chunk) {
|
||||
this.sectionList = null;
|
||||
this.chunk = chunk;
|
||||
@@ -96,13 +93,18 @@ public class ChunkDataPacket implements SCPacket {
|
||||
|
||||
@Override
|
||||
public void writeSelf(NetOutputStream netStream) {
|
||||
if (sectionList == null && chunk == null) {
|
||||
log.warn("Empty chunk data!"); //TODO для такого нужна заглушка
|
||||
return;
|
||||
}
|
||||
|
||||
netStream.writeInt(x); // Chunk X
|
||||
netStream.writeInt(z); // Chunk Y
|
||||
netStream.writeBoolean(initChunk); // Init Chunk
|
||||
|
||||
int maxH = 0;
|
||||
int bitMask = 0;
|
||||
if (sectionList == null && chunk != null) {
|
||||
int bitMask = 0;
|
||||
for (int h = 15; h >= 0; h--) {
|
||||
bitMask = bitMask << 1;
|
||||
ChunkSection chunkSection = chunk.getChunkSection(h);
|
||||
@@ -113,11 +115,8 @@ public class ChunkDataPacket implements SCPacket {
|
||||
bitMask |= 0x00;
|
||||
}
|
||||
}
|
||||
|
||||
netStream.writeVarInt(bitMask); // Primary Bit Mask
|
||||
} else if (sectionList != null && chunk == null) {
|
||||
sectionList.sort(Comparator.comparingInt(ChunkSection::getY));
|
||||
int bitMask = 0;
|
||||
for (int h = 15, i = 0; h >= 0; h--) {
|
||||
bitMask = bitMask << 1;
|
||||
ChunkSection chunkSection = sectionList.get(i);
|
||||
@@ -128,16 +127,15 @@ public class ChunkDataPacket implements SCPacket {
|
||||
bitMask |= 0x00;
|
||||
}
|
||||
}
|
||||
|
||||
netStream.writeVarInt(bitMask); // Primary Bit Mask
|
||||
} else {
|
||||
log.warn("Empty chunk data");
|
||||
return;
|
||||
}
|
||||
netStream.writeVarInt(bitMask); // Primary Bit Mask
|
||||
|
||||
final ByteArrayOutputNetStream data = new ByteArrayOutputNetStream();
|
||||
int dataItems = 0;
|
||||
final int airBlockPalette = serializeBlockState(BlockType.AIR);
|
||||
|
||||
final ByteArrayOutputNetStream biomes = new ByteArrayOutputNetStream();
|
||||
boolean biomeWrite = true;
|
||||
|
||||
List<CompoundTag> nbtList = new ArrayList<>();
|
||||
|
||||
for (int h = 0; h < maxH; h++) {
|
||||
ChunkSection chunkSection = null;
|
||||
@@ -152,64 +150,30 @@ public class ChunkDataPacket implements SCPacket {
|
||||
continue;
|
||||
}
|
||||
|
||||
final List<Integer> palette = new ArrayList<>();
|
||||
palette.add(airBlockPalette);
|
||||
final ByteArrayOutputNetStream dataArray = new ByteArrayOutputNetStream();
|
||||
final ByteArrayOutputNetStream blockLight = new ByteArrayOutputNetStream();
|
||||
final ByteArrayOutputNetStream skyLight = new ByteArrayOutputNetStream();
|
||||
final ByteArrayOutputNetStream biomes = new ByteArrayOutputNetStream();
|
||||
|
||||
long dataValueCompacted = 0;
|
||||
int blockLightCompacted = 0;
|
||||
int skyLightCompacted = 0;
|
||||
|
||||
int idxHalfLong = 0;
|
||||
int idxHalfByte = 0;
|
||||
boolean biomeFinally = false;
|
||||
final PalettedChunkSection palettedChunkSection = new PalettedChunkSection();
|
||||
|
||||
for (int y = 0; y < 16; y++) {
|
||||
for (int z = 0; z < 16; z++) {
|
||||
for (int x = 0; x < 16; x++) {
|
||||
Block block = chunkSection.getBlock(x, y, z);
|
||||
int blockState = serializeBlockState(block.getType());
|
||||
|
||||
int currentIndexPaletteBlock;
|
||||
if (!palette.contains(blockState)) {
|
||||
palette.add(blockState);
|
||||
currentIndexPaletteBlock = palette.size()-1;
|
||||
} else {
|
||||
currentIndexPaletteBlock = palette.indexOf(blockState);
|
||||
palettedChunkSection.addBlock(
|
||||
block,
|
||||
chunkSection.getSkyLight(x, y, z)
|
||||
);
|
||||
|
||||
CompoundTag nbt = block.getNBTData();
|
||||
if (nbt != null) {
|
||||
nbtList.add(nbt);
|
||||
}
|
||||
|
||||
if (idxHalfLong == 0) {
|
||||
dataValueCompacted = currentIndexPaletteBlock;
|
||||
idxHalfLong++;
|
||||
} else if (idxHalfLong > 0 && idxHalfLong < 15) {
|
||||
dataValueCompacted = (dataValueCompacted << 4) | currentIndexPaletteBlock;
|
||||
idxHalfLong++;
|
||||
} else {
|
||||
dataValueCompacted = (dataValueCompacted << 4) | currentIndexPaletteBlock;
|
||||
dataArray.writeLong(dataValueCompacted);
|
||||
idxHalfLong = 0;
|
||||
dataItems++;
|
||||
}
|
||||
|
||||
if (idxHalfByte == 0) {
|
||||
blockLightCompacted = block.getLight();
|
||||
skyLightCompacted = chunkSection.getSkyLight(x, y, z);
|
||||
idxHalfByte++;
|
||||
} else {
|
||||
blockLightCompacted = (blockLightCompacted << 4) | block.getLight();
|
||||
blockLight.writeByte(blockLightCompacted);
|
||||
skyLightCompacted = (skyLightCompacted << 4) | chunkSection.getSkyLight(x, y, z);
|
||||
skyLight.writeByte(skyLightCompacted);
|
||||
idxHalfByte = 0;
|
||||
}
|
||||
|
||||
if (!biomeFinally) {
|
||||
biomes.writeByte(chunk.getBiome(x, z).getId());
|
||||
if (biomeWrite) {
|
||||
biomes.writeByte(chunk.getBiome(
|
||||
(chunk.getX() << 4) + x,
|
||||
(chunk.getZ() << 4) + z
|
||||
).getId());
|
||||
if (x == 15 && z == 15) {
|
||||
biomeFinally = true;
|
||||
biomeWrite = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -217,30 +181,137 @@ public class ChunkDataPacket implements SCPacket {
|
||||
}
|
||||
|
||||
// <Chunk Section>
|
||||
// <Palette>
|
||||
data.writeUnsignedByte(4); // Bits Per Block
|
||||
data.writeVarInt(palette.size()); // Size of palette
|
||||
palette.forEach(data::writeVarInt); // Palette
|
||||
// </Palette>
|
||||
// <Data Array>
|
||||
data.writeVarInt(dataItems); // Size of Data Array
|
||||
data.writeBytes(dataArray.toByteArray()); // Data Array
|
||||
// </Data Array>
|
||||
// <Block Light>
|
||||
data.writeBytes(blockLight.toByteArray());
|
||||
// </Block Light>
|
||||
// <Sky Light>
|
||||
data.writeBytes(skyLight.toByteArray());
|
||||
// </Sky Light>
|
||||
palettedChunkSection.writeToNetStream(data);
|
||||
// </Chunk Section>
|
||||
// <Biomes>
|
||||
data.writeBytes(biomes.toByteArray());
|
||||
// </Biomes>
|
||||
}
|
||||
// <Biomes>
|
||||
data.writeBytes(biomes.toByteArray());
|
||||
// </Biomes>
|
||||
|
||||
netStream.writeVarInt(data.size()); // Size of Data
|
||||
netStream.writeBytes(data.toByteArray()); // Data
|
||||
netStream.writeVarInt(0); // Number of block entities
|
||||
/* writeNBT */
|
||||
netStream.writeVarInt(nbtList.size()); // Number of block entities
|
||||
// <NBT>
|
||||
for (CompoundTag compoundTag : nbtList) {
|
||||
netStream.writeNBT(compoundTag);
|
||||
}
|
||||
// </NBT>
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ChunkDataPacket{" +
|
||||
"x=" + x +
|
||||
", z=" + z +
|
||||
", chunk=" + chunk +
|
||||
'}';
|
||||
}
|
||||
|
||||
private class PalettedChunkSection {
|
||||
private TIntList palette = new TIntArrayList();
|
||||
private byte[] blocks = new byte[4096];
|
||||
private NibbleArray blockLight = new NibbleArray();
|
||||
private NibbleArray skyLight = new NibbleArray();
|
||||
|
||||
private int coordsToIndex(BlockLocation location) {
|
||||
return coordsToIndex(location.getX(), location.getY(), location.getZ());
|
||||
}
|
||||
|
||||
private int coordsToIndex(int x, int y, int z) {
|
||||
return y << 8 | z << 4 | x;
|
||||
}
|
||||
|
||||
private int serializeBlockState(BlockType blockType) {
|
||||
return (blockType.getId() << 4) | blockType.getMeta();
|
||||
}
|
||||
|
||||
byte addBlockType(BlockType blockType) {
|
||||
int blockState = serializeBlockState(blockType);
|
||||
|
||||
int idx = palette.indexOf(blockState);
|
||||
if (idx == -1) {
|
||||
palette.add(blockState);
|
||||
idx = palette.size()-1;
|
||||
}
|
||||
|
||||
return (byte) idx;
|
||||
}
|
||||
|
||||
void addBlock(Block block, int skyLight) {
|
||||
BlockLocation location = new BlockLocation(
|
||||
block.getLocation().getX() - ((block.getLocation().getX() >> 4) << 4),
|
||||
block.getLocation().getY() - ((block.getLocation().getY() >> 4) << 4),
|
||||
block.getLocation().getZ() - ((block.getLocation().getZ() >> 4) << 4)
|
||||
);
|
||||
blocks[coordsToIndex(location)] = addBlockType(block.getType());
|
||||
blockLight.set(location, block.getLight());
|
||||
this.skyLight.set(location, skyLight);
|
||||
}
|
||||
|
||||
void writeToNetStream(final NetOutputStream netOutputStream) {
|
||||
int bitsPerBlock = 4;
|
||||
if (palette.size() > 15) {
|
||||
if (palette.size() <= 31)
|
||||
bitsPerBlock = 5;
|
||||
else if (palette.size() <= 63)
|
||||
bitsPerBlock = 6;
|
||||
else if (palette.size() <= 127)
|
||||
bitsPerBlock = 7;
|
||||
else if (palette.size() <= 255)
|
||||
bitsPerBlock = 8;
|
||||
}
|
||||
|
||||
// <Palette>
|
||||
netOutputStream.writeUnsignedByte(bitsPerBlock); // Bits Per Block
|
||||
netOutputStream.writeVarInt(palette.size()); // Size of palette
|
||||
palette.forEach(value -> { netOutputStream.writeVarInt(value); return true; }); // Palette
|
||||
// </Palette>
|
||||
// <Data Array>
|
||||
final int dataLength = (4096/*16*16*16*/ * bitsPerBlock) / 64/*size of long in bits*/;
|
||||
netOutputStream.writeVarInt(dataLength); // Size of Data Array
|
||||
// <Array>
|
||||
long value = 0;
|
||||
int lastPos = 0;
|
||||
boolean fairy = false;
|
||||
long fairyValue = 0;
|
||||
for (int y = 0; y < 16; y++) {
|
||||
for (int z = 0; z < 16; z++) {
|
||||
for (int x = 0; x < 16; x++) {
|
||||
final int blockNumber = (((y << 4) + z) << 4) + x;
|
||||
final int startLong = ( blockNumber * bitsPerBlock ) / 64;
|
||||
final int startOffset = ( blockNumber * bitsPerBlock ) % 64;
|
||||
final int endLong = ((blockNumber + 1) * bitsPerBlock - 1) / 64;
|
||||
|
||||
final long idxBlockInPalette = blocks[coordsToIndex(x, y, z)];
|
||||
|
||||
if (startLong != lastPos) {
|
||||
netOutputStream.writeLong(value);
|
||||
lastPos = startLong;
|
||||
if (fairy) {
|
||||
value = fairyValue;
|
||||
fairy = false;
|
||||
} else {
|
||||
value = 0;
|
||||
}
|
||||
}
|
||||
value |= (idxBlockInPalette << startOffset);
|
||||
|
||||
if (startLong != endLong) {
|
||||
fairyValue = idxBlockInPalette >> (64 - startOffset);
|
||||
fairy = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
netOutputStream.writeLong(value);
|
||||
// </Array>
|
||||
// </Data Array>
|
||||
// <Block Light>
|
||||
netOutputStream.writeBytes(blockLight.getRawData());
|
||||
// </Block Light>
|
||||
// <Sky Light>
|
||||
netOutputStream.writeBytes(skyLight.getRawData());
|
||||
// </Sky Light>
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ public class ByteArrayInputNetStream extends NetInputStream_p340 {
|
||||
|
||||
@Override
|
||||
public boolean readBoolean() {
|
||||
throw new UnsupportedOperationException();
|
||||
return readByte() != 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -39,7 +39,7 @@ public class ByteArrayInputNetStream extends NetInputStream_p340 {
|
||||
|
||||
@Override
|
||||
public int readUnsignedByte() {
|
||||
throw new UnsupportedOperationException();
|
||||
return bais.read() & 0xFF;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -8,6 +8,8 @@ import java.util.Random;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
class ByteArrayInputNetStreamTest {
|
||||
private Random random;
|
||||
@@ -17,6 +19,23 @@ class ByteArrayInputNetStreamTest {
|
||||
random = new Random(System.currentTimeMillis());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testReadBoolean() {
|
||||
ByteArrayOutputNetStream byteArrayOutputNetStream = new ByteArrayOutputNetStream();
|
||||
byteArrayOutputNetStream.writeBoolean(true);
|
||||
|
||||
ByteArrayInputNetStream byteArrayInputNetStream = new ByteArrayInputNetStream(byteArrayOutputNetStream.toByteArray());
|
||||
|
||||
assertTrue(byteArrayInputNetStream.readBoolean());
|
||||
|
||||
byteArrayOutputNetStream = new ByteArrayOutputNetStream();
|
||||
byteArrayOutputNetStream.writeBoolean(false);
|
||||
|
||||
byteArrayInputNetStream = new ByteArrayInputNetStream(byteArrayOutputNetStream.toByteArray());
|
||||
|
||||
assertFalse(byteArrayInputNetStream.readBoolean());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testReadByte() throws IOException {
|
||||
final byte[] bytes = new byte[1];
|
||||
@@ -73,6 +92,23 @@ class ByteArrayInputNetStreamTest {
|
||||
assertEquals(5, r);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testReadUnsignedByte() {
|
||||
ByteArrayOutputNetStream byteArrayOutputNetStream = new ByteArrayOutputNetStream();
|
||||
byteArrayOutputNetStream.writeUnsignedByte(30);
|
||||
|
||||
ByteArrayInputNetStream byteArrayInputNetStream = new ByteArrayInputNetStream(byteArrayOutputNetStream.toByteArray());
|
||||
|
||||
assertEquals(30, byteArrayInputNetStream.readUnsignedByte());
|
||||
|
||||
byteArrayOutputNetStream = new ByteArrayOutputNetStream();
|
||||
byteArrayOutputNetStream.writeUnsignedByte(130);
|
||||
|
||||
byteArrayInputNetStream = new ByteArrayInputNetStream(byteArrayOutputNetStream.toByteArray());
|
||||
|
||||
assertEquals(130, byteArrayInputNetStream.readUnsignedByte());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testReadInt() {
|
||||
final int integerDig = random.nextInt();
|
||||
|
||||
@@ -104,7 +104,7 @@ class ByteArrayOutputNetStreamTest {
|
||||
byteArrayOutputNetStream.writeShort(smallInt);
|
||||
|
||||
assertArrayEquals(new byte[]{ (byte) (smallInt >>> 8),
|
||||
(byte) smallInt },
|
||||
(byte) smallInt },
|
||||
byteArrayOutputNetStream.toByteArray());
|
||||
}
|
||||
|
||||
@@ -116,9 +116,9 @@ class ByteArrayOutputNetStreamTest {
|
||||
byteArrayOutputNetStream.writeInt(integerDig);
|
||||
|
||||
assertArrayEquals(new byte[]{ (byte) ((integerDig >>> 24) & 0xFF),
|
||||
(byte) ((integerDig >>> 16) & 0xFF),
|
||||
(byte) ((integerDig >>> 8) & 0xFF),
|
||||
(byte) (integerDig & 0xFF) },
|
||||
(byte) ((integerDig >>> 16) & 0xFF),
|
||||
(byte) ((integerDig >>> 8) & 0xFF),
|
||||
(byte) (integerDig & 0xFF) },
|
||||
byteArrayOutputNetStream.toByteArray());
|
||||
}
|
||||
|
||||
@@ -130,13 +130,13 @@ class ByteArrayOutputNetStreamTest {
|
||||
byteArrayOutputNetStream.writeLong(longDig);
|
||||
|
||||
assertArrayEquals(new byte[]{ (byte) ((longDig >>> 56) & 0xFF),
|
||||
(byte) ((longDig >>> 48) & 0xFF),
|
||||
(byte) ((longDig >>> 40) & 0xFF),
|
||||
(byte) ((longDig >>> 32) & 0xFF),
|
||||
(byte) ((longDig >>> 24) & 0xFF),
|
||||
(byte) ((longDig >>> 16) & 0xFF),
|
||||
(byte) ((longDig >>> 8) & 0xFF),
|
||||
(byte) (longDig & 0xFF) },
|
||||
(byte) ((longDig >>> 48) & 0xFF),
|
||||
(byte) ((longDig >>> 40) & 0xFF),
|
||||
(byte) ((longDig >>> 32) & 0xFF),
|
||||
(byte) ((longDig >>> 24) & 0xFF),
|
||||
(byte) ((longDig >>> 16) & 0xFF),
|
||||
(byte) ((longDig >>> 8) & 0xFF),
|
||||
(byte) (longDig & 0xFF) },
|
||||
byteArrayOutputNetStream.toByteArray());
|
||||
}
|
||||
|
||||
@@ -149,9 +149,9 @@ class ByteArrayOutputNetStreamTest {
|
||||
final int floatBits = Float.floatToIntBits(floatDig);
|
||||
|
||||
assertArrayEquals(new byte[]{ (byte) ((floatBits >>> 24) & 0xFF),
|
||||
(byte) ((floatBits >>> 16) & 0xFF),
|
||||
(byte) ((floatBits >>> 8) & 0xFF),
|
||||
(byte) (floatBits & 0xFF) },
|
||||
(byte) ((floatBits >>> 16) & 0xFF),
|
||||
(byte) ((floatBits >>> 8) & 0xFF),
|
||||
(byte) (floatBits & 0xFF) },
|
||||
byteArrayOutputNetStream.toByteArray());
|
||||
}
|
||||
|
||||
@@ -164,13 +164,13 @@ class ByteArrayOutputNetStreamTest {
|
||||
final long doubleBits = Double.doubleToLongBits(doubleDig);
|
||||
|
||||
assertArrayEquals(new byte[]{ (byte) ((doubleBits >>> 56) & 0xFF),
|
||||
(byte) ((doubleBits >>> 48) & 0xFF),
|
||||
(byte) ((doubleBits >>> 40) & 0xFF),
|
||||
(byte) ((doubleBits >>> 32) & 0xFF),
|
||||
(byte) ((doubleBits >>> 24) & 0xFF),
|
||||
(byte) ((doubleBits >>> 16) & 0xFF),
|
||||
(byte) ((doubleBits >>> 8) & 0xFF),
|
||||
(byte) (doubleBits & 0xFF) },
|
||||
(byte) ((doubleBits >>> 48) & 0xFF),
|
||||
(byte) ((doubleBits >>> 40) & 0xFF),
|
||||
(byte) ((doubleBits >>> 32) & 0xFF),
|
||||
(byte) ((doubleBits >>> 24) & 0xFF),
|
||||
(byte) ((doubleBits >>> 16) & 0xFF),
|
||||
(byte) ((doubleBits >>> 8) & 0xFF),
|
||||
(byte) (doubleBits & 0xFF) },
|
||||
byteArrayOutputNetStream.toByteArray());
|
||||
}
|
||||
|
||||
@@ -238,22 +238,22 @@ class ByteArrayOutputNetStreamTest {
|
||||
final long leastSignificantBits = uuid.getLeastSignificantBits();
|
||||
|
||||
assertArrayEquals(new byte[]{ (byte) ((mostSignificantBits >>> 56) & 0xFF),
|
||||
(byte) ((mostSignificantBits >>> 48) & 0xFF),
|
||||
(byte) ((mostSignificantBits >>> 40) & 0xFF),
|
||||
(byte) ((mostSignificantBits >>> 32) & 0xFF),
|
||||
(byte) ((mostSignificantBits >>> 24) & 0xFF),
|
||||
(byte) ((mostSignificantBits >>> 16) & 0xFF),
|
||||
(byte) ((mostSignificantBits >>> 8) & 0xFF),
|
||||
(byte) (mostSignificantBits & 0xFF),
|
||||
(byte) ((mostSignificantBits >>> 48) & 0xFF),
|
||||
(byte) ((mostSignificantBits >>> 40) & 0xFF),
|
||||
(byte) ((mostSignificantBits >>> 32) & 0xFF),
|
||||
(byte) ((mostSignificantBits >>> 24) & 0xFF),
|
||||
(byte) ((mostSignificantBits >>> 16) & 0xFF),
|
||||
(byte) ((mostSignificantBits >>> 8) & 0xFF),
|
||||
(byte) (mostSignificantBits & 0xFF),
|
||||
|
||||
(byte) ((leastSignificantBits >>> 56) & 0xFF),
|
||||
(byte) ((leastSignificantBits >>> 48) & 0xFF),
|
||||
(byte) ((leastSignificantBits >>> 40) & 0xFF),
|
||||
(byte) ((leastSignificantBits >>> 32) & 0xFF),
|
||||
(byte) ((leastSignificantBits >>> 24) & 0xFF),
|
||||
(byte) ((leastSignificantBits >>> 16) & 0xFF),
|
||||
(byte) ((leastSignificantBits >>> 8) & 0xFF),
|
||||
(byte) (leastSignificantBits & 0xFF) },
|
||||
(byte) ((leastSignificantBits >>> 56) & 0xFF),
|
||||
(byte) ((leastSignificantBits >>> 48) & 0xFF),
|
||||
(byte) ((leastSignificantBits >>> 40) & 0xFF),
|
||||
(byte) ((leastSignificantBits >>> 32) & 0xFF),
|
||||
(byte) ((leastSignificantBits >>> 24) & 0xFF),
|
||||
(byte) ((leastSignificantBits >>> 16) & 0xFF),
|
||||
(byte) ((leastSignificantBits >>> 8) & 0xFF),
|
||||
(byte) (leastSignificantBits & 0xFF) },
|
||||
byteArrayOutputNetStream.toByteArray());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,312 @@
|
||||
package mc.core.network.proto_1_12_2.packets;
|
||||
|
||||
import com.flowpowered.nbt.*;
|
||||
import javafx.util.Pair;
|
||||
import mc.core.network.proto_1_12_2.ByteArrayOutputNetStream;
|
||||
import mc.core.network.proto_1_12_2.packets.DumbChunkData.DumbChunkSection;
|
||||
import mc.core.world.Biome;
|
||||
import mc.core.world.block.*;
|
||||
import mc.core.world.chunk.Chunk;
|
||||
import mc.core.world.chunk.ChunkSection;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
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.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.mockito.Matchers.anyInt;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
class ChunkDataPacketTest {
|
||||
private static List<Pair<DumbChunkData, DumbChunkData>> listOfParams;
|
||||
|
||||
private static DumbChunkData createExpectedData(String xz) throws IOException {
|
||||
InputStream inputStream = ChunkDataPacketTest.class.getResourceAsStream(String.format("ChunkDataPacket%s.bin", xz));
|
||||
assertNotNull(inputStream);
|
||||
return DumbChunkData.ReadFromNetInputStream(IOUtils.toByteArray(inputStream));
|
||||
}
|
||||
|
||||
private static Block createChestBlock00(BlockType type, int x, int y, int z, int height) {
|
||||
final BlockLocation location = new BlockLocation(x, y, z);
|
||||
|
||||
final CompoundMap compoundMap = new CompoundMap();
|
||||
compoundMap.put(new IntTag("x", x));
|
||||
compoundMap.put(new IntTag("y", (height << 4) + y));
|
||||
compoundMap.put(new IntTag("z", z));
|
||||
compoundMap.put(new StringTag("id", type.getNamedId()));
|
||||
final CompoundTag compoundTag = new CompoundTag("", compoundMap);
|
||||
|
||||
return new AbstractBlock(type) {
|
||||
@Override
|
||||
public BlockLocation getLocation() {
|
||||
return location;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompoundTag getNBTData() {
|
||||
return compoundTag;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private static ChunkSection createChunkSection00(int height) {
|
||||
final ChunkSection chunkSection = mock(ChunkSection.class);
|
||||
when(chunkSection.getSkyLight(anyInt(), anyInt(), anyInt())).thenReturn(0);
|
||||
when(chunkSection.getY()).thenReturn(height);
|
||||
|
||||
if (height == 0) {
|
||||
when(chunkSection.getBlock(anyInt(), anyInt(), anyInt())).thenAnswer(invocation -> {
|
||||
Object[] args = invocation.getArguments();
|
||||
final int x = (int) args[0];
|
||||
final int y = (int) args[1];
|
||||
final int z = (int) args[2];
|
||||
|
||||
BlockFactory blockFactory = new BlockFactory();
|
||||
|
||||
if (y == 0) {
|
||||
// @formatter:off
|
||||
if (x == 0 && z == 0) return blockFactory.create(BlockType.STONE, x, y, z);
|
||||
else if (x == 15 && z == 0) return blockFactory.create(BlockType.GRANITE, x, y, z);
|
||||
else if (x == 0 && z == 15) return blockFactory.create(BlockType.POLISHED_GRANITE, x, y, z);
|
||||
else if (x == 15 && z == 15) return blockFactory.create(BlockType.DIORITE, x, y, z);
|
||||
else return blockFactory.create(BlockType.BEDROCK, x, y, z);
|
||||
// @formatter:on
|
||||
} else {
|
||||
return blockFactory.create(BlockType.STONE, x, y, z);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
when(chunkSection.getBlock(anyInt(), anyInt(), anyInt())).thenAnswer(invocation -> {
|
||||
Object[] args = invocation.getArguments();
|
||||
final int x = (int) args[0];
|
||||
final int y = (int) args[1];
|
||||
final int z = (int) args[2];
|
||||
|
||||
BlockFactory blockFactory = new BlockFactory();
|
||||
|
||||
// @formatter:off
|
||||
if (y == 0) return blockFactory.create(BlockType.DIRT, x, y, z);
|
||||
else if (y == 1) return blockFactory.create(BlockType.GRASS, x, y, z);
|
||||
else if (y == 2) {
|
||||
if ((x == 2 || x == 4 || x == 5) && z == 1)
|
||||
return createChestBlock00(BlockType.CHEST_NORTH, x, y, z, height);
|
||||
else if ((x == 2 || x == 3 || x == 5) && z == 6)
|
||||
return createChestBlock00(BlockType.CHEST_SOUTH, x, y, z, height);
|
||||
else if (x == 1 && (z == 2 || z == 3 || z == 5))
|
||||
return createChestBlock00(BlockType.CHEST_WEST, x, y, z, height);
|
||||
else if (x == 6 && (z == 2 || z == 4 || z == 5))
|
||||
return createChestBlock00(BlockType.CHEST_EAST, x, y, z, height);
|
||||
else
|
||||
return blockFactory.create(BlockType.AIR, x, y, z);
|
||||
}
|
||||
else return blockFactory.create(BlockType.AIR, x, y, z);
|
||||
// @formatter:on
|
||||
});
|
||||
}
|
||||
|
||||
return chunkSection;
|
||||
}
|
||||
|
||||
private static Chunk createMockChunk00() {
|
||||
final ChunkSection chunkSection0 = createChunkSection00(0);
|
||||
final ChunkSection chunkSection1 = createChunkSection00(1);
|
||||
|
||||
final Chunk chunk = mock(Chunk.class);
|
||||
when(chunk.getX()).thenReturn(0);
|
||||
when(chunk.getZ()).thenReturn(0);
|
||||
when(chunk.getBiome(anyInt(), anyInt())).thenReturn(Biome.PLAINS);
|
||||
when(chunk.getChunkSection(0)).thenReturn(chunkSection0);
|
||||
when(chunk.getChunkSection(1)).thenReturn(chunkSection1);
|
||||
|
||||
return chunk;
|
||||
}
|
||||
|
||||
private static ChunkSection createChunkSection01() {
|
||||
final ChunkSection chunkSection = mock(ChunkSection.class);
|
||||
when(chunkSection.getSkyLight(anyInt(), anyInt(), anyInt())).thenReturn(0);
|
||||
when(chunkSection.getY()).thenReturn(0);
|
||||
|
||||
final List<BlockType> types = 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
|
||||
);
|
||||
|
||||
when(chunkSection.getBlock(anyInt(), anyInt(), anyInt())).thenAnswer(invocation -> {
|
||||
Object[] args = invocation.getArguments();
|
||||
final int x = (int) args[0];
|
||||
final int y = (int) args[1];
|
||||
final int z = (int) args[2];
|
||||
|
||||
BlockFactory blockFactory = new BlockFactory();
|
||||
|
||||
if (y == 0) {
|
||||
// @formatter:off
|
||||
if (x == 0 && z == 0) return blockFactory.create(BlockType.STONE, x, y, z);
|
||||
else if (x == 15 && z == 0) return blockFactory.create(BlockType.GRANITE, x, y, z);
|
||||
else if (x == 0 && z == 15) return blockFactory.create(BlockType.POLISHED_GRANITE, x, y, z);
|
||||
else if (x == 15 && z == 15) return blockFactory.create(BlockType.DIORITE, x, y, z);
|
||||
else return blockFactory.create(BlockType.BEDROCK, x, y, z);
|
||||
// @formatter:on
|
||||
} else if (y == 1) {
|
||||
return blockFactory.create(types.get(x), x, y, z);
|
||||
} else {
|
||||
return blockFactory.create(BlockType.AIR, x, y, z);
|
||||
}
|
||||
});
|
||||
|
||||
return chunkSection;
|
||||
}
|
||||
|
||||
private static Chunk createMockChunk01() {
|
||||
final ChunkSection chunkSection0 = createChunkSection01();
|
||||
|
||||
final Chunk chunk = mock(Chunk.class);
|
||||
when(chunk.getX()).thenReturn(0);
|
||||
when(chunk.getZ()).thenReturn(1);
|
||||
when(chunk.getBiome(anyInt(), anyInt())).thenReturn(Biome.PLAINS);
|
||||
when(chunk.getChunkSection(0)).thenReturn(chunkSection0);
|
||||
|
||||
return chunk;
|
||||
}
|
||||
|
||||
private static void verifyMock(Chunk chunk) {
|
||||
verify(chunk, atLeast(1)).getX();
|
||||
verify(chunk, atLeast(1)).getZ();
|
||||
verify(chunk, times(256)).getBiome(anyInt(), anyInt());
|
||||
verify(chunk, atLeast(2)).getChunkSection(anyInt());
|
||||
}
|
||||
|
||||
private static DumbChunkData createActualData(Chunk chunk) {
|
||||
ChunkDataPacket packet = new ChunkDataPacket();
|
||||
packet.setX(chunk.getX());
|
||||
packet.setZ(chunk.getZ());
|
||||
packet.setChunk(chunk);
|
||||
packet.setInitChunk(true);
|
||||
|
||||
ByteArrayOutputNetStream netStream = new ByteArrayOutputNetStream();
|
||||
packet.writeSelf(netStream);
|
||||
|
||||
verifyMock(chunk);
|
||||
|
||||
return DumbChunkData.ReadFromNetInputStream(netStream.toByteArray());
|
||||
}
|
||||
|
||||
@BeforeAll
|
||||
static void beforeClassTest() throws IOException {
|
||||
listOfParams = Arrays.asList(
|
||||
new Pair<>(createExpectedData("00"), createActualData(createMockChunk00())),
|
||||
new Pair<>(createExpectedData("01"), createActualData(createMockChunk01()))
|
||||
);
|
||||
}
|
||||
|
||||
private static Stream<Arguments> streamArguments() {
|
||||
return listOfParams.stream().map(pair -> Arguments.of(pair.getKey(), pair.getValue()));
|
||||
}
|
||||
|
||||
@DisplayName("testGeneral")
|
||||
@ParameterizedTest(name = "[{index}] {0}")
|
||||
@MethodSource("streamArguments")
|
||||
void testGeneral(DumbChunkData expected, DumbChunkData actual) {
|
||||
assertEquals(expected.getX(), actual.getX(), "X coord not equals");
|
||||
assertEquals(expected.getZ(), actual.getZ(), "Z coord not equals");
|
||||
assertEquals(expected.isInitChunk(), actual.isInitChunk(), "Flag init chunk not equals");
|
||||
assertEquals(expected.getBitMask(), actual.getBitMask(), "BitMask not equals");
|
||||
assertArrayEquals(expected.getBiomes(), actual.getBiomes(), "Biomes not equals");
|
||||
}
|
||||
|
||||
@DisplayName("testNBT")
|
||||
@ParameterizedTest(name = "[{index}] {0}")
|
||||
@MethodSource("streamArguments")
|
||||
void testNBT(DumbChunkData expected, DumbChunkData actual) {
|
||||
assertEquals(expected.getNumberNBT(), actual.getNumberNBT());
|
||||
assertEquals(expected.getNbt().size(), actual.getNbt().size());
|
||||
|
||||
for (Tag<?> tag : actual.getNbt()) {
|
||||
assertTrue(expected.getNbt().contains(tag));
|
||||
}
|
||||
}
|
||||
|
||||
@DisplayName("testData (disabled light test)")
|
||||
@ParameterizedTest(name = "[{index}] {0}")
|
||||
@MethodSource("streamArguments")
|
||||
void testData(DumbChunkData expected, DumbChunkData actual) {
|
||||
assertEquals(expected.getData().length, actual.getData().length);
|
||||
|
||||
for (int numberSection = 0; numberSection < expected.getData().length; numberSection++) {
|
||||
final DumbChunkSection expectedSection = expected.getData()[numberSection];
|
||||
final DumbChunkSection actualSection = actual.getData()[numberSection];
|
||||
|
||||
// Palette
|
||||
testPalette(expectedSection, actualSection, numberSection);
|
||||
|
||||
// Data
|
||||
testDataBlock(expectedSection, actualSection, numberSection);
|
||||
|
||||
// Block and Sky light
|
||||
// DISABLE //
|
||||
//testLight(expectedSection, actualSection, numberSection);
|
||||
}
|
||||
}
|
||||
|
||||
private void testPalette(DumbChunkSection expected, DumbChunkSection actual, int numberSection) {
|
||||
assertEquals(expected.getBitsPerBlock(), actual.getBitsPerBlock());
|
||||
|
||||
if (expected.getPalette().size() > actual.getPalette().size()) {
|
||||
for (int j = 0; j < actual.getPalette().size(); j++) {
|
||||
assertTrue(expected.getPalette().contains(
|
||||
actual.getPalette().get(j)
|
||||
), String.format("[%d] Palette not contains %s", numberSection, actual.getPalette().get(j)));
|
||||
}
|
||||
} else {
|
||||
for (int j = 0; j < expected.getPalette().size(); j++) {
|
||||
assertTrue(actual.getPalette().contains(
|
||||
expected.getPalette().get(j)
|
||||
), String.format("[%d] Palette not contains %s", numberSection, actual.getPalette().get(j)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void testDataBlock(DumbChunkSection expected, DumbChunkSection actual, int numberSection) {
|
||||
assertEquals(expected.getData().size(), actual.getData().size());
|
||||
|
||||
for (int j = 0; j < expected.getData().size(); j++) {
|
||||
assertEquals(
|
||||
expected.getData().get(j),
|
||||
actual.getData().get(j),
|
||||
String.format("[%d] Data (blocks)", numberSection)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private void testLight(DumbChunkSection expected, DumbChunkSection actual, int numberSection) {
|
||||
// Block light
|
||||
assertArrayEquals(expected.getBlockLight(), actual.getBlockLight(),
|
||||
String.format("[%d] Block light", numberSection));
|
||||
|
||||
// Sky light
|
||||
assertArrayEquals(expected.getSkyLight(), actual.getSkyLight(),
|
||||
String.format("[%d] Sky light", numberSection));
|
||||
}
|
||||
}
|
||||
@@ -1,93 +0,0 @@
|
||||
package mc.core.network.proto_1_12_2.packets;
|
||||
|
||||
import com.google.common.io.ByteStreams;
|
||||
import mc.core.network.proto_1_12_2.ByteArrayOutputNetStream;
|
||||
import mc.core.world.Biome;
|
||||
import mc.core.world.World;
|
||||
import mc.core.world.WorldType;
|
||||
import mc.core.world.block.BlockFactory;
|
||||
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.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.mockito.Matchers.anyInt;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
class ChunkdataPacketTest {
|
||||
private static byte[] expectedPacketData;
|
||||
private World world;
|
||||
|
||||
@BeforeAll
|
||||
static void beforeClassTest() throws IOException {
|
||||
InputStream inputStream = ChunkdataPacketTest.class.getResourceAsStream("ChunkDataPacket.bin");
|
||||
expectedPacketData = ByteStreams.toByteArray(inputStream);
|
||||
}
|
||||
|
||||
@BeforeEach
|
||||
void prepareWorld() {
|
||||
final ChunkSection chunkSection = mock(ChunkSection.class);
|
||||
when(chunkSection.getSkyLight(anyInt(), anyInt(), anyInt())).thenAnswer(invocation -> {
|
||||
int y = (int)invocation.getArguments()[1];
|
||||
|
||||
if (y <= 3) return 0;
|
||||
else return 15;
|
||||
});
|
||||
when(chunkSection.getBlock(anyInt(), anyInt(), anyInt())).thenAnswer(invocation -> {
|
||||
Object[] args = invocation.getArguments();
|
||||
int x = (int) args[0];
|
||||
int y = (int) args[1];
|
||||
int z = (int) args[2];
|
||||
|
||||
BlockFactory blockFactory = new BlockFactory();
|
||||
|
||||
if (y == 0) return blockFactory.create(BlockType.BEDROCK, x, y, z);
|
||||
else if (y >= 1 && y <= 2) return blockFactory.create(BlockType.DIRT, x, y, z);
|
||||
else if (y == 3) return blockFactory.create(BlockType.GRASS, x, y, z);
|
||||
else return blockFactory.create(BlockType.AIR, x, y, z);
|
||||
});
|
||||
|
||||
world = mock(World.class);
|
||||
when(world.getType()).thenReturn(WorldType.FLAT);
|
||||
when(world.getChunk(anyInt(), anyInt())).thenAnswer(invocation -> {
|
||||
Object[] args = invocation.getArguments();
|
||||
|
||||
Chunk chunk = mock(Chunk.class);
|
||||
when(chunk.getX()).thenReturn((int) args[0]);
|
||||
when(chunk.getZ()).thenReturn((int) args[1]);
|
||||
when(chunk.getBiome(anyInt(), anyInt())).thenReturn(Biome.PLAINS);
|
||||
when(chunk.getChunkSection(anyInt())).thenAnswer(invocation1 -> {
|
||||
int height = (int)invocation1.getArguments()[0];
|
||||
|
||||
if (height < 1) return chunkSection;
|
||||
else return null;
|
||||
});
|
||||
|
||||
return chunk;
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void writePacket() {
|
||||
ChunkDataPacket packet = new ChunkDataPacket();
|
||||
packet.setX(0);
|
||||
packet.setZ(0);
|
||||
packet.setChunk(world.getChunk(0, 0));
|
||||
packet.setInitChunk(true);
|
||||
|
||||
ByteArrayOutputNetStream netStream = new ByteArrayOutputNetStream();
|
||||
packet.writeSelf(netStream);
|
||||
byte[] actualPacketData = netStream.toByteArray();
|
||||
|
||||
assertEquals(expectedPacketData.length, actualPacketData.length);
|
||||
assertArrayEquals(expectedPacketData, actualPacketData);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
package mc.core.network.proto_1_12_2.packets;
|
||||
|
||||
import com.flowpowered.nbt.Tag;
|
||||
import lombok.AccessLevel;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import mc.core.network.proto_1_12_2.ByteArrayInputNetStream;
|
||||
import mc.core.world.block.BlockType;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.LongBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
@NoArgsConstructor(access = AccessLevel.PRIVATE)
|
||||
@Getter
|
||||
class DumbChunkData {
|
||||
private int x;
|
||||
private int z;
|
||||
private boolean initChunk;
|
||||
private int bitMask;
|
||||
|
||||
private int sizeOfData;
|
||||
private DumbChunkSection[] data;
|
||||
private byte[] biomes;
|
||||
|
||||
private int numberNBT;
|
||||
private List<Tag<?>> nbt;
|
||||
|
||||
private static BlockType deserializeBlockState(int blockState) {
|
||||
return BlockType.getByIdMeta(blockState >> 4, blockState & 0x0F);
|
||||
}
|
||||
|
||||
static DumbChunkData ReadFromNetInputStream(byte[] bytes) {
|
||||
ByteArrayInputNetStream netStream = new ByteArrayInputNetStream(bytes);
|
||||
|
||||
DumbChunkData dumbChunkData = new DumbChunkData();
|
||||
|
||||
dumbChunkData.x = netStream.readInt();
|
||||
dumbChunkData.z = netStream.readInt();
|
||||
dumbChunkData.initChunk = netStream.readBoolean();
|
||||
|
||||
dumbChunkData.bitMask = netStream.readVarInt();
|
||||
int countOfSections = 0;
|
||||
for (int shift = 0; shift < 8; shift++) {
|
||||
countOfSections += ((dumbChunkData.bitMask >> shift) & 0x01) > 0 ? 1 : 0;
|
||||
}
|
||||
|
||||
dumbChunkData.sizeOfData = netStream.readVarInt();
|
||||
|
||||
dumbChunkData.data = new DumbChunkSection[countOfSections];
|
||||
for (int c = 0; c < countOfSections; c++) {
|
||||
DumbChunkSection dumbChunkSection = new DumbChunkSection();
|
||||
|
||||
dumbChunkSection.bitsPerBlock = netStream.readUnsignedByte();
|
||||
int sizePalette = netStream.readVarInt();
|
||||
dumbChunkSection.palette = new ArrayList<>(sizePalette);
|
||||
for (int i = 0; i < sizePalette; i++) {
|
||||
dumbChunkSection.palette.add(deserializeBlockState(netStream.readVarInt()));
|
||||
}
|
||||
|
||||
final byte[] rawData = new byte[netStream.readVarInt() * 8];
|
||||
netStream.readBytes(rawData);
|
||||
LongBuffer data = ByteBuffer.wrap(rawData).asLongBuffer();
|
||||
|
||||
final int bitMask = (1 << dumbChunkSection.bitsPerBlock) - 1;
|
||||
dumbChunkSection.data = new ArrayList<>(4096);
|
||||
for (int y = 0; y < 16; y++) {
|
||||
for (int z = 0; z < 16; z++) {
|
||||
for (int x = 0; x < 16; x++) {
|
||||
final int blockNumber = (((y << 4) + z) << 4) + x;
|
||||
final int startLong = ( blockNumber * dumbChunkSection.bitsPerBlock ) / 64;
|
||||
final int startOffset = ( blockNumber * dumbChunkSection.bitsPerBlock ) % 64;
|
||||
final int endLong = ((blockNumber + 1) * dumbChunkSection.bitsPerBlock - 1) / 64;
|
||||
|
||||
int idxBlock;
|
||||
if (startLong == endLong) {
|
||||
idxBlock = (int)(data.get(startLong) >> startOffset);
|
||||
} else {
|
||||
int endOffset = 64 - startOffset;
|
||||
long mask = (1 << endOffset) - 1;
|
||||
idxBlock = (int)(((data.get(startLong) >> startOffset) & mask) | data.get(endLong) << endOffset);
|
||||
}
|
||||
|
||||
dumbChunkSection.data.add(dumbChunkSection.palette.get(idxBlock & bitMask));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dumbChunkSection.blockLight = new byte[2048];
|
||||
netStream.readBytes(dumbChunkSection.blockLight);
|
||||
dumbChunkSection.skyLight = new byte[2048];
|
||||
netStream.readBytes(dumbChunkSection.skyLight);
|
||||
|
||||
dumbChunkData.data[c] = dumbChunkSection;
|
||||
}
|
||||
|
||||
dumbChunkData.biomes = new byte[256];
|
||||
netStream.readBytes(dumbChunkData.biomes);
|
||||
|
||||
dumbChunkData.numberNBT = netStream.readVarInt();
|
||||
if (dumbChunkData.numberNBT > 0) {
|
||||
dumbChunkData.nbt = new ArrayList<>(dumbChunkData.numberNBT);
|
||||
for (int i = 0; i < dumbChunkData.numberNBT; i++) {
|
||||
dumbChunkData.nbt.add(netStream.readNBT());
|
||||
}
|
||||
} else {
|
||||
dumbChunkData.nbt = Collections.emptyList();
|
||||
}
|
||||
|
||||
return dumbChunkData;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "DumbChunkData{" +
|
||||
"x=" + x +
|
||||
", z=" + z +
|
||||
'}';
|
||||
}
|
||||
|
||||
@NoArgsConstructor(access = AccessLevel.PRIVATE)
|
||||
@Getter
|
||||
static class DumbChunkSection {
|
||||
private int bitsPerBlock;
|
||||
private List<BlockType> palette;
|
||||
|
||||
private List<BlockType> data;
|
||||
private byte[] blockLight;
|
||||
private byte[] skyLight;
|
||||
}
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
@@ -14,6 +14,7 @@ import mc.core.network.proto_1_12_2.State;
|
||||
import mc.core.network.proto_1_12_2.netty.wrappers.WrapperNetOutputStream;
|
||||
|
||||
import static mc.core.network.proto_1_12_2.netty.NettyServer.ATTR_STATE;
|
||||
import static org.slf4j.helpers.MessageFormatter.format;
|
||||
|
||||
@Slf4j
|
||||
public class PacketEncoder extends MessageToByteEncoder<SCPacket> {
|
||||
@@ -29,8 +30,12 @@ public class PacketEncoder extends MessageToByteEncoder<SCPacket> {
|
||||
|
||||
log.debug("Send {}:{}", state, packet);
|
||||
|
||||
NetOutputStream netStream = new WrapperNetOutputStream(out);
|
||||
netStream.writeVarInt(id);
|
||||
packet.writeSelf(netStream);
|
||||
try {
|
||||
NetOutputStream netStream = new WrapperNetOutputStream(out);
|
||||
netStream.writeVarInt(id);
|
||||
packet.writeSelf(netStream);
|
||||
} catch (Throwable t) {
|
||||
log.error(format("Error encoding packet {}:{}", state, packet).getMessage(), t);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import mc.core.eventbus.Subscriber;
|
||||
import mc.core.eventbus.events.SC_ChunkLoadEvent;
|
||||
import mc.core.eventbus.events.SC_ChunkUnloadEvent;
|
||||
import mc.core.eventbus.events.SC_PlayerMoveEvent;
|
||||
import mc.core.network.NetChannel;
|
||||
import mc.core.network.proto_1_12_2.TeleportManager;
|
||||
import mc.core.network.proto_1_12_2.packets.ChunkDataPacket;
|
||||
import mc.core.network.proto_1_12_2.packets.PlayerPositionAndLookPacket;
|
||||
@@ -27,9 +28,14 @@ public class PlayerEventListener {
|
||||
|
||||
@Subscriber
|
||||
public void playerChunkLoadHandler(SC_ChunkLoadEvent event) {
|
||||
if (event.getNeedLoadChunks().size() == 0) return;
|
||||
|
||||
final NetChannel channel = event.getPlayer().getChannel();
|
||||
|
||||
for(Integer compressXZ : event.getNeedLoadChunks()) {
|
||||
int[] xz = CompactedCoords.uncompressXZ(compressXZ);
|
||||
Chunk chunk = event.getPlayer().getWorld().getChunk(xz[0], xz[1]);
|
||||
if (chunk == null) continue;
|
||||
|
||||
ChunkDataPacket packet = new ChunkDataPacket();
|
||||
packet.setX(xz[0]);
|
||||
@@ -37,12 +43,18 @@ public class PlayerEventListener {
|
||||
packet.setInitChunk(true);
|
||||
packet.setChunk(chunk);
|
||||
|
||||
event.getPlayer().getChannel().writeAndFlush(packet);
|
||||
channel.write(packet);
|
||||
}
|
||||
|
||||
channel.flush();
|
||||
}
|
||||
|
||||
@Subscriber
|
||||
public void playerChunkUnloadHandler(SC_ChunkUnloadEvent event) {
|
||||
if (event.getNeedUnloadChunks().size() == 0) return;
|
||||
|
||||
final NetChannel channel = event.getPlayer().getChannel();
|
||||
|
||||
for(Integer compressXZ : event.getNeedUnloadChunks()) {
|
||||
int[] xz = CompactedCoords.uncompressXZ(compressXZ);
|
||||
|
||||
@@ -50,7 +62,9 @@ public class PlayerEventListener {
|
||||
packet.setX(xz[0]);
|
||||
packet.setZ(xz[1]);
|
||||
|
||||
event.getPlayer().getChannel().writeAndFlush(packet);
|
||||
channel.write(packet);
|
||||
}
|
||||
|
||||
channel.flush();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -108,9 +108,9 @@ public class LoginHandler extends AbstractStateHandler implements LoginStateHand
|
||||
playerData.setPing(0);
|
||||
playerData.setHasDisplayName(true);
|
||||
playerData.setDisplayName(Text.builder()
|
||||
.append(Text.of(TextColor.RED, TextStyle.BOLD, player.getName().substring(0,1)))
|
||||
.append(Text.of(TextColor.WHITE, player.getName().substring(1)))
|
||||
.build()
|
||||
.append(Text.of(TextColor.RED, TextStyle.BOLD, player.getName().substring(0,1)))
|
||||
.append(Text.of(TextColor.WHITE, player.getName().substring(1)))
|
||||
.build()
|
||||
);
|
||||
pkt5.getListPlayers().add(playerData);
|
||||
channel.writeAndFlush(pkt5);
|
||||
|
||||
@@ -6,3 +6,4 @@ include('h2_playermanager')
|
||||
include('vanilla_commands')
|
||||
include('proto_1.12.2') // Protocol 1.12.2
|
||||
include('proto_1.12.2_netty') // Protocol 1.12.2 (Netty impl.)
|
||||
include('anvil-loader') // Vanilla world loader (aka Anvil)
|
||||
|
||||
@@ -26,7 +26,6 @@ public class SimpleChunk implements Chunk {
|
||||
this.chunkSection.setParent(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Block getBlock(int x, int y, int z) {
|
||||
return chunkSection.getBlock(
|
||||
x - (x >> 4) << 4,
|
||||
|
||||
@@ -4,6 +4,7 @@ import com.google.common.collect.Lists;
|
||||
import mc.core.world.block.Block;
|
||||
import mc.core.world.block.BlockType;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.List;
|
||||
@@ -27,6 +28,7 @@ class SimpleChunkSectionTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
@Disabled
|
||||
void getBlock() {
|
||||
for (int y = 15; y >= 0; y--) {
|
||||
for (int x = 0; x < 16; x++) {
|
||||
|
||||
Reference in New Issue
Block a user