Merge branch 'release/limbo'
This commit is contained in:
@@ -1,13 +1,18 @@
|
||||
# MC-CORE
|
||||
|
||||

|
||||

|
||||

|
||||

|
||||
|
||||
Модульный **Minecraft** сервер.
|
||||
|
||||
## Модули
|
||||
|
||||
* **Core** - ядро сервера
|
||||
* **Proto 1.12.2** - описание протокола версии [1.12.2 (340)](https://wiki.vg/index.php?title=Protocol&oldid=14204)
|
||||
* **Proto 1.12.2 Netty** - реализация протокола на сетевом движке [Netty.IO](https://netty.io/)
|
||||
* **H2 Player manager** - хранение данных игроков в [H2 Database](http://www.h2database.com/)
|
||||
* **Simple world** - реализация простго генератора плоского (flat) мира
|
||||
* **Anvil loader** - загрузчик "ванильных" (vanilla, ["Anvil"](https://minecraft.gamepedia.com/Anvil_file_format)) карт Minecraft
|
||||
|
||||
## Сборка
|
||||
|
||||
|
||||
5
anvil-loader/README.MD
Normal file
5
anvil-loader/README.MD
Normal file
@@ -0,0 +1,5 @@
|
||||
# Anvil loader
|
||||
|
||||
Загрузчик "ванильных" (vanilla, ["Anvil"](https://minecraft.gamepedia.com/Anvil_file_format)) карт Minecraft.
|
||||
|
||||
Пример настройки можно посмотреть в файле `sample-config.xml`
|
||||
8
anvil-loader/build.gradle
Normal file
8
anvil-loader/build.gradle
Normal file
@@ -0,0 +1,8 @@
|
||||
version '0.1'
|
||||
|
||||
dependencies {
|
||||
/* Core */
|
||||
compile_excludeCopy project(':core')
|
||||
|
||||
compile (group: 'net.sf.trove4j', name: 'trove4j', version: '3.0.3')
|
||||
}
|
||||
28
anvil-loader/sample-config.xml
Normal file
28
anvil-loader/sample-config.xml
Normal file
@@ -0,0 +1,28 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:context="http://www.springframework.org/schema/context"
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/beans
|
||||
http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
|
||||
http://www.springframework.org/schema/context
|
||||
http://www.springframework.org/schema/context/spring-context.xsd">
|
||||
<context:annotation-config />
|
||||
|
||||
<bean id="spawnLocation" class="mc.core.EntityLocation">
|
||||
<constructor-arg index="0" type="double" value="8"/>
|
||||
<constructor-arg index="1" type="double" value="64"/>
|
||||
<constructor-arg index="2" type="double" value="8"/>
|
||||
<constructor-arg index="3" type="float" value="0"/>
|
||||
<constructor-arg index="4" type="float" value="0"/>
|
||||
</bean>
|
||||
|
||||
<!-- В качестве хранилища, используется модуль Simple world -->
|
||||
<bean id="simpleWorld" class="mc.world.simple.SimpleWorld">
|
||||
<property name="spawn" ref="spawnLocation"/>
|
||||
<property name="chunkProvider">
|
||||
<bean class="mc.world.anvil.AnvilChunkProvider">
|
||||
<constructor-arg index="0" type="java.lang.String" value="path/to/minecraft/maps/New world"/>
|
||||
</bean>
|
||||
</property>
|
||||
</bean>
|
||||
</beans>
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
72
anvil-loader/src/main/java/mc/world/anvil/AnvilBlock.java
Normal file
72
anvil-loader/src/main/java/mc/world/anvil/AnvilBlock.java
Normal file
@@ -0,0 +1,72 @@
|
||||
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);
|
||||
return BlockType.getByIdMeta(id & 0xFF, meta);
|
||||
}
|
||||
|
||||
@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 & 31) >> 4) << 4 | ((x & 31) >> 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,58 @@
|
||||
package mc.world.anvil;
|
||||
|
||||
import gnu.trove.list.TByteList;
|
||||
import gnu.trove.list.array.TByteArrayList;
|
||||
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 TByteArrayList();
|
||||
@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) {
|
||||
}
|
||||
}
|
||||
}
|
||||
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.
@@ -1,10 +1,24 @@
|
||||
# Core
|
||||
|
||||
Ядро сервера
|
||||
Ядро сервера.
|
||||
|
||||
Пример настройки можно посмотреть в файле `sample-config.xml`.
|
||||
|
||||
## Spring beans
|
||||
|
||||
### ConfigFromSpring
|
||||
### Разное
|
||||
|
||||
#### CoreEventListener
|
||||
|
||||
Стандартный обработчик системных событий.
|
||||
|
||||
**Bean example:**
|
||||
|
||||
```xml
|
||||
<bean class="mc.core.CoreEventListener"/>
|
||||
```
|
||||
|
||||
#### ConfigFromSpring
|
||||
|
||||
Настройка параметров сервера через конфигурацию "спринга".
|
||||
|
||||
@@ -25,20 +39,36 @@
|
||||
</bean>
|
||||
```
|
||||
|
||||
### GameLoop
|
||||
#### GameLoop
|
||||
|
||||
**Bean example:**
|
||||
|
||||
Доступные параметры:
|
||||
* `gameTimer` - бин, управляющий ходом времени
|
||||
* `percentWarnLowTps` - порог "низкого" значения TPS, в процентах
|
||||
|
||||
```xml
|
||||
<bean id="gameLoop" class="mc.core.GameLoop">
|
||||
<property name="gameTimer" ref="timeProcessor"/>
|
||||
<property name="percentWarnLowTps" value="15"/>
|
||||
</bean>
|
||||
```
|
||||
|
||||
### IdleTime
|
||||
#### SimpleChatProcessor
|
||||
|
||||
Простой обработчик чата.
|
||||
|
||||
**Implements:** `mc.core.chat.ChatProcessor`
|
||||
|
||||
**Bean example:**
|
||||
|
||||
```xml
|
||||
<bean id="chatProcessor" class="mc.core.chat.SimpleChatProcessor" />
|
||||
```
|
||||
|
||||
### Время
|
||||
|
||||
#### IdleTime
|
||||
|
||||
Игровое время суток застывает на указанной отметке.
|
||||
|
||||
@@ -55,7 +85,7 @@
|
||||
</bean>
|
||||
```
|
||||
|
||||
### TimePerTick
|
||||
#### TimePerTick
|
||||
|
||||
Игровое время суток соответствует игровым тикам (20 tps)
|
||||
|
||||
@@ -72,7 +102,7 @@
|
||||
</bean>
|
||||
```
|
||||
|
||||
### RealTime
|
||||
#### RealTime
|
||||
|
||||
Игровое время суток соответствует реальному времени
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
version '0.1'
|
||||
version '0.2'
|
||||
|
||||
apply plugin: 'maven'
|
||||
apply plugin: 'application'
|
||||
|
||||
28
core/sample-config.xml
Normal file
28
core/sample-config.xml
Normal file
@@ -0,0 +1,28 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:context="http://www.springframework.org/schema/context"
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/beans
|
||||
http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
|
||||
http://www.springframework.org/schema/context
|
||||
http://www.springframework.org/schema/context/spring-context.xsd">
|
||||
<context:annotation-config />
|
||||
|
||||
<bean id="config" class="mc.core.embedded.ConfigFromSpring">
|
||||
<property name="descriptionServer" value="MC Core - LIMBO"/>
|
||||
<property name="maxPlayers" value="1"/>
|
||||
</bean>
|
||||
|
||||
<bean id="timer" class="mc.core.time.IdleTime">
|
||||
<property name="gameTime" value="1000"/>
|
||||
</bean>
|
||||
|
||||
<bean class="mc.core.CoreEventListener"/>
|
||||
|
||||
<bean id="gameLoop" class="mc.core.GameLoop">
|
||||
<property name="gameTimer" ref="timer"/>
|
||||
<property name="percentWarnLowTps" value="15"/>
|
||||
</bean>
|
||||
|
||||
<bean id="chatProcessor" class="mc.core.chat.SimpleChatProcessor" />
|
||||
</beans>
|
||||
@@ -18,7 +18,7 @@ public class GameLoop extends Thread {
|
||||
private TimeProcessor gameTimer;
|
||||
|
||||
public GameLoop() {
|
||||
super();
|
||||
super("Game Loop Thread");
|
||||
setTps(20);
|
||||
setPercentWarnLowTps(5);
|
||||
}
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
package mc.core.chat;
|
||||
|
||||
import mc.core.player.Player;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
public interface CommandExecutor {
|
||||
String getName();
|
||||
Optional<String[]> getAliases();
|
||||
Optional<String> getUsage();
|
||||
String getDescription();
|
||||
void execute(Player sender, String... args);
|
||||
}
|
||||
@@ -1,90 +0,0 @@
|
||||
package mc.core.chat;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import mc.core.player.Player;
|
||||
import mc.core.text.Text;
|
||||
import mc.core.text.TextColor;
|
||||
import mc.core.text.TextTemplate;
|
||||
import org.slf4j.Marker;
|
||||
import org.slf4j.helpers.BasicMarkerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
@Slf4j
|
||||
public class CommanderChatProcessor extends SimpleChatProcessor {
|
||||
private static final Marker COMMAND_MARKER = new BasicMarkerFactory().getMarker("Command");
|
||||
private static final TextTemplate UNKNOW_COMMAND_MSG = TextTemplate.builder()
|
||||
.append(Text.of("Unknown command \"", TextColor.RED))
|
||||
.arg("command", TextColor.WHITE)
|
||||
.append(Text.of("\"", TextColor.RED))
|
||||
.build();
|
||||
@Autowired
|
||||
private ApplicationContext applicationContext;
|
||||
private Map<String, CommandExecutor> commands = new HashMap<>();
|
||||
|
||||
@PostConstruct
|
||||
public void init() {
|
||||
Map<String, CommandExecutor> beans = applicationContext.getBeansOfType(CommandExecutor.class);
|
||||
beans.values().forEach(commandExecutor -> {
|
||||
log.trace("Add command \"{}\" ({})", commandExecutor.getName(), commandExecutor.getClass().getName());
|
||||
if (commands.containsKey(commandExecutor.getName())) {
|
||||
log.warn("Override command \"{}\"", commandExecutor.getName());
|
||||
log.debug("{} -> {}",
|
||||
commands.get(commandExecutor.getName()).getClass().getName(),
|
||||
commandExecutor.getClass().getName()
|
||||
);
|
||||
}
|
||||
commands.put(commandExecutor.getName(), commandExecutor);
|
||||
|
||||
if (commandExecutor.getAliases().isPresent()) {
|
||||
Arrays.stream(commandExecutor.getAliases().get()).forEach(aliase -> {
|
||||
log.trace("Add aliase \"{}\" ({})", aliase, commandExecutor.getClass().getName());
|
||||
if (commands.containsKey(aliase)) {
|
||||
log.warn("Override aliase \"{}\"", aliase);
|
||||
log.debug("{} -> {}",
|
||||
commands.get(aliase).getClass().getName(),
|
||||
commandExecutor.getClass().getName()
|
||||
);
|
||||
}
|
||||
commands.put(aliase, commandExecutor);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
log.debug("Load {} commands", commands.size());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void process(Player player, String message) {
|
||||
if (message.startsWith("/")) {
|
||||
log.info(COMMAND_MARKER, "<{}> {}", player.getName(), message);
|
||||
|
||||
int idx = message.indexOf(' ');
|
||||
if (idx == -1) {
|
||||
idx = message.length();
|
||||
}
|
||||
|
||||
String command = message.substring(1, idx).toLowerCase();
|
||||
if (commands.containsKey(command)) {
|
||||
String[] args = message.substring(idx).split(" ");
|
||||
commands.get(command).execute(player, args);
|
||||
} else {
|
||||
player.getChannel().sendChatMessage(
|
||||
UNKNOW_COMMAND_MSG.apply("command", command),
|
||||
MessageType.SYSTEM_MESSAGE);
|
||||
}
|
||||
} else {
|
||||
super.process(player, message);
|
||||
}
|
||||
}
|
||||
|
||||
public Collection<CommandExecutor> getAllCommands() {
|
||||
return commands.values();
|
||||
}
|
||||
}
|
||||
@@ -41,10 +41,6 @@ public class NibbleArray {
|
||||
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;
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
package mc.core.world.block;
|
||||
|
||||
import com.google.common.collect.HashBasedTable;
|
||||
import com.google.common.collect.Table;
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
@Slf4j
|
||||
@@ -491,25 +494,29 @@ public enum BlockType {
|
||||
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()
|
||||
.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;
|
||||
|
||||
private static final Table<Integer, Integer, BlockType> typeTable = HashBasedTable.create();
|
||||
|
||||
static {
|
||||
Arrays.stream(BlockType.values())
|
||||
.forEach(blockType -> typeTable.put(blockType.id, blockType.meta, blockType));
|
||||
}
|
||||
|
||||
public static BlockType getByIdMeta(int id, int meta) {
|
||||
if (id < 0) {
|
||||
log.warn("Incorrect id \"{}\"", id);
|
||||
return BEDROCK;
|
||||
}
|
||||
|
||||
return Optional.ofNullable(typeTable.get(id, meta)).orElseGet(() -> {
|
||||
log.warn("Unknow block type: {}:{}", id, meta);
|
||||
return BEDROCK;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
5
h2_playermanager/README.MD
Normal file
5
h2_playermanager/README.MD
Normal file
@@ -0,0 +1,5 @@
|
||||
# H2 Player manager
|
||||
|
||||
Хранилище данных игроков на базе [H2 Database](http://www.h2database.com/).
|
||||
|
||||
Пример настройки можно посмотреть в файле `sample-config.xml`
|
||||
17
h2_playermanager/build.gradle
Normal file
17
h2_playermanager/build.gradle
Normal file
@@ -0,0 +1,17 @@
|
||||
version '0.1'
|
||||
|
||||
ext {
|
||||
spring_data_version = '2.1.0.RELEASE'
|
||||
}
|
||||
|
||||
dependencies {
|
||||
/* Core */
|
||||
compile_excludeCopy project(':core')
|
||||
|
||||
/* Spring */
|
||||
compile (group: 'org.springframework.data', name: 'spring-data-jpa', version: spring_data_version)
|
||||
|
||||
/* Database */
|
||||
compile (group: 'org.hibernate', name: 'hibernate-entitymanager', version: '5.3.6.Final')
|
||||
compile (group: 'com.h2database', name: 'h2', version: '1.4.197')
|
||||
}
|
||||
39
h2_playermanager/sample-config.xml
Normal file
39
h2_playermanager/sample-config.xml
Normal file
@@ -0,0 +1,39 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:context="http://www.springframework.org/schema/context"
|
||||
xmlns:jpa="http://www.springframework.org/schema/data/jpa"
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/beans
|
||||
http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
|
||||
http://www.springframework.org/schema/context
|
||||
http://www.springframework.org/schema/context/spring-context.xsd
|
||||
http://www.springframework.org/schema/data/jpa
|
||||
http://www.springframework.org/schema/data/jpa/spring-jpa.xsd">
|
||||
<context:annotation-config />
|
||||
<context:component-scan base-package="mc.core.h2db"/>
|
||||
<jpa:repositories base-package="mc.core.h2db.repository"/>
|
||||
|
||||
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
|
||||
<property name="driverClassName" value="org.h2.Driver"/>
|
||||
<property name="url" value="jdbc:h2:mem:test;DB_CLOSE_DELAY=-1"/>
|
||||
<property name="username" value="sa"/>
|
||||
<property name="password" value="s3cReT"/>
|
||||
</bean>
|
||||
|
||||
<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
|
||||
<property name="dataSource" ref="dataSource"/>
|
||||
<property name="persistenceProviderClass" value="org.hibernate.jpa.HibernatePersistenceProvider"/>
|
||||
<property name="packagesToScan" value="mc.core.h2db.entity"/>
|
||||
<property name="jpaProperties">
|
||||
<props>
|
||||
<prop key="hibernate.dialect">org.hibernate.dialect.H2Dialect</prop>
|
||||
<prop key="hibernate.show_sql">false</prop>
|
||||
<prop key="hibernate.hbm2ddl.auto">update</prop>
|
||||
</props>
|
||||
</property>
|
||||
</bean>
|
||||
|
||||
<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
|
||||
<property name="entityManagerFactory" ref="entityManagerFactory"/>
|
||||
</bean>
|
||||
</beans>
|
||||
63
h2_playermanager/src/main/java/mc/core/h2db/H2Player.java
Normal file
63
h2_playermanager/src/main/java/mc/core/h2db/H2Player.java
Normal file
@@ -0,0 +1,63 @@
|
||||
package mc.core.h2db;
|
||||
|
||||
import lombok.Data;
|
||||
import mc.core.EntityLocation;
|
||||
import mc.core.exception.ResourceUnloadedException;
|
||||
import mc.core.network.NetChannel;
|
||||
import mc.core.player.Player;
|
||||
import mc.core.player.PlayerSettings;
|
||||
import mc.core.world.World;
|
||||
|
||||
import java.lang.ref.Reference;
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
|
||||
@Data
|
||||
public class H2Player implements Player {
|
||||
private int id;
|
||||
private UUID uuid;
|
||||
private String name;
|
||||
private boolean online = false;
|
||||
private List<Integer> loadedChunks;
|
||||
private NetChannel channel;
|
||||
private EntityLocation location;
|
||||
private Reference<World> $refWorld;
|
||||
private boolean flying = false;
|
||||
private PlayerSettings settings;
|
||||
|
||||
@Override
|
||||
public World getWorld() {
|
||||
if ($refWorld == null) {
|
||||
return null;
|
||||
} else if ($refWorld.get() == null) {
|
||||
throw new ResourceUnloadedException("You're trying to get unloaded world");
|
||||
} else {
|
||||
return $refWorld.get();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setWorld(World world) {
|
||||
if (world == null) {
|
||||
this.$refWorld = null;
|
||||
} else {
|
||||
this.$refWorld = new WeakReference<>(world);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) return true;
|
||||
if (obj == null || getClass() != obj.getClass()) return false;
|
||||
H2Player player = (H2Player) obj;
|
||||
return id == player.id &&
|
||||
Objects.equals(uuid, player.uuid);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(id, uuid);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
package mc.core.h2db;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import lombok.Setter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import mc.core.EntityLocation;
|
||||
import mc.core.h2db.service.H2PlayerService;
|
||||
import mc.core.network.BroadcastNetChannel;
|
||||
import mc.core.network.NetChannel;
|
||||
import mc.core.player.Player;
|
||||
import mc.core.player.PlayerManager;
|
||||
import mc.core.player.PlayerSettings;
|
||||
import mc.core.world.World;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
@Slf4j
|
||||
@Component
|
||||
public class H2PlayerManager implements PlayerManager {
|
||||
@Setter
|
||||
@Autowired
|
||||
private H2PlayerService h2PlayerService;
|
||||
private Set<H2Player> playerList = Collections.synchronizedSet(new HashSet<>());
|
||||
|
||||
@Override
|
||||
public Player createPlayer(String name, EntityLocation location, World world) {
|
||||
H2Player h2Player = new H2Player();
|
||||
h2Player.setName(name);
|
||||
h2Player.setUuid(UUID.randomUUID());
|
||||
h2Player.setLocation(location.clone());
|
||||
h2Player.setLoadedChunks(new ArrayList<>());
|
||||
h2Player.setWorld(world);
|
||||
h2Player.setSettings(new PlayerSettings());
|
||||
|
||||
return h2PlayerService.save(h2Player);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void joinServer(Player player) {
|
||||
//TODO в дальнейшем следует именно этому методу передать функции инсерта в БД
|
||||
H2Player h2Player = (H2Player) player;
|
||||
playerList.add(h2Player);
|
||||
h2Player.setOnline(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void leftServer(Player player) {
|
||||
H2Player h2Player = (H2Player) player;
|
||||
h2PlayerService.save(h2Player);
|
||||
h2Player.setOnline(false);
|
||||
h2Player.getLoadedChunks().clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Player getPlayer(String name) {
|
||||
return playerList.stream()
|
||||
.filter(player -> player.getName().equals(name))
|
||||
.filter(H2Player::isOnline)
|
||||
.findFirst().orElse(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Player> getPlayers() {
|
||||
return playerList.stream()
|
||||
.filter(H2Player::isOnline)
|
||||
.collect(ImmutableList.toImmutableList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCountPlayers() {
|
||||
return (int) playerList.stream()
|
||||
.filter(H2Player::isOnline)
|
||||
.count();
|
||||
}
|
||||
|
||||
@Override
|
||||
public NetChannel getBroadcastChannel() {
|
||||
return new BroadcastNetChannel(
|
||||
playerList.stream()
|
||||
.filter(H2Player::isOnline)
|
||||
.map(player -> (Player)player)
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Player getOfflinePlayer(String name) {
|
||||
return playerList.stream()
|
||||
.filter(player -> player.getName().equals(name))
|
||||
.filter(player -> !player.isOnline())
|
||||
.findFirst().orElseGet(() -> h2PlayerService.getByName(name));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
package mc.core.h2db.entity;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import mc.core.EntityLocation;
|
||||
import mc.core.h2db.H2Player;
|
||||
import mc.core.world.World;
|
||||
import org.hibernate.annotations.GenericGenerator;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
|
||||
import javax.persistence.*;
|
||||
import java.util.UUID;
|
||||
|
||||
@Entity
|
||||
@Table(name = "players",
|
||||
indexes = {@Index(name = "idx_players_uuid", columnList = "uuid", unique = true),
|
||||
@Index(name = "idx_players_name", columnList = "name")})
|
||||
@NoArgsConstructor
|
||||
@Data
|
||||
public class H2PlayerEntity {
|
||||
@Id
|
||||
@GeneratedValue(generator = "increment")
|
||||
@GenericGenerator(name= "increment", strategy= "increment")
|
||||
@Column(nullable = false)
|
||||
private Long id;
|
||||
|
||||
@Column(length = 36, nullable = false)
|
||||
private String uuid;
|
||||
|
||||
@Column(length = 16, nullable = false)
|
||||
private String name;
|
||||
|
||||
@Column(name = "location_x", nullable = false)
|
||||
private Double locationX;
|
||||
|
||||
@Column(name = "location_y", nullable = false)
|
||||
private Double locationY;
|
||||
|
||||
@Column(name = "location_z", nullable = false)
|
||||
private Double locationZ;
|
||||
|
||||
@Column(name = "location_yaw", nullable = false)
|
||||
private Float locationYaw;
|
||||
|
||||
@Column(name = "location_pitch", nullable = false)
|
||||
private Float locationPitch;
|
||||
|
||||
@Column(name = "location_world", length = 64, nullable = false)
|
||||
private String locationWorld;
|
||||
|
||||
public H2PlayerEntity(H2Player player) {
|
||||
this.id = (long) player.getId();
|
||||
setUuid(player.getUuid().toString());
|
||||
setName(this.name = player.getName());
|
||||
this.locationX = player.getLocation().getX();
|
||||
this.locationY = player.getLocation().getY();
|
||||
this.locationZ = player.getLocation().getZ();
|
||||
this.locationYaw = player.getLocation().getYaw();
|
||||
this.locationPitch = player.getLocation().getPitch();
|
||||
this.locationWorld = player.getWorld().getName();
|
||||
}
|
||||
|
||||
public void setUuid(String uuid) {
|
||||
if (uuid == null || uuid.trim().isEmpty()) {
|
||||
this.uuid = null;
|
||||
} else {
|
||||
this.uuid = uuid;
|
||||
}
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
if (name == null || name.trim().isEmpty()) {
|
||||
this.name = null;
|
||||
} else {
|
||||
this.name = name;
|
||||
}
|
||||
}
|
||||
|
||||
public H2Player toPlayer(ApplicationContext context) {
|
||||
H2Player player = new H2Player();
|
||||
return toPlayer(player, context);
|
||||
}
|
||||
|
||||
public H2Player toPlayer(H2Player player, ApplicationContext context) {
|
||||
player.setId(this.id.intValue());
|
||||
player.setUuid(UUID.fromString(this.uuid));
|
||||
player.setName(this.name);
|
||||
if (player.getLocation() == null) {
|
||||
player.setLocation(new EntityLocation(
|
||||
this.locationX, this.locationY, this.locationZ,
|
||||
this.locationYaw, this.locationPitch
|
||||
));
|
||||
} else {
|
||||
player.getLocation().setXYZ(this.locationX, this.locationY, this.locationZ);
|
||||
player.getLocation().setYawPitch(this.locationYaw, this.locationPitch);
|
||||
}
|
||||
|
||||
player.setWorld(context.getBean(this.locationWorld, World.class));
|
||||
|
||||
return player;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package mc.core.h2db.repository;
|
||||
|
||||
import mc.core.h2db.entity.H2PlayerEntity;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
@Repository
|
||||
public interface H2PlayerEntityRepository extends JpaRepository<H2PlayerEntity, Long> {
|
||||
Optional<H2PlayerEntity> findByName(String name);
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package mc.core.h2db.service;
|
||||
|
||||
import mc.core.h2db.H2Player;
|
||||
|
||||
public interface H2PlayerService {
|
||||
H2Player save(H2Player player);
|
||||
void remove(H2Player player);
|
||||
|
||||
H2Player getByName(String name);
|
||||
H2Player getById(int id);
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package mc.core.h2db.service;
|
||||
|
||||
import mc.core.h2db.H2Player;
|
||||
import mc.core.h2db.entity.H2PlayerEntity;
|
||||
import mc.core.h2db.repository.H2PlayerEntityRepository;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
@Service
|
||||
public class H2PlayerServiceImpl implements H2PlayerService {
|
||||
@Autowired
|
||||
private ApplicationContext context;
|
||||
@Autowired
|
||||
private H2PlayerEntityRepository h2PlayerEntityRepository;
|
||||
|
||||
@Override
|
||||
public H2Player save(H2Player player) {
|
||||
H2PlayerEntity entity = new H2PlayerEntity(player);
|
||||
//TODO возможно имеет смысл здесь оптимизация
|
||||
//вместо toPlayer() сделать toPlayer(H2Player) который в существующий
|
||||
//будет дописывать/обновлять данные
|
||||
return h2PlayerEntityRepository.saveAndFlush(entity).toPlayer(player, context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove(H2Player player) {
|
||||
h2PlayerEntityRepository.deleteById((long) player.getId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public H2Player getByName(String name) {
|
||||
Optional<H2PlayerEntity> optEntity = h2PlayerEntityRepository.findByName(name);
|
||||
return optEntity.map(entiry -> entiry.toPlayer(context)).orElse(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public H2Player getById(int id) {
|
||||
Optional<H2PlayerEntity> optEntity = h2PlayerEntityRepository.findById((long) id);
|
||||
return optEntity.map(entiry -> entiry.toPlayer(context)).orElse(null);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,159 @@
|
||||
package mc.core.h2db;
|
||||
|
||||
import mc.core.EntityLocation;
|
||||
import mc.core.h2db.service.H2PlayerService;
|
||||
import mc.core.player.Player;
|
||||
import mc.core.world.World;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.test.annotation.DirtiesContext;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit.jupiter.SpringExtension;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
@ExtendWith(SpringExtension.class)
|
||||
@ContextConfiguration(classes = {TestSpringConfig.class})
|
||||
@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD)
|
||||
class H2PlayerManagerTest {
|
||||
@Autowired
|
||||
private H2PlayerService h2PlayerService;
|
||||
@Autowired
|
||||
private World mockWorld;
|
||||
@Autowired
|
||||
private H2PlayerManager playerManager;
|
||||
|
||||
@Test
|
||||
void createPlayer() {
|
||||
final String playerName = "NEW_PLAYER";
|
||||
final Player newPlayer = playerManager.createPlayer(playerName, EntityLocation.ZERO(), mockWorld);
|
||||
|
||||
assertNotNull(newPlayer);
|
||||
assertEquals(H2Player.class, newPlayer.getClass());
|
||||
assertTrue(newPlayer.getId() > 0);
|
||||
|
||||
final H2Player queryPlayer = h2PlayerService.getByName(playerName);
|
||||
assertTrue(queryPlayer.getId() > 0);
|
||||
|
||||
assertEquals(newPlayer, queryPlayer);
|
||||
assertEquals(newPlayer.getName(), queryPlayer.getName());
|
||||
assertEquals(newPlayer.getLocation(), queryPlayer.getLocation());
|
||||
assertEquals(newPlayer.getWorld(), queryPlayer.getWorld());
|
||||
}
|
||||
|
||||
@Test
|
||||
void joinServer() {
|
||||
assertEquals(0, playerManager.getCountPlayers());
|
||||
|
||||
final String playerName = "NEW_PLAYER";
|
||||
final Player player = playerManager.createPlayer(playerName, EntityLocation.ZERO(), mockWorld);
|
||||
playerManager.joinServer(player);
|
||||
|
||||
assertEquals(1, playerManager.getCountPlayers());
|
||||
assertTrue(player.isOnline());
|
||||
}
|
||||
|
||||
@Test
|
||||
void leftServer() {
|
||||
assertEquals(0, playerManager.getCountPlayers());
|
||||
|
||||
final String playerName = "NEW_PLAYER";
|
||||
final Player player = playerManager.createPlayer(playerName, EntityLocation.ZERO(), mockWorld);
|
||||
playerManager.joinServer(player);
|
||||
|
||||
assertEquals(1, playerManager.getCountPlayers());
|
||||
assertTrue(player.isOnline());
|
||||
|
||||
final int playerId = player.getId();
|
||||
|
||||
final String anotherName = "ANOTHER_NAME";
|
||||
((H2Player)player).setName(anotherName);
|
||||
|
||||
playerManager.leftServer(player);
|
||||
|
||||
assertEquals(0, playerManager.getCountPlayers());
|
||||
|
||||
assertFalse(player.isOnline());
|
||||
assertTrue(player.getLoadedChunks().isEmpty());
|
||||
|
||||
final H2Player queryPlayer = h2PlayerService.getById(playerId);
|
||||
|
||||
assertNotNull(queryPlayer);
|
||||
((H2Player)player).setId(playerId);
|
||||
assertEquals(player, queryPlayer);
|
||||
}
|
||||
|
||||
@Test
|
||||
void getPlayer() {
|
||||
assertEquals(0, playerManager.getCountPlayers());
|
||||
|
||||
final String playerName = "NEW_PLAYER";
|
||||
final Player player = playerManager.createPlayer(playerName, EntityLocation.ZERO(), mockWorld);
|
||||
assertNotNull(player);
|
||||
|
||||
playerManager.joinServer(player);
|
||||
|
||||
assertEquals(1, playerManager.getCountPlayers());
|
||||
|
||||
Player queryPlayer = playerManager.getPlayer(playerName);
|
||||
|
||||
assertEquals(player, queryPlayer);
|
||||
}
|
||||
|
||||
@Test
|
||||
void getPlayers() {
|
||||
assertEquals(0, playerManager.getCountPlayers());
|
||||
|
||||
final String playerName = "NEW_PLAYER";
|
||||
final Player player = playerManager.createPlayer(playerName, EntityLocation.ZERO(), mockWorld);
|
||||
assertNotNull(player);
|
||||
|
||||
playerManager.joinServer(player);
|
||||
|
||||
assertEquals(1, playerManager.getCountPlayers());
|
||||
|
||||
List<Player> players = playerManager.getPlayers();
|
||||
assertEquals(1, players.size());
|
||||
try {
|
||||
players.add(new H2Player());
|
||||
fail();
|
||||
} catch (Exception e) {
|
||||
assertTrue(true);
|
||||
}
|
||||
|
||||
assertEquals(player, players.get(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
void getCountPlayers() {
|
||||
assertEquals(0, playerManager.getCountPlayers());
|
||||
|
||||
final String playerName = "NEW_PLAYER";
|
||||
final Player player = playerManager.createPlayer(playerName, EntityLocation.ZERO(), mockWorld);
|
||||
assertNotNull(player);
|
||||
|
||||
playerManager.joinServer(player);
|
||||
|
||||
assertEquals(1, playerManager.getCountPlayers());
|
||||
|
||||
playerManager.leftServer(player);
|
||||
|
||||
assertEquals(0, playerManager.getCountPlayers());
|
||||
}
|
||||
|
||||
@Test
|
||||
void getOfflinePlayer() {
|
||||
final String playerName = "NEW_PLAYER";
|
||||
final Player player = playerManager.createPlayer(playerName, EntityLocation.ZERO(), mockWorld);
|
||||
playerManager.joinServer(player);
|
||||
playerManager.leftServer(player);
|
||||
|
||||
assertEquals(0, playerManager.getCountPlayers());
|
||||
|
||||
Player offlinePlayer = playerManager.getOfflinePlayer(playerName);
|
||||
assertEquals(player, offlinePlayer);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package mc.core.h2db;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotEquals;
|
||||
|
||||
class H2PlayerTest {
|
||||
@Test
|
||||
void equals_() {
|
||||
UUID uuid = UUID.randomUUID();
|
||||
|
||||
H2Player player1 = new H2Player();
|
||||
player1.setId(1);
|
||||
player1.setUuid(uuid);
|
||||
player1.setName("Player1");
|
||||
|
||||
H2Player player2 = new H2Player();
|
||||
player2.setId(1);
|
||||
player2.setUuid(uuid);
|
||||
player2.setName("Player2");
|
||||
|
||||
assertEquals(player1, player2);
|
||||
|
||||
player2.setId(2);
|
||||
|
||||
assertNotEquals(player1, player2);
|
||||
|
||||
player2.setId(1);
|
||||
player2.setUuid(UUID.randomUUID());
|
||||
|
||||
assertNotEquals(player1, player2);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
package mc.core.h2db;
|
||||
|
||||
import mc.core.h2db.service.H2PlayerService;
|
||||
import mc.core.world.World;
|
||||
import org.hibernate.jpa.HibernatePersistenceProvider;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.ComponentScan;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
|
||||
import org.springframework.jdbc.datasource.DriverManagerDataSource;
|
||||
import org.springframework.orm.jpa.JpaTransactionManager;
|
||||
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
|
||||
import org.springframework.transaction.annotation.EnableTransactionManagement;
|
||||
|
||||
import javax.persistence.EntityManagerFactory;
|
||||
import javax.sql.DataSource;
|
||||
import java.util.Properties;
|
||||
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@Configuration
|
||||
@EnableJpaRepositories
|
||||
@EnableTransactionManagement
|
||||
@ComponentScan("mc.core.h2db")
|
||||
public class TestSpringConfig {
|
||||
private static final String DATABASE_DRIVER = "org.h2.Driver";
|
||||
private static final String DATABASE_URL = "jdbc:h2:mem:test;DB_CLOSE_DELAY=-1";
|
||||
private static final String DATABASE_USERNAME = "sa";
|
||||
private static final String DATABASE_PASSWORD = "s3cReT";
|
||||
|
||||
static {
|
||||
System.setProperty("org.jboss.logging.provider", "slf4j");
|
||||
}
|
||||
|
||||
private Properties hibernateProp() {
|
||||
Properties properties = new Properties();
|
||||
properties.put("hibernate.dialect", "org.hibernate.dialect.H2Dialect");
|
||||
properties.put("hibernate.show_sql", "true");
|
||||
properties.put("hibernate.format_sql", "true");
|
||||
properties.put("hibernate.use_sql_comments", "true");
|
||||
properties.put("hibernate.hbm2ddl.auto", "create");
|
||||
|
||||
return properties;
|
||||
}
|
||||
|
||||
@Bean("mockWorld")
|
||||
public World mockWorld() {
|
||||
World mockWorld = mock(World.class);
|
||||
when(mockWorld.getName()).thenReturn("mockWorld");
|
||||
return mockWorld;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public DataSource dataSource() {
|
||||
DriverManagerDataSource dmds = new DriverManagerDataSource();
|
||||
dmds.setDriverClassName(DATABASE_DRIVER);
|
||||
dmds.setUrl(DATABASE_URL);
|
||||
dmds.setUsername(DATABASE_USERNAME);
|
||||
dmds.setPassword(DATABASE_PASSWORD);
|
||||
|
||||
return dmds;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public LocalContainerEntityManagerFactoryBean entityManagerFactory(DataSource dataSource) {
|
||||
LocalContainerEntityManagerFactoryBean entityManagerFactoryBean = new LocalContainerEntityManagerFactoryBean();
|
||||
entityManagerFactoryBean.setDataSource(dataSource);
|
||||
entityManagerFactoryBean.setPersistenceProviderClass(HibernatePersistenceProvider.class);
|
||||
entityManagerFactoryBean.setPackagesToScan("mc.core.h2db.entity");
|
||||
entityManagerFactoryBean.setJpaProperties(hibernateProp());
|
||||
|
||||
return entityManagerFactoryBean;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public JpaTransactionManager transactionManager(EntityManagerFactory entityManagerFactory) {
|
||||
JpaTransactionManager transactionManager = new JpaTransactionManager();
|
||||
transactionManager.setEntityManagerFactory(entityManagerFactory);
|
||||
|
||||
return transactionManager;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public H2PlayerManager h2PlayerManager(H2PlayerService h2PlayerService) {
|
||||
H2PlayerManager playerManager = new H2PlayerManager();
|
||||
playerManager.setH2PlayerService(h2PlayerService);
|
||||
return playerManager;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,155 @@
|
||||
package mc.core.h2db.service;
|
||||
|
||||
import mc.core.EntityLocation;
|
||||
import mc.core.h2db.H2Player;
|
||||
import mc.core.h2db.TestSpringConfig;
|
||||
import mc.core.world.World;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.test.annotation.DirtiesContext;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit.jupiter.SpringExtension;
|
||||
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ThreadLocalRandom;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
@ExtendWith(SpringExtension.class)
|
||||
@ContextConfiguration(classes = {TestSpringConfig.class})
|
||||
@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD)
|
||||
class H2PlayerServiceTest {
|
||||
@Autowired
|
||||
private H2PlayerService h2PlayerService;
|
||||
@Autowired
|
||||
private World world;
|
||||
|
||||
private H2Player buildPlayer() {
|
||||
final ThreadLocalRandom rnd = ThreadLocalRandom.current();
|
||||
final double minD = 0.0d, maxD = 10.0d;
|
||||
final float minF = 0.0f, maxF = 359.9f;
|
||||
final int minI = 1000, maxI = 9999;
|
||||
|
||||
final H2Player player = new H2Player();
|
||||
player.setUuid(UUID.randomUUID());
|
||||
player.setName("player" + rnd.nextInt(minI, maxI));
|
||||
player.setLocation(new EntityLocation(
|
||||
rnd.nextDouble(minD, maxD),
|
||||
rnd.nextDouble(minD, maxD),
|
||||
rnd.nextDouble(minD, maxD),
|
||||
rnd.nextFloat() * (maxF - minF) + minF,
|
||||
rnd.nextFloat() * (maxF - minF) + minF
|
||||
));
|
||||
player.setWorld(world);
|
||||
|
||||
return player;
|
||||
}
|
||||
|
||||
private void assertPlayers(H2Player expected, H2Player actual) {
|
||||
assertEquals(expected, actual);
|
||||
assertEquals(expected.getName(), actual.getName());
|
||||
assertEquals(expected.getLocation(), actual.getLocation());
|
||||
assertNotNull(actual.getWorld());
|
||||
assertEquals(expected.getWorld(), actual.getWorld());
|
||||
}
|
||||
|
||||
@Test
|
||||
void save() {
|
||||
H2Player player = buildPlayer();
|
||||
H2Player savedPlayer = h2PlayerService.save(player);
|
||||
|
||||
player.setId(savedPlayer.getId()); //FIXME костыль, однако
|
||||
assertPlayers(player, savedPlayer);
|
||||
}
|
||||
|
||||
@Test
|
||||
void save_NameEmpty() {
|
||||
assertThrows(Exception.class, () -> {
|
||||
H2Player player = buildPlayer();
|
||||
player.setName("");
|
||||
h2PlayerService.save(player);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void save_NameNull() {
|
||||
assertThrows(Exception.class, () -> {
|
||||
H2Player player = buildPlayer();
|
||||
player.setName(null);
|
||||
h2PlayerService.save(player);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void save_UuidNull() {
|
||||
assertThrows(Exception.class, () -> {
|
||||
H2Player player = buildPlayer();
|
||||
player.setUuid(null);
|
||||
h2PlayerService.save(player);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void save_LocationNull() {
|
||||
assertThrows(Exception.class, () -> {
|
||||
H2Player player = buildPlayer();
|
||||
player.setLocation(null);
|
||||
h2PlayerService.save(player);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void remove() {
|
||||
H2Player player = h2PlayerService.save(buildPlayer());
|
||||
h2PlayerService.remove(player);
|
||||
|
||||
H2Player player2 = h2PlayerService.getById(player.getId());
|
||||
assertNull(player2);
|
||||
}
|
||||
|
||||
@Test
|
||||
void remove_NotExists() {
|
||||
assertThrows(Exception.class, () -> {
|
||||
H2Player player = h2PlayerService.save(buildPlayer());
|
||||
h2PlayerService.remove(player);
|
||||
h2PlayerService.remove(player);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void getByName() {
|
||||
H2Player player = h2PlayerService.save(buildPlayer());
|
||||
|
||||
H2Player player2 = h2PlayerService.getByName(player.getName());
|
||||
assertPlayers(player, player2);
|
||||
}
|
||||
|
||||
@Test
|
||||
void getByName_NotExists() {
|
||||
assertNull(h2PlayerService.getByName("UNKNOW_PLAYER"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void getByName_Empty() {
|
||||
assertNull(h2PlayerService.getByName(""));
|
||||
}
|
||||
|
||||
@Test
|
||||
void getByName_Null() {
|
||||
assertNull(h2PlayerService.getByName(null));
|
||||
}
|
||||
|
||||
@Test
|
||||
void getById() {
|
||||
H2Player player = h2PlayerService.save(buildPlayer());
|
||||
|
||||
H2Player player2 = h2PlayerService.getById(player.getId());
|
||||
assertPlayers(player, player2);
|
||||
}
|
||||
|
||||
@Test
|
||||
void getById_NotExists() {
|
||||
assertNull(h2PlayerService.getById(9999));
|
||||
}
|
||||
}
|
||||
3
proto_1.12.2/README.MD
Normal file
3
proto_1.12.2/README.MD
Normal file
@@ -0,0 +1,3 @@
|
||||
# Protocol 1.12.2
|
||||
|
||||
Описание протокола версии [1.12.2 (340)](https://wiki.vg/index.php?title=Protocol&oldid=14204)
|
||||
9
proto_1.12.2/build.gradle
Normal file
9
proto_1.12.2/build.gradle
Normal file
@@ -0,0 +1,9 @@
|
||||
version '0.1'
|
||||
|
||||
dependencies {
|
||||
/* Core */
|
||||
compile_excludeCopy project(':core')
|
||||
|
||||
/* Components */
|
||||
compile (group: 'com.google.code.gson', name: 'gson', version: '2.8.5')
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
package mc.core.network.proto_1_12_2;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
|
||||
public class ByteArrayOutputNetStream extends NetOutputStream_p340 {
|
||||
private ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
|
||||
@Override
|
||||
public void writeBoolean(boolean value) {
|
||||
baos.write(value ? 1 : 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeByte(int value) {
|
||||
baos.write(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeUnsignedByte(int value) {
|
||||
baos.write((byte)(value & 0xFF));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeBytes(byte[] buffer, int offset, int lengtn) {
|
||||
baos.write(buffer, offset, lengtn);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeShort(int value) {
|
||||
baos.write((byte) (value >>> 8));
|
||||
baos.write((byte) value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeInt(int value) {
|
||||
baos.write((value >>> 24) & 0xFF);
|
||||
baos.write((value >>> 16) & 0xFF);
|
||||
baos.write((value >>> 8) & 0xFF);
|
||||
baos.write(value & 0xFF);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeLong(long value) {
|
||||
baos.write((int) ((value >>> 56) & 0xFF));
|
||||
baos.write((int) ((value >>> 48) & 0xFF));
|
||||
baos.write((int) ((value >>> 40) & 0xFF));
|
||||
baos.write((int) ((value >>> 32) & 0xFF));
|
||||
baos.write((int) ((value >>> 24) & 0xFF));
|
||||
baos.write((int) ((value >>> 16) & 0xFF));
|
||||
baos.write((int) ((value >>> 8) & 0xFF));
|
||||
baos.write((int) (value & 0xFF));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeFloat(float value) {
|
||||
writeInt(Float.floatToIntBits(value));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeDouble(double value) {
|
||||
writeLong(Double.doubleToLongBits(value));
|
||||
}
|
||||
|
||||
public int size() {
|
||||
return baos.size();
|
||||
}
|
||||
|
||||
public byte[] toByteArray() {
|
||||
return baos.toByteArray();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package mc.core.network.proto_1_12_2;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
public enum Direction {
|
||||
BOTTOM(0), // -Y
|
||||
TOP(1), // +Y
|
||||
NORTH(2), // -Z
|
||||
SOUTH(3), // +Z
|
||||
WEST(4), // -X
|
||||
EAST(5); // +X
|
||||
|
||||
public static Direction getById(final int id) {
|
||||
return Arrays.stream(Direction.values())
|
||||
.filter(direction -> direction.id == id)
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
}
|
||||
|
||||
@Getter
|
||||
private final int id;
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
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;
|
||||
int result = 0;
|
||||
byte read;
|
||||
do {
|
||||
if ((numRead+1) > 5) {
|
||||
log.warn("VarInt is too big");
|
||||
break;
|
||||
}
|
||||
read = readByte();
|
||||
int value = (read & 0b01111111);
|
||||
result |= (value << (7 * numRead));
|
||||
|
||||
numRead++;
|
||||
} while ((read & 0b10000000) != 0);
|
||||
|
||||
if (countReadBytes != null) {
|
||||
countReadBytes.set(numRead);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int readVarInt() {
|
||||
return readVarInt(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String readString() {
|
||||
int size = readVarInt();
|
||||
if (size == 0) {
|
||||
log.warn("String zero length??");
|
||||
return "";
|
||||
}
|
||||
|
||||
byte[] bytes = new byte[size];
|
||||
readBytes(bytes);
|
||||
return new String(bytes, StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
@Override
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
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) {
|
||||
writeByte(value & 127 | 128);
|
||||
value >>>= 7;
|
||||
}
|
||||
|
||||
writeByte(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeString(String value) {
|
||||
if (value.length() > Short.MAX_VALUE) {
|
||||
log.warn("String \"{}\" too long!", value);
|
||||
byte[] buf = value.substring(0, Short.MAX_VALUE).getBytes(StandardCharsets.UTF_8);
|
||||
writeVarInt(Short.MAX_VALUE);
|
||||
writeBytes(buf);
|
||||
} else {
|
||||
byte[] buf = value.getBytes(StandardCharsets.UTF_8);
|
||||
writeVarInt(value.length());
|
||||
writeBytes(buf);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeUUID(UUID uuid) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
package mc.core.network.proto_1_12_2;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import mc.core.network.CSPacket;
|
||||
import mc.core.network.SCPacket;
|
||||
import mc.core.network.proto_1_12_2.packets.*;
|
||||
import mc.core.network.proto_1_12_2.packets.clientside.*;
|
||||
import mc.core.network.proto_1_12_2.packets.serverside.*;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Для каждого состояния протокола имеется свой набор пакетов.
|
||||
*/
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
public enum State {
|
||||
/**
|
||||
* Не известная стадия.
|
||||
* Переход на этут стадию является следствием ошибки в работе протокола (умышленного или нет)
|
||||
*/
|
||||
UNKNOWN(-1, ImmutableMap.of(), ImmutableMap.of()),
|
||||
|
||||
/**
|
||||
* Рукопожатие.
|
||||
* С этого состояния начинается сюбое соединение с сервером.
|
||||
*/
|
||||
HANDSHAKE(0,
|
||||
ImmutableMap.<Integer, Class<? extends CSPacket>>builder()
|
||||
.put(0x00, HandshakePacket.class)
|
||||
.build(),
|
||||
null
|
||||
),
|
||||
|
||||
/**
|
||||
* Информация о сервере.
|
||||
* Используется для получения Motd, кол-ва слотов и т.д.
|
||||
*/
|
||||
STATUS(1,
|
||||
ImmutableMap.<Integer, Class<? extends CSPacket>>builder()
|
||||
.put(0x00, StatusRequestPacket.class)
|
||||
.put(0x01, PingPacket.class)
|
||||
.build(),
|
||||
ImmutableMap.<Class<? extends SCPacket>, Integer>builder()
|
||||
.put(StatusResponsePacket.class, 0x00)
|
||||
.put(PingPacket.class, 0x01)
|
||||
.build()
|
||||
),
|
||||
|
||||
/**
|
||||
* Стадия логина/авторизации.
|
||||
*/
|
||||
LOGIN(2,
|
||||
ImmutableMap.<Integer, Class<? extends CSPacket>>builder()
|
||||
.put(0x00, LoginStartPacket.class)
|
||||
.build(),
|
||||
ImmutableMap.<Class<? extends SCPacket>, Integer>builder()
|
||||
.put(DisconnectPacket.class, 0x00)
|
||||
.put(LoginSuccessPacket.class, 0x02)
|
||||
.build()
|
||||
),
|
||||
|
||||
/**
|
||||
* Игровая стадия.
|
||||
* Основная стадия протокола.
|
||||
*/
|
||||
PLAY(3,
|
||||
ImmutableMap.<Integer, Class<? extends CSPacket>>builder()
|
||||
.put(0x00, TeleportConfirmPacket.class)
|
||||
.put(0x02, ChatMessageClientPacket.class)
|
||||
.put(0x04, ClientSettingsPacket.class)
|
||||
.put(0x09, PluginMessagePacket.class)
|
||||
.put(0x0B, KeepAlivePacket.class)
|
||||
.put(0x0D, PlayerPositionPacket.class)
|
||||
.put(0x0E, PlayerPositionAndLookPacket.class)
|
||||
.put(0x0F, PlayerLookPacket.class)
|
||||
.put(0x13, PlayerAbilitiesPacket.class)
|
||||
.put(0x1A, HeldItemChangePacket.class)
|
||||
.build(),
|
||||
ImmutableMap.<Class<? extends SCPacket>, Integer>builder()
|
||||
.put(BossBarPacket.class, 0x0C)
|
||||
.put(ChatMessageServerPacket.class, 0x0F)
|
||||
.put(PluginMessagePacket.class, 0x18)
|
||||
.put(UnloadChunkPacket.class, 0x1D)
|
||||
.put(KeepAlivePacket.class, 0x1F)
|
||||
.put(ChunkDataPacket.class, 0x20)
|
||||
.put(JoinGamePacket.class, 0x23)
|
||||
.put(PlayerAbilitiesPacket.class, 0x2C)
|
||||
.put(PlayerListItemPacket.class, 0x2E)
|
||||
.put(PlayerPositionAndLookPacket.class, 0x2F)
|
||||
.put(SpawnPositionPacket.class, 0x46)
|
||||
.put(TimeUpdatePacket.class, 0x47)
|
||||
.put(TitlePacket.class, 0x48)
|
||||
.put(PlayerListHeaderAndFooterPacket.class, 0x4A)
|
||||
.build()
|
||||
);
|
||||
|
||||
public static State valueOf(int id) {
|
||||
if (id == 0) return HANDSHAKE;
|
||||
else if (id == 1) return STATUS;
|
||||
else if (id == 2) return LOGIN;
|
||||
else if (id == 3) return PLAY;
|
||||
else {
|
||||
log.warn("Unknown state: {}", id);
|
||||
return UNKNOWN;
|
||||
}
|
||||
}
|
||||
|
||||
@Getter
|
||||
private final int id;
|
||||
private final Map<Integer, Class<? extends CSPacket>> clientSidePacketsMap;
|
||||
private final Map<Class<? extends SCPacket>, Integer> serverSidePacketsMap;
|
||||
|
||||
public Class<? extends CSPacket> getClientSidePacket(int id) {
|
||||
return clientSidePacketsMap.get(id);
|
||||
}
|
||||
|
||||
public Integer getServerSidePacket(Class<? extends SCPacket> clazz) {
|
||||
return serverSidePacketsMap.get(clazz);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
package mc.core.network.proto_1_12_2;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import mc.core.EntityLocation;
|
||||
import mc.core.player.Player;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Random;
|
||||
|
||||
public class TeleportManager {
|
||||
private static TeleportManager instance = new TeleportManager();
|
||||
|
||||
public static TeleportManager getInstance() {
|
||||
return instance;
|
||||
}
|
||||
|
||||
@AllArgsConstructor
|
||||
private class TpData {
|
||||
public Player player;
|
||||
public EntityLocation newLocation;
|
||||
// TODO необходимо добавить TimeStamp, что бы понимать, когда клиент отвергнул телепортацию
|
||||
// т.е. идея такова: долгое молчание клиента знак отвержения телепортации.
|
||||
}
|
||||
|
||||
private final Random RAND = new Random();
|
||||
private final Map<Integer, TpData> teleportMap = new HashMap<>();
|
||||
|
||||
private TeleportManager() {}
|
||||
|
||||
public int append(Player player, EntityLocation location) {
|
||||
int teleportId;
|
||||
do {
|
||||
teleportId = RAND.nextInt(9999);
|
||||
} while (teleportMap.containsKey(teleportId));
|
||||
|
||||
teleportMap.put(teleportId, new TpData(player, location.clone()));
|
||||
return teleportId;
|
||||
}
|
||||
|
||||
public void apply(int teleportId) {
|
||||
if (teleportMap.containsKey(teleportId)) {
|
||||
TpData data = teleportMap.remove(teleportId);
|
||||
data.player.getLocation().set(data.newLocation);
|
||||
}
|
||||
}
|
||||
|
||||
public void removeDataPlayer(Player player) {
|
||||
teleportMap.entrySet().removeIf(entry -> entry.getValue().player.equals(player));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package mc.core.network.proto_1_12_2.packets;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
import lombok.ToString;
|
||||
import mc.core.network.CSPacket;
|
||||
import mc.core.network.NetInputStream;
|
||||
import mc.core.network.NetOutputStream;
|
||||
import mc.core.network.SCPacket;
|
||||
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
@Setter
|
||||
@ToString
|
||||
public class KeepAlivePacket implements CSPacket, SCPacket {
|
||||
private long payload;
|
||||
|
||||
@Override
|
||||
public void readSelf(NetInputStream netStream) {
|
||||
this.payload = netStream.readLong();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeSelf(NetOutputStream netStream) {
|
||||
netStream.writeLong(this.payload);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package mc.core.network.proto_1_12_2.packets;
|
||||
|
||||
import lombok.ToString;
|
||||
import mc.core.network.CSPacket;
|
||||
import mc.core.network.NetInputStream;
|
||||
import mc.core.network.NetOutputStream;
|
||||
import mc.core.network.SCPacket;
|
||||
|
||||
@ToString
|
||||
public class PingPacket implements CSPacket, SCPacket {
|
||||
private long payload;
|
||||
|
||||
@Override
|
||||
public void readSelf(NetInputStream netStream) {
|
||||
this.payload = netStream.readLong();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeSelf(NetOutputStream netStream) {
|
||||
netStream.writeLong(payload);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
package mc.core.network.proto_1_12_2.packets;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
import lombok.ToString;
|
||||
import mc.core.network.CSPacket;
|
||||
import mc.core.network.NetInputStream;
|
||||
import mc.core.network.NetOutputStream;
|
||||
import mc.core.network.SCPacket;
|
||||
|
||||
@NoArgsConstructor
|
||||
@Getter
|
||||
@Setter
|
||||
@ToString
|
||||
public class PlayerAbilitiesPacket implements SCPacket, CSPacket {
|
||||
private static final byte $GOD_MODE_MASK = 0x01,
|
||||
$FLYING_MASK = 0x02,
|
||||
$CAN_FLY_MASK = 0x04,
|
||||
$IDB_MASK = 0x08;
|
||||
|
||||
private boolean godMode = false;
|
||||
private boolean flying = false;
|
||||
private boolean canFly = false;
|
||||
private boolean instantDestroyBlocks = false;
|
||||
private float flyingSpeed = 0.05f;
|
||||
private float fieldOfView = flyingSpeed;
|
||||
private float walkingSpeed;
|
||||
|
||||
@Override
|
||||
public void writeSelf(NetOutputStream netStream) {
|
||||
byte flag = 0;
|
||||
if (godMode) flag = (byte)(flag | $GOD_MODE_MASK);
|
||||
if (flying) flag = (byte)(flag | $FLYING_MASK);
|
||||
if (canFly) flag = (byte)(flag | $CAN_FLY_MASK);
|
||||
if (instantDestroyBlocks) flag = (byte)(flag | $IDB_MASK);
|
||||
|
||||
netStream.writeByte(flag);
|
||||
netStream.writeFloat(flyingSpeed);
|
||||
netStream.writeFloat(fieldOfView);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readSelf(NetInputStream netStream) {
|
||||
byte flag = netStream.readByte();
|
||||
godMode = (flag & $GOD_MODE_MASK) > 0;
|
||||
canFly = (flag & $CAN_FLY_MASK) > 0;
|
||||
flying = (flag & $FLYING_MASK) > 0;
|
||||
instantDestroyBlocks = (flag & $IDB_MASK) > 0;
|
||||
|
||||
flyingSpeed = netStream.readFloat();
|
||||
walkingSpeed = netStream.readFloat();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
package mc.core.network.proto_1_12_2.packets;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
import lombok.ToString;
|
||||
import mc.core.EntityLocation;
|
||||
import mc.core.network.CSPacket;
|
||||
import mc.core.network.NetInputStream;
|
||||
import mc.core.network.NetOutputStream;
|
||||
import mc.core.network.SCPacket;
|
||||
|
||||
@NoArgsConstructor
|
||||
@Getter
|
||||
@ToString
|
||||
public class PlayerPositionAndLookPacket implements SCPacket, CSPacket {
|
||||
@Setter
|
||||
private EntityLocation location;
|
||||
@Setter
|
||||
private int teleportId;
|
||||
private boolean onGround = false;
|
||||
|
||||
@Override
|
||||
public void writeSelf(NetOutputStream netStream) {
|
||||
netStream.writeDouble(location.getX());
|
||||
netStream.writeDouble(location.getY());
|
||||
netStream.writeDouble(location.getZ());
|
||||
netStream.writeFloat(location.getYaw());
|
||||
netStream.writeFloat(location.getPitch());
|
||||
netStream.writeByte(0); // It's a bitfield, X/Y/Z/Y_ROT/X_ROT. If X is setXYZ, the x value is relative and not absolute.
|
||||
/* X - 0x01
|
||||
* Y - 0x02
|
||||
* Z - 0x04
|
||||
* Y_ROT - 0x08
|
||||
* X_ROT - 0x10
|
||||
*/
|
||||
netStream.writeVarInt(teleportId); // Client should confirm this packet with Teleport Confirm containing the same Teleport ID
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readSelf(NetInputStream netStream) {
|
||||
this.location = new EntityLocation(
|
||||
netStream.readDouble(),
|
||||
netStream.readDouble(),
|
||||
netStream.readDouble(),
|
||||
netStream.readFloat(),
|
||||
netStream.readFloat()
|
||||
);
|
||||
|
||||
this.onGround = netStream.readBoolean();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package mc.core.network.proto_1_12_2.packets;
|
||||
|
||||
import lombok.*;
|
||||
import mc.core.network.CSPacket;
|
||||
import mc.core.network.NetInputStream;
|
||||
import mc.core.network.NetOutputStream;
|
||||
import mc.core.network.SCPacket;
|
||||
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
@Getter
|
||||
@Setter
|
||||
@ToString
|
||||
public class PluginMessagePacket implements SCPacket, CSPacket {
|
||||
private String channelName;
|
||||
private byte[] data;
|
||||
|
||||
@Override
|
||||
public void readSelf(NetInputStream netStream) {
|
||||
channelName = netStream.readString();
|
||||
data = new byte[netStream.getDataSize() - channelName.getBytes().length - 1];
|
||||
netStream.readBytes(data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeSelf(NetOutputStream netStream) {
|
||||
netStream.writeString(channelName);
|
||||
netStream.writeBytes(data);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package mc.core.network.proto_1_12_2.packets.clientside;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.ToString;
|
||||
import mc.core.network.CSPacket;
|
||||
import mc.core.network.NetInputStream;
|
||||
|
||||
@Getter
|
||||
@ToString
|
||||
public class ChatMessageClientPacket implements CSPacket {
|
||||
private String message;
|
||||
|
||||
@Override
|
||||
public void readSelf(NetInputStream netStream) {
|
||||
this.message = netStream.readString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package mc.core.network.proto_1_12_2.packets.clientside;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.ToString;
|
||||
import mc.core.network.CSPacket;
|
||||
import mc.core.network.NetInputStream;
|
||||
|
||||
@NoArgsConstructor
|
||||
@Getter
|
||||
@ToString
|
||||
public class ClientSettingsPacket implements CSPacket {
|
||||
private String locale;
|
||||
private int viewDistance;
|
||||
private int chatMode;
|
||||
private boolean chatColors;
|
||||
private boolean capeEnabled,
|
||||
jacketEnabled,
|
||||
leftSleeveEnabled,
|
||||
rightSleeveEnabled,
|
||||
leftPantsLegEnabled,
|
||||
rightPantsLegEnabled,
|
||||
hatEnabled;
|
||||
private int mainHand;
|
||||
|
||||
@Override
|
||||
public void readSelf(NetInputStream netStream) {
|
||||
locale = netStream.readString();
|
||||
viewDistance = netStream.readByte();
|
||||
chatMode = netStream.readVarInt();
|
||||
chatColors = netStream.readBoolean();
|
||||
|
||||
int bitmask = netStream.readUnsignedByte();
|
||||
capeEnabled = (bitmask & 0x01) > 0;
|
||||
jacketEnabled = (bitmask & 0x02) > 0;
|
||||
leftSleeveEnabled = (bitmask & 0x04) > 0;
|
||||
rightSleeveEnabled = (bitmask & 0x08) > 0;
|
||||
leftPantsLegEnabled = (bitmask & 0x10) > 0;
|
||||
rightPantsLegEnabled = (bitmask & 0x20) > 0;
|
||||
hatEnabled = (bitmask & 0x40) > 0;
|
||||
|
||||
mainHand = netStream.readVarInt();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package mc.core.network.proto_1_12_2.packets.clientside;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.ToString;
|
||||
import mc.core.network.CSPacket;
|
||||
import mc.core.network.NetInputStream;
|
||||
import mc.core.network.proto_1_12_2.State;
|
||||
|
||||
@NoArgsConstructor
|
||||
@Getter
|
||||
@ToString
|
||||
public class HandshakePacket implements CSPacket {
|
||||
private int protocolVersion;
|
||||
private String address;
|
||||
private int serverPort;
|
||||
private State nextState;
|
||||
|
||||
@Override
|
||||
public void readSelf(NetInputStream netStream) {
|
||||
this.protocolVersion = netStream.readVarInt();
|
||||
this.address = netStream.readString();
|
||||
this.serverPort = netStream.readUnsignedShort();
|
||||
this.nextState = State.valueOf(netStream.readVarInt());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package mc.core.network.proto_1_12_2.packets.clientside;
|
||||
|
||||
import mc.core.network.CSPacket;
|
||||
import mc.core.network.NetInputStream;
|
||||
|
||||
public class HeldItemChangePacket implements CSPacket {
|
||||
private int slot;
|
||||
|
||||
@Override
|
||||
public void readSelf(NetInputStream netStream) {
|
||||
this.slot = netStream.readShort();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package mc.core.network.proto_1_12_2.packets.clientside;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.ToString;
|
||||
import mc.core.network.CSPacket;
|
||||
import mc.core.network.NetInputStream;
|
||||
|
||||
@Getter
|
||||
@ToString
|
||||
public class LoginStartPacket implements CSPacket {
|
||||
private String playerName;
|
||||
|
||||
@Override
|
||||
public void readSelf(NetInputStream netStream) {
|
||||
this.playerName = netStream.readString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package mc.core.network.proto_1_12_2.packets.clientside;
|
||||
|
||||
import lombok.Getter;
|
||||
import mc.core.network.CSPacket;
|
||||
import mc.core.network.NetInputStream;
|
||||
|
||||
@Getter
|
||||
public class PlayerLookPacket implements CSPacket {
|
||||
private float yaw, pitch;
|
||||
private boolean onGround; // True if the client is on the ground, false otherwise
|
||||
|
||||
@Override
|
||||
public void readSelf(NetInputStream netStream) {
|
||||
this.yaw = netStream.readFloat();
|
||||
this.pitch = netStream.readFloat();
|
||||
this.onGround = netStream.readBoolean();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package mc.core.network.proto_1_12_2.packets.clientside;
|
||||
|
||||
import lombok.Getter;
|
||||
import mc.core.network.CSPacket;
|
||||
import mc.core.network.NetInputStream;
|
||||
|
||||
@Getter
|
||||
public class PlayerPositionPacket implements CSPacket {
|
||||
private double x, y, z; // Y - is feet position. Normally Head Y - +1.62d
|
||||
private boolean onGround; // True if the client is on the ground, false otherwise
|
||||
|
||||
@Override
|
||||
public void readSelf(NetInputStream netStream) {
|
||||
this.x = netStream.readDouble();
|
||||
this.y = netStream.readDouble();
|
||||
this.z = netStream.readDouble();
|
||||
this.onGround = netStream.readBoolean();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package mc.core.network.proto_1_12_2.packets.clientside;
|
||||
|
||||
import lombok.ToString;
|
||||
import mc.core.network.CSPacket;
|
||||
import mc.core.network.NetInputStream;
|
||||
|
||||
@ToString
|
||||
public class StatusRequestPacket implements CSPacket {
|
||||
@Override
|
||||
public void readSelf(NetInputStream netStream) {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package mc.core.network.proto_1_12_2.packets.clientside;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.ToString;
|
||||
import mc.core.network.CSPacket;
|
||||
import mc.core.network.NetInputStream;
|
||||
|
||||
@Getter
|
||||
@ToString
|
||||
public class TeleportConfirmPacket implements CSPacket {
|
||||
private int teleportId;
|
||||
|
||||
@Override
|
||||
public void readSelf(NetInputStream netStream) {
|
||||
teleportId = netStream.readVarInt();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
package mc.core.network.proto_1_12_2.packets.serverside;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.Setter;
|
||||
import lombok.ToString;
|
||||
import mc.core.network.NetOutputStream;
|
||||
import mc.core.network.SCPacket;
|
||||
import mc.core.network.proto_1_12_2.serializers.TextToStringConverter;
|
||||
import mc.core.text.Text;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
@Setter
|
||||
@ToString
|
||||
public class BossBarPacket implements SCPacket {
|
||||
@RequiredArgsConstructor
|
||||
public enum Action {
|
||||
ADD(0),
|
||||
REMOVE(1),
|
||||
UPDATE_HEALTH(2),
|
||||
UPDATE_TITLE(3),
|
||||
UPDATE_STYLE(4),
|
||||
UPDATE_FLAGS(5);
|
||||
|
||||
@Getter
|
||||
private final int id;
|
||||
}
|
||||
|
||||
@RequiredArgsConstructor
|
||||
public enum Color {
|
||||
PINK(0),
|
||||
BLUE(1),
|
||||
RED(2),
|
||||
GREEN(3),
|
||||
YELLOW(4),
|
||||
PURPLE(5),
|
||||
WHITE(5);
|
||||
|
||||
@Getter
|
||||
private final int id;
|
||||
}
|
||||
|
||||
@RequiredArgsConstructor
|
||||
public enum Division {
|
||||
NO(0),
|
||||
_0(0),
|
||||
_6(1),
|
||||
_10(2),
|
||||
_12(3),
|
||||
_20(4);
|
||||
|
||||
@Getter
|
||||
private final int id;
|
||||
}
|
||||
|
||||
@RequiredArgsConstructor
|
||||
public enum Flag {
|
||||
NO(0x00),
|
||||
DAKR_SKY(0x01),
|
||||
DRAGON_BAR(0x02);
|
||||
|
||||
@Getter
|
||||
private final int id;
|
||||
}
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
public static class BarData {
|
||||
private Text title;
|
||||
/*
|
||||
* From 0 to 1.
|
||||
* Values greater than 1 do not crash a Notchian client,
|
||||
* and start rendering part of a second health bar at around 1.5.
|
||||
* (https://i.johni0702.de/nA.png)
|
||||
*/
|
||||
private float health;
|
||||
private Color color;
|
||||
private Division division;
|
||||
private Flag flags;
|
||||
}
|
||||
|
||||
private UUID uuid; // Unique ID for this bar
|
||||
private Action action;
|
||||
private BarData barData;
|
||||
|
||||
@Override
|
||||
public void writeSelf(NetOutputStream netStream) {
|
||||
netStream.writeUUID(uuid);
|
||||
netStream.writeVarInt(action.id);
|
||||
|
||||
if (action == Action.REMOVE) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (action == Action.ADD || action == Action.UPDATE_TITLE) {
|
||||
netStream.writeString(TextToStringConverter.getInstance().mapping(barData.title));
|
||||
}
|
||||
|
||||
if (action == Action.ADD || action == Action.UPDATE_HEALTH) {
|
||||
netStream.writeFloat(barData.health);
|
||||
}
|
||||
|
||||
if (action == Action.ADD || action == Action.UPDATE_STYLE) {
|
||||
netStream.writeVarInt(barData.color.id);
|
||||
netStream.writeVarInt(barData.division.id);
|
||||
}
|
||||
|
||||
if (action == Action.ADD || action == Action.UPDATE_FLAGS) {
|
||||
netStream.writeUnsignedByte(barData.flags.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package mc.core.network.proto_1_12_2.packets.serverside;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
import lombok.ToString;
|
||||
import mc.core.chat.MessageType;
|
||||
import mc.core.network.NetOutputStream;
|
||||
import mc.core.network.SCPacket;
|
||||
import mc.core.network.proto_1_12_2.serializers.TextToStringConverter;
|
||||
import mc.core.text.Text;
|
||||
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
@Setter
|
||||
@ToString
|
||||
public class ChatMessageServerPacket implements SCPacket {
|
||||
private Text text;
|
||||
private MessageType type;
|
||||
|
||||
@Override
|
||||
public void writeSelf(NetOutputStream netStream) {
|
||||
netStream.writeString(TextToStringConverter.getInstance().mapping(text));
|
||||
netStream.writeByte(type.getId());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,309 @@
|
||||
package mc.core.network.proto_1_12_2.packets.serverside;
|
||||
|
||||
import com.flowpowered.nbt.CompoundTag;
|
||||
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.BlockType;
|
||||
import mc.core.world.chunk.Chunk;
|
||||
import mc.core.world.chunk.ChunkSection;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
|
||||
/*
|
||||
Packet structure
|
||||
|
||||
- https://wiki.vg/Chunk_Format#Packet_structure
|
||||
|
||||
+------------------------------------------------------+
|
||||
| Field | Type |
|
||||
|--------------------------|---------------------------|
|
||||
| Chunk X | int |
|
||||
|--------------------------|---------------------------|
|
||||
| Chunk Y | int |
|
||||
|--------------------------|---------------------------|
|
||||
| Init Chunk | boolean | ("Ground-Up Continuous")
|
||||
|--------------------------|---------------------------|
|
||||
| Primary Bit Mask | VarInt |
|
||||
|--------------------------|---------------------------|
|
||||
| Size of Data | VarInt |
|
||||
|--------------------------|---------------------------|
|
||||
| Data | Byte array | - https://wiki.vg/Chunk_Format#Data_structure
|
||||
| +------------------------------------------------+ |
|
||||
| | Chunk Section | Byte array | | - https://wiki.vg/Chunk_Format#Chunk_Section_structure
|
||||
| | +------------------------------------------+ | |
|
||||
| | | Bits Per Block | Unsigned Byte | | | (we use 4 bits per block)
|
||||
| | |--------------------|---------------------| | |
|
||||
| | | Palette | Byte array | | | - https://wiki.vg/Chunk_Format#Palettes
|
||||
| | | +------------------------------------+ | | | (we use Indirect type palette)
|
||||
| | | | Size of palette | VarInt | | | |
|
||||
| | | |-----------------|------------------| | | |
|
||||
| | | | Palette | Array of VarInt | | | |
|
||||
| | | +------------------------------------+ | | |
|
||||
| | |--------------------|---------------------| | |
|
||||
| | | Size of Data Array | VarInt | | |
|
||||
| | |--------------------|---------------------| | |
|
||||
| | | Data Array | Array of Long | | |
|
||||
| | |--------------------|---------------------| | |
|
||||
| | | Block Light | Byte Array | | | (Half byte per block)
|
||||
| | |--------------------|---------------------| | |
|
||||
| | | Sky Light | Optional Byte Array | | | (Only if in the Overworld; half byte per block)
|
||||
| | +------------------------------------------+ | |
|
||||
| |-----------------------|------------------------| |
|
||||
| | Biomes | Optional Byte array | |
|
||||
| +------------------------------------------------+ |
|
||||
|--------------------------|---------------------------|
|
||||
| Number of block entities | VarInt |
|
||||
|--------------------------|---------------------------|
|
||||
| Block entities | Array of NBT |
|
||||
+------------------------------------------------------+
|
||||
*/
|
||||
|
||||
@Slf4j
|
||||
@NoArgsConstructor
|
||||
public class ChunkDataPacket implements SCPacket {
|
||||
@Setter
|
||||
private int x;
|
||||
@Setter
|
||||
private int z;
|
||||
@Setter
|
||||
private boolean initChunk = true; // "Ground-Up Continuous"
|
||||
private Chunk chunk;
|
||||
private List<ChunkSection> sectionList;
|
||||
|
||||
public void setChunk(Chunk chunk) {
|
||||
this.sectionList = null;
|
||||
this.chunk = chunk;
|
||||
}
|
||||
|
||||
public void setChunkSectionList(List<ChunkSection> sectionList) {
|
||||
this.chunk = null;
|
||||
this.sectionList = sectionList;
|
||||
}
|
||||
|
||||
@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) {
|
||||
for (int h = 15; h >= 0; h--) {
|
||||
bitMask = bitMask << 1;
|
||||
ChunkSection chunkSection = chunk.getChunkSection(h);
|
||||
if (chunkSection != null && chunkSection.getY() == h) {
|
||||
bitMask |= 0x01;
|
||||
maxH++;
|
||||
} else {
|
||||
bitMask |= 0x00;
|
||||
}
|
||||
}
|
||||
} else if (sectionList != null && chunk == null) {
|
||||
sectionList.sort(Comparator.comparingInt(ChunkSection::getY));
|
||||
for (int h = 15, i = 0; h >= 0; h--) {
|
||||
bitMask = bitMask << 1;
|
||||
ChunkSection chunkSection = sectionList.get(i);
|
||||
if (chunkSection != null && chunkSection.getY() == h) {
|
||||
bitMask |= 0x01;
|
||||
maxH++;
|
||||
} else {
|
||||
bitMask |= 0x00;
|
||||
}
|
||||
}
|
||||
}
|
||||
netStream.writeVarInt(bitMask); // Primary Bit Mask
|
||||
|
||||
final ByteArrayOutputNetStream data = new ByteArrayOutputNetStream();
|
||||
|
||||
final ByteArrayOutputNetStream biomes = new ByteArrayOutputNetStream();
|
||||
boolean biomeWrite = true;
|
||||
|
||||
List<CompoundTag> nbtList = new ArrayList<>();
|
||||
|
||||
for (int h = 0; h < maxH; h++) {
|
||||
ChunkSection chunkSection = null;
|
||||
|
||||
if (chunk != null) {
|
||||
chunkSection = chunk.getChunkSection(h);
|
||||
} else if (sectionList != null) {
|
||||
chunkSection = sectionList.remove(0);
|
||||
}
|
||||
|
||||
if (chunkSection == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
palettedChunkSection.addBlock(
|
||||
block,
|
||||
chunkSection.getSkyLight(x, y, z)
|
||||
);
|
||||
|
||||
CompoundTag nbt = block.getNBTData();
|
||||
if (nbt != null) {
|
||||
nbtList.add(nbt);
|
||||
}
|
||||
|
||||
if (biomeWrite) {
|
||||
biomes.writeByte(chunk.getBiome(
|
||||
(chunk.getX() << 4) + x,
|
||||
(chunk.getZ() << 4) + z
|
||||
).getId());
|
||||
if (x == 15 && z == 15) {
|
||||
biomeWrite = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// <Chunk Section>
|
||||
palettedChunkSection.writeToNetStream(data);
|
||||
// </Chunk Section>
|
||||
}
|
||||
// <Biomes>
|
||||
data.writeBytes(biomes.toByteArray());
|
||||
// </Biomes>
|
||||
|
||||
netStream.writeVarInt(data.size()); // Size of Data
|
||||
netStream.writeBytes(data.toByteArray()); // Data
|
||||
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 List<Integer> palette = new ArrayList<>();
|
||||
private byte[] blocks = new byte[4096];
|
||||
private NibbleArray blockLight = new NibbleArray();
|
||||
private NibbleArray skyLight = new NibbleArray();
|
||||
|
||||
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) {
|
||||
final int bx = block.getLocation().getX() - ((block.getLocation().getX() >> 4) << 4);
|
||||
final int by = block.getLocation().getY() - ((block.getLocation().getY() >> 4) << 4);
|
||||
final int bz = block.getLocation().getZ() - ((block.getLocation().getZ() >> 4) << 4);
|
||||
|
||||
blocks[coordsToIndex(bx, by, bz)] = addBlockType(block.getType());
|
||||
blockLight.set(bx, by, bz, block.getLight());
|
||||
this.skyLight.set(bx, by, bz, 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(netOutputStream::writeVarInt); // 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>
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package mc.core.network.proto_1_12_2.packets.serverside;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
import mc.core.network.NetOutputStream;
|
||||
import mc.core.network.SCPacket;
|
||||
import mc.core.network.proto_1_12_2.serializers.TextToStringConverter;
|
||||
import mc.core.text.Text;
|
||||
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
@Setter
|
||||
public class DisconnectPacket implements SCPacket {
|
||||
private Text reason;
|
||||
|
||||
@Override
|
||||
public void writeSelf(NetOutputStream netStream) {
|
||||
netStream.writeString(TextToStringConverter.getInstance().mapping(reason));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package mc.core.network.proto_1_12_2.packets.serverside;
|
||||
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
import lombok.ToString;
|
||||
import mc.core.network.NetOutputStream;
|
||||
import mc.core.network.SCPacket;
|
||||
import mc.core.player.PlayerMode;
|
||||
|
||||
@NoArgsConstructor
|
||||
@Setter
|
||||
@ToString
|
||||
public class JoinGamePacket implements SCPacket {
|
||||
private int entityId;
|
||||
private PlayerMode mode;
|
||||
private int dimension;
|
||||
private int difficulty;
|
||||
private String levelType;
|
||||
|
||||
@Override
|
||||
public void writeSelf(NetOutputStream netStream) {
|
||||
netStream.writeInt(entityId);
|
||||
netStream.writeUnsignedByte(mode.getId());
|
||||
netStream.writeInt(dimension);
|
||||
netStream.writeUnsignedByte(difficulty);
|
||||
netStream.writeUnsignedByte(0); // Max Players, unused
|
||||
netStream.writeString(levelType);
|
||||
netStream.writeBoolean(false); // Reduced Debug Info
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package mc.core.network.proto_1_12_2.packets.serverside;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
import lombok.ToString;
|
||||
import mc.core.network.NetOutputStream;
|
||||
import mc.core.network.SCPacket;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
@Setter
|
||||
@ToString
|
||||
public class LoginSuccessPacket implements SCPacket {
|
||||
private UUID uuid;
|
||||
private String playerName;
|
||||
|
||||
@Override
|
||||
public void writeSelf(NetOutputStream netStream) {
|
||||
netStream.writeString(uuid.toString());
|
||||
netStream.writeString(playerName);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package mc.core.network.proto_1_12_2.packets.serverside;
|
||||
|
||||
import lombok.Setter;
|
||||
import lombok.ToString;
|
||||
import mc.core.network.NetOutputStream;
|
||||
import mc.core.network.SCPacket;
|
||||
import mc.core.network.proto_1_12_2.serializers.TextToStringConverter;
|
||||
import mc.core.text.Text;
|
||||
|
||||
@Setter
|
||||
@ToString
|
||||
public class PlayerListHeaderAndFooterPacket implements SCPacket {
|
||||
// To remove the header/footer, send a empty translatable component: {"translate":""}
|
||||
private Text header;
|
||||
private Text footer;
|
||||
|
||||
@Override
|
||||
public void writeSelf(NetOutputStream netStream) {
|
||||
if (header == null) {
|
||||
netStream.writeString("{\"translate\":\"\"}");
|
||||
} else {
|
||||
netStream.writeString(TextToStringConverter.getInstance().mapping(header));
|
||||
}
|
||||
|
||||
if (footer == null) {
|
||||
netStream.writeString("{\"translate\":\"\"}");
|
||||
} else {
|
||||
netStream.writeString(TextToStringConverter.getInstance().mapping(footer));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
package mc.core.network.proto_1_12_2.packets.serverside;
|
||||
|
||||
import lombok.*;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import mc.core.network.NetOutputStream;
|
||||
import mc.core.network.SCPacket;
|
||||
import mc.core.network.proto_1_12_2.serializers.TextToStringConverter;
|
||||
import mc.core.player.PlayerMode;
|
||||
import mc.core.text.Text;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
@Slf4j
|
||||
@Getter
|
||||
@Setter
|
||||
@ToString
|
||||
public class PlayerListItemPacket implements SCPacket {
|
||||
@RequiredArgsConstructor
|
||||
public enum Action {
|
||||
ADD_PLAYER(0),
|
||||
UPDATE_GAMEMODE(1),
|
||||
UPDATE_LATENCY(2),
|
||||
UPDATE_DISPLAY_NAME(3),
|
||||
REMOVE_PLAYER(4);
|
||||
|
||||
@Getter
|
||||
private final int id;
|
||||
}
|
||||
|
||||
@Data
|
||||
@ToString
|
||||
public static class PlayerData {
|
||||
private UUID uuid;
|
||||
private String name;
|
||||
private Properties properties = new Properties();
|
||||
private PlayerMode gameMode;
|
||||
private int ping;
|
||||
private boolean hasDisplayName = false;
|
||||
private Text displayName;
|
||||
}
|
||||
|
||||
private Action action;
|
||||
private List<PlayerData> listPlayers = new ArrayList<>();
|
||||
|
||||
@Override
|
||||
public void writeSelf(NetOutputStream netStream) {
|
||||
netStream.writeVarInt(action.id);
|
||||
netStream.writeVarInt(listPlayers.size());
|
||||
|
||||
for (PlayerData playerData : listPlayers) {
|
||||
netStream.writeUUID(playerData.uuid);
|
||||
|
||||
if (action == Action.ADD_PLAYER) {
|
||||
netStream.writeString(playerData.name);
|
||||
netStream.writeVarInt(playerData.properties.size());
|
||||
|
||||
for (Map.Entry<Object, Object> entry : playerData.properties.entrySet()) {
|
||||
netStream.writeString(entry.getKey().toString());
|
||||
netStream.writeString(entry.getValue().toString());
|
||||
netStream.writeBoolean(false); // Is Signed
|
||||
}
|
||||
}
|
||||
|
||||
if (action == Action.ADD_PLAYER || action == Action.UPDATE_GAMEMODE) {
|
||||
netStream.writeVarInt(playerData.gameMode.getId());
|
||||
}
|
||||
|
||||
if (action == Action.ADD_PLAYER || action == Action.UPDATE_LATENCY) {
|
||||
netStream.writeVarInt(playerData.ping);
|
||||
}
|
||||
|
||||
if (action == Action.ADD_PLAYER || action == Action.UPDATE_DISPLAY_NAME) {
|
||||
netStream.writeBoolean(playerData.hasDisplayName);
|
||||
if (playerData.hasDisplayName) {
|
||||
netStream.writeString(TextToStringConverter.getInstance().mapping(playerData.displayName));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package mc.core.network.proto_1_12_2.packets.serverside;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
import lombok.ToString;
|
||||
import mc.core.EntityLocation;
|
||||
import mc.core.network.NetOutputStream;
|
||||
import mc.core.network.SCPacket;
|
||||
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
@Setter
|
||||
@ToString
|
||||
public class SpawnPositionPacket implements SCPacket {
|
||||
private EntityLocation location;
|
||||
|
||||
private long location2long(EntityLocation entityLocation) {
|
||||
return (((long) entityLocation.getBlockX() & 0x3FFFFFF) << 38)
|
||||
| (((long) entityLocation.getBlockY() & 0x0000FFF) << 26)
|
||||
| ((long) entityLocation.getBlockZ() & 0x3FFFFFF);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeSelf(NetOutputStream netStream) {
|
||||
netStream.writeLong(location2long(location));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
package mc.core.network.proto_1_12_2.packets.serverside;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
import lombok.Setter;
|
||||
import lombok.ToString;
|
||||
import mc.core.network.NetOutputStream;
|
||||
import mc.core.network.SCPacket;
|
||||
|
||||
@Setter
|
||||
@ToString
|
||||
public class StatusResponsePacket implements SCPacket {
|
||||
public static final String NAME = "1.12.2";
|
||||
public static final int PROTOCOL = 340;
|
||||
private static final JsonObject versionObj;
|
||||
|
||||
static {
|
||||
versionObj = new JsonObject();
|
||||
versionObj.addProperty("name", NAME);
|
||||
versionObj.addProperty("protocol", PROTOCOL);
|
||||
}
|
||||
|
||||
private int maxOnline;
|
||||
private int online;
|
||||
private String description;
|
||||
private byte[] faviconBase64;
|
||||
|
||||
@Override
|
||||
public void writeSelf(NetOutputStream netStream) {
|
||||
JsonObject playersObj = new JsonObject();
|
||||
playersObj.addProperty("max", maxOnline);
|
||||
playersObj.addProperty("online", online);
|
||||
|
||||
JsonObject descriptionObj = new JsonObject();
|
||||
descriptionObj.addProperty("text", description);
|
||||
|
||||
JsonObject rootObj = new JsonObject();
|
||||
rootObj.add("version", versionObj);
|
||||
rootObj.add("players", playersObj);
|
||||
rootObj.add("description", descriptionObj);
|
||||
|
||||
if (faviconBase64 != null && faviconBase64.length > 0) {
|
||||
rootObj.addProperty("favicon",
|
||||
"data:image/png;base64," + new String(faviconBase64)
|
||||
);
|
||||
}
|
||||
|
||||
netStream.writeString(rootObj.toString());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package mc.core.network.proto_1_12_2.packets.serverside;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
import lombok.ToString;
|
||||
import mc.core.network.NetOutputStream;
|
||||
import mc.core.network.SCPacket;
|
||||
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
@Setter
|
||||
@ToString
|
||||
public class TimeUpdatePacket implements SCPacket {
|
||||
private long time;
|
||||
private long worldage;
|
||||
|
||||
@Override
|
||||
public void writeSelf(NetOutputStream netStream) {
|
||||
netStream.writeLong(worldage);
|
||||
netStream.writeLong(time);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
package mc.core.network.proto_1_12_2.packets.serverside;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.Setter;
|
||||
import lombok.ToString;
|
||||
import mc.core.network.NetOutputStream;
|
||||
import mc.core.network.SCPacket;
|
||||
import mc.core.network.proto_1_12_2.serializers.TextToStringConverter;
|
||||
import mc.core.text.Text;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
@Setter
|
||||
@ToString
|
||||
public class TitlePacket implements SCPacket {
|
||||
private static final int TICKS_PER_SEC = 20,
|
||||
MIN_MS = (1000 / TICKS_PER_SEC);
|
||||
|
||||
@RequiredArgsConstructor
|
||||
public enum Action {
|
||||
SET_TITLE(0),
|
||||
SET_SUBTITLE(1),
|
||||
SET_ACTION_BAR(2),
|
||||
SET_DISPLAY_TIME(3),
|
||||
HIDE(4),
|
||||
RESET(5);
|
||||
|
||||
@Getter
|
||||
private final int id;
|
||||
}
|
||||
|
||||
private final Action action;
|
||||
private Text text = null;
|
||||
private Integer fadeInTime = null;
|
||||
private Integer stayTime = null;
|
||||
private Integer fadeOutTime = null;
|
||||
|
||||
public TitlePacket(Action action, Object... values) {
|
||||
if (values.length == 0 && (action != Action.HIDE && action != Action.RESET)) {
|
||||
this.action = Action.HIDE;
|
||||
return;
|
||||
}
|
||||
|
||||
this.action = action;
|
||||
|
||||
switch (this.action) {
|
||||
case SET_TITLE:
|
||||
case SET_SUBTITLE:
|
||||
case SET_ACTION_BAR:
|
||||
if (values[0] == null) {
|
||||
this.text = Text.of();
|
||||
} else if (values[0] instanceof Text) {
|
||||
this.text = (Text) values[0];
|
||||
} else {
|
||||
this.text = Text.of(values[0].toString());
|
||||
}
|
||||
break;
|
||||
case SET_DISPLAY_TIME:
|
||||
if (values.length < 3) {
|
||||
this.fadeInTime = 0;
|
||||
this.stayTime = 0;
|
||||
this.fadeOutTime = 0;
|
||||
} else {
|
||||
if (values[0] instanceof Integer) {
|
||||
if (((Integer) values[0]) < MIN_MS) {
|
||||
this.fadeInTime = 1;
|
||||
} else {
|
||||
this.fadeInTime = ((Integer) values[0]) / MIN_MS;
|
||||
}
|
||||
} else {
|
||||
this.fadeInTime = 0;
|
||||
}
|
||||
|
||||
if (values[1] instanceof Integer) {
|
||||
if (((Integer) values[1]) < MIN_MS) {
|
||||
this.stayTime = 1;
|
||||
} else {
|
||||
this.stayTime = ((Integer) values[1]) / MIN_MS;
|
||||
}
|
||||
} else {
|
||||
this.stayTime = 0;
|
||||
}
|
||||
|
||||
if (values[2] instanceof Integer) {
|
||||
if (((Integer) values[2]) < MIN_MS) {
|
||||
this.fadeOutTime = 1;
|
||||
} else {
|
||||
this.fadeOutTime = ((Integer) values[2]) / MIN_MS;
|
||||
}
|
||||
} else {
|
||||
this.fadeOutTime = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeSelf(NetOutputStream netStream) {
|
||||
netStream.writeVarInt(action.id);
|
||||
|
||||
switch (action) {
|
||||
case SET_TITLE:
|
||||
case SET_SUBTITLE:
|
||||
case SET_ACTION_BAR:
|
||||
netStream.writeString(TextToStringConverter.getInstance().mapping(this.text));
|
||||
break;
|
||||
case SET_DISPLAY_TIME:
|
||||
netStream.writeInt(this.fadeInTime);
|
||||
netStream.writeInt(this.stayTime);
|
||||
netStream.writeInt(this.fadeOutTime);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package mc.core.network.proto_1_12_2.packets.serverside;
|
||||
|
||||
import lombok.Setter;
|
||||
import mc.core.network.NetOutputStream;
|
||||
import mc.core.network.SCPacket;
|
||||
|
||||
public class UnloadChunkPacket implements SCPacket {
|
||||
@Setter
|
||||
private int x, z;
|
||||
|
||||
@Override
|
||||
public void writeSelf(NetOutputStream netStream) {
|
||||
netStream.writeInt(x);
|
||||
netStream.writeInt(z);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
package mc.core.network.proto_1_12_2.serializers;
|
||||
|
||||
import mc.core.world.block.BlockLocation;
|
||||
|
||||
import static com.google.common.math.IntMath.isPowerOfTwo;
|
||||
|
||||
public class BlockLocationSerializer {
|
||||
private static final int[] MULTIPLY_DE_BRUIJN_BIT_POSITION = new int[] {0, 1, 28, 2, 29, 14, 24, 3, 30, 22, 20, 15, 25, 17, 4, 8, 31, 27, 13, 23, 21, 19, 16, 7, 26, 12, 18, 6, 11, 5, 10, 9};
|
||||
private static final int NUM_X_BITS = 1 + log2(smallestEncompassingPowerOfTwo(30000000));
|
||||
private static final int NUM_Z_BITS = NUM_X_BITS;
|
||||
private static final int NUM_Y_BITS = 64 - NUM_X_BITS - NUM_Z_BITS;
|
||||
private static final int Y_SHIFT = NUM_Z_BITS;
|
||||
private static final int X_SHIFT = Y_SHIFT + NUM_Y_BITS;
|
||||
private static final long X_MASK = (1L << NUM_X_BITS) - 1L;
|
||||
private static final long Y_MASK = (1L << NUM_Y_BITS) - 1L;
|
||||
private static final long Z_MASK = (1L << NUM_Z_BITS) - 1L;
|
||||
|
||||
/*
|
||||
* net.minecraft.util.math.MathHelper#log2(int)
|
||||
*/
|
||||
private static int log2(int value) {
|
||||
return log2DeBruijn(value) - (isPowerOfTwo(value) ? 0 : 1);
|
||||
}
|
||||
|
||||
/*
|
||||
* net.minecraft.util.math.MathHelper#log2DeBruijn(int)
|
||||
*/
|
||||
private static int log2DeBruijn(int value) {
|
||||
value = isPowerOfTwo(value) ? value : smallestEncompassingPowerOfTwo(value);
|
||||
return MULTIPLY_DE_BRUIJN_BIT_POSITION[(int)((long)value * 125613361L >> 27) & 31];
|
||||
}
|
||||
|
||||
/*
|
||||
* net.minecraft.util.math.MathHelper#smallestEncompassingPowerOfTwo(int)
|
||||
*/
|
||||
private static int smallestEncompassingPowerOfTwo(int value) {
|
||||
int i = value - 1;
|
||||
i = i | i >> 1;
|
||||
i = i | i >> 2;
|
||||
i = i | i >> 4;
|
||||
i = i | i >> 8;
|
||||
i = i | i >> 16;
|
||||
return i + 1;
|
||||
}
|
||||
|
||||
public static long toLong(BlockLocation location) {
|
||||
return ((long)location.getX() & X_MASK) << X_SHIFT |
|
||||
((long)location.getY() & Y_MASK) << Y_SHIFT |
|
||||
((long)location.getZ() & Z_MASK);
|
||||
}
|
||||
|
||||
public static BlockLocation fromLong(long value) {
|
||||
BlockLocation location = BlockLocation.ZERO();
|
||||
fromLong(value, location);
|
||||
return location;
|
||||
}
|
||||
|
||||
public static void fromLong(long value, BlockLocation location) {
|
||||
location.setX((int)(value << 64 - X_SHIFT - NUM_X_BITS >> 64 - NUM_X_BITS));
|
||||
location.setY((int)(value << 64 - Y_SHIFT - NUM_Y_BITS >> 64 - NUM_Y_BITS));
|
||||
location.setZ((int)(value << 64 - NUM_Z_BITS >> 64 - NUM_Z_BITS));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
package mc.core.network.proto_1_12_2.serializers;
|
||||
|
||||
public interface Converter<F, T> {
|
||||
T mapping(F fromObject);
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
package mc.core.network.proto_1_12_2.serializers;
|
||||
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonObject;
|
||||
import lombok.Getter;
|
||||
import mc.core.text.Text;
|
||||
|
||||
public class TextToStringConverter implements Converter<Text, String> {
|
||||
@Getter
|
||||
private static TextToStringConverter instance = new TextToStringConverter();
|
||||
|
||||
private JsonObject serialize(Text text) {
|
||||
JsonObject jsonObject = new JsonObject();
|
||||
jsonObject.addProperty("text", text.getContent());
|
||||
|
||||
if (text.getColor() != null) {
|
||||
jsonObject.addProperty("color", text.getColor().getName());
|
||||
}
|
||||
|
||||
if (text.getStyle() != null) {
|
||||
if (text.getStyle().getBold().isPresent()) {
|
||||
jsonObject.addProperty("bold", text.getStyle().getBold().get());
|
||||
}
|
||||
if (text.getStyle().getItalic().isPresent()) {
|
||||
jsonObject.addProperty("italic", text.getStyle().getItalic().get());
|
||||
}
|
||||
if (text.getStyle().getObfuscated().isPresent()) {
|
||||
jsonObject.addProperty("obfuscated", text.getStyle().getObfuscated().get());
|
||||
}
|
||||
if (text.getStyle().getStrikethrough().isPresent()) {
|
||||
jsonObject.addProperty("strikethrough", text.getStyle().getStrikethrough().get());
|
||||
}
|
||||
if (text.getStyle().getUnderline().isPresent()) {
|
||||
jsonObject.addProperty("underlined", text.getStyle().getUnderline().get());
|
||||
}
|
||||
}
|
||||
|
||||
if (text.getChildren() != null) {
|
||||
JsonArray extra = new JsonArray();
|
||||
text.getChildren().forEach(child -> extra.add(serialize(child)));
|
||||
jsonObject.add("extra", extra);
|
||||
}
|
||||
|
||||
return jsonObject;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String mapping(Text fromObject) {
|
||||
return serialize(fromObject).toString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
package mc.core.network.proto_1_12_2;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
@Slf4j
|
||||
public class ByteArrayInputNetStream extends NetInputStream_p340 {
|
||||
private ByteArrayInputStream bais;
|
||||
|
||||
public ByteArrayInputNetStream(byte[] buff) {
|
||||
bais = new ByteArrayInputStream(buff);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean readBoolean() {
|
||||
return readByte() != 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte readByte() {
|
||||
return (byte) bais.read();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int readBytes(byte[] buffer, int offset, int length) {
|
||||
try {
|
||||
int read = bais.read(buffer, offset, length);
|
||||
if (read < length) {
|
||||
throw new IOException("not enough data");
|
||||
}
|
||||
return read;
|
||||
} catch (IOException e) {
|
||||
log.error("", e);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int readUnsignedByte() {
|
||||
return bais.read() & 0xFF;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int readUnsignedShort() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public short readShort() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int readInt() {
|
||||
int ch1 = bais.read();
|
||||
int ch2 = bais.read();
|
||||
int ch3 = bais.read();
|
||||
int ch4 = bais.read();
|
||||
if ((ch1 | ch2 | ch3 | ch4) < 0) return 0;
|
||||
return ((ch1 << 24) + (ch2 << 16) + (ch3 << 8) + (ch4));
|
||||
}
|
||||
|
||||
@Override
|
||||
public long readLong() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public float readFloat() {
|
||||
return Float.intBitsToFloat(readInt());
|
||||
}
|
||||
|
||||
@Override
|
||||
public double readDouble() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void skipBytes(int count) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
package mc.core.network.proto_1_12_2;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.io.IOException;
|
||||
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;
|
||||
|
||||
@BeforeEach
|
||||
void before() {
|
||||
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];
|
||||
random.nextBytes(bytes);
|
||||
|
||||
ByteArrayOutputNetStream byteArrayOutputNetStream = new ByteArrayOutputNetStream();
|
||||
byteArrayOutputNetStream.writeByte(bytes[0]);
|
||||
|
||||
ByteArrayInputNetStream byteArrayInputNetStream = new ByteArrayInputNetStream(byteArrayOutputNetStream.toByteArray());
|
||||
|
||||
assertEquals(bytes[0], byteArrayInputNetStream.readByte());
|
||||
|
||||
byteArrayInputNetStream = new ByteArrayInputNetStream(byteArrayOutputNetStream.toByteArray());
|
||||
|
||||
assertEquals(bytes[0], byteArrayInputNetStream.read());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testReadBytes() throws IOException {
|
||||
final byte[] expectedBytes = new byte[10];
|
||||
random.nextBytes(expectedBytes);
|
||||
|
||||
ByteArrayOutputNetStream byteArrayOutputNetStream = new ByteArrayOutputNetStream();
|
||||
byteArrayOutputNetStream.writeBytes(expectedBytes);
|
||||
|
||||
ByteArrayInputNetStream byteArrayInputNetStream = new ByteArrayInputNetStream(byteArrayOutputNetStream.toByteArray());
|
||||
byte[] actualBytes = new byte[10];
|
||||
byteArrayInputNetStream.readBytes(actualBytes);
|
||||
|
||||
assertArrayEquals(expectedBytes, actualBytes);
|
||||
|
||||
byteArrayInputNetStream = new ByteArrayInputNetStream(byteArrayOutputNetStream.toByteArray());
|
||||
actualBytes = new byte[10];
|
||||
int r = byteArrayInputNetStream.read(actualBytes);
|
||||
|
||||
assertArrayEquals(expectedBytes, actualBytes);
|
||||
assertEquals(expectedBytes.length, r);
|
||||
|
||||
byteArrayInputNetStream = new ByteArrayInputNetStream(byteArrayOutputNetStream.toByteArray());
|
||||
actualBytes = new byte[10];
|
||||
byteArrayInputNetStream.readBytes(actualBytes, 2, 5);
|
||||
byte[] nibbleExpectedBytes = new byte[10];
|
||||
System.arraycopy(expectedBytes, 0, nibbleExpectedBytes, 2, 5);
|
||||
|
||||
assertArrayEquals(nibbleExpectedBytes, actualBytes);
|
||||
|
||||
byteArrayInputNetStream = new ByteArrayInputNetStream(byteArrayOutputNetStream.toByteArray());
|
||||
actualBytes = new byte[10];
|
||||
r = byteArrayInputNetStream.read(actualBytes, 2, 5);
|
||||
nibbleExpectedBytes = new byte[10];
|
||||
System.arraycopy(expectedBytes, 0, nibbleExpectedBytes, 2, 5);
|
||||
|
||||
assertArrayEquals(nibbleExpectedBytes, actualBytes);
|
||||
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();
|
||||
|
||||
ByteArrayOutputNetStream byteArrayOutputNetStream = new ByteArrayOutputNetStream();
|
||||
byteArrayOutputNetStream.writeInt(integerDig);
|
||||
|
||||
ByteArrayInputNetStream byteArrayInputNetStream = new ByteArrayInputNetStream(byteArrayOutputNetStream.toByteArray());
|
||||
|
||||
assertEquals(integerDig, byteArrayInputNetStream.readInt());
|
||||
}
|
||||
|
||||
@Test
|
||||
void readFloat() {
|
||||
final float floatDig = random.nextFloat();
|
||||
|
||||
ByteArrayOutputNetStream byteArrayOutputNetStream = new ByteArrayOutputNetStream();
|
||||
byteArrayOutputNetStream.writeFloat(floatDig);
|
||||
|
||||
ByteArrayInputNetStream byteArrayInputNetStream = new ByteArrayInputNetStream(byteArrayOutputNetStream.toByteArray());
|
||||
|
||||
assertEquals(floatDig, byteArrayInputNetStream.readFloat());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,259 @@
|
||||
package mc.core.network.proto_1_12_2;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Random;
|
||||
import java.util.UUID;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
|
||||
|
||||
class ByteArrayOutputNetStreamTest {
|
||||
private Random random;
|
||||
|
||||
@BeforeEach
|
||||
void before() {
|
||||
random = new Random(System.currentTimeMillis());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testWriteBoolean() {
|
||||
ByteArrayOutputNetStream byteArrayOutputNetStream = new ByteArrayOutputNetStream();
|
||||
byteArrayOutputNetStream.writeBoolean(true);
|
||||
|
||||
assertArrayEquals(new byte[]{0x01}, byteArrayOutputNetStream.toByteArray());
|
||||
|
||||
byteArrayOutputNetStream = new ByteArrayOutputNetStream();
|
||||
byteArrayOutputNetStream.writeBoolean(false);
|
||||
|
||||
assertArrayEquals(new byte[]{0x00}, byteArrayOutputNetStream.toByteArray());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testWriteByte() throws IOException {
|
||||
final byte[] bytes = new byte[1];
|
||||
random.nextBytes(bytes);
|
||||
|
||||
ByteArrayOutputNetStream byteArrayOutputNetStream = new ByteArrayOutputNetStream();
|
||||
byteArrayOutputNetStream.writeByte(bytes[0]);
|
||||
|
||||
assertArrayEquals(bytes, byteArrayOutputNetStream.toByteArray());
|
||||
|
||||
byteArrayOutputNetStream = new ByteArrayOutputNetStream();
|
||||
byteArrayOutputNetStream.write(bytes[0]);
|
||||
|
||||
assertArrayEquals(bytes, byteArrayOutputNetStream.toByteArray());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testWriteUnsignedByte() {
|
||||
final byte[] bytes = new byte[1];
|
||||
random.nextBytes(bytes);
|
||||
|
||||
ByteArrayOutputNetStream byteArrayOutputNetStream = new ByteArrayOutputNetStream();
|
||||
byteArrayOutputNetStream.writeUnsignedByte(bytes[0]);
|
||||
|
||||
assertArrayEquals(bytes, byteArrayOutputNetStream.toByteArray());
|
||||
|
||||
byteArrayOutputNetStream = new ByteArrayOutputNetStream();
|
||||
byteArrayOutputNetStream.writeUnsignedByte(0xFF);
|
||||
|
||||
assertArrayEquals(new byte[]{(byte) 0xFF}, byteArrayOutputNetStream.toByteArray());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testWriteBytes() throws IOException {
|
||||
final byte[] expectedBytes = new byte[10];
|
||||
random.nextBytes(expectedBytes);
|
||||
|
||||
ByteArrayOutputNetStream byteArrayOutputNetStream = new ByteArrayOutputNetStream();
|
||||
byteArrayOutputNetStream.writeBytes(expectedBytes);
|
||||
|
||||
assertArrayEquals(expectedBytes, byteArrayOutputNetStream.toByteArray());
|
||||
|
||||
byteArrayOutputNetStream = new ByteArrayOutputNetStream();
|
||||
byteArrayOutputNetStream.write(expectedBytes);
|
||||
|
||||
assertArrayEquals(expectedBytes, byteArrayOutputNetStream.toByteArray());
|
||||
|
||||
byteArrayOutputNetStream = new ByteArrayOutputNetStream();
|
||||
byteArrayOutputNetStream.writeBytes(expectedBytes, 2, 5);
|
||||
byte[] nibbleExpectedBytes = new byte[5];
|
||||
System.arraycopy(expectedBytes, 2, nibbleExpectedBytes, 0, 5);
|
||||
|
||||
assertArrayEquals(nibbleExpectedBytes, byteArrayOutputNetStream.toByteArray());
|
||||
|
||||
byteArrayOutputNetStream = new ByteArrayOutputNetStream();
|
||||
byteArrayOutputNetStream.write(expectedBytes, 2, 5);
|
||||
nibbleExpectedBytes = new byte[5];
|
||||
System.arraycopy(expectedBytes, 2, nibbleExpectedBytes, 0, 5);
|
||||
|
||||
assertArrayEquals(nibbleExpectedBytes, byteArrayOutputNetStream.toByteArray());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testWriteShort() {
|
||||
int smallInt;
|
||||
do {
|
||||
smallInt = random.nextInt();
|
||||
} while (smallInt > Short.MAX_VALUE || smallInt < Short.MIN_VALUE);
|
||||
|
||||
ByteArrayOutputNetStream byteArrayOutputNetStream = new ByteArrayOutputNetStream();
|
||||
byteArrayOutputNetStream.writeShort(smallInt);
|
||||
|
||||
assertArrayEquals(new byte[]{ (byte) (smallInt >>> 8),
|
||||
(byte) smallInt },
|
||||
byteArrayOutputNetStream.toByteArray());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testWriteInt() {
|
||||
final int integerDig = random.nextInt();
|
||||
|
||||
ByteArrayOutputNetStream byteArrayOutputNetStream = new ByteArrayOutputNetStream();
|
||||
byteArrayOutputNetStream.writeInt(integerDig);
|
||||
|
||||
assertArrayEquals(new byte[]{ (byte) ((integerDig >>> 24) & 0xFF),
|
||||
(byte) ((integerDig >>> 16) & 0xFF),
|
||||
(byte) ((integerDig >>> 8) & 0xFF),
|
||||
(byte) (integerDig & 0xFF) },
|
||||
byteArrayOutputNetStream.toByteArray());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testWriteLong() {
|
||||
final long longDig = random.nextLong();
|
||||
|
||||
ByteArrayOutputNetStream byteArrayOutputNetStream = new ByteArrayOutputNetStream();
|
||||
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) },
|
||||
byteArrayOutputNetStream.toByteArray());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testWriteFloat() {
|
||||
final float floatDig = random.nextFloat();
|
||||
|
||||
ByteArrayOutputNetStream byteArrayOutputNetStream = new ByteArrayOutputNetStream();
|
||||
byteArrayOutputNetStream.writeFloat(floatDig);
|
||||
final int floatBits = Float.floatToIntBits(floatDig);
|
||||
|
||||
assertArrayEquals(new byte[]{ (byte) ((floatBits >>> 24) & 0xFF),
|
||||
(byte) ((floatBits >>> 16) & 0xFF),
|
||||
(byte) ((floatBits >>> 8) & 0xFF),
|
||||
(byte) (floatBits & 0xFF) },
|
||||
byteArrayOutputNetStream.toByteArray());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testWriteDouble() {
|
||||
final double doubleDig = random.nextDouble();
|
||||
|
||||
ByteArrayOutputNetStream byteArrayOutputNetStream = new ByteArrayOutputNetStream();
|
||||
byteArrayOutputNetStream.writeDouble(doubleDig);
|
||||
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) },
|
||||
byteArrayOutputNetStream.toByteArray());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testWriteVarInt() {
|
||||
final int b1Int = 120;
|
||||
ByteArrayOutputNetStream byteArrayOutputNetStream = new ByteArrayOutputNetStream();
|
||||
byteArrayOutputNetStream.writeVarInt(b1Int);
|
||||
|
||||
assertArrayEquals(new byte[]{ 0x78 },
|
||||
byteArrayOutputNetStream.toByteArray());
|
||||
|
||||
final int b2Int = 12000;
|
||||
byteArrayOutputNetStream = new ByteArrayOutputNetStream();
|
||||
byteArrayOutputNetStream.writeVarInt(b2Int);
|
||||
|
||||
assertArrayEquals(new byte[]{ (byte) 0xE0, 0x5D },
|
||||
byteArrayOutputNetStream.toByteArray());
|
||||
|
||||
final int b3Int = 120000;
|
||||
byteArrayOutputNetStream = new ByteArrayOutputNetStream();
|
||||
byteArrayOutputNetStream.writeVarInt(b3Int);
|
||||
|
||||
assertArrayEquals(new byte[]{ (byte) 0xC0, (byte) 0xA9, 0x07 },
|
||||
byteArrayOutputNetStream.toByteArray());
|
||||
|
||||
final int b4Int = 120000000;
|
||||
byteArrayOutputNetStream = new ByteArrayOutputNetStream();
|
||||
byteArrayOutputNetStream.writeVarInt(b4Int);
|
||||
|
||||
assertArrayEquals(new byte[]{ (byte) 0x80, (byte) 0x9C, (byte) 0x9C, (byte) 0x39 },
|
||||
byteArrayOutputNetStream.toByteArray());
|
||||
|
||||
final int b5Int = 1200000000;
|
||||
byteArrayOutputNetStream = new ByteArrayOutputNetStream();
|
||||
byteArrayOutputNetStream.writeVarInt(b5Int);
|
||||
|
||||
assertArrayEquals(new byte[]{ (byte) 0x80, (byte) 0x98, (byte) 0x9A, (byte) 0xBC, 0x04 },
|
||||
byteArrayOutputNetStream.toByteArray());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testWriteString() {
|
||||
final String string = "Hello? Есть тут кто?";
|
||||
|
||||
ByteArrayOutputNetStream byteArrayOutputNetStream = new ByteArrayOutputNetStream();
|
||||
byteArrayOutputNetStream.writeString(string);
|
||||
|
||||
final byte[] strBytes = string.getBytes(StandardCharsets.UTF_8);
|
||||
final byte[] bytes = new byte[strBytes.length + 1];
|
||||
bytes[0] = (byte) string.length(); // здесь считается, что размер поместится в один байт
|
||||
System.arraycopy(strBytes, 0, bytes, 1, strBytes.length);
|
||||
|
||||
assertArrayEquals(bytes, byteArrayOutputNetStream.toByteArray());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testWriteUUID() {
|
||||
final UUID uuid = UUID.randomUUID();
|
||||
|
||||
ByteArrayOutputNetStream byteArrayOutputNetStream = new ByteArrayOutputNetStream();
|
||||
byteArrayOutputNetStream.writeUUID(uuid);
|
||||
|
||||
final long mostSignificantBits = uuid.getMostSignificantBits();
|
||||
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) ((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,313 @@
|
||||
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.network.proto_1_12_2.packets.serverside.ChunkDataPacket;
|
||||
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));
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package mc.core.network.proto_1_12_2.packets;
|
||||
|
||||
import mc.core.network.proto_1_12_2.ByteArrayInputNetStream;
|
||||
import mc.core.network.proto_1_12_2.ByteArrayOutputNetStream;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.Random;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
class PlayerAbilitiesPacketTest {
|
||||
private Random rnd = new Random();
|
||||
private PlayerAbilitiesPacket packet;
|
||||
|
||||
@BeforeEach
|
||||
void before() {
|
||||
packet = new PlayerAbilitiesPacket();
|
||||
packet.setGodMode(rnd.nextBoolean());
|
||||
packet.setFlying(rnd.nextBoolean());
|
||||
packet.setCanFly(rnd.nextBoolean());
|
||||
packet.setInstantDestroyBlocks(rnd.nextBoolean());
|
||||
packet.setFlyingSpeed(rnd.nextFloat());
|
||||
}
|
||||
|
||||
@Test
|
||||
void writePacket() {
|
||||
ByteArrayOutputNetStream netOutputStream = new ByteArrayOutputNetStream();
|
||||
packet.writeSelf(netOutputStream);
|
||||
|
||||
ByteArrayInputNetStream netInputStream = new ByteArrayInputNetStream(netOutputStream.toByteArray());
|
||||
PlayerAbilitiesPacket outPkt = new PlayerAbilitiesPacket();
|
||||
outPkt.readSelf(netInputStream);
|
||||
|
||||
assertEquals(packet.isGodMode(), outPkt.isGodMode(), "god mode");
|
||||
assertEquals(packet.isFlying(), outPkt.isFlying(), "flying");
|
||||
assertEquals(packet.isCanFly(), outPkt.isCanFly(), "can fly");
|
||||
assertEquals(packet.isInstantDestroyBlocks(), outPkt.isInstantDestroyBlocks(), "instant destroy block");
|
||||
assertEquals(packet.getFlyingSpeed(), outPkt.getFlyingSpeed(), 0.00001f, "flying speed");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package mc.core.network.proto_1_12_2.serializers;
|
||||
|
||||
import mc.core.world.block.BlockLocation;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.concurrent.ThreadLocalRandom;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
class BlockLocationSerializerTest {
|
||||
private static final ThreadLocalRandom rnd = ThreadLocalRandom.current();
|
||||
private static final int minI = 0, maxI = 10;
|
||||
private int x, y, z;
|
||||
|
||||
@BeforeEach
|
||||
void before() {
|
||||
x = rnd.nextInt(minI, maxI);
|
||||
y = rnd.nextInt(minI, maxI);
|
||||
z = rnd.nextInt(minI, maxI);
|
||||
}
|
||||
|
||||
@Test
|
||||
void serialize() {
|
||||
BlockLocation location = new BlockLocation(x, y, z);
|
||||
final long serializedCoords = BlockLocationSerializer.toLong(location);
|
||||
|
||||
BlockLocation deserLoc = BlockLocationSerializer.fromLong(serializedCoords);
|
||||
|
||||
assertEquals(location, deserLoc);
|
||||
}
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
7
proto_1.12.2_netty/README.MD
Normal file
7
proto_1.12.2_netty/README.MD
Normal file
@@ -0,0 +1,7 @@
|
||||
# Protocol 1.12.2: Netty impl.
|
||||
|
||||
Реализация протокола на сетевом движке [Netty.IO](https://netty.io/).
|
||||
|
||||
Пример настройки можно посмотреть в файле `sample-config.xml`
|
||||
|
||||
|
||||
13
proto_1.12.2_netty/build.gradle
Normal file
13
proto_1.12.2_netty/build.gradle
Normal file
@@ -0,0 +1,13 @@
|
||||
version '0.1'
|
||||
|
||||
ext {
|
||||
netty_version = '4.1.22.Final'
|
||||
}
|
||||
|
||||
dependencies {
|
||||
/* Protocol 1.12.2 */
|
||||
compile_excludeCopy project(':proto_1.12.2')
|
||||
|
||||
/* Netty */
|
||||
compile (group: 'io.netty', name: 'netty-all', version: netty_version)
|
||||
}
|
||||
24
proto_1.12.2_netty/sample-config.xml
Normal file
24
proto_1.12.2_netty/sample-config.xml
Normal file
@@ -0,0 +1,24 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:context="http://www.springframework.org/schema/context"
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/beans
|
||||
http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
|
||||
http://www.springframework.org/schema/context
|
||||
http://www.springframework.org/schema/context/spring-context.xsd">
|
||||
<context:annotation-config />
|
||||
<context:component-scan base-package="mc.core.network.proto_1_12_2.netty" />
|
||||
|
||||
<!-- Для логирования сетевых пакетов, можно добавить нижезакомментированный бин -->
|
||||
<!-- <bean id="pipeline.log" class="io.netty.handler.logging.LoggingHandler" scope="prototype"/> -->
|
||||
<bean id="pipeline.decoder" class="mc.core.network.proto_1_12_2.netty.PacketDecoder" scope="prototype"/>
|
||||
<bean id="pipeline.postencoder" class="mc.core.network.proto_1_12_2.netty.PacketPostEncoder" scope="prototype"/>
|
||||
<bean id="pipeline.encoder" class="mc.core.network.proto_1_12_2.netty.PacketEncoder" scope="prototype"/>
|
||||
<bean id="pipeline.handler" class="mc.core.network.proto_1_12_2.netty.PacketHandler" scope="prototype"/>
|
||||
|
||||
<bean id="server" class="mc.core.network.proto_1_12_2.netty.NettyServer">
|
||||
<property name="host" value="127.0.0.1"/>
|
||||
<property name="port" value="25565"/>
|
||||
<property name="workerGroupCount" value="2"/>
|
||||
</bean>
|
||||
</beans>
|
||||
@@ -0,0 +1,52 @@
|
||||
package mc.core.network.proto_1_12_2.netty;
|
||||
|
||||
import lombok.Setter;
|
||||
import mc.core.network.proto_1_12_2.packets.KeepAlivePacket;
|
||||
import mc.core.player.PlayerManager;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
public class KeepAliveThread extends Thread {
|
||||
private final Object lock = new Object();
|
||||
@Autowired
|
||||
private PlayerManager playerManager;
|
||||
@Setter
|
||||
private int interval = 10;
|
||||
|
||||
public KeepAliveThread() {
|
||||
super("Keep-Alive Thread");
|
||||
}
|
||||
|
||||
public void notifyLock() {
|
||||
synchronized (lock) {
|
||||
lock.notify();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
final KeepAlivePacket keepAlivePacket = new KeepAlivePacket();
|
||||
|
||||
while (!Thread.currentThread().isInterrupted()) {
|
||||
while(playerManager.getCountPlayers() == 0) {
|
||||
synchronized (lock) {
|
||||
try {
|
||||
lock.wait();
|
||||
} catch (InterruptedException e) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
keepAlivePacket.setPayload(System.currentTimeMillis());
|
||||
playerManager.getBroadcastChannel().writeAndFlush(keepAlivePacket);
|
||||
|
||||
try {
|
||||
Thread.sleep(interval);
|
||||
} catch (InterruptedException e) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
package mc.core.network.proto_1_12_2.netty;
|
||||
|
||||
import io.netty.bootstrap.ServerBootstrap;
|
||||
import io.netty.channel.ChannelFuture;
|
||||
import io.netty.channel.ChannelHandler;
|
||||
import io.netty.channel.ChannelInitializer;
|
||||
import io.netty.channel.EventLoopGroup;
|
||||
import io.netty.channel.nio.NioEventLoopGroup;
|
||||
import io.netty.channel.socket.SocketChannel;
|
||||
import io.netty.channel.socket.nio.NioServerSocketChannel;
|
||||
import io.netty.util.AttributeKey;
|
||||
import lombok.Setter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import mc.core.eventbus.EventBus;
|
||||
import mc.core.network.Server;
|
||||
import mc.core.network.StartServerException;
|
||||
import mc.core.network.proto_1_12_2.State;
|
||||
import mc.core.network.proto_1_12_2.packets.serverside.StatusResponsePacket;
|
||||
import mc.core.player.Player;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
@Slf4j
|
||||
@Component
|
||||
public class NettyServer implements Server {
|
||||
public static final AttributeKey<State> ATTR_STATE = AttributeKey.newInstance("ATTR_STATE");
|
||||
public static final AttributeKey<Player> ATTR_PLAYER = AttributeKey.newInstance("ATTR_PLAYER");
|
||||
|
||||
@Autowired
|
||||
private ApplicationContext context;
|
||||
@Autowired
|
||||
private KeepAliveThread keepAliveThread;
|
||||
@Setter
|
||||
private String host;
|
||||
@Setter
|
||||
private int port;
|
||||
@Setter
|
||||
private int workerGroupCount = 0;
|
||||
private EventLoopGroup bossGroup, workerGroup;
|
||||
|
||||
private ChannelInitializer buildChannelInitializer() {
|
||||
return new ChannelInitializer<SocketChannel>() {
|
||||
@Override
|
||||
protected void initChannel(SocketChannel socketChannel) {
|
||||
Map<String, ChannelHandler> beans = context.getBeansOfType(ChannelHandler.class);
|
||||
beans.forEach(socketChannel.pipeline()::addLast);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private ServerBootstrap buildServerBootstrap() {
|
||||
ServerBootstrap bootstrap = new ServerBootstrap();
|
||||
|
||||
bootstrap.group(bossGroup, workerGroup)
|
||||
.channel(NioServerSocketChannel.class)
|
||||
.childHandler(buildChannelInitializer());
|
||||
|
||||
return bootstrap;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start() throws StartServerException {
|
||||
log.info("Use protocol {}", StatusResponsePacket.NAME);
|
||||
|
||||
EventBus.getInstance().registerSubscribes(new PlayerEventListener());
|
||||
|
||||
bossGroup = new NioEventLoopGroup(1);
|
||||
workerGroup = new NioEventLoopGroup(workerGroupCount);
|
||||
|
||||
ServerBootstrap serverBootstrap = buildServerBootstrap();
|
||||
|
||||
log.info("Start server: {}:{}", host, port);
|
||||
try {
|
||||
ChannelFuture channelFuture = serverBootstrap.bind(host, port).sync();
|
||||
keepAliveThread.start();
|
||||
channelFuture.channel().closeFuture().sync();
|
||||
} catch (InterruptedException e) {
|
||||
throw new StartServerException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop() {
|
||||
log.info("Server shutdown");
|
||||
keepAliveThread.interrupt();
|
||||
workerGroup.shutdownGracefully();
|
||||
bossGroup.shutdownGracefully();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
package mc.core.network.proto_1_12_2.netty;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.handler.codec.ReplayingDecoder;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import mc.core.network.CSPacket;
|
||||
import mc.core.network.NetInputStream;
|
||||
import mc.core.network.proto_1_12_2.State;
|
||||
import mc.core.network.proto_1_12_2.netty.wrappers.WrapperNetInputStream;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import static mc.core.network.proto_1_12_2.netty.NettyServer.ATTR_STATE;
|
||||
|
||||
@Slf4j
|
||||
public class PacketDecoder extends ReplayingDecoder<CSPacket> {
|
||||
|
||||
@Override
|
||||
public void channelActive(ChannelHandlerContext ctx) {
|
||||
ctx.channel().attr(ATTR_STATE).set(State.HANDSHAKE);
|
||||
ctx.fireChannelActive();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelInactive(ChannelHandlerContext ctx) {
|
||||
ctx.channel().attr(ATTR_STATE).set(null);
|
||||
ctx.fireChannelInactive();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
|
||||
State state = ctx.channel().attr(ATTR_STATE).get();
|
||||
NetInputStream netStream = new WrapperNetInputStream(in);
|
||||
|
||||
int packetSize = netStream.readVarInt();
|
||||
log.debug("Packet size: {}", packetSize);
|
||||
int leftDataPacket = packetSize;
|
||||
|
||||
final AtomicInteger countReadBytes = new AtomicInteger(0);
|
||||
int packetId = netStream.readVarInt(countReadBytes);
|
||||
String hexPacketId = Integer.toHexString(packetId).toUpperCase();
|
||||
if (hexPacketId.length() == 1) hexPacketId = "0" + hexPacketId;
|
||||
log.debug("Packet id: 0x{}", hexPacketId);
|
||||
leftDataPacket = leftDataPacket - countReadBytes.get();
|
||||
|
||||
Class<? extends CSPacket> packetClass = state.getClientSidePacket(packetId);
|
||||
if (packetClass == null) {
|
||||
log.warn("Unknown packet: {}:0x{}", state.name(), hexPacketId); //TODO
|
||||
in.skipBytes(leftDataPacket);
|
||||
} else {
|
||||
netStream.setDataSize(leftDataPacket);
|
||||
CSPacket packet = packetClass.newInstance();
|
||||
try {
|
||||
packet.readSelf(netStream);
|
||||
log.debug("Known packet: {}:{}", state.name(), packet.toString());
|
||||
out.add(packet);
|
||||
} catch (Exception e) {
|
||||
log.warn("Known packet: {}:{}. But throw exception. See debug log.", state.name(), packet.getClass().getSimpleName());
|
||||
log.debug("Read packet", e);
|
||||
in.skipBytes(leftDataPacket);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package mc.core.network.proto_1_12_2.netty;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.handler.codec.MessageToByteEncoder;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import mc.core.network.NetOutputStream;
|
||||
import mc.core.network.SCPacket;
|
||||
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> {
|
||||
|
||||
@Override
|
||||
protected void encode(ChannelHandlerContext ctx, SCPacket packet, ByteBuf out) {
|
||||
State state = ctx.channel().attr(ATTR_STATE).get();
|
||||
Integer id = state.getServerSidePacket(packet.getClass());
|
||||
if (id == null) {
|
||||
log.error("Not defined ID packet: {}:{}", state.name(), packet.getClass());
|
||||
return;
|
||||
}
|
||||
|
||||
log.debug("Send {}:{}", state, packet);
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
package mc.core.network.proto_1_12_2.netty;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.SimpleChannelInboundHandler;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import mc.core.chat.ChatProcessor;
|
||||
import mc.core.network.CSPacket;
|
||||
import mc.core.network.proto_1_12_2.State;
|
||||
import mc.core.network.proto_1_12_2.netty.handlers.*;
|
||||
import mc.core.player.Player;
|
||||
import mc.core.player.PlayerManager;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.annotation.ComponentScan;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import java.util.Collection;
|
||||
|
||||
import static mc.core.network.proto_1_12_2.netty.NettyServer.ATTR_PLAYER;
|
||||
import static mc.core.network.proto_1_12_2.netty.NettyServer.ATTR_STATE;
|
||||
|
||||
@Slf4j
|
||||
@ComponentScan("mc.core.network.proto_1_12_2.netty.handlers")
|
||||
public class PacketHandler extends SimpleChannelInboundHandler<CSPacket> {
|
||||
@Autowired
|
||||
private ApplicationContext applicationContext;
|
||||
@Autowired
|
||||
private PlayerManager playerManager;
|
||||
@Autowired
|
||||
private ChatProcessor chatProcessor;
|
||||
private ImmutableMap<State, ImmutableList<StateHandler>> handlersMap = ImmutableMap.of();
|
||||
|
||||
@PostConstruct
|
||||
public void init() {
|
||||
ImmutableMap.Builder<State, ImmutableList<StateHandler>> builder = ImmutableMap.builder();
|
||||
|
||||
Collection<HandshakeStateHandler> beans1 = applicationContext.getBeansOfType(HandshakeStateHandler.class).values();
|
||||
builder.put(State.HANDSHAKE, ImmutableList.copyOf(beans1));
|
||||
|
||||
Collection<StatusStateHandler> beans2 = applicationContext.getBeansOfType(StatusStateHandler.class).values();
|
||||
builder.put(State.STATUS, ImmutableList.copyOf(beans2));
|
||||
|
||||
Collection<LoginStateHandler> beans3 = applicationContext.getBeansOfType(LoginStateHandler.class).values();
|
||||
builder.put(State.LOGIN, ImmutableList.copyOf(beans3));
|
||||
|
||||
Collection<PlayStateHandler> beans4 = applicationContext.getBeansOfType(PlayStateHandler.class).values();
|
||||
builder.put(State.PLAY, ImmutableList.copyOf(beans4));
|
||||
|
||||
this.handlersMap = builder.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
|
||||
super.channelInactive(ctx);
|
||||
Player player = ctx.channel().attr(ATTR_PLAYER).get();
|
||||
if (player != null) {
|
||||
playerManager.leftServer(player);
|
||||
player.setChannel(null);
|
||||
}
|
||||
ctx.channel().attr(ATTR_PLAYER).set(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void channelRead0(ChannelHandlerContext ctx, CSPacket packet) throws Exception {
|
||||
ImmutableList<StateHandler> stateHandlers = this.handlersMap.get(ctx.channel().attr(ATTR_STATE).get());
|
||||
for (StateHandler handler : stateHandlers) {
|
||||
handler.handle(ctx.channel(), packet);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package mc.core.network.proto_1_12_2.netty;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.handler.codec.MessageToByteEncoder;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import mc.core.network.proto_1_12_2.netty.wrappers.WrapperNetOutputStream;
|
||||
|
||||
/**
|
||||
* Подсчет размера отправляемого пакета
|
||||
*/
|
||||
@Slf4j
|
||||
public class PacketPostEncoder extends MessageToByteEncoder<ByteBuf> {
|
||||
|
||||
private static int getVarIntSize(int input) {
|
||||
for (int i = 1; i < 5; ++i) {
|
||||
if ((input & -1 << i * 7) == 0) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
return 5;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void encode(ChannelHandlerContext ctx, ByteBuf msg, ByteBuf out) {
|
||||
final int pktSize = msg.readableBytes();
|
||||
final int sizeOfPktSize = getVarIntSize(pktSize);
|
||||
|
||||
if (sizeOfPktSize > 3) {
|
||||
log.warn("unable to fit {} into {}", pktSize, 3);
|
||||
}
|
||||
|
||||
out.ensureWritable(sizeOfPktSize + pktSize);
|
||||
(new WrapperNetOutputStream(out)).writeVarInt(pktSize);
|
||||
out.writeBytes(msg);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
package mc.core.network.proto_1_12_2.netty;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
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.serverside.ChunkDataPacket;
|
||||
import mc.core.network.proto_1_12_2.packets.PlayerPositionAndLookPacket;
|
||||
import mc.core.network.proto_1_12_2.packets.serverside.UnloadChunkPacket;
|
||||
import mc.core.utils.CompactedCoords;
|
||||
import mc.core.world.chunk.Chunk;
|
||||
|
||||
@Slf4j
|
||||
public class PlayerEventListener {
|
||||
@Subscriber
|
||||
public void playerMoveEventHandler(SC_PlayerMoveEvent event) {
|
||||
log.debug("(SC) playerMoveEventHandler()");
|
||||
PlayerPositionAndLookPacket packet = new PlayerPositionAndLookPacket();
|
||||
packet.setLocation(event.getNewLocation());
|
||||
int tpId = TeleportManager.getInstance().append(event.getPlayer(), event.getNewLocation());
|
||||
packet.setTeleportId(tpId);
|
||||
|
||||
event.getPlayer().getChannel().writeAndFlush(packet);
|
||||
}
|
||||
|
||||
@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]);
|
||||
packet.setZ(xz[1]);
|
||||
packet.setInitChunk(true);
|
||||
packet.setChunk(chunk);
|
||||
|
||||
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);
|
||||
|
||||
UnloadChunkPacket packet = new UnloadChunkPacket();
|
||||
packet.setX(xz[0]);
|
||||
packet.setZ(xz[1]);
|
||||
|
||||
channel.write(packet);
|
||||
}
|
||||
|
||||
channel.flush();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
package mc.core.network.proto_1_12_2.netty.handlers;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import io.netty.channel.Channel;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import mc.core.network.CSPacket;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static mc.core.network.proto_1_12_2.netty.NettyServer.ATTR_STATE;
|
||||
|
||||
@Slf4j
|
||||
public abstract class AbstractStateHandler implements StateHandler {
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.METHOD)
|
||||
protected @interface Handler {
|
||||
}
|
||||
|
||||
private ImmutableMap<Class<? /*extends CSPacket*/>, Method> methods = ImmutableMap.of();
|
||||
|
||||
@PostConstruct
|
||||
protected void init() {
|
||||
this.methods = Stream.of(this.getClass().getDeclaredMethods())
|
||||
.filter(method -> method.isAnnotationPresent(Handler.class)
|
||||
&& method.getParameterCount() == 2
|
||||
&& method.getParameterTypes()[0].isAssignableFrom(Channel.class)
|
||||
&& CSPacket.class.isAssignableFrom(method.getParameterTypes()[1]))
|
||||
.collect(ImmutableMap.toImmutableMap(
|
||||
method -> method.getParameterTypes()[1],
|
||||
Function.identity()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handle(Channel channel, CSPacket packet) throws Exception {
|
||||
if (this.methods.containsKey(packet.getClass())) {
|
||||
this.methods.get(packet.getClass()).invoke(this, channel, packet);
|
||||
} else {
|
||||
log.trace("No def listener of {}:{}",
|
||||
channel.attr(ATTR_STATE).get(),
|
||||
packet.getClass().getSimpleName());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package mc.core.network.proto_1_12_2.netty.handlers;
|
||||
|
||||
import io.netty.channel.Channel;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import mc.core.network.proto_1_12_2.State;
|
||||
import mc.core.network.proto_1_12_2.packets.clientside.HandshakePacket;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import static mc.core.network.proto_1_12_2.netty.NettyServer.ATTR_STATE;
|
||||
|
||||
@Slf4j
|
||||
@Component
|
||||
public class HandshakeHandler extends AbstractStateHandler implements HandshakeStateHandler {
|
||||
@Handler
|
||||
public void onHandshake(Channel channel, HandshakePacket packet) {
|
||||
if (packet.getNextState().equals(State.UNKNOWN)) return;
|
||||
//FIXME обрати внимание: хацкер может намеренно передать state:03 и тогда может начатся пиздец
|
||||
log.debug("New state: {}", packet.getNextState());
|
||||
channel.attr(ATTR_STATE).set(packet.getNextState());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package mc.core.network.proto_1_12_2.netty.handlers;
|
||||
|
||||
/**
|
||||
* Marker interface
|
||||
*/
|
||||
public interface HandshakeStateHandler extends StateHandler {
|
||||
}
|
||||
@@ -0,0 +1,129 @@
|
||||
package mc.core.network.proto_1_12_2.netty.handlers;
|
||||
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelFutureListener;
|
||||
import mc.core.eventbus.EventBus;
|
||||
import mc.core.eventbus.events.CS_PlayerMoveEvent;
|
||||
import mc.core.network.proto_1_12_2.State;
|
||||
import mc.core.network.proto_1_12_2.TeleportManager;
|
||||
import mc.core.network.proto_1_12_2.netty.KeepAliveThread;
|
||||
import mc.core.network.proto_1_12_2.netty.wrappers.WrapperNetChannel;
|
||||
import mc.core.network.proto_1_12_2.packets.*;
|
||||
import mc.core.network.proto_1_12_2.packets.clientside.LoginStartPacket;
|
||||
import mc.core.network.proto_1_12_2.packets.serverside.*;
|
||||
import mc.core.player.Player;
|
||||
import mc.core.player.PlayerManager;
|
||||
import mc.core.player.PlayerMode;
|
||||
import mc.core.text.Text;
|
||||
import mc.core.text.TextColor;
|
||||
import mc.core.text.TextStyle;
|
||||
import mc.core.world.World;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import static mc.core.network.proto_1_12_2.netty.NettyServer.ATTR_PLAYER;
|
||||
import static mc.core.network.proto_1_12_2.netty.NettyServer.ATTR_STATE;
|
||||
|
||||
@Component
|
||||
public class LoginHandler extends AbstractStateHandler implements LoginStateHandler {
|
||||
@Autowired
|
||||
private PlayerManager playerManager;
|
||||
@Autowired
|
||||
private KeepAliveThread keepAliveThread;
|
||||
@Autowired
|
||||
private World world;
|
||||
|
||||
@Handler
|
||||
public void onLoginStart(Channel channel, LoginStartPacket packet) {
|
||||
Player player = playerManager.getPlayer(packet.getPlayerName());
|
||||
if (player != null) {
|
||||
channel.writeAndFlush(new DisconnectPacket(
|
||||
Text.builder("Player \"")
|
||||
.append(Text.of(packet.getPlayerName(), TextColor.YELLOW))
|
||||
.append(Text.of("\" is online", TextColor.WHITE))
|
||||
.build()))
|
||||
.addListener(ChannelFutureListener.CLOSE);
|
||||
} else {
|
||||
player = playerManager.getOfflinePlayer(packet.getPlayerName());
|
||||
|
||||
if (player == null) {
|
||||
player = playerManager.createPlayer(
|
||||
packet.getPlayerName(),
|
||||
world.getSpawn(),
|
||||
world
|
||||
);
|
||||
|
||||
if (player == null) {
|
||||
channel.writeAndFlush(new DisconnectPacket(
|
||||
Text.of("Internal server error: can't create new player"))
|
||||
).addListener(ChannelFutureListener.CLOSE);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
channel.writeAndFlush(new LoginSuccessPacket(
|
||||
player.getUuid(),
|
||||
packet.getPlayerName()));
|
||||
channel.attr(ATTR_PLAYER).set(player);
|
||||
channel.attr(ATTR_STATE).set(State.PLAY);
|
||||
|
||||
// Join Game
|
||||
JoinGamePacket pkt1 = new JoinGamePacket();
|
||||
pkt1.setEntityId(player.getId()); //TODO отделить системный ID от EntityID
|
||||
pkt1.setMode(PlayerMode.CREATIVE); //TODO перенести в Config
|
||||
pkt1.setDimension(0/*Overworld*/); //TODO перенести в World
|
||||
pkt1.setDifficulty(0/*Peaceful*/); //TODO перенести в Config
|
||||
pkt1.setLevelType(world.getType().getName());
|
||||
channel.write(pkt1);
|
||||
|
||||
// Spawn Position
|
||||
SpawnPositionPacket pkt2 = new SpawnPositionPacket();
|
||||
pkt2.setLocation(world.getSpawn());
|
||||
channel.write(pkt2);
|
||||
|
||||
// Player Abilities
|
||||
PlayerAbilitiesPacket pkt3 = new PlayerAbilitiesPacket(); //TODO перенести в Player
|
||||
pkt3.setCanFly(true);
|
||||
pkt3.setFlying(true);
|
||||
pkt3.setGodMode(true);
|
||||
pkt3.setInstantDestroyBlocks(true);
|
||||
channel.write(pkt3);
|
||||
|
||||
channel.flush();
|
||||
|
||||
// Player Position And Look
|
||||
PlayerPositionAndLookPacket pkt4 = new PlayerPositionAndLookPacket();
|
||||
pkt4.setLocation(player.getLocation());
|
||||
pkt4.setTeleportId(TeleportManager.getInstance().append(player, player.getLocation()));
|
||||
channel.writeAndFlush(pkt4);
|
||||
|
||||
player.setChannel(new WrapperNetChannel(channel));
|
||||
|
||||
// Send <Tab> items
|
||||
//TODO обновление должно приходить всем игрокам на сервере
|
||||
PlayerListItemPacket pkt5 = new PlayerListItemPacket();
|
||||
pkt5.setAction(PlayerListItemPacket.Action.ADD_PLAYER);
|
||||
PlayerListItemPacket.PlayerData playerData = new PlayerListItemPacket.PlayerData();
|
||||
playerData.setUuid(player.getUuid());
|
||||
playerData.setName(player.getName());
|
||||
playerData.setGameMode(PlayerMode.CREATIVE);
|
||||
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()
|
||||
);
|
||||
pkt5.getListPlayers().add(playerData);
|
||||
channel.writeAndFlush(pkt5);
|
||||
|
||||
playerManager.joinServer(player);
|
||||
keepAliveThread.notifyLock();
|
||||
|
||||
CS_PlayerMoveEvent event = new CS_PlayerMoveEvent(player, player.getLocation());
|
||||
event.setNewLocation(player.getLocation());
|
||||
event.setRecalcChunk(true);
|
||||
EventBus.getInstance().post(event);
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user