Kind of more networking stuff

This commit is contained in:
DBotThePony 2024-02-02 12:09:22 +07:00
parent 2b95bf5e3e
commit 4d58f0ab71
Signed by: DBot
GPG Key ID: DCC23B5715498507
26 changed files with 548 additions and 602 deletions

View File

@ -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.gl.vertex.VertexBuilder
import ru.dbotthepony.kstarbound.client.input.UserInput import ru.dbotthepony.kstarbound.client.input.UserInput
import ru.dbotthepony.kstarbound.client.network.ClientConnection import ru.dbotthepony.kstarbound.client.network.ClientConnection
import ru.dbotthepony.kstarbound.client.network.packets.TrackedPositionPacket import ru.dbotthepony.kstarbound.server.network.packets.TrackedPositionPacket
import ru.dbotthepony.kstarbound.client.network.packets.TrackedSizePacket import ru.dbotthepony.kstarbound.server.network.packets.TrackedSizePacket
import ru.dbotthepony.kstarbound.client.render.Camera import ru.dbotthepony.kstarbound.client.render.Camera
import ru.dbotthepony.kstarbound.client.render.Font import ru.dbotthepony.kstarbound.client.render.Font
import ru.dbotthepony.kstarbound.client.render.LayeredRenderer 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.render.TileRenderers
import ru.dbotthepony.kstarbound.client.world.ClientWorld import ru.dbotthepony.kstarbound.client.world.ClientWorld
import ru.dbotthepony.kstarbound.defs.image.Image 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.Matrix3f
import ru.dbotthepony.kvector.arrays.Matrix3fStack import ru.dbotthepony.kvector.arrays.Matrix3fStack
import ru.dbotthepony.kvector.util2d.AABB import ru.dbotthepony.kvector.util2d.AABB
import ru.dbotthepony.kvector.util2d.AABBi
import ru.dbotthepony.kvector.vector.RGBAColor import ru.dbotthepony.kvector.vector.RGBAColor
import ru.dbotthepony.kvector.vector.Vector2d import ru.dbotthepony.kvector.vector.Vector2d
import ru.dbotthepony.kvector.vector.Vector2f import ru.dbotthepony.kvector.vector.Vector2f
import ru.dbotthepony.kvector.vector.Vector2i
import ru.dbotthepony.kvector.vector.Vector4f import ru.dbotthepony.kvector.vector.Vector4f
import java.io.Closeable import java.io.Closeable
import java.io.File import java.io.File

View File

@ -54,6 +54,7 @@ class ClientConnection(val client: StarboundClient, type: ConnectionType, uuid:
private val LOGGER = LogManager.getLogger() private val LOGGER = LogManager.getLogger()
fun connectToLocalServer(client: StarboundClient, address: LocalAddress, uuid: UUID): ClientConnection { 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) val connection = ClientConnection(client, ConnectionType.MEMORY, uuid)
Bootstrap() Bootstrap()
@ -73,6 +74,7 @@ class ClientConnection(val client: StarboundClient, type: ConnectionType, uuid:
} }
fun connectToRemoteServer(client: StarboundClient, address: SocketAddress, uuid: UUID): ClientConnection { 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) val connection = ClientConnection(client, ConnectionType.NETWORK, uuid)
Bootstrap() Bootstrap()

View File

@ -2,8 +2,8 @@ package ru.dbotthepony.kstarbound.client.network.packets
import ru.dbotthepony.kstarbound.client.network.ClientConnection import ru.dbotthepony.kstarbound.client.network.ClientConnection
import ru.dbotthepony.kstarbound.network.IClientPacket import ru.dbotthepony.kstarbound.network.IClientPacket
import ru.dbotthepony.kstarbound.util.readChunkPos import ru.dbotthepony.kstarbound.io.readChunkPos
import ru.dbotthepony.kstarbound.util.writeVec2i import ru.dbotthepony.kstarbound.io.writeVec2i
import ru.dbotthepony.kstarbound.world.ChunkPos import ru.dbotthepony.kstarbound.world.ChunkPos
import java.io.DataInputStream import java.io.DataInputStream
import java.io.DataOutputStream import java.io.DataOutputStream

View File

@ -2,10 +2,10 @@ package ru.dbotthepony.kstarbound.client.network.packets
import ru.dbotthepony.kstarbound.client.network.ClientConnection import ru.dbotthepony.kstarbound.client.network.ClientConnection
import ru.dbotthepony.kstarbound.network.IClientPacket import ru.dbotthepony.kstarbound.network.IClientPacket
import ru.dbotthepony.kstarbound.util.readChunkPos import ru.dbotthepony.kstarbound.io.readChunkPos
import ru.dbotthepony.kstarbound.util.readCollection import ru.dbotthepony.kstarbound.io.readCollection
import ru.dbotthepony.kstarbound.util.writeCollection import ru.dbotthepony.kstarbound.io.writeCollection
import ru.dbotthepony.kstarbound.util.writeVec2i import ru.dbotthepony.kstarbound.io.writeVec2i
import ru.dbotthepony.kstarbound.world.CHUNK_SIZE import ru.dbotthepony.kstarbound.world.CHUNK_SIZE
import ru.dbotthepony.kstarbound.world.Chunk import ru.dbotthepony.kstarbound.world.Chunk
import ru.dbotthepony.kstarbound.world.ChunkPos import ru.dbotthepony.kstarbound.world.ChunkPos

View File

@ -4,10 +4,10 @@ import io.netty.buffer.ByteBuf
import ru.dbotthepony.kstarbound.client.network.ClientConnection import ru.dbotthepony.kstarbound.client.network.ClientConnection
import ru.dbotthepony.kstarbound.client.world.ClientWorld import ru.dbotthepony.kstarbound.client.world.ClientWorld
import ru.dbotthepony.kstarbound.network.IClientPacket import ru.dbotthepony.kstarbound.network.IClientPacket
import ru.dbotthepony.kstarbound.util.readUUID import ru.dbotthepony.kstarbound.io.readUUID
import ru.dbotthepony.kstarbound.util.readVec2i import ru.dbotthepony.kstarbound.io.readVec2i
import ru.dbotthepony.kstarbound.util.writeUUID import ru.dbotthepony.kstarbound.io.writeUUID
import ru.dbotthepony.kstarbound.util.writeVec2i import ru.dbotthepony.kstarbound.io.writeVec2i
import ru.dbotthepony.kstarbound.world.World import ru.dbotthepony.kstarbound.world.World
import ru.dbotthepony.kstarbound.world.WorldGeometry import ru.dbotthepony.kstarbound.world.WorldGeometry
import ru.dbotthepony.kvector.vector.Vector2i import ru.dbotthepony.kvector.vector.Vector2i

View File

@ -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)
}
}
}

View File

@ -4,7 +4,7 @@ import it.unimi.dsi.fastutil.objects.Object2ObjectFunction
import it.unimi.dsi.fastutil.objects.Object2ObjectLinkedOpenHashMap import it.unimi.dsi.fastutil.objects.Object2ObjectLinkedOpenHashMap
import ru.dbotthepony.kstarbound.IStarboundFile import ru.dbotthepony.kstarbound.IStarboundFile
import ru.dbotthepony.kstarbound.getValue import ru.dbotthepony.kstarbound.getValue
import ru.dbotthepony.kstarbound.json.BinaryJsonReader import ru.dbotthepony.kstarbound.json.readJsonObject
import java.io.BufferedInputStream import java.io.BufferedInputStream
import java.io.Closeable import java.io.Closeable
import java.io.DataInputStream import java.io.DataInputStream
@ -182,7 +182,7 @@ class StarboundPak(val path: File, callback: (finished: Boolean, status: String)
} }
// сразу за INDEX идут метаданные в формате Binary Json // сразу за INDEX идут метаданные в формате Binary Json
val metadata = BinaryJsonReader.readObject(DataInputStream(object : InputStream() { val metadata = DataInputStream(object : InputStream() {
override fun read(): Int { override fun read(): Int {
return reader.read() 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 { override fun read(b: ByteArray, off: Int, len: Int): Int {
return reader.read(b, off, len) return reader.read(b, off, len)
} }
})) }).readJsonObject()
// сразу за метаданными идёт количество файлов внутри данного pak в формате Big Endian variable int // сразу за метаданными идёт количество файлов внутри данного pak в формате Big Endian variable int
val indexNodeCount = reader.readVarLong() val indexNodeCount = reader.readVarLong()

View File

@ -1,136 +1,158 @@
package ru.dbotthepony.kstarbound.io package ru.dbotthepony.kstarbound.io
import it.unimi.dsi.fastutil.bytes.ByteArrayList 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.IOException
import java.io.InputStream import java.io.InputStream
import java.io.OutputStream
import java.io.RandomAccessFile import java.io.RandomAccessFile
import java.math.BigDecimal
/** import java.util.*
* Читает Variable Length Integer как Long import java.util.function.IntConsumer
*/ import java.util.function.IntSupplier
fun RandomAccessFile.readVarLong(): Long { import kotlin.collections.ArrayList
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
}
data class VarIntReadResult(val value: Int, val cells: Int) data class VarIntReadResult(val value: Int, val cells: Int)
data class VarLongReadResult(val value: Long, val cells: Int)
/** private fun readVarLongInfo(supplier: IntSupplier): VarLongReadResult {
* Читает 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 {
var result = 0L var result = 0L
var read = read() var read = supplier.asInt
var i = 1
while (true) { 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 break
}
read = read() read = supplier.asInt
i++
} }
return result return VarLongReadResult(result, i)
} }
/** private fun readVarIntInfo(supplier: IntSupplier): VarIntReadResult {
* Читает Variable Length Integer как Int val read = readVarLongInfo(supplier)
*/ return VarIntReadResult(read.value.toInt(), read.cells)
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 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 { fun RandomAccessFile.readString(length: Int): String {
require(length >= 0) { "Invalid length $length" } 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) 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 { fun InputStream.readByteChar(): Char {
return read().toChar() 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 : OutputStream, V> S.writeCollection(collection: Collection<V>, writer: S.(V) -> Unit) {
writeVarInt(collection.size)
for (value in collection) {
writer(this, value)
}
}
fun <S : InputStream, V, C : MutableCollection<V>> 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 : InputStream, V> 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)
}

View File

@ -9,16 +9,73 @@ import com.google.gson.JsonPrimitive
import com.google.gson.JsonSyntaxException import com.google.gson.JsonSyntaxException
import com.google.gson.stream.JsonReader import com.google.gson.stream.JsonReader
import com.google.gson.stream.JsonToken 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.readString
import ru.dbotthepony.kstarbound.io.readVarInt import ru.dbotthepony.kstarbound.io.readVarInt
import ru.dbotthepony.kstarbound.io.readVarLong
import java.io.DataInputStream import java.io.DataInputStream
import java.io.EOFException import java.io.EOFException
import java.io.InputStream import java.io.InputStream
import java.io.Reader import java.io.Reader
import java.util.LinkedList 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) { class BinaryJsonReader(private val stream: DataInputStream) : JsonReader(unreadable) {
constructor(stream: InputStream) : this(DataInputStream(stream)) constructor(stream: InputStream) : this(DataInputStream(stream))
@ -35,7 +92,7 @@ class BinaryJsonReader(private val stream: DataInputStream) : JsonReader(unreada
TYPE_NULL -> stack.addLast(nullReader) TYPE_NULL -> stack.addLast(nullReader)
TYPE_DOUBLE -> stack.addLast(DoubleReader(stream.readDouble())) TYPE_DOUBLE -> stack.addLast(DoubleReader(stream.readDouble()))
TYPE_BOOLEAN -> stack.addLast(if (stream.readBoolean()) trueReader else falseReader) 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_STRING -> stack.addLast(StringReader(stream.readVarInt()))
TYPE_OBJECT -> stack.addLast(ObjectReader(stream.readVarInt())) TYPE_OBJECT -> stack.addLast(ObjectReader(stream.readVarInt()))
TYPE_ARRAY -> stack.addLast(ArrayReader(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_ARRAY = 0x06
const val TYPE_OBJECT = 0x07 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() { private val unreadable = object : Reader() {
override fun read(cbuf: CharArray, off: Int, len: Int): Int { override fun read(cbuf: CharArray, off: Int, len: Int): Int {
throw AssertionError() throw AssertionError()

View File

@ -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)
}
}

View File

@ -1,11 +1,12 @@
package ru.dbotthepony.kstarbound.json package ru.dbotthepony.kstarbound.json
import com.google.gson.JsonElement import com.google.gson.JsonElement
import ru.dbotthepony.kstarbound.io.readBinaryString
import java.io.DataInputStream import java.io.DataInputStream
data class VersionedJson(val identifier: String, val version: Int?, val content: JsonElement) { data class VersionedJson(val identifier: String, val version: Int?, val content: JsonElement) {
constructor(data: DataInputStream) : this( constructor(data: DataInputStream) : this(
BinaryJsonReader.readString(data), data.readBinaryString(),
data.read().let { if (it > 0) data.readInt() else null }, data.read().let { if (it > 0) data.readInt() else null },
BinaryJsonReader.readElement(data)) data.readJsonElement())
} }

View File

@ -9,7 +9,6 @@ import org.apache.logging.log4j.LogManager
import ru.dbotthepony.kstarbound.network.packets.DisconnectPacket import ru.dbotthepony.kstarbound.network.packets.DisconnectPacket
import ru.dbotthepony.kstarbound.network.packets.HelloListener import ru.dbotthepony.kstarbound.network.packets.HelloListener
import ru.dbotthepony.kstarbound.network.packets.HelloPacket import ru.dbotthepony.kstarbound.network.packets.HelloPacket
import ru.dbotthepony.kstarbound.network.packets.PacketMapping
import java.util.* import java.util.*
abstract class Connection(val side: ConnectionSide, val type: ConnectionType, val localUUID: UUID) : ChannelInboundHandlerAdapter(), IConnectionDetails { 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() protected abstract fun inGame()
fun helloReceived(helloPacket: HelloPacket) { fun helloReceived(helloPacket: HelloPacket) {
println("Hello received $side: $helloPacket") LOGGER.info("${side.opposite} HELLO Received from ${channel?.remoteAddress()}: $helloPacket")
otherSide = helloPacket otherSide = helloPacket
if (side == ConnectionSide.SERVER) { 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") val channel = channel ?: throw IllegalStateException("No network channel is bound")
if (type == ConnectionType.NETWORK) { if (type == ConnectionType.NETWORK) {
channel.pipeline().addLast(PacketMapping.Inbound(side)) channel.pipeline().addLast(PacketRegistry.Inbound(side))
} else { } else {
channel.pipeline().addLast(PacketMapping.InboundValidator(side)) channel.pipeline().addLast(PacketRegistry.InboundValidator(side))
} }
channel.pipeline().addLast(this) channel.pipeline().addLast(this)
if (type == ConnectionType.NETWORK) { if (type == ConnectionType.NETWORK) {
channel.pipeline().addLast(PacketMapping.Outbound(side)) channel.pipeline().addLast(PacketRegistry.Outbound(side))
channel.pipeline().addFirst(DatagramEncoder) channel.pipeline().addFirst(DatagramEncoder)
channel.pipeline().addFirst(DatagramDecoder()) channel.pipeline().addFirst(DatagramDecoder())
} else { } else {
channel.pipeline().addLast(PacketMapping.OutboundValidator(side)) channel.pipeline().addLast(PacketRegistry.OutboundValidator(side))
} }
inGame() inGame()

View File

@ -9,11 +9,18 @@ import io.netty.channel.ChannelOutboundHandlerAdapter
import io.netty.channel.ChannelPromise import io.netty.channel.ChannelPromise
import it.unimi.dsi.fastutil.objects.Reference2ObjectOpenHashMap import it.unimi.dsi.fastutil.objects.Reference2ObjectOpenHashMap
import org.apache.logging.log4j.LogManager 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.DataInputStream
import java.io.DataOutputStream import java.io.DataOutputStream
import kotlin.reflect.KClass import kotlin.reflect.KClass
class PacketMapper { object PacketRegistry {
private val packets = ArrayList<Type<*>>() private val packets = ArrayList<Type<*>>()
private val clazz2Type = Reference2ObjectOpenHashMap<KClass<*>, Type<*>>() private val clazz2Type = Reference2ObjectOpenHashMap<KClass<*>, Type<*>>()
@ -22,7 +29,7 @@ class PacketMapper {
val size: Int val size: Int
get() = packets.size get() = packets.size
fun <T : IPacket> add(type: KClass<T>, reader: IPacketReader<T>, direction: PacketDirection = PacketDirection.get(type)): PacketMapper { fun <T : IPacket> add(type: KClass<T>, reader: IPacketReader<T>, direction: PacketDirection = PacketDirection.get(type)): PacketRegistry {
if (packets.size >= 255) if (packets.size >= 255)
throw IndexOutOfBoundsException("Unable to add any more packet types! 255 is the max") throw IndexOutOfBoundsException("Unable to add any more packet types! 255 is the max")
@ -35,11 +42,11 @@ class PacketMapper {
return this return this
} }
inline fun <reified T : IPacket> add(reader: IPacketReader<T>, direction: PacketDirection = PacketDirection.get(T::class)): PacketMapper { inline fun <reified T : IPacket> add(reader: IPacketReader<T>, direction: PacketDirection = PacketDirection.get(T::class)): PacketRegistry {
return add(T::class, reader, direction) 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) { override fun channelRead(ctx: ChannelHandlerContext, msg: Any) {
if (msg is ByteBuf) { if (msg is ByteBuf) {
val packetType = msg.readUnsignedByte().toInt() 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) { override fun channelRead(ctx: ChannelHandlerContext, msg: Any) {
val type = clazz2Type[msg::class] 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) { override fun write(ctx: ChannelHandlerContext, msg: Any, promise: ChannelPromise) {
val type = clazz2Type[msg::class] 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) { override fun write(ctx: ChannelHandlerContext, msg: Any, promise: ChannelPromise) {
val type = clazz2Type[msg::class] 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)
} }
} }

View File

@ -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)
}

View File

@ -16,8 +16,8 @@ import ru.dbotthepony.kstarbound.network.IServerPacket
import ru.dbotthepony.kstarbound.network.readUTF import ru.dbotthepony.kstarbound.network.readUTF
import ru.dbotthepony.kstarbound.network.writeUTF import ru.dbotthepony.kstarbound.network.writeUTF
import ru.dbotthepony.kstarbound.server.network.ServerConnection import ru.dbotthepony.kstarbound.server.network.ServerConnection
import ru.dbotthepony.kstarbound.util.readUUID import ru.dbotthepony.kstarbound.io.readUUID
import ru.dbotthepony.kstarbound.util.writeUUID import ru.dbotthepony.kstarbound.io.writeUUID
import java.io.DataInputStream import java.io.DataInputStream
import java.io.DataOutputStream import java.io.DataOutputStream
import java.util.UUID import java.util.UUID

View File

@ -7,6 +7,7 @@ import io.netty.channel.ChannelInitializer
import io.netty.channel.local.LocalAddress import io.netty.channel.local.LocalAddress
import io.netty.channel.local.LocalServerChannel import io.netty.channel.local.LocalServerChannel
import io.netty.channel.socket.nio.NioServerSocketChannel import io.netty.channel.socket.nio.NioServerSocketChannel
import org.apache.logging.log4j.LogManager
import ru.dbotthepony.kstarbound.network.Connection import ru.dbotthepony.kstarbound.network.Connection
import ru.dbotthepony.kstarbound.network.ConnectionSide import ru.dbotthepony.kstarbound.network.ConnectionSide
import ru.dbotthepony.kstarbound.network.ConnectionType 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<Channel>() { val channel = ServerBootstrap().channel(LocalServerChannel::class.java).group(Connection.NIO_POOL).childHandler(object : ChannelInitializer<Channel>() {
override fun initChannel(ch: Channel) { override fun initChannel(ch: Channel) {
LOGGER.info("Incoming connection from ${ch.remoteAddress()}")
val connection = ServerConnection(server, ConnectionType.MEMORY) val connection = ServerConnection(server, ConnectionType.MEMORY)
connections.add(connection) connections.add(connection)
connection.bind(ch) connection.bind(ch)
@ -63,6 +65,7 @@ class ServerChannels(val server: StarboundServer) : Closeable {
lock.withLock { lock.withLock {
val channel = ServerBootstrap().channel(NioServerSocketChannel::class.java).group(Connection.NIO_POOL).childHandler(object : ChannelInitializer<Channel>() { val channel = ServerBootstrap().channel(NioServerSocketChannel::class.java).group(Connection.NIO_POOL).childHandler(object : ChannelInitializer<Channel>() {
override fun initChannel(ch: Channel) { override fun initChannel(ch: Channel) {
LOGGER.info("Incoming connection from ${ch.remoteAddress()}")
val connection = ServerConnection(server, ConnectionType.NETWORK) val connection = ServerConnection(server, ConnectionType.NETWORK)
connections.add(connection) connections.add(connection)
connection.bind(ch) connection.bind(ch)
@ -90,4 +93,8 @@ class ServerChannels(val server: StarboundServer) : Closeable {
connections.clear() connections.clear()
} }
} }
companion object {
private val LOGGER = LogManager.getLogger()
}
} }

View File

@ -5,6 +5,7 @@ import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap
import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet
import ru.dbotthepony.kstarbound.client.network.packets.ForgetChunkPacket import ru.dbotthepony.kstarbound.client.network.packets.ForgetChunkPacket
import ru.dbotthepony.kstarbound.client.network.packets.InitialChunkDataPacket 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.network.Player
import ru.dbotthepony.kstarbound.server.StarboundServer import ru.dbotthepony.kstarbound.server.StarboundServer
import ru.dbotthepony.kstarbound.server.world.ServerWorld import ru.dbotthepony.kstarbound.server.world.ServerWorld
@ -104,6 +105,11 @@ class ServerPlayer(connection: ServerConnection) : Player<ServerConnection>(conn
if (chunk != null) { if (chunk != null) {
connection.send(InitialChunkDataPacket(chunk)) connection.send(InitialChunkDataPacket(chunk))
chunk.objects.forEach {
connection.send(SpawnWorldObjectPacket(it.serialize()))
}
sentChunks.add(pos) sentChunks.add(pos)
} }
} }

View File

@ -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
}
}

View File

@ -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.network.IServerPacket
import ru.dbotthepony.kstarbound.server.network.ServerConnection 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.DataInputStream
import java.io.DataOutputStream 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 { data class TrackedSizePacket(val width: Int, val height: Int) : IServerPacket {
constructor(stream: DataInputStream) : this(stream.readUnsignedByte(), stream.readUnsignedByte()) constructor(stream: DataInputStream) : this(stream.readUnsignedByte(), stream.readUnsignedByte())

View File

@ -61,12 +61,7 @@ class LegacyChunkSource(val db: BTreeDB) : IChunkSource {
if (obj.identifier == "ObjectEntity") { if (obj.identifier == "ObjectEntity") {
try { try {
val content = obj.content.asJsonObject objects.add(WorldObject.fromJson(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)
} catch (err: Throwable) { } catch (err: Throwable) {
LOGGER.error("Unable to deserialize entity in chunk $pos", err) LOGGER.error("Unable to deserialize entity in chunk $pos", err)
} }

View File

@ -1,10 +1,10 @@
package ru.dbotthepony.kstarbound.tools package ru.dbotthepony.kstarbound.tools
import com.google.gson.GsonBuilder import com.google.gson.GsonBuilder
import ru.dbotthepony.kstarbound.json.BinaryJsonReader
import ru.dbotthepony.kstarbound.io.readHeader import ru.dbotthepony.kstarbound.io.readHeader
import ru.dbotthepony.kstarbound.io.readString import ru.dbotthepony.kstarbound.io.readString
import ru.dbotthepony.kstarbound.io.readVarInt import ru.dbotthepony.kstarbound.io.readVarInt
import ru.dbotthepony.kstarbound.json.readJsonElement
import java.io.BufferedInputStream import java.io.BufferedInputStream
import java.io.DataInputStream import java.io.DataInputStream
import java.io.File import java.io.File
@ -96,7 +96,7 @@ fun main(vararg args: String) {
val name = dataStream.readString(dataStream.readVarInt()) val name = dataStream.readString(dataStream.readVarInt())
val magic = dataStream.read() val magic = dataStream.read()
val version = dataStream.readInt() val version = dataStream.readInt()
val data = BinaryJsonReader.readElement(dataStream) val data = dataStream.readJsonElement()
val gson = GsonBuilder().setPrettyPrinting().create() val gson = GsonBuilder().setPrettyPrinting().create()
dataStream.close() dataStream.close()

View File

@ -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 : OutputStream, V> S.writeCollection(collection: Collection<V>, writer: S.(V) -> Unit) {
writeVarIntLE(collection.size)
for (value in collection) {
writer(this, value)
}
}
fun <S : InputStream, V, C : MutableCollection<V>> 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 : InputStream, V> 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)
}

View File

@ -44,8 +44,8 @@ abstract class Chunk<WorldType : World<WorldType, This>, This : Chunk<WorldType,
var backgroundChangeset = 0 var backgroundChangeset = 0
private set private set
protected val entities = ReferenceOpenHashSet<Entity>() val entities = ReferenceOpenHashSet<Entity>()
protected val objects = ReferenceOpenHashSet<WorldObject>() val objects = ReferenceOpenHashSet<WorldObject>()
protected val subscribers = ObjectArraySet<IChunkSubscriber>() protected val subscribers = ObjectArraySet<IChunkSubscriber>()
// local cells' tile access // local cells' tile access

View File

@ -1,7 +1,7 @@
package ru.dbotthepony.kstarbound.world package ru.dbotthepony.kstarbound.world
import ru.dbotthepony.kstarbound.util.readVec2i import ru.dbotthepony.kstarbound.io.readVec2i
import ru.dbotthepony.kstarbound.util.writeVec2i import ru.dbotthepony.kstarbound.io.writeVec2i
import ru.dbotthepony.kvector.api.IStruct2d import ru.dbotthepony.kvector.api.IStruct2d
import ru.dbotthepony.kvector.api.IStruct2f import ru.dbotthepony.kvector.api.IStruct2f
import ru.dbotthepony.kvector.api.IStruct2i import ru.dbotthepony.kvector.api.IStruct2i

View File

@ -7,7 +7,6 @@ import ru.dbotthepony.kstarbound.defs.tile.BuiltinMetaMaterials
import ru.dbotthepony.kstarbound.defs.tile.MaterialModifier import ru.dbotthepony.kstarbound.defs.tile.MaterialModifier
import ru.dbotthepony.kstarbound.defs.tile.TileDefinition import ru.dbotthepony.kstarbound.defs.tile.TileDefinition
import ru.dbotthepony.kstarbound.util.HashTableInterner import ru.dbotthepony.kstarbound.util.HashTableInterner
import ru.dbotthepony.kstarbound.util.writeUTF
import java.io.DataInputStream import java.io.DataInputStream
import java.io.DataOutputStream import java.io.DataOutputStream

View File

@ -14,6 +14,7 @@ import ru.dbotthepony.kstarbound.defs.JsonDriven
import ru.dbotthepony.kstarbound.defs.image.SpriteReference import ru.dbotthepony.kstarbound.defs.image.SpriteReference
import ru.dbotthepony.kstarbound.defs.`object`.ObjectDefinition import ru.dbotthepony.kstarbound.defs.`object`.ObjectDefinition
import ru.dbotthepony.kstarbound.defs.`object`.ObjectOrientation 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.server.world.ServerWorld
import ru.dbotthepony.kstarbound.util.MailboxExecutorService import ru.dbotthepony.kstarbound.util.MailboxExecutorService
import ru.dbotthepony.kstarbound.util.get 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() val mailbox = MailboxExecutorService()
var world: World<*, *> by Delegates.notNull() var world: World<*, *> by Delegates.notNull()
private set private set
@ -197,5 +214,13 @@ open class WorldObject(
private val strings by lazy { Starbound.gson.getAdapter(String::class.java) } private val strings by lazy { Starbound.gson.getAdapter(String::class.java) }
private val directions by lazy { Starbound.gson.getAdapter(Side::class.java) } private val directions by lazy { Starbound.gson.getAdapter(Side::class.java) }
private val vectors by lazy { Starbound.gson.getAdapter(Vector2i::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
}
} }
} }