Refactor and improve movement-related packets, fix keep alive
This commit is contained in:
parent
3767d66065
commit
dc156fcf34
31 changed files with 183 additions and 123 deletions
12
README.md
12
README.md
|
@ -1,5 +1,16 @@
|
|||
# Uranos
|
||||
|
||||
Uranos is a game server implementing the Minecraft protocol. That means you can use the official Minecraft client to
|
||||
join Uranos servers.
|
||||
|
||||
Its goal is to be a modern alternative to Bukkit, SpigotMC and Paper. It is primarily intended to make creating custom
|
||||
games inside of Minecraft easier than it is possible with the existing alternatives, but it can also be used for
|
||||
something like a lobby/hub server.
|
||||
|
||||
The most important thing for Uranos is
|
||||
[developer experience (DX)](https://medium.com/swlh/what-is-dx-developer-experience-401a0e44a9d9). After that comes
|
||||
performance.
|
||||
|
||||
## Milestones
|
||||
|
||||
- Players can see entities
|
||||
|
@ -19,6 +30,7 @@
|
|||
- Entity AI framework
|
||||
- Scoreboards + Teams
|
||||
- Crafting
|
||||
- Rate limiting packets
|
||||
|
||||
## Development
|
||||
|
||||
|
|
|
@ -13,6 +13,7 @@ import space.uranos.entity.CowEntity
|
|||
import space.uranos.net.ServerListInfo
|
||||
import space.uranos.net.event.ServerListInfoRequestEvent
|
||||
import space.uranos.net.event.SessionAfterLoginEvent
|
||||
import space.uranos.net.packet.play.EntityHeadYawPacket
|
||||
import space.uranos.player.GameMode
|
||||
import space.uranos.plugin.Plugin
|
||||
import space.uranos.testplugin.anvil.AnvilWorld
|
||||
|
@ -22,8 +23,10 @@ import space.uranos.util.secondsToTicks
|
|||
import space.uranos.world.*
|
||||
import space.uranos.world.block.GreenWoolBlock
|
||||
import space.uranos.world.block.RedWoolBlock
|
||||
import kotlin.random.Random
|
||||
import kotlin.random.nextUBytes
|
||||
|
||||
class TestPlugin: Plugin("Test", "1.0.0") {
|
||||
class TestPlugin : Plugin("Test", "1.0.0") {
|
||||
override suspend fun onEnable() {
|
||||
val dimension = Dimension(
|
||||
"test:test",
|
||||
|
@ -83,9 +86,18 @@ class TestPlugin: Plugin("Test", "1.0.0") {
|
|||
}
|
||||
|
||||
var x = 1.0
|
||||
Uranos.scheduler.executeRepeating(1) {
|
||||
Uranos.scheduler.executeRepeating(20) {
|
||||
x += 0.2
|
||||
runInServerThread { entity.position = Position(x + 0.5, x, 0.5, 0f, 0f) }
|
||||
runInServerThread {
|
||||
Uranos.players.forEach {
|
||||
it.session.send(
|
||||
EntityHeadYawPacket(
|
||||
entity.numericID,
|
||||
Random.nextUBytes(1)[0]
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
if (x >= 10.0) x = 0.0
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,16 +7,15 @@ package space.uranos
|
|||
|
||||
import space.uranos.world.VoxelLocation
|
||||
import space.uranos.world.World
|
||||
import java.lang.IllegalArgumentException
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
/**
|
||||
* A combination of x, y and z coordinates and an orientation (yaw and pitch).
|
||||
*
|
||||
* @param yaw The yaw rotation in degrees. Must be in `[0; 360[`.
|
||||
* @param pitch The pitch rotation as a value between -90 (up) and 90 (down).
|
||||
* @param headPitch The pitch rotation of the head as a value between -90 (up) and 90 (down).
|
||||
*/
|
||||
data class Position(val x: Double, val y: Double, val z: Double, val yaw: Float, val pitch: Float) {
|
||||
data class Position(val x: Double, val y: Double, val z: Double, val yaw: Float, val headPitch: Float) {
|
||||
init {
|
||||
if (yaw >= 360) throw IllegalArgumentException("yaw must be lower than 360")
|
||||
if (yaw < 0) throw IllegalArgumentException("yaw must not be negative")
|
||||
|
@ -38,8 +37,8 @@ data class Position(val x: Double, val y: Double, val z: Double, val yaw: Float,
|
|||
fun toVector() = Vector(x, y, z)
|
||||
infix fun inside(world: World): Pair<World, Position> = world to this
|
||||
|
||||
val yawIn256Steps get() = ((yaw / 360) * 256).toInt().toUByte()
|
||||
val pitchIn256Steps get() = ((yaw / 360) * 256).toInt().toUByte()
|
||||
val yawIn256Steps: UByte get() = ((yaw / 360) * 256).toInt().toUByte()
|
||||
val headPitchIn256Steps: UByte get() = ((yaw / 360) * 256).toInt().toUByte()
|
||||
|
||||
operator fun get(part: CoordinatePart): Double = when (part) {
|
||||
CoordinatePart.X -> x
|
||||
|
|
|
@ -18,7 +18,7 @@ import space.uranos.player.GameMode
|
|||
import space.uranos.player.Player
|
||||
import space.uranos.world.World
|
||||
import java.net.InetAddress
|
||||
import java.util.*
|
||||
import java.util.UUID
|
||||
|
||||
abstract class Session {
|
||||
val events by lazy { EventBusWrapper<Session>(this) }
|
||||
|
@ -114,14 +114,14 @@ abstract class Session {
|
|||
)
|
||||
|
||||
/**
|
||||
* Sends a packet.
|
||||
* Sends a packet instantly.
|
||||
*/
|
||||
abstract suspend fun send(packet: OutgoingPacket)
|
||||
abstract suspend fun sendNow(packet: OutgoingPacket)
|
||||
|
||||
/**
|
||||
* Sends a packet the next tick.
|
||||
* Adds packet to the queue.
|
||||
*/
|
||||
abstract fun sendNextTick(packet: OutgoingPacket)
|
||||
abstract fun send(packet: OutgoingPacket)
|
||||
|
||||
/**
|
||||
* Sends a plugin message packet.
|
||||
|
|
|
@ -7,14 +7,14 @@ package space.uranos.util
|
|||
|
||||
import kotlin.reflect.KProperty
|
||||
|
||||
class MemoizedDelegate<T>(private val dependingGetter: () -> Any?, private val initializer: () -> T) {
|
||||
class MemoizedDelegate<T>(private val key: () -> Any?, private val initializer: () -> T) {
|
||||
private object UNINITIALIZED
|
||||
|
||||
private var lastDependingValue: Any? = null
|
||||
private var value: Any? = UNINITIALIZED
|
||||
|
||||
operator fun getValue(thisRef: Any?, property: KProperty<*>): T {
|
||||
val currentDependingValue = dependingGetter()
|
||||
val currentDependingValue = key()
|
||||
|
||||
if (value == UNINITIALIZED || (lastDependingValue != currentDependingValue)) {
|
||||
value = initializer()
|
||||
|
|
|
@ -17,7 +17,7 @@ import space.uranos.Vector
|
|||
*/
|
||||
data class VoxelLocation(val x: Int, val y: UByte, val z: Int) {
|
||||
/**
|
||||
* Converts this VoxelLocation to a
|
||||
* Converts this VoxelLocation to a Location.
|
||||
*/
|
||||
fun asLocation(): Location = Location(x.toDouble(), y.toDouble(), z.toDouble())
|
||||
|
||||
|
|
|
@ -9,11 +9,11 @@ import io.netty.buffer.ByteBuf
|
|||
import space.uranos.net.MinecraftProtocolDataTypes.writeVarInt
|
||||
import space.uranos.net.packet.OutgoingPacketCodec
|
||||
|
||||
object EntityOrientationPacketCodec :
|
||||
OutgoingPacketCodec<EntityOrientationPacket>(0x29, EntityOrientationPacket::class) {
|
||||
override fun EntityOrientationPacket.encode(dst: ByteBuf) {
|
||||
object EntityHeadPitchPacketCodec :
|
||||
OutgoingPacketCodec<EntityHeadPitchPacket>(0x29, EntityHeadPitchPacket::class) {
|
||||
override fun EntityHeadPitchPacket.encode(dst: ByteBuf) {
|
||||
dst.writeVarInt(entityID)
|
||||
dst.writeByte(yaw.toInt())
|
||||
dst.writeByte(0) // Should be yaw, but is actually ignored. Use EntityHeadYawPacket instead.
|
||||
dst.writeByte(pitch.toInt())
|
||||
dst.writeBoolean(onGround)
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
/*
|
||||
* Copyright 2020-2021 Moritz Ruth and Uranos contributors
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file
|
||||
*/
|
||||
|
||||
package space.uranos.net.packet.play
|
||||
|
||||
import io.netty.buffer.ByteBuf
|
||||
import space.uranos.net.MinecraftProtocolDataTypes.writeVarInt
|
||||
import space.uranos.net.packet.OutgoingPacketCodec
|
||||
|
||||
object EntityHeadYawPacketCodec : OutgoingPacketCodec<EntityHeadYawPacket>(0x3A, EntityHeadYawPacket::class) {
|
||||
override fun EntityHeadYawPacket.encode(dst: ByteBuf) {
|
||||
dst.writeVarInt(entityID)
|
||||
dst.writeByte(yaw.toInt())
|
||||
}
|
||||
}
|
|
@ -17,7 +17,7 @@ object EntityRelativeMoveWithOrientationPacketCodec :
|
|||
dst.writeShort(deltaY.toInt())
|
||||
dst.writeShort(deltaZ.toInt())
|
||||
dst.writeByte(yaw.toInt())
|
||||
dst.writeByte(pitch.toInt())
|
||||
dst.writeByte(headPitch.toInt())
|
||||
dst.writeBoolean(onGround)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,11 +13,11 @@ object EntityTeleportPacketCodec :
|
|||
OutgoingPacketCodec<EntityTeleportPacket>(0x56, EntityTeleportPacket::class) {
|
||||
override fun EntityTeleportPacket.encode(dst: ByteBuf) {
|
||||
dst.writeVarInt(entityID)
|
||||
dst.writeDouble(position.x)
|
||||
dst.writeDouble(position.y)
|
||||
dst.writeDouble(position.z)
|
||||
dst.writeByte(position.yawIn256Steps.toInt())
|
||||
dst.writeByte(position.pitchIn256Steps.toInt())
|
||||
dst.writeDouble(x)
|
||||
dst.writeDouble(y)
|
||||
dst.writeDouble(z)
|
||||
dst.writeByte(yaw.toInt())
|
||||
dst.writeByte(headPitch.toInt())
|
||||
dst.writeBoolean(onGround)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,7 +17,7 @@ object OutgoingPlayerPositionPacketCodec :
|
|||
dst.writeDouble(position.y)
|
||||
dst.writeDouble(position.z)
|
||||
dst.writeFloat(position.yaw)
|
||||
dst.writeFloat(position.pitch)
|
||||
dst.writeFloat(position.headPitch)
|
||||
dst.writeByte(bitmask(relativeX, relativeY, relativeZ, relativeYaw, relativePitch))
|
||||
dst.writeVarInt(0) // Teleport ID, I am not sure why this is needed
|
||||
}
|
||||
|
|
|
@ -18,7 +18,8 @@ object PlayProtocol : Protocol(
|
|||
DeclareRecipesPacketCodec,
|
||||
DestroyEntitiesPacketCodec,
|
||||
DisconnectPacketCodec,
|
||||
EntityOrientationPacketCodec,
|
||||
EntityHeadPitchPacketCodec,
|
||||
EntityHeadYawPacketCodec,
|
||||
EntityRelativeMovePacketCodec,
|
||||
EntityRelativeMoveWithOrientationPacketCodec,
|
||||
EntityTeleportPacketCodec,
|
||||
|
@ -40,5 +41,5 @@ object PlayProtocol : Protocol(
|
|||
SpawnObjectEntityPacketCodec,
|
||||
SpawnPaintingPacketCodec,
|
||||
TagsPacketCodec,
|
||||
UpdateViewPositionPacketCodec
|
||||
ViewPositionPacketCodec
|
||||
)
|
||||
|
|
|
@ -20,7 +20,7 @@ object SpawnLivingEntityPacketCodec : OutgoingPacketCodec<SpawnLivingEntityPacke
|
|||
dst.writeDouble(position.y)
|
||||
dst.writeDouble(position.z)
|
||||
dst.writeByte(position.yawIn256Steps.toInt())
|
||||
dst.writeByte(position.pitchIn256Steps.toInt())
|
||||
dst.writeByte(position.headPitchIn256Steps.toInt())
|
||||
dst.writeByte(0) // Head pitch; I do not know what this does
|
||||
dst.writeShort((velocity.x * 8000).toInt().toShort().toInt())
|
||||
dst.writeShort((velocity.y * 8000).toInt().toShort().toInt())
|
||||
|
|
|
@ -20,7 +20,7 @@ object SpawnObjectEntityPacketCodec : OutgoingPacketCodec<SpawnObjectEntityPacke
|
|||
dst.writeDouble(position.y)
|
||||
dst.writeDouble(position.z)
|
||||
dst.writeByte(position.yawIn256Steps.toInt())
|
||||
dst.writeByte(position.pitchIn256Steps.toInt())
|
||||
dst.writeByte(position.headPitchIn256Steps.toInt())
|
||||
dst.writeInt(data)
|
||||
dst.writeShort((velocity.x * 8000).toInt().toShort().toInt())
|
||||
dst.writeShort((velocity.y * 8000).toInt().toShort().toInt())
|
||||
|
|
|
@ -9,9 +9,9 @@ import io.netty.buffer.ByteBuf
|
|||
import space.uranos.net.MinecraftProtocolDataTypes.writeVarInt
|
||||
import space.uranos.net.packet.OutgoingPacketCodec
|
||||
|
||||
object UpdateViewPositionPacketCodec :
|
||||
OutgoingPacketCodec<UpdateViewPositionPacket>(0x40, UpdateViewPositionPacket::class) {
|
||||
override fun UpdateViewPositionPacket.encode(dst: ByteBuf) {
|
||||
object ViewPositionPacketCodec :
|
||||
OutgoingPacketCodec<ViewPositionPacket>(0x40, ViewPositionPacket::class) {
|
||||
override fun ViewPositionPacket.encode(dst: ByteBuf) {
|
||||
dst.writeVarInt(chunkKey.x)
|
||||
dst.writeVarInt(chunkKey.z)
|
||||
}
|
|
@ -7,9 +7,8 @@ package space.uranos.net.packet.play
|
|||
|
||||
import space.uranos.net.packet.OutgoingPacket
|
||||
|
||||
data class EntityOrientationPacket(
|
||||
data class EntityHeadPitchPacket(
|
||||
val entityID: Int,
|
||||
val yaw: UByte,
|
||||
val pitch: UByte,
|
||||
val onGround: Boolean
|
||||
) : OutgoingPacket()
|
|
@ -0,0 +1,13 @@
|
|||
/*
|
||||
* Copyright 2020-2021 Moritz Ruth and Uranos contributors
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file
|
||||
*/
|
||||
|
||||
package space.uranos.net.packet.play
|
||||
|
||||
import space.uranos.net.packet.OutgoingPacket
|
||||
|
||||
data class EntityHeadYawPacket(
|
||||
val entityID: Int,
|
||||
val yaw: UByte
|
||||
) : OutgoingPacket()
|
|
@ -19,6 +19,6 @@ data class EntityRelativeMoveWithOrientationPacket(
|
|||
/**
|
||||
* Absolute value.
|
||||
*/
|
||||
val pitch: UByte,
|
||||
val headPitch: UByte,
|
||||
val onGround: Boolean
|
||||
) : OutgoingPacket()
|
||||
|
|
|
@ -10,6 +10,20 @@ import space.uranos.net.packet.OutgoingPacket
|
|||
|
||||
data class EntityTeleportPacket(
|
||||
val entityID: Int,
|
||||
val position: Position,
|
||||
val x: Double,
|
||||
val y: Double,
|
||||
val z: Double,
|
||||
val yaw: UByte,
|
||||
val headPitch: UByte,
|
||||
val onGround: Boolean
|
||||
) : OutgoingPacket()
|
||||
) : OutgoingPacket() {
|
||||
constructor(entityID: Int, position: Position, onGround: Boolean) : this(
|
||||
entityID,
|
||||
position.x,
|
||||
position.y,
|
||||
position.z,
|
||||
position.yawIn256Steps,
|
||||
position.headPitchIn256Steps,
|
||||
onGround
|
||||
)
|
||||
}
|
||||
|
|
|
@ -12,4 +12,4 @@ import space.uranos.world.Chunk
|
|||
* The reason why this packet exists and what it does is not clear at the moment of writing.
|
||||
* It it sent every time the player crosses a chunk or chunk section border.
|
||||
*/
|
||||
data class UpdateViewPositionPacket(val chunkKey: Chunk.Key) : OutgoingPacket()
|
||||
data class ViewPositionPacket(val chunkKey: Chunk.Key) : OutgoingPacket()
|
|
@ -8,21 +8,20 @@ package space.uranos.util
|
|||
import space.uranos.Position
|
||||
import space.uranos.abs
|
||||
import space.uranos.net.packet.OutgoingPacket
|
||||
import space.uranos.net.packet.play.EntityOrientationPacket
|
||||
import space.uranos.net.packet.play.EntityHeadPitchPacket
|
||||
import space.uranos.net.packet.play.EntityRelativeMovePacket
|
||||
import space.uranos.net.packet.play.EntityRelativeMoveWithOrientationPacket
|
||||
import space.uranos.net.packet.play.EntityTeleportPacket
|
||||
|
||||
fun createEntityMovementPacket(entityID: Int, oldPosition: Position, newPosition: Position): OutgoingPacket? {
|
||||
val delta = abs(newPosition.toVector() - oldPosition.toVector())
|
||||
val orientationChanged = oldPosition.yaw != newPosition.yaw || oldPosition.pitch != newPosition.pitch
|
||||
val orientationChanged = oldPosition.yaw != newPosition.yaw || oldPosition.headPitch != newPosition.headPitch
|
||||
val onGround = true // TODO: Find out what onGround does
|
||||
|
||||
return if (delta.x + delta.y + delta.z == 0.0) {
|
||||
if (orientationChanged) EntityOrientationPacket(
|
||||
if (orientationChanged) EntityHeadPitchPacket(
|
||||
entityID,
|
||||
newPosition.yawIn256Steps,
|
||||
newPosition.pitchIn256Steps,
|
||||
newPosition.headPitchIn256Steps,
|
||||
onGround
|
||||
) else null
|
||||
} else if (delta.x > 8 || delta.y > 8 || delta.z > 8) {
|
||||
|
@ -34,7 +33,7 @@ fun createEntityMovementPacket(entityID: Int, oldPosition: Position, newPosition
|
|||
getDeltaShort(delta.y),
|
||||
getDeltaShort(delta.z),
|
||||
newPosition.yawIn256Steps,
|
||||
newPosition.pitchIn256Steps,
|
||||
newPosition.headPitchIn256Steps,
|
||||
onGround
|
||||
) else EntityRelativeMovePacket(
|
||||
entityID,
|
||||
|
|
|
@ -10,9 +10,8 @@ import kotlinx.coroutines.runBlocking
|
|||
import kotlinx.coroutines.suspendCancellableCoroutine
|
||||
import space.uranos.logging.Logger
|
||||
import space.uranos.server.Server
|
||||
import java.util.*
|
||||
import java.util.Collections
|
||||
import java.util.concurrent.*
|
||||
import kotlin.collections.LinkedHashSet
|
||||
import kotlin.coroutines.resume
|
||||
|
||||
/**
|
||||
|
@ -35,6 +34,8 @@ class UranosScheduler : Scheduler {
|
|||
@Volatile
|
||||
var cancelled: Boolean = false
|
||||
|
||||
val creationStackTrace = Thread.currentThread().stackTrace
|
||||
|
||||
override fun cancel() {
|
||||
tasks.remove(this)
|
||||
cancelled = true
|
||||
|
|
|
@ -133,22 +133,28 @@ class UranosServer internal constructor() : Server() {
|
|||
}
|
||||
|
||||
private fun startTicking() {
|
||||
var index = 0
|
||||
scheduler.executeRepeating(1, 0) {
|
||||
runInServerThread {
|
||||
players.forEach { it.container.tick() }
|
||||
internalEntities.forEach { it.tick() }
|
||||
sessions.forEach { it.packetsAdapter.tick() }
|
||||
sessions.forEach { it.tick(index) }
|
||||
}
|
||||
|
||||
index++
|
||||
}
|
||||
}
|
||||
|
||||
private fun startPingSync() {
|
||||
// Not in UranosSession.tick because this simplifies reusing the packet and the ping is not a critical information
|
||||
scheduler.executeRepeating(msToTicks(config.pingUpdateInterval.toMillis()), 0) {
|
||||
val packet = PlayerInfoPacket(
|
||||
PlayerInfoPacket.Action.UpdateLatency(players.map { it.uuid to it.session.ping }.toMap())
|
||||
)
|
||||
|
||||
players.forEach { it.session.send(packet) }
|
||||
players.forEach {
|
||||
it.session.send(packet)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -156,8 +162,8 @@ class UranosServer internal constructor() : Server() {
|
|||
eventBus.on<ViewingChangedEvent>(EventHandlerPosition.LAST) { event ->
|
||||
if (event.target == event.player.entity) return@on
|
||||
|
||||
if (event.viewing) event.player.session.sendNextTick(event.target.createSpawnPacket())
|
||||
else event.player.session.sendNextTick(DestroyEntitiesPacket(arrayOf(event.target.numericID)))
|
||||
if (event.viewing) event.player.session.send(event.target.createSpawnPacket())
|
||||
else event.player.session.send(DestroyEntitiesPacket(arrayOf(event.target.numericID)))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -69,7 +69,7 @@ sealed class UranosEntity(server: UranosServer) : Entity {
|
|||
}
|
||||
}
|
||||
|
||||
suspend fun tick() {
|
||||
open suspend fun tick() {
|
||||
container.tick()
|
||||
}
|
||||
}
|
||||
|
@ -81,7 +81,7 @@ abstract class UranosLivingEntity(server: UranosServer) : UranosEntity(server),
|
|||
override var position: Position by container.ifChanged(Position.ZERO) { value ->
|
||||
if (viewers.isNotEmpty()) createEntityMovementPacket(numericID, lastSentPosition, value)?.let {
|
||||
for (viewer in viewers) {
|
||||
viewer.session.sendNextTick(it)
|
||||
viewer.session.send(it)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -98,7 +98,7 @@ abstract class UranosObjectEntity(server: UranosServer) : UranosEntity(server),
|
|||
override var position: Position by container.ifChanged(Position.ZERO) { value ->
|
||||
createEntityMovementPacket(numericID, lastSentPosition, value)?.let {
|
||||
for (viewer in viewers) {
|
||||
viewer.session.sendNextTick(it)
|
||||
viewer.session.send(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,7 +22,7 @@ import space.uranos.util.AuthenticationHelper
|
|||
import space.uranos.util.EncryptionUtils
|
||||
import space.uranos.world.Chunk
|
||||
import java.security.MessageDigest
|
||||
import java.util.*
|
||||
import java.util.UUID
|
||||
import javax.crypto.Cipher
|
||||
import javax.crypto.spec.SecretKeySpec
|
||||
|
||||
|
@ -35,10 +35,10 @@ class LoginAndJoinProcedure(val session: UranosSession) {
|
|||
if (session.server.config.authenticateAndEncrypt) {
|
||||
val verifyToken = EncryptionUtils.generateVerifyToken()
|
||||
session.state = Session.State.WaitingForEncryptionVerification(packet.username, verifyToken)
|
||||
session.send(EncryptionRequestPacket(session.server.x509EncodedPublicKey, verifyToken))
|
||||
session.sendNow(EncryptionRequestPacket(session.server.x509EncodedPublicKey, verifyToken))
|
||||
} else {
|
||||
val uuid = UUID.nameUUIDFromBytes("OfflinePlayer:${packet.username}".toByteArray())
|
||||
session.send(LoginSuccessPacket(uuid, packet.username))
|
||||
session.sendNow(LoginSuccessPacket(uuid, packet.username))
|
||||
session.state = Session.State.LoginSucceeded(packet.username, uuid)
|
||||
afterLogin()
|
||||
}
|
||||
|
@ -71,10 +71,10 @@ class LoginAndJoinProcedure(val session: UranosSession) {
|
|||
|
||||
val result = AuthenticationHelper.authenticate(hashString, state.username)
|
||||
|
||||
session.send(SetCompressionPacket(session.server.config.packetCompressionThreshold))
|
||||
session.sendNow(SetCompressionPacket(session.server.config.packetCompressionThreshold))
|
||||
session.enableCompressionCodec()
|
||||
|
||||
session.send(LoginSuccessPacket(result.uuid, result.username))
|
||||
session.sendNow(LoginSuccessPacket(result.uuid, result.username))
|
||||
session.state = Session.State.LoginSucceeded(result.username, result.uuid)
|
||||
afterLogin()
|
||||
}
|
||||
|
@ -93,7 +93,7 @@ class LoginAndJoinProcedure(val session: UranosSession) {
|
|||
session.disconnect(internalReason = "No spawn location set")
|
||||
}
|
||||
else -> {
|
||||
session.send(
|
||||
session.sendNow(
|
||||
JoinGamePacket(
|
||||
0,
|
||||
event.gameMode,
|
||||
|
@ -111,9 +111,9 @@ class LoginAndJoinProcedure(val session: UranosSession) {
|
|||
)
|
||||
|
||||
// As this is only visual, there is no way of changing it aside from intercepting the packet.
|
||||
session.send(ServerDifficultyPacket(DifficultySettings(Difficulty.NORMAL, false)))
|
||||
session.sendNow(ServerDifficultyPacket(DifficultySettings(Difficulty.NORMAL, false)))
|
||||
|
||||
session.send(
|
||||
session.sendNow(
|
||||
PlayerAbilitiesPacket(
|
||||
event.invulnerable,
|
||||
event.flying,
|
||||
|
@ -174,17 +174,17 @@ class LoginAndJoinProcedure(val session: UranosSession) {
|
|||
|
||||
session.state = Session.State.Joining(player)
|
||||
|
||||
session.send(SelectedHotbarSlotPacket(state.selectedHotbarSlot))
|
||||
session.sendNow(SelectedHotbarSlotPacket(state.selectedHotbarSlot))
|
||||
|
||||
session.send(DeclareRecipesPacket(session.server.recipeRegistry.items.values))
|
||||
session.send(tagsPacket)
|
||||
session.sendNow(DeclareRecipesPacket(session.server.recipeRegistry.items.values))
|
||||
session.sendNow(tagsPacket)
|
||||
|
||||
// session.send(DeclareCommandsPacket(session.server.commandRegistry.items.values))
|
||||
// UnlockRecipes
|
||||
|
||||
session.send(OutgoingPlayerPositionPacket(state.position))
|
||||
session.sendNow(OutgoingPlayerPositionPacket(state.position))
|
||||
|
||||
session.send(PlayerInfoPacket(PlayerInfoPacket.Action.AddPlayer((session.server.players + player).map {
|
||||
session.sendNow(PlayerInfoPacket(PlayerInfoPacket.Action.AddPlayer((session.server.players + player).map {
|
||||
it.uuid to PlayerInfoPacket.Action.AddPlayer.Data(
|
||||
it.name,
|
||||
it.gameMode,
|
||||
|
@ -194,7 +194,7 @@ class LoginAndJoinProcedure(val session: UranosSession) {
|
|||
)
|
||||
}.toMap())))
|
||||
|
||||
session.send(
|
||||
session.sendNow(
|
||||
PlayerInfoPacket(
|
||||
PlayerInfoPacket.Action.UpdateLatency(
|
||||
session.server.players.map { it.uuid to it.session.ping }.toMap()
|
||||
|
@ -202,14 +202,14 @@ class LoginAndJoinProcedure(val session: UranosSession) {
|
|||
)
|
||||
)
|
||||
|
||||
session.send(UpdateViewPositionPacket(Chunk.Key.from(player.entity.position.toVoxelLocation())))
|
||||
session.sendNow(ViewPositionPacket(Chunk.Key.from(player.entity.position.toVoxelLocation())))
|
||||
|
||||
session.scheduleKeepAlivePacket(true)
|
||||
session.sendKeepAlivePacket()
|
||||
|
||||
player.spawnInitially(state.world)
|
||||
session.state = Session.State.Playing(player)
|
||||
|
||||
// WorldBorder
|
||||
session.sendNextTick(CompassTargetPacket(player.compassTarget))
|
||||
session.send(CompassTargetPacket(player.compassTarget))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -37,7 +37,7 @@ class PacketsAdapter(val session: UranosSession) {
|
|||
}
|
||||
|
||||
private suspend fun handlePacket(packet: IncomingPacket) {
|
||||
if (session.server.config.logging.shouldLog(packet)) session.logger.trace { "Packet received: $packet" }
|
||||
if (session.server.config.logging.shouldLog(packet)) session.logger.info { "Packet received: $packet" }
|
||||
|
||||
session.server.eventBus.emit(PacketReceivedEvent(session, packet)).ifNotCancelled {
|
||||
try {
|
||||
|
@ -70,7 +70,7 @@ class PacketsAdapter(val session: UranosSession) {
|
|||
}
|
||||
|
||||
suspend fun send(packet: OutgoingPacket) {
|
||||
if (session.server.config.logging.shouldLog(packet)) session.logger.trace { "Sending packet: $packet" }
|
||||
if (session.server.config.logging.shouldLog(packet)) session.logger.info { "Sending packet: $packet" }
|
||||
|
||||
session.server.eventBus.emit(PacketSendEvent(session, packet)).ifNotCancelled {
|
||||
try {
|
||||
|
@ -118,6 +118,7 @@ class PacketsAdapter(val session: UranosSession) {
|
|||
session.logger warn "$message. This will cause the client to disconnect in production mode."
|
||||
else session.failAndDisconnectBecauseOfClient(message)
|
||||
|
||||
ReferenceCountUtil.release(data)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
|
@ -23,10 +23,10 @@ import space.uranos.net.packet.play.PlayProtocol
|
|||
import space.uranos.net.packet.play.PlayerInfoPacket
|
||||
import space.uranos.net.packet.status.StatusProtocol
|
||||
import space.uranos.server.event.SessionInitializedEvent
|
||||
import space.uranos.util.msToTicks
|
||||
import java.net.InetAddress
|
||||
import java.net.InetSocketAddress
|
||||
import javax.crypto.SecretKey
|
||||
import kotlin.properties.Delegates
|
||||
|
||||
class UranosSession(val channel: io.netty.channel.Channel, val server: UranosServer) : Session() {
|
||||
override val address: InetAddress = (channel.remoteAddress() as InetSocketAddress).address
|
||||
|
@ -38,9 +38,13 @@ class UranosSession(val channel: io.netty.channel.Channel, val server: UranosSer
|
|||
|
||||
override var ping: Int = -1
|
||||
set(value) {
|
||||
if (field == -1) {
|
||||
val packet =
|
||||
PlayerInfoPacket(PlayerInfoPacket.Action.UpdateLatency(mapOf((state as State.WithPlayer).player.uuid to value)))
|
||||
val player = earlyPlayer
|
||||
|
||||
if (player != null && field == -1) {
|
||||
val packet = PlayerInfoPacket(
|
||||
PlayerInfoPacket.Action.UpdateLatency(mapOf(player.uuid to value))
|
||||
)
|
||||
|
||||
scope.launch { server.players.forEach { it.session.send(packet) } }
|
||||
}
|
||||
|
||||
|
@ -71,12 +75,12 @@ class UranosSession(val channel: io.netty.channel.Channel, val server: UranosSer
|
|||
|
||||
val packetsAdapter = PacketsAdapter(this)
|
||||
|
||||
override suspend fun send(packet: OutgoingPacket) = packetsAdapter.send(packet)
|
||||
override fun sendNextTick(packet: OutgoingPacket) = packetsAdapter.sendNextTick(packet)
|
||||
override suspend fun sendNow(packet: OutgoingPacket) = packetsAdapter.send(packet)
|
||||
override fun send(packet: OutgoingPacket) = packetsAdapter.sendNextTick(packet)
|
||||
|
||||
override suspend fun sendPluginMessage(channel: String, data: ByteBuf) {
|
||||
if (this.currentProtocol != PlayProtocol) throw IllegalStateException("The session is not using the PLAY protocol")
|
||||
send(OutgoingPluginMessagePacket(channel, data))
|
||||
sendNow(OutgoingPluginMessagePacket(channel, data))
|
||||
data.release()
|
||||
}
|
||||
|
||||
|
@ -99,8 +103,8 @@ class UranosSession(val channel: io.netty.channel.Channel, val server: UranosSer
|
|||
} else reason
|
||||
|
||||
when (currentProtocol) {
|
||||
LoginProtocol -> send(space.uranos.net.packet.login.DisconnectPacket(finalReason))
|
||||
PlayProtocol -> send(space.uranos.net.packet.play.DisconnectPacket(finalReason))
|
||||
LoginProtocol -> sendNow(space.uranos.net.packet.login.DisconnectPacket(finalReason))
|
||||
PlayProtocol -> sendNow(space.uranos.net.packet.play.DisconnectPacket(finalReason))
|
||||
}
|
||||
} else logger trace "Disconnected"
|
||||
|
||||
|
@ -110,30 +114,6 @@ class UranosSession(val channel: io.netty.channel.Channel, val server: UranosSer
|
|||
|
||||
val joinProcedure = LoginAndJoinProcedure(this)
|
||||
|
||||
var lastKeepAlivePacketTimestamp by Delegates.notNull<Long>()
|
||||
lateinit var keepAliveDisconnectJob: Job
|
||||
|
||||
fun scheduleKeepAlivePacket(isFirst: Boolean = false) {
|
||||
scope.launch { // TODO: Fix random disconnects (maybe some response packets are skipped?)
|
||||
if (!isFirst) {
|
||||
val timeSinceLastPacket = (System.currentTimeMillis() - lastKeepAlivePacketTimestamp).toInt()
|
||||
delay(KEEP_ALIVE_PACKET_INTERVAL.toLong() - timeSinceLastPacket)
|
||||
}
|
||||
|
||||
lastKeepAlivePacketTimestamp = System.currentTimeMillis()
|
||||
send(OutgoingKeepAlivePacket(lastKeepAlivePacketTimestamp))
|
||||
|
||||
keepAliveDisconnectJob = launch {
|
||||
delay(server.config.timeout.toMillis())
|
||||
disconnect(
|
||||
TextComponent of "Timed out",
|
||||
"The client did not respond to the KeepAlive packet for " +
|
||||
"${server.config.timeout.toMillis()}ms"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun onConnect() = scope.launch {
|
||||
logger trace "Connected"
|
||||
packetsAdapter.launchPacketDataChannelConsumer()
|
||||
|
@ -147,7 +127,7 @@ class UranosSession(val channel: io.netty.channel.Channel, val server: UranosSer
|
|||
scope.launch {
|
||||
if (state == State.Disconnected) return@launch
|
||||
if (!expected && currentProtocol != HandshakingProtocol && currentProtocol != StatusProtocol)
|
||||
logger trace "The client disconnected unexpectedly" // TODO: This is sometimes logged multiple times
|
||||
logger trace "The client disconnected unexpectedly"
|
||||
|
||||
packetsAdapter.stopProcessingIncomingPackets()
|
||||
coroutineContext.cancel(DisconnectedCancellationException())
|
||||
|
@ -192,7 +172,15 @@ class UranosSession(val channel: io.netty.channel.Channel, val server: UranosSer
|
|||
.addAfter("framing", "compression", CompressionCodec(server.config.packetCompressionThreshold))
|
||||
}
|
||||
|
||||
fun sendKeepAlivePacket() = send(OutgoingKeepAlivePacket(System.currentTimeMillis()))
|
||||
|
||||
suspend fun tick(index: Int) {
|
||||
if (earlyPlayer != null && index % KEEP_ALIVE_PACKET_INTERVAL == 0L) sendKeepAlivePacket()
|
||||
|
||||
packetsAdapter.tick()
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val KEEP_ALIVE_PACKET_INTERVAL = 1000
|
||||
val KEEP_ALIVE_PACKET_INTERVAL = msToTicks(1000)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,17 +5,15 @@
|
|||
|
||||
package space.uranos.net.packet.play
|
||||
|
||||
import space.uranos.chat.TextComponent
|
||||
import space.uranos.net.PacketReceivedEventHandler
|
||||
import space.uranos.net.UranosSession
|
||||
|
||||
object IncomingKeepAlivePacketHandler : PacketReceivedEventHandler<IncomingKeepAlivePacket>() {
|
||||
override suspend fun handle(session: UranosSession, packet: IncomingKeepAlivePacket) {
|
||||
if (session.lastKeepAlivePacketTimestamp == packet.id) {
|
||||
session.ping = (System.currentTimeMillis() - session.lastKeepAlivePacketTimestamp).toInt()
|
||||
session.keepAliveDisconnectJob.cancel()
|
||||
session.scheduleKeepAlivePacket()
|
||||
} else {
|
||||
session.disconnect(internalReason = "The ID of the last IncomingKeepAlive packet does not match the expected one.")
|
||||
}
|
||||
val ping = (System.currentTimeMillis() - packet.id).toInt()
|
||||
|
||||
if (ping >= session.server.config.timeout.toMillis()) session.disconnect(TextComponent of "Timed out")
|
||||
else session.ping = ping
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,7 +10,7 @@ import space.uranos.net.UranosSession
|
|||
|
||||
object PlayerOrientationPacketHandler : PacketReceivedEventHandler<PlayerOrientationPacket>() {
|
||||
override suspend fun handle(session: UranosSession, packet: PlayerOrientationPacket) {
|
||||
session.earlyPlayer?.entity?.let { it.position = it.position.copy(yaw = packet.yaw, pitch = packet.pitch) }
|
||||
session.earlyPlayer?.entity?.let { it.position = it.position.copy(yaw = packet.yaw, headPitch = packet.pitch) }
|
||||
?: error("Player not yet initialized")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,11 +19,11 @@ object StatusProtocolHandler : ProtocolPacketReceivedEventHandler(mapOf(
|
|||
response == null ->
|
||||
session.disconnect(internalReason = "The response property of the ServerListInfoRequestEvent is null.")
|
||||
|
||||
else -> session.send(ResponsePacket(response))
|
||||
else -> session.sendNow(ResponsePacket(response))
|
||||
}
|
||||
},
|
||||
PingPacket::class to PacketReceivedEventHandler.of<PingPacket> { session, packet ->
|
||||
session.send(PongPacket(packet.payload))
|
||||
session.sendNow(PongPacket(packet.payload))
|
||||
session.disconnect()
|
||||
}
|
||||
))
|
||||
|
|
|
@ -44,12 +44,12 @@ class UranosPlayer(
|
|||
override var selectedHotbarSlot by container.ifChanged(
|
||||
selectedHotbarSlot,
|
||||
{ clampArgument("selectedHotbarSlot", 0..8, it) }) {
|
||||
session.send(SelectedHotbarSlotPacket(it))
|
||||
session.sendNow(SelectedHotbarSlotPacket(it))
|
||||
}
|
||||
|
||||
override var playerListName by container.ifChanged<TextComponent?>(TextComponent of name) { value ->
|
||||
session.server.players.forEach {
|
||||
it.session.sendNextTick(PlayerInfoPacket(PlayerInfoPacket.Action.UpdateDisplayName(mapOf(uuid to value))))
|
||||
it.session.send(PlayerInfoPacket(PlayerInfoPacket.Action.UpdateDisplayName(mapOf(uuid to value))))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -90,7 +90,7 @@ class UranosPlayer(
|
|||
|
||||
private suspend fun sendChunksAndLight() {
|
||||
val chunks = currentlyViewedChunks.sortedBy { abs(it.key.x) + abs(it.key.z) }
|
||||
chunks.forEach { session.send(ChunkLightDataPacket(it.key, it.getLightData(this))) }
|
||||
chunks.forEach { session.send(ChunkDataPacket(it.key, it.getData(this))) }
|
||||
chunks.forEach { session.sendNow(ChunkLightDataPacket(it.key, it.getLightData(this))) }
|
||||
chunks.forEach { session.sendNow(ChunkDataPacket(it.key, it.getData(this))) }
|
||||
}
|
||||
}
|
||||
|
|
Reference in a new issue