209 lines
6.1 KiB
Kotlin
209 lines
6.1 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.kommons.util.AABBi
|
|
import ru.dbotthepony.kommons.vector.Vector2d
|
|
import ru.dbotthepony.kommons.vector.Vector2i
|
|
import ru.dbotthepony.kstarbound.GlobalDefaults
|
|
import ru.dbotthepony.kstarbound.network.syncher.BasicNetworkedElement
|
|
import ru.dbotthepony.kstarbound.network.syncher.NetworkedGroup
|
|
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.player.PlayerEntity
|
|
import java.io.Closeable
|
|
import kotlin.math.roundToInt
|
|
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
|
|
val begin = value * -65536
|
|
entityIDRange = begin .. begin + 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()
|
|
|
|
var playerEntity: PlayerEntity? = null
|
|
protected set
|
|
|
|
// holy shit
|
|
val clientSpectatingEntities = BasicNetworkedElement(IntAVLTreeSet(), StreamCodec.Collection(VarIntValueCodec) { IntAVLTreeSet() })
|
|
val clientStateGroup = MasterElement(NetworkedGroup(windowXMin, windowYMin, windowWidth, windowHeight, playerID, clientSpectatingEntities))
|
|
|
|
// in tiles
|
|
fun trackingTileRegions(): List<AABBi> {
|
|
val result = ArrayList<AABBi>()
|
|
|
|
val mins = Vector2i(windowXMin.get() - GlobalDefaults.client.windowMonitoringBorder, windowYMin.get() - GlobalDefaults.client.windowMonitoringBorder)
|
|
val maxs = Vector2i(windowWidth.get() + GlobalDefaults.client.windowMonitoringBorder, windowHeight.get() + GlobalDefaults.client.windowMonitoringBorder)
|
|
val window = AABBi(mins, maxs + mins)
|
|
|
|
if (window.mins != Vector2i.ZERO && window.maxs != Vector2i.ZERO) {
|
|
result.add(window)
|
|
}
|
|
|
|
val playerEntity = playerEntity
|
|
|
|
if (playerEntity != null) {
|
|
// add an extra region the size of the window centered on the player's position to prevent nearby sectors
|
|
// being unloaded due to camera panning or centering on other entities
|
|
val diff = Vector2d(window.width / 2.0, window.height / 2.0)
|
|
|
|
val pmins = playerEntity.position - diff
|
|
val pmaxs = playerEntity.position + diff
|
|
|
|
result.add(AABBi(
|
|
Vector2i(pmins.x.roundToInt(), pmins.y.roundToInt()),
|
|
Vector2i(pmaxs.x.roundToInt(), pmaxs.y.roundToInt()),
|
|
))
|
|
}
|
|
|
|
for (entity in clientSpectatingEntities.get()) {
|
|
// TODO
|
|
}
|
|
|
|
return result
|
|
}
|
|
|
|
companion object {
|
|
private val LOGGER = LogManager.getLogger()
|
|
|
|
val NIO_POOL by lazy {
|
|
NioEventLoopGroup(1, ThreadFactoryBuilder().setDaemon(true).setNameFormat("Starbound Network IO %d").build())
|
|
}
|
|
}
|
|
}
|