Archived
1
0
Fork 0

Add player movement related packets and work on entities

This commit is contained in:
Moritz Ruth 2021-01-06 18:09:01 +01:00
parent cb50c7bb39
commit 4e34dbc1e2
No known key found for this signature in database
GPG key ID: AFD57E23E753841B
28 changed files with 249 additions and 78 deletions

View file

@ -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)

View file

@ -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()

View file

@ -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 {

View file

@ -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
} }

View file

@ -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
} }

View file

@ -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()
}

View file

@ -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

View file

@ -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

View file

@ -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.

View file

@ -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.
* *

View file

@ -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)
} }
} }

View file

@ -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()
)
}

View file

@ -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)

View file

@ -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,

View file

@ -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()
)
}

View file

@ -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()
)
}

View file

@ -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)
} }
} }

View file

@ -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()

View file

@ -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()

View file

@ -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,

View file

@ -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()

View file

@ -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()

View file

@ -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")
} }

View file

@ -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)

View file

@ -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 {

View file

@ -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
} }

View file

@ -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."

View file

@ -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)))
} }
} }
} }