Add player movement related packets and work on entities
This commit is contained in:
parent
cb50c7bb39
commit
4e34dbc1e2
28 changed files with 249 additions and 78 deletions
|
@ -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)
|
|
@ -35,13 +35,13 @@ interface Scheduler {
|
||||||
*
|
*
|
||||||
* If you do not need the return value of [block], you should use [executeAfter] instead.
|
* If you do not need the return value of [block], you should use [executeAfter] instead.
|
||||||
*/
|
*/
|
||||||
suspend fun <R : Any> runAfter(delay: Int, block: suspend () -> R): R
|
suspend fun <R : Any> 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.
|
* Like [runAfter], but the task is *not* cancelled when the current coroutine scope is cancelled.
|
||||||
*/
|
*/
|
||||||
suspend fun <R : Any> runDetachedAfter(delay: Int, block: suspend () -> R): R =
|
suspend fun <R : Any> runDetachedAfter(delay: Int, inServerThread: Boolean = false, block: suspend () -> R): R =
|
||||||
withContext(NonCancellable) { runAfter(delay, block) }
|
withContext(NonCancellable) { runAfter(delay, inServerThread, block) }
|
||||||
|
|
||||||
interface Task {
|
interface Task {
|
||||||
fun cancel()
|
fun cancel()
|
||||||
|
|
|
@ -28,6 +28,9 @@ abstract class Entity internal constructor() {
|
||||||
var world: World? = null; private set
|
var world: World? = null; private set
|
||||||
|
|
||||||
suspend fun setWorld(world: World?) {
|
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
|
if (world == this.world) return
|
||||||
|
|
||||||
worldMutex.withLock {
|
worldMutex.withLock {
|
||||||
|
|
|
@ -9,7 +9,7 @@ import space.uranos.Position
|
||||||
import space.uranos.Vector
|
import space.uranos.Vector
|
||||||
|
|
||||||
abstract class LivingEntity : Entity(), Mobile {
|
abstract class LivingEntity : Entity(), Mobile {
|
||||||
abstract val headPitch: Float
|
abstract var headPitch: Float
|
||||||
abstract override val position: Position
|
abstract override var position: Position
|
||||||
abstract override val velocity: Vector
|
abstract override var velocity: Vector
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,10 +9,10 @@ import space.uranos.Position
|
||||||
import space.uranos.Vector
|
import space.uranos.Vector
|
||||||
|
|
||||||
interface Mobile {
|
interface Mobile {
|
||||||
val position: Position
|
var position: Position
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The velocity in blocks per tick.
|
* The velocity in blocks per tick.
|
||||||
*/
|
*/
|
||||||
val velocity: Vector
|
var velocity: Vector
|
||||||
}
|
}
|
||||||
|
|
|
@ -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()
|
||||||
|
}
|
|
@ -19,8 +19,7 @@ import java.util.*
|
||||||
import kotlin.coroutines.CoroutineContext
|
import kotlin.coroutines.CoroutineContext
|
||||||
|
|
||||||
abstract class Session {
|
abstract class Session {
|
||||||
@Suppress("LeakingThis")
|
val events by lazy { EventBusWrapper<Session>(this) }
|
||||||
val events = EventBusWrapper<Session>(this)
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The IP address of this session
|
* The IP address of this session
|
||||||
|
@ -76,6 +75,7 @@ abstract class Session {
|
||||||
val gameMode: GameMode,
|
val gameMode: GameMode,
|
||||||
val world: World,
|
val world: World,
|
||||||
val position: Position,
|
val position: Position,
|
||||||
|
val headPitch: Float,
|
||||||
val invulnerable: Boolean,
|
val invulnerable: Boolean,
|
||||||
val reducedDebugInfo: Boolean,
|
val reducedDebugInfo: Boolean,
|
||||||
val selectedHotbarSlot: Int
|
val selectedHotbarSlot: Int
|
||||||
|
|
|
@ -30,6 +30,8 @@ class SessionAfterLoginEvent(override val target: Session) : SessionEvent(), Can
|
||||||
*/
|
*/
|
||||||
var initialWorldAndLocation: Pair<World, Position>? = null
|
var initialWorldAndLocation: Pair<World, Position>? = null
|
||||||
|
|
||||||
|
var headPitch: Float = 0f
|
||||||
|
|
||||||
var maxViewDistance: Int = 32
|
var maxViewDistance: Int = 32
|
||||||
set(value) {
|
set(value) {
|
||||||
if (value in 2..32) field = value
|
if (value in 2..32) field = value
|
||||||
|
@ -61,7 +63,7 @@ class SessionAfterLoginEvent(override val target: Session) : SessionEvent(), Can
|
||||||
var reducedDebugInfo: Boolean = false
|
var reducedDebugInfo: Boolean = false
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* See [Player.invulnerable].
|
* See [Player.vulnerable].
|
||||||
*/
|
*/
|
||||||
var invulnerable: Boolean = false
|
var invulnerable: Boolean = false
|
||||||
|
|
||||||
|
|
|
@ -5,12 +5,11 @@
|
||||||
|
|
||||||
package space.uranos.player
|
package space.uranos.player
|
||||||
|
|
||||||
import space.uranos.Position
|
|
||||||
import space.uranos.chat.TextComponent
|
import space.uranos.chat.TextComponent
|
||||||
|
import space.uranos.entity.PlayerEntity
|
||||||
import space.uranos.net.Session
|
import space.uranos.net.Session
|
||||||
import space.uranos.world.Chunk
|
import space.uranos.world.Chunk
|
||||||
import space.uranos.world.VoxelLocation
|
import space.uranos.world.VoxelLocation
|
||||||
import space.uranos.world.World
|
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import kotlin.coroutines.CoroutineContext
|
import kotlin.coroutines.CoroutineContext
|
||||||
|
|
||||||
|
@ -22,6 +21,8 @@ interface Player {
|
||||||
*/
|
*/
|
||||||
val session: Session
|
val session: Session
|
||||||
|
|
||||||
|
val entity: PlayerEntity
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The name of this player.
|
* The name of this player.
|
||||||
*/
|
*/
|
||||||
|
@ -51,16 +52,6 @@ interface Player {
|
||||||
val chatMode: ChatMode
|
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.
|
* The index of the hotbar slot which is currently selected.
|
||||||
* Must be in `0..8`.
|
* Must be in `0..8`.
|
||||||
|
@ -75,9 +66,9 @@ interface Player {
|
||||||
var reducedDebugInfo: Boolean
|
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.
|
* Whether the player can start flying by itself.
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
|
|
||||||
package space.uranos.server
|
package space.uranos.server
|
||||||
|
|
||||||
|
import space.uranos.CalledFromWrongThread
|
||||||
import space.uranos.Registry
|
import space.uranos.Registry
|
||||||
import space.uranos.Scheduler
|
import space.uranos.Scheduler
|
||||||
import space.uranos.command.Command
|
import space.uranos.command.Command
|
||||||
|
@ -28,6 +29,8 @@ abstract class Server {
|
||||||
abstract val eventBus: EventBus
|
abstract val eventBus: EventBus
|
||||||
abstract val eventHandlerPositions: EventHandlerPositionManager
|
abstract val eventHandlerPositions: EventHandlerPositionManager
|
||||||
|
|
||||||
|
protected abstract val serverThread: Thread
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* [CoroutineContext] confined to the server thread.
|
* [CoroutineContext] confined to the server thread.
|
||||||
*
|
*
|
||||||
|
@ -65,6 +68,13 @@ abstract class Server {
|
||||||
*/
|
*/
|
||||||
abstract fun shutdown()
|
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.
|
* Set of all existing [Entity] instances.
|
||||||
*
|
*
|
||||||
|
|
|
@ -9,9 +9,9 @@ import io.netty.buffer.ByteBuf
|
||||||
import space.uranos.net.MinecraftProtocolDataTypes.writeVoxelLocation
|
import space.uranos.net.MinecraftProtocolDataTypes.writeVoxelLocation
|
||||||
import space.uranos.net.packet.OutgoingPacketCodec
|
import space.uranos.net.packet.OutgoingPacketCodec
|
||||||
|
|
||||||
object SetCompassTargetPacketCodec :
|
object CompassTargetPacketCodec :
|
||||||
OutgoingPacketCodec<SetCompassTargetPacket>(0x42, SetCompassTargetPacket::class) {
|
OutgoingPacketCodec<CompassTargetPacket>(0x42, CompassTargetPacket::class) {
|
||||||
override fun SetCompassTargetPacket.encode(dst: ByteBuf) {
|
override fun CompassTargetPacket.encode(dst: ByteBuf) {
|
||||||
dst.writeVoxelLocation(target)
|
dst.writeVoxelLocation(target)
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -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<IncomingPlayerPositionPacket>(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()
|
||||||
|
)
|
||||||
|
}
|
|
@ -10,9 +10,9 @@ import space.uranos.net.MinecraftProtocolDataTypes.writeVarInt
|
||||||
import space.uranos.net.packet.OutgoingPacketCodec
|
import space.uranos.net.packet.OutgoingPacketCodec
|
||||||
import space.uranos.util.bitmask
|
import space.uranos.util.bitmask
|
||||||
|
|
||||||
object PlayerPositionAndLookPacketCodec :
|
object OutgoingPlayerPositionPacketCodec :
|
||||||
OutgoingPacketCodec<PlayerPositionAndLookPacket>(0x34, PlayerPositionAndLookPacket::class) {
|
OutgoingPacketCodec<OutgoingPlayerPositionPacket>(0x34, OutgoingPlayerPositionPacket::class) {
|
||||||
override fun PlayerPositionAndLookPacket.encode(dst: ByteBuf) {
|
override fun OutgoingPlayerPositionPacket.encode(dst: ByteBuf) {
|
||||||
dst.writeDouble(position.x)
|
dst.writeDouble(position.x)
|
||||||
dst.writeDouble(position.y)
|
dst.writeDouble(position.y)
|
||||||
dst.writeDouble(position.z)
|
dst.writeDouble(position.z)
|
|
@ -12,20 +12,23 @@ object PlayProtocol : Protocol(
|
||||||
ChunkDataPacketCodec,
|
ChunkDataPacketCodec,
|
||||||
ChunkLightDataPacketCodec,
|
ChunkLightDataPacketCodec,
|
||||||
ClientSettingsPacketCodec,
|
ClientSettingsPacketCodec,
|
||||||
|
CompassTargetPacketCodec,
|
||||||
DeclareCommandsPacketCodec,
|
DeclareCommandsPacketCodec,
|
||||||
DeclareRecipesPacketCodec,
|
DeclareRecipesPacketCodec,
|
||||||
DisconnectPacketCodec,
|
DisconnectPacketCodec,
|
||||||
IncomingKeepAlivePacketCodec,
|
IncomingKeepAlivePacketCodec,
|
||||||
|
IncomingPlayerPositionPacketCodec,
|
||||||
IncomingPluginMessagePacketCodec,
|
IncomingPluginMessagePacketCodec,
|
||||||
JoinGamePacketCodec,
|
JoinGamePacketCodec,
|
||||||
OutgoingKeepAlivePacketCodec,
|
OutgoingKeepAlivePacketCodec,
|
||||||
|
OutgoingPlayerPositionPacketCodec,
|
||||||
OutgoingPluginMessagePacketCodec,
|
OutgoingPluginMessagePacketCodec,
|
||||||
PlayerAbilitiesPacketCodec,
|
PlayerAbilitiesPacketCodec,
|
||||||
PlayerInfoPacketCodec,
|
PlayerInfoPacketCodec,
|
||||||
PlayerPositionAndLookPacketCodec,
|
PlayerLocationPacketCodec,
|
||||||
|
PlayerOrientationPacketCodec,
|
||||||
|
SelectedHotbarSlotPacketCodec,
|
||||||
ServerDifficultyPacketCodec,
|
ServerDifficultyPacketCodec,
|
||||||
SetCompassTargetPacketCodec,
|
|
||||||
SetSelectedHotbarSlotPacketCodec,
|
|
||||||
SpawnExperienceOrbPacketCodec,
|
SpawnExperienceOrbPacketCodec,
|
||||||
SpawnLivingEntityPacketCodec,
|
SpawnLivingEntityPacketCodec,
|
||||||
SpawnObjectEntityPacketCodec,
|
SpawnObjectEntityPacketCodec,
|
||||||
|
|
|
@ -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<PlayerLocationPacket>(0x12, PlayerLocationPacket::class) {
|
||||||
|
override fun decode(msg: ByteBuf): PlayerLocationPacket = PlayerLocationPacket(
|
||||||
|
Location(msg.readDouble(), msg.readDouble(), msg.readDouble()),
|
||||||
|
msg.readBoolean()
|
||||||
|
)
|
||||||
|
}
|
|
@ -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<PlayerOrientationPacket>(0x14, PlayerOrientationPacket::class) {
|
||||||
|
override fun decode(msg: ByteBuf): PlayerOrientationPacket = PlayerOrientationPacket(
|
||||||
|
360 - msg.readFloat() % 360,
|
||||||
|
msg.readFloat(),
|
||||||
|
msg.readBoolean()
|
||||||
|
)
|
||||||
|
}
|
|
@ -8,9 +8,9 @@ package space.uranos.net.packet.play
|
||||||
import io.netty.buffer.ByteBuf
|
import io.netty.buffer.ByteBuf
|
||||||
import space.uranos.net.packet.OutgoingPacketCodec
|
import space.uranos.net.packet.OutgoingPacketCodec
|
||||||
|
|
||||||
object SetSelectedHotbarSlotPacketCodec :
|
object SelectedHotbarSlotPacketCodec :
|
||||||
OutgoingPacketCodec<SetSelectedHotbarSlotPacket>(0x3F, SetSelectedHotbarSlotPacket::class) {
|
OutgoingPacketCodec<SelectedHotbarSlotPacket>(0x3F, SelectedHotbarSlotPacket::class) {
|
||||||
override fun SetSelectedHotbarSlotPacket.encode(dst: ByteBuf) {
|
override fun SelectedHotbarSlotPacket.encode(dst: ByteBuf) {
|
||||||
dst.writeByte(index)
|
dst.writeByte(index)
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -8,4 +8,5 @@ package space.uranos.net.packet.play
|
||||||
import space.uranos.net.packet.OutgoingPacket
|
import space.uranos.net.packet.OutgoingPacket
|
||||||
import space.uranos.world.VoxelLocation
|
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()
|
|
@ -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()
|
|
@ -12,7 +12,7 @@ import kotlin.random.Random
|
||||||
/**
|
/**
|
||||||
* Teleports the receiving player to the specified position.
|
* Teleports the receiving player to the specified position.
|
||||||
*/
|
*/
|
||||||
data class PlayerPositionAndLookPacket(
|
data class OutgoingPlayerPositionPacket(
|
||||||
val position: Position,
|
val position: Position,
|
||||||
val relativeX: Boolean = false,
|
val relativeX: Boolean = false,
|
||||||
val relativeY: Boolean = false,
|
val relativeY: Boolean = false,
|
|
@ -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()
|
|
@ -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()
|
|
@ -10,7 +10,7 @@ import space.uranos.net.packet.OutgoingPacket
|
||||||
/**
|
/**
|
||||||
* Sent by the server to select a specific hotbar slot.
|
* 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 {
|
init {
|
||||||
if (index !in 0..8) throw IllegalArgumentException("index must be between 0 and 8")
|
if (index !in 0..8) throw IllegalArgumentException("index must be between 0 and 8")
|
||||||
}
|
}
|
|
@ -5,15 +5,13 @@
|
||||||
|
|
||||||
package space.uranos
|
package space.uranos
|
||||||
|
|
||||||
import kotlinx.coroutines.CancellableContinuation
|
import kotlinx.coroutines.*
|
||||||
import kotlinx.coroutines.runBlocking
|
|
||||||
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.*
|
||||||
import java.util.concurrent.ConcurrentHashMap
|
import java.util.concurrent.ConcurrentHashMap
|
||||||
import java.util.concurrent.Executors
|
|
||||||
import java.util.concurrent.ScheduledExecutorService
|
import java.util.concurrent.ScheduledExecutorService
|
||||||
|
import java.util.concurrent.ScheduledFuture
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
import kotlin.collections.LinkedHashSet
|
import kotlin.collections.LinkedHashSet
|
||||||
import kotlin.coroutines.resume
|
import kotlin.coroutines.resume
|
||||||
|
@ -21,8 +19,7 @@ import kotlin.coroutines.resume
|
||||||
/**
|
/**
|
||||||
* Basically ExecutorService but for coroutines and with ticks.
|
* Basically ExecutorService but for coroutines and with ticks.
|
||||||
*/
|
*/
|
||||||
class UranosScheduler : Scheduler {
|
class UranosScheduler(private val executor: ScheduledExecutorService) : Scheduler {
|
||||||
private lateinit var executor: ScheduledExecutorService
|
|
||||||
private val tasks = ConcurrentHashMap.newKeySet<Task<out Any>>()
|
private val tasks = ConcurrentHashMap.newKeySet<Task<out Any>>()
|
||||||
private val shutdownTasks = Collections.synchronizedSet(LinkedHashSet<suspend () -> Unit>())
|
private val shutdownTasks = Collections.synchronizedSet(LinkedHashSet<suspend () -> Unit>())
|
||||||
|
|
||||||
|
@ -42,12 +39,13 @@ class UranosScheduler : Scheduler {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun startTicking() {
|
private var future: ScheduledFuture<*>? = null
|
||||||
if (this::executor.isInitialized) throw IllegalStateException("Ticking was already started")
|
|
||||||
|
fun start() {
|
||||||
|
if (future != null) throw IllegalStateException("Already started")
|
||||||
val interval = 1000L / Server.TICKS_PER_SECOND
|
val interval = 1000L / Server.TICKS_PER_SECOND
|
||||||
|
|
||||||
executor = Executors.newSingleThreadScheduledExecutor { r -> Thread(r, "Scheduler") }
|
future = executor.scheduleAtFixedRate({
|
||||||
executor.scheduleAtFixedRate({
|
|
||||||
runBlocking {
|
runBlocking {
|
||||||
val startTime = System.currentTimeMillis()
|
val startTime = System.currentTimeMillis()
|
||||||
|
|
||||||
|
@ -76,15 +74,14 @@ class UranosScheduler : Scheduler {
|
||||||
}, 0, interval, TimeUnit.MILLISECONDS)
|
}, 0, interval, TimeUnit.MILLISECONDS)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun stopTicking() {
|
private fun stop() {
|
||||||
if (!this::executor.isInitialized) throw IllegalStateException("Ticking was not started")
|
if (future == null) throw IllegalArgumentException("Not started")
|
||||||
|
future!!.cancel(false)
|
||||||
executor.shutdown()
|
future = null
|
||||||
executor.awaitTermination(3, TimeUnit.SECONDS)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun shutdown() {
|
suspend fun shutdown() {
|
||||||
stopTicking()
|
stop()
|
||||||
shutdownTasks.forEach { it.invoke() }
|
shutdownTasks.forEach { it.invoke() }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -110,12 +107,14 @@ class UranosScheduler : Scheduler {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun <R : Any> runAfter(delay: Int, block: suspend () -> R): R {
|
// TODO: Use the current coroutine context for the task execution
|
||||||
|
override suspend fun <R : Any> runAfter(delay: Int, inServerThread: Boolean, block: suspend () -> R): R {
|
||||||
lateinit var continuation: CancellableContinuation<R>
|
lateinit var continuation: CancellableContinuation<R>
|
||||||
|
val context = currentCoroutineContext()
|
||||||
|
|
||||||
val fn = suspend {
|
val fn =
|
||||||
continuation.resume(block())
|
if (inServerThread) suspend { continuation.resume(block()) }
|
||||||
}
|
else suspend { withContext(context) { continuation.resume(block()) } }
|
||||||
|
|
||||||
val task = Task(fn, null, delay)
|
val task = Task(fn, null, delay)
|
||||||
|
|
||||||
|
|
|
@ -19,6 +19,7 @@ import space.uranos.event.UranosEventHandlerPositionManager
|
||||||
import space.uranos.logging.Logger
|
import space.uranos.logging.Logger
|
||||||
import space.uranos.logging.UranosLoggingOutputProvider
|
import space.uranos.logging.UranosLoggingOutputProvider
|
||||||
import space.uranos.net.UranosSocketServer
|
import space.uranos.net.UranosSocketServer
|
||||||
|
import space.uranos.player.UranosPlayer
|
||||||
import space.uranos.plugin.UranosPluginManager
|
import space.uranos.plugin.UranosPluginManager
|
||||||
import space.uranos.recipe.Recipe
|
import space.uranos.recipe.Recipe
|
||||||
import space.uranos.server.Server
|
import space.uranos.server.Server
|
||||||
|
@ -28,6 +29,7 @@ import space.uranos.world.Dimension
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.security.KeyPair
|
import java.security.KeyPair
|
||||||
import java.util.concurrent.Executors
|
import java.util.concurrent.Executors
|
||||||
|
import java.util.concurrent.ScheduledExecutorService
|
||||||
import kotlin.coroutines.CoroutineContext
|
import kotlin.coroutines.CoroutineContext
|
||||||
import kotlin.system.exitProcess
|
import kotlin.system.exitProcess
|
||||||
|
|
||||||
|
@ -46,12 +48,15 @@ class UranosServer internal constructor() : Server() {
|
||||||
|
|
||||||
val x509EncodedPublicKey: ByteArray = EncryptionUtils.generateX509Key(keyPair.public).encoded
|
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 =
|
override val coroutineContext: CoroutineContext =
|
||||||
CoroutineName("Server") + SupervisorJob() +
|
CoroutineName("Server") + SupervisorJob() + scheduledExecutorService.asCoroutineDispatcher()
|
||||||
Executors.newSingleThreadExecutor { r -> Thread(r, "server") }.asCoroutineDispatcher()
|
|
||||||
|
|
||||||
override val sessions by socketServer::sessions
|
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 pluginManager = UranosPluginManager(this)
|
||||||
override val serverDirectory: File =
|
override val serverDirectory: File =
|
||||||
|
@ -67,7 +72,7 @@ class UranosServer internal constructor() : Server() {
|
||||||
override val biomeRegistry = BiomeRegistry()
|
override val biomeRegistry = BiomeRegistry()
|
||||||
|
|
||||||
override val loggingOutputProvider = UranosLoggingOutputProvider
|
override val loggingOutputProvider = UranosLoggingOutputProvider
|
||||||
override val scheduler = UranosScheduler()
|
override val scheduler = UranosScheduler(scheduledExecutorService)
|
||||||
|
|
||||||
val config = ConfigLoader.Builder()
|
val config = ConfigLoader.Builder()
|
||||||
.addPropertySource(
|
.addPropertySource(
|
||||||
|
@ -110,7 +115,7 @@ class UranosServer internal constructor() : Server() {
|
||||||
socketServer.bind()
|
socketServer.bind()
|
||||||
logger info "Listening on ${config.host}:${config.port}"
|
logger info "Listening on ${config.host}:${config.port}"
|
||||||
|
|
||||||
scheduler.startTicking()
|
scheduler.start()
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
|
@ -135,6 +135,7 @@ class LoginAndJoinProcedure(val session: UranosSession) {
|
||||||
event.gameMode,
|
event.gameMode,
|
||||||
initialWorldAndLocation.first,
|
initialWorldAndLocation.first,
|
||||||
initialWorldAndLocation.second,
|
initialWorldAndLocation.second,
|
||||||
|
event.headPitch,
|
||||||
event.invulnerable,
|
event.invulnerable,
|
||||||
event.reducedDebugInfo,
|
event.reducedDebugInfo,
|
||||||
event.selectedHotbarSlot
|
event.selectedHotbarSlot
|
||||||
|
@ -160,8 +161,8 @@ class LoginAndJoinProcedure(val session: UranosSession) {
|
||||||
state.uuid,
|
state.uuid,
|
||||||
state.gameMode,
|
state.gameMode,
|
||||||
settings,
|
settings,
|
||||||
state.world,
|
|
||||||
state.position,
|
state.position,
|
||||||
|
state.headPitch,
|
||||||
state.reducedDebugInfo,
|
state.reducedDebugInfo,
|
||||||
state.fieldOfView,
|
state.fieldOfView,
|
||||||
state.canFly,
|
state.canFly,
|
||||||
|
@ -174,7 +175,7 @@ class LoginAndJoinProcedure(val session: UranosSession) {
|
||||||
|
|
||||||
session.state = Session.State.Joining(player)
|
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(DeclareRecipesPacket(session.server.recipeRegistry.items.values))
|
||||||
session.send(tagsPacket)
|
session.send(tagsPacket)
|
||||||
|
@ -182,7 +183,7 @@ class LoginAndJoinProcedure(val session: UranosSession) {
|
||||||
// session.send(DeclareCommandsPacket(session.server.commandRegistry.items.values))
|
// session.send(DeclareCommandsPacket(session.server.commandRegistry.items.values))
|
||||||
// UnlockRecipes
|
// UnlockRecipes
|
||||||
|
|
||||||
session.send(PlayerPositionAndLookPacket(state.position))
|
session.send(OutgoingPlayerPositionPacket(state.position))
|
||||||
|
|
||||||
session.send(PlayerInfoPacket(PlayerInfoPacket.Action.AddPlayer((session.server.players + player).map {
|
session.send(PlayerInfoPacket(PlayerInfoPacket.Action.AddPlayer((session.server.players + player).map {
|
||||||
it.uuid to PlayerInfoPacket.Action.AddPlayer.Data(
|
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)
|
session.scheduleKeepAlivePacket(true)
|
||||||
player.sendChunksAndLight()
|
player.sendChunksAndLight()
|
||||||
|
|
||||||
// WorldBorder
|
// WorldBorder
|
||||||
session.send(SetCompassTargetPacket(player.compassTarget))
|
session.send(CompassTargetPacket(player.compassTarget))
|
||||||
session.send(PlayerPositionAndLookPacket(state.position))
|
session.send(OutgoingPlayerPositionPacket(state.position))
|
||||||
|
|
||||||
// TODO: Wait for ClientStatus(action=0) packet
|
// TODO: Wait for ClientStatus(action=0) packet
|
||||||
}
|
}
|
||||||
|
|
|
@ -140,7 +140,7 @@ class UranosSession(private val channel: io.netty.channel.Channel, val server: U
|
||||||
val codec = currentProtocol!!.incomingPacketCodecsByID[packetID]
|
val codec = currentProtocol!!.incomingPacketCodecsByID[packetID]
|
||||||
|
|
||||||
if (codec == null) {
|
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)
|
if (server.config.developmentMode)
|
||||||
logger warn "$message. This will cause the client to disconnect in production mode."
|
logger warn "$message. This will cause the client to disconnect in production mode."
|
||||||
|
|
|
@ -7,13 +7,13 @@ package space.uranos.player
|
||||||
|
|
||||||
import space.uranos.Position
|
import space.uranos.Position
|
||||||
import space.uranos.chat.TextComponent
|
import space.uranos.chat.TextComponent
|
||||||
|
import space.uranos.entity.PlayerEntity
|
||||||
import space.uranos.net.Session
|
import space.uranos.net.Session
|
||||||
import space.uranos.net.packet.play.ChunkDataPacket
|
import space.uranos.net.packet.play.ChunkDataPacket
|
||||||
import space.uranos.net.packet.play.ChunkLightDataPacket
|
import space.uranos.net.packet.play.ChunkLightDataPacket
|
||||||
import space.uranos.util.clampArgument
|
import space.uranos.util.clampArgument
|
||||||
import space.uranos.world.Chunk
|
import space.uranos.world.Chunk
|
||||||
import space.uranos.world.VoxelLocation
|
import space.uranos.world.VoxelLocation
|
||||||
import space.uranos.world.World
|
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import kotlin.math.abs
|
import kotlin.math.abs
|
||||||
|
|
||||||
|
@ -23,14 +23,14 @@ class UranosPlayer(
|
||||||
override val uuid: UUID,
|
override val uuid: UUID,
|
||||||
override var gameMode: GameMode,
|
override var gameMode: GameMode,
|
||||||
override var settings: Player.Settings,
|
override var settings: Player.Settings,
|
||||||
override val world: World,
|
position: Position,
|
||||||
override var position: Position,
|
headPitch: Float,
|
||||||
override var reducedDebugInfo: Boolean,
|
override var reducedDebugInfo: Boolean,
|
||||||
override var fieldOfView: Float,
|
override var fieldOfView: Float,
|
||||||
override var canFly: Boolean,
|
override var canFly: Boolean,
|
||||||
override var flying: Boolean,
|
override var flying: Boolean,
|
||||||
override var flyingSpeed: Float,
|
override var flyingSpeed: Float,
|
||||||
override var invulnerable: Boolean,
|
override var vulnerable: Boolean,
|
||||||
override var compassTarget: VoxelLocation,
|
override var compassTarget: VoxelLocation,
|
||||||
selectedHotbarSlot: Int
|
selectedHotbarSlot: Int
|
||||||
) : Player {
|
) : Player {
|
||||||
|
@ -51,18 +51,20 @@ class UranosPlayer(
|
||||||
updateCurrentlyViewedChunks()
|
updateCurrentlyViewedChunks()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override val entity: PlayerEntity = PlayerEntity(position, this, headPitch)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets [currentlyViewedChunks] to all chunks in the view distance.
|
* Sets [currentlyViewedChunks] to all chunks in the view distance.
|
||||||
*/
|
*/
|
||||||
private fun updateCurrentlyViewedChunks() {
|
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
|
val edgeLength = settings.viewDistance + 1
|
||||||
|
|
||||||
currentlyViewedChunks = buildList(edgeLength * edgeLength) {
|
currentlyViewedChunks = buildList(edgeLength * edgeLength) {
|
||||||
for (x in (centerX - settings.viewDistance)..(centerX + settings.viewDistance)) {
|
for (x in (centerX - settings.viewDistance)..(centerX + settings.viewDistance)) {
|
||||||
for (z in (centerZ - settings.viewDistance)..(centerZ + 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)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Reference in a new issue