Merge branch 'proto_1.12.2' into release/zero
This commit is contained in:
11
.gitignore
vendored
11
.gitignore
vendored
@@ -5,3 +5,14 @@ out/
|
|||||||
*.ipr
|
*.ipr
|
||||||
*.iws
|
*.iws
|
||||||
*.ids
|
*.ids
|
||||||
|
|
||||||
|
## GRADLE ##
|
||||||
|
.gradle/
|
||||||
|
build/
|
||||||
|
gradle/
|
||||||
|
gradlew
|
||||||
|
gradlew.*
|
||||||
|
|
||||||
|
## PROJECT ##
|
||||||
|
libs/
|
||||||
|
*.log
|
||||||
|
|||||||
9
anvil-loader/build.gradle
Normal file
9
anvil-loader/build.gradle
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
group 'mc'
|
||||||
|
version '1.0-SNAPSHOT'
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
/* Core */
|
||||||
|
compile_excludeCopy project(':core')
|
||||||
|
|
||||||
|
compile (group: 'net.sf.trove4j', name: 'trove4j', version: '3.0.3')
|
||||||
|
}
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
package com.flowpowered.nbt;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
public class LongArrayTag extends Tag<long[]> {
|
||||||
|
/**
|
||||||
|
* The value.
|
||||||
|
*/
|
||||||
|
private final long[] value;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates the tag.
|
||||||
|
*
|
||||||
|
* @param name The name.
|
||||||
|
* @param value The value.
|
||||||
|
*/
|
||||||
|
public LongArrayTag(String name, long[] value) {
|
||||||
|
super(TagType.TAG_LONG_ARRAY, name);
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long[] getValue() {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
StringBuilder hex = new StringBuilder();
|
||||||
|
for (long s : value) {
|
||||||
|
String hexDigits = Long.toHexString(s).toUpperCase();
|
||||||
|
if (hexDigits.length() == 1) {
|
||||||
|
hex.append("0");
|
||||||
|
}
|
||||||
|
hex.append(hexDigits).append(" ");
|
||||||
|
}
|
||||||
|
|
||||||
|
String name = getName();
|
||||||
|
String append = "";
|
||||||
|
if (name != null && !name.equals("")) {
|
||||||
|
append = "(\"" + this.getName() + "\")";
|
||||||
|
}
|
||||||
|
return "TAG_Long_Array" + append + ": " + hex.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public LongArrayTag clone() {
|
||||||
|
long[] clonedArray = cloneArray(value);
|
||||||
|
|
||||||
|
return new LongArrayTag(getName(), clonedArray);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object other) {
|
||||||
|
if (!(other instanceof IntArrayTag)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
LongArrayTag tag = (LongArrayTag) other;
|
||||||
|
return Arrays.equals(value, tag.value) && getName().equals(tag.getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
private long[] cloneArray(long[] longArray) {
|
||||||
|
if (longArray == null) {
|
||||||
|
return null;
|
||||||
|
} else {
|
||||||
|
int length = longArray.length;
|
||||||
|
byte[] newArray = new byte[length];
|
||||||
|
System.arraycopy(longArray, 0, newArray, 0, length);
|
||||||
|
return longArray;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
package com.flowpowered.nbt;
|
||||||
|
|
||||||
|
import java.nio.charset.Charset;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A class which holds constant values.
|
||||||
|
*/
|
||||||
|
public final class NBTConstants {
|
||||||
|
/**
|
||||||
|
* The character set used by NBT (UTF-8).
|
||||||
|
*/
|
||||||
|
public static final Charset CHARSET = Charset.forName("UTF-8");
|
||||||
|
/**
|
||||||
|
* Tag type constants.
|
||||||
|
*/
|
||||||
|
@Deprecated
|
||||||
|
public static final int TYPE_END = TagType.TAG_END.getId(),
|
||||||
|
TYPE_BYTE = TagType.TAG_BYTE.getId(),
|
||||||
|
TYPE_SHORT = TagType.TAG_SHORT.getId(),
|
||||||
|
TYPE_INT = TagType.TAG_INT.getId(),
|
||||||
|
TYPE_LONG = TagType.TAG_LONG.getId(),
|
||||||
|
TYPE_FLOAT = TagType.TAG_FLOAT.getId(),
|
||||||
|
TYPE_DOUBLE = TagType.TAG_DOUBLE.getId(),
|
||||||
|
TYPE_BYTE_ARRAY = TagType.TAG_BYTE_ARRAY.getId(),
|
||||||
|
TYPE_STRING = TagType.TAG_STRING.getId(),
|
||||||
|
TYPE_LIST = TagType.TAG_LIST.getId(),
|
||||||
|
TYPE_COMPOUND = TagType.TAG_COMPOUND.getId(),
|
||||||
|
TYPE_INT_ARRAY = TagType.TAG_INT_ARRAY.getId(),
|
||||||
|
TYPE_SHORT_ARRAY = TagType.TAG_SHORT_ARRAY.getId(),
|
||||||
|
TYPE_LONG_ARRAY = TagType.TAG_LONG_ARRAY.getId();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default private constructor.
|
||||||
|
*/
|
||||||
|
private NBTConstants() {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
94
anvil-loader/src/main/java/com/flowpowered/nbt/TagType.java
Normal file
94
anvil-loader/src/main/java/com/flowpowered/nbt/TagType.java
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
package com.flowpowered.nbt;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public enum TagType {
|
||||||
|
TAG_END(EndTag.class, "TAG_End", 0),
|
||||||
|
TAG_BYTE(ByteTag.class, "TAG_Byte", 1),
|
||||||
|
TAG_SHORT(ShortTag.class, "TAG_Short", 2),
|
||||||
|
TAG_INT(IntTag.class, "TAG_Int", 3),
|
||||||
|
TAG_LONG(LongTag.class, "TAG_Long", 4),
|
||||||
|
TAG_FLOAT(FloatTag.class, "TAG_Float", 5),
|
||||||
|
TAG_DOUBLE(DoubleTag.class, "TAG_Double", 6),
|
||||||
|
TAG_BYTE_ARRAY(ByteArrayTag.class, "TAG_Byte_Array", 7),
|
||||||
|
TAG_STRING(StringTag.class, "TAG_String", 8),
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
TAG_LIST((Class) ListTag.class, "TAG_List", 9),
|
||||||
|
// Java generics, y u so suck
|
||||||
|
TAG_COMPOUND(CompoundTag.class, "TAG_Compound", 10),
|
||||||
|
TAG_INT_ARRAY(IntArrayTag.class, "TAG_Int_Array", 11),
|
||||||
|
TAG_LONG_ARRAY(LongArrayTag.class, "TAG_Long_Array", 12),
|
||||||
|
TAG_SHORT_ARRAY(ShortArrayTag.class, "TAG_Short_Array", 100),;
|
||||||
|
private static final Map<Class<? extends Tag<?>>, TagType> BY_CLASS = new HashMap<Class<? extends Tag<?>>, TagType>();
|
||||||
|
private static final Map<String, TagType> BY_NAME = new HashMap<String, TagType>();
|
||||||
|
private static final TagType[] BY_ID;
|
||||||
|
|
||||||
|
static {
|
||||||
|
BY_ID = new TagType[BaseData.maxId + 1];
|
||||||
|
for (TagType type : TagType.values()) {
|
||||||
|
BY_CLASS.put(type.getTagClass(), type);
|
||||||
|
BY_NAME.put(type.getTypeName(), type);
|
||||||
|
BY_ID[type.getId()] = type;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private final Class<? extends Tag<?>> tagClass;
|
||||||
|
private final String typeName;
|
||||||
|
private final int id;
|
||||||
|
|
||||||
|
private TagType(Class<? extends Tag<?>> tagClass, String typeName, int id) {
|
||||||
|
this.tagClass = tagClass;
|
||||||
|
this.typeName = typeName;
|
||||||
|
this.id = id;
|
||||||
|
// Such a hack, shame that Java makes this such a pain
|
||||||
|
if (this.id > BaseData.maxId) {
|
||||||
|
BaseData.maxId = this.id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Class<? extends Tag<?>> getTagClass() {
|
||||||
|
return tagClass;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getTypeName() {
|
||||||
|
return typeName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static TagType getByTagClass(Class<? extends Tag<?>> clazz) {
|
||||||
|
TagType ret = BY_CLASS.get(clazz);
|
||||||
|
if (ret == null) {
|
||||||
|
throw new IllegalArgumentException("Tag type " + clazz + " is unknown!");
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static TagType getByTypeName(String typeName) {
|
||||||
|
TagType ret = BY_NAME.get(typeName);
|
||||||
|
if (ret == null) {
|
||||||
|
throw new IllegalArgumentException("Tag type " + typeName + " is unknown!");
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static TagType getById(int id) {
|
||||||
|
if (id >= 0 && id < BY_ID.length) {
|
||||||
|
TagType ret = BY_ID[id];
|
||||||
|
if (ret == null) {
|
||||||
|
throw new IllegalArgumentException("Tag type id " + id + " is unknown!");
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
} else {
|
||||||
|
throw new IndexOutOfBoundsException("Tag type id " + id + " is out of bounds!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class BaseData {
|
||||||
|
private static int maxId = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,210 @@
|
|||||||
|
package com.flowpowered.nbt.stream;
|
||||||
|
|
||||||
|
import java.io.Closeable;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.nio.ByteOrder;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.zip.GZIPInputStream;
|
||||||
|
|
||||||
|
import com.flowpowered.nbt.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class reads NBT, or Named Binary Tag streams, and produces an object graph of subclasses of the {@link Tag} object. <p /> The NBT format was created by Markus Persson, and the specification
|
||||||
|
* may be found at <a href="https://flowpowered.com/nbt/spec.txt"> https://flowpowered.com/nbt/spec.txt</a>.
|
||||||
|
*/
|
||||||
|
public final class NBTInputStream implements Closeable {
|
||||||
|
/**
|
||||||
|
* The data input stream.
|
||||||
|
*/
|
||||||
|
private final EndianSwitchableInputStream is;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new {@link NBTInputStream}, which will source its data from the specified input stream. This assumes the stream is compressed.
|
||||||
|
*
|
||||||
|
* @param is The input stream.
|
||||||
|
* @throws java.io.IOException if an I/O error occurs.
|
||||||
|
*/
|
||||||
|
public NBTInputStream(InputStream is) throws IOException {
|
||||||
|
this(is, true, ByteOrder.BIG_ENDIAN);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new {@link NBTInputStream}, which sources its data from the specified input stream. A flag must be passed which indicates if the stream is compressed with GZIP or not. This assumes the
|
||||||
|
* stream uses big endian encoding.
|
||||||
|
*
|
||||||
|
* @param is The input stream.
|
||||||
|
* @param compressed A flag indicating if the stream is compressed.
|
||||||
|
* @throws java.io.IOException if an I/O error occurs.
|
||||||
|
*/
|
||||||
|
public NBTInputStream(InputStream is, boolean compressed) throws IOException {
|
||||||
|
this(is, compressed, ByteOrder.BIG_ENDIAN);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new {@link NBTInputStream}, which sources its data from the specified input stream. A flag must be passed which indicates if the stream is compressed with GZIP or not.
|
||||||
|
*
|
||||||
|
* @param is The input stream.
|
||||||
|
* @param compressed A flag indicating if the stream is compressed.
|
||||||
|
* @param endianness Whether to read numbers from the InputStream with little endian encoding.
|
||||||
|
* @throws java.io.IOException if an I/O error occurs.
|
||||||
|
*/
|
||||||
|
public NBTInputStream(InputStream is, boolean compressed, ByteOrder endianness) throws IOException {
|
||||||
|
this.is = new EndianSwitchableInputStream(compressed ? new GZIPInputStream(is) : is, endianness);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads an NBT {@link Tag} from the stream.
|
||||||
|
*
|
||||||
|
* @return The tag that was read.
|
||||||
|
* @throws java.io.IOException if an I/O error occurs.
|
||||||
|
*/
|
||||||
|
public Tag readTag() throws IOException {
|
||||||
|
return readTag(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads an NBT {@link Tag} from the stream.
|
||||||
|
*
|
||||||
|
* @param depth The depth of this tag.
|
||||||
|
* @return The tag that was read.
|
||||||
|
* @throws java.io.IOException if an I/O error occurs.
|
||||||
|
*/
|
||||||
|
private Tag readTag(int depth) throws IOException {
|
||||||
|
int typeId = is.readByte() & 0xFF;
|
||||||
|
TagType type = TagType.getById(typeId);
|
||||||
|
|
||||||
|
String name;
|
||||||
|
if (type != TagType.TAG_END) {
|
||||||
|
int nameLength = is.readShort() & 0xFFFF;
|
||||||
|
byte[] nameBytes = new byte[nameLength];
|
||||||
|
is.readFully(nameBytes);
|
||||||
|
name = new String(nameBytes, NBTConstants.CHARSET.name());
|
||||||
|
} else {
|
||||||
|
name = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
return readTagPayload(type, name, depth);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads the payload of a {@link Tag}, given the name and type.
|
||||||
|
*
|
||||||
|
* @param type The type.
|
||||||
|
* @param name The name.
|
||||||
|
* @param depth The depth.
|
||||||
|
* @return The tag.
|
||||||
|
* @throws java.io.IOException if an I/O error occurs.
|
||||||
|
*/
|
||||||
|
@SuppressWarnings ({"unchecked", "rawtypes"})
|
||||||
|
private Tag readTagPayload(TagType type, String name, int depth) throws IOException {
|
||||||
|
switch (type) {
|
||||||
|
case TAG_END:
|
||||||
|
if (depth == 0) {
|
||||||
|
throw new IOException("TAG_End found without a TAG_Compound/TAG_List tag preceding it.");
|
||||||
|
} else {
|
||||||
|
return new EndTag();
|
||||||
|
}
|
||||||
|
|
||||||
|
case TAG_BYTE:
|
||||||
|
return new ByteTag(name, is.readByte());
|
||||||
|
|
||||||
|
case TAG_SHORT:
|
||||||
|
return new ShortTag(name, is.readShort());
|
||||||
|
|
||||||
|
case TAG_INT:
|
||||||
|
return new IntTag(name, is.readInt());
|
||||||
|
|
||||||
|
case TAG_LONG:
|
||||||
|
return new LongTag(name, is.readLong());
|
||||||
|
|
||||||
|
case TAG_FLOAT:
|
||||||
|
return new FloatTag(name, is.readFloat());
|
||||||
|
|
||||||
|
case TAG_DOUBLE:
|
||||||
|
return new DoubleTag(name, is.readDouble());
|
||||||
|
|
||||||
|
case TAG_BYTE_ARRAY:
|
||||||
|
int length = is.readInt();
|
||||||
|
byte[] bytes = new byte[length];
|
||||||
|
is.readFully(bytes);
|
||||||
|
return new ByteArrayTag(name, bytes);
|
||||||
|
|
||||||
|
case TAG_STRING:
|
||||||
|
length = is.readShort();
|
||||||
|
bytes = new byte[length];
|
||||||
|
is.readFully(bytes);
|
||||||
|
return new StringTag(name, new String(bytes, NBTConstants.CHARSET.name()));
|
||||||
|
|
||||||
|
case TAG_LIST:
|
||||||
|
TagType childType = TagType.getById(is.readByte());
|
||||||
|
length = is.readInt();
|
||||||
|
|
||||||
|
Class<? extends Tag> clazz = childType.getTagClass();
|
||||||
|
List<Tag> tagList = new ArrayList<Tag>(length);
|
||||||
|
for (int i = 0; i < length; i++) {
|
||||||
|
Tag tag = readTagPayload(childType, "", depth + 1);
|
||||||
|
if (tag instanceof EndTag) {
|
||||||
|
throw new IOException("TAG_End not permitted in a list.");
|
||||||
|
} else if (!clazz.isInstance(tag)) {
|
||||||
|
throw new IOException("Mixed tag types within a list.");
|
||||||
|
}
|
||||||
|
tagList.add(tag);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new ListTag(name, clazz, tagList);
|
||||||
|
|
||||||
|
case TAG_COMPOUND:
|
||||||
|
CompoundMap compoundTagList = new CompoundMap();
|
||||||
|
while (true) {
|
||||||
|
Tag tag = readTag(depth + 1);
|
||||||
|
if (tag instanceof EndTag) {
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
compoundTagList.put(tag);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new CompoundTag(name, compoundTagList);
|
||||||
|
|
||||||
|
case TAG_INT_ARRAY:
|
||||||
|
length = is.readInt();
|
||||||
|
int[] ints = new int[length];
|
||||||
|
for (int i = 0; i < length; i++) {
|
||||||
|
ints[i] = is.readInt();
|
||||||
|
}
|
||||||
|
return new IntArrayTag(name, ints);
|
||||||
|
|
||||||
|
case TAG_SHORT_ARRAY:
|
||||||
|
length = is.readInt();
|
||||||
|
short[] shorts = new short[length];
|
||||||
|
for (int i = 0; i < length; i++) {
|
||||||
|
shorts[i] = is.readShort();
|
||||||
|
}
|
||||||
|
return new ShortArrayTag(name, shorts);
|
||||||
|
|
||||||
|
case TAG_LONG_ARRAY:
|
||||||
|
length = is.readInt();
|
||||||
|
long[] longs = new long[length];
|
||||||
|
for (int i = 0; i < length; i++) {
|
||||||
|
longs[i] = is.readLong();
|
||||||
|
}
|
||||||
|
return new LongArrayTag(name, longs);
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new IOException("Invalid tag type: " + type + ".");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void close() throws IOException {
|
||||||
|
is.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return whether this NBTInputStream reads numbers in little-endian format.
|
||||||
|
*/
|
||||||
|
public ByteOrder getByteOrder() {
|
||||||
|
return is.getEndianness();
|
||||||
|
}
|
||||||
|
}
|
||||||
80
anvil-loader/src/main/java/mc/world/anvil/AnvilBlock.java
Normal file
80
anvil-loader/src/main/java/mc/world/anvil/AnvilBlock.java
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
package mc.world.anvil;
|
||||||
|
|
||||||
|
import com.flowpowered.nbt.CompoundTag;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import mc.core.world.block.Block;
|
||||||
|
import mc.core.world.block.BlockLocation;
|
||||||
|
import mc.core.world.block.BlockType;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
public class AnvilBlock implements Block {
|
||||||
|
private final AnvilChunkSection chunkSection;
|
||||||
|
private final BlockLocation location;
|
||||||
|
private BlockLocation globalLocation;
|
||||||
|
|
||||||
|
public AnvilBlock(AnvilChunkSection chunkSection, int x, int y, int z) {
|
||||||
|
this.chunkSection = chunkSection;
|
||||||
|
this.location = new BlockLocation(x, y, z);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getLight() {
|
||||||
|
return chunkSection.getBlockLight().get(location);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setLight(int light) {
|
||||||
|
// nope...
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BlockType getType() {
|
||||||
|
final byte id = chunkSection.getBlocks().get((location.getY() << 8) + (location.getZ() << 4) + location.getX());
|
||||||
|
final int meta = chunkSection.getBlocksMeta().get(location);
|
||||||
|
BlockType type = BlockType.getByIdMeta(id & 0xFF, meta);
|
||||||
|
if (type.equals(BlockType.BEDROCK) && id != 7) {
|
||||||
|
log.warn("ChunkSection: {},{},{} | Block: {}",
|
||||||
|
chunkSection.getParent().getX(),
|
||||||
|
chunkSection.getY(),
|
||||||
|
chunkSection.getParent().getZ(),
|
||||||
|
location.toString());
|
||||||
|
}
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BlockLocation getLocation() {
|
||||||
|
if (globalLocation == null) {
|
||||||
|
globalLocation = new BlockLocation(
|
||||||
|
(chunkSection.getParent().getX() << 4) + location.getX(),
|
||||||
|
(chunkSection.getY() << 4) + location.getY(),
|
||||||
|
(chunkSection.getParent().getZ() << 4) + location.getZ()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return globalLocation;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CompoundTag getNBTData() {
|
||||||
|
CompoundTag compoundTag = ((AnvilChunk)chunkSection.getParent()).getNbtByGlobalXYZ(
|
||||||
|
(chunkSection.getParent().getX() << 4) + location.getX(),
|
||||||
|
(chunkSection.getY() << 4) + location.getY(),
|
||||||
|
(chunkSection.getParent().getZ() << 4) + location.getZ()
|
||||||
|
);
|
||||||
|
|
||||||
|
if (compoundTag != null) {
|
||||||
|
compoundTag.getValue().remove("Items");
|
||||||
|
compoundTag.getValue().remove("Lock");
|
||||||
|
}
|
||||||
|
|
||||||
|
return compoundTag;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "AnvilBlock{" +
|
||||||
|
"location=" + getLocation() +
|
||||||
|
", type=" + getType() +
|
||||||
|
'}';
|
||||||
|
}
|
||||||
|
}
|
||||||
137
anvil-loader/src/main/java/mc/world/anvil/AnvilChunk.java
Normal file
137
anvil-loader/src/main/java/mc/world/anvil/AnvilChunk.java
Normal file
@@ -0,0 +1,137 @@
|
|||||||
|
package mc.world.anvil;
|
||||||
|
|
||||||
|
import com.flowpowered.nbt.ByteArrayTag;
|
||||||
|
import com.flowpowered.nbt.ByteTag;
|
||||||
|
import com.flowpowered.nbt.CompoundMap;
|
||||||
|
import com.flowpowered.nbt.CompoundTag;
|
||||||
|
import com.flowpowered.nbt.IntTag;
|
||||||
|
import com.flowpowered.nbt.ListTag;
|
||||||
|
import gnu.trove.list.TByteList;
|
||||||
|
import gnu.trove.list.array.TByteArrayList;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import mc.core.utils.NibbleArray;
|
||||||
|
import mc.core.world.Biome;
|
||||||
|
import mc.core.world.block.Block;
|
||||||
|
import mc.core.world.chunk.Chunk;
|
||||||
|
import mc.core.world.chunk.ChunkSection;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
@Getter
|
||||||
|
public class AnvilChunk implements Chunk {
|
||||||
|
private int x;
|
||||||
|
private int z;
|
||||||
|
private TByteList biomes = new TByteArrayList(256);
|
||||||
|
private List<ChunkSection> sections;
|
||||||
|
private ListTag<CompoundTag> tileEntities;
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
AnvilChunk(CompoundTag chunkTag) {
|
||||||
|
CompoundMap levelTagMap = ((CompoundTag) chunkTag.getValue().get("Level")).getValue();
|
||||||
|
|
||||||
|
this.x = ((IntTag) levelTagMap.get("xPos")).getValue();
|
||||||
|
this.z = ((IntTag) levelTagMap.get("zPos")).getValue();
|
||||||
|
|
||||||
|
biomes.add(((ByteArrayTag) levelTagMap.get("Biomes")).getValue());
|
||||||
|
tileEntities = (ListTag<CompoundTag>) levelTagMap.get("TileEntities");
|
||||||
|
|
||||||
|
List<CompoundTag> sections = ((ListTag<CompoundTag>) levelTagMap.get("Sections")).getValue();
|
||||||
|
this.sections = new ArrayList<>(sections.size());
|
||||||
|
|
||||||
|
for (CompoundTag sectionTag : sections) {
|
||||||
|
CompoundMap sectionTagValue = sectionTag.getValue();
|
||||||
|
|
||||||
|
AnvilChunkSection chunkSection = new AnvilChunkSection();
|
||||||
|
chunkSection.setParent(this);
|
||||||
|
chunkSection.setY(((ByteTag) sectionTagValue.get("Y")).getValue());
|
||||||
|
|
||||||
|
chunkSection.setBlockLight(new NibbleArray(((ByteArrayTag) sectionTagValue.get("BlockLight")).getValue()));
|
||||||
|
chunkSection.setSkyLight(new NibbleArray(((ByteArrayTag) sectionTagValue.get("SkyLight")).getValue()));
|
||||||
|
chunkSection.getBlocks().add(((ByteArrayTag) sectionTagValue.get("Blocks")).getValue());
|
||||||
|
chunkSection.setBlocksMeta(new NibbleArray(((ByteArrayTag) sectionTagValue.get("Data")).getValue()));
|
||||||
|
|
||||||
|
this.sections.add(chunkSection);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CompoundTag getNbtByGlobalXYZ(int x, int y, int z) {
|
||||||
|
for (CompoundTag compoundTag : tileEntities.getValue()) {
|
||||||
|
CompoundMap compoundMap = compoundTag.getValue();
|
||||||
|
if (((IntTag)compoundMap.get("x")).getValue() == x
|
||||||
|
&& ((IntTag)compoundMap.get("y")).getValue() == y
|
||||||
|
&& ((IntTag)compoundMap.get("z")).getValue() == z) {
|
||||||
|
return compoundTag;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ChunkSection getChunkSection(int height) {
|
||||||
|
if (height > sections.size()-1) return null;
|
||||||
|
return sections.get(height);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setChunkSection(int height, ChunkSection chunkSection) {
|
||||||
|
// nope...
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Block getBlock(int x, int y, int z) {
|
||||||
|
final int height = y >> 4;
|
||||||
|
return sections.get(height).getBlock(
|
||||||
|
x - getX() << 4,
|
||||||
|
y - height << 4,
|
||||||
|
z - getZ() << 4
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setBlock(Block block) {
|
||||||
|
// nope...
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getSkyLight(int x, int y, int z) {
|
||||||
|
final int height = y >> 4;
|
||||||
|
return sections.get(height).getSkyLight(
|
||||||
|
x - getX() << 4,
|
||||||
|
y - height << 4,
|
||||||
|
z - getZ() << 4
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setSkyLight(int x, int y, int z, int lightLevel) {
|
||||||
|
// nope...
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getAddition(int x, int y, int z) {
|
||||||
|
final int height = y >> 4;
|
||||||
|
return sections.get(height).getAddition(
|
||||||
|
x - getX() << 4,
|
||||||
|
y - height << 4,
|
||||||
|
z - getZ() << 4
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setAddition(int x, int y, int z, int value) {
|
||||||
|
// nope...
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Biome getBiome(int x, int z) {
|
||||||
|
return Biome.getById(biomes.get((z >> 4) << 4 | (x >> 4)) & 255);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setBiome(int x, int z, Biome biome) {
|
||||||
|
// nope...
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
package mc.world.anvil;
|
||||||
|
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import lombok.Setter;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import mc.core.world.chunk.Chunk;
|
||||||
|
import mc.core.world.chunk.ChunkProvider;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.nio.file.Paths;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
@Component
|
||||||
|
@NoArgsConstructor
|
||||||
|
@Setter
|
||||||
|
public class AnvilChunkProvider implements ChunkProvider {
|
||||||
|
private RegionManager regionManager;
|
||||||
|
|
||||||
|
public AnvilChunkProvider(String mapPath) {
|
||||||
|
Path pathMap = Paths.get(mapPath);
|
||||||
|
if (Files.exists(pathMap)) {
|
||||||
|
log.info("Use Anvil map from \"{}\"", pathMap);
|
||||||
|
this.setRegionManager(new RegionManager(pathMap.resolve("region")));
|
||||||
|
} else {
|
||||||
|
log.error("Anvil map: path \"{}\" not found!!!", pathMap);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Chunk getChunk(int x, int z) {
|
||||||
|
Region region = regionManager.getRegion(x >> 5, z >> 5);
|
||||||
|
if (region == null) {
|
||||||
|
return new EmptyChunk(x, z);
|
||||||
|
} else {
|
||||||
|
return region.getChunk(x, z);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void saveChunk(Chunk chunk) {
|
||||||
|
// nope
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void saveChunk(Chunk... chunks) {
|
||||||
|
// nope
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
package mc.world.anvil;
|
||||||
|
|
||||||
|
import gnu.trove.list.TByteList;
|
||||||
|
import gnu.trove.list.linked.TByteLinkedList;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.Setter;
|
||||||
|
import mc.core.utils.NibbleArray;
|
||||||
|
import mc.core.world.block.Block;
|
||||||
|
import mc.core.world.chunk.Chunk;
|
||||||
|
import mc.core.world.chunk.ChunkSection;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
public class AnvilChunkSection implements ChunkSection {
|
||||||
|
@Setter
|
||||||
|
private Chunk parent;
|
||||||
|
|
||||||
|
@Setter
|
||||||
|
private int y;
|
||||||
|
|
||||||
|
private TByteList blocks = new TByteLinkedList();
|
||||||
|
@Setter
|
||||||
|
private NibbleArray blocksMeta;
|
||||||
|
@Setter
|
||||||
|
private NibbleArray blockLight;
|
||||||
|
@Setter
|
||||||
|
private NibbleArray skyLight;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Block getBlock(int x, int y, int z) {
|
||||||
|
return new AnvilBlock(this, x, y, z);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setBlock(Block block) {
|
||||||
|
// nope...
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getSkyLight(int x, int y, int z) {
|
||||||
|
return skyLight.get(x, y, z);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setSkyLight(int x, int y, int z, int lightLevel) {
|
||||||
|
// nope...
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getAddition(int x, int y, int z) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setAddition(int x, int y, int z, int value) {
|
||||||
|
// nope...
|
||||||
|
}
|
||||||
|
}
|
||||||
153
anvil-loader/src/main/java/mc/world/anvil/EmptyChunk.java
Normal file
153
anvil-loader/src/main/java/mc/world/anvil/EmptyChunk.java
Normal file
@@ -0,0 +1,153 @@
|
|||||||
|
package mc.world.anvil;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import mc.core.world.Biome;
|
||||||
|
import mc.core.world.block.Block;
|
||||||
|
import mc.core.world.block.BlockLocation;
|
||||||
|
import mc.core.world.block.BlockType;
|
||||||
|
import mc.core.world.chunk.Chunk;
|
||||||
|
import mc.core.world.chunk.ChunkSection;
|
||||||
|
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
public class EmptyChunk implements Chunk {
|
||||||
|
private int x;
|
||||||
|
private int z;
|
||||||
|
private List<ChunkSection> $sections = new LinkedList<>();
|
||||||
|
|
||||||
|
public EmptyChunk(int x, int z) {
|
||||||
|
this.x = x;
|
||||||
|
this.z = z;
|
||||||
|
|
||||||
|
for (int i = 0; i < 16; i++) {
|
||||||
|
this.$sections.add(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ChunkSection getChunkSection(int height) {
|
||||||
|
ChunkSection section;
|
||||||
|
if ((section = $sections.get(height)) == null) {
|
||||||
|
section = new EmptySection(height);
|
||||||
|
$sections.set(height, section);
|
||||||
|
}
|
||||||
|
|
||||||
|
return section;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setChunkSection(int height, ChunkSection chunkSection) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Block getBlock(int x, int y, int z) {
|
||||||
|
return getChunkSection(y >> 4).getBlock(
|
||||||
|
x - getX() << 4,
|
||||||
|
y - (y >> 4) << 4,
|
||||||
|
z - getZ() << 4
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setBlock(Block block) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getSkyLight(int x, int y, int z) {
|
||||||
|
return getChunkSection(y >> 4).getSkyLight(x, y, z);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setSkyLight(int x, int y, int z, int lightLevel) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getAddition(int x, int y, int z) {
|
||||||
|
return getChunkSection(y >> 4).getAddition(x, y, z);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setAddition(int x, int y, int z, int value) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Biome getBiome(int x, int z) {
|
||||||
|
return Biome.PLAINS;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setBiome(int x, int z, Biome biome) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@NoArgsConstructor
|
||||||
|
@Getter
|
||||||
|
public class EmptySection implements ChunkSection {
|
||||||
|
private int y;
|
||||||
|
|
||||||
|
EmptySection(int y) {
|
||||||
|
this.y = y;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Chunk getParent() {
|
||||||
|
return EmptyChunk.this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setParent(Chunk chunk) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Block getBlock(int localX, int localY, int localZ) {
|
||||||
|
return new Block() {
|
||||||
|
@Override
|
||||||
|
public int getLight() {
|
||||||
|
return 15;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setLight(int light) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BlockType getType() {
|
||||||
|
return BlockType.AIR;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BlockLocation getLocation() {
|
||||||
|
return new BlockLocation(
|
||||||
|
(getParent().getX() << 4) + localX,
|
||||||
|
(getY() << 4) + localY,
|
||||||
|
(getParent().getZ() << 4) + localZ
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setBlock(Block block) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getSkyLight(int localX, int localY, int localZ) {
|
||||||
|
return 15;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setSkyLight(int localX, int localY, int localZ, int lightLevel) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getAddition(int localX, int localY, int localZ) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setAddition(int localX, int localY, int localZ, int value) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
26
anvil-loader/src/main/java/mc/world/anvil/LevelInfo.java
Normal file
26
anvil-loader/src/main/java/mc/world/anvil/LevelInfo.java
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
package mc.world.anvil;
|
||||||
|
|
||||||
|
import com.flowpowered.nbt.*;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.ToString;
|
||||||
|
import mc.core.world.block.BlockLocation;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@ToString
|
||||||
|
class LevelInfo {
|
||||||
|
private long seed;
|
||||||
|
private BlockLocation spawn;
|
||||||
|
private int version;
|
||||||
|
|
||||||
|
LevelInfo(CompoundTag levelDatTag) {
|
||||||
|
CompoundMap dataMapTag = ((CompoundTag) levelDatTag.getValue().get("Data")).getValue();
|
||||||
|
|
||||||
|
seed = ((LongTag) dataMapTag.get("RandomSeed")).getValue();
|
||||||
|
spawn = new BlockLocation(
|
||||||
|
((IntTag) dataMapTag.get("SpawnX")).getValue(),
|
||||||
|
((IntTag) dataMapTag.get("SpawnY")).getValue(),
|
||||||
|
((IntTag) dataMapTag.get("SpawnZ")).getValue()
|
||||||
|
);
|
||||||
|
version = ((IntTag) dataMapTag.get("version")).getValue();
|
||||||
|
}
|
||||||
|
}
|
||||||
108
anvil-loader/src/main/java/mc/world/anvil/Region.java
Normal file
108
anvil-loader/src/main/java/mc/world/anvil/Region.java
Normal file
@@ -0,0 +1,108 @@
|
|||||||
|
package mc.world.anvil;
|
||||||
|
|
||||||
|
import com.flowpowered.nbt.CompoundTag;
|
||||||
|
import com.flowpowered.nbt.Tag;
|
||||||
|
import com.flowpowered.nbt.stream.NBTInputStream;
|
||||||
|
import gnu.trove.list.TByteList;
|
||||||
|
import gnu.trove.list.array.TByteArrayList;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import mc.core.world.chunk.Chunk;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
import java.io.*;
|
||||||
|
import java.util.zip.InflaterInputStream;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
class Region implements Closeable {
|
||||||
|
private static final byte BYTE_TRUE = 1,
|
||||||
|
BYTE_FALSE = 0;
|
||||||
|
|
||||||
|
private RandomAccessFile file;
|
||||||
|
private TByteList sectorFree;
|
||||||
|
private final int[] offsets = new int[1024];
|
||||||
|
|
||||||
|
Region(File file) throws IOException {
|
||||||
|
this.file = new RandomAccessFile(file, "rw");
|
||||||
|
|
||||||
|
int sizeOfSectorFree = (int)this.file.length() / 4096;
|
||||||
|
sectorFree = new TByteArrayList(sizeOfSectorFree);
|
||||||
|
|
||||||
|
sectorFree.add(BYTE_FALSE);
|
||||||
|
sectorFree.add(BYTE_FALSE);
|
||||||
|
for (int i = 0; i < sizeOfSectorFree-2; i++) {
|
||||||
|
sectorFree.add(BYTE_TRUE);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < offsets.length; ++i) {
|
||||||
|
int read = this.file.readInt();
|
||||||
|
offsets[i] = read;
|
||||||
|
|
||||||
|
if (read != 0 && (read >> 8) + (read & 255) <= this.sectorFree.size()) {
|
||||||
|
for (int j = 0; j < (read & 255); ++j) {
|
||||||
|
this.sectorFree.set((read >> 8) + j, BYTE_FALSE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.file.skipBytes(1024);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
Chunk getChunk(int x, int z) {
|
||||||
|
int offset;
|
||||||
|
try {
|
||||||
|
offset = getOffset(x & 31, z & 31);
|
||||||
|
} catch (Exception e) {
|
||||||
|
return new EmptyChunk(x, z);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (offset == 0) {
|
||||||
|
return new EmptyChunk(x, z);
|
||||||
|
}
|
||||||
|
|
||||||
|
int v1 = offset >> 8;
|
||||||
|
int v2 = offset & 255;
|
||||||
|
|
||||||
|
if (v1 + v2 > sectorFree.size()) {
|
||||||
|
return new EmptyChunk(x, z);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
file.seek((long) (v1 * 4096));
|
||||||
|
int read = file.readInt();
|
||||||
|
if (read <= 0 || read > 4096 * v2) {
|
||||||
|
return new EmptyChunk(x, z);
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean gzippedData = (file.readByte() == 0x01);
|
||||||
|
|
||||||
|
if (gzippedData) {
|
||||||
|
log.warn("GZipped");
|
||||||
|
} else {
|
||||||
|
byte[] buffer = new byte[read - 1];
|
||||||
|
file.read(buffer);
|
||||||
|
InflaterInputStream inputStream = new InflaterInputStream(new ByteArrayInputStream(buffer));
|
||||||
|
|
||||||
|
NBTInputStream nbtInputStream = new NBTInputStream(inputStream, false);
|
||||||
|
|
||||||
|
Tag rootTag = nbtInputStream.readTag();
|
||||||
|
nbtInputStream.close();
|
||||||
|
return new AnvilChunk((CompoundTag) rootTag);
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
log.error("Get chunk", e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int getOffset(int x, int z) {
|
||||||
|
return offsets[x + z * 32];
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() throws IOException {
|
||||||
|
if (file != null) file.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
49
anvil-loader/src/main/java/mc/world/anvil/RegionManager.java
Normal file
49
anvil-loader/src/main/java/mc/world/anvil/RegionManager.java
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
package mc.world.anvil;
|
||||||
|
|
||||||
|
import gnu.trove.map.TIntObjectMap;
|
||||||
|
import gnu.trove.map.hash.TIntObjectHashMap;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import mc.core.utils.CompactedCoords;
|
||||||
|
import org.springframework.lang.Nullable;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.nio.file.Paths;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
public class RegionManager {
|
||||||
|
private final Path regionFilesPath;
|
||||||
|
private final TIntObjectMap<Region> regions = new TIntObjectHashMap<>();
|
||||||
|
|
||||||
|
public RegionManager(String regionFilesPath) {
|
||||||
|
this(Paths.get(regionFilesPath));
|
||||||
|
}
|
||||||
|
|
||||||
|
public RegionManager(Path regionFilesPath) {
|
||||||
|
this.regionFilesPath = regionFilesPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public Region getRegion(int x, int z) {
|
||||||
|
final int xz = CompactedCoords.compressXZ(x, z);
|
||||||
|
|
||||||
|
if (regions.containsKey(xz)) {
|
||||||
|
return regions.get(xz);
|
||||||
|
} else {
|
||||||
|
Path regionFilePath = regionFilesPath.resolve("r." + x + "." + z + ".mca");
|
||||||
|
if (Files.exists(regionFilePath)) {
|
||||||
|
try {
|
||||||
|
Region region = new Region(regionFilePath.toFile());
|
||||||
|
regions.put(xz, region);
|
||||||
|
return region;
|
||||||
|
} catch (IOException e) {
|
||||||
|
log.error("load region from file", e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
213
anvil-loader/src/test/java/mc/world/anvil/RegionTest.java
Normal file
213
anvil-loader/src/test/java/mc/world/anvil/RegionTest.java
Normal file
@@ -0,0 +1,213 @@
|
|||||||
|
package mc.world.anvil;
|
||||||
|
|
||||||
|
import com.flowpowered.nbt.CompoundMap;
|
||||||
|
import com.flowpowered.nbt.CompoundTag;
|
||||||
|
import com.flowpowered.nbt.IntTag;
|
||||||
|
import com.flowpowered.nbt.StringTag;
|
||||||
|
import lombok.SneakyThrows;
|
||||||
|
import mc.core.world.block.Block;
|
||||||
|
import mc.core.world.block.BlockType;
|
||||||
|
import mc.core.world.chunk.Chunk;
|
||||||
|
import mc.core.world.chunk.ChunkSection;
|
||||||
|
import org.junit.jupiter.api.BeforeAll;
|
||||||
|
import org.junit.jupiter.api.DisplayName;
|
||||||
|
import org.junit.jupiter.params.ParameterizedTest;
|
||||||
|
import org.junit.jupiter.params.provider.Arguments;
|
||||||
|
import org.junit.jupiter.params.provider.MethodSource;
|
||||||
|
|
||||||
|
import java.nio.file.Paths;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||||
|
|
||||||
|
class RegionTest {
|
||||||
|
private static RegionManager regionManager;
|
||||||
|
|
||||||
|
@BeforeAll
|
||||||
|
@SneakyThrows
|
||||||
|
static void before() {
|
||||||
|
regionManager = new RegionManager(Paths.get(RegionTest.class.getResource("/region/").toURI()));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void assertZeroPlast(int x, int z, Block block, String msg) {
|
||||||
|
// @formatter:off
|
||||||
|
if (x == 0 && z == 0) assertEquals(BlockType.STONE, block.getType(), msg);
|
||||||
|
else if (x == 15 && z == 0) assertEquals(BlockType.GRANITE, block.getType(), msg);
|
||||||
|
else if (x == 0 && z == 15) assertEquals(BlockType.POLISHED_GRANITE, block.getType(), msg);
|
||||||
|
else if (x == 15 && z == 15) assertEquals(BlockType.DIORITE, block.getType(), msg);
|
||||||
|
else assertEquals(BlockType.BEDROCK, block.getType(), msg);
|
||||||
|
// @formatter:on
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ChunkChecker chunkChecker00() {
|
||||||
|
return new ChunkChecker() {
|
||||||
|
private void checkSection0(ChunkSection chunkSection) {
|
||||||
|
for (int y = 0; y < 16; y++) {
|
||||||
|
for (int x = 0; x < 16; x++) {
|
||||||
|
for (int z = 0; z < 16; z++) {
|
||||||
|
Block block = chunkSection.getBlock(x, y, z);
|
||||||
|
String msg = String.format("coords: %d %d %d", x, y, z);
|
||||||
|
|
||||||
|
if (y == 0) {
|
||||||
|
assertZeroPlast(x, z, block, msg);
|
||||||
|
} else {
|
||||||
|
assertEquals(BlockType.STONE, block.getType(), msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private CompoundTag createExceptedNBT(Block block) {
|
||||||
|
CompoundMap compoundMap = new CompoundMap();
|
||||||
|
compoundMap.put(new IntTag("x", block.getLocation().getX()));
|
||||||
|
compoundMap.put(new IntTag("y", block.getLocation().getY()));
|
||||||
|
compoundMap.put(new IntTag("z", block.getLocation().getZ()));
|
||||||
|
compoundMap.put(new StringTag("id", block.getType().getNamedId()));
|
||||||
|
|
||||||
|
return new CompoundTag("", compoundMap);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void checkSection1(ChunkSection chunkSection) {
|
||||||
|
for (int y = 0; y < 16; y++) {
|
||||||
|
for (int x = 0; x < 16; x++) {
|
||||||
|
for (int z = 0; z < 16; z++) {
|
||||||
|
Block block = chunkSection.getBlock(x, y, z);
|
||||||
|
String msg = String.format("coords: %d %d %d", x, y, z);
|
||||||
|
|
||||||
|
// @formatter:off
|
||||||
|
if (y == 0) assertEquals(BlockType.DIRT, block.getType(), msg);
|
||||||
|
else if (y == 1) assertEquals(BlockType.GRASS, block.getType(), msg);
|
||||||
|
else if (y == 2) {
|
||||||
|
if ((x == 2 || x == 4 || x == 5) && z == 1) {
|
||||||
|
assertEquals(BlockType.CHEST_NORTH, block.getType(), msg);
|
||||||
|
assertEquals(createExceptedNBT(block), block.getNBTData());
|
||||||
|
} else if ((x == 2 || x == 3 || x == 5) && z == 6) {
|
||||||
|
assertEquals(BlockType.CHEST_SOUTH, block.getType(), msg);
|
||||||
|
assertEquals(createExceptedNBT(block), block.getNBTData());
|
||||||
|
} else if (x == 1 && (z == 2 || z == 3 || z == 5)) {
|
||||||
|
assertEquals(BlockType.CHEST_WEST, block.getType(), msg);
|
||||||
|
assertEquals(createExceptedNBT(block), block.getNBTData());
|
||||||
|
} else if (x == 6 && (z == 2 || z == 4 || z == 5)) {
|
||||||
|
assertEquals(BlockType.CHEST_EAST, block.getType(), msg);
|
||||||
|
assertEquals(createExceptedNBT(block), block.getNBTData());
|
||||||
|
} else {
|
||||||
|
assertEquals(BlockType.AIR, block.getType(), msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else assertEquals(BlockType.AIR, block.getType(), msg);
|
||||||
|
// @formatter:on
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void check(Chunk chunk) {
|
||||||
|
ChunkSection chunkSection = chunk.getChunkSection(0);
|
||||||
|
assertNotNull(chunkSection);
|
||||||
|
checkSection0(chunkSection);
|
||||||
|
|
||||||
|
chunkSection = chunk.getChunkSection(1);
|
||||||
|
assertNotNull(chunkSection);
|
||||||
|
checkSection1(chunkSection);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ChunkChecker chunkChecker01() {
|
||||||
|
return new ChunkChecker() {
|
||||||
|
@Override
|
||||||
|
public void check(Chunk chunk) {
|
||||||
|
ChunkSection section = chunk.getChunkSection(0);
|
||||||
|
assertNotNull(section);
|
||||||
|
|
||||||
|
final List<BlockType> exceptedTypes = Arrays.asList(
|
||||||
|
BlockType.CLAY,
|
||||||
|
BlockType.ORE_REDSTONE,
|
||||||
|
BlockType.ORE_DIAMOND,
|
||||||
|
BlockType.OBSIDIAN,
|
||||||
|
BlockType.STONE_MOSS,
|
||||||
|
BlockType.SANDSTONE,
|
||||||
|
BlockType.ORE_LAPIS,
|
||||||
|
BlockType.WOOD_JUNGLE,
|
||||||
|
BlockType.WOOD_BIRCH,
|
||||||
|
BlockType.WOOD_SPRUCE,
|
||||||
|
BlockType.WOOD_OAK,
|
||||||
|
BlockType.ORE_COAL,
|
||||||
|
BlockType.ORE_IRON,
|
||||||
|
BlockType.ORE_GOLD,
|
||||||
|
BlockType.GRAVEL,
|
||||||
|
BlockType.SAND
|
||||||
|
);
|
||||||
|
|
||||||
|
for (int y = 0; y < 2; y++) {
|
||||||
|
for (int x = 0; x < 16; x++) {
|
||||||
|
for (int z = 0; z < 16; z++) {
|
||||||
|
Block block = section.getBlock(x, y, z);
|
||||||
|
String msg = String.format("coords: %d %d %d", x, y, z);
|
||||||
|
|
||||||
|
if (y == 0) {
|
||||||
|
assertZeroPlast(x, z, block, msg);
|
||||||
|
} else {
|
||||||
|
assertEquals(exceptedTypes.get(x), block.getType(), msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ChunkChecker chunkChecker0N1() {
|
||||||
|
return new ChunkChecker() {
|
||||||
|
@Override
|
||||||
|
public void check(Chunk chunk) {
|
||||||
|
ChunkSection section = chunk.getChunkSection(0);
|
||||||
|
assertNotNull(section);
|
||||||
|
|
||||||
|
for (int y = 0; y < 1; y++) {
|
||||||
|
for (int x = 0; x < 16; x++) {
|
||||||
|
for (int z = 0; z < 16; z++) {
|
||||||
|
assertZeroPlast(x, z,
|
||||||
|
section.getBlock(x, y, z),
|
||||||
|
String.format("coords: %d %d %d", x, y, z));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Stream<Arguments> streamArguments() {
|
||||||
|
|
||||||
|
|
||||||
|
return Stream.of(
|
||||||
|
Arguments.of(0, 0, chunkChecker00()),
|
||||||
|
Arguments.of(0, 1, chunkChecker01()),
|
||||||
|
Arguments.of(0, -1, chunkChecker0N1())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@DisplayName("testGetChunk")
|
||||||
|
@ParameterizedTest(name = "[{index}] chunk {0},{1}")
|
||||||
|
@MethodSource("streamArguments")
|
||||||
|
void testGetChunk(int chunkX, int chunkZ, ChunkChecker chunkChecker) {
|
||||||
|
final Region region = regionManager.getRegion(chunkX >> 5, chunkZ >> 5);
|
||||||
|
assertNotNull(region);
|
||||||
|
|
||||||
|
final Chunk chunk = region.getChunk(chunkX, chunkZ);
|
||||||
|
assertNotNull(chunk);
|
||||||
|
assertFalse(chunk instanceof EmptyChunk);
|
||||||
|
|
||||||
|
chunkChecker.check(chunk);
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ChunkChecker {
|
||||||
|
void check(Chunk chunk);
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
anvil-loader/src/test/resources/region/r.0.-1.mca
Normal file
BIN
anvil-loader/src/test/resources/region/r.0.-1.mca
Normal file
Binary file not shown.
BIN
anvil-loader/src/test/resources/region/r.0.0.mca
Normal file
BIN
anvil-loader/src/test/resources/region/r.0.0.mca
Normal file
Binary file not shown.
162
build.gradle
Normal file
162
build.gradle
Normal file
@@ -0,0 +1,162 @@
|
|||||||
|
import java.nio.file.Files
|
||||||
|
import java.nio.file.Paths
|
||||||
|
|
||||||
|
buildscript {
|
||||||
|
repositories {
|
||||||
|
maven { url "https://plugins.gradle.org/m2/" }
|
||||||
|
}
|
||||||
|
dependencies {
|
||||||
|
classpath (group: 'org.sonarsource.scanner.gradle', name: 'sonarqube-gradle-plugin', version: '2.6.2')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Проверка кода в SonarQube.
|
||||||
|
* Для запуска локальной проверки кода, используются следующий command line:
|
||||||
|
* gradle sonarqube \
|
||||||
|
* -Dsonar.host.url=http://127.0.0.1:9000
|
||||||
|
* -Dsonar.login=<TOKEN>
|
||||||
|
* где
|
||||||
|
* - <TOKEN> - сгенерированный токен учетки "сонара"
|
||||||
|
*/
|
||||||
|
plugins {
|
||||||
|
id "org.sonarqube" version "2.6.2"
|
||||||
|
}
|
||||||
|
|
||||||
|
allprojects {
|
||||||
|
apply plugin: 'java'
|
||||||
|
|
||||||
|
compileJava {
|
||||||
|
sourceCompatibility = 1.8
|
||||||
|
targetCompatibility = 1.8
|
||||||
|
options.encoding = 'UTF-8'
|
||||||
|
}
|
||||||
|
|
||||||
|
repositories {
|
||||||
|
mavenCentral()
|
||||||
|
maven { url 'https://oss.sonatype.org/content/groups/public/' }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
subprojects {
|
||||||
|
group 'mc'
|
||||||
|
|
||||||
|
ext {
|
||||||
|
slf4j_version = '1.7.25'
|
||||||
|
spring_version = '5.1.0.RELEASE'
|
||||||
|
lombok_version = '1.18.4'
|
||||||
|
junit_version = '5.3.1'
|
||||||
|
}
|
||||||
|
|
||||||
|
configurations {
|
||||||
|
compile_excludeCopy
|
||||||
|
compile.extendsFrom compile_excludeCopy
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
compile (group: 'org.jetbrains', name: 'annotations', version: '16.0.3')
|
||||||
|
|
||||||
|
/* Logger */
|
||||||
|
compile (group: 'org.slf4j', name: 'slf4j-api', version: slf4j_version)
|
||||||
|
compile (group: 'org.slf4j', name: 'jcl-over-slf4j', version: slf4j_version)
|
||||||
|
|
||||||
|
/* Spring */
|
||||||
|
compile (group: 'org.springframework', name: 'spring-context', version: spring_version)
|
||||||
|
|
||||||
|
/* Lombok */
|
||||||
|
annotationProcessor (group: 'org.projectlombok', name: 'lombok', version: lombok_version)
|
||||||
|
compile (group: 'org.projectlombok', name: 'lombok', version: lombok_version)
|
||||||
|
testAnnotationProcessor (group: 'org.projectlombok', name: 'lombok', version: lombok_version)
|
||||||
|
testCompile (group: 'org.projectlombok', name: 'lombok', version: lombok_version)
|
||||||
|
|
||||||
|
/* Testing */
|
||||||
|
testImplementation (group: 'org.junit.jupiter', name: 'junit-jupiter-api', version: junit_version)
|
||||||
|
testRuntimeOnly(group: 'org.junit.jupiter', name: 'junit-jupiter-engine', version: junit_version)
|
||||||
|
testImplementation(group: 'org.junit.jupiter', name: 'junit-jupiter-params', version: junit_version)
|
||||||
|
testCompile (group: 'org.slf4j', name: 'slf4j-simple', version: slf4j_version)
|
||||||
|
testCompile (group: 'org.mockito', name: 'mockito-core', version: '1.10.19')
|
||||||
|
testCompile (group: 'org.springframework', name: 'spring-test', version: spring_version)
|
||||||
|
}
|
||||||
|
|
||||||
|
test {
|
||||||
|
useJUnitPlatform()
|
||||||
|
}
|
||||||
|
|
||||||
|
task copyDep(type: Copy) {
|
||||||
|
into 'libs'
|
||||||
|
from configurations.compile + configurations.runtime - configurations.compile_excludeCopy
|
||||||
|
}
|
||||||
|
|
||||||
|
task cleanDep(type: Delete) {
|
||||||
|
delete 'libs'
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Сборка
|
||||||
|
*/
|
||||||
|
task deploy() {
|
||||||
|
dependsOn { jar }
|
||||||
|
|
||||||
|
doLast {
|
||||||
|
def deployDir = System.getProperty("deploy")
|
||||||
|
if (deployDir == null) {
|
||||||
|
println "Need param -Ddeploy=path/to/deploy"
|
||||||
|
throw new Exception("Need param -Ddir=path/to/deploy")
|
||||||
|
}
|
||||||
|
|
||||||
|
def target = Paths.get(deployDir, jar.archivePath.getName())
|
||||||
|
if (Files.notExists(target)) {
|
||||||
|
println jar.archivePath
|
||||||
|
Files.copy(jar.archivePath.toPath(), target)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def libsDir = System.getProperty("libs", deployDir+File.separator+"libs")
|
||||||
|
if (Files.notExists(Paths.get(libsDir))) {
|
||||||
|
(new File(libsDir)).mkdirs()
|
||||||
|
}
|
||||||
|
|
||||||
|
def libsCollection = configurations.compile + configurations.runtime - configurations.compile_excludeCopy
|
||||||
|
libsCollection.each{ libFile ->
|
||||||
|
target = Paths.get(libsDir, libFile.getName())
|
||||||
|
if (Files.notExists(target)) {
|
||||||
|
println libFile
|
||||||
|
Files.copy(libFile.toPath(), target)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Запуск сервера.
|
||||||
|
* Для указания рабочей папки, указываем JVM параметр
|
||||||
|
* -DworkDir=path\to\workdir
|
||||||
|
* Если используется отдельная папка для имплементации логгера, то указываем
|
||||||
|
* -DlogImplDir=path\to\logimpldir
|
||||||
|
* Если необходимо передать дополнительные JVM параметры серверу, то указываем их с двойной "D", например:
|
||||||
|
* -DDspringConfig=spring.xml
|
||||||
|
* -DDlog4j.configurationFile=log4j2.xml
|
||||||
|
*/
|
||||||
|
task runServer(type: JavaExec) {
|
||||||
|
main = 'mc.core.Main'
|
||||||
|
|
||||||
|
workingDir = System.getProperty("workDir", ".")
|
||||||
|
|
||||||
|
subprojects.findAll().each{ prj ->
|
||||||
|
classpath += prj.sourceSets.main.runtimeClasspath
|
||||||
|
}
|
||||||
|
|
||||||
|
if (System.getProperty("logImplDir") != null) {
|
||||||
|
classpath += files(fileTree(dir: new File(System.getProperty("logImplDir"))))
|
||||||
|
} else {
|
||||||
|
classpath += files(fileTree(dir: new File(workingDir, "log-impl")))
|
||||||
|
}
|
||||||
|
|
||||||
|
System.getProperties().stringPropertyNames().stream()
|
||||||
|
.filter{propName -> propName.startsWith("D")}
|
||||||
|
.forEach{propName -> jvmArgs += "-D" + propName.substring(1) + "=" + System.getProperty(propName)}
|
||||||
|
|
||||||
|
ignoreExitValue = true
|
||||||
|
}
|
||||||
69
core/README.MD
Normal file
69
core/README.MD
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
# Core
|
||||||
|
|
||||||
|
Ядро сервера
|
||||||
|
|
||||||
|
## Spring beans
|
||||||
|
|
||||||
|
### ConfigFromSpring
|
||||||
|
|
||||||
|
Implements: `mc.core.Config`
|
||||||
|
|
||||||
|
Bean:
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<bean id="config" class="mc.core.embedded.ConfigFromSpring">
|
||||||
|
<property name="descriptionServer" value="MC Core"/>
|
||||||
|
<property name="maxPlayers" value="100"/>
|
||||||
|
<property name="faviconBase64" value="icon.png"/>
|
||||||
|
</bean>
|
||||||
|
```
|
||||||
|
|
||||||
|
### IdleTime
|
||||||
|
|
||||||
|
Implements: `mc.core.time.TimeProcessor`
|
||||||
|
|
||||||
|
Bean:
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<bean id="idleTime" class="mc.core.time.IdleTime">
|
||||||
|
<constructor-arg index="0" type="long" value="1000"/>
|
||||||
|
</bean>
|
||||||
|
```
|
||||||
|
|
||||||
|
в качестве параметра конструктора указывается стартовое время.
|
||||||
|
|
||||||
|
### TimePerTick
|
||||||
|
|
||||||
|
Implements: `mc.core.time.TimeProcessor`
|
||||||
|
|
||||||
|
Bean:
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<bean id="timePerTick" class="mc.core.time.TimePerTick">
|
||||||
|
<property name="startGameTime" value="1000"/>
|
||||||
|
</bean>
|
||||||
|
```
|
||||||
|
|
||||||
|
в качестве параметра указывается стартовое время.
|
||||||
|
|
||||||
|
### RealTime
|
||||||
|
|
||||||
|
Implements: `mc.core.time.TimeProcessor`
|
||||||
|
|
||||||
|
Bean:
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<bean id="realTime" class="mc.core.time.RealTime"/>
|
||||||
|
```
|
||||||
|
|
||||||
|
### GameLoop
|
||||||
|
|
||||||
|
Bean:
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<bean id="gameLoop" class="mc.core.GameLoop">
|
||||||
|
<property name="gameTimer" ref="timeProcessor"/>
|
||||||
|
</bean>
|
||||||
|
```
|
||||||
|
|
||||||
|
`gameTimer` - бин, управляющий ходом времени
|
||||||
14
core/build.gradle
Normal file
14
core/build.gradle
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
version '1.0-SNAPSHOT'
|
||||||
|
|
||||||
|
apply plugin: 'maven'
|
||||||
|
apply plugin: 'application'
|
||||||
|
|
||||||
|
mainClassName = "mc.core.Main"
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
/* Components */
|
||||||
|
compile (group: 'commons-io', name: 'commons-io', version: '2.6')
|
||||||
|
compile (group: 'com.google.guava', name: 'guava', version: '26.0-jre')
|
||||||
|
/* Named Binary Tags */
|
||||||
|
compile (group: 'com.flowpowered', name: 'flow-nbt', version: '1.0.1-SNAPSHOT')
|
||||||
|
}
|
||||||
11
core/src/main/java/mc/core/Config.java
Normal file
11
core/src/main/java/mc/core/Config.java
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
/*
|
||||||
|
* DmitriyMX <dimon550@gmail.com>
|
||||||
|
* 2018-04-08
|
||||||
|
*/
|
||||||
|
package mc.core;
|
||||||
|
|
||||||
|
public interface Config {
|
||||||
|
int getMaxPlayers();
|
||||||
|
String getDescriptionServer();
|
||||||
|
byte[] getFaviconBase64();
|
||||||
|
}
|
||||||
85
core/src/main/java/mc/core/CoreEventListener.java
Normal file
85
core/src/main/java/mc/core/CoreEventListener.java
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
package mc.core;
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import mc.core.eventbus.EventBus;
|
||||||
|
import mc.core.eventbus.Subscriber;
|
||||||
|
import mc.core.eventbus.events.CS_PlayerMoveEvent;
|
||||||
|
import mc.core.eventbus.events.SC_ChunkLoadEvent;
|
||||||
|
import mc.core.eventbus.events.SC_ChunkUnloadEvent;
|
||||||
|
import mc.core.utils.CompactedCoords;
|
||||||
|
import mc.core.world.chunk.Chunk;
|
||||||
|
|
||||||
|
import javax.annotation.PostConstruct;
|
||||||
|
import java.util.Iterator;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
public class CoreEventListener {
|
||||||
|
@PostConstruct
|
||||||
|
public void registerEventHandlers() {
|
||||||
|
EventBus.getInstance().registerSubscribes(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Subscriber
|
||||||
|
public void handlerPlayerMoveEvent(CS_PlayerMoveEvent event) {
|
||||||
|
Chunk chunk;
|
||||||
|
chunk = event.getPlayer().getWorld().getChunk(event.getOldLocation().toBlockLocation()); // Old chunk
|
||||||
|
if (chunk == null) return;
|
||||||
|
int ccX = chunk.getX();
|
||||||
|
int ccZ = chunk.getZ();
|
||||||
|
chunk = event.getPlayer().getWorld().getChunk(event.getNewLocation().toBlockLocation()); // Next chunk
|
||||||
|
if (chunk == null) return;
|
||||||
|
int ncX = chunk.getX();
|
||||||
|
int ncZ = chunk.getZ();
|
||||||
|
|
||||||
|
if (event.isRecalcChunk() || (ncX != ccX || ncZ != ccZ)) {
|
||||||
|
final int viewDistance = event.getPlayer().getSettings().getViewDistance() + 1;
|
||||||
|
int cMinX = chunk.getX() - viewDistance;
|
||||||
|
int cMaxX = chunk.getX() + viewDistance;
|
||||||
|
int cMinZ = chunk.getZ() - viewDistance;
|
||||||
|
int cMaxZ = chunk.getZ() + viewDistance;
|
||||||
|
|
||||||
|
SC_ChunkLoadEvent eventChunkLoad = new SC_ChunkLoadEvent(event.getPlayer());
|
||||||
|
for (int cZ = cMinZ; cZ <= cMaxZ; cZ++) {
|
||||||
|
for (int cX = cMinX; cX <= cMaxX; cX++) {
|
||||||
|
int compressXZ = CompactedCoords.compressXZ(cX, cZ);
|
||||||
|
if (!event.getPlayer().getLoadedChunks().contains(compressXZ)) {
|
||||||
|
if (!event.getPlayer().getLoadedChunks().contains(compressXZ)) {
|
||||||
|
eventChunkLoad.getNeedLoadChunks().add(compressXZ);
|
||||||
|
event.getPlayer().getLoadedChunks().add(compressXZ);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!eventChunkLoad.getNeedLoadChunks().isEmpty()) {
|
||||||
|
EventBus.getInstance().post(eventChunkLoad);
|
||||||
|
}
|
||||||
|
|
||||||
|
SC_ChunkUnloadEvent eventChunkUnload = new SC_ChunkUnloadEvent(event.getPlayer());
|
||||||
|
Iterator<Integer> itr = event.getPlayer().getLoadedChunks().iterator();
|
||||||
|
while(itr.hasNext()) {
|
||||||
|
int compressXZ = itr.next();
|
||||||
|
int[] xz = CompactedCoords.uncompressXZ(compressXZ);
|
||||||
|
if (xz[0] > cMaxX || xz[0] < cMinX || xz[1] > cMaxZ || xz[1] < cMinZ) {
|
||||||
|
eventChunkUnload.getNeedUnloadChunks().add(compressXZ);
|
||||||
|
itr.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!eventChunkUnload.getNeedUnloadChunks().isEmpty()) {
|
||||||
|
EventBus.getInstance().post(eventChunkUnload);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
event.getPlayer().getLocation().setXYZ(
|
||||||
|
event.getNewLocation().getX(),
|
||||||
|
event.getNewLocation().getY(),
|
||||||
|
event.getNewLocation().getZ()
|
||||||
|
);
|
||||||
|
|
||||||
|
// TODO отсылать клиенту только(!) для корректировки позиции
|
||||||
|
// SC_PlayerMoveEvent nextEvent = new SC_PlayerMoveEvent(event.getPlayer());
|
||||||
|
// nextEvent.setNewLocation(event.getNewLocation());
|
||||||
|
// EventBusGetter.INSTANCE.post(nextEvent);
|
||||||
|
}
|
||||||
|
}
|
||||||
60
core/src/main/java/mc/core/EntityLocation.java
Normal file
60
core/src/main/java/mc/core/EntityLocation.java
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
package mc.core;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import mc.core.world.block.BlockLocation;
|
||||||
|
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
@Data
|
||||||
|
public class EntityLocation implements Cloneable {
|
||||||
|
private double x, y, z;
|
||||||
|
private float yaw, pitch;
|
||||||
|
|
||||||
|
public static EntityLocation ZERO() {
|
||||||
|
return new EntityLocation(0d,0d,0d,0f,0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void set(EntityLocation location) {
|
||||||
|
setXYZ(location.x, location.y, location.z);
|
||||||
|
setYawPitch(location.yaw, location.pitch);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setXYZ(double x, double y, double z) {
|
||||||
|
this.x = x;
|
||||||
|
this.y = y;
|
||||||
|
this.z = z;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setYawPitch(float yaw, float pitch) {
|
||||||
|
this.yaw = yaw;
|
||||||
|
this.pitch = pitch;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getBlockX() {
|
||||||
|
return (int) Math.floor(x);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getBlockY() {
|
||||||
|
return (int) Math.floor(y);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getBlockZ() {
|
||||||
|
return (int) Math.floor(z);
|
||||||
|
}
|
||||||
|
|
||||||
|
public BlockLocation toBlockLocation() {
|
||||||
|
return new BlockLocation(getBlockX(), getBlockY(), getBlockZ());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public EntityLocation clone() {
|
||||||
|
try {
|
||||||
|
return (EntityLocation) super.clone();
|
||||||
|
} catch (CloneNotSupportedException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
return ZERO();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
64
core/src/main/java/mc/core/GameLoop.java
Normal file
64
core/src/main/java/mc/core/GameLoop.java
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
/*
|
||||||
|
* DmitriyMX <dimon550@gmail.com>
|
||||||
|
* 2018-04-21
|
||||||
|
*/
|
||||||
|
package mc.core;
|
||||||
|
|
||||||
|
import lombok.Setter;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import mc.core.eventbus.EventBus;
|
||||||
|
import mc.core.player.PlayerManager;
|
||||||
|
import mc.core.time.TimeProcessor;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
public class GameLoop extends Thread {
|
||||||
|
private final TpsWatcher TPS_WATCHER = TpsWatcher.getInstance();
|
||||||
|
@Autowired
|
||||||
|
private PlayerManager playerManager;
|
||||||
|
|
||||||
|
/* Time */
|
||||||
|
@Setter
|
||||||
|
private TimeProcessor gameTimer;
|
||||||
|
|
||||||
|
public GameLoop() {
|
||||||
|
super();
|
||||||
|
setTps(20);
|
||||||
|
setPercentWarnLowTps(5);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPercentWarnLowTps(int value) {
|
||||||
|
TPS_WATCHER.setPercentWarnLowTps(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTps(int tps) {
|
||||||
|
TPS_WATCHER.setTps(tps);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTps(boolean value) {
|
||||||
|
TPS_WATCHER.setTraceTPS(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
TPS_WATCHER.startWatch();
|
||||||
|
|
||||||
|
while (!isInterrupted()) {
|
||||||
|
TPS_WATCHER.check();
|
||||||
|
|
||||||
|
/* --- --- --- */
|
||||||
|
|
||||||
|
EventBus.getInstance().process();
|
||||||
|
|
||||||
|
/* TODO нужно перенести этот функционал на Network */
|
||||||
|
playerManager.getBroadcastChannel().sendTimeUpdate(
|
||||||
|
gameTimer.getGameTime(),
|
||||||
|
gameTimer.getWorldAge()
|
||||||
|
);
|
||||||
|
|
||||||
|
/* --- --- --- */
|
||||||
|
|
||||||
|
TPS_WATCHER.tick();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
57
core/src/main/java/mc/core/ImmutableEntityLocation.java
Normal file
57
core/src/main/java/mc/core/ImmutableEntityLocation.java
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
package mc.core;
|
||||||
|
|
||||||
|
public class ImmutableEntityLocation extends EntityLocation {
|
||||||
|
public ImmutableEntityLocation(double x, double y, double z, float yaw, float pitch) {
|
||||||
|
super(x, y, z, yaw, pitch);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ImmutableEntityLocation(EntityLocation location) {
|
||||||
|
this(
|
||||||
|
location.getX(),
|
||||||
|
location.getY(),
|
||||||
|
location.getZ(),
|
||||||
|
location.getYaw(),
|
||||||
|
location.getPitch()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setX(double x) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setY(double y) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setZ(double z) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setYaw(float yaw) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setPitch(float pitch) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void set(EntityLocation location) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setXYZ(double x, double y, double z) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setYawPitch(float yaw, float pitch) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
}
|
||||||
53
core/src/main/java/mc/core/Main.java
Normal file
53
core/src/main/java/mc/core/Main.java
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
/*
|
||||||
|
* DmitriyMX <dimon550@gmail.com>
|
||||||
|
* 2018-03-25
|
||||||
|
*/
|
||||||
|
package mc.core;
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import mc.core.network.Server;
|
||||||
|
import mc.core.network.StartServerException;
|
||||||
|
import org.apache.commons.io.IOUtils;
|
||||||
|
import org.springframework.context.ApplicationContext;
|
||||||
|
import org.springframework.context.support.FileSystemXmlApplicationContext;
|
||||||
|
|
||||||
|
import java.io.FileOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Paths;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
public class Main {
|
||||||
|
private static ApplicationContext createContext() {
|
||||||
|
final String springXml = System.getProperty("springConfig", "./spring.xml");
|
||||||
|
|
||||||
|
if (!Files.exists(Paths.get(springXml))) {
|
||||||
|
log.info("File \"{}\" not found. Get default config.", springXml);
|
||||||
|
try (FileOutputStream fos = new FileOutputStream(springXml)) {
|
||||||
|
IOUtils.copy(Main.class.getResourceAsStream("/spring.xml"), fos);
|
||||||
|
} catch (IOException e) {
|
||||||
|
log.error("Get default spring config", e);
|
||||||
|
System.exit(-1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new FileSystemXmlApplicationContext(springXml);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
ApplicationContext appContext = createContext();
|
||||||
|
|
||||||
|
GameLoop gameLoop = appContext.getBean(GameLoop.class);
|
||||||
|
gameLoop.start();
|
||||||
|
|
||||||
|
Server server = appContext.getBean("server", Server.class);
|
||||||
|
Runtime.getRuntime().addShutdownHook(new Thread(server::stop));
|
||||||
|
try {
|
||||||
|
server.start();
|
||||||
|
} catch (StartServerException e) {
|
||||||
|
log.error("Can't start server", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
gameLoop.interrupt();
|
||||||
|
}
|
||||||
|
}
|
||||||
82
core/src/main/java/mc/core/TpsWatcher.java
Normal file
82
core/src/main/java/mc/core/TpsWatcher.java
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
/*
|
||||||
|
* DmitriyMX <dimon550@gmail.com>
|
||||||
|
* 2018-06-12
|
||||||
|
*/
|
||||||
|
package mc.core;
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
class TpsWatcher {
|
||||||
|
private static final TpsWatcher instance = new TpsWatcher();
|
||||||
|
|
||||||
|
private boolean traceTps = false;
|
||||||
|
|
||||||
|
private int tps;
|
||||||
|
private long pause;
|
||||||
|
private int lowTps;
|
||||||
|
private int percentLowTps;
|
||||||
|
|
||||||
|
private int factTps;
|
||||||
|
private long lastTime;
|
||||||
|
private long futureTime;
|
||||||
|
|
||||||
|
public static TpsWatcher getInstance() {
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
private TpsWatcher(){ }
|
||||||
|
|
||||||
|
public void setTps(int value) {
|
||||||
|
if (value > 1000) {
|
||||||
|
log.warn("TPS can't be '{}'. Set 1000", value);
|
||||||
|
value = 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
tps = value;
|
||||||
|
pause = (1000 / value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPercentWarnLowTps(int value) {
|
||||||
|
if (value > 100) {
|
||||||
|
log.warn("Percent warn low TPS can't be '{}'. Set 100", value);
|
||||||
|
value = 100;
|
||||||
|
}
|
||||||
|
|
||||||
|
lowTps = tps - (int)(tps * (value / 100f));
|
||||||
|
percentLowTps = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTraceTPS(boolean value) {
|
||||||
|
traceTps = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void startWatch() {
|
||||||
|
log.info("Target TPS: {}; Low TPS: {}({}%)", tps, lowTps, percentLowTps);
|
||||||
|
factTps = 0;
|
||||||
|
lastTime = System.currentTimeMillis();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void check() {
|
||||||
|
if ((System.currentTimeMillis() - lastTime) > 1000) {
|
||||||
|
lastTime = System.currentTimeMillis();
|
||||||
|
if (factTps < lowTps) {
|
||||||
|
log.warn("Low TPS: {}/{}", factTps, tps);
|
||||||
|
} else if (traceTps) {
|
||||||
|
log.trace("TPS: {}/{}", factTps, tps);
|
||||||
|
}
|
||||||
|
factTps = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
futureTime = System.currentTimeMillis() + pause;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void tick() {
|
||||||
|
factTps++;
|
||||||
|
try {
|
||||||
|
long pause = futureTime - System.currentTimeMillis();
|
||||||
|
Thread.sleep((pause <= 0 ? 0 : pause));
|
||||||
|
} catch (InterruptedException ignored) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
15
core/src/main/java/mc/core/chat/ChatProcessor.java
Normal file
15
core/src/main/java/mc/core/chat/ChatProcessor.java
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
/*
|
||||||
|
* DmitriyMX <dimon550@gmail.com>
|
||||||
|
* 2018-05-06
|
||||||
|
*/
|
||||||
|
package mc.core.chat;
|
||||||
|
|
||||||
|
import mc.core.player.Player;
|
||||||
|
import org.slf4j.Marker;
|
||||||
|
import org.slf4j.helpers.BasicMarkerFactory;
|
||||||
|
|
||||||
|
public abstract class ChatProcessor {
|
||||||
|
protected static final Marker CHAT_MARKER = new BasicMarkerFactory().getMarker("Chat");
|
||||||
|
|
||||||
|
public abstract void process(Player player, String message);
|
||||||
|
}
|
||||||
17
core/src/main/java/mc/core/chat/CommandExecutor.java
Normal file
17
core/src/main/java/mc/core/chat/CommandExecutor.java
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
/*
|
||||||
|
* DmitriyMX <dimon550@gmail.com>
|
||||||
|
* 2018-05-22
|
||||||
|
*/
|
||||||
|
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);
|
||||||
|
}
|
||||||
94
core/src/main/java/mc/core/chat/CommanderChatProcessor.java
Normal file
94
core/src/main/java/mc/core/chat/CommanderChatProcessor.java
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
/*
|
||||||
|
* DmitriyMX <dimon550@gmail.com>
|
||||||
|
* 2018-05-23
|
||||||
|
*/
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
18
core/src/main/java/mc/core/chat/MessageType.java
Normal file
18
core/src/main/java/mc/core/chat/MessageType.java
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
/*
|
||||||
|
* DmitriyMX <dimon550@gmail.com>
|
||||||
|
* 2018-06-24
|
||||||
|
*/
|
||||||
|
package mc.core.chat;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
@Getter
|
||||||
|
public enum MessageType {
|
||||||
|
CHAT_MESSAGE(0), // chat box
|
||||||
|
SYSTEM_MESSAGE(1), // chat box
|
||||||
|
GAME_INFO(2); // above hotbar
|
||||||
|
|
||||||
|
private final int id;
|
||||||
|
}
|
||||||
29
core/src/main/java/mc/core/chat/SimpleChatProcessor.java
Normal file
29
core/src/main/java/mc/core/chat/SimpleChatProcessor.java
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
/*
|
||||||
|
* DmitriyMX <dimon550@gmail.com>
|
||||||
|
* 2018-05-06
|
||||||
|
*/
|
||||||
|
package mc.core.chat;
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import mc.core.player.Player;
|
||||||
|
import mc.core.player.PlayerManager;
|
||||||
|
import mc.core.text.Text;
|
||||||
|
import mc.core.text.TextColor;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
public class SimpleChatProcessor extends ChatProcessor {
|
||||||
|
@Autowired
|
||||||
|
private PlayerManager playerManager;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void process(Player player, String message) {
|
||||||
|
log.info(CHAT_MARKER, "<{}> {}", player.getName(), message);
|
||||||
|
playerManager.getBroadcastChannel().sendChatMessage(
|
||||||
|
Text.builder(TextColor.GOLD, player.getName())
|
||||||
|
.append(Text.of(TextColor.GRAY, ": "))
|
||||||
|
.append(Text.of(TextColor.WHITE, message))
|
||||||
|
.build()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
37
core/src/main/java/mc/core/embedded/ConfigFromSpring.java
Normal file
37
core/src/main/java/mc/core/embedded/ConfigFromSpring.java
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
/*
|
||||||
|
* DmitriyMX <dimon550@gmail.com>
|
||||||
|
* 2018-04-08
|
||||||
|
*/
|
||||||
|
package mc.core.embedded;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.Setter;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import mc.core.Config;
|
||||||
|
import org.apache.commons.io.FileUtils;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Base64;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
@Getter
|
||||||
|
public class ConfigFromSpring implements Config {
|
||||||
|
@Setter
|
||||||
|
private String descriptionServer;
|
||||||
|
private byte[] faviconBase64;
|
||||||
|
@Setter
|
||||||
|
private int maxPlayers;
|
||||||
|
|
||||||
|
public void setFaviconBase64(File faviconImageFile) {
|
||||||
|
log.debug("faviconImageFile: {}", faviconImageFile.getAbsolutePath());
|
||||||
|
try {
|
||||||
|
faviconBase64 = Base64.getEncoder().encode(
|
||||||
|
FileUtils.readFileToByteArray(faviconImageFile)
|
||||||
|
);
|
||||||
|
} catch (IOException e) {
|
||||||
|
log.warn("Can't load favicon", e);
|
||||||
|
faviconBase64 = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
83
core/src/main/java/mc/core/embedded/FakePlayerManager.java
Normal file
83
core/src/main/java/mc/core/embedded/FakePlayerManager.java
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
package mc.core.embedded;
|
||||||
|
|
||||||
|
import mc.core.EntityLocation;
|
||||||
|
import mc.core.chat.MessageType;
|
||||||
|
import mc.core.network.NetChannel;
|
||||||
|
import mc.core.network.SCPacket;
|
||||||
|
import mc.core.player.Player;
|
||||||
|
import mc.core.player.PlayerManager;
|
||||||
|
import mc.core.text.Text;
|
||||||
|
import mc.core.text.Title;
|
||||||
|
import mc.core.world.World;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class FakePlayerManager implements PlayerManager {
|
||||||
|
public static class FakeNetChannet implements NetChannel {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void sendTimeUpdate(long time, long age) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void sendChatMessage(Text text, MessageType type) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void sendTitle(Title title) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void writeAndFlush(SCPacket pkt) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void write(SCPacket pkt) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void flush() {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final NetChannel FAKE_NET_CHANNEL = new FakeNetChannet();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Player createPlayer(String name, EntityLocation location, World world) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void joinServer(Player player) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void leftServer(Player player) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Player getPlayer(String name) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<Player> getPlayers() {
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getCountPlayers() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public NetChannel getBroadcastChannel() {
|
||||||
|
return FAKE_NET_CHANNEL;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Player getOfflinePlayer(String name) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
18
core/src/main/java/mc/core/embedded/FakeServer.java
Normal file
18
core/src/main/java/mc/core/embedded/FakeServer.java
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
/*
|
||||||
|
* DmitriyMX <dimon550@gmail.com>
|
||||||
|
* 2018-06-29
|
||||||
|
*/
|
||||||
|
package mc.core.embedded;
|
||||||
|
|
||||||
|
import mc.core.network.Server;
|
||||||
|
import mc.core.network.StartServerException;
|
||||||
|
|
||||||
|
public class FakeServer implements Server {
|
||||||
|
@Override
|
||||||
|
public void start() throws StartServerException {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void stop() {
|
||||||
|
}
|
||||||
|
}
|
||||||
4
core/src/main/java/mc/core/eventbus/Event.java
Normal file
4
core/src/main/java/mc/core/eventbus/Event.java
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
package mc.core.eventbus;
|
||||||
|
|
||||||
|
public interface Event {
|
||||||
|
}
|
||||||
89
core/src/main/java/mc/core/eventbus/EventBus.java
Normal file
89
core/src/main/java/mc/core/eventbus/EventBus.java
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
package mc.core.eventbus;
|
||||||
|
|
||||||
|
import javafx.util.Pair;
|
||||||
|
import lombok.AccessLevel;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.slf4j.helpers.MessageFormatter;
|
||||||
|
|
||||||
|
import java.lang.reflect.InvocationTargetException;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
@NoArgsConstructor(access = AccessLevel.PRIVATE)
|
||||||
|
public class EventBus {
|
||||||
|
@Getter
|
||||||
|
private static final EventBus instance = new EventBus();
|
||||||
|
|
||||||
|
private Queue<Event> eventQueue = new ConcurrentLinkedQueue<>();
|
||||||
|
private Map<Class<? extends Event>, List<Pair<Object, Method>>> subscribes = new HashMap<>();
|
||||||
|
|
||||||
|
private Stream<Method> getMethods(Object subscriberObject) {
|
||||||
|
return Stream.of(subscriberObject.getClass().getDeclaredMethods())
|
||||||
|
.filter(method -> method.isAnnotationPresent(Subscriber.class))
|
||||||
|
.filter(method -> method.getReturnType().equals(Void.TYPE))
|
||||||
|
.filter(method -> method.getParameterCount() == 1)
|
||||||
|
.filter(method -> Event.class.isAssignableFrom(method.getParameterTypes()[0]));
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public void registerSubscribes(Object subscriberObject) {
|
||||||
|
getMethods(subscriberObject)
|
||||||
|
.forEach(method -> {
|
||||||
|
final Class<? extends Event> type = (Class<? extends Event>) method.getParameterTypes()[0];
|
||||||
|
final List<Pair<Object, Method>> pairs;
|
||||||
|
if (subscribes.containsKey(type)) {
|
||||||
|
pairs = subscribes.get(type);
|
||||||
|
} else {
|
||||||
|
pairs = new ArrayList<>();
|
||||||
|
subscribes.put(type, pairs);
|
||||||
|
}
|
||||||
|
pairs.add(new Pair<>(subscriberObject, method));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public void unregisterSubscribes(Object subscriberObject) {
|
||||||
|
getMethods(subscriberObject)
|
||||||
|
.forEach(method -> {
|
||||||
|
final Class<? extends Event> type = (Class<? extends Event>) method.getParameterTypes()[0];
|
||||||
|
if (subscribes.containsKey(type)) {
|
||||||
|
final List<Pair<Object, Method>> pairs = subscribes.get(type);
|
||||||
|
pairs.removeIf(pair -> pair.getKey() == subscriberObject);
|
||||||
|
|
||||||
|
if (pairs.isEmpty()) {
|
||||||
|
subscribes.remove(type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void post(Event event) {
|
||||||
|
eventQueue.add(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void process() {
|
||||||
|
Event event;
|
||||||
|
while ((event = eventQueue.poll()) != null) {
|
||||||
|
final Class<? extends Event> type = event.getClass();
|
||||||
|
if (subscribes.containsKey(type)) {
|
||||||
|
final List<Pair<Object, Method>> pairs = subscribes.get(type);
|
||||||
|
for (Pair<Object, Method> pair : pairs) {
|
||||||
|
try {
|
||||||
|
pair.getValue().invoke(pair.getKey(), event);
|
||||||
|
} catch (IllegalAccessException | InvocationTargetException e) {
|
||||||
|
log.error(MessageFormatter.format("Invoke method '{}#{}'",
|
||||||
|
pair.getKey().getClass().getSimpleName(),
|
||||||
|
pair.getValue().getName()).getMessage(),
|
||||||
|
e
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
11
core/src/main/java/mc/core/eventbus/Subscriber.java
Normal file
11
core/src/main/java/mc/core/eventbus/Subscriber.java
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
package mc.core.eventbus;
|
||||||
|
|
||||||
|
import java.lang.annotation.ElementType;
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.RetentionPolicy;
|
||||||
|
import java.lang.annotation.Target;
|
||||||
|
|
||||||
|
@Target(value= ElementType.METHOD)
|
||||||
|
@Retention(value= RetentionPolicy.RUNTIME)
|
||||||
|
public @interface Subscriber {
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
package mc.core.eventbus.events;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.Setter;
|
||||||
|
import mc.core.EntityLocation;
|
||||||
|
import mc.core.ImmutableEntityLocation;
|
||||||
|
import mc.core.eventbus.Event;
|
||||||
|
import mc.core.player.Player;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
public class CS_PlayerMoveEvent implements Event {
|
||||||
|
private final Player player;
|
||||||
|
private final ImmutableEntityLocation oldLocation;
|
||||||
|
@Setter
|
||||||
|
private EntityLocation newLocation;
|
||||||
|
@Setter
|
||||||
|
private boolean recalcChunk = false;
|
||||||
|
|
||||||
|
public CS_PlayerMoveEvent(Player player, EntityLocation oldLocation) {
|
||||||
|
this.player = player;
|
||||||
|
this.oldLocation = new ImmutableEntityLocation(oldLocation);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
package mc.core.eventbus.events;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import mc.core.eventbus.Event;
|
||||||
|
import mc.core.player.Player;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class SC_ChunkLoadEvent implements Event {
|
||||||
|
@Getter
|
||||||
|
private final Player player;
|
||||||
|
@Getter
|
||||||
|
private List<Integer> needLoadChunks = new ArrayList<>();
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
package mc.core.eventbus.events;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import mc.core.eventbus.Event;
|
||||||
|
import mc.core.player.Player;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class SC_ChunkUnloadEvent implements Event {
|
||||||
|
@Getter
|
||||||
|
private final Player player;
|
||||||
|
@Getter
|
||||||
|
private List<Integer> needUnloadChunks = new ArrayList<>();
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package mc.core.eventbus.events;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.Setter;
|
||||||
|
import mc.core.EntityLocation;
|
||||||
|
import mc.core.eventbus.Event;
|
||||||
|
import mc.core.player.Player;
|
||||||
|
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
@Getter
|
||||||
|
public class SC_PlayerMoveEvent implements Event {
|
||||||
|
private final Player player;
|
||||||
|
@Setter
|
||||||
|
private EntityLocation newLocation;
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
package mc.core.exception;
|
||||||
|
|
||||||
|
public class ResourceUnloadedException extends RuntimeException {
|
||||||
|
public ResourceUnloadedException(String msg) {
|
||||||
|
super(msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
48
core/src/main/java/mc/core/network/BroadcastNetChannel.java
Normal file
48
core/src/main/java/mc/core/network/BroadcastNetChannel.java
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
/*
|
||||||
|
* DmitriyMX <dimon550@gmail.com>
|
||||||
|
* 2018-04-21
|
||||||
|
*/
|
||||||
|
package mc.core.network;
|
||||||
|
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import mc.core.chat.MessageType;
|
||||||
|
import mc.core.player.Player;
|
||||||
|
import mc.core.text.Text;
|
||||||
|
import mc.core.text.Title;
|
||||||
|
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class BroadcastNetChannel implements NetChannel {
|
||||||
|
private final Stream<Player> playerStream;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void sendTimeUpdate(final long time, final long age) {
|
||||||
|
playerStream.forEach(player -> player.getChannel().sendTimeUpdate(time, age));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void sendChatMessage(final Text text, final MessageType type) {
|
||||||
|
playerStream.forEach(player -> player.getChannel().sendChatMessage(text, type));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void sendTitle(final Title title) {
|
||||||
|
playerStream.forEach(player -> player.getChannel().sendTitle(title));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void writeAndFlush(final SCPacket pkt) {
|
||||||
|
playerStream.forEach(player -> player.getChannel().writeAndFlush(pkt));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void write(SCPacket pkt) {
|
||||||
|
playerStream.forEach(player -> player.getChannel().write(pkt));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void flush() {
|
||||||
|
playerStream.forEach(player -> player.getChannel().flush());
|
||||||
|
}
|
||||||
|
}
|
||||||
12
core/src/main/java/mc/core/network/CSPacket.java
Normal file
12
core/src/main/java/mc/core/network/CSPacket.java
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
/*
|
||||||
|
* DmitriyMX <dimon550@gmail.com>
|
||||||
|
* 2018-04-08
|
||||||
|
*/
|
||||||
|
package mc.core.network;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Пакеты Client->Server
|
||||||
|
*/
|
||||||
|
public interface CSPacket {
|
||||||
|
void readSelf(NetInputStream netStream);
|
||||||
|
}
|
||||||
22
core/src/main/java/mc/core/network/NetChannel.java
Normal file
22
core/src/main/java/mc/core/network/NetChannel.java
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
/*
|
||||||
|
* DmitriyMX <dimon550@gmail.com>
|
||||||
|
* 2018-04-13
|
||||||
|
*/
|
||||||
|
package mc.core.network;
|
||||||
|
|
||||||
|
import mc.core.chat.MessageType;
|
||||||
|
import mc.core.text.Text;
|
||||||
|
import mc.core.text.Title;
|
||||||
|
|
||||||
|
public interface NetChannel {
|
||||||
|
void sendTimeUpdate(long time, long age);
|
||||||
|
default void sendChatMessage(Text text) {
|
||||||
|
sendChatMessage(text, MessageType.CHAT_MESSAGE);
|
||||||
|
}
|
||||||
|
void sendChatMessage(Text text, MessageType type);
|
||||||
|
void sendTitle(Title title);
|
||||||
|
|
||||||
|
void writeAndFlush(SCPacket pkt);
|
||||||
|
void write(SCPacket pkt);
|
||||||
|
void flush();
|
||||||
|
}
|
||||||
58
core/src/main/java/mc/core/network/NetInputStream.java
Normal file
58
core/src/main/java/mc/core/network/NetInputStream.java
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
package mc.core.network;
|
||||||
|
|
||||||
|
import com.flowpowered.nbt.Tag;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.Setter;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.util.UUID;
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
|
public abstract class NetInputStream extends InputStream {
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
private int dataSize;
|
||||||
|
|
||||||
|
public abstract boolean readBoolean();
|
||||||
|
public abstract byte readByte();
|
||||||
|
public int readBytes(byte[] buffer) {
|
||||||
|
return readBytes(buffer, 0, buffer.length);
|
||||||
|
}
|
||||||
|
public abstract int readBytes(byte[] buffer, int offset, int length);
|
||||||
|
public abstract int readUnsignedByte();
|
||||||
|
public abstract int readUnsignedShort();
|
||||||
|
public abstract short readShort();
|
||||||
|
public abstract int readInt();
|
||||||
|
public abstract int readVarInt();
|
||||||
|
public abstract int readVarInt(AtomicInteger countReadBytes);
|
||||||
|
public abstract long readLong();
|
||||||
|
public abstract float readFloat();
|
||||||
|
public abstract double readDouble();
|
||||||
|
public abstract String readString();
|
||||||
|
public abstract UUID readUUID();
|
||||||
|
public abstract Tag<?> readNBT();
|
||||||
|
|
||||||
|
public abstract void skipBytes(int count);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int read() {
|
||||||
|
return readByte();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int read(@NotNull byte[] b) {
|
||||||
|
return readBytes(b);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int read(@NotNull byte[] b, int off, int len) {
|
||||||
|
return readBytes(b, off, len);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long skip(long n) {
|
||||||
|
skipBytes((int) n);
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
}
|
||||||
41
core/src/main/java/mc/core/network/NetOutputStream.java
Normal file
41
core/src/main/java/mc/core/network/NetOutputStream.java
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
package mc.core.network;
|
||||||
|
|
||||||
|
import com.flowpowered.nbt.Tag;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
public abstract class NetOutputStream extends OutputStream {
|
||||||
|
public abstract void writeBoolean(boolean value);
|
||||||
|
public abstract void writeByte(int value);
|
||||||
|
public abstract void writeUnsignedByte(int value);
|
||||||
|
public void writeBytes(byte[] buffer) {
|
||||||
|
writeBytes(buffer, 0, buffer.length);
|
||||||
|
}
|
||||||
|
public abstract void writeBytes(byte[] buffer, int offset, int lengtn);
|
||||||
|
public abstract void writeShort(int value);
|
||||||
|
public abstract void writeInt(int value);
|
||||||
|
public abstract void writeVarInt(int value);
|
||||||
|
public abstract void writeLong(long value);
|
||||||
|
public abstract void writeFloat(float value);
|
||||||
|
public abstract void writeDouble(double value);
|
||||||
|
public abstract void writeString(String value);
|
||||||
|
public abstract void writeUUID(UUID uuid);
|
||||||
|
public abstract void writeNBT(Tag<?> tag);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void write(int b) throws IOException {
|
||||||
|
writeByte(b);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void write(byte[] b) throws IOException {
|
||||||
|
writeBytes(b);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void write(byte[] b, int off, int len) throws IOException {
|
||||||
|
writeBytes(b, off, len);
|
||||||
|
}
|
||||||
|
}
|
||||||
12
core/src/main/java/mc/core/network/SCPacket.java
Normal file
12
core/src/main/java/mc/core/network/SCPacket.java
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
/*
|
||||||
|
* DmitriyMX <dimon550@gmail.com>
|
||||||
|
* 2018-04-08
|
||||||
|
*/
|
||||||
|
package mc.core.network;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Пакеты Server->Client
|
||||||
|
*/
|
||||||
|
public interface SCPacket {
|
||||||
|
void writeSelf(NetOutputStream netStream);
|
||||||
|
}
|
||||||
10
core/src/main/java/mc/core/network/Server.java
Normal file
10
core/src/main/java/mc/core/network/Server.java
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
/*
|
||||||
|
* DmitriyMX <dimon550@gmail.com>
|
||||||
|
* 2018-03-25
|
||||||
|
*/
|
||||||
|
package mc.core.network;
|
||||||
|
|
||||||
|
public interface Server {
|
||||||
|
void start() throws StartServerException;
|
||||||
|
void stop();
|
||||||
|
}
|
||||||
11
core/src/main/java/mc/core/network/StartServerException.java
Normal file
11
core/src/main/java/mc/core/network/StartServerException.java
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
/*
|
||||||
|
* DmitriyMX <dimon550@gmail.com>
|
||||||
|
* 2018-03-25
|
||||||
|
*/
|
||||||
|
package mc.core.network;
|
||||||
|
|
||||||
|
public class StartServerException extends Exception {
|
||||||
|
public StartServerException(Throwable cause) {
|
||||||
|
super(cause);
|
||||||
|
}
|
||||||
|
}
|
||||||
35
core/src/main/java/mc/core/player/Player.java
Normal file
35
core/src/main/java/mc/core/player/Player.java
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
/*
|
||||||
|
* DmitriyMX <dimon550@gmail.com>
|
||||||
|
* 2018-04-13
|
||||||
|
*/
|
||||||
|
package mc.core.player;
|
||||||
|
|
||||||
|
import mc.core.EntityLocation;
|
||||||
|
import mc.core.network.NetChannel;
|
||||||
|
import mc.core.world.World;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
public interface Player {
|
||||||
|
int getId();
|
||||||
|
UUID getUuid();
|
||||||
|
String getName();
|
||||||
|
boolean isOnline();
|
||||||
|
|
||||||
|
/** Compacted list of Chunk coords (x,z) */
|
||||||
|
List<Integer> getLoadedChunks();
|
||||||
|
|
||||||
|
NetChannel getChannel();
|
||||||
|
void setChannel(NetChannel channel);
|
||||||
|
|
||||||
|
EntityLocation getLocation();
|
||||||
|
World getWorld();
|
||||||
|
void setWorld(World world);
|
||||||
|
|
||||||
|
boolean isFlying();
|
||||||
|
void setFlying(boolean value);
|
||||||
|
|
||||||
|
PlayerSettings getSettings();
|
||||||
|
void setSettings(PlayerSettings settings);
|
||||||
|
}
|
||||||
20
core/src/main/java/mc/core/player/PlayerManager.java
Normal file
20
core/src/main/java/mc/core/player/PlayerManager.java
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
package mc.core.player;
|
||||||
|
|
||||||
|
import mc.core.EntityLocation;
|
||||||
|
import mc.core.network.NetChannel;
|
||||||
|
import mc.core.world.World;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public interface PlayerManager {
|
||||||
|
Player createPlayer(String name, EntityLocation location, World world);
|
||||||
|
void joinServer(Player player);
|
||||||
|
void leftServer(Player player);
|
||||||
|
|
||||||
|
Player getPlayer(String name);
|
||||||
|
List<Player> getPlayers();
|
||||||
|
int getCountPlayers();
|
||||||
|
NetChannel getBroadcastChannel();
|
||||||
|
|
||||||
|
Player getOfflinePlayer(String name);
|
||||||
|
}
|
||||||
19
core/src/main/java/mc/core/player/PlayerMode.java
Normal file
19
core/src/main/java/mc/core/player/PlayerMode.java
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
/*
|
||||||
|
* DmitriyMX <dimon550@gmail.com>
|
||||||
|
* 2018-05-06
|
||||||
|
*/
|
||||||
|
package mc.core.player;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
@Getter
|
||||||
|
public enum PlayerMode {
|
||||||
|
SURVIVAL(0),
|
||||||
|
CREATIVE(1),
|
||||||
|
ADVENTURE(2),
|
||||||
|
SPECTATOR(3);
|
||||||
|
|
||||||
|
private final int id;
|
||||||
|
}
|
||||||
56
core/src/main/java/mc/core/player/PlayerSettings.java
Normal file
56
core/src/main/java/mc/core/player/PlayerSettings.java
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
/*
|
||||||
|
* DmitriyMX <dimon550@gmail.com>
|
||||||
|
* 2018-06-23
|
||||||
|
*/
|
||||||
|
package mc.core.player;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.Setter;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
public class PlayerSettings {
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public enum ChatMode {
|
||||||
|
ENABLED(0),
|
||||||
|
COMMANDS_ONLY(1),
|
||||||
|
HIDDEN(2);
|
||||||
|
|
||||||
|
public static ChatMode getById(int id) {
|
||||||
|
if (id == 0) return ENABLED;
|
||||||
|
else if (id == 1) return COMMANDS_ONLY;
|
||||||
|
else return HIDDEN;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
private final int id;
|
||||||
|
}
|
||||||
|
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public enum Hand {
|
||||||
|
LEFT(0),
|
||||||
|
RIGHT(1);
|
||||||
|
|
||||||
|
public static Hand getById(int id) {
|
||||||
|
if (id == 0) return LEFT;
|
||||||
|
else return RIGHT;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
private final int id;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String locate = "en_US";
|
||||||
|
private int viewDistance = 8;
|
||||||
|
private ChatMode chatMode = ChatMode.ENABLED;
|
||||||
|
private boolean chatColors = true;
|
||||||
|
private boolean capeEnabled = true,
|
||||||
|
jacketEnabled = true,
|
||||||
|
leftSleeveEnabled = true,
|
||||||
|
rightSleeveEnabled = true,
|
||||||
|
leftPantsLegEnabled = true,
|
||||||
|
rightPantsLegEnabled = true,
|
||||||
|
hatEnabled = true;
|
||||||
|
private Hand mainHand = Hand.RIGHT;
|
||||||
|
}
|
||||||
237
core/src/main/java/mc/core/text/Text.java
Normal file
237
core/src/main/java/mc/core/text/Text.java
Normal file
@@ -0,0 +1,237 @@
|
|||||||
|
package mc.core.text;
|
||||||
|
|
||||||
|
import com.google.common.collect.ImmutableList;
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
public class Text {
|
||||||
|
private static final Text EMPTY = new Text();
|
||||||
|
private static final Text NEW_LINE = new Text("\n", null, null, null);
|
||||||
|
|
||||||
|
private final String content;
|
||||||
|
private final TextColor color;
|
||||||
|
private final TextStyle style;
|
||||||
|
private final ImmutableList<Text> children;
|
||||||
|
|
||||||
|
private Text() {
|
||||||
|
content = "";
|
||||||
|
color = null;
|
||||||
|
style = null;
|
||||||
|
children = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Text(String content, TextColor color, TextStyle style, ImmutableList<Text> children) {
|
||||||
|
this.content = content;
|
||||||
|
this.color = color;
|
||||||
|
this.style = style;
|
||||||
|
this.children = children;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isEmpty() {
|
||||||
|
boolean result = (content == null || content.isEmpty());
|
||||||
|
|
||||||
|
if (children != null && !children.isEmpty()) {
|
||||||
|
for (Text child : children) {
|
||||||
|
result = result && child.isEmpty();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String toPlain() {
|
||||||
|
if (children != null && !children.isEmpty()) {
|
||||||
|
final StringJoiner sj = new StringJoiner("");
|
||||||
|
children.forEach(child -> sj.add(child.toPlain()));
|
||||||
|
return sj.toString();
|
||||||
|
} else {
|
||||||
|
return content;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) return true;
|
||||||
|
if (o == null || getClass() != o.getClass()) return false;
|
||||||
|
Text text = (Text) o;
|
||||||
|
return Objects.equals(toPlain(), text.toPlain());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Objects.hash(toPlain());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Builder {
|
||||||
|
@Getter
|
||||||
|
private String content;
|
||||||
|
@Getter
|
||||||
|
private TextColor color;
|
||||||
|
@Getter
|
||||||
|
private TextStyle style;
|
||||||
|
private List<Text> children;
|
||||||
|
|
||||||
|
public Builder() {
|
||||||
|
this("");
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder(String content) {
|
||||||
|
this.content = content;
|
||||||
|
this.color = null;
|
||||||
|
this.style = null;
|
||||||
|
this.children = new ArrayList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder(Object... objects) {
|
||||||
|
this.children = new ArrayList<>();
|
||||||
|
|
||||||
|
for(Object obj : objects) {
|
||||||
|
if (obj instanceof String) {
|
||||||
|
if (this.content == null) {
|
||||||
|
this.content = (String) obj;
|
||||||
|
} else {
|
||||||
|
this.content = this.content.concat((String) obj);
|
||||||
|
}
|
||||||
|
} else if (obj instanceof TextStyle) {
|
||||||
|
if (this.style == null) {
|
||||||
|
this.style = TextStyle.none();
|
||||||
|
} else {
|
||||||
|
this.style.merge((TextStyle) obj);
|
||||||
|
}
|
||||||
|
} else if (obj instanceof TextColor) {
|
||||||
|
this.color = (TextColor) obj;
|
||||||
|
} else if (obj instanceof Text) {
|
||||||
|
children.add((Text) obj);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Text> getChildren() {
|
||||||
|
return Collections.unmodifiableList(children);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder color(TextColor color) {
|
||||||
|
this.color = color;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder style(TextStyle style) {
|
||||||
|
if (this.style == null) {
|
||||||
|
this.style = TextStyle.none();
|
||||||
|
} else {
|
||||||
|
this.style.merge(style);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder style(TextStyle... styles) {
|
||||||
|
if (this.style == null) {
|
||||||
|
this.style = TextStyle.none();
|
||||||
|
}
|
||||||
|
|
||||||
|
for(TextStyle style : styles) {
|
||||||
|
this.style.merge(style);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder append(String string) {
|
||||||
|
return append(Text.of(string));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder append(Text child) {
|
||||||
|
if (child != null) {
|
||||||
|
this.children.add(child);
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder append(Text... children) {
|
||||||
|
Collections.addAll(this.children, children);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Text build() {
|
||||||
|
if (children.isEmpty() && (content == null || content.isEmpty())) {
|
||||||
|
return Text.EMPTY;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (children.size() == 1 && children.get(0) != null) {
|
||||||
|
return children.get(0);
|
||||||
|
} else {
|
||||||
|
return new Text(
|
||||||
|
content,
|
||||||
|
color,
|
||||||
|
style,
|
||||||
|
ImmutableList.copyOf(children)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Builder builder() {
|
||||||
|
return new Builder();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Builder builder(String content) {
|
||||||
|
return new Builder(content);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Builder builder(Object... objects) {
|
||||||
|
return new Builder(objects);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Text of() {
|
||||||
|
return EMPTY;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Text of(String string) {
|
||||||
|
if (string == null || string.isEmpty()) {
|
||||||
|
return EMPTY;
|
||||||
|
} else if (string.equals("\n")) {
|
||||||
|
return NEW_LINE;
|
||||||
|
} else {
|
||||||
|
return new Text(string, null, null, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Text of(Object... objects) {
|
||||||
|
TextColor color = null;
|
||||||
|
TextStyle style = null;
|
||||||
|
String content = null;
|
||||||
|
|
||||||
|
for(Object obj : objects) {
|
||||||
|
if (obj instanceof String) {
|
||||||
|
if (content == null) {
|
||||||
|
content = (String) obj;
|
||||||
|
} else {
|
||||||
|
content = content.concat((String) obj);
|
||||||
|
}
|
||||||
|
} else if (obj instanceof TextStyle) {
|
||||||
|
if (style == null) {
|
||||||
|
style = (TextStyle) obj;
|
||||||
|
} else {
|
||||||
|
style.merge((TextStyle) obj);
|
||||||
|
}
|
||||||
|
} else if (obj instanceof TextColor) {
|
||||||
|
color = (TextColor) obj;
|
||||||
|
} else if (obj != null){
|
||||||
|
if (content == null) {
|
||||||
|
content = obj.toString();
|
||||||
|
} else {
|
||||||
|
content = content.concat(obj.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (content == null || content.isEmpty()) {
|
||||||
|
return EMPTY;
|
||||||
|
} else {
|
||||||
|
return new Text(content, color, style, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
32
core/src/main/java/mc/core/text/TextColor.java
Normal file
32
core/src/main/java/mc/core/text/TextColor.java
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
/*
|
||||||
|
* DmitriyMX <dimon550@gmail.com>
|
||||||
|
* 2018-06-11
|
||||||
|
*/
|
||||||
|
package mc.core.text;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
@Getter
|
||||||
|
public enum TextColor {
|
||||||
|
BLACK ("black", '0'),
|
||||||
|
DARK_BLUE ("dark_blue", '1'),
|
||||||
|
DARK_GREEN ("dark_green", '2'),
|
||||||
|
DARK_AQUA ("dark_aqua", '3'),
|
||||||
|
DARK_RED ("dark_red", '4'),
|
||||||
|
DARK_PUEPLE("dark_purple", '5'),
|
||||||
|
GOLD ("gold", '6'),
|
||||||
|
GRAY ("gray", '7'),
|
||||||
|
DARK_GRAY ("dark_gray", '8'),
|
||||||
|
BLUE ("blue", '9'),
|
||||||
|
GREEN ("green", 'a'),
|
||||||
|
AQUA ("aqua", 'b'),
|
||||||
|
RED ("red", 'c'),
|
||||||
|
PUEPLE ("light_purple",'d'),
|
||||||
|
YELLOW ("yellow", 'e'),
|
||||||
|
WHITE ("white", 'f');
|
||||||
|
|
||||||
|
private final String name;
|
||||||
|
private final char code;
|
||||||
|
}
|
||||||
69
core/src/main/java/mc/core/text/TextStyle.java
Normal file
69
core/src/main/java/mc/core/text/TextStyle.java
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
/*
|
||||||
|
* DmitriyMX <dimon550@gmail.com>
|
||||||
|
* 2018-06-11
|
||||||
|
*/
|
||||||
|
package mc.core.text;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.Setter;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
public class TextStyle {
|
||||||
|
public static final TextStyle BOLD = new TextStyle(true, null, null, null, null);
|
||||||
|
public static final TextStyle ITALIC = new TextStyle(null, true, null, null, null);
|
||||||
|
public static final TextStyle UNDERLINE = new TextStyle(null, null, true, null, null);
|
||||||
|
public static final TextStyle STRIKETHOUGH = new TextStyle(null, null, null, true, null);
|
||||||
|
public static final TextStyle OBFUSCATED = new TextStyle(null, null, null, null, true);
|
||||||
|
public static final TextStyle RESET = new TextStyle(false, false, false, false, false);
|
||||||
|
|
||||||
|
private static class OptionalBoolean {
|
||||||
|
private static final Optional<Boolean> TRUE = Optional.of(true);
|
||||||
|
private static final Optional<Boolean> FALSE = Optional.of(false);
|
||||||
|
private static final Optional<Boolean> NONE = Optional.empty();
|
||||||
|
|
||||||
|
static Optional<Boolean> of(boolean bool) {
|
||||||
|
return bool ? TRUE : FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Optional<Boolean> of(@Nullable Boolean bool) {
|
||||||
|
if (bool != null) {
|
||||||
|
return of(bool.booleanValue());
|
||||||
|
}
|
||||||
|
return NONE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Optional<Boolean> bold;
|
||||||
|
private Optional<Boolean> italic;
|
||||||
|
private Optional<Boolean> underline;
|
||||||
|
private Optional<Boolean> strikethrough;
|
||||||
|
private Optional<Boolean> obfuscated;
|
||||||
|
|
||||||
|
public TextStyle(@Nullable Boolean bold,
|
||||||
|
@Nullable Boolean italic,
|
||||||
|
@Nullable Boolean underline,
|
||||||
|
@Nullable Boolean strikethrough,
|
||||||
|
@Nullable Boolean obfuscated) {
|
||||||
|
this.bold = OptionalBoolean.of(bold);
|
||||||
|
this.italic = OptionalBoolean.of(italic);
|
||||||
|
this.underline = OptionalBoolean.of(underline);
|
||||||
|
this.strikethrough = OptionalBoolean.of(strikethrough);
|
||||||
|
this.obfuscated = OptionalBoolean.of(obfuscated);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void merge(TextStyle style) {
|
||||||
|
if (style.bold.isPresent()) this.bold = style.bold;
|
||||||
|
if (style.italic.isPresent()) this.italic = style.italic;
|
||||||
|
if (style.underline.isPresent()) this.underline = style.underline;
|
||||||
|
if (style.strikethrough.isPresent()) this.strikethrough = style.strikethrough;
|
||||||
|
if (style.obfuscated.isPresent()) this.obfuscated = style.obfuscated;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static TextStyle none() {
|
||||||
|
return new TextStyle(null,null,null,null, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
150
core/src/main/java/mc/core/text/TextTemplate.java
Normal file
150
core/src/main/java/mc/core/text/TextTemplate.java
Normal file
@@ -0,0 +1,150 @@
|
|||||||
|
/*
|
||||||
|
* DmitriyMX <dimon550@gmail.com>
|
||||||
|
* 2018-06-13
|
||||||
|
*/
|
||||||
|
package mc.core.text;
|
||||||
|
|
||||||
|
import com.google.common.collect.ImmutableList;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.Setter;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
public class TextTemplate {
|
||||||
|
private final ImmutableList<Object> elements;
|
||||||
|
|
||||||
|
private TextTemplate(ImmutableList<Object> elements) {
|
||||||
|
this.elements = elements;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Text apply(Object... objects) {
|
||||||
|
Map<String, Object> variableMap = new HashMap<>((objects.length % 2) == 1 ? (objects.length / 2) + 1 : (objects.length / 2));
|
||||||
|
|
||||||
|
boolean skipValue = false;
|
||||||
|
String key = null;
|
||||||
|
for (Object obj : objects) {
|
||||||
|
if (skipValue) {
|
||||||
|
skipValue = false;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (key == null) {
|
||||||
|
if (obj == null || obj.toString().trim().isEmpty()) {
|
||||||
|
skipValue = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
key = obj.toString().trim();
|
||||||
|
} else {
|
||||||
|
variableMap.put(key, obj);
|
||||||
|
key = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (key != null) {
|
||||||
|
variableMap.put(key, "");
|
||||||
|
}
|
||||||
|
|
||||||
|
return apply(variableMap);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Text apply(Map<String, Object> variables) {
|
||||||
|
Text.Builder textBuilder = Text.builder();
|
||||||
|
|
||||||
|
for(Object obj : elements) {
|
||||||
|
if (obj instanceof Text) {
|
||||||
|
textBuilder.append((Text) obj);
|
||||||
|
} else if (obj instanceof Arg) {
|
||||||
|
Arg arg = (Arg) obj;
|
||||||
|
if (variables.containsKey(arg.getKey())) {
|
||||||
|
Object valueObj = variables.get(arg.getKey());
|
||||||
|
|
||||||
|
if (valueObj instanceof Text) {
|
||||||
|
textBuilder.append((Text) valueObj);
|
||||||
|
} else {
|
||||||
|
textBuilder.append(Text.of(valueObj, arg.getColor(), arg.getStyle()));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
textBuilder.append(Text.of(arg.getDefaultValue(), arg.getColor(), arg.getStyle()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return textBuilder.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
@Getter
|
||||||
|
public static class Arg {
|
||||||
|
private final String key;
|
||||||
|
private final String defaultValue;
|
||||||
|
@Setter
|
||||||
|
private TextColor color;
|
||||||
|
@Setter
|
||||||
|
private TextStyle style;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Builder {
|
||||||
|
private List<Object> elements = new ArrayList<>();
|
||||||
|
|
||||||
|
public Builder append(Text element) {
|
||||||
|
this.elements.add(element);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder append(Text... elements) {
|
||||||
|
Collections.addAll(this.elements, elements);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder arg(String name) {
|
||||||
|
this.elements.add(new Arg(name, null));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder arg(String name, String defaultValue) {
|
||||||
|
this.elements.add(new Arg(name, defaultValue));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder arg(Object... objects) {
|
||||||
|
String key = null,
|
||||||
|
defaultValue = null;
|
||||||
|
TextColor color = null;
|
||||||
|
TextStyle style = null;
|
||||||
|
|
||||||
|
for(Object obj : objects) {
|
||||||
|
if (obj instanceof String) {
|
||||||
|
if (key == null) {
|
||||||
|
key = (String) obj;
|
||||||
|
} else {
|
||||||
|
defaultValue = (String) obj;
|
||||||
|
}
|
||||||
|
} else if (obj instanceof TextColor) {
|
||||||
|
color = (TextColor) obj;
|
||||||
|
} else if (obj instanceof TextStyle) {
|
||||||
|
if (style == null) {
|
||||||
|
style = TextStyle.none();
|
||||||
|
}
|
||||||
|
style.merge((TextStyle) obj);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Arg arg = new Arg(key, defaultValue);
|
||||||
|
arg.setColor(color);
|
||||||
|
arg.setStyle(style);
|
||||||
|
this.elements.add(arg);
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TextTemplate build() {
|
||||||
|
return new TextTemplate(ImmutableList.copyOf(elements));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Builder builder() {
|
||||||
|
return new Builder();
|
||||||
|
}
|
||||||
|
}
|
||||||
34
core/src/main/java/mc/core/text/Title.java
Normal file
34
core/src/main/java/mc/core/text/Title.java
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
/*
|
||||||
|
* DmitriyMX <dimon550@gmail.com>
|
||||||
|
* 2018-06-24
|
||||||
|
*/
|
||||||
|
package mc.core.text;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.Setter;
|
||||||
|
import lombok.ToString;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
@ToString
|
||||||
|
public class Title {
|
||||||
|
private Text title = null;
|
||||||
|
private Text subtitle = null;
|
||||||
|
private Text textActionBar = null;
|
||||||
|
private Integer fadeInTime = null;
|
||||||
|
private Integer stayTime = null;
|
||||||
|
private Integer fadeOutTime = null;
|
||||||
|
private Boolean hide = null;
|
||||||
|
private Boolean reset = null;
|
||||||
|
|
||||||
|
public void clear() {
|
||||||
|
this.title = null;
|
||||||
|
this.subtitle = null;
|
||||||
|
this.textActionBar = null;
|
||||||
|
this.fadeInTime = null;
|
||||||
|
this.stayTime = null;
|
||||||
|
this.fadeOutTime = null;
|
||||||
|
this.hide = null;
|
||||||
|
this.reset = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
14
core/src/main/java/mc/core/time/AbstractTimeProcessor.java
Normal file
14
core/src/main/java/mc/core/time/AbstractTimeProcessor.java
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
/*
|
||||||
|
* DmitriyMX <dimon550@gmail.com>
|
||||||
|
* 2018-06-24
|
||||||
|
*/
|
||||||
|
package mc.core.time;
|
||||||
|
|
||||||
|
public abstract class AbstractTimeProcessor implements TimeProcessor {
|
||||||
|
private long worldAge = 0;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getWorldAge() {
|
||||||
|
return worldAge++;
|
||||||
|
}
|
||||||
|
}
|
||||||
14
core/src/main/java/mc/core/time/IdleTime.java
Normal file
14
core/src/main/java/mc/core/time/IdleTime.java
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
/*
|
||||||
|
* DmitriyMX <dimon550@gmail.com>
|
||||||
|
* 2018-05-01
|
||||||
|
*/
|
||||||
|
package mc.core.time;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class IdleTime extends AbstractTimeProcessor {
|
||||||
|
@Getter
|
||||||
|
private final long gameTime;
|
||||||
|
}
|
||||||
39
core/src/main/java/mc/core/time/RealTime.java
Normal file
39
core/src/main/java/mc/core/time/RealTime.java
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
/*
|
||||||
|
* DmitriyMX <dimon550@gmail.com>
|
||||||
|
* 2018-05-01
|
||||||
|
*/
|
||||||
|
package mc.core.time;
|
||||||
|
|
||||||
|
import java.util.Calendar;
|
||||||
|
|
||||||
|
public class RealTime extends AbstractTimeProcessor {
|
||||||
|
private static final long DIFF = 21600L;
|
||||||
|
private static final long HOUR24 = 86400L;
|
||||||
|
private final Calendar calendar = Calendar.getInstance();
|
||||||
|
private long lastUpdate = 0;
|
||||||
|
private long gameTime;
|
||||||
|
|
||||||
|
private void calcRealTime() {
|
||||||
|
lastUpdate = System.currentTimeMillis();
|
||||||
|
|
||||||
|
calendar.setTimeInMillis(lastUpdate);
|
||||||
|
calendar.set(Calendar.HOUR_OF_DAY, 0);
|
||||||
|
calendar.set(Calendar.MINUTE, 0);
|
||||||
|
calendar.set(Calendar.SECOND, 0);
|
||||||
|
calendar.set(Calendar.MILLISECOND, 0);
|
||||||
|
|
||||||
|
long time = (lastUpdate - calendar.getTimeInMillis())/1000;
|
||||||
|
if (time < DIFF) time += HOUR24;
|
||||||
|
|
||||||
|
gameTime = (long) ((time - DIFF) / 3.6);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getGameTime() {
|
||||||
|
if ((System.currentTimeMillis() - lastUpdate) > 1000) {
|
||||||
|
calcRealTime();
|
||||||
|
}
|
||||||
|
|
||||||
|
return gameTime;
|
||||||
|
}
|
||||||
|
}
|
||||||
21
core/src/main/java/mc/core/time/TimePerTick.java
Normal file
21
core/src/main/java/mc/core/time/TimePerTick.java
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
/*
|
||||||
|
* DmitriyMX <dimon550@gmail.com>
|
||||||
|
* 2018-05-01
|
||||||
|
*/
|
||||||
|
package mc.core.time;
|
||||||
|
|
||||||
|
public class TimePerTick extends AbstractTimeProcessor {
|
||||||
|
private long gameTime;
|
||||||
|
|
||||||
|
public void setStartGameTime(long value) {
|
||||||
|
gameTime = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getGameTime() {
|
||||||
|
gameTime++;
|
||||||
|
if (gameTime > 24000) gameTime = 0;
|
||||||
|
|
||||||
|
return gameTime;
|
||||||
|
}
|
||||||
|
}
|
||||||
10
core/src/main/java/mc/core/time/TimeProcessor.java
Normal file
10
core/src/main/java/mc/core/time/TimeProcessor.java
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
/*
|
||||||
|
* DmitriyMX <dimon550@gmail.com>
|
||||||
|
* 2018-05-01
|
||||||
|
*/
|
||||||
|
package mc.core.time;
|
||||||
|
|
||||||
|
public interface TimeProcessor {
|
||||||
|
long getGameTime();
|
||||||
|
long getWorldAge();
|
||||||
|
}
|
||||||
22
core/src/main/java/mc/core/utils/CompactedCoords.java
Normal file
22
core/src/main/java/mc/core/utils/CompactedCoords.java
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
package mc.core.utils;
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
public class CompactedCoords {
|
||||||
|
public static int compressXZ(int x, int z) {
|
||||||
|
if (x < Short.MIN_VALUE || x > Short.MAX_VALUE ||
|
||||||
|
z < Short.MIN_VALUE || z > Short.MAX_VALUE) {
|
||||||
|
log.warn("Coord over range: [{},{}]", x, z);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ((x & 0xFFFF) << 16) | (z & 0xFFFF);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int[] uncompressXZ(int compactValue) {
|
||||||
|
return new int[]{
|
||||||
|
compactValue >> 16,
|
||||||
|
(compactValue & 0x8000) > 0 ? compactValue | 0xFFFF0000 : compactValue & 0xFFFF
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
65
core/src/main/java/mc/core/utils/NibbleArray.java
Normal file
65
core/src/main/java/mc/core/utils/NibbleArray.java
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
package mc.core.utils;
|
||||||
|
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import mc.core.world.block.BlockLocation;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Сжатый массив значений 0-15
|
||||||
|
*/
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class NibbleArray {
|
||||||
|
private final byte[] data;
|
||||||
|
|
||||||
|
public NibbleArray(int capacity) {
|
||||||
|
this.data = new byte[capacity];
|
||||||
|
}
|
||||||
|
|
||||||
|
public NibbleArray() {
|
||||||
|
this.data = new byte[2048];
|
||||||
|
}
|
||||||
|
|
||||||
|
private int coordsToIndex(int x, int y, int z) {
|
||||||
|
return y << 8 | z << 4 | x;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int nibbleIndex(int index) {
|
||||||
|
return index >> 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isLowerNibble(int index) {
|
||||||
|
return (index & 1) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int get(BlockLocation location) {
|
||||||
|
return get(location.getX(), location.getY(), location.getZ());
|
||||||
|
}
|
||||||
|
|
||||||
|
public int get(int x, int y, int z) {
|
||||||
|
final int idx = coordsToIndex(x, y, z);
|
||||||
|
|
||||||
|
final int ni = nibbleIndex(idx);
|
||||||
|
return isLowerNibble(idx) ? this.data[ni] & 0x0F : this.data[ni] >> 4 & 0x0F;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void set(BlockLocation location, int value) {
|
||||||
|
set(location.getX(), location.getY(), location.getZ(), value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void set(int x, int y, int z, int value) {
|
||||||
|
if (value < 0) value = 0;
|
||||||
|
else if (value > 15) value = 15;
|
||||||
|
|
||||||
|
final int idx = coordsToIndex(x, y, z);
|
||||||
|
final int ni = nibbleIndex(idx);
|
||||||
|
|
||||||
|
if (isLowerNibble(idx)) {
|
||||||
|
this.data[ni] = (byte)(value);
|
||||||
|
} else {
|
||||||
|
this.data[ni] = (byte)(this.data[ni] | value << 4);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] getRawData() {
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
}
|
||||||
80
core/src/main/java/mc/core/world/Biome.java
Normal file
80
core/src/main/java/mc/core/world/Biome.java
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
package mc.core.world;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public enum Biome {
|
||||||
|
OCEAN(0),
|
||||||
|
PLAINS(1),
|
||||||
|
DESERT(2),
|
||||||
|
EXTREME_HILLS(3),
|
||||||
|
FOREST(4),
|
||||||
|
TAIGA(5),
|
||||||
|
SWAMPLAND(6),
|
||||||
|
RIVER(7),
|
||||||
|
HELL(8),
|
||||||
|
SKY(9),
|
||||||
|
FROZEN_OCEAN(10),
|
||||||
|
FROZEN_RIVER(11),
|
||||||
|
ICE_PLAINS(12),
|
||||||
|
ICE_MOUNTAINS(13),
|
||||||
|
MUSHROOM_ISLAND(14),
|
||||||
|
MUSHROOM_ISLAND_SHORE(15),
|
||||||
|
BEACH(16),
|
||||||
|
DESERT_HILLS(17),
|
||||||
|
FOREST_HILLS(18),
|
||||||
|
TAIGA_HILLS(19),
|
||||||
|
EXTREME_HILLS_EDGE(20),
|
||||||
|
JUNGLE(21),
|
||||||
|
JUNGLE_HILLS(22),
|
||||||
|
JUNGLE_EDGE(23),
|
||||||
|
DEEP_OCEAN(24),
|
||||||
|
STONE_BEACH(25),
|
||||||
|
COLD_BEACH(26),
|
||||||
|
BIRCH_FOREST(27),
|
||||||
|
BIRCH_FOREST_HILLS(28),
|
||||||
|
ROOFED_FOREST(29),
|
||||||
|
TAIGA_COLD(30),
|
||||||
|
TAIGA_COLD_HILLS(31),
|
||||||
|
REDWOOD_TAIGA(32),
|
||||||
|
REDWOOD_TAIGA_HILLS(33),
|
||||||
|
EXTREME_HILLS_WITH_TREES(34),
|
||||||
|
SAVANNA(35),
|
||||||
|
SAVANNA_ROCK(36),
|
||||||
|
MESA(37),
|
||||||
|
MESA_ROCK(38),
|
||||||
|
MESA_CLEAR_ROCK(39),
|
||||||
|
VOID(127),
|
||||||
|
MUTATED_PLAINS(129),
|
||||||
|
MUTATED_DESERT(130),
|
||||||
|
MUTATED_EXTREME_HILLS(131),
|
||||||
|
MUTATED_FOREST(132),
|
||||||
|
MUTATED_TAIGA(133),
|
||||||
|
MUTATED_SWAMPLAND(134),
|
||||||
|
MUTATED_ICE_FLATS(140),
|
||||||
|
MUTATED_JUNGLE(149),
|
||||||
|
MUTATED_JUNGLE_EDGE(151),
|
||||||
|
MUTATED_BIRCH_FOREST(155),
|
||||||
|
MUTATED_BIRCH_FOREST_HILLS(156),
|
||||||
|
MUTATED_ROOFED_FOREST(157),
|
||||||
|
MUTATED_TAIGA_COLD(158),
|
||||||
|
MUTATED_REDWOOD_TAIGA(160),
|
||||||
|
MUTATED_REDWOOD_TAIGA_HILLS(161),
|
||||||
|
MUTATED_EXTREME_HILLS_WITH_TREES(162),
|
||||||
|
MUTATED_SAVANNA(163),
|
||||||
|
MUTATED_SAVANNA_ROCK(164),
|
||||||
|
MUTATED_MESA(165),
|
||||||
|
MUTATED_MESA_ROCK(166),
|
||||||
|
MUTATED_MESA_CLEAR_ROCK(167);
|
||||||
|
|
||||||
|
public static Biome getById(final int id) {
|
||||||
|
return Arrays.stream(Biome.values()).filter(biome -> biome.id == id).findFirst().orElse(Biome.PLAINS);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
private final int id;
|
||||||
|
}
|
||||||
63
core/src/main/java/mc/core/world/World.java
Normal file
63
core/src/main/java/mc/core/world/World.java
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
package mc.core.world;
|
||||||
|
|
||||||
|
import mc.core.EntityLocation;
|
||||||
|
import mc.core.world.block.Block;
|
||||||
|
import mc.core.world.block.BlockLocation;
|
||||||
|
import mc.core.world.chunk.Chunk;
|
||||||
|
|
||||||
|
public interface World {
|
||||||
|
String getName();
|
||||||
|
WorldType getType();
|
||||||
|
|
||||||
|
EntityLocation getSpawn();
|
||||||
|
|
||||||
|
void setSpawn(EntityLocation location);
|
||||||
|
|
||||||
|
default void setSpawn(double x, double y, double z, float yaw, float pitch) {
|
||||||
|
setSpawn(new EntityLocation(x, y, z, yaw, pitch));
|
||||||
|
}
|
||||||
|
|
||||||
|
default void setSpawn(double x, double y, double z) {
|
||||||
|
setSpawn(x, y, z, 0f, 0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Получить чанк по его координатам
|
||||||
|
* @param x chunk X
|
||||||
|
* @param z chunk Z
|
||||||
|
* @return {@link mc.core.world.chunk.Chunk}
|
||||||
|
*/
|
||||||
|
Chunk getChunk(int x, int z);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Получить чанк по глобальным координатам блока
|
||||||
|
* @param location {@link BlockLocation}
|
||||||
|
* @return {@link Chunk}
|
||||||
|
*/
|
||||||
|
default Chunk getChunk(BlockLocation location) {
|
||||||
|
return getChunk(location.getX() >> 4, location.getZ() >> 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Установить чанк по координатам
|
||||||
|
* @param x глобальный X
|
||||||
|
* @param z глобальный Z
|
||||||
|
* @param chunk {@link mc.core.world.chunk.Chunk}
|
||||||
|
*/
|
||||||
|
void setChunk(int x, int z, Chunk chunk);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Получить блок по его координатам
|
||||||
|
* @param x X
|
||||||
|
* @param y Y
|
||||||
|
* @param z Z
|
||||||
|
* @return {@link Block}
|
||||||
|
*/
|
||||||
|
Block getBlock(int x, int y, int z);
|
||||||
|
|
||||||
|
default Block getBlock(BlockLocation location) {
|
||||||
|
return getBlock(location.getX(), location.getY(), location.getZ());
|
||||||
|
}
|
||||||
|
|
||||||
|
void setBlock(Block block);
|
||||||
|
}
|
||||||
13
core/src/main/java/mc/core/world/WorldType.java
Normal file
13
core/src/main/java/mc/core/world/WorldType.java
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
package mc.core.world;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
@Getter
|
||||||
|
public enum WorldType {
|
||||||
|
DEFAULT("default"),
|
||||||
|
FLAT("flat");
|
||||||
|
|
||||||
|
private final String name;
|
||||||
|
}
|
||||||
23
core/src/main/java/mc/core/world/block/AbstractBlock.java
Normal file
23
core/src/main/java/mc/core/world/block/AbstractBlock.java
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
package mc.core.world.block;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.Setter;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
public abstract class AbstractBlock implements Block {
|
||||||
|
@Setter
|
||||||
|
private BlockLocation location;
|
||||||
|
private int light = 0;
|
||||||
|
private final BlockType type;
|
||||||
|
|
||||||
|
protected AbstractBlock(BlockType type) {
|
||||||
|
this.type = type;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setLight(int light) {
|
||||||
|
if (light > 15) this.light = 15;
|
||||||
|
else if (light < 0) this.light = 0;
|
||||||
|
else this.light = light;
|
||||||
|
}
|
||||||
|
}
|
||||||
17
core/src/main/java/mc/core/world/block/Block.java
Normal file
17
core/src/main/java/mc/core/world/block/Block.java
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
package mc.core.world.block;
|
||||||
|
|
||||||
|
import com.flowpowered.nbt.CompoundTag;
|
||||||
|
|
||||||
|
public interface Block {
|
||||||
|
int getLight();
|
||||||
|
void setLight(int light);
|
||||||
|
BlockType getType();
|
||||||
|
BlockLocation getLocation();
|
||||||
|
|
||||||
|
default CompoundTag getNBTData() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
default void setNBTData(CompoundTag nbtData) {
|
||||||
|
}
|
||||||
|
}
|
||||||
18
core/src/main/java/mc/core/world/block/BlockFactory.java
Normal file
18
core/src/main/java/mc/core/world/block/BlockFactory.java
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
package mc.core.world.block;
|
||||||
|
|
||||||
|
//TODO избавится от этого "аппендикса"
|
||||||
|
@Deprecated
|
||||||
|
public class BlockFactory {
|
||||||
|
|
||||||
|
public Block create(BlockType blockType, int x, int y, int z) {
|
||||||
|
return new EmbeddedBlock(blockType, x, y, z);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** For first-time generation */
|
||||||
|
private class EmbeddedBlock extends AbstractBlock {
|
||||||
|
EmbeddedBlock(BlockType type, int x, int y, int z) {
|
||||||
|
super(type);
|
||||||
|
setLocation(new BlockLocation(x, y, z));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
32
core/src/main/java/mc/core/world/block/BlockLocation.java
Normal file
32
core/src/main/java/mc/core/world/block/BlockLocation.java
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
package mc.core.world.block;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
@Data
|
||||||
|
public class BlockLocation implements Cloneable {
|
||||||
|
private int x, y, z;
|
||||||
|
|
||||||
|
public static BlockLocation ZERO() {
|
||||||
|
return new BlockLocation(0,0,0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setXYZ(int x, int y, int z) {
|
||||||
|
this.x = x;
|
||||||
|
this.y = y;
|
||||||
|
this.z = z;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BlockLocation clone() {
|
||||||
|
try {
|
||||||
|
return (BlockLocation) super.clone();
|
||||||
|
} catch (CloneNotSupportedException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
return ZERO();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
515
core/src/main/java/mc/core/world/block/BlockType.java
Normal file
515
core/src/main/java/mc/core/world/block/BlockType.java
Normal file
@@ -0,0 +1,515 @@
|
|||||||
|
package mc.core.world.block;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public enum BlockType {
|
||||||
|
|
||||||
|
AIR(0, 0),
|
||||||
|
|
||||||
|
STONE (1, 0),
|
||||||
|
STONE_MOSS(48, 0),
|
||||||
|
|
||||||
|
GRANITE (1, 1),
|
||||||
|
POLISHED_GRANITE(1, 2),
|
||||||
|
|
||||||
|
DIORITE(1, 3),
|
||||||
|
ANDESITE(1, 5),
|
||||||
|
|
||||||
|
GRASS(2, 0),
|
||||||
|
PATH(208, 0),
|
||||||
|
|
||||||
|
DIRT(3, 0),
|
||||||
|
|
||||||
|
/** Farmland, Dry, Moisture 0 */
|
||||||
|
FARMLAND (60, 0),
|
||||||
|
/** Farmland, Dry, Moisture 1 */
|
||||||
|
FARMLAND_1(60, 1),
|
||||||
|
/** Farmland, Dry, Moisture 2 */
|
||||||
|
FARMLAND_2(60, 2),
|
||||||
|
/** Farmland, Dry, Moisture 3 */
|
||||||
|
FARMLAND_3(60, 3),
|
||||||
|
/** Farmland, Dry, Moisture 4 */
|
||||||
|
FARMLAND_4(60, 4),
|
||||||
|
/** Farmland, Dry, Moisture 5 */
|
||||||
|
FARMLAND_5(60, 5),
|
||||||
|
/** Farmland, Dry, Moisture 6 */
|
||||||
|
FARMLAND_6(60, 6),
|
||||||
|
/** Farmland, Dry, Moisture 7 */
|
||||||
|
FARMLAND_7(60, 7),
|
||||||
|
|
||||||
|
COBBLESTONE(4, 0),
|
||||||
|
BEDROCK(7, 0),
|
||||||
|
|
||||||
|
/** Water, flowing, Level 7 (Source) */
|
||||||
|
WATER_FLOWING (8, 0),
|
||||||
|
/** Water, flowing, Level 6 */
|
||||||
|
WATER_FLOWING_1 (8, 1),
|
||||||
|
/** Water, flowing, Level 5 */
|
||||||
|
WATER_FLOWING_2 (8, 2),
|
||||||
|
/** Water, flowing, Level 4 */
|
||||||
|
WATER_FLOWING_3 (8, 3),
|
||||||
|
/** Water, flowing, Level 3 */
|
||||||
|
WATER_FLOWING_4 (8, 4),
|
||||||
|
/** Water, flowing, Level 2 */
|
||||||
|
WATER_FLOWING_5 (8, 5),
|
||||||
|
/** Water, flowing, Level 1 */
|
||||||
|
WATER_FLOWING_6 (8, 6),
|
||||||
|
/** Water, flowing, Level 0 */
|
||||||
|
WATER_FLOWING_7 (8, 7),
|
||||||
|
/** Water, flowing, Level 15 */
|
||||||
|
WATER_FLOWING_8 (8, 8),
|
||||||
|
/** Water, flowing, Level 14 */
|
||||||
|
WATER_FLOWING_9 (8, 9),
|
||||||
|
/** Water, flowing, Level 13 */
|
||||||
|
WATER_FLOWING_10(8, 10),
|
||||||
|
/** Water, flowing, Level 12 */
|
||||||
|
WATER_FLOWING_11(8, 11),
|
||||||
|
/** Water, flowing, Level 11 */
|
||||||
|
WATER_FLOWING_12(8, 12),
|
||||||
|
/** Water, flowing, Level 10 */
|
||||||
|
WATER_FLOWING_13(8, 13),
|
||||||
|
/** Water, flowing, Level 9 */
|
||||||
|
WATER_FLOWING_14(8, 14),
|
||||||
|
/** Water, flowing, Level 8 */
|
||||||
|
WATER_FLOWING_15(8, 15),
|
||||||
|
|
||||||
|
/** Water, still, Level 7 (Source) */
|
||||||
|
WATER_STILL (9, 0),
|
||||||
|
/** Water, still, Level 6 */
|
||||||
|
WATER_STILL_1 (9, 1),
|
||||||
|
/** Water, still, Level 5 */
|
||||||
|
WATER_STILL_2 (9, 2),
|
||||||
|
/** Water, still, Level 4 */
|
||||||
|
WATER_STILL_3 (9, 3),
|
||||||
|
/** Water, still, Level 3 */
|
||||||
|
WATER_STILL_4 (9, 4),
|
||||||
|
/** Water, still, Level 2 */
|
||||||
|
WATER_STILL_5 (9, 5),
|
||||||
|
/** Water, still, Level 1 */
|
||||||
|
WATER_STILL_6 (9, 6),
|
||||||
|
/** Water, still, Level 0 */
|
||||||
|
WATER_STILL_7 (9, 7),
|
||||||
|
/** Water, still, Level 15 */
|
||||||
|
WATER_STILL_8 (9, 8),
|
||||||
|
/** Water, still, Level 14 */
|
||||||
|
WATER_STILL_9 (9, 9),
|
||||||
|
/** Water, still, Level 13 */
|
||||||
|
WATER_STILL_10(9, 10),
|
||||||
|
/** Water, still, Level 12 */
|
||||||
|
WATER_STILL_11(9, 11),
|
||||||
|
/** Water, still, Level 11 */
|
||||||
|
WATER_STILL_12(9, 12),
|
||||||
|
/** Water, still, Level 10 */
|
||||||
|
WATER_STILL_13(9, 13),
|
||||||
|
/** Water, still, Level 9 */
|
||||||
|
WATER_STILL_14(9, 14),
|
||||||
|
/** Water, still, Level 8 */
|
||||||
|
WATER_STILL_15(9, 15),
|
||||||
|
|
||||||
|
/** Lava, flowing, Level 7 (Source) */
|
||||||
|
LAVA_FLOWING (10, 0),
|
||||||
|
/** Lava, flowing, Level 6 */
|
||||||
|
LAVA_FLOWING_1 (10, 1),
|
||||||
|
/** Lava, flowing, Level 5 */
|
||||||
|
LAVA_FLOWING_2 (10, 2),
|
||||||
|
/** Lava, flowing, Level 4 */
|
||||||
|
LAVA_FLOWING_3 (10, 3),
|
||||||
|
/** Lava, flowing, Level 3 */
|
||||||
|
LAVA_FLOWING_4 (10, 4),
|
||||||
|
/** Lava, flowing, Level 2 */
|
||||||
|
LAVA_FLOWING_5 (10, 5),
|
||||||
|
/** Lava, flowing, Level 1 */
|
||||||
|
LAVA_FLOWING_6 (10, 6),
|
||||||
|
/** Lava, flowing, Level 0 */
|
||||||
|
LAVA_FLOWING_7 (10, 7),
|
||||||
|
/** Lava, flowing, Level 15 */
|
||||||
|
LAVA_FLOWING_8 (10, 8),
|
||||||
|
/** Lava, flowing, Level 14 */
|
||||||
|
LAVA_FLOWING_9 (10, 9),
|
||||||
|
/** Lava, flowing, Level 13 */
|
||||||
|
LAVA_FLOWING_10(10, 10),
|
||||||
|
/** Lava, flowing, Level 12 */
|
||||||
|
LAVA_FLOWING_11(10, 11),
|
||||||
|
/** Lava, flowing, Level 11 */
|
||||||
|
LAVA_FLOWING_12(10, 12),
|
||||||
|
/** Lava, flowing, Level 10 */
|
||||||
|
LAVA_FLOWING_13(10, 13),
|
||||||
|
/** Lava, flowing, Level 9 */
|
||||||
|
LAVA_FLOWING_14(10, 14),
|
||||||
|
/** Lava, flowing, Level 8 */
|
||||||
|
LAVA_FLOWING_15(10, 15),
|
||||||
|
|
||||||
|
/** Lava, still, Level 7 (Source) */
|
||||||
|
LAVA_STILL (11, 0),
|
||||||
|
/** Lava, still, Level 6 */
|
||||||
|
LAVA_STILL_1 (11, 1),
|
||||||
|
/** Lava, still, Level 5 */
|
||||||
|
LAVA_STILL_2 (11, 2),
|
||||||
|
/** Lava, still, Level 4 */
|
||||||
|
LAVA_STILL_3 (11, 3),
|
||||||
|
/** Lava, still, Level 3 */
|
||||||
|
LAVA_STILL_4 (11, 4),
|
||||||
|
/** Lava, still, Level 2 */
|
||||||
|
LAVA_STILL_5 (11, 5),
|
||||||
|
/** Lava, still, Level 1 */
|
||||||
|
LAVA_STILL_6 (11, 6),
|
||||||
|
/** Lava, still, Level 0 */
|
||||||
|
LAVA_STILL_7 (11, 7),
|
||||||
|
/** Lava, still, Level 15 */
|
||||||
|
LAVA_STILL_8 (11, 8),
|
||||||
|
/** Lava, still, Level 14 */
|
||||||
|
LAVA_STILL_9 (11, 9),
|
||||||
|
/** Lava, still, Level 13 */
|
||||||
|
LAVA_STILL_10(11, 10),
|
||||||
|
/** Lava, still, Level 12 */
|
||||||
|
LAVA_STILL_11(11, 11),
|
||||||
|
/** Lava, still, Level 11 */
|
||||||
|
LAVA_STILL_12(11, 12),
|
||||||
|
/** Lava, still, Level 10 */
|
||||||
|
LAVA_STILL_13(11, 13),
|
||||||
|
/** Lava, still, Level 9 */
|
||||||
|
LAVA_STILL_14(11, 14),
|
||||||
|
/** Lava, still, Level 8 */
|
||||||
|
LAVA_STILL_15(11, 15),
|
||||||
|
|
||||||
|
SAND (12, 0),
|
||||||
|
SANDSTONE(24, 0),
|
||||||
|
|
||||||
|
GRAVEL(13, 0),
|
||||||
|
|
||||||
|
ORE_GOLD (14, 0),
|
||||||
|
ORE_IRON (15, 0),
|
||||||
|
ORE_COAL (16, 0),
|
||||||
|
ORE_LAPIS (21, 0),
|
||||||
|
ORE_DIAMOND (56, 0),
|
||||||
|
ORE_REDSTONE (73, 0),
|
||||||
|
ORE_GLOWING_REDSTONE(74, 0),
|
||||||
|
ORE_EMERALD (129, 0),
|
||||||
|
|
||||||
|
// Upright
|
||||||
|
WOOD_OAK (17, 0),
|
||||||
|
WOOD_SPRUCE (17, 1),
|
||||||
|
WOOD_BIRCH (17, 2),
|
||||||
|
WOOD_JUNGLE (17, 3),
|
||||||
|
WOOD_ACACIA (162, 0),
|
||||||
|
WOOD_OAK_DARK(162, 1),
|
||||||
|
|
||||||
|
// East/West
|
||||||
|
WOOD_OAK_EW (17, 4),
|
||||||
|
WOOD_SPRUCE_EW (17, 5),
|
||||||
|
WOOD_BIRCH_EW (17, 6),
|
||||||
|
WOOD_JUNGLE_EW (17, 7),
|
||||||
|
WOOD_ACACIA_EW (162, 4),
|
||||||
|
WOOD_OAK_DARK_EW(162, 5),
|
||||||
|
|
||||||
|
// North/South
|
||||||
|
WOOD_OAK_NS (17, 8),
|
||||||
|
WOOD_SPRUCE_NS (17, 9),
|
||||||
|
WOOD_BIRCH_NS (17, 10),
|
||||||
|
WOOD_JUNGLE_NS (17, 11),
|
||||||
|
WOOD_ACACIA_NS (162, 8),
|
||||||
|
WOOD_OAK_DARK_NS(162, 9),
|
||||||
|
|
||||||
|
PLANK_WOOD_OAK (5, 0),
|
||||||
|
PLANK_WOOD_SPRUCE (5, 1),
|
||||||
|
PLANK_WOOD_BIRCH (5, 2),
|
||||||
|
PLANK_WOOD_JUNGLE (5, 3),
|
||||||
|
PLANK_WOOD_ACACIA (5, 4),
|
||||||
|
PLANK_WOOD_OAK_DARK(5, 5),
|
||||||
|
|
||||||
|
DOOR_LOW_OAK_EAST(64, 0),
|
||||||
|
DOOR_LOW_OAK_SOUTH(64, 1),
|
||||||
|
DOOR_LOW_OAK_WEST(64, 2),
|
||||||
|
DOOR_LOW_OAK_NORTH(64, 3),
|
||||||
|
DOOR_LOW_OAK_EAST_OPENED(64, 4),
|
||||||
|
DOOR_LOW_OAK_SOUTH_OPENED(64, 5),
|
||||||
|
DOOR_LOW_OAK_WEST_OPENED(64, 6),
|
||||||
|
DOOR_LOW_OAK_NORTH_OPENED(64, 7),
|
||||||
|
|
||||||
|
DOOR_UP_OAK_LEFT(64, 8),
|
||||||
|
DOOR_UP_OAK_RIGHT(64, 9),
|
||||||
|
DOOR_UP_OAK_LEFT_POWERED(64, 10),
|
||||||
|
DOOR_UP_OAK_RIGHT_POWERED(64, 11),
|
||||||
|
DOOR_UP_OAK_12(64, 12),
|
||||||
|
DOOR_UP_OAK_13(64, 13),
|
||||||
|
DOOR_UP_OAK_14(64, 14),
|
||||||
|
DOOR_UP_OAK_15(64, 15),
|
||||||
|
|
||||||
|
FENCE_OAK(85, 0),
|
||||||
|
|
||||||
|
// Decay after Tree Update
|
||||||
|
LEAVES_OAK (18, 0),
|
||||||
|
LEAVES_SPRUCE (18, 1),
|
||||||
|
LEAVES_BIRCH (18, 2),
|
||||||
|
LEAVES_JUNGLE (18, 3),
|
||||||
|
LEAVES_ACACIA (161, 0),
|
||||||
|
LEAVES_OAK_DARK(161, 1),
|
||||||
|
|
||||||
|
// No Decay
|
||||||
|
LEAVES_OAK2 (18, 4),
|
||||||
|
LEAVES_SPRUCE2 (18, 5),
|
||||||
|
LEAVES_BIRCH2 (18, 6),
|
||||||
|
LEAVES_JUNGLE2 (18, 7),
|
||||||
|
LEAVES_ACACIA2 (161, 4),
|
||||||
|
LEAVES_OAK_DARK2(161, 5),
|
||||||
|
|
||||||
|
// Decay
|
||||||
|
LEAVES_OAK3 (18, 8),
|
||||||
|
LEAVES_SPRUCE3 (18, 9),
|
||||||
|
LEAVES_BIRCH3 (18, 10),
|
||||||
|
LEAVES_JUNGLE3 (18, 11),
|
||||||
|
LEAVES_ACACIA3 (161, 8),
|
||||||
|
LEAVES_OAK_DARK3(161, 9),
|
||||||
|
|
||||||
|
// No decay, unused
|
||||||
|
@Deprecated
|
||||||
|
LEAVES_OAK4 (18, 12),
|
||||||
|
@Deprecated
|
||||||
|
LEAVES_SPRUCE4 (18, 13),
|
||||||
|
@Deprecated
|
||||||
|
LEAVES_BIRCH4 (18, 14),
|
||||||
|
@Deprecated
|
||||||
|
LEAVES_JUNGLE4 (18, 15),
|
||||||
|
@Deprecated
|
||||||
|
LEAVES_ACACIA4 (161, 12),
|
||||||
|
@Deprecated
|
||||||
|
LEAVES_OAK_DARK4(161, 13),
|
||||||
|
|
||||||
|
COBWEB(30, 0),
|
||||||
|
TALLGRASS(31, 1),
|
||||||
|
DANDELION(37, 0),
|
||||||
|
|
||||||
|
FLOWER_POPPY (38, 0),
|
||||||
|
FLOWER_BLUE_ORCHID (38, 1),
|
||||||
|
FLOWER_ALLIUM (38, 2),
|
||||||
|
FLOWER_AZURE_BLUET (38, 3),
|
||||||
|
FLOWER_TULIP_RED (38, 4),
|
||||||
|
FLOWER_TULIP_ORANGE(38, 5),
|
||||||
|
FLOWER_TULIP_WHITE (38, 6),
|
||||||
|
FLOWER_TULIP_PINK (38, 7),
|
||||||
|
FLOWER_OXEYE_DAISY (38, 8),
|
||||||
|
|
||||||
|
MUSHROOM_BROWN(39, 0),
|
||||||
|
MUSHROOM_RED (40, 0),
|
||||||
|
|
||||||
|
MUSHROOM_BLOCK_BROWN_ALL_INSIDE(99, 0),
|
||||||
|
MUSHROOM_BLOCK_BROWN_NW (99, 1),
|
||||||
|
MUSHROOM_BLOCK_BROWN_NORT (99, 2),
|
||||||
|
MUSHROOM_BLOCK_BROWN_NE (99, 3),
|
||||||
|
MUSHROOM_BLOCK_BROWN_WEST (99, 4),
|
||||||
|
MUSHROOM_BLOCK_BROWN_CENTER (99, 5),
|
||||||
|
MUSHROOM_BLOCK_BROWN_EAST (99, 6),
|
||||||
|
MUSHROOM_BLOCK_BROWN_SW (99, 7),
|
||||||
|
MUSHROOM_BLOCK_BROWN_SOUTH (99, 8),
|
||||||
|
MUSHROOM_BLOCK_BROWN_SE (99, 9),
|
||||||
|
MUSHROOM_BLOCK_BROWN_STEM (99, 10),
|
||||||
|
MUSHROOM_BLOCK_BROWN_ALL_OUSIDE(99, 14),
|
||||||
|
MUSHROOM_BLOCK_BROWN_ALL_STEM (99, 15),
|
||||||
|
|
||||||
|
MUSHROOM_BLOCK_RED_ALL_INSIDE(100, 0),
|
||||||
|
MUSHROOM_BLOCK_RED_NW (100, 1),
|
||||||
|
MUSHROOM_BLOCK_RED_NORT (100, 2),
|
||||||
|
MUSHROOM_BLOCK_RED_NE (100, 3),
|
||||||
|
MUSHROOM_BLOCK_RED_WEST (100, 4),
|
||||||
|
MUSHROOM_BLOCK_RED_CENTER (100, 5),
|
||||||
|
MUSHROOM_BLOCK_RED_EAST (100, 6),
|
||||||
|
MUSHROOM_BLOCK_RED_SW (100, 7),
|
||||||
|
MUSHROOM_BLOCK_RED_SOUTH (100, 8),
|
||||||
|
MUSHROOM_BLOCK_RED_SE (100, 9),
|
||||||
|
MUSHROOM_BLOCK_RED_STEM (100, 10),
|
||||||
|
MUSHROOM_BLOCK_RED_ALL_OUSIDE(100, 14),
|
||||||
|
MUSHROOM_BLOCK_RED_ALL_STEM (100, 15),
|
||||||
|
|
||||||
|
OBSIDIAN(49, 0),
|
||||||
|
|
||||||
|
TORCH_EAST (50, 1),
|
||||||
|
TORCH_WEST (50, 2),
|
||||||
|
TORCH_SOUTH(50, 3),
|
||||||
|
TORCH_NORTH(50, 4),
|
||||||
|
TORCH_UP (50, 5),
|
||||||
|
|
||||||
|
MONSTER_SPAWNER(52, 0),
|
||||||
|
|
||||||
|
CHEST_NORTH(54, 2, "minecraft:chest"),
|
||||||
|
CHEST_SOUTH(54, 3, "minecraft:chest"),
|
||||||
|
CHEST_WEST (54, 4, "minecraft:chest"),
|
||||||
|
CHEST_EAST (54, 5, "minecraft:chest"),
|
||||||
|
|
||||||
|
RAIL_NS (66, 0),
|
||||||
|
RAIL_EW (66, 1),
|
||||||
|
RAIL_ASCENDING_EAST (66, 2),
|
||||||
|
RAIL_ASCENDING_WEST (66, 3),
|
||||||
|
RAIL_ASCENDING_NORTH(66, 4),
|
||||||
|
RAIL_ASCENDING_SOUTH(66, 5),
|
||||||
|
RAIL_CURVED_SE (66, 6),
|
||||||
|
RAIL_CURVED_SW (66, 7),
|
||||||
|
RAIL_CURVED_NW (66, 8),
|
||||||
|
RAIL_CURVED_NE (66, 9),
|
||||||
|
|
||||||
|
SNOW(78, 0),
|
||||||
|
|
||||||
|
CLAY(82, 0),
|
||||||
|
CLAY_HARDENED(172, 0),
|
||||||
|
|
||||||
|
/** Sugar canes (Age 0) */
|
||||||
|
SUGAR_CANES(83, 0),
|
||||||
|
/** Sugar canes (Age 1) */
|
||||||
|
SUGAR_CANES_1(83, 1),
|
||||||
|
/** Sugar canes (Age 2) */
|
||||||
|
SUGAR_CANES_2(83, 2),
|
||||||
|
/** Sugar canes (Age 3) */
|
||||||
|
SUGAR_CANES_3(83, 3),
|
||||||
|
/** Sugar canes (Age 4) */
|
||||||
|
SUGAR_CANES_4(83, 4),
|
||||||
|
/** Sugar canes (Age 5) */
|
||||||
|
SUGAR_CANES_5(83, 5),
|
||||||
|
/** Sugar canes (Age 6) */
|
||||||
|
SUGAR_CANES_6(83, 6),
|
||||||
|
/** Sugar canes (Age 7) */
|
||||||
|
SUGAR_CANES_7(83, 7),
|
||||||
|
/** Sugar canes (Age 8) */
|
||||||
|
SUGAR_CANES_8(83, 8),
|
||||||
|
/** Sugar canes (Age 9) */
|
||||||
|
SUGAR_CANES_9(83, 9),
|
||||||
|
/** Sugar canes (Age 10) */
|
||||||
|
SUGAR_CANES_10(83, 10),
|
||||||
|
/** Sugar canes (Age 11) */
|
||||||
|
SUGAR_CANES_11(83, 11),
|
||||||
|
/** Sugar canes (Age 12) */
|
||||||
|
SUGAR_CANES_12(83, 12),
|
||||||
|
/** Sugar canes (Age 13) */
|
||||||
|
SUGAR_CANES_13(83, 13),
|
||||||
|
/** Sugar canes (Age 14) */
|
||||||
|
SUGAR_CANES_14(83, 14),
|
||||||
|
/** Sugar canes (Age 15) */
|
||||||
|
SUGAR_CANES_15(83, 15),
|
||||||
|
|
||||||
|
PUMPKIN_SOUTH(86, 0),
|
||||||
|
PUMPKIN_WEST (86, 1),
|
||||||
|
PUMPKIN_NORTH(86, 2),
|
||||||
|
PUMPKIN_EAST (86, 3),
|
||||||
|
|
||||||
|
STONE_MONSTER_EGG(97, 0),
|
||||||
|
|
||||||
|
GLASS_PANE(102, 0),
|
||||||
|
|
||||||
|
VINE (106, 0),
|
||||||
|
VINE_SOUTH(106, 1),
|
||||||
|
VINE_WEST (106, 2),
|
||||||
|
VINE_SW (106, 3),
|
||||||
|
VINE_NORTH(106, 4),
|
||||||
|
VINE_NS (106, 5),
|
||||||
|
VINE_NW (106, 6),
|
||||||
|
VINE_NSW (106, 7), // North, South, West
|
||||||
|
VINE_EAST (106, 8),
|
||||||
|
VINE_ES (106, 9),
|
||||||
|
VINE_EW (106, 10),
|
||||||
|
VINE_ESW (106, 11),
|
||||||
|
VINE_EN (106, 12),
|
||||||
|
VINE_ENS (106, 13),
|
||||||
|
VINE_ENW (106, 14),
|
||||||
|
VINE_ENSW (106, 14),
|
||||||
|
|
||||||
|
WATERLILY(111, 0),
|
||||||
|
|
||||||
|
LILAC(175, 1),
|
||||||
|
DOUBLE_TALLGRASS(175, 2),
|
||||||
|
ROSE_BUSH(175, 4),
|
||||||
|
PEONY(175, 5),
|
||||||
|
ROSE_BUSH_10(175, 10),
|
||||||
|
|
||||||
|
/** Wheat (Age 0) */
|
||||||
|
WHEAT (59, 0),
|
||||||
|
/** Wheat (Age 1) */
|
||||||
|
WHEAT_1(59, 1),
|
||||||
|
/** Wheat (Age 2) */
|
||||||
|
WHEAT_2(59, 2),
|
||||||
|
/** Wheat (Age 3) */
|
||||||
|
WHEAT_3(59, 3),
|
||||||
|
/** Wheat (Age 4) */
|
||||||
|
WHEAT_4(59, 4),
|
||||||
|
/** Wheat (Age 5) */
|
||||||
|
WHEAT_5(59, 5),
|
||||||
|
/** Wheat (Age 6) */
|
||||||
|
WHEAT_6(59, 6),
|
||||||
|
/** Wheat (Age 7) */
|
||||||
|
WHEAT_7(59, 7),
|
||||||
|
|
||||||
|
/** Carrots (Age 0) */
|
||||||
|
CARROTS(141, 0),
|
||||||
|
/** Carrots (Age 1) */
|
||||||
|
CARROTS_1(141, 1),
|
||||||
|
/** Carrots (Age 2) */
|
||||||
|
CARROTS_2(141, 2),
|
||||||
|
/** Carrots (Age 3) */
|
||||||
|
CARROTS_3(141, 3),
|
||||||
|
/** Carrots (Age 4) */
|
||||||
|
CARROTS_4(141, 4),
|
||||||
|
/** Carrots (Age 5) */
|
||||||
|
CARROTS_5(141, 5),
|
||||||
|
/** Carrots (Age 6) */
|
||||||
|
CARROTS_6(141, 6),
|
||||||
|
/** Carrots (Age 7) */
|
||||||
|
CARROTS_7(141, 7),
|
||||||
|
|
||||||
|
/** Potatoes (Age 0) */
|
||||||
|
POTATOES (142, 0),
|
||||||
|
/** Potatoes (Age 1) */
|
||||||
|
POTATOES_1(142, 1),
|
||||||
|
/** Potatoes (Age 2) */
|
||||||
|
POTATOES_2(142, 2),
|
||||||
|
/** Potatoes (Age 3) */
|
||||||
|
POTATOES_3(142, 3),
|
||||||
|
/** Potatoes (Age 4) */
|
||||||
|
POTATOES_4(142, 4),
|
||||||
|
/** Potatoes (Age 5) */
|
||||||
|
POTATOES_5(142, 5),
|
||||||
|
/** Potatoes (Age 6) */
|
||||||
|
POTATOES_6(142, 6),
|
||||||
|
/** Potatoes (Age 7) */
|
||||||
|
POTATOES_7(142, 7),
|
||||||
|
|
||||||
|
/** Beetroot (Age 0) */
|
||||||
|
BEETROOT (207, 0),
|
||||||
|
/** Beetroot (Age 1) */
|
||||||
|
BEETROOT_1(207, 1),
|
||||||
|
/** Beetroot (Age 2) */
|
||||||
|
BEETROOT_2(207, 2),
|
||||||
|
/** Beetroot (Age 3) */
|
||||||
|
BEETROOT_3(207, 3);
|
||||||
|
|
||||||
|
BlockType(int id, int meta) {
|
||||||
|
this.id = id;
|
||||||
|
this.meta = meta;
|
||||||
|
this.namedId = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static BlockType getByIdMeta(int id, int meta) {
|
||||||
|
if (id < 0) {
|
||||||
|
log.warn("Incorrect id \"{}\"", id);
|
||||||
|
return BEDROCK;
|
||||||
|
}
|
||||||
|
|
||||||
|
Stream<BlockType> stream = Arrays.stream(BlockType.values());
|
||||||
|
return stream.filter(blockType -> blockType.id == id && blockType.meta == meta)
|
||||||
|
.findFirst()
|
||||||
|
.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;
|
||||||
|
}
|
||||||
65
core/src/main/java/mc/core/world/chunk/Chunk.java
Normal file
65
core/src/main/java/mc/core/world/chunk/Chunk.java
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
package mc.core.world.chunk;
|
||||||
|
|
||||||
|
import mc.core.world.Biome;
|
||||||
|
import mc.core.world.block.Block;
|
||||||
|
|
||||||
|
/* 16x256x16 */
|
||||||
|
public interface Chunk {
|
||||||
|
/**
|
||||||
|
* Глобальная координата X
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
int getX();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Глобальная координата Z
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
int getZ();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Получить секцию чанка
|
||||||
|
* @param height высота (0-15)
|
||||||
|
* @return {@link mc.core.world.chunk.ChunkSection}
|
||||||
|
*/
|
||||||
|
ChunkSection getChunkSection(int height);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Установить секцию чанка
|
||||||
|
* @param height высота (0-15)
|
||||||
|
* @param chunkSection {@link mc.core.world.chunk.ChunkSection}
|
||||||
|
*/
|
||||||
|
void setChunkSection(int height, ChunkSection chunkSection);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Получить блок по глобальным координатам секции чанка
|
||||||
|
* @param x global X
|
||||||
|
* @param y global Y
|
||||||
|
* @param z global Z
|
||||||
|
* @return {@link Block}
|
||||||
|
*/
|
||||||
|
Block getBlock(int x, int y, int z);
|
||||||
|
void setBlock(Block block);
|
||||||
|
|
||||||
|
int getSkyLight(int x, int y, int z);
|
||||||
|
void setSkyLight(int x, int y, int z, int lightLevel);
|
||||||
|
|
||||||
|
int getAddition(int x, int y, int z);
|
||||||
|
void setAddition(int x, int y, int z, int value);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Получить тип биома по глобальным координатам
|
||||||
|
* @param x global X
|
||||||
|
* @param z global Z
|
||||||
|
* @return {@link mc.core.world.Biome}
|
||||||
|
*/
|
||||||
|
Biome getBiome(int x, int z);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Указать данные по биому
|
||||||
|
* @param x global X
|
||||||
|
* @param z global Z
|
||||||
|
* @param biome {@link mc.core.world.Biome}
|
||||||
|
*/
|
||||||
|
void setBiome(int x, int z, Biome biome);
|
||||||
|
}
|
||||||
14
core/src/main/java/mc/core/world/chunk/ChunkProvider.java
Normal file
14
core/src/main/java/mc/core/world/chunk/ChunkProvider.java
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
package mc.core.world.chunk;
|
||||||
|
|
||||||
|
public interface ChunkProvider {
|
||||||
|
/**
|
||||||
|
* Получить чанк по координатам
|
||||||
|
* @param x глобальный X
|
||||||
|
* @param z глобальный Z
|
||||||
|
* @return {@link mc.core.world.chunk.Chunk}
|
||||||
|
*/
|
||||||
|
Chunk getChunk(int x , int z);
|
||||||
|
|
||||||
|
void saveChunk(Chunk chunk);
|
||||||
|
void saveChunk(Chunk... chunks);
|
||||||
|
}
|
||||||
53
core/src/main/java/mc/core/world/chunk/ChunkSection.java
Normal file
53
core/src/main/java/mc/core/world/chunk/ChunkSection.java
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
package mc.core.world.chunk;
|
||||||
|
|
||||||
|
import mc.core.world.block.Block;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Секция чанка размером 16x16x16 блоков
|
||||||
|
*/
|
||||||
|
public interface ChunkSection {
|
||||||
|
Chunk getParent();
|
||||||
|
void setParent(Chunk chunk);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Высота
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
int getY();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Получить блок по локальным координатам секции чанка
|
||||||
|
* @param localX local X (0-15)
|
||||||
|
* @param localY local Y (0-15)
|
||||||
|
* @param localZ local Z (0-15)
|
||||||
|
* @return {@link Block}
|
||||||
|
*/
|
||||||
|
Block getBlock(int localX, int localY, int localZ);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Установить блок
|
||||||
|
* @param block {@link mc.core.world.block.Block}
|
||||||
|
*/
|
||||||
|
void setBlock(Block block);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Получить данные о естественной подсветке
|
||||||
|
* @param localX локальный X (0-15)
|
||||||
|
* @param localY локальный Y (0-15)
|
||||||
|
* @param localZ локальный Z (0-15)
|
||||||
|
* @return integer значение 0-15, где 0 - это света нет, а 15 - получает прямой солнечный свет
|
||||||
|
*/
|
||||||
|
int getSkyLight(int localX, int localY, int localZ);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Указать данные о естественной подсветке
|
||||||
|
* @param localX локальный X (0-15)
|
||||||
|
* @param localY локальный Y (0-15)
|
||||||
|
* @param localZ локальный Z (0-15)
|
||||||
|
* @param lightLevel значение 0-15, где 0 - это света нет, а 15 - получает прямой солнечный свет
|
||||||
|
*/
|
||||||
|
void setSkyLight(int localX, int localY, int localZ, int lightLevel);
|
||||||
|
|
||||||
|
int getAddition(int localX, int localY, int localZ);
|
||||||
|
void setAddition(int localX, int localY, int localZ, int value);
|
||||||
|
}
|
||||||
20
core/src/main/resources/spring.xml
Normal file
20
core/src/main/resources/spring.xml
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
<?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="playerManager" class="mc.core.embedded.FakePlayerManager"/>
|
||||||
|
|
||||||
|
<bean id="gameLoop" class="mc.core.GameLoop">
|
||||||
|
<property name="gameTimer">
|
||||||
|
<bean class="mc.core.time.TimePerTick"/>
|
||||||
|
</property>
|
||||||
|
</bean>
|
||||||
|
|
||||||
|
<bean id="server" class="mc.core.embedded.FakeServer"/>
|
||||||
|
</beans>
|
||||||
91
core/src/test/java/mc/core/EntityLocationTest.java
Normal file
91
core/src/test/java/mc/core/EntityLocationTest.java
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
package mc.core;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import java.util.concurrent.ThreadLocalRandom;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
|
class EntityLocationTest {
|
||||||
|
private static final ThreadLocalRandom rnd = ThreadLocalRandom.current();
|
||||||
|
private static final double minD = 0.0d, maxD = 10.0d;
|
||||||
|
private static final float minF = 0.0f, maxF = 359.9f;
|
||||||
|
private double x, y, z;
|
||||||
|
private float yaw, pitch;
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void before() {
|
||||||
|
x = rnd.nextDouble(minD, maxD);
|
||||||
|
y = rnd.nextDouble(minD, maxD);
|
||||||
|
z = rnd.nextDouble(minD, maxD);
|
||||||
|
yaw = rnd.nextFloat() * (maxF - minF) + minF;
|
||||||
|
pitch = rnd.nextFloat() * (maxF - minF) + minF;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void equals_() {
|
||||||
|
EntityLocation loc1 = new EntityLocation(x, y, z, yaw, pitch);
|
||||||
|
EntityLocation loc2 = new EntityLocation(x, y, z, yaw, pitch);
|
||||||
|
assertEquals(loc1, loc2);
|
||||||
|
|
||||||
|
loc2 = new EntityLocation(x+1, y+2, z-3, yaw, pitch);
|
||||||
|
assertNotEquals(loc1, loc2);
|
||||||
|
|
||||||
|
loc2 = new EntityLocation(x, y, z, yaw-1, pitch+2);
|
||||||
|
assertNotEquals(loc1, loc2);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void clone_() {
|
||||||
|
EntityLocation locOrig = new EntityLocation(x, y, z, yaw, pitch);
|
||||||
|
EntityLocation locClone = locOrig.clone();
|
||||||
|
assertEquals(locOrig, locClone);
|
||||||
|
assertNotSame(locOrig, locClone);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void getBlockXZ() {
|
||||||
|
EntityLocation location;
|
||||||
|
|
||||||
|
location = new EntityLocation(0d, 0, 0d, 0f, 0f);
|
||||||
|
assertEquals(0, location.getBlockX());
|
||||||
|
assertEquals(0, location.getBlockZ());
|
||||||
|
|
||||||
|
location.setXYZ(0.1d, 0, 0.1d);
|
||||||
|
assertEquals(0, location.getBlockX());
|
||||||
|
assertEquals(0, location.getBlockZ());
|
||||||
|
|
||||||
|
location.setXYZ(0.5d, 0, 0.5d);
|
||||||
|
assertEquals(0, location.getBlockX());
|
||||||
|
assertEquals(0, location.getBlockZ());
|
||||||
|
|
||||||
|
location.setXYZ(0.9d, 0, 0.9d);
|
||||||
|
assertEquals(0, location.getBlockX());
|
||||||
|
assertEquals(0, location.getBlockZ());
|
||||||
|
|
||||||
|
location.setXYZ(1d, 0, 1d);
|
||||||
|
assertEquals(1, location.getBlockX());
|
||||||
|
assertEquals(1, location.getBlockZ());
|
||||||
|
|
||||||
|
location.setXYZ(-0.1d, 0, -0.1d);
|
||||||
|
assertEquals(-1, location.getBlockX());
|
||||||
|
assertEquals(-1, location.getBlockZ());
|
||||||
|
|
||||||
|
location.setXYZ(-0.5d, 0, -0.5d);
|
||||||
|
assertEquals(-1, location.getBlockX());
|
||||||
|
assertEquals(-1, location.getBlockZ());
|
||||||
|
|
||||||
|
location.setXYZ(-0.9d, 0, -0.9d);
|
||||||
|
assertEquals(-1, location.getBlockX());
|
||||||
|
assertEquals(-1, location.getBlockZ());
|
||||||
|
|
||||||
|
location.setXYZ(-1d, 0, -1d);
|
||||||
|
assertEquals(-1, location.getBlockX());
|
||||||
|
assertEquals(-1, location.getBlockZ());
|
||||||
|
|
||||||
|
location.setXYZ(-1.1d, 0, -1.1d);
|
||||||
|
assertEquals(-2, location.getBlockX());
|
||||||
|
assertEquals(-2, location.getBlockZ());
|
||||||
|
}
|
||||||
|
}
|
||||||
33
core/src/test/java/mc/core/ImmutableEntityLocationTest.java
Normal file
33
core/src/test/java/mc/core/ImmutableEntityLocationTest.java
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
package mc.core;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||||
|
|
||||||
|
class ImmutableEntityLocationTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void setValue() {
|
||||||
|
EntityLocation location = new ImmutableEntityLocation(1d, 2d, 3d, 4f, 5f);
|
||||||
|
|
||||||
|
assertThrows(UnsupportedOperationException.class, () -> {
|
||||||
|
location.setX(1);
|
||||||
|
location.setY(1);
|
||||||
|
location.setZ(1);
|
||||||
|
location.setYaw(1);
|
||||||
|
location.setPitch(1);
|
||||||
|
location.setXYZ(1, 2, 3);
|
||||||
|
location.setYawPitch(1, 2);
|
||||||
|
location.set(EntityLocation.ZERO());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void clone_() {
|
||||||
|
EntityLocation locOrig = new ImmutableEntityLocation(1d, 2d, 3d, 4f, 5f);
|
||||||
|
EntityLocation locClone = locOrig.clone();
|
||||||
|
|
||||||
|
assertEquals(locOrig, locClone);
|
||||||
|
}
|
||||||
|
}
|
||||||
125
core/src/test/java/mc/core/TestEventBus.java
Normal file
125
core/src/test/java/mc/core/TestEventBus.java
Normal file
@@ -0,0 +1,125 @@
|
|||||||
|
package mc.core;
|
||||||
|
|
||||||
|
import javafx.util.Pair;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import mc.core.eventbus.Event;
|
||||||
|
import mc.core.eventbus.EventBus;
|
||||||
|
import mc.core.eventbus.Subscriber;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.mockito.internal.util.reflection.Whitebox;
|
||||||
|
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Queue;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertSame;
|
||||||
|
|
||||||
|
class TestEventBus {
|
||||||
|
private List<String> resultList = new ArrayList<>();
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
private Map<Class<? extends Event>, List<Pair<Object, Method>>> getEventBusFieldSubscribes() {
|
||||||
|
return (Map<Class<? extends Event>, List<Pair<Object, Method>>>)
|
||||||
|
Whitebox.getInternalState(EventBus.getInstance(), "subscribes");
|
||||||
|
}
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
void before() {
|
||||||
|
getEventBusFieldSubscribes().clear();
|
||||||
|
((Queue<Event>) Whitebox.getInternalState(EventBus.getInstance(), "eventQueue")).clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testRegisterSubscribes() {
|
||||||
|
DumbEventHandler handler = new DumbEventHandler();
|
||||||
|
EventBus.getInstance().registerSubscribes(handler);
|
||||||
|
|
||||||
|
Map<Class<? extends Event>, List<Pair<Object, Method>>> subscribes = getEventBusFieldSubscribes();
|
||||||
|
assertEquals(1, subscribes.size());
|
||||||
|
|
||||||
|
List<Pair<Object, Method>> pairs = subscribes.values().iterator().next();
|
||||||
|
assertEquals(1, pairs.size());
|
||||||
|
|
||||||
|
Pair<Object, Method> pair = pairs.get(0);
|
||||||
|
assertSame(handler, pair.getKey());
|
||||||
|
assertEquals("corectSubscribe", pair.getValue().getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testUnregisterSubscribes() {
|
||||||
|
DumbEventHandler handler = new DumbEventHandler();
|
||||||
|
EventBus.getInstance().registerSubscribes(handler);
|
||||||
|
|
||||||
|
EventBus.getInstance().unregisterSubscribes(handler);
|
||||||
|
|
||||||
|
Map<Class<? extends Event>, List<Pair<Object, Method>>> subscribes = getEventBusFieldSubscribes();
|
||||||
|
assertEquals(0, subscribes.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
void testPost() {
|
||||||
|
EventBus.getInstance().post(new DumbEvent());
|
||||||
|
|
||||||
|
Queue<Event> eventQueue = (Queue<Event>) Whitebox.getInternalState(EventBus.getInstance(), "eventQueue");
|
||||||
|
assertEquals(1, eventQueue.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testProcess() {
|
||||||
|
Stream.of(new DumbEventHandler("D1 "), new DumbEventHandler("D2 "))
|
||||||
|
.forEach(handler -> EventBus.getInstance().registerSubscribes(handler));
|
||||||
|
|
||||||
|
Stream.of(new DumbEvent("message 1"), new DumbEvent("message 2"))
|
||||||
|
.forEach(event -> EventBus.getInstance().post(event));
|
||||||
|
|
||||||
|
EventBus.getInstance().process();
|
||||||
|
|
||||||
|
assertEquals(4, resultList.size());
|
||||||
|
assertEquals("D1 message 1", resultList.get(0));
|
||||||
|
assertEquals("D2 message 1", resultList.get(1));
|
||||||
|
assertEquals("D1 message 2", resultList.get(2));
|
||||||
|
assertEquals("D2 message 2", resultList.get(3));
|
||||||
|
}
|
||||||
|
|
||||||
|
@AllArgsConstructor
|
||||||
|
@NoArgsConstructor
|
||||||
|
private class DumbEvent implements Event {
|
||||||
|
String message;
|
||||||
|
}
|
||||||
|
|
||||||
|
@AllArgsConstructor
|
||||||
|
@NoArgsConstructor
|
||||||
|
public class DumbEventHandler {
|
||||||
|
private String prefix = "";
|
||||||
|
|
||||||
|
@Subscriber
|
||||||
|
public void corectSubscribe(DumbEvent event) {
|
||||||
|
resultList.add(prefix + event.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Subscriber
|
||||||
|
public Object incorectSubscribeReturnType(DumbEvent event) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Subscriber
|
||||||
|
public void incorrectSubscriberTypeParameter(Object object) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Subscriber
|
||||||
|
public void incorrectSubscriberManyParameters(DumbEvent event, Object object) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public void someMethod() {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
34
core/src/test/java/mc/core/TestSpringConfig.java
Normal file
34
core/src/test/java/mc/core/TestSpringConfig.java
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
package mc.core;
|
||||||
|
|
||||||
|
import mc.core.world.World;
|
||||||
|
import mc.core.world.chunk.Chunk;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
||||||
|
import static org.mockito.Matchers.anyInt;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
public class TestSpringConfig {
|
||||||
|
@Bean()
|
||||||
|
public World simpleMockWorld() {
|
||||||
|
return mock(World.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public World chunkedMockWorld() {
|
||||||
|
World world = mock(World.class);
|
||||||
|
when(world.getChunk(anyInt(), anyInt())).thenAnswer(invocation -> {
|
||||||
|
Object[] args = invocation.getArguments();
|
||||||
|
|
||||||
|
Chunk chunk = mock(Chunk.class);
|
||||||
|
when(chunk.getX()).thenReturn((int) args[0]);
|
||||||
|
when(chunk.getZ()).thenReturn((int) args[1]);
|
||||||
|
|
||||||
|
return chunk;
|
||||||
|
});
|
||||||
|
|
||||||
|
return world;
|
||||||
|
}
|
||||||
|
}
|
||||||
76
core/src/test/java/mc/core/text/TextTest.java
Normal file
76
core/src/test/java/mc/core/text/TextTest.java
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
package mc.core.text;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
|
class TextTest {
|
||||||
|
@Test
|
||||||
|
void toPlain() {
|
||||||
|
final String m1 = "mes";
|
||||||
|
final String m2 = "sage";
|
||||||
|
final String message = m1 + m2;
|
||||||
|
|
||||||
|
assertEquals(message, Text.of(message).toPlain());
|
||||||
|
assertEquals(message, Text.builder(message).build().toPlain());
|
||||||
|
assertEquals(message, Text.builder(Text.of(message)).build().toPlain());
|
||||||
|
assertEquals(message, Text.builder().append(message).build().toPlain());
|
||||||
|
assertEquals(message, Text.builder().append(Text.of(message)).build().toPlain());
|
||||||
|
|
||||||
|
assertEquals(message, Text.builder(m1, m2).build().toPlain());
|
||||||
|
assertEquals(message, Text.builder(Text.of(m1), Text.of(m2)).build().toPlain());
|
||||||
|
assertEquals(message, Text.builder().append(Text.of(m1), Text.of(m2)).build().toPlain());
|
||||||
|
assertEquals(message, Text.builder().append(Text.of(m1)).append(Text.of(m2)).build().toPlain());
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void equals_() {
|
||||||
|
assertEquals(Text.of(), Text.of(""));
|
||||||
|
assertEquals(Text.of(), Text.builder().build());
|
||||||
|
assertEquals(Text.of(), Text.builder("").build());
|
||||||
|
assertEquals(Text.of(), Text.builder().append().build());
|
||||||
|
assertEquals(Text.of(), Text.builder().append("").build());
|
||||||
|
|
||||||
|
assertNotEquals(Text.of(), Text.of("??"));
|
||||||
|
assertNotEquals(Text.of(), Text.builder("??").build());
|
||||||
|
assertNotEquals(Text.of(), Text.builder().append("??").build());
|
||||||
|
|
||||||
|
assertEquals(Text.of("message"), Text.builder("message").build());
|
||||||
|
assertEquals(Text.of("message"), Text.builder(Text.of("message")).build());
|
||||||
|
assertEquals(Text.of("message"), Text.builder().append("message").build());
|
||||||
|
assertEquals(Text.of("message"), Text.builder().append(Text.of("message")).build());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void isEmpty() {
|
||||||
|
assertTrue(Text.of().isEmpty());
|
||||||
|
assertTrue(Text.of((String) null).isEmpty());
|
||||||
|
assertTrue(Text.of((Text) null).isEmpty());
|
||||||
|
assertTrue(Text.of("").isEmpty());
|
||||||
|
assertTrue(Text.of("", "").isEmpty());
|
||||||
|
|
||||||
|
assertTrue(Text.builder().build().isEmpty());
|
||||||
|
assertTrue(Text.builder((String) null).build().isEmpty());
|
||||||
|
assertTrue(Text.builder((Text) null).build().isEmpty());
|
||||||
|
assertTrue(Text.builder("").build().isEmpty());
|
||||||
|
assertTrue(Text.builder("", "").build().isEmpty());
|
||||||
|
assertTrue(Text.builder(Text.of()).build().isEmpty());
|
||||||
|
assertTrue(Text.builder(Text.of(), Text.of()).build().isEmpty());
|
||||||
|
|
||||||
|
assertTrue(Text.builder().append().build().isEmpty());
|
||||||
|
assertTrue(Text.builder().append((String) null).build().isEmpty());
|
||||||
|
assertTrue(Text.builder().append((Text) null).build().isEmpty());
|
||||||
|
assertTrue(Text.builder().append("").build().isEmpty());
|
||||||
|
assertTrue(Text.builder().append(Text.of()).build().isEmpty());
|
||||||
|
assertTrue(Text.builder().append(Text.of(), Text.of()).build().isEmpty());
|
||||||
|
assertTrue(Text.builder().append(Text.of()).append(Text.of()).build().isEmpty());
|
||||||
|
|
||||||
|
assertFalse(Text.of("??").isEmpty());
|
||||||
|
assertFalse(Text.builder("??").build().isEmpty());
|
||||||
|
assertFalse(Text.builder(Text.of("??")).build().isEmpty());
|
||||||
|
assertFalse(Text.builder().append("??").build().isEmpty());
|
||||||
|
assertFalse(Text.builder().append(Text.of("??")).build().isEmpty());
|
||||||
|
}
|
||||||
|
}
|
||||||
37
core/src/test/java/mc/core/utils/CompactedCoordsTest.java
Normal file
37
core/src/test/java/mc/core/utils/CompactedCoordsTest.java
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
package mc.core.utils;
|
||||||
|
|
||||||
|
import org.junit.jupiter.params.ParameterizedTest;
|
||||||
|
import org.junit.jupiter.params.provider.Arguments;
|
||||||
|
import org.junit.jupiter.params.provider.MethodSource;
|
||||||
|
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
|
||||||
|
class CompactedCoordsTest {
|
||||||
|
private static Stream<Arguments> streamTestParams() {
|
||||||
|
return Stream.of(
|
||||||
|
Arguments.of(Short.MIN_VALUE, Short.MIN_VALUE),
|
||||||
|
Arguments.of(Short.MIN_VALUE, Short.MAX_VALUE),
|
||||||
|
Arguments.of(Short.MAX_VALUE, Short.MAX_VALUE),
|
||||||
|
Arguments.of(Short.MAX_VALUE, Short.MIN_VALUE),
|
||||||
|
Arguments.of(0, 0),
|
||||||
|
Arguments.of(-1, -1),
|
||||||
|
Arguments.of(-1, 1),
|
||||||
|
Arguments.of(1, 1),
|
||||||
|
Arguments.of(1, -1)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ParameterizedTest
|
||||||
|
@MethodSource("streamTestParams")
|
||||||
|
void testCompress(int x, int z) {
|
||||||
|
final int compressXZ = CompactedCoords.compressXZ(x, z);
|
||||||
|
int[] xz = CompactedCoords.uncompressXZ(compressXZ);
|
||||||
|
|
||||||
|
assertTrue(x == xz[0] && z == xz[1],
|
||||||
|
String.format("x = %d, vx = %d; z = %d, vz = %d",
|
||||||
|
x, xz[0],
|
||||||
|
z, xz[1]));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
package mc.core.world.block;
|
||||||
|
|
||||||
|
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;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertNotEquals;
|
||||||
|
|
||||||
|
class BlockLocationTest {
|
||||||
|
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 equals_() {
|
||||||
|
BlockLocation loc1 = new BlockLocation(x, y, z);
|
||||||
|
BlockLocation loc2 = new BlockLocation(x, y, z);
|
||||||
|
assertEquals(loc1, loc2);
|
||||||
|
|
||||||
|
loc2 = new BlockLocation(x+1, y+2, z-3);
|
||||||
|
assertNotEquals(loc1, loc2);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void clone_() {
|
||||||
|
BlockLocation locOrig = new BlockLocation(x, y, z);
|
||||||
|
BlockLocation locClone = locOrig.clone();
|
||||||
|
assertEquals(locOrig, locClone);
|
||||||
|
}
|
||||||
|
}
|
||||||
17
h2_playermanager/build.gradle
Normal file
17
h2_playermanager/build.gradle
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
version '1.0-SNAPSHOT'
|
||||||
|
|
||||||
|
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')
|
||||||
|
}
|
||||||
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,98 @@
|
|||||||
|
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.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
@Component
|
||||||
|
public class H2PlayerManager implements PlayerManager {
|
||||||
|
@Setter
|
||||||
|
@Autowired
|
||||||
|
private H2PlayerService h2PlayerService;
|
||||||
|
private List<H2Player> playerList = Collections.synchronizedList(new ArrayList<>());
|
||||||
|
|
||||||
|
@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));
|
||||||
|
}
|
||||||
|
}
|
||||||
10
proto_1.12.2/build.gradle
Normal file
10
proto_1.12.2/build.gradle
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
version '1.0-SNAPSHOT'
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
/* Core */
|
||||||
|
compile_excludeCopy project(':core')
|
||||||
|
|
||||||
|
/* Components */
|
||||||
|
compile (group: 'com.google.code.gson', name: 'gson', version: '2.8.5')
|
||||||
|
compile (group: 'net.sf.trove4j', name: 'trove4j', version: '3.0.3')
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user