diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/client/StarboundClient.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/client/StarboundClient.kt index a1fde66a..1d425bf0 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/client/StarboundClient.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/client/StarboundClient.kt @@ -46,12 +46,11 @@ import ru.dbotthepony.kstarbound.client.gl.vertex.GeometryType import ru.dbotthepony.kstarbound.client.gl.vertex.VertexBuilder import ru.dbotthepony.kstarbound.client.input.UserInput import ru.dbotthepony.kstarbound.client.network.ClientConnection -import ru.dbotthepony.kstarbound.client.network.packets.TrackedPositionPacket -import ru.dbotthepony.kstarbound.client.network.packets.TrackedSizePacket +import ru.dbotthepony.kstarbound.server.network.packets.TrackedPositionPacket +import ru.dbotthepony.kstarbound.server.network.packets.TrackedSizePacket import ru.dbotthepony.kstarbound.client.render.Camera import ru.dbotthepony.kstarbound.client.render.Font import ru.dbotthepony.kstarbound.client.render.LayeredRenderer -import ru.dbotthepony.kstarbound.client.render.TextAlignY import ru.dbotthepony.kstarbound.client.render.TileRenderers import ru.dbotthepony.kstarbound.client.world.ClientWorld import ru.dbotthepony.kstarbound.defs.image.Image @@ -66,11 +65,9 @@ import ru.dbotthepony.kvector.api.IStruct4f import ru.dbotthepony.kvector.arrays.Matrix3f import ru.dbotthepony.kvector.arrays.Matrix3fStack import ru.dbotthepony.kvector.util2d.AABB -import ru.dbotthepony.kvector.util2d.AABBi import ru.dbotthepony.kvector.vector.RGBAColor import ru.dbotthepony.kvector.vector.Vector2d import ru.dbotthepony.kvector.vector.Vector2f -import ru.dbotthepony.kvector.vector.Vector2i import ru.dbotthepony.kvector.vector.Vector4f import java.io.Closeable import java.io.File diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/client/network/ClientConnection.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/client/network/ClientConnection.kt index 2dc09eed..8bdab58a 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/client/network/ClientConnection.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/client/network/ClientConnection.kt @@ -54,6 +54,7 @@ class ClientConnection(val client: StarboundClient, type: ConnectionType, uuid: private val LOGGER = LogManager.getLogger() fun connectToLocalServer(client: StarboundClient, address: LocalAddress, uuid: UUID): ClientConnection { + LOGGER.info("Trying to connect to local server at $address with Client UUID $uuid") val connection = ClientConnection(client, ConnectionType.MEMORY, uuid) Bootstrap() @@ -73,6 +74,7 @@ class ClientConnection(val client: StarboundClient, type: ConnectionType, uuid: } fun connectToRemoteServer(client: StarboundClient, address: SocketAddress, uuid: UUID): ClientConnection { + LOGGER.info("Trying to connect to remote server at $address with Client UUID $uuid") val connection = ClientConnection(client, ConnectionType.NETWORK, uuid) Bootstrap() diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/client/network/packets/ForgetChunkPacket.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/client/network/packets/ForgetChunkPacket.kt index e91c9c47..9bdd9394 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/client/network/packets/ForgetChunkPacket.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/client/network/packets/ForgetChunkPacket.kt @@ -2,8 +2,8 @@ package ru.dbotthepony.kstarbound.client.network.packets import ru.dbotthepony.kstarbound.client.network.ClientConnection import ru.dbotthepony.kstarbound.network.IClientPacket -import ru.dbotthepony.kstarbound.util.readChunkPos -import ru.dbotthepony.kstarbound.util.writeVec2i +import ru.dbotthepony.kstarbound.io.readChunkPos +import ru.dbotthepony.kstarbound.io.writeVec2i import ru.dbotthepony.kstarbound.world.ChunkPos import java.io.DataInputStream import java.io.DataOutputStream diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/client/network/packets/InitialChunkDataPacket.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/client/network/packets/InitialChunkDataPacket.kt index db9dd3c0..0f02ff77 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/client/network/packets/InitialChunkDataPacket.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/client/network/packets/InitialChunkDataPacket.kt @@ -2,10 +2,10 @@ package ru.dbotthepony.kstarbound.client.network.packets import ru.dbotthepony.kstarbound.client.network.ClientConnection import ru.dbotthepony.kstarbound.network.IClientPacket -import ru.dbotthepony.kstarbound.util.readChunkPos -import ru.dbotthepony.kstarbound.util.readCollection -import ru.dbotthepony.kstarbound.util.writeCollection -import ru.dbotthepony.kstarbound.util.writeVec2i +import ru.dbotthepony.kstarbound.io.readChunkPos +import ru.dbotthepony.kstarbound.io.readCollection +import ru.dbotthepony.kstarbound.io.writeCollection +import ru.dbotthepony.kstarbound.io.writeVec2i import ru.dbotthepony.kstarbound.world.CHUNK_SIZE import ru.dbotthepony.kstarbound.world.Chunk import ru.dbotthepony.kstarbound.world.ChunkPos diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/client/network/packets/JoinWorldPacket.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/client/network/packets/JoinWorldPacket.kt index 03afc7de..6eece38f 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/client/network/packets/JoinWorldPacket.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/client/network/packets/JoinWorldPacket.kt @@ -4,10 +4,10 @@ import io.netty.buffer.ByteBuf import ru.dbotthepony.kstarbound.client.network.ClientConnection import ru.dbotthepony.kstarbound.client.world.ClientWorld import ru.dbotthepony.kstarbound.network.IClientPacket -import ru.dbotthepony.kstarbound.util.readUUID -import ru.dbotthepony.kstarbound.util.readVec2i -import ru.dbotthepony.kstarbound.util.writeUUID -import ru.dbotthepony.kstarbound.util.writeVec2i +import ru.dbotthepony.kstarbound.io.readUUID +import ru.dbotthepony.kstarbound.io.readVec2i +import ru.dbotthepony.kstarbound.io.writeUUID +import ru.dbotthepony.kstarbound.io.writeVec2i import ru.dbotthepony.kstarbound.world.World import ru.dbotthepony.kstarbound.world.WorldGeometry import ru.dbotthepony.kvector.vector.Vector2i diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/client/network/packets/SpawnWorldObjectPacket.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/client/network/packets/SpawnWorldObjectPacket.kt new file mode 100644 index 00000000..58f76406 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/client/network/packets/SpawnWorldObjectPacket.kt @@ -0,0 +1,28 @@ +package ru.dbotthepony.kstarbound.client.network.packets + +import com.google.gson.JsonObject +import ru.dbotthepony.kstarbound.client.network.ClientConnection +import ru.dbotthepony.kstarbound.json.readJsonObject +import ru.dbotthepony.kstarbound.json.writeJsonObject +import ru.dbotthepony.kstarbound.network.IClientPacket +import ru.dbotthepony.kstarbound.world.entities.WorldObject +import java.io.DataInputStream +import java.io.DataOutputStream + +class SpawnWorldObjectPacket(val data: JsonObject) : IClientPacket { + constructor(stream: DataInputStream) : this(stream.readJsonObject()) + + override fun write(stream: DataOutputStream) { + stream.writeJsonObject(data) + } + + override fun play(connection: ClientConnection) { + val world = connection.client.world ?: return + val obj = WorldObject.fromJson(data) + + world.mailbox.submit { + val chunk = world.chunkMap[world.geometry.chunkFromCell(obj.pos)] ?: return@submit + chunk.addObject(obj) + } + } +} diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/io/StarboundPak.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/io/StarboundPak.kt index 76b89824..2bee5715 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/io/StarboundPak.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/io/StarboundPak.kt @@ -4,7 +4,7 @@ import it.unimi.dsi.fastutil.objects.Object2ObjectFunction import it.unimi.dsi.fastutil.objects.Object2ObjectLinkedOpenHashMap import ru.dbotthepony.kstarbound.IStarboundFile import ru.dbotthepony.kstarbound.getValue -import ru.dbotthepony.kstarbound.json.BinaryJsonReader +import ru.dbotthepony.kstarbound.json.readJsonObject import java.io.BufferedInputStream import java.io.Closeable import java.io.DataInputStream @@ -182,7 +182,7 @@ class StarboundPak(val path: File, callback: (finished: Boolean, status: String) } // сразу за INDEX идут метаданные в формате Binary Json - val metadata = BinaryJsonReader.readObject(DataInputStream(object : InputStream() { + val metadata = DataInputStream(object : InputStream() { override fun read(): Int { return reader.read() } @@ -190,7 +190,7 @@ class StarboundPak(val path: File, callback: (finished: Boolean, status: String) override fun read(b: ByteArray, off: Int, len: Int): Int { return reader.read(b, off, len) } - })) + }).readJsonObject() // сразу за метаданными идёт количество файлов внутри данного pak в формате Big Endian variable int val indexNodeCount = reader.readVarLong() diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/io/Streams.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/io/Streams.kt index 50ca994f..9b6b5be3 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/io/Streams.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/io/Streams.kt @@ -1,136 +1,158 @@ package ru.dbotthepony.kstarbound.io import it.unimi.dsi.fastutil.bytes.ByteArrayList +import ru.dbotthepony.kstarbound.world.ChunkPos +import ru.dbotthepony.kvector.api.IStruct2d +import ru.dbotthepony.kvector.api.IStruct2i +import ru.dbotthepony.kvector.vector.Vector2d +import ru.dbotthepony.kvector.vector.Vector2i +import java.io.DataInput +import java.io.DataOutput import java.io.IOException import java.io.InputStream +import java.io.OutputStream import java.io.RandomAccessFile - -/** - * Читает Variable Length Integer как Long - */ -fun RandomAccessFile.readVarLong(): Long { - var result = 0L - var read = read() - - while (true) { - result = (result shl 7) or (read.toLong() and 0x7FL) - - if (read and 0x80 == 0) { - break - } - - read = read() - } - - return result -} +import java.math.BigDecimal +import java.util.* +import java.util.function.IntConsumer +import java.util.function.IntSupplier +import kotlin.collections.ArrayList data class VarIntReadResult(val value: Int, val cells: Int) +data class VarLongReadResult(val value: Long, val cells: Int) -/** - * Читает Variable Length Integer как Int - */ -fun RandomAccessFile.readVarInt(): Int { - var result = 0 - var read = read() - - while (true) { - result = (result shl 7) or (read and 0x7F) - - if (read and 0x80 == 0) { - break - } - - read = read() - } - - return result -} - -/** - * Читает Variable Length Integer как Int - */ -fun RandomAccessFile.readVarIntInfo(): VarIntReadResult { - var result = 0 - var read = read() - var i = 1 - - while (true) { - result = (result shl 7) or (read and 0x7F) - - if (read and 0x80 == 0) { - break - } - - read = read() - i++ - } - - return VarIntReadResult(result, i) -} - -/** - * Читает Variable Length Integer как Int - */ -fun InputStream.readVarIntInfo(): VarIntReadResult { - var result = 0 - var read = read() - var i = 1 - - while (true) { - result = (result shl 7) or (read and 0x7F) - - if (read and 0x80 == 0) { - break - } - - read = read() - i++ - } - - return VarIntReadResult(result, i) -} - -/** - * Читает Variable Length Integer как Long - */ -fun InputStream.readVarLong(): Long { +private fun readVarLongInfo(supplier: IntSupplier): VarLongReadResult { var result = 0L - var read = read() + var read = supplier.asInt + var i = 1 while (true) { - result = (result shl 7) or (read.toLong() and 0x7FL) + result = (result shl 7) or (read.toLong() and 0x7F) - if (read and 0x80 == 0) { + if (read and 0x80 == 0) break - } - read = read() + read = supplier.asInt + i++ } - return result + return VarLongReadResult(result, i) } -/** - * Читает Variable Length Integer как Int - */ -fun InputStream.readVarInt(): Int { - var result = 0 - var read = read() - - while (true) { - result = (result shl 7) or (read and 0x7F) - - if (read and 0x80 == 0) { - break - } - - read = read() - } - - return result +private fun readVarIntInfo(supplier: IntSupplier): VarIntReadResult { + val read = readVarLongInfo(supplier) + return VarIntReadResult(read.value.toInt(), read.cells) } +private fun readVarInt(supplier: IntSupplier): Int { + return readVarIntInfo(supplier).value +} + +private fun readVarLong(supplier: IntSupplier): Long { + return readVarLongInfo(supplier).value +} + +private fun Int.fromSignedVar(): Int { + val sign = this and 1 + + if (sign == 0) + return this ushr 1 + else + return -(this ushr 1) - 1 +} + +private fun VarIntReadResult.fromSignedVar(): VarIntReadResult { + val sign = this.value and 1 + + if (sign == 0) + return VarIntReadResult(this.value ushr 1, this.cells) + else + return VarIntReadResult(-(this.value ushr 1) - 1, this.cells) +} + +private fun Long.fromSignedVar(): Long { + val sign = this and 1 + + if (sign == 0L) + return this ushr 1 + else + return -(this ushr 1) - 1L +} + +private fun VarLongReadResult.fromSignedVar(): VarLongReadResult { + val sign = this.value and 1 + + if (sign == 0L) + return VarLongReadResult(this.value ushr 1, this.cells) + else + return VarLongReadResult(-(this.value ushr 1) - 1, this.cells) +} + +fun RandomAccessFile.readVarLong() = readVarLong(::read) +fun RandomAccessFile.readVarInt() = readVarInt(::read) +fun RandomAccessFile.readVarLongInfo() = readVarLongInfo(::read) +fun RandomAccessFile.readVarIntInfo() = readVarIntInfo(::read) +fun InputStream.readVarLong() = readVarLong(::read) +fun InputStream.readVarInt() = readVarInt(::read) +fun InputStream.readVarLongInfo() = readVarLongInfo(::read) +fun InputStream.readVarIntInfo() = readVarIntInfo(::read) + +fun RandomAccessFile.readSignedVarLong() = readVarLong(::read).fromSignedVar() +fun RandomAccessFile.readSignedVarInt() = readVarInt(::read).fromSignedVar() +fun RandomAccessFile.readSignedVarLongInfo() = readVarLongInfo(::read).fromSignedVar() +fun RandomAccessFile.readSignedVarIntInfo() = readVarIntInfo(::read).fromSignedVar() +fun InputStream.readSignedVarLong() = readVarLong(::read).fromSignedVar() +fun InputStream.readSignedVarInt() = readVarInt(::read).fromSignedVar() +fun InputStream.readSignedVarLongInfo() = readVarLongInfo(::read).fromSignedVar() +fun InputStream.readSignedVarIntInfo() = readVarIntInfo(::read).fromSignedVar() + +private fun writeVarLong(write: IntConsumer, value: Long): Int { + var value = value + var i = 0 + + do { + val toWrite = (value and 0x7F).toInt() + value = value ushr 7 + + if (value == 0L) + write.accept(toWrite) + else + write.accept(toWrite or 0x80) + + i++ + } while (value != 0L) + + return i +} + +private fun writeVarInt(write: IntConsumer, value: Int): Int { + return writeVarLong(write, value.toLong()) +} + +private fun Int.toSignedVar(): Int { + if (this >= 0) + return this shl 1 + else + return (this shl 1) or 1 +} + +private fun Long.toSignedVar(): Long { + if (this >= 0) + return this shl 1 + else + return ((-this - 1) shl 1) or 1L +} + +fun RandomAccessFile.writeVarLong(value: Long) = writeVarLong(::write, value) +fun RandomAccessFile.writeVarInt(value: Int) = writeVarInt(::write, value) +fun OutputStream.writeVarLong(value: Long) = writeVarLong(::write, value) +fun OutputStream.writeVarInt(value: Int) = writeVarInt(::write, value) + +fun RandomAccessFile.writeSignedVarLong(value: Long) = writeVarLong(::write, value.toSignedVar()) +fun RandomAccessFile.writeSignedVarInt(value: Int) = writeVarInt(::write, value.toSignedVar()) +fun OutputStream.writeSignedVarLong(value: Long) = writeVarLong(::write, value.toSignedVar()) +fun OutputStream.writeSignedVarInt(value: Int) = writeVarInt(::write, value.toSignedVar()) + fun RandomAccessFile.readString(length: Int): String { require(length >= 0) { "Invalid length $length" } @@ -161,38 +183,6 @@ fun InputStream.readString(length: Int, allowSmaller: Boolean = false): String { return bytes.toString(Charsets.UTF_8) } -fun RandomAccessFile.readCString(): String { - val bytes = ByteArrayList() - var read = read() - - while (read != 0) { - bytes.add(read.toByte()) - read = read() - } - - return ByteArray(bytes.size).also { - for (i in it.indices) { - it[i] = bytes.getByte(i) - } - }.toString(Charsets.UTF_8) -} - -fun InputStream.readCString(): String { - val bytes = ByteArrayList() - var read = read() - - while (read != 0) { - bytes.add(read.toByte()) - read = read() - } - - return ByteArray(bytes.size).also { - for (i in it.indices) { - it[i] = bytes.getByte(i) - } - }.toString(Charsets.UTF_8) -} - fun InputStream.readByteChar(): Char { return read().toChar() } @@ -206,3 +196,140 @@ fun InputStream.readHeader(header: String) { } } } + +fun OutputStream.writeUTF(value: String) { + write(value.toByteArray().also { check(!it.any { it.toInt() == 0 }) { "Provided UTF string contains NUL" } }) + write(0) +} + +fun InputStream.readUTF(): String { + val bytes = ByteArrayList() + var read = read() + + while (read != 0) { + bytes.add(read.toByte()) + read = read() + } + + return String(bytes.toByteArray()) +} + +fun InputStream.readUUID(): UUID { + return UUID(readLong(), readLong()) +} + +fun OutputStream.writeUUID(value: UUID) { + writeLong(value.mostSignificantBits) + writeLong(value.leastSignificantBits) +} + +fun OutputStream.writeVec2i(value: IStruct2i) { + writeSignedVarInt(value.component1()) + writeSignedVarInt(value.component2()) +} + +fun OutputStream.writeVec2d(value: IStruct2d) { + writeDouble(value.component1()) + writeDouble(value.component2()) +} + +fun InputStream.readVec2i(): Vector2i { + return Vector2i(readSignedVarInt(), readSignedVarInt()) +} + +fun InputStream.readVec2d(): Vector2d { + return Vector2d(readDouble(), readDouble()) +} + +fun InputStream.readChunkPos(): ChunkPos { + return ChunkPos(readSignedVarInt(), readSignedVarInt()) +} + +fun S.writeCollection(collection: Collection, writer: S.(V) -> Unit) { + writeVarInt(collection.size) + + for (value in collection) { + writer(this, value) + } +} + +fun > S.readCollection(reader: S.() -> V, factory: (Int) -> C): C { + val size = readVarInt() + val collection = factory.invoke(size) + + for (i in 0 until size) { + collection.add(reader(this)) + } + + return collection +} + +fun S.readCollection(reader: S.() -> V) = readCollection(reader, ::ArrayList) + +fun OutputStream.writeInt(value: Int) { + if (this is DataOutput) { + writeInt(value) + return + } + + write(value ushr 24) + write(value ushr 16) + write(value ushr 8) + write(value) +} + +fun InputStream.readInt(): Int { + if (this is DataInput) { + return readInt() + } + + return (read() shl 24) or (read() shl 16) or (read() shl 8) or read() +} + +fun OutputStream.writeLong(value: Long) { + if (this is DataOutput) { + writeLong(value) + return + } + + write((value ushr 48).toInt()) + write((value ushr 40).toInt()) + write((value ushr 32).toInt()) + write((value ushr 24).toInt()) + write((value ushr 16).toInt()) + write((value ushr 8).toInt()) + write(value.toInt()) +} + +fun InputStream.readLong(): Long { + if (this is DataInput) { + return readLong() + } + + return (read().toLong() shl 48) or + (read().toLong() shl 40) or + (read().toLong() shl 32) or + (read().toLong() shl 24) or + (read().toLong() shl 16) or + (read().toLong() shl 8) or + read().toLong() +} + +fun OutputStream.writeFloat(value: Float) = writeInt(value.toBits()) +fun InputStream.readFloat() = Float.fromBits(readInt()) +fun OutputStream.writeDouble(value: Double) = writeLong(value.toBits()) +fun InputStream.readDouble() = Double.fromBits(readLong()) + +fun InputStream.readBinaryString(): String { + val size = readVarInt() + require(size >= 0) { "Negative payload size: $size" } + val bytes = ByteArray(size) + read(bytes) + return bytes.decodeToString() +} + +fun OutputStream.writeBinaryString(input: String) { + val bytes = input.encodeToByteArray() + writeVarInt(bytes.size) + write(bytes) +} diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/json/BinaryJsonReader.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/json/BinaryJsonReader.kt index 7e2798a3..6ed4426e 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/json/BinaryJsonReader.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/json/BinaryJsonReader.kt @@ -9,16 +9,73 @@ import com.google.gson.JsonPrimitive import com.google.gson.JsonSyntaxException import com.google.gson.stream.JsonReader import com.google.gson.stream.JsonToken -import ru.dbotthepony.kstarbound.Starbound +import ru.dbotthepony.kstarbound.io.readBinaryString +import ru.dbotthepony.kstarbound.io.readSignedVarLong import ru.dbotthepony.kstarbound.io.readString import ru.dbotthepony.kstarbound.io.readVarInt -import ru.dbotthepony.kstarbound.io.readVarLong import java.io.DataInputStream import java.io.EOFException import java.io.InputStream import java.io.Reader import java.util.LinkedList +/** + * Позволяет читать двоичный JSON прямиком в [JsonElement] + */ +fun DataInputStream.readJsonElement(): JsonElement { + return when (val id = read()) { + BinaryJsonReader.TYPE_NULL -> JsonNull.INSTANCE + BinaryJsonReader.TYPE_DOUBLE -> JsonPrimitive(readDouble()) + BinaryJsonReader.TYPE_BOOLEAN -> InternedJsonElementAdapter.of(readBoolean()) + BinaryJsonReader.TYPE_INT -> JsonPrimitive(readSignedVarLong()) + BinaryJsonReader.TYPE_STRING -> JsonPrimitive(readBinaryString()) + BinaryJsonReader.TYPE_ARRAY -> readJsonArray() + BinaryJsonReader.TYPE_OBJECT -> readJsonObject() + else -> throw JsonParseException("Unknown element type $id") + } +} + +/** + * Позволяет читать двоичный JSON объект прямиком в [JsonObject] + */ +fun DataInputStream.readJsonObject(): JsonObject { + val values = readVarInt() + if (values == 0) return JsonObject() + if (values < 0) throw JsonSyntaxException("Tried to read json object with $values elements in it") + + val build = JsonObject() + + for (i in 0 until values) { + val key = try { + readBinaryString() + } catch(err: Throwable) { + throw JsonSyntaxException("Reading json object at $i", err) + } + + try { + build.add(key, readJsonElement()) + } catch(err: Throwable) { + throw JsonSyntaxException("Reading json object at $i with name $key", err) + } + } + + return build +} + +/** + * Позволяет читать двоичный JSON массив прямиком в [JsonArray] + */ +fun DataInputStream.readJsonArray(): JsonArray { + val values = readVarInt() + + if (values == 0) return JsonArray() + if (values < 0) throw JsonSyntaxException("Tried to read json array with $values elements in it") + + val build = JsonArray(values) + for (i in 0 until values) build.add(readJsonElement()) + return build +} + class BinaryJsonReader(private val stream: DataInputStream) : JsonReader(unreadable) { constructor(stream: InputStream) : this(DataInputStream(stream)) @@ -35,7 +92,7 @@ class BinaryJsonReader(private val stream: DataInputStream) : JsonReader(unreada TYPE_NULL -> stack.addLast(nullReader) TYPE_DOUBLE -> stack.addLast(DoubleReader(stream.readDouble())) TYPE_BOOLEAN -> stack.addLast(if (stream.readBoolean()) trueReader else falseReader) - TYPE_INT -> stack.addLast(LongReader(fixSignedInt(stream.readVarLong()))) + TYPE_INT -> stack.addLast(LongReader(stream.readSignedVarLong())) TYPE_STRING -> stack.addLast(StringReader(stream.readVarInt())) TYPE_OBJECT -> stack.addLast(ObjectReader(stream.readVarInt())) TYPE_ARRAY -> stack.addLast(ArrayReader(stream.readVarInt())) @@ -343,79 +400,6 @@ class BinaryJsonReader(private val stream: DataInputStream) : JsonReader(unreada const val TYPE_ARRAY = 0x06 const val TYPE_OBJECT = 0x07 - private fun fixSignedInt(read: Long): Long { - val sign = read and 0x1L - @Suppress("name_shadowing") - val read = read ushr 1 - - if (sign == 1L) { - return -read - 1L - } else { - return read - } - } - - fun readString(reader: DataInputStream): String { - return Starbound.STRINGS.intern(reader.readString(reader.readVarInt())) - } - - /** - * Позволяет читать двоичный JSON прямиком в [JsonElement] - */ - fun readElement(reader: DataInputStream): JsonElement { - return when (val id = reader.read()) { - TYPE_NULL -> JsonNull.INSTANCE - TYPE_DOUBLE -> JsonPrimitive(reader.readDouble()) - TYPE_BOOLEAN -> InternedJsonElementAdapter.of(reader.readBoolean()) - TYPE_INT -> JsonPrimitive(fixSignedInt(reader.readVarLong())) - TYPE_STRING -> JsonPrimitive(readString(reader)) - TYPE_ARRAY -> readArray(reader) - TYPE_OBJECT -> readObject(reader) - else -> throw JsonParseException("Unknown element type $id") - } - } - - /** - * Позволяет читать двоичный JSON объект прямиком в [JsonObject] - */ - fun readObject(reader: DataInputStream): JsonObject { - val values = reader.readVarInt() - if (values == 0) return JsonObject() - if (values < 0) throw JsonSyntaxException("Tried to read json object with $values elements in it") - - val build = JsonObject() - - for (i in 0 until values) { - val key = try { - readString(reader) - } catch(err: Throwable) { - throw JsonSyntaxException("Reading json object at $i", err) - } - - try { - build.add(key, readElement(reader)) - } catch(err: Throwable) { - throw JsonSyntaxException("Reading json object at $i with name $key", err) - } - } - - return build - } - - /** - * Позволяет читать двоичный JSON массив прямиком в [JsonArray] - */ - fun readArray(reader: DataInputStream): JsonArray { - val values = reader.readVarInt() - - if (values == 0) return JsonArray() - if (values < 0) throw JsonSyntaxException("Tried to read json array with $values elements in it") - - val build = JsonArray(values) - for (i in 0 until values) build.add(readElement(reader)) - return build - } - private val unreadable = object : Reader() { override fun read(cbuf: CharArray, off: Int, len: Int): Int { throw AssertionError() diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/json/BinaryJsonWriter.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/json/BinaryJsonWriter.kt new file mode 100644 index 00000000..07b6bf0d --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/json/BinaryJsonWriter.kt @@ -0,0 +1,71 @@ +package ru.dbotthepony.kstarbound.json + +import com.google.gson.JsonArray +import com.google.gson.JsonElement +import com.google.gson.JsonNull +import com.google.gson.JsonObject +import com.google.gson.JsonPrimitive +import ru.dbotthepony.kstarbound.io.writeBinaryString +import ru.dbotthepony.kstarbound.io.writeSignedVarLong +import ru.dbotthepony.kstarbound.io.writeVarInt +import java.io.DataOutputStream + +fun DataOutputStream.writeJsonElement(value: JsonElement) { + when (value) { + is JsonNull -> write(BinaryJsonReader.TYPE_NULL) + is JsonPrimitive -> { + if (value.isNumber) { + when (val v = value.asNumber) { + is Float, is Double -> { + write(BinaryJsonReader.TYPE_DOUBLE) + writeDouble(v.toDouble()) + } + + is Int, is Long, is Byte, is Short -> { + write(BinaryJsonReader.TYPE_INT) + writeSignedVarLong(v.toLong()) + } + + else -> { + throw IllegalArgumentException("Unknown number type: ${v::class} ($v)") + } + } + } else if (value.isString) { + write(BinaryJsonReader.TYPE_STRING) + writeBinaryString(value.asString) + } else if (value.isBoolean) { + write(BinaryJsonReader.TYPE_BOOLEAN) + write(if (value.asBoolean) 1 else 0) + } else { + throw IllegalArgumentException("Unknown primitive value type: $value") + } + } + + is JsonObject -> { + write(BinaryJsonReader.TYPE_OBJECT) + writeJsonObject(value) + } + + is JsonArray -> { + write(BinaryJsonReader.TYPE_ARRAY) + writeJsonArray(value) + } + } +} + +fun DataOutputStream.writeJsonObject(value: JsonObject) { + writeVarInt(value.size()) + + for ((k, v) in value.entrySet()) { + writeBinaryString(k) + writeJsonElement(v) + } +} + +fun DataOutputStream.writeJsonArray(value: JsonArray) { + writeVarInt(value.size()) + + for (v in value) { + writeJsonElement(v) + } +} diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/json/VersionedJson.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/json/VersionedJson.kt index 1039fb1c..1f6747c2 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/json/VersionedJson.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/json/VersionedJson.kt @@ -1,11 +1,12 @@ package ru.dbotthepony.kstarbound.json import com.google.gson.JsonElement +import ru.dbotthepony.kstarbound.io.readBinaryString import java.io.DataInputStream data class VersionedJson(val identifier: String, val version: Int?, val content: JsonElement) { constructor(data: DataInputStream) : this( - BinaryJsonReader.readString(data), + data.readBinaryString(), data.read().let { if (it > 0) data.readInt() else null }, - BinaryJsonReader.readElement(data)) + data.readJsonElement()) } diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/network/Connection.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/network/Connection.kt index e32c5a34..0d408e6f 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/network/Connection.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/network/Connection.kt @@ -9,7 +9,6 @@ import org.apache.logging.log4j.LogManager import ru.dbotthepony.kstarbound.network.packets.DisconnectPacket import ru.dbotthepony.kstarbound.network.packets.HelloListener import ru.dbotthepony.kstarbound.network.packets.HelloPacket -import ru.dbotthepony.kstarbound.network.packets.PacketMapping import java.util.* abstract class Connection(val side: ConnectionSide, val type: ConnectionType, val localUUID: UUID) : ChannelInboundHandlerAdapter(), IConnectionDetails { @@ -44,7 +43,7 @@ abstract class Connection(val side: ConnectionSide, val type: ConnectionType, va protected abstract fun inGame() fun helloReceived(helloPacket: HelloPacket) { - println("Hello received $side: $helloPacket") + LOGGER.info("${side.opposite} HELLO Received from ${channel?.remoteAddress()}: $helloPacket") otherSide = helloPacket if (side == ConnectionSide.SERVER) { @@ -63,20 +62,20 @@ abstract class Connection(val side: ConnectionSide, val type: ConnectionType, va val channel = channel ?: throw IllegalStateException("No network channel is bound") if (type == ConnectionType.NETWORK) { - channel.pipeline().addLast(PacketMapping.Inbound(side)) + channel.pipeline().addLast(PacketRegistry.Inbound(side)) } else { - channel.pipeline().addLast(PacketMapping.InboundValidator(side)) + channel.pipeline().addLast(PacketRegistry.InboundValidator(side)) } channel.pipeline().addLast(this) if (type == ConnectionType.NETWORK) { - channel.pipeline().addLast(PacketMapping.Outbound(side)) + channel.pipeline().addLast(PacketRegistry.Outbound(side)) channel.pipeline().addFirst(DatagramEncoder) channel.pipeline().addFirst(DatagramDecoder()) } else { - channel.pipeline().addLast(PacketMapping.OutboundValidator(side)) + channel.pipeline().addLast(PacketRegistry.OutboundValidator(side)) } inGame() diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/network/PacketMapper.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/network/PacketRegistry.kt similarity index 76% rename from src/main/kotlin/ru/dbotthepony/kstarbound/network/PacketMapper.kt rename to src/main/kotlin/ru/dbotthepony/kstarbound/network/PacketRegistry.kt index 0b4fb87e..a24ced89 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/network/PacketMapper.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/network/PacketRegistry.kt @@ -9,11 +9,18 @@ import io.netty.channel.ChannelOutboundHandlerAdapter import io.netty.channel.ChannelPromise import it.unimi.dsi.fastutil.objects.Reference2ObjectOpenHashMap import org.apache.logging.log4j.LogManager +import ru.dbotthepony.kstarbound.client.network.packets.ForgetChunkPacket +import ru.dbotthepony.kstarbound.client.network.packets.InitialChunkDataPacket +import ru.dbotthepony.kstarbound.client.network.packets.JoinWorldPacket +import ru.dbotthepony.kstarbound.client.network.packets.SpawnWorldObjectPacket +import ru.dbotthepony.kstarbound.network.packets.DisconnectPacket +import ru.dbotthepony.kstarbound.server.network.packets.TrackedPositionPacket +import ru.dbotthepony.kstarbound.server.network.packets.TrackedSizePacket import java.io.DataInputStream import java.io.DataOutputStream import kotlin.reflect.KClass -class PacketMapper { +object PacketRegistry { private val packets = ArrayList>() private val clazz2Type = Reference2ObjectOpenHashMap, Type<*>>() @@ -22,7 +29,7 @@ class PacketMapper { val size: Int get() = packets.size - fun add(type: KClass, reader: IPacketReader, direction: PacketDirection = PacketDirection.get(type)): PacketMapper { + fun add(type: KClass, reader: IPacketReader, direction: PacketDirection = PacketDirection.get(type)): PacketRegistry { if (packets.size >= 255) throw IndexOutOfBoundsException("Unable to add any more packet types! 255 is the max") @@ -35,11 +42,11 @@ class PacketMapper { return this } - inline fun add(reader: IPacketReader, direction: PacketDirection = PacketDirection.get(T::class)): PacketMapper { + inline fun add(reader: IPacketReader, direction: PacketDirection = PacketDirection.get(T::class)): PacketRegistry { return add(T::class, reader, direction) } - inner class Inbound(val side: ConnectionSide) : ChannelInboundHandlerAdapter() { + class Inbound(val side: ConnectionSide) : ChannelInboundHandlerAdapter() { override fun channelRead(ctx: ChannelHandlerContext, msg: Any) { if (msg is ByteBuf) { val packetType = msg.readUnsignedByte().toInt() @@ -66,7 +73,7 @@ class PacketMapper { } } - inner class InboundValidator(val side: ConnectionSide) : ChannelInboundHandlerAdapter() { + class InboundValidator(val side: ConnectionSide) : ChannelInboundHandlerAdapter() { override fun channelRead(ctx: ChannelHandlerContext, msg: Any) { val type = clazz2Type[msg::class] @@ -84,7 +91,7 @@ class PacketMapper { } } - inner class Outbound(val side: ConnectionSide) : ChannelOutboundHandlerAdapter() { + class Outbound(val side: ConnectionSide) : ChannelOutboundHandlerAdapter() { override fun write(ctx: ChannelHandlerContext, msg: Any, promise: ChannelPromise) { val type = clazz2Type[msg::class] @@ -101,7 +108,7 @@ class PacketMapper { } } - inner class OutboundValidator(val side: ConnectionSide) : ChannelOutboundHandlerAdapter() { + class OutboundValidator(val side: ConnectionSide) : ChannelOutboundHandlerAdapter() { override fun write(ctx: ChannelHandlerContext, msg: Any, promise: ChannelPromise) { val type = clazz2Type[msg::class] @@ -115,7 +122,15 @@ class PacketMapper { } } - companion object { - private val LOGGER = LogManager.getLogger() + private val LOGGER = LogManager.getLogger() + + init { + add(::DisconnectPacket) + add(::JoinWorldPacket) + add(::InitialChunkDataPacket) + add(::ForgetChunkPacket) + add(::TrackedPositionPacket) + add(::TrackedSizePacket) + add(::SpawnWorldObjectPacket) } } diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/network/packets/PacketMapping.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/network/packets/PacketMapping.kt deleted file mode 100644 index 83d18c62..00000000 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/network/packets/PacketMapping.kt +++ /dev/null @@ -1,17 +0,0 @@ -package ru.dbotthepony.kstarbound.network.packets - -import ru.dbotthepony.kstarbound.client.network.packets.ForgetChunkPacket -import ru.dbotthepony.kstarbound.client.network.packets.InitialChunkDataPacket -import ru.dbotthepony.kstarbound.client.network.packets.JoinWorldPacket -import ru.dbotthepony.kstarbound.client.network.packets.TrackedPositionPacket -import ru.dbotthepony.kstarbound.client.network.packets.TrackedSizePacket -import ru.dbotthepony.kstarbound.network.PacketMapper - -val PacketMapping = PacketMapper().also { - it.add(::DisconnectPacket) - it.add(::JoinWorldPacket) - it.add(::InitialChunkDataPacket) - it.add(::ForgetChunkPacket) - it.add(::TrackedPositionPacket) - it.add(::TrackedSizePacket) -} diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/network/packets/TehnicalPackets.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/network/packets/TehnicalPackets.kt index a8058e17..18338e35 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/network/packets/TehnicalPackets.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/network/packets/TehnicalPackets.kt @@ -16,8 +16,8 @@ import ru.dbotthepony.kstarbound.network.IServerPacket import ru.dbotthepony.kstarbound.network.readUTF import ru.dbotthepony.kstarbound.network.writeUTF import ru.dbotthepony.kstarbound.server.network.ServerConnection -import ru.dbotthepony.kstarbound.util.readUUID -import ru.dbotthepony.kstarbound.util.writeUUID +import ru.dbotthepony.kstarbound.io.readUUID +import ru.dbotthepony.kstarbound.io.writeUUID import java.io.DataInputStream import java.io.DataOutputStream import java.util.UUID diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/server/network/ServerChannels.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/server/network/ServerChannels.kt index 22cb3c2b..338a561d 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/server/network/ServerChannels.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/server/network/ServerChannels.kt @@ -7,6 +7,7 @@ import io.netty.channel.ChannelInitializer import io.netty.channel.local.LocalAddress import io.netty.channel.local.LocalServerChannel import io.netty.channel.socket.nio.NioServerSocketChannel +import org.apache.logging.log4j.LogManager import ru.dbotthepony.kstarbound.network.Connection import ru.dbotthepony.kstarbound.network.ConnectionSide import ru.dbotthepony.kstarbound.network.ConnectionType @@ -41,6 +42,7 @@ class ServerChannels(val server: StarboundServer) : Closeable { val channel = ServerBootstrap().channel(LocalServerChannel::class.java).group(Connection.NIO_POOL).childHandler(object : ChannelInitializer() { override fun initChannel(ch: Channel) { + LOGGER.info("Incoming connection from ${ch.remoteAddress()}") val connection = ServerConnection(server, ConnectionType.MEMORY) connections.add(connection) connection.bind(ch) @@ -63,6 +65,7 @@ class ServerChannels(val server: StarboundServer) : Closeable { lock.withLock { val channel = ServerBootstrap().channel(NioServerSocketChannel::class.java).group(Connection.NIO_POOL).childHandler(object : ChannelInitializer() { override fun initChannel(ch: Channel) { + LOGGER.info("Incoming connection from ${ch.remoteAddress()}") val connection = ServerConnection(server, ConnectionType.NETWORK) connections.add(connection) connection.bind(ch) @@ -90,4 +93,8 @@ class ServerChannels(val server: StarboundServer) : Closeable { connections.clear() } } + + companion object { + private val LOGGER = LogManager.getLogger() + } } diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/server/network/ServerPlayer.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/server/network/ServerPlayer.kt index f80f0b0f..db080856 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/server/network/ServerPlayer.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/server/network/ServerPlayer.kt @@ -5,6 +5,7 @@ import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet import ru.dbotthepony.kstarbound.client.network.packets.ForgetChunkPacket import ru.dbotthepony.kstarbound.client.network.packets.InitialChunkDataPacket +import ru.dbotthepony.kstarbound.client.network.packets.SpawnWorldObjectPacket import ru.dbotthepony.kstarbound.network.Player import ru.dbotthepony.kstarbound.server.StarboundServer import ru.dbotthepony.kstarbound.server.world.ServerWorld @@ -104,6 +105,11 @@ class ServerPlayer(connection: ServerConnection) : Player(conn if (chunk != null) { connection.send(InitialChunkDataPacket(chunk)) + + chunk.objects.forEach { + connection.send(SpawnWorldObjectPacket(it.serialize())) + } + sentChunks.add(pos) } } diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/server/network/packets/TrackedPositionPacket.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/server/network/packets/TrackedPositionPacket.kt new file mode 100644 index 00000000..fa0a3a33 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/server/network/packets/TrackedPositionPacket.kt @@ -0,0 +1,21 @@ +package ru.dbotthepony.kstarbound.server.network.packets + +import ru.dbotthepony.kstarbound.network.IServerPacket +import ru.dbotthepony.kstarbound.server.network.ServerConnection +import ru.dbotthepony.kstarbound.io.readVec2d +import ru.dbotthepony.kstarbound.io.writeVec2d +import ru.dbotthepony.kvector.vector.Vector2d +import java.io.DataInputStream +import java.io.DataOutputStream + +data class TrackedPositionPacket(val pos: Vector2d) : IServerPacket { + constructor(stream: DataInputStream) : this(stream.readVec2d()) + + override fun write(stream: DataOutputStream) { + stream.writeVec2d(pos) + } + + override fun play(connection: ServerConnection) { + connection.player.trackedPosition = pos + } +} \ No newline at end of file diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/client/network/packets/TrackedPositionPacket.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/server/network/packets/TrackedSizePacket.kt similarity index 60% rename from src/main/kotlin/ru/dbotthepony/kstarbound/client/network/packets/TrackedPositionPacket.kt rename to src/main/kotlin/ru/dbotthepony/kstarbound/server/network/packets/TrackedSizePacket.kt index e349fdf2..b3c2a1f6 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/client/network/packets/TrackedPositionPacket.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/server/network/packets/TrackedSizePacket.kt @@ -1,25 +1,10 @@ -package ru.dbotthepony.kstarbound.client.network.packets +package ru.dbotthepony.kstarbound.server.network.packets import ru.dbotthepony.kstarbound.network.IServerPacket import ru.dbotthepony.kstarbound.server.network.ServerConnection -import ru.dbotthepony.kstarbound.util.readVec2d -import ru.dbotthepony.kstarbound.util.writeVec2d -import ru.dbotthepony.kvector.vector.Vector2d import java.io.DataInputStream import java.io.DataOutputStream -data class TrackedPositionPacket(val pos: Vector2d) : IServerPacket { - constructor(stream: DataInputStream) : this(stream.readVec2d()) - - override fun write(stream: DataOutputStream) { - stream.writeVec2d(pos) - } - - override fun play(connection: ServerConnection) { - connection.player.trackedPosition = pos - } -} - data class TrackedSizePacket(val width: Int, val height: Int) : IServerPacket { constructor(stream: DataInputStream) : this(stream.readUnsignedByte(), stream.readUnsignedByte()) diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/server/world/LegacyChunkSource.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/server/world/LegacyChunkSource.kt index e1b4ddea..1a458518 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/server/world/LegacyChunkSource.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/server/world/LegacyChunkSource.kt @@ -61,12 +61,7 @@ class LegacyChunkSource(val db: BTreeDB) : IChunkSource { if (obj.identifier == "ObjectEntity") { try { - val content = obj.content.asJsonObject - val prototype = Registries.worldObjects[content["name"]?.asString ?: throw IllegalArgumentException("Missing object name")] ?: throw IllegalArgumentException("No such object defined for '${content["name"]}'") - val pos = content.get("tilePosition", vectors) { throw IllegalArgumentException("No tilePosition was present in saved data") } - val result = WorldObject(prototype, pos) - result.deserialize(content) - objects.add(result) + objects.add(WorldObject.fromJson(obj.content.asJsonObject)) } catch (err: Throwable) { LOGGER.error("Unable to deserialize entity in chunk $pos", err) } diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/tools/Sbon2Json.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/tools/Sbon2Json.kt index 42dc2701..0ea8a54a 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/tools/Sbon2Json.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/tools/Sbon2Json.kt @@ -1,10 +1,10 @@ package ru.dbotthepony.kstarbound.tools import com.google.gson.GsonBuilder -import ru.dbotthepony.kstarbound.json.BinaryJsonReader import ru.dbotthepony.kstarbound.io.readHeader import ru.dbotthepony.kstarbound.io.readString import ru.dbotthepony.kstarbound.io.readVarInt +import ru.dbotthepony.kstarbound.json.readJsonElement import java.io.BufferedInputStream import java.io.DataInputStream import java.io.File @@ -96,7 +96,7 @@ fun main(vararg args: String) { val name = dataStream.readString(dataStream.readVarInt()) val magic = dataStream.read() val version = dataStream.readInt() - val data = BinaryJsonReader.readElement(dataStream) + val data = dataStream.readJsonElement() val gson = GsonBuilder().setPrettyPrinting().create() dataStream.close() diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/util/StreamUtils.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/util/StreamUtils.kt deleted file mode 100644 index 8f6d4b50..00000000 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/util/StreamUtils.kt +++ /dev/null @@ -1,299 +0,0 @@ -package ru.dbotthepony.kstarbound.util - -import io.netty.buffer.ByteBuf -import it.unimi.dsi.fastutil.bytes.ByteArrayList -import ru.dbotthepony.kstarbound.world.ChunkPos -import ru.dbotthepony.kvector.api.IStruct2d -import ru.dbotthepony.kvector.api.IStruct2i -import ru.dbotthepony.kvector.vector.Vector2d -import ru.dbotthepony.kvector.vector.Vector2i -import java.io.DataInput -import java.io.DataOutput -import java.io.InputStream -import java.io.OutputStream -import java.math.BigDecimal -import java.math.BigInteger -import java.util.* -import kotlin.math.absoluteValue - -fun OutputStream.writeLEInt(value: Int) { - write(value) - write(value ushr 8) - write(value ushr 16) - write(value ushr 24) -} - -fun OutputStream.writeLEShort(value: Int) { - write(value) - write(value ushr 8) -} - -fun OutputStream.writeLELong(value: Long) { - writeLEInt(value.toInt()) - writeLEInt((value ushr 32).toInt()) -} - -fun OutputStream.writeLEFloat(value: Float) = writeLEInt(value.toBits()) -fun OutputStream.writeLEDouble(value: Double) = writeLELong(value.toBits()) - -fun InputStream.readLEShort(): Int { - return read() or (read() shl 8) -} - -fun InputStream.readLEInt(): Int { - return read() or (read() shl 8) or (read() shl 16) or (read() shl 24) -} - -fun InputStream.readLELong(): Long { - return readLEInt().toLong() or (readLEInt().toLong() shl 32) -} - -fun InputStream.readLEFloat() = Float.fromBits(readLEInt()) -fun InputStream.readLEDouble() = Double.fromBits(readLELong()) - -fun OutputStream.writeUTF(value: String) { - write(value.toByteArray().also { check(!it.any { it.toInt() == 0 }) { "Provided UTF string contains NUL" } }) - write(0) -} - -fun InputStream.readUTF(): String { - val bytes = ByteArrayList() - var read = read() - - while (read != 0) { - bytes.add(read.toByte()) - read = read() - } - - return String(bytes.toByteArray()) -} - -fun InputStream.readUUID(): UUID { - return UUID(readLong(), readLong()) -} - -fun OutputStream.writeUUID(value: UUID) { - writeLong(value.mostSignificantBits) - writeLong(value.leastSignificantBits) -} - -fun OutputStream.writeVec2i(value: IStruct2i) { - writeInt(value.component1()) - writeInt(value.component2()) -} - -fun OutputStream.writeVec2d(value: IStruct2d) { - writeDouble(value.component1()) - writeDouble(value.component2()) -} - -fun InputStream.readVec2i(): Vector2i { - return Vector2i(readInt(), readInt()) -} - -fun InputStream.readVec2d(): Vector2d { - return Vector2d(readDouble(), readDouble()) -} - -fun InputStream.readChunkPos(): ChunkPos { - return ChunkPos(readInt(), readInt()) -} - -fun OutputStream.writeBigDecimal(value: BigDecimal) { - writeInt(value.scale()) - val bytes = value.unscaledValue().toByteArray() - writeVarIntLE(bytes.size) - write(bytes) -} - -fun InputStream.readBigDecimal(): BigDecimal { - val scale = readInt() - val size = readVarIntLE() - require(size >= 0) { "Negative payload size: $size" } - val bytes = ByteArray(size) - read(bytes) - return BigDecimal(BigInteger(bytes), scale) -} - -fun S.writeCollection(collection: Collection, writer: S.(V) -> Unit) { - writeVarIntLE(collection.size) - - for (value in collection) { - writer(this, value) - } -} - -fun > S.readCollection(reader: S.() -> V, factory: (Int) -> C): C { - val size = readVarIntLE() - val collection = factory.invoke(size) - - for (i in 0 until size) { - collection.add(reader(this)) - } - - return collection -} - -fun S.readCollection(reader: S.() -> V) = readCollection(reader, ::ArrayList) - -fun OutputStream.writeInt(value: Int) { - if (this is DataOutput) { - writeInt(value) - return - } - - write(value ushr 24) - write(value ushr 16) - write(value ushr 8) - write(value) -} - -fun InputStream.readInt(): Int { - if (this is DataInput) { - return readInt() - } - - return (read() shl 24) or (read() shl 16) or (read() shl 8) or read() -} - -fun OutputStream.writeLong(value: Long) { - if (this is DataOutput) { - writeLong(value) - return - } - - write((value ushr 48).toInt()) - write((value ushr 40).toInt()) - write((value ushr 32).toInt()) - write((value ushr 24).toInt()) - write((value ushr 16).toInt()) - write((value ushr 8).toInt()) - write(value.toInt()) -} - -fun InputStream.readLong(): Long { - if (this is DataInput) { - return readLong() - } - - return (read().toLong() shl 48) or - (read().toLong() shl 40) or - (read().toLong() shl 32) or - (read().toLong() shl 24) or - (read().toLong() shl 16) or - (read().toLong() shl 8) or - read().toLong() -} - -fun OutputStream.writeFloat(value: Float) = writeInt(value.toBits()) -fun InputStream.readFloat() = Float.fromBits(readInt()) -fun OutputStream.writeDouble(value: Double) = writeLong(value.toBits()) -fun InputStream.readDouble() = Double.fromBits(readLong()) - -fun InputStream.readVarIntLE(): Int { - val readFirst = read() - - if (readFirst < 0) { - throw NoSuchElementException("Reached end of stream") - } - - if (readFirst and 64 == 0) { - return if (readFirst and 128 != 0) -(readFirst and 63) else readFirst and 63 - } - - var result = 0 - var nextBit = readFirst and 64 - var read = readFirst and 63 - var i = 0 - - while (nextBit != 0) { - result = result or (read shl i) - read = read() - - if (read < 0) { - throw NoSuchElementException("Reached end of stream") - } - - nextBit = read and 128 - read = read and 127 - - if (i == 0) - i = 6 - else - i += 7 - } - - result = result or (read shl i) - return if (readFirst and 128 != 0) -result else result -} - -fun OutputStream.writeVarIntLE(value: Int) { - write((if (value < 0) 128 else 0) or (if (value in -63 .. 63) 0 else 64) or (value.absoluteValue and 63)) - var written = value.absoluteValue ushr 6 - - while (written != 0) { - write((written and 127) or (if (written >= 128) 128 else 0)) - written = written ushr 7 - } -} - -fun InputStream.readVarLongLE(): Long { - val readFirst = read() - - if (readFirst < 0) { - throw NoSuchElementException("Reached end of stream") - } - - if (readFirst and 64 == 0) { - return if (readFirst and 128 != 0) -(readFirst and 63).toLong() else (readFirst and 63).toLong() - } - - var result = 0L - var nextBit = readFirst and 64 - var read = readFirst and 63 - var i = 0 - - while (nextBit != 0) { - result = result or (read shl i).toLong() - read = read() - - if (read < 0) { - throw NoSuchElementException("Reached end of stream") - } - - nextBit = read and 128 - read = read and 127 - - if (i == 0) - i = 6 - else - i += 7 - } - - result = result or (read shl i).toLong() - return if (readFirst and 128 != 0) -result else result -} - -fun OutputStream.writeVarLongLE(value: Long) { - write((if (value < 0L) 128 else 0) or (if (value in -63 .. 63) 0 else 64) or (value.absoluteValue and 63).toInt()) - var written = value.absoluteValue ushr 6 - - while (written != 0L) { - write((written and 127).toInt() or (if (written >= 128) 128 else 0)) - written = written ushr 7 - } -} - -fun InputStream.readBinaryString(): String { - val size = readVarIntLE() - require(size >= 0) { "Negative payload size: $size" } - val bytes = ByteArray(size) - read(bytes) - return bytes.decodeToString() -} - -fun OutputStream.writeBinaryString(input: String) { - val bytes = input.encodeToByteArray() - writeVarIntLE(bytes.size) - write(bytes) -} diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/world/Chunk.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/world/Chunk.kt index 6719d2bd..26f75e55 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/world/Chunk.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/world/Chunk.kt @@ -44,8 +44,8 @@ abstract class Chunk, This : Chunk() - protected val objects = ReferenceOpenHashSet() + val entities = ReferenceOpenHashSet() + val objects = ReferenceOpenHashSet() protected val subscribers = ObjectArraySet() // local cells' tile access diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/world/WorldGeometry.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/world/WorldGeometry.kt index af79e201..d5214f56 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/world/WorldGeometry.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/world/WorldGeometry.kt @@ -1,7 +1,7 @@ package ru.dbotthepony.kstarbound.world -import ru.dbotthepony.kstarbound.util.readVec2i -import ru.dbotthepony.kstarbound.util.writeVec2i +import ru.dbotthepony.kstarbound.io.readVec2i +import ru.dbotthepony.kstarbound.io.writeVec2i import ru.dbotthepony.kvector.api.IStruct2d import ru.dbotthepony.kvector.api.IStruct2f import ru.dbotthepony.kvector.api.IStruct2i diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/world/api/AbstractTileState.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/world/api/AbstractTileState.kt index d4f08b57..7aa2928d 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/world/api/AbstractTileState.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/world/api/AbstractTileState.kt @@ -7,7 +7,6 @@ import ru.dbotthepony.kstarbound.defs.tile.BuiltinMetaMaterials import ru.dbotthepony.kstarbound.defs.tile.MaterialModifier import ru.dbotthepony.kstarbound.defs.tile.TileDefinition import ru.dbotthepony.kstarbound.util.HashTableInterner -import ru.dbotthepony.kstarbound.util.writeUTF import java.io.DataInputStream import java.io.DataOutputStream diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/world/entities/WorldObject.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/world/entities/WorldObject.kt index 6130b08f..d4e24bf7 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/world/entities/WorldObject.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/world/entities/WorldObject.kt @@ -14,6 +14,7 @@ import ru.dbotthepony.kstarbound.defs.JsonDriven import ru.dbotthepony.kstarbound.defs.image.SpriteReference import ru.dbotthepony.kstarbound.defs.`object`.ObjectDefinition import ru.dbotthepony.kstarbound.defs.`object`.ObjectOrientation +import ru.dbotthepony.kstarbound.server.world.LegacyChunkSource import ru.dbotthepony.kstarbound.server.world.ServerWorld import ru.dbotthepony.kstarbound.util.MailboxExecutorService import ru.dbotthepony.kstarbound.util.get @@ -46,6 +47,22 @@ open class WorldObject( } } + fun serialize(): JsonObject { + val into = JsonObject() + into["name"] = prototype.key + into["tilePosition"] = vectors.toJsonTree(pos) + into["direction"] = directions.toJsonTree(direction) + into["orientationIndex"] = orientationIndex + into["interactive"] = interactive + + if (uniqueId != null) { + into["uniqueId"] = uniqueId!! + } + + into["parameters"] = properties.deepCopy() + return into + } + val mailbox = MailboxExecutorService() var world: World<*, *> by Delegates.notNull() private set @@ -197,5 +214,13 @@ open class WorldObject( private val strings by lazy { Starbound.gson.getAdapter(String::class.java) } private val directions by lazy { Starbound.gson.getAdapter(Side::class.java) } private val vectors by lazy { Starbound.gson.getAdapter(Vector2i::class.java) } + + fun fromJson(content: JsonObject): WorldObject { + val prototype = Registries.worldObjects[content["name"]?.asString ?: throw IllegalArgumentException("Missing object name")] ?: throw IllegalArgumentException("No such object defined for '${content["name"]}'") + val pos = content.get("tilePosition", vectors) { throw IllegalArgumentException("No tilePosition was present in saved data") } + val result = WorldObject(prototype, pos) + result.deserialize(content) + return result + } } }