KStarbound/src/main/kotlin/ru/dbotthepony/kstarbound/client/ClientConnection.kt

157 lines
4.5 KiB
Kotlin

package ru.dbotthepony.kstarbound.client
import io.netty.bootstrap.Bootstrap
import io.netty.channel.Channel
import io.netty.channel.ChannelHandlerContext
import io.netty.channel.ChannelInitializer
import io.netty.channel.local.LocalAddress
import io.netty.channel.local.LocalChannel
import io.netty.channel.socket.nio.NioSocketChannel
import org.apache.logging.log4j.LogManager
import ru.dbotthepony.kommons.util.KOptional
import ru.dbotthepony.kstarbound.Starbound
import ru.dbotthepony.kstarbound.network.Connection
import ru.dbotthepony.kstarbound.network.ConnectionSide
import ru.dbotthepony.kstarbound.network.ConnectionType
import ru.dbotthepony.kstarbound.network.IClientPacket
import ru.dbotthepony.kstarbound.network.packets.ClientContextUpdatePacket
import ru.dbotthepony.kstarbound.network.packets.ProtocolRequestPacket
import ru.dbotthepony.kstarbound.network.packets.serverbound.ClientDisconnectRequestPacket
import ru.dbotthepony.kstarbound.network.packets.serverbound.WorldClientStateUpdatePacket
import java.net.SocketAddress
import java.util.*
// clientside part of connection
class ClientConnection(val client: StarboundClient, type: ConnectionType) : Connection(ConnectionSide.CLIENT, type) {
private fun sendHello(asLegacy: Boolean = false) {
isLegacy = asLegacy
if (asLegacy) {
channel.write(ProtocolRequestPacket(Starbound.LEGACY_PROTOCOL_VERSION))
} else {
channel.write(ProtocolRequestPacket(Starbound.NATIVE_PROTOCOL_VERSION))
}
channel.flush()
}
fun enqueue(task: StarboundClient.() -> Unit) {
client.mailbox.execute { task.invoke(client) }
}
override fun inGame() {
}
override fun toString(): String {
val channel = if (hasChannel) channel.remoteAddress().toString() else "<no channel>"
return "ClientConnection[ID=$connectionID channel=$channel]"
}
override fun channelRead(ctx: ChannelHandlerContext, msg: Any) {
if (msg is IClientPacket) {
try {
msg.play(this)
} catch (err: Throwable) {
LOGGER.error("Failed to read incoming packet $msg", err)
disconnect(err.toString())
}
} else {
LOGGER.error("Unknown incoming packet type $msg")
disconnect("Unknown incoming packet type $msg")
}
}
private var clientStateNetVersion = 0L
override fun flush() {
if (!pendingDisconnect && isConnected) {
val entries = rpc.write()
if (entries != null) {
channel.write(ClientContextUpdatePacket(entries, KOptional(), KOptional()))
}
val (data, new) = clientStateGroup.write(clientStateNetVersion)
if (data.isNotEmpty())
channel.write(WorldClientStateUpdatePacket(data))
clientStateNetVersion = new
}
super.flush()
}
private var pendingDisconnect = false
fun disconnectNow() {
pendingDisconnect = false
if (channel.isOpen) {
channel.close()
}
}
override fun disconnect(reason: String) {
if (pendingDisconnect)
return
if (channel.isOpen) {
pendingDisconnect = true
sendAndFlush(ClientDisconnectRequestPacket)
} else {
disconnectNow()
}
}
override fun onChannelClosed() {
super.onChannelClosed()
if (pendingDisconnect) {
disconnectNow()
}
}
fun bootstrap(address: SocketAddress = channel.remoteAddress(), asLegacy: Boolean = false) {
LOGGER.info("Trying to connect to remote server at $address with ${if (asLegacy) "legacy" else "native"} protocol")
Bootstrap()
.group(NIO_POOL)
.channel(NioSocketChannel::class.java)
.handler(object : ChannelInitializer<Channel>() { override fun initChannel(ch: Channel) { bind(ch) } })
.connect(address)
.syncUninterruptibly()
sendHello(asLegacy)
}
companion object {
private val LOGGER = LogManager.getLogger()
fun connectToLocalServer(client: StarboundClient, address: LocalAddress): ClientConnection {
LOGGER.info("Trying to connect to local server at $address")
val connection = ClientConnection(client, ConnectionType.MEMORY)
Bootstrap()
.group(NIO_POOL)
.channel(LocalChannel::class.java)
.handler(object : ChannelInitializer<Channel>() { override fun initChannel(ch: Channel) { connection.bind(ch) } })
.connect(address)
.syncUninterruptibly()
connection.sendHello()
return connection
}
fun connectToLocalServer(client: StarboundClient, address: Channel): ClientConnection {
return connectToLocalServer(client, address.localAddress() as LocalAddress)
}
fun connectToRemoteServer(client: StarboundClient, address: SocketAddress): ClientConnection {
val connection = ClientConnection(client, ConnectionType.NETWORK)
connection.bootstrap(address)
return connection
}
}
}