168 lines
4.7 KiB
Kotlin
168 lines
4.7 KiB
Kotlin
package ru.dbotthepony.kstarbound.network
|
|
|
|
import com.google.common.util.concurrent.ThreadFactoryBuilder
|
|
import io.netty.channel.Channel
|
|
import io.netty.channel.ChannelHandlerContext
|
|
import io.netty.channel.ChannelInboundHandlerAdapter
|
|
import io.netty.channel.ChannelOption
|
|
import io.netty.channel.nio.NioEventLoopGroup
|
|
import it.unimi.dsi.fastutil.ints.IntAVLTreeSet
|
|
import org.apache.logging.log4j.LogManager
|
|
import ru.dbotthepony.kommons.io.StreamCodec
|
|
import ru.dbotthepony.kommons.io.VarIntValueCodec
|
|
import ru.dbotthepony.kstarbound.network.syncher.BasicNetworkedElement
|
|
import ru.dbotthepony.kstarbound.network.syncher.GroupElement
|
|
import ru.dbotthepony.kstarbound.network.syncher.MasterElement
|
|
import ru.dbotthepony.kstarbound.network.syncher.networkedSignedInt
|
|
import ru.dbotthepony.kstarbound.player.Avatar
|
|
import ru.dbotthepony.kstarbound.server.ServerChannels
|
|
import ru.dbotthepony.kstarbound.world.entities.PlayerEntity
|
|
import java.io.Closeable
|
|
import java.util.*
|
|
import kotlin.properties.Delegates
|
|
|
|
abstract class Connection(val side: ConnectionSide, val type: ConnectionType) : ChannelInboundHandlerAdapter(), Closeable {
|
|
abstract override fun channelRead(ctx: ChannelHandlerContext, msg: Any)
|
|
|
|
var avatar: Avatar? = null
|
|
var character: PlayerEntity? = null
|
|
val rpc = JsonRPC()
|
|
|
|
var entityIDRange: IntRange by Delegates.notNull()
|
|
private set
|
|
|
|
var connectionID: Int = -1
|
|
set(value) {
|
|
require(value in 1 .. ServerChannels.MAX_PLAYERS) { "Connection ID is out of range: $value" }
|
|
field = value
|
|
entityIDRange = value * -65536 .. 65535
|
|
}
|
|
|
|
var nickname: String = ""
|
|
|
|
val hasChannel get() = ::channel.isInitialized
|
|
lateinit var channel: Channel
|
|
private set
|
|
|
|
var isLegacy: Boolean = true
|
|
protected set
|
|
|
|
private val handshakeValidator = PacketRegistry.HANDSHAKE.Validator(side)
|
|
private val handshakeSerializer = PacketRegistry.HANDSHAKE.Serializer(side)
|
|
|
|
private val nativeValidator = PacketRegistry.NATIVE.Validator(side)
|
|
private val nativeSerializer = PacketRegistry.NATIVE.Serializer(side)
|
|
|
|
private val legacyValidator = PacketRegistry.LEGACY.Validator(side)
|
|
private val legacySerializer = PacketRegistry.LEGACY.Serializer(side)
|
|
|
|
var isConnected = false
|
|
private set
|
|
|
|
open fun setupLegacy() {
|
|
isConnected = true
|
|
LOGGER.info("Handshake successful on ${channel.remoteAddress()}, channel is using legacy protocol")
|
|
|
|
if (type == ConnectionType.MEMORY) {
|
|
channel.pipeline().remove(handshakeValidator)
|
|
channel.pipeline().addFirst(legacyValidator)
|
|
} else {
|
|
channel.pipeline().remove(handshakeSerializer)
|
|
channel.pipeline().addFirst(legacySerializer)
|
|
}
|
|
|
|
isLegacy = true
|
|
}
|
|
|
|
open fun setupNative() {
|
|
isConnected = true
|
|
LOGGER.info("Handshake successful on ${channel.remoteAddress()}, channel is using native protocol")
|
|
|
|
if (type == ConnectionType.MEMORY) {
|
|
channel.pipeline().remove(handshakeValidator)
|
|
channel.pipeline().addFirst(nativeValidator)
|
|
} else {
|
|
channel.pipeline().remove(handshakeSerializer)
|
|
channel.pipeline().addFirst(nativeSerializer)
|
|
}
|
|
|
|
isLegacy = false
|
|
|
|
inGame()
|
|
}
|
|
|
|
protected open fun onChannelClosed() {
|
|
LOGGER.info("$this is terminated")
|
|
}
|
|
|
|
fun bind(channel: Channel) {
|
|
channel.config().setOption(ChannelOption.TCP_NODELAY, true)
|
|
this.channel = channel
|
|
|
|
if (type == ConnectionType.MEMORY)
|
|
channel.pipeline().addFirst(handshakeValidator)
|
|
else
|
|
channel.pipeline().addFirst(handshakeSerializer)
|
|
|
|
channel.pipeline().addLast(this)
|
|
|
|
channel.closeFuture().addListener {
|
|
onChannelClosed()
|
|
}
|
|
}
|
|
|
|
// one connection can be reused for second channel (while first is being closed)
|
|
override fun isSharable(): Boolean {
|
|
return true
|
|
}
|
|
|
|
override fun ensureNotSharable() {
|
|
|
|
}
|
|
|
|
abstract fun inGame()
|
|
|
|
fun send(packet: IPacket) {
|
|
if (channel.isOpen && isConnected) {
|
|
channel.write(packet)
|
|
}
|
|
}
|
|
|
|
fun sendAndFlush(packet: IPacket) {
|
|
if (channel.isOpen && isConnected) {
|
|
channel.write(packet)
|
|
flush()
|
|
}
|
|
}
|
|
|
|
open fun flush() {
|
|
channel.flush()
|
|
}
|
|
|
|
abstract fun disconnect(reason: String = "")
|
|
|
|
override fun close() {
|
|
channel.close()
|
|
}
|
|
|
|
val windowXMin = networkedSignedInt()
|
|
val windowYMin = networkedSignedInt()
|
|
val windowWidth = networkedSignedInt()
|
|
val windowHeight = networkedSignedInt()
|
|
val playerID = networkedSignedInt()
|
|
|
|
// holy shit
|
|
val clientPresenceEntities = BasicNetworkedElement(IntAVLTreeSet(), StreamCodec.Collection(VarIntValueCodec) { IntAVLTreeSet() })
|
|
|
|
val clientStateGroup = MasterElement(GroupElement(windowXMin, windowYMin, windowWidth, windowHeight, playerID, clientPresenceEntities))
|
|
|
|
companion object {
|
|
private val EMPTY_UUID = UUID(0L, 0L)
|
|
private val LOGGER = LogManager.getLogger()
|
|
|
|
val NIO_POOL by lazy {
|
|
NioEventLoopGroup(ThreadFactoryBuilder().setDaemon(true).setNameFormat("Starbound Network IO %d").build())
|
|
}
|
|
}
|
|
}
|