KStarbound/src/main/kotlin/ru/dbotthepony/kstarbound/server/ServerConnection.kt
2024-02-10 16:49:58 +07:00

164 lines
4.4 KiB
Kotlin

package ru.dbotthepony.kstarbound.server
import io.netty.channel.ChannelHandlerContext
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap
import it.unimi.dsi.fastutil.objects.ObjectLinkedOpenHashSet
import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet
import org.apache.logging.log4j.LogManager
import ru.dbotthepony.kommons.vector.Vector2d
import ru.dbotthepony.kstarbound.client.network.packets.ForgetChunkPacket
import ru.dbotthepony.kstarbound.client.network.packets.ChunkCellsPacket
import ru.dbotthepony.kstarbound.client.network.packets.ForgetEntityPacket
import ru.dbotthepony.kstarbound.client.network.packets.SpawnWorldObjectPacket
import ru.dbotthepony.kstarbound.network.Connection
import ru.dbotthepony.kstarbound.network.ConnectionSide
import ru.dbotthepony.kstarbound.network.ConnectionType
import ru.dbotthepony.kstarbound.network.IServerPacket
import ru.dbotthepony.kstarbound.network.packets.HelloPacket
import ru.dbotthepony.kstarbound.server.world.ServerWorld
import ru.dbotthepony.kstarbound.world.ChunkPos
import ru.dbotthepony.kstarbound.world.IChunkListener
import ru.dbotthepony.kstarbound.world.api.ImmutableCell
import ru.dbotthepony.kstarbound.world.entities.AbstractEntity
import ru.dbotthepony.kstarbound.world.entities.WorldObject
import java.util.*
class ServerConnection(val server: StarboundServer, type: ConnectionType) : Connection(ConnectionSide.SERVER, type, UUID(0L, 0L)) {
var world: ServerWorld? = null
var trackedPosition: Vector2d = Vector2d.ZERO
set(value) {
if (field != value) {
field = value
needsToRecomputeTrackedChunks = true
}
}
var trackedPositionChunk: ChunkPos = ChunkPos.ZERO
private set
var trackedChunksWidth = 4
set(value) {
if (field != value) {
field = value
needsToRecomputeTrackedChunks = true
}
}
var trackedChunksHeight = 4
set(value) {
if (field != value) {
field = value
needsToRecomputeTrackedChunks = true
}
}
private val tickets = Object2ObjectOpenHashMap<ChunkPos, ServerWorld.ITicket>()
private val pendingSend = ObjectLinkedOpenHashSet<ChunkPos>()
private var needsToRecomputeTrackedChunks = true
private inner class ChunkListener(val pos: ChunkPos) : IChunkListener {
override fun onEntityAdded(entity: AbstractEntity) {
if (entity is WorldObject)
send(SpawnWorldObjectPacket(entity.uuid, entity.serialize()))
}
override fun onEntityRemoved(entity: AbstractEntity) {
send(ForgetEntityPacket(entity.uuid))
}
override fun onCellChanges(x: Int, y: Int, cell: ImmutableCell) {
pendingSend.add(pos)
}
}
fun onLeaveWorld() {
tickets.values.forEach { it.cancel() }
tickets.clear()
pendingSend.clear()
}
private fun recomputeTrackedChunks() {
val world = world ?: return
val trackedPositionChunk = world.geometry.chunkFromCell(trackedPosition)
needsToRecomputeTrackedChunks = false
if (trackedPositionChunk == this.trackedPositionChunk) return
val tracked = ObjectOpenHashSet<ChunkPos>()
for (x in -trackedChunksWidth .. trackedChunksWidth) {
for (y in -trackedChunksHeight .. trackedChunksHeight) {
tracked.add(world.geometry.wrap(trackedPositionChunk + ChunkPos(x, y)))
}
}
val itr = tickets.entries.iterator()
for ((pos, ticket) in itr) {
if (pos !in tracked) {
send(ForgetChunkPacket(pos))
pendingSend.remove(pos)
ticket.cancel()
itr.remove()
}
}
for (pos in tracked) {
if (pos !in tickets) {
val ticket = world.permanentChunkTicket(pos)
tickets[pos] = ticket
ticket.listener = ChunkListener(pos)
pendingSend.add(pos)
}
}
}
fun tick() {
channel?.flush()
val world = world
if (world == null) {
onLeaveWorld()
return
}
if (needsToRecomputeTrackedChunks) {
recomputeTrackedChunks()
}
val itr = pendingSend.iterator()
for (pos in itr) {
val chunk = world.chunkMap[pos] ?: continue
send(ChunkCellsPacket(chunk))
itr.remove()
}
}
override fun channelRead(ctx: ChannelHandlerContext, msg: Any) {
if (msg is IServerPacket) {
try {
msg.play(this)
} catch (err: Throwable) {
LOGGER.error("Failed to read serverbound packet $msg", err)
disconnect(err.toString())
}
} else {
LOGGER.error("Unknown serverbound packet type $msg")
disconnect("Unknown serverbound packet type $msg")
}
}
override fun inGame() {
server.playerInGame(this)
}
override fun onHelloReceived(helloPacket: HelloPacket) {
}
companion object {
private val LOGGER = LogManager.getLogger()
}
}