From 4e34dbc1e2200487de438bab80c569d7a004aafc Mon Sep 17 00:00:00 2001 From: Moritz Ruth Date: Wed, 6 Jan 2021 18:09:01 +0100 Subject: [PATCH] Add player movement related packets and work on entities --- .../space/uranos/CalledFromWrongThread.kt | 8 ++++ .../src/main/kotlin/space/uranos/Scheduler.kt | 6 +-- .../main/kotlin/space/uranos/entity/Entity.kt | 3 ++ .../space/uranos/entity/LivingEntity.kt | 6 +-- .../main/kotlin/space/uranos/entity/Mobile.kt | 4 +- .../space/uranos/entity/PlayerEntity.kt | 32 +++++++++++++++ .../main/kotlin/space/uranos/net/Session.kt | 4 +- .../net/event/SessionAfterLoginEvent.kt | 4 +- .../main/kotlin/space/uranos/player/Player.kt | 19 +++------ .../main/kotlin/space/uranos/server/Server.kt | 10 +++++ ...etCodec.kt => CompassTargetPacketCodec.kt} | 6 +-- .../play/IncomingPlayerPositionPacketCodec.kt | 18 +++++++++ ...t => OutgoingPlayerPositionPacketCodec.kt} | 6 +-- .../uranos/net/packet/play/PlayProtocol.kt | 9 +++-- .../packet/play/PlayerLocationPacketCodec.kt | 18 +++++++++ .../play/PlayerOrientationPacketCodec.kt | 18 +++++++++ ...ec.kt => SelectedHotbarSlotPacketCodec.kt} | 6 +-- ...TargetPacket.kt => CompassTargetPacket.kt} | 3 +- .../play/IncomingPlayerPositionPacket.kt | 17 ++++++++ ...ket.kt => OutgoingPlayerPositionPacket.kt} | 2 +- .../net/packet/play/PlayerLocationPacket.kt | 17 ++++++++ .../packet/play/PlayerOrientationPacket.kt | 26 +++++++++++++ ...tPacket.kt => SelectedHotbarSlotPacket.kt} | 2 +- .../kotlin/space/uranos/UranosScheduler.kt | 39 +++++++++---------- .../main/kotlin/space/uranos/UranosServer.kt | 15 ++++--- .../space/uranos/net/LoginAndJoinProcedure.kt | 13 ++++--- .../kotlin/space/uranos/net/UranosSession.kt | 2 +- .../space/uranos/player/UranosPlayer.kt | 14 ++++--- 28 files changed, 249 insertions(+), 78 deletions(-) create mode 100644 uranos-api/src/main/kotlin/space/uranos/CalledFromWrongThread.kt create mode 100644 uranos-api/src/main/kotlin/space/uranos/entity/PlayerEntity.kt rename uranos-packet-codecs/src/main/kotlin/space/uranos/net/packet/play/{SetCompassTargetPacketCodec.kt => CompassTargetPacketCodec.kt} (68%) create mode 100644 uranos-packet-codecs/src/main/kotlin/space/uranos/net/packet/play/IncomingPlayerPositionPacketCodec.kt rename uranos-packet-codecs/src/main/kotlin/space/uranos/net/packet/play/{PlayerPositionAndLookPacketCodec.kt => OutgoingPlayerPositionPacketCodec.kt} (77%) create mode 100644 uranos-packet-codecs/src/main/kotlin/space/uranos/net/packet/play/PlayerLocationPacketCodec.kt create mode 100644 uranos-packet-codecs/src/main/kotlin/space/uranos/net/packet/play/PlayerOrientationPacketCodec.kt rename uranos-packet-codecs/src/main/kotlin/space/uranos/net/packet/play/{SetSelectedHotbarSlotPacketCodec.kt => SelectedHotbarSlotPacketCodec.kt} (61%) rename uranos-packets/src/main/kotlin/space/uranos/net/packet/play/{SetCompassTargetPacket.kt => CompassTargetPacket.kt} (69%) create mode 100644 uranos-packets/src/main/kotlin/space/uranos/net/packet/play/IncomingPlayerPositionPacket.kt rename uranos-packets/src/main/kotlin/space/uranos/net/packet/play/{PlayerPositionAndLookPacket.kt => OutgoingPlayerPositionPacket.kt} (94%) create mode 100644 uranos-packets/src/main/kotlin/space/uranos/net/packet/play/PlayerLocationPacket.kt create mode 100644 uranos-packets/src/main/kotlin/space/uranos/net/packet/play/PlayerOrientationPacket.kt rename uranos-packets/src/main/kotlin/space/uranos/net/packet/play/{SetSelectedHotbarSlotPacket.kt => SelectedHotbarSlotPacket.kt} (84%) diff --git a/uranos-api/src/main/kotlin/space/uranos/CalledFromWrongThread.kt b/uranos-api/src/main/kotlin/space/uranos/CalledFromWrongThread.kt new file mode 100644 index 0000000..80498dd --- /dev/null +++ b/uranos-api/src/main/kotlin/space/uranos/CalledFromWrongThread.kt @@ -0,0 +1,8 @@ +/* + * 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 + +class CalledFromWrongThread(message: String) : Exception(message) diff --git a/uranos-api/src/main/kotlin/space/uranos/Scheduler.kt b/uranos-api/src/main/kotlin/space/uranos/Scheduler.kt index 2435878..d69e46a 100644 --- a/uranos-api/src/main/kotlin/space/uranos/Scheduler.kt +++ b/uranos-api/src/main/kotlin/space/uranos/Scheduler.kt @@ -35,13 +35,13 @@ interface Scheduler { * * If you do not need the return value of [block], you should use [executeAfter] instead. */ - suspend fun runAfter(delay: Int, block: suspend () -> R): R + suspend fun runAfter(delay: Int, inServerThread: Boolean = false, block: suspend () -> R): R /** * Like [runAfter], but the task is *not* cancelled when the current coroutine scope is cancelled. */ - suspend fun runDetachedAfter(delay: Int, block: suspend () -> R): R = - withContext(NonCancellable) { runAfter(delay, block) } + suspend fun runDetachedAfter(delay: Int, inServerThread: Boolean = false, block: suspend () -> R): R = + withContext(NonCancellable) { runAfter(delay, inServerThread, block) } interface Task { fun cancel() diff --git a/uranos-api/src/main/kotlin/space/uranos/entity/Entity.kt b/uranos-api/src/main/kotlin/space/uranos/entity/Entity.kt index 4d7bd17..a3e278b 100644 --- a/uranos-api/src/main/kotlin/space/uranos/entity/Entity.kt +++ b/uranos-api/src/main/kotlin/space/uranos/entity/Entity.kt @@ -28,6 +28,9 @@ abstract class Entity internal constructor() { var world: World? = null; private set suspend fun setWorld(world: World?) { + if (world == null && this is PlayerEntity) + throw IllegalArgumentException("You cannot set the world of a PlayerEntity to null") + if (world == this.world) return worldMutex.withLock { diff --git a/uranos-api/src/main/kotlin/space/uranos/entity/LivingEntity.kt b/uranos-api/src/main/kotlin/space/uranos/entity/LivingEntity.kt index e3be770..75375c4 100644 --- a/uranos-api/src/main/kotlin/space/uranos/entity/LivingEntity.kt +++ b/uranos-api/src/main/kotlin/space/uranos/entity/LivingEntity.kt @@ -9,7 +9,7 @@ import space.uranos.Position import space.uranos.Vector abstract class LivingEntity : Entity(), Mobile { - abstract val headPitch: Float - abstract override val position: Position - abstract override val velocity: Vector + abstract var headPitch: Float + abstract override var position: Position + abstract override var velocity: Vector } diff --git a/uranos-api/src/main/kotlin/space/uranos/entity/Mobile.kt b/uranos-api/src/main/kotlin/space/uranos/entity/Mobile.kt index 9b4b5e0..52ebe13 100644 --- a/uranos-api/src/main/kotlin/space/uranos/entity/Mobile.kt +++ b/uranos-api/src/main/kotlin/space/uranos/entity/Mobile.kt @@ -9,10 +9,10 @@ import space.uranos.Position import space.uranos.Vector interface Mobile { - val position: Position + var position: Position /** * The velocity in blocks per tick. */ - val velocity: Vector + var velocity: Vector } diff --git a/uranos-api/src/main/kotlin/space/uranos/entity/PlayerEntity.kt b/uranos-api/src/main/kotlin/space/uranos/entity/PlayerEntity.kt new file mode 100644 index 0000000..3b453fa --- /dev/null +++ b/uranos-api/src/main/kotlin/space/uranos/entity/PlayerEntity.kt @@ -0,0 +1,32 @@ +/* + * 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.entity + +import space.uranos.Position +import space.uranos.Vector +import space.uranos.player.Player +import space.uranos.world.World + +open class PlayerEntity( + override var position: Position, + /** + * The player to which this entity belongs. + * + * **Not every player entity belongs to a player.** + */ + open val player: Player? = null, + override var headPitch: Float = 0f +) : LivingEntity() { + final override val type: EntityType = Type + override var velocity: Vector = Vector.ZERO + + /** + * Because [world] is never `null` for player entities, you can use this property instead of writing `world!!`. + */ + val safeWorld: World get() = world!! + + companion object Type : PlayerEntityType() +} diff --git a/uranos-api/src/main/kotlin/space/uranos/net/Session.kt b/uranos-api/src/main/kotlin/space/uranos/net/Session.kt index b6d41cc..abc7459 100644 --- a/uranos-api/src/main/kotlin/space/uranos/net/Session.kt +++ b/uranos-api/src/main/kotlin/space/uranos/net/Session.kt @@ -19,8 +19,7 @@ import java.util.* import kotlin.coroutines.CoroutineContext abstract class Session { - @Suppress("LeakingThis") - val events = EventBusWrapper(this) + val events by lazy { EventBusWrapper(this) } /** * The IP address of this session @@ -76,6 +75,7 @@ abstract class Session { val gameMode: GameMode, val world: World, val position: Position, + val headPitch: Float, val invulnerable: Boolean, val reducedDebugInfo: Boolean, val selectedHotbarSlot: Int diff --git a/uranos-api/src/main/kotlin/space/uranos/net/event/SessionAfterLoginEvent.kt b/uranos-api/src/main/kotlin/space/uranos/net/event/SessionAfterLoginEvent.kt index caec52e..964c898 100644 --- a/uranos-api/src/main/kotlin/space/uranos/net/event/SessionAfterLoginEvent.kt +++ b/uranos-api/src/main/kotlin/space/uranos/net/event/SessionAfterLoginEvent.kt @@ -30,6 +30,8 @@ class SessionAfterLoginEvent(override val target: Session) : SessionEvent(), Can */ var initialWorldAndLocation: Pair? = null + var headPitch: Float = 0f + var maxViewDistance: Int = 32 set(value) { if (value in 2..32) field = value @@ -61,7 +63,7 @@ class SessionAfterLoginEvent(override val target: Session) : SessionEvent(), Can var reducedDebugInfo: Boolean = false /** - * See [Player.invulnerable]. + * See [Player.vulnerable]. */ var invulnerable: Boolean = false diff --git a/uranos-api/src/main/kotlin/space/uranos/player/Player.kt b/uranos-api/src/main/kotlin/space/uranos/player/Player.kt index 1206332..d24c0d2 100644 --- a/uranos-api/src/main/kotlin/space/uranos/player/Player.kt +++ b/uranos-api/src/main/kotlin/space/uranos/player/Player.kt @@ -5,12 +5,11 @@ package space.uranos.player -import space.uranos.Position import space.uranos.chat.TextComponent +import space.uranos.entity.PlayerEntity import space.uranos.net.Session import space.uranos.world.Chunk import space.uranos.world.VoxelLocation -import space.uranos.world.World import java.util.* import kotlin.coroutines.CoroutineContext @@ -22,6 +21,8 @@ interface Player { */ val session: Session + val entity: PlayerEntity + /** * The name of this player. */ @@ -51,16 +52,6 @@ interface Player { val chatMode: ChatMode ) - /** - * The current position of this player. - */ - var position: Position - - /** - * The world which currently contains this player. - */ - val world: World - /** * The index of the hotbar slot which is currently selected. * Must be in `0..8`. @@ -75,9 +66,9 @@ interface Player { var reducedDebugInfo: Boolean /** - * Whether the player cannot take damage through other entities or the world. + * Whether the player can take damage through other entities or the world. */ - var invulnerable: Boolean + var vulnerable: Boolean /** * Whether the player can start flying by itself. diff --git a/uranos-api/src/main/kotlin/space/uranos/server/Server.kt b/uranos-api/src/main/kotlin/space/uranos/server/Server.kt index 75e99a0..5aa19f6 100644 --- a/uranos-api/src/main/kotlin/space/uranos/server/Server.kt +++ b/uranos-api/src/main/kotlin/space/uranos/server/Server.kt @@ -5,6 +5,7 @@ package space.uranos.server +import space.uranos.CalledFromWrongThread import space.uranos.Registry import space.uranos.Scheduler import space.uranos.command.Command @@ -28,6 +29,8 @@ abstract class Server { abstract val eventBus: EventBus abstract val eventHandlerPositions: EventHandlerPositionManager + protected abstract val serverThread: Thread + /** * [CoroutineContext] confined to the server thread. * @@ -65,6 +68,13 @@ abstract class Server { */ abstract fun shutdown() + /** + * Throws [CalledFromWrongThread] when called from a thread which is not the server thread. + */ + fun ensureServerThread(errorMessage: String) { + if (Thread.currentThread() != serverThread) throw CalledFromWrongThread(errorMessage) + } + /** * Set of all existing [Entity] instances. * diff --git a/uranos-packet-codecs/src/main/kotlin/space/uranos/net/packet/play/SetCompassTargetPacketCodec.kt b/uranos-packet-codecs/src/main/kotlin/space/uranos/net/packet/play/CompassTargetPacketCodec.kt similarity index 68% rename from uranos-packet-codecs/src/main/kotlin/space/uranos/net/packet/play/SetCompassTargetPacketCodec.kt rename to uranos-packet-codecs/src/main/kotlin/space/uranos/net/packet/play/CompassTargetPacketCodec.kt index 2b93680..c3722bb 100644 --- a/uranos-packet-codecs/src/main/kotlin/space/uranos/net/packet/play/SetCompassTargetPacketCodec.kt +++ b/uranos-packet-codecs/src/main/kotlin/space/uranos/net/packet/play/CompassTargetPacketCodec.kt @@ -9,9 +9,9 @@ import io.netty.buffer.ByteBuf import space.uranos.net.MinecraftProtocolDataTypes.writeVoxelLocation import space.uranos.net.packet.OutgoingPacketCodec -object SetCompassTargetPacketCodec : - OutgoingPacketCodec(0x42, SetCompassTargetPacket::class) { - override fun SetCompassTargetPacket.encode(dst: ByteBuf) { +object CompassTargetPacketCodec : + OutgoingPacketCodec(0x42, CompassTargetPacket::class) { + override fun CompassTargetPacket.encode(dst: ByteBuf) { dst.writeVoxelLocation(target) } } diff --git a/uranos-packet-codecs/src/main/kotlin/space/uranos/net/packet/play/IncomingPlayerPositionPacketCodec.kt b/uranos-packet-codecs/src/main/kotlin/space/uranos/net/packet/play/IncomingPlayerPositionPacketCodec.kt new file mode 100644 index 0000000..2c8e24b --- /dev/null +++ b/uranos-packet-codecs/src/main/kotlin/space/uranos/net/packet/play/IncomingPlayerPositionPacketCodec.kt @@ -0,0 +1,18 @@ +/* + * 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.Position +import space.uranos.net.packet.IncomingPacketCodec + +object IncomingPlayerPositionPacketCodec : + IncomingPacketCodec(0x13, IncomingPlayerPositionPacket::class) { + override fun decode(msg: ByteBuf): IncomingPlayerPositionPacket = IncomingPlayerPositionPacket( + Position(msg.readDouble(), msg.readDouble(), msg.readDouble(), 360 - msg.readFloat() % 360, msg.readFloat()), + msg.readBoolean() + ) +} diff --git a/uranos-packet-codecs/src/main/kotlin/space/uranos/net/packet/play/PlayerPositionAndLookPacketCodec.kt b/uranos-packet-codecs/src/main/kotlin/space/uranos/net/packet/play/OutgoingPlayerPositionPacketCodec.kt similarity index 77% rename from uranos-packet-codecs/src/main/kotlin/space/uranos/net/packet/play/PlayerPositionAndLookPacketCodec.kt rename to uranos-packet-codecs/src/main/kotlin/space/uranos/net/packet/play/OutgoingPlayerPositionPacketCodec.kt index ef1d6f5..e1e3a4b 100644 --- a/uranos-packet-codecs/src/main/kotlin/space/uranos/net/packet/play/PlayerPositionAndLookPacketCodec.kt +++ b/uranos-packet-codecs/src/main/kotlin/space/uranos/net/packet/play/OutgoingPlayerPositionPacketCodec.kt @@ -10,9 +10,9 @@ import space.uranos.net.MinecraftProtocolDataTypes.writeVarInt import space.uranos.net.packet.OutgoingPacketCodec import space.uranos.util.bitmask -object PlayerPositionAndLookPacketCodec : - OutgoingPacketCodec(0x34, PlayerPositionAndLookPacket::class) { - override fun PlayerPositionAndLookPacket.encode(dst: ByteBuf) { +object OutgoingPlayerPositionPacketCodec : + OutgoingPacketCodec(0x34, OutgoingPlayerPositionPacket::class) { + override fun OutgoingPlayerPositionPacket.encode(dst: ByteBuf) { dst.writeDouble(position.x) dst.writeDouble(position.y) dst.writeDouble(position.z) diff --git a/uranos-packet-codecs/src/main/kotlin/space/uranos/net/packet/play/PlayProtocol.kt b/uranos-packet-codecs/src/main/kotlin/space/uranos/net/packet/play/PlayProtocol.kt index 3e3bd47..39dadd7 100644 --- a/uranos-packet-codecs/src/main/kotlin/space/uranos/net/packet/play/PlayProtocol.kt +++ b/uranos-packet-codecs/src/main/kotlin/space/uranos/net/packet/play/PlayProtocol.kt @@ -12,20 +12,23 @@ object PlayProtocol : Protocol( ChunkDataPacketCodec, ChunkLightDataPacketCodec, ClientSettingsPacketCodec, + CompassTargetPacketCodec, DeclareCommandsPacketCodec, DeclareRecipesPacketCodec, DisconnectPacketCodec, IncomingKeepAlivePacketCodec, + IncomingPlayerPositionPacketCodec, IncomingPluginMessagePacketCodec, JoinGamePacketCodec, OutgoingKeepAlivePacketCodec, + OutgoingPlayerPositionPacketCodec, OutgoingPluginMessagePacketCodec, PlayerAbilitiesPacketCodec, PlayerInfoPacketCodec, - PlayerPositionAndLookPacketCodec, + PlayerLocationPacketCodec, + PlayerOrientationPacketCodec, + SelectedHotbarSlotPacketCodec, ServerDifficultyPacketCodec, - SetCompassTargetPacketCodec, - SetSelectedHotbarSlotPacketCodec, SpawnExperienceOrbPacketCodec, SpawnLivingEntityPacketCodec, SpawnObjectEntityPacketCodec, diff --git a/uranos-packet-codecs/src/main/kotlin/space/uranos/net/packet/play/PlayerLocationPacketCodec.kt b/uranos-packet-codecs/src/main/kotlin/space/uranos/net/packet/play/PlayerLocationPacketCodec.kt new file mode 100644 index 0000000..2d579ff --- /dev/null +++ b/uranos-packet-codecs/src/main/kotlin/space/uranos/net/packet/play/PlayerLocationPacketCodec.kt @@ -0,0 +1,18 @@ +/* + * 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.Location +import space.uranos.net.packet.IncomingPacketCodec + +object PlayerLocationPacketCodec : + IncomingPacketCodec(0x12, PlayerLocationPacket::class) { + override fun decode(msg: ByteBuf): PlayerLocationPacket = PlayerLocationPacket( + Location(msg.readDouble(), msg.readDouble(), msg.readDouble()), + msg.readBoolean() + ) +} diff --git a/uranos-packet-codecs/src/main/kotlin/space/uranos/net/packet/play/PlayerOrientationPacketCodec.kt b/uranos-packet-codecs/src/main/kotlin/space/uranos/net/packet/play/PlayerOrientationPacketCodec.kt new file mode 100644 index 0000000..aa83126 --- /dev/null +++ b/uranos-packet-codecs/src/main/kotlin/space/uranos/net/packet/play/PlayerOrientationPacketCodec.kt @@ -0,0 +1,18 @@ +/* + * 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.packet.IncomingPacketCodec + +object PlayerOrientationPacketCodec : + IncomingPacketCodec(0x14, PlayerOrientationPacket::class) { + override fun decode(msg: ByteBuf): PlayerOrientationPacket = PlayerOrientationPacket( + 360 - msg.readFloat() % 360, + msg.readFloat(), + msg.readBoolean() + ) +} diff --git a/uranos-packet-codecs/src/main/kotlin/space/uranos/net/packet/play/SetSelectedHotbarSlotPacketCodec.kt b/uranos-packet-codecs/src/main/kotlin/space/uranos/net/packet/play/SelectedHotbarSlotPacketCodec.kt similarity index 61% rename from uranos-packet-codecs/src/main/kotlin/space/uranos/net/packet/play/SetSelectedHotbarSlotPacketCodec.kt rename to uranos-packet-codecs/src/main/kotlin/space/uranos/net/packet/play/SelectedHotbarSlotPacketCodec.kt index d41b92e..03507bd 100644 --- a/uranos-packet-codecs/src/main/kotlin/space/uranos/net/packet/play/SetSelectedHotbarSlotPacketCodec.kt +++ b/uranos-packet-codecs/src/main/kotlin/space/uranos/net/packet/play/SelectedHotbarSlotPacketCodec.kt @@ -8,9 +8,9 @@ package space.uranos.net.packet.play import io.netty.buffer.ByteBuf import space.uranos.net.packet.OutgoingPacketCodec -object SetSelectedHotbarSlotPacketCodec : - OutgoingPacketCodec(0x3F, SetSelectedHotbarSlotPacket::class) { - override fun SetSelectedHotbarSlotPacket.encode(dst: ByteBuf) { +object SelectedHotbarSlotPacketCodec : + OutgoingPacketCodec(0x3F, SelectedHotbarSlotPacket::class) { + override fun SelectedHotbarSlotPacket.encode(dst: ByteBuf) { dst.writeByte(index) } } diff --git a/uranos-packets/src/main/kotlin/space/uranos/net/packet/play/SetCompassTargetPacket.kt b/uranos-packets/src/main/kotlin/space/uranos/net/packet/play/CompassTargetPacket.kt similarity index 69% rename from uranos-packets/src/main/kotlin/space/uranos/net/packet/play/SetCompassTargetPacket.kt rename to uranos-packets/src/main/kotlin/space/uranos/net/packet/play/CompassTargetPacket.kt index 84cb76e..77efb84 100644 --- a/uranos-packets/src/main/kotlin/space/uranos/net/packet/play/SetCompassTargetPacket.kt +++ b/uranos-packets/src/main/kotlin/space/uranos/net/packet/play/CompassTargetPacket.kt @@ -8,4 +8,5 @@ package space.uranos.net.packet.play import space.uranos.net.packet.OutgoingPacket import space.uranos.world.VoxelLocation -data class SetCompassTargetPacket(val target: VoxelLocation) : OutgoingPacket() +// TODO: Remove "Set" prefix for all packet names +data class CompassTargetPacket(val target: VoxelLocation) : OutgoingPacket() diff --git a/uranos-packets/src/main/kotlin/space/uranos/net/packet/play/IncomingPlayerPositionPacket.kt b/uranos-packets/src/main/kotlin/space/uranos/net/packet/play/IncomingPlayerPositionPacket.kt new file mode 100644 index 0000000..30e4f4c --- /dev/null +++ b/uranos-packets/src/main/kotlin/space/uranos/net/packet/play/IncomingPlayerPositionPacket.kt @@ -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 space.uranos.Position +import space.uranos.net.packet.IncomingPacket + +/** + * Combination of [PlayerLocationPacket] and [PlayerOrientationPacket]. + */ +data class IncomingPlayerPositionPacket( + val position: Position, + val onGround: Boolean +) : IncomingPacket() diff --git a/uranos-packets/src/main/kotlin/space/uranos/net/packet/play/PlayerPositionAndLookPacket.kt b/uranos-packets/src/main/kotlin/space/uranos/net/packet/play/OutgoingPlayerPositionPacket.kt similarity index 94% rename from uranos-packets/src/main/kotlin/space/uranos/net/packet/play/PlayerPositionAndLookPacket.kt rename to uranos-packets/src/main/kotlin/space/uranos/net/packet/play/OutgoingPlayerPositionPacket.kt index c50adf5..48e61a6 100644 --- a/uranos-packets/src/main/kotlin/space/uranos/net/packet/play/PlayerPositionAndLookPacket.kt +++ b/uranos-packets/src/main/kotlin/space/uranos/net/packet/play/OutgoingPlayerPositionPacket.kt @@ -12,7 +12,7 @@ import kotlin.random.Random /** * Teleports the receiving player to the specified position. */ -data class PlayerPositionAndLookPacket( +data class OutgoingPlayerPositionPacket( val position: Position, val relativeX: Boolean = false, val relativeY: Boolean = false, diff --git a/uranos-packets/src/main/kotlin/space/uranos/net/packet/play/PlayerLocationPacket.kt b/uranos-packets/src/main/kotlin/space/uranos/net/packet/play/PlayerLocationPacket.kt new file mode 100644 index 0000000..831d3d1 --- /dev/null +++ b/uranos-packets/src/main/kotlin/space/uranos/net/packet/play/PlayerLocationPacket.kt @@ -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 space.uranos.Location +import space.uranos.net.packet.IncomingPacket + +/** + * Sent by the client to update the player's x, y and z coordinates on the server. + */ +data class PlayerLocationPacket( + val location: Location, + val onGround: Boolean +) : IncomingPacket() diff --git a/uranos-packets/src/main/kotlin/space/uranos/net/packet/play/PlayerOrientationPacket.kt b/uranos-packets/src/main/kotlin/space/uranos/net/packet/play/PlayerOrientationPacket.kt new file mode 100644 index 0000000..215c907 --- /dev/null +++ b/uranos-packets/src/main/kotlin/space/uranos/net/packet/play/PlayerOrientationPacket.kt @@ -0,0 +1,26 @@ +/* + * 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.Position +import space.uranos.net.packet.IncomingPacket + +/** + * Sent by the client to update the player's orientation on the server. + * + * @see [Position] + */ +data class PlayerOrientationPacket( + /** + * Yaw in degrees. + */ + val yaw: Float, + /** + * Pitch in degrees. + */ + val pitch: Float, + val onGround: Boolean +) : IncomingPacket() diff --git a/uranos-packets/src/main/kotlin/space/uranos/net/packet/play/SetSelectedHotbarSlotPacket.kt b/uranos-packets/src/main/kotlin/space/uranos/net/packet/play/SelectedHotbarSlotPacket.kt similarity index 84% rename from uranos-packets/src/main/kotlin/space/uranos/net/packet/play/SetSelectedHotbarSlotPacket.kt rename to uranos-packets/src/main/kotlin/space/uranos/net/packet/play/SelectedHotbarSlotPacket.kt index cea7898..7998292 100644 --- a/uranos-packets/src/main/kotlin/space/uranos/net/packet/play/SetSelectedHotbarSlotPacket.kt +++ b/uranos-packets/src/main/kotlin/space/uranos/net/packet/play/SelectedHotbarSlotPacket.kt @@ -10,7 +10,7 @@ import space.uranos.net.packet.OutgoingPacket /** * Sent by the server to select a specific hotbar slot. */ -data class SetSelectedHotbarSlotPacket(val index: Int) : OutgoingPacket() { +data class SelectedHotbarSlotPacket(val index: Int) : OutgoingPacket() { init { if (index !in 0..8) throw IllegalArgumentException("index must be between 0 and 8") } diff --git a/uranos-server/src/main/kotlin/space/uranos/UranosScheduler.kt b/uranos-server/src/main/kotlin/space/uranos/UranosScheduler.kt index 3cc32b9..970c9f7 100644 --- a/uranos-server/src/main/kotlin/space/uranos/UranosScheduler.kt +++ b/uranos-server/src/main/kotlin/space/uranos/UranosScheduler.kt @@ -5,15 +5,13 @@ package space.uranos -import kotlinx.coroutines.CancellableContinuation -import kotlinx.coroutines.runBlocking -import kotlinx.coroutines.suspendCancellableCoroutine +import kotlinx.coroutines.* import space.uranos.logging.Logger import space.uranos.server.Server import java.util.* import java.util.concurrent.ConcurrentHashMap -import java.util.concurrent.Executors import java.util.concurrent.ScheduledExecutorService +import java.util.concurrent.ScheduledFuture import java.util.concurrent.TimeUnit import kotlin.collections.LinkedHashSet import kotlin.coroutines.resume @@ -21,8 +19,7 @@ import kotlin.coroutines.resume /** * Basically ExecutorService but for coroutines and with ticks. */ -class UranosScheduler : Scheduler { - private lateinit var executor: ScheduledExecutorService +class UranosScheduler(private val executor: ScheduledExecutorService) : Scheduler { private val tasks = ConcurrentHashMap.newKeySet>() private val shutdownTasks = Collections.synchronizedSet(LinkedHashSet Unit>()) @@ -42,12 +39,13 @@ class UranosScheduler : Scheduler { } } - fun startTicking() { - if (this::executor.isInitialized) throw IllegalStateException("Ticking was already started") + private var future: ScheduledFuture<*>? = null + + fun start() { + if (future != null) throw IllegalStateException("Already started") val interval = 1000L / Server.TICKS_PER_SECOND - executor = Executors.newSingleThreadScheduledExecutor { r -> Thread(r, "Scheduler") } - executor.scheduleAtFixedRate({ + future = executor.scheduleAtFixedRate({ runBlocking { val startTime = System.currentTimeMillis() @@ -76,15 +74,14 @@ class UranosScheduler : Scheduler { }, 0, interval, TimeUnit.MILLISECONDS) } - private fun stopTicking() { - if (!this::executor.isInitialized) throw IllegalStateException("Ticking was not started") - - executor.shutdown() - executor.awaitTermination(3, TimeUnit.SECONDS) + private fun stop() { + if (future == null) throw IllegalArgumentException("Not started") + future!!.cancel(false) + future = null } suspend fun shutdown() { - stopTicking() + stop() shutdownTasks.forEach { it.invoke() } } @@ -110,12 +107,14 @@ class UranosScheduler : Scheduler { } } - override suspend fun runAfter(delay: Int, block: suspend () -> R): R { + // TODO: Use the current coroutine context for the task execution + override suspend fun runAfter(delay: Int, inServerThread: Boolean, block: suspend () -> R): R { lateinit var continuation: CancellableContinuation + val context = currentCoroutineContext() - val fn = suspend { - continuation.resume(block()) - } + val fn = + if (inServerThread) suspend { continuation.resume(block()) } + else suspend { withContext(context) { continuation.resume(block()) } } val task = Task(fn, null, delay) diff --git a/uranos-server/src/main/kotlin/space/uranos/UranosServer.kt b/uranos-server/src/main/kotlin/space/uranos/UranosServer.kt index 44471ac..8462e0d 100644 --- a/uranos-server/src/main/kotlin/space/uranos/UranosServer.kt +++ b/uranos-server/src/main/kotlin/space/uranos/UranosServer.kt @@ -19,6 +19,7 @@ import space.uranos.event.UranosEventHandlerPositionManager import space.uranos.logging.Logger import space.uranos.logging.UranosLoggingOutputProvider import space.uranos.net.UranosSocketServer +import space.uranos.player.UranosPlayer import space.uranos.plugin.UranosPluginManager import space.uranos.recipe.Recipe import space.uranos.server.Server @@ -28,6 +29,7 @@ import space.uranos.world.Dimension import java.io.File import java.security.KeyPair import java.util.concurrent.Executors +import java.util.concurrent.ScheduledExecutorService import kotlin.coroutines.CoroutineContext import kotlin.system.exitProcess @@ -46,12 +48,15 @@ class UranosServer internal constructor() : Server() { val x509EncodedPublicKey: ByteArray = EncryptionUtils.generateX509Key(keyPair.public).encoded + override lateinit var serverThread: Thread + private val scheduledExecutorService: ScheduledExecutorService = + Executors.newSingleThreadScheduledExecutor { r -> Thread(r, "server").also { serverThread = it } } + override val coroutineContext: CoroutineContext = - CoroutineName("Server") + SupervisorJob() + - Executors.newSingleThreadExecutor { r -> Thread(r, "server") }.asCoroutineDispatcher() + CoroutineName("Server") + SupervisorJob() + scheduledExecutorService.asCoroutineDispatcher() override val sessions by socketServer::sessions - override val players get() = sessions.mapNotNull { it.player } + override val players get() = sessions.mapNotNull { it.player as UranosPlayer? } override val pluginManager = UranosPluginManager(this) override val serverDirectory: File = @@ -67,7 +72,7 @@ class UranosServer internal constructor() : Server() { override val biomeRegistry = BiomeRegistry() override val loggingOutputProvider = UranosLoggingOutputProvider - override val scheduler = UranosScheduler() + override val scheduler = UranosScheduler(scheduledExecutorService) val config = ConfigLoader.Builder() .addPropertySource( @@ -110,7 +115,7 @@ class UranosServer internal constructor() : Server() { socketServer.bind() logger info "Listening on ${config.host}:${config.port}" - scheduler.startTicking() + scheduler.start() } companion object { diff --git a/uranos-server/src/main/kotlin/space/uranos/net/LoginAndJoinProcedure.kt b/uranos-server/src/main/kotlin/space/uranos/net/LoginAndJoinProcedure.kt index 927f065..1236cfc 100644 --- a/uranos-server/src/main/kotlin/space/uranos/net/LoginAndJoinProcedure.kt +++ b/uranos-server/src/main/kotlin/space/uranos/net/LoginAndJoinProcedure.kt @@ -135,6 +135,7 @@ class LoginAndJoinProcedure(val session: UranosSession) { event.gameMode, initialWorldAndLocation.first, initialWorldAndLocation.second, + event.headPitch, event.invulnerable, event.reducedDebugInfo, event.selectedHotbarSlot @@ -160,8 +161,8 @@ class LoginAndJoinProcedure(val session: UranosSession) { state.uuid, state.gameMode, settings, - state.world, state.position, + state.headPitch, state.reducedDebugInfo, state.fieldOfView, state.canFly, @@ -174,7 +175,7 @@ class LoginAndJoinProcedure(val session: UranosSession) { session.state = Session.State.Joining(player) - session.send(SetSelectedHotbarSlotPacket(state.selectedHotbarSlot)) + session.send(SelectedHotbarSlotPacket(state.selectedHotbarSlot)) session.send(DeclareRecipesPacket(session.server.recipeRegistry.items.values)) session.send(tagsPacket) @@ -182,7 +183,7 @@ class LoginAndJoinProcedure(val session: UranosSession) { // session.send(DeclareCommandsPacket(session.server.commandRegistry.items.values)) // UnlockRecipes - session.send(PlayerPositionAndLookPacket(state.position)) + session.send(OutgoingPlayerPositionPacket(state.position)) session.send(PlayerInfoPacket(PlayerInfoPacket.Action.AddPlayer((session.server.players + player).map { it.uuid to PlayerInfoPacket.Action.AddPlayer.Data( @@ -203,14 +204,14 @@ class LoginAndJoinProcedure(val session: UranosSession) { ) ) - session.send(UpdateViewPositionPacket(Chunk.Key.from(player.position.toVoxelLocation()))) + session.send(UpdateViewPositionPacket(Chunk.Key.from(player.entity.position.toVoxelLocation()))) session.scheduleKeepAlivePacket(true) player.sendChunksAndLight() // WorldBorder - session.send(SetCompassTargetPacket(player.compassTarget)) - session.send(PlayerPositionAndLookPacket(state.position)) + session.send(CompassTargetPacket(player.compassTarget)) + session.send(OutgoingPlayerPositionPacket(state.position)) // TODO: Wait for ClientStatus(action=0) packet } diff --git a/uranos-server/src/main/kotlin/space/uranos/net/UranosSession.kt b/uranos-server/src/main/kotlin/space/uranos/net/UranosSession.kt index bd5054e..aad7990 100644 --- a/uranos-server/src/main/kotlin/space/uranos/net/UranosSession.kt +++ b/uranos-server/src/main/kotlin/space/uranos/net/UranosSession.kt @@ -140,7 +140,7 @@ class UranosSession(private val channel: io.netty.channel.Channel, val server: U val codec = currentProtocol!!.incomingPacketCodecsByID[packetID] if (codec == null) { - val message = "Received an unknown packet (ID: $packetID)" + val message = "Received an unknown packet (ID: 0x${packetID.toString(16).padStart(2, '0')})" if (server.config.developmentMode) logger warn "$message. This will cause the client to disconnect in production mode." diff --git a/uranos-server/src/main/kotlin/space/uranos/player/UranosPlayer.kt b/uranos-server/src/main/kotlin/space/uranos/player/UranosPlayer.kt index 35bc290..543d03f 100644 --- a/uranos-server/src/main/kotlin/space/uranos/player/UranosPlayer.kt +++ b/uranos-server/src/main/kotlin/space/uranos/player/UranosPlayer.kt @@ -7,13 +7,13 @@ package space.uranos.player import space.uranos.Position import space.uranos.chat.TextComponent +import space.uranos.entity.PlayerEntity import space.uranos.net.Session import space.uranos.net.packet.play.ChunkDataPacket import space.uranos.net.packet.play.ChunkLightDataPacket import space.uranos.util.clampArgument import space.uranos.world.Chunk import space.uranos.world.VoxelLocation -import space.uranos.world.World import java.util.* import kotlin.math.abs @@ -23,14 +23,14 @@ class UranosPlayer( override val uuid: UUID, override var gameMode: GameMode, override var settings: Player.Settings, - override val world: World, - override var position: Position, + position: Position, + headPitch: Float, override var reducedDebugInfo: Boolean, override var fieldOfView: Float, override var canFly: Boolean, override var flying: Boolean, override var flyingSpeed: Float, - override var invulnerable: Boolean, + override var vulnerable: Boolean, override var compassTarget: VoxelLocation, selectedHotbarSlot: Int ) : Player { @@ -51,18 +51,20 @@ class UranosPlayer( updateCurrentlyViewedChunks() } + override val entity: PlayerEntity = PlayerEntity(position, this, headPitch) + /** * Sets [currentlyViewedChunks] to all chunks in the view distance. */ private fun updateCurrentlyViewedChunks() { - val (centerX, centerZ) = Chunk.Key.from(position.toVoxelLocation()) + val (centerX, centerZ) = Chunk.Key.from(entity.position.toVoxelLocation()) val edgeLength = settings.viewDistance + 1 currentlyViewedChunks = buildList(edgeLength * edgeLength) { for (x in (centerX - settings.viewDistance)..(centerX + settings.viewDistance)) { for (z in (centerZ - settings.viewDistance)..(centerZ + settings.viewDistance)) { - add(world.getChunk(Chunk.Key(x, z))) + add(entity.safeWorld.getChunk(Chunk.Key(x, z))) } } }