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