164 lines
4.4 KiB
Kotlin
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()
|
|
}
|
|
}
|