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
|
||||||
|
|
||||||
|
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
|
## Milestones
|
||||||
|
|
||||||
- Players can see entities
|
- Players can see entities
|
||||||
|
@ -19,6 +30,7 @@
|
||||||
- Entity AI framework
|
- Entity AI framework
|
||||||
- Scoreboards + Teams
|
- Scoreboards + Teams
|
||||||
- Crafting
|
- Crafting
|
||||||
|
- Rate limiting packets
|
||||||
|
|
||||||
## Development
|
## Development
|
||||||
|
|
||||||
|
|
|
@ -13,6 +13,7 @@ import space.uranos.entity.CowEntity
|
||||||
import space.uranos.net.ServerListInfo
|
import space.uranos.net.ServerListInfo
|
||||||
import space.uranos.net.event.ServerListInfoRequestEvent
|
import space.uranos.net.event.ServerListInfoRequestEvent
|
||||||
import space.uranos.net.event.SessionAfterLoginEvent
|
import space.uranos.net.event.SessionAfterLoginEvent
|
||||||
|
import space.uranos.net.packet.play.EntityHeadYawPacket
|
||||||
import space.uranos.player.GameMode
|
import space.uranos.player.GameMode
|
||||||
import space.uranos.plugin.Plugin
|
import space.uranos.plugin.Plugin
|
||||||
import space.uranos.testplugin.anvil.AnvilWorld
|
import space.uranos.testplugin.anvil.AnvilWorld
|
||||||
|
@ -22,8 +23,10 @@ import space.uranos.util.secondsToTicks
|
||||||
import space.uranos.world.*
|
import space.uranos.world.*
|
||||||
import space.uranos.world.block.GreenWoolBlock
|
import space.uranos.world.block.GreenWoolBlock
|
||||||
import space.uranos.world.block.RedWoolBlock
|
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() {
|
override suspend fun onEnable() {
|
||||||
val dimension = Dimension(
|
val dimension = Dimension(
|
||||||
"test:test",
|
"test:test",
|
||||||
|
@ -83,9 +86,18 @@ class TestPlugin: Plugin("Test", "1.0.0") {
|
||||||
}
|
}
|
||||||
|
|
||||||
var x = 1.0
|
var x = 1.0
|
||||||
Uranos.scheduler.executeRepeating(1) {
|
Uranos.scheduler.executeRepeating(20) {
|
||||||
x += 0.2
|
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
|
if (x >= 10.0) x = 0.0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,16 +7,15 @@ package space.uranos
|
||||||
|
|
||||||
import space.uranos.world.VoxelLocation
|
import space.uranos.world.VoxelLocation
|
||||||
import space.uranos.world.World
|
import space.uranos.world.World
|
||||||
import java.lang.IllegalArgumentException
|
|
||||||
import kotlin.math.roundToInt
|
import kotlin.math.roundToInt
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A combination of x, y and z coordinates and an orientation (yaw and pitch).
|
* 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 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 {
|
init {
|
||||||
if (yaw >= 360) throw IllegalArgumentException("yaw must be lower than 360")
|
if (yaw >= 360) throw IllegalArgumentException("yaw must be lower than 360")
|
||||||
if (yaw < 0) throw IllegalArgumentException("yaw must not be negative")
|
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)
|
fun toVector() = Vector(x, y, z)
|
||||||
infix fun inside(world: World): Pair<World, Position> = world to this
|
infix fun inside(world: World): Pair<World, Position> = world to this
|
||||||
|
|
||||||
val yawIn256Steps get() = ((yaw / 360) * 256).toInt().toUByte()
|
val yawIn256Steps: UByte get() = ((yaw / 360) * 256).toInt().toUByte()
|
||||||
val pitchIn256Steps get() = ((yaw / 360) * 256).toInt().toUByte()
|
val headPitchIn256Steps: UByte get() = ((yaw / 360) * 256).toInt().toUByte()
|
||||||
|
|
||||||
operator fun get(part: CoordinatePart): Double = when (part) {
|
operator fun get(part: CoordinatePart): Double = when (part) {
|
||||||
CoordinatePart.X -> x
|
CoordinatePart.X -> x
|
||||||
|
|
|
@ -18,7 +18,7 @@ import space.uranos.player.GameMode
|
||||||
import space.uranos.player.Player
|
import space.uranos.player.Player
|
||||||
import space.uranos.world.World
|
import space.uranos.world.World
|
||||||
import java.net.InetAddress
|
import java.net.InetAddress
|
||||||
import java.util.*
|
import java.util.UUID
|
||||||
|
|
||||||
abstract class Session {
|
abstract class Session {
|
||||||
val events by lazy { EventBusWrapper<Session>(this) }
|
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.
|
* Sends a plugin message packet.
|
||||||
|
|
|
@ -7,14 +7,14 @@ package space.uranos.util
|
||||||
|
|
||||||
import kotlin.reflect.KProperty
|
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 object UNINITIALIZED
|
||||||
|
|
||||||
private var lastDependingValue: Any? = null
|
private var lastDependingValue: Any? = null
|
||||||
private var value: Any? = UNINITIALIZED
|
private var value: Any? = UNINITIALIZED
|
||||||
|
|
||||||
operator fun getValue(thisRef: Any?, property: KProperty<*>): T {
|
operator fun getValue(thisRef: Any?, property: KProperty<*>): T {
|
||||||
val currentDependingValue = dependingGetter()
|
val currentDependingValue = key()
|
||||||
|
|
||||||
if (value == UNINITIALIZED || (lastDependingValue != currentDependingValue)) {
|
if (value == UNINITIALIZED || (lastDependingValue != currentDependingValue)) {
|
||||||
value = initializer()
|
value = initializer()
|
||||||
|
|
|
@ -17,7 +17,7 @@ import space.uranos.Vector
|
||||||
*/
|
*/
|
||||||
data class VoxelLocation(val x: Int, val y: UByte, val z: Int) {
|
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())
|
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.MinecraftProtocolDataTypes.writeVarInt
|
||||||
import space.uranos.net.packet.OutgoingPacketCodec
|
import space.uranos.net.packet.OutgoingPacketCodec
|
||||||
|
|
||||||
object EntityOrientationPacketCodec :
|
object EntityHeadPitchPacketCodec :
|
||||||
OutgoingPacketCodec<EntityOrientationPacket>(0x29, EntityOrientationPacket::class) {
|
OutgoingPacketCodec<EntityHeadPitchPacket>(0x29, EntityHeadPitchPacket::class) {
|
||||||
override fun EntityOrientationPacket.encode(dst: ByteBuf) {
|
override fun EntityHeadPitchPacket.encode(dst: ByteBuf) {
|
||||||
dst.writeVarInt(entityID)
|
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.writeByte(pitch.toInt())
|
||||||
dst.writeBoolean(onGround)
|
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(deltaY.toInt())
|
||||||
dst.writeShort(deltaZ.toInt())
|
dst.writeShort(deltaZ.toInt())
|
||||||
dst.writeByte(yaw.toInt())
|
dst.writeByte(yaw.toInt())
|
||||||
dst.writeByte(pitch.toInt())
|
dst.writeByte(headPitch.toInt())
|
||||||
dst.writeBoolean(onGround)
|
dst.writeBoolean(onGround)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,11 +13,11 @@ object EntityTeleportPacketCodec :
|
||||||
OutgoingPacketCodec<EntityTeleportPacket>(0x56, EntityTeleportPacket::class) {
|
OutgoingPacketCodec<EntityTeleportPacket>(0x56, EntityTeleportPacket::class) {
|
||||||
override fun EntityTeleportPacket.encode(dst: ByteBuf) {
|
override fun EntityTeleportPacket.encode(dst: ByteBuf) {
|
||||||
dst.writeVarInt(entityID)
|
dst.writeVarInt(entityID)
|
||||||
dst.writeDouble(position.x)
|
dst.writeDouble(x)
|
||||||
dst.writeDouble(position.y)
|
dst.writeDouble(y)
|
||||||
dst.writeDouble(position.z)
|
dst.writeDouble(z)
|
||||||
dst.writeByte(position.yawIn256Steps.toInt())
|
dst.writeByte(yaw.toInt())
|
||||||
dst.writeByte(position.pitchIn256Steps.toInt())
|
dst.writeByte(headPitch.toInt())
|
||||||
dst.writeBoolean(onGround)
|
dst.writeBoolean(onGround)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,7 +17,7 @@ object OutgoingPlayerPositionPacketCodec :
|
||||||
dst.writeDouble(position.y)
|
dst.writeDouble(position.y)
|
||||||
dst.writeDouble(position.z)
|
dst.writeDouble(position.z)
|
||||||
dst.writeFloat(position.yaw)
|
dst.writeFloat(position.yaw)
|
||||||
dst.writeFloat(position.pitch)
|
dst.writeFloat(position.headPitch)
|
||||||
dst.writeByte(bitmask(relativeX, relativeY, relativeZ, relativeYaw, relativePitch))
|
dst.writeByte(bitmask(relativeX, relativeY, relativeZ, relativeYaw, relativePitch))
|
||||||
dst.writeVarInt(0) // Teleport ID, I am not sure why this is needed
|
dst.writeVarInt(0) // Teleport ID, I am not sure why this is needed
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,7 +18,8 @@ object PlayProtocol : Protocol(
|
||||||
DeclareRecipesPacketCodec,
|
DeclareRecipesPacketCodec,
|
||||||
DestroyEntitiesPacketCodec,
|
DestroyEntitiesPacketCodec,
|
||||||
DisconnectPacketCodec,
|
DisconnectPacketCodec,
|
||||||
EntityOrientationPacketCodec,
|
EntityHeadPitchPacketCodec,
|
||||||
|
EntityHeadYawPacketCodec,
|
||||||
EntityRelativeMovePacketCodec,
|
EntityRelativeMovePacketCodec,
|
||||||
EntityRelativeMoveWithOrientationPacketCodec,
|
EntityRelativeMoveWithOrientationPacketCodec,
|
||||||
EntityTeleportPacketCodec,
|
EntityTeleportPacketCodec,
|
||||||
|
@ -40,5 +41,5 @@ object PlayProtocol : Protocol(
|
||||||
SpawnObjectEntityPacketCodec,
|
SpawnObjectEntityPacketCodec,
|
||||||
SpawnPaintingPacketCodec,
|
SpawnPaintingPacketCodec,
|
||||||
TagsPacketCodec,
|
TagsPacketCodec,
|
||||||
UpdateViewPositionPacketCodec
|
ViewPositionPacketCodec
|
||||||
)
|
)
|
||||||
|
|
|
@ -20,7 +20,7 @@ object SpawnLivingEntityPacketCodec : OutgoingPacketCodec<SpawnLivingEntityPacke
|
||||||
dst.writeDouble(position.y)
|
dst.writeDouble(position.y)
|
||||||
dst.writeDouble(position.z)
|
dst.writeDouble(position.z)
|
||||||
dst.writeByte(position.yawIn256Steps.toInt())
|
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.writeByte(0) // Head pitch; I do not know what this does
|
||||||
dst.writeShort((velocity.x * 8000).toInt().toShort().toInt())
|
dst.writeShort((velocity.x * 8000).toInt().toShort().toInt())
|
||||||
dst.writeShort((velocity.y * 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.y)
|
||||||
dst.writeDouble(position.z)
|
dst.writeDouble(position.z)
|
||||||
dst.writeByte(position.yawIn256Steps.toInt())
|
dst.writeByte(position.yawIn256Steps.toInt())
|
||||||
dst.writeByte(position.pitchIn256Steps.toInt())
|
dst.writeByte(position.headPitchIn256Steps.toInt())
|
||||||
dst.writeInt(data)
|
dst.writeInt(data)
|
||||||
dst.writeShort((velocity.x * 8000).toInt().toShort().toInt())
|
dst.writeShort((velocity.x * 8000).toInt().toShort().toInt())
|
||||||
dst.writeShort((velocity.y * 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.MinecraftProtocolDataTypes.writeVarInt
|
||||||
import space.uranos.net.packet.OutgoingPacketCodec
|
import space.uranos.net.packet.OutgoingPacketCodec
|
||||||
|
|
||||||
object UpdateViewPositionPacketCodec :
|
object ViewPositionPacketCodec :
|
||||||
OutgoingPacketCodec<UpdateViewPositionPacket>(0x40, UpdateViewPositionPacket::class) {
|
OutgoingPacketCodec<ViewPositionPacket>(0x40, ViewPositionPacket::class) {
|
||||||
override fun UpdateViewPositionPacket.encode(dst: ByteBuf) {
|
override fun ViewPositionPacket.encode(dst: ByteBuf) {
|
||||||
dst.writeVarInt(chunkKey.x)
|
dst.writeVarInt(chunkKey.x)
|
||||||
dst.writeVarInt(chunkKey.z)
|
dst.writeVarInt(chunkKey.z)
|
||||||
}
|
}
|
|
@ -7,9 +7,8 @@ package space.uranos.net.packet.play
|
||||||
|
|
||||||
import space.uranos.net.packet.OutgoingPacket
|
import space.uranos.net.packet.OutgoingPacket
|
||||||
|
|
||||||
data class EntityOrientationPacket(
|
data class EntityHeadPitchPacket(
|
||||||
val entityID: Int,
|
val entityID: Int,
|
||||||
val yaw: UByte,
|
|
||||||
val pitch: UByte,
|
val pitch: UByte,
|
||||||
val onGround: Boolean
|
val onGround: Boolean
|
||||||
) : OutgoingPacket()
|
) : 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.
|
* Absolute value.
|
||||||
*/
|
*/
|
||||||
val pitch: UByte,
|
val headPitch: UByte,
|
||||||
val onGround: Boolean
|
val onGround: Boolean
|
||||||
) : OutgoingPacket()
|
) : OutgoingPacket()
|
||||||
|
|
|
@ -10,6 +10,20 @@ import space.uranos.net.packet.OutgoingPacket
|
||||||
|
|
||||||
data class EntityTeleportPacket(
|
data class EntityTeleportPacket(
|
||||||
val entityID: Int,
|
val entityID: Int,
|
||||||
val position: Position,
|
val x: Double,
|
||||||
|
val y: Double,
|
||||||
|
val z: Double,
|
||||||
|
val yaw: UByte,
|
||||||
|
val headPitch: UByte,
|
||||||
val onGround: Boolean
|
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.
|
* 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.
|
* 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.Position
|
||||||
import space.uranos.abs
|
import space.uranos.abs
|
||||||
import space.uranos.net.packet.OutgoingPacket
|
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.EntityRelativeMovePacket
|
||||||
import space.uranos.net.packet.play.EntityRelativeMoveWithOrientationPacket
|
import space.uranos.net.packet.play.EntityRelativeMoveWithOrientationPacket
|
||||||
import space.uranos.net.packet.play.EntityTeleportPacket
|
import space.uranos.net.packet.play.EntityTeleportPacket
|
||||||
|
|
||||||
fun createEntityMovementPacket(entityID: Int, oldPosition: Position, newPosition: Position): OutgoingPacket? {
|
fun createEntityMovementPacket(entityID: Int, oldPosition: Position, newPosition: Position): OutgoingPacket? {
|
||||||
val delta = abs(newPosition.toVector() - oldPosition.toVector())
|
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
|
val onGround = true // TODO: Find out what onGround does
|
||||||
|
|
||||||
return if (delta.x + delta.y + delta.z == 0.0) {
|
return if (delta.x + delta.y + delta.z == 0.0) {
|
||||||
if (orientationChanged) EntityOrientationPacket(
|
if (orientationChanged) EntityHeadPitchPacket(
|
||||||
entityID,
|
entityID,
|
||||||
newPosition.yawIn256Steps,
|
newPosition.headPitchIn256Steps,
|
||||||
newPosition.pitchIn256Steps,
|
|
||||||
onGround
|
onGround
|
||||||
) else null
|
) else null
|
||||||
} else if (delta.x > 8 || delta.y > 8 || delta.z > 8) {
|
} 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.y),
|
||||||
getDeltaShort(delta.z),
|
getDeltaShort(delta.z),
|
||||||
newPosition.yawIn256Steps,
|
newPosition.yawIn256Steps,
|
||||||
newPosition.pitchIn256Steps,
|
newPosition.headPitchIn256Steps,
|
||||||
onGround
|
onGround
|
||||||
) else EntityRelativeMovePacket(
|
) else EntityRelativeMovePacket(
|
||||||
entityID,
|
entityID,
|
||||||
|
|
|
@ -10,9 +10,8 @@ import kotlinx.coroutines.runBlocking
|
||||||
import kotlinx.coroutines.suspendCancellableCoroutine
|
import kotlinx.coroutines.suspendCancellableCoroutine
|
||||||
import space.uranos.logging.Logger
|
import space.uranos.logging.Logger
|
||||||
import space.uranos.server.Server
|
import space.uranos.server.Server
|
||||||
import java.util.*
|
import java.util.Collections
|
||||||
import java.util.concurrent.*
|
import java.util.concurrent.*
|
||||||
import kotlin.collections.LinkedHashSet
|
|
||||||
import kotlin.coroutines.resume
|
import kotlin.coroutines.resume
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -35,6 +34,8 @@ class UranosScheduler : Scheduler {
|
||||||
@Volatile
|
@Volatile
|
||||||
var cancelled: Boolean = false
|
var cancelled: Boolean = false
|
||||||
|
|
||||||
|
val creationStackTrace = Thread.currentThread().stackTrace
|
||||||
|
|
||||||
override fun cancel() {
|
override fun cancel() {
|
||||||
tasks.remove(this)
|
tasks.remove(this)
|
||||||
cancelled = true
|
cancelled = true
|
||||||
|
|
|
@ -133,22 +133,28 @@ class UranosServer internal constructor() : Server() {
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun startTicking() {
|
private fun startTicking() {
|
||||||
|
var index = 0
|
||||||
scheduler.executeRepeating(1, 0) {
|
scheduler.executeRepeating(1, 0) {
|
||||||
runInServerThread {
|
runInServerThread {
|
||||||
players.forEach { it.container.tick() }
|
players.forEach { it.container.tick() }
|
||||||
internalEntities.forEach { it.tick() }
|
internalEntities.forEach { it.tick() }
|
||||||
sessions.forEach { it.packetsAdapter.tick() }
|
sessions.forEach { it.tick(index) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
index++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun startPingSync() {
|
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) {
|
scheduler.executeRepeating(msToTicks(config.pingUpdateInterval.toMillis()), 0) {
|
||||||
val packet = PlayerInfoPacket(
|
val packet = PlayerInfoPacket(
|
||||||
PlayerInfoPacket.Action.UpdateLatency(players.map { it.uuid to it.session.ping }.toMap())
|
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 ->
|
eventBus.on<ViewingChangedEvent>(EventHandlerPosition.LAST) { event ->
|
||||||
if (event.target == event.player.entity) return@on
|
if (event.target == event.player.entity) return@on
|
||||||
|
|
||||||
if (event.viewing) event.player.session.sendNextTick(event.target.createSpawnPacket())
|
if (event.viewing) event.player.session.send(event.target.createSpawnPacket())
|
||||||
else event.player.session.sendNextTick(DestroyEntitiesPacket(arrayOf(event.target.numericID)))
|
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()
|
container.tick()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -81,7 +81,7 @@ abstract class UranosLivingEntity(server: UranosServer) : UranosEntity(server),
|
||||||
override var position: Position by container.ifChanged(Position.ZERO) { value ->
|
override var position: Position by container.ifChanged(Position.ZERO) { value ->
|
||||||
if (viewers.isNotEmpty()) createEntityMovementPacket(numericID, lastSentPosition, value)?.let {
|
if (viewers.isNotEmpty()) createEntityMovementPacket(numericID, lastSentPosition, value)?.let {
|
||||||
for (viewer in viewers) {
|
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 ->
|
override var position: Position by container.ifChanged(Position.ZERO) { value ->
|
||||||
createEntityMovementPacket(numericID, lastSentPosition, value)?.let {
|
createEntityMovementPacket(numericID, lastSentPosition, value)?.let {
|
||||||
for (viewer in viewers) {
|
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.util.EncryptionUtils
|
||||||
import space.uranos.world.Chunk
|
import space.uranos.world.Chunk
|
||||||
import java.security.MessageDigest
|
import java.security.MessageDigest
|
||||||
import java.util.*
|
import java.util.UUID
|
||||||
import javax.crypto.Cipher
|
import javax.crypto.Cipher
|
||||||
import javax.crypto.spec.SecretKeySpec
|
import javax.crypto.spec.SecretKeySpec
|
||||||
|
|
||||||
|
@ -35,10 +35,10 @@ class LoginAndJoinProcedure(val session: UranosSession) {
|
||||||
if (session.server.config.authenticateAndEncrypt) {
|
if (session.server.config.authenticateAndEncrypt) {
|
||||||
val verifyToken = EncryptionUtils.generateVerifyToken()
|
val verifyToken = EncryptionUtils.generateVerifyToken()
|
||||||
session.state = Session.State.WaitingForEncryptionVerification(packet.username, verifyToken)
|
session.state = Session.State.WaitingForEncryptionVerification(packet.username, verifyToken)
|
||||||
session.send(EncryptionRequestPacket(session.server.x509EncodedPublicKey, verifyToken))
|
session.sendNow(EncryptionRequestPacket(session.server.x509EncodedPublicKey, verifyToken))
|
||||||
} else {
|
} else {
|
||||||
val uuid = UUID.nameUUIDFromBytes("OfflinePlayer:${packet.username}".toByteArray())
|
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)
|
session.state = Session.State.LoginSucceeded(packet.username, uuid)
|
||||||
afterLogin()
|
afterLogin()
|
||||||
}
|
}
|
||||||
|
@ -71,10 +71,10 @@ class LoginAndJoinProcedure(val session: UranosSession) {
|
||||||
|
|
||||||
val result = AuthenticationHelper.authenticate(hashString, state.username)
|
val result = AuthenticationHelper.authenticate(hashString, state.username)
|
||||||
|
|
||||||
session.send(SetCompressionPacket(session.server.config.packetCompressionThreshold))
|
session.sendNow(SetCompressionPacket(session.server.config.packetCompressionThreshold))
|
||||||
session.enableCompressionCodec()
|
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)
|
session.state = Session.State.LoginSucceeded(result.username, result.uuid)
|
||||||
afterLogin()
|
afterLogin()
|
||||||
}
|
}
|
||||||
|
@ -93,7 +93,7 @@ class LoginAndJoinProcedure(val session: UranosSession) {
|
||||||
session.disconnect(internalReason = "No spawn location set")
|
session.disconnect(internalReason = "No spawn location set")
|
||||||
}
|
}
|
||||||
else -> {
|
else -> {
|
||||||
session.send(
|
session.sendNow(
|
||||||
JoinGamePacket(
|
JoinGamePacket(
|
||||||
0,
|
0,
|
||||||
event.gameMode,
|
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.
|
// 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(
|
PlayerAbilitiesPacket(
|
||||||
event.invulnerable,
|
event.invulnerable,
|
||||||
event.flying,
|
event.flying,
|
||||||
|
@ -174,17 +174,17 @@ class LoginAndJoinProcedure(val session: UranosSession) {
|
||||||
|
|
||||||
session.state = Session.State.Joining(player)
|
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.sendNow(DeclareRecipesPacket(session.server.recipeRegistry.items.values))
|
||||||
session.send(tagsPacket)
|
session.sendNow(tagsPacket)
|
||||||
|
|
||||||
// session.send(DeclareCommandsPacket(session.server.commandRegistry.items.values))
|
// session.send(DeclareCommandsPacket(session.server.commandRegistry.items.values))
|
||||||
// UnlockRecipes
|
// 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.uuid to PlayerInfoPacket.Action.AddPlayer.Data(
|
||||||
it.name,
|
it.name,
|
||||||
it.gameMode,
|
it.gameMode,
|
||||||
|
@ -194,7 +194,7 @@ class LoginAndJoinProcedure(val session: UranosSession) {
|
||||||
)
|
)
|
||||||
}.toMap())))
|
}.toMap())))
|
||||||
|
|
||||||
session.send(
|
session.sendNow(
|
||||||
PlayerInfoPacket(
|
PlayerInfoPacket(
|
||||||
PlayerInfoPacket.Action.UpdateLatency(
|
PlayerInfoPacket.Action.UpdateLatency(
|
||||||
session.server.players.map { it.uuid to it.session.ping }.toMap()
|
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)
|
player.spawnInitially(state.world)
|
||||||
session.state = Session.State.Playing(player)
|
session.state = Session.State.Playing(player)
|
||||||
|
|
||||||
// WorldBorder
|
// 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) {
|
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 {
|
session.server.eventBus.emit(PacketReceivedEvent(session, packet)).ifNotCancelled {
|
||||||
try {
|
try {
|
||||||
|
@ -70,7 +70,7 @@ class PacketsAdapter(val session: UranosSession) {
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun send(packet: OutgoingPacket) {
|
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 {
|
session.server.eventBus.emit(PacketSendEvent(session, packet)).ifNotCancelled {
|
||||||
try {
|
try {
|
||||||
|
@ -118,6 +118,7 @@ class PacketsAdapter(val session: UranosSession) {
|
||||||
session.logger warn "$message. This will cause the client to disconnect in production mode."
|
session.logger warn "$message. This will cause the client to disconnect in production mode."
|
||||||
else session.failAndDisconnectBecauseOfClient(message)
|
else session.failAndDisconnectBecauseOfClient(message)
|
||||||
|
|
||||||
|
ReferenceCountUtil.release(data)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -23,10 +23,10 @@ import space.uranos.net.packet.play.PlayProtocol
|
||||||
import space.uranos.net.packet.play.PlayerInfoPacket
|
import space.uranos.net.packet.play.PlayerInfoPacket
|
||||||
import space.uranos.net.packet.status.StatusProtocol
|
import space.uranos.net.packet.status.StatusProtocol
|
||||||
import space.uranos.server.event.SessionInitializedEvent
|
import space.uranos.server.event.SessionInitializedEvent
|
||||||
|
import space.uranos.util.msToTicks
|
||||||
import java.net.InetAddress
|
import java.net.InetAddress
|
||||||
import java.net.InetSocketAddress
|
import java.net.InetSocketAddress
|
||||||
import javax.crypto.SecretKey
|
import javax.crypto.SecretKey
|
||||||
import kotlin.properties.Delegates
|
|
||||||
|
|
||||||
class UranosSession(val channel: io.netty.channel.Channel, val server: UranosServer) : Session() {
|
class UranosSession(val channel: io.netty.channel.Channel, val server: UranosServer) : Session() {
|
||||||
override val address: InetAddress = (channel.remoteAddress() as InetSocketAddress).address
|
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
|
override var ping: Int = -1
|
||||||
set(value) {
|
set(value) {
|
||||||
if (field == -1) {
|
val player = earlyPlayer
|
||||||
val packet =
|
|
||||||
PlayerInfoPacket(PlayerInfoPacket.Action.UpdateLatency(mapOf((state as State.WithPlayer).player.uuid to value)))
|
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) } }
|
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)
|
val packetsAdapter = PacketsAdapter(this)
|
||||||
|
|
||||||
override suspend fun send(packet: OutgoingPacket) = packetsAdapter.send(packet)
|
override suspend fun sendNow(packet: OutgoingPacket) = packetsAdapter.send(packet)
|
||||||
override fun sendNextTick(packet: OutgoingPacket) = packetsAdapter.sendNextTick(packet)
|
override fun send(packet: OutgoingPacket) = packetsAdapter.sendNextTick(packet)
|
||||||
|
|
||||||
override suspend fun sendPluginMessage(channel: String, data: ByteBuf) {
|
override suspend fun sendPluginMessage(channel: String, data: ByteBuf) {
|
||||||
if (this.currentProtocol != PlayProtocol) throw IllegalStateException("The session is not using the PLAY protocol")
|
if (this.currentProtocol != PlayProtocol) throw IllegalStateException("The session is not using the PLAY protocol")
|
||||||
send(OutgoingPluginMessagePacket(channel, data))
|
sendNow(OutgoingPluginMessagePacket(channel, data))
|
||||||
data.release()
|
data.release()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -99,8 +103,8 @@ class UranosSession(val channel: io.netty.channel.Channel, val server: UranosSer
|
||||||
} else reason
|
} else reason
|
||||||
|
|
||||||
when (currentProtocol) {
|
when (currentProtocol) {
|
||||||
LoginProtocol -> send(space.uranos.net.packet.login.DisconnectPacket(finalReason))
|
LoginProtocol -> sendNow(space.uranos.net.packet.login.DisconnectPacket(finalReason))
|
||||||
PlayProtocol -> send(space.uranos.net.packet.play.DisconnectPacket(finalReason))
|
PlayProtocol -> sendNow(space.uranos.net.packet.play.DisconnectPacket(finalReason))
|
||||||
}
|
}
|
||||||
} else logger trace "Disconnected"
|
} else logger trace "Disconnected"
|
||||||
|
|
||||||
|
@ -110,30 +114,6 @@ class UranosSession(val channel: io.netty.channel.Channel, val server: UranosSer
|
||||||
|
|
||||||
val joinProcedure = LoginAndJoinProcedure(this)
|
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 {
|
fun onConnect() = scope.launch {
|
||||||
logger trace "Connected"
|
logger trace "Connected"
|
||||||
packetsAdapter.launchPacketDataChannelConsumer()
|
packetsAdapter.launchPacketDataChannelConsumer()
|
||||||
|
@ -147,7 +127,7 @@ class UranosSession(val channel: io.netty.channel.Channel, val server: UranosSer
|
||||||
scope.launch {
|
scope.launch {
|
||||||
if (state == State.Disconnected) return@launch
|
if (state == State.Disconnected) return@launch
|
||||||
if (!expected && currentProtocol != HandshakingProtocol && currentProtocol != StatusProtocol)
|
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()
|
packetsAdapter.stopProcessingIncomingPackets()
|
||||||
coroutineContext.cancel(DisconnectedCancellationException())
|
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))
|
.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 {
|
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
|
package space.uranos.net.packet.play
|
||||||
|
|
||||||
|
import space.uranos.chat.TextComponent
|
||||||
import space.uranos.net.PacketReceivedEventHandler
|
import space.uranos.net.PacketReceivedEventHandler
|
||||||
import space.uranos.net.UranosSession
|
import space.uranos.net.UranosSession
|
||||||
|
|
||||||
object IncomingKeepAlivePacketHandler : PacketReceivedEventHandler<IncomingKeepAlivePacket>() {
|
object IncomingKeepAlivePacketHandler : PacketReceivedEventHandler<IncomingKeepAlivePacket>() {
|
||||||
override suspend fun handle(session: UranosSession, packet: IncomingKeepAlivePacket) {
|
override suspend fun handle(session: UranosSession, packet: IncomingKeepAlivePacket) {
|
||||||
if (session.lastKeepAlivePacketTimestamp == packet.id) {
|
val ping = (System.currentTimeMillis() - packet.id).toInt()
|
||||||
session.ping = (System.currentTimeMillis() - session.lastKeepAlivePacketTimestamp).toInt()
|
|
||||||
session.keepAliveDisconnectJob.cancel()
|
if (ping >= session.server.config.timeout.toMillis()) session.disconnect(TextComponent of "Timed out")
|
||||||
session.scheduleKeepAlivePacket()
|
else session.ping = ping
|
||||||
} else {
|
|
||||||
session.disconnect(internalReason = "The ID of the last IncomingKeepAlive packet does not match the expected one.")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,7 +10,7 @@ import space.uranos.net.UranosSession
|
||||||
|
|
||||||
object PlayerOrientationPacketHandler : PacketReceivedEventHandler<PlayerOrientationPacket>() {
|
object PlayerOrientationPacketHandler : PacketReceivedEventHandler<PlayerOrientationPacket>() {
|
||||||
override suspend fun handle(session: UranosSession, packet: 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")
|
?: error("Player not yet initialized")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,11 +19,11 @@ object StatusProtocolHandler : ProtocolPacketReceivedEventHandler(mapOf(
|
||||||
response == null ->
|
response == null ->
|
||||||
session.disconnect(internalReason = "The response property of the ServerListInfoRequestEvent is 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 ->
|
PingPacket::class to PacketReceivedEventHandler.of<PingPacket> { session, packet ->
|
||||||
session.send(PongPacket(packet.payload))
|
session.sendNow(PongPacket(packet.payload))
|
||||||
session.disconnect()
|
session.disconnect()
|
||||||
}
|
}
|
||||||
))
|
))
|
||||||
|
|
|
@ -44,12 +44,12 @@ class UranosPlayer(
|
||||||
override var selectedHotbarSlot by container.ifChanged(
|
override var selectedHotbarSlot by container.ifChanged(
|
||||||
selectedHotbarSlot,
|
selectedHotbarSlot,
|
||||||
{ clampArgument("selectedHotbarSlot", 0..8, it) }) {
|
{ clampArgument("selectedHotbarSlot", 0..8, it) }) {
|
||||||
session.send(SelectedHotbarSlotPacket(it))
|
session.sendNow(SelectedHotbarSlotPacket(it))
|
||||||
}
|
}
|
||||||
|
|
||||||
override var playerListName by container.ifChanged<TextComponent?>(TextComponent of name) { value ->
|
override var playerListName by container.ifChanged<TextComponent?>(TextComponent of name) { value ->
|
||||||
session.server.players.forEach {
|
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() {
|
private suspend fun sendChunksAndLight() {
|
||||||
val chunks = currentlyViewedChunks.sortedBy { abs(it.key.x) + abs(it.key.z) }
|
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.sendNow(ChunkLightDataPacket(it.key, it.getLightData(this))) }
|
||||||
chunks.forEach { session.send(ChunkDataPacket(it.key, it.getData(this))) }
|
chunks.forEach { session.sendNow(ChunkDataPacket(it.key, it.getData(this))) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Reference in a new issue