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() private val pendingSend = ObjectLinkedOpenHashSet() 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() 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() } }