157 lines
4.5 KiB
Kotlin
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
|
|
}
|
|
}
|
|
} |