Sync entity positions
This commit is contained in:
parent
792536ae7a
commit
3767d66065
19 changed files with 245 additions and 16 deletions
|
@ -17,6 +17,7 @@ import space.uranos.player.GameMode
|
|||
import space.uranos.plugin.Plugin
|
||||
import space.uranos.testplugin.anvil.AnvilWorld
|
||||
import space.uranos.util.RGBColor
|
||||
import space.uranos.util.runInServerThread
|
||||
import space.uranos.util.secondsToTicks
|
||||
import space.uranos.world.*
|
||||
import space.uranos.world.block.GreenWoolBlock
|
||||
|
@ -61,7 +62,7 @@ class TestPlugin: Plugin("Test", "1.0.0") {
|
|||
}
|
||||
|
||||
val entity = Uranos.create<CowEntity>()
|
||||
entity.position = Position(0.0, 10.0, 0.0, 0f, 0f)
|
||||
entity.position = Position(0.5, 10.0, 0.5, 0f, 0f)
|
||||
entity.setWorld(world)
|
||||
|
||||
Uranos.eventBus.on<SessionAfterLoginEvent> { event ->
|
||||
|
@ -80,5 +81,12 @@ class TestPlugin: Plugin("Test", "1.0.0") {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
var x = 1.0
|
||||
Uranos.scheduler.executeRepeating(1) {
|
||||
x += 0.2
|
||||
runInServerThread { entity.position = Position(x + 0.5, x, 0.5, 0f, 0f) }
|
||||
if (x >= 10.0) x = 0.0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,8 +5,4 @@
|
|||
|
||||
package space.uranos.entity
|
||||
|
||||
interface LivingEntity : Entity, Mobile {
|
||||
// TODO: This should probably be headYaw, but wiki.vg says headPitch.
|
||||
// And it is only used in the SpawnLivingEntity packet
|
||||
var headPitch: Float
|
||||
}
|
||||
interface LivingEntity : Entity, Mobile
|
||||
|
|
|
@ -5,15 +5,17 @@
|
|||
|
||||
package space.uranos.util
|
||||
|
||||
import java.util.*
|
||||
import java.util.Spliterator
|
||||
import java.util.function.Predicate
|
||||
import java.util.stream.Stream
|
||||
|
||||
abstract class WatchableSet<T>(private val backingSet: MutableSet<T>) : MutableSet<T> by backingSet {
|
||||
abstract fun onAdd(element: T)
|
||||
abstract fun onRemove(element: T)
|
||||
abstract fun beforeAdd(element: T)
|
||||
|
||||
override fun add(element: T): Boolean {
|
||||
beforeAdd(element)
|
||||
val added = backingSet.add(element)
|
||||
if (added) onAdd(element)
|
||||
return added
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
/*
|
||||
* Copyright 2020-2021 Moritz Ruth and Uranos contributors
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file
|
||||
*/
|
||||
|
||||
package space.uranos.net.packet.play
|
||||
|
||||
import io.netty.buffer.ByteBuf
|
||||
import space.uranos.net.MinecraftProtocolDataTypes.writeVarInt
|
||||
import space.uranos.net.packet.OutgoingPacketCodec
|
||||
|
||||
object EntityOrientationPacketCodec :
|
||||
OutgoingPacketCodec<EntityOrientationPacket>(0x29, EntityOrientationPacket::class) {
|
||||
override fun EntityOrientationPacket.encode(dst: ByteBuf) {
|
||||
dst.writeVarInt(entityID)
|
||||
dst.writeByte(yaw.toInt())
|
||||
dst.writeByte(pitch.toInt())
|
||||
dst.writeBoolean(onGround)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
/*
|
||||
* Copyright 2020-2021 Moritz Ruth and Uranos contributors
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file
|
||||
*/
|
||||
|
||||
package space.uranos.net.packet.play
|
||||
|
||||
import io.netty.buffer.ByteBuf
|
||||
import space.uranos.net.MinecraftProtocolDataTypes.writeVarInt
|
||||
import space.uranos.net.packet.OutgoingPacketCodec
|
||||
|
||||
object EntityRelativeMovePacketCodec :
|
||||
OutgoingPacketCodec<EntityRelativeMovePacket>(0x27, EntityRelativeMovePacket::class) {
|
||||
override fun EntityRelativeMovePacket.encode(dst: ByteBuf) {
|
||||
dst.writeVarInt(entityID)
|
||||
dst.writeShort(deltaX.toInt())
|
||||
dst.writeShort(deltaY.toInt())
|
||||
dst.writeShort(deltaZ.toInt())
|
||||
dst.writeBoolean(onGround)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
/*
|
||||
* Copyright 2020-2021 Moritz Ruth and Uranos contributors
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file
|
||||
*/
|
||||
|
||||
package space.uranos.net.packet.play
|
||||
|
||||
import io.netty.buffer.ByteBuf
|
||||
import space.uranos.net.MinecraftProtocolDataTypes.writeVarInt
|
||||
import space.uranos.net.packet.OutgoingPacketCodec
|
||||
|
||||
object EntityRelativeMoveWithOrientationPacketCodec :
|
||||
OutgoingPacketCodec<EntityRelativeMoveWithOrientationPacket>(0x28, EntityRelativeMoveWithOrientationPacket::class) {
|
||||
override fun EntityRelativeMoveWithOrientationPacket.encode(dst: ByteBuf) {
|
||||
dst.writeVarInt(entityID)
|
||||
dst.writeShort(deltaX.toInt())
|
||||
dst.writeShort(deltaY.toInt())
|
||||
dst.writeShort(deltaZ.toInt())
|
||||
dst.writeByte(yaw.toInt())
|
||||
dst.writeByte(pitch.toInt())
|
||||
dst.writeBoolean(onGround)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
/*
|
||||
* Copyright 2020-2021 Moritz Ruth and Uranos contributors
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file
|
||||
*/
|
||||
|
||||
package space.uranos.net.packet.play
|
||||
|
||||
import io.netty.buffer.ByteBuf
|
||||
import space.uranos.net.MinecraftProtocolDataTypes.writeVarInt
|
||||
import space.uranos.net.packet.OutgoingPacketCodec
|
||||
|
||||
object EntityTeleportPacketCodec :
|
||||
OutgoingPacketCodec<EntityTeleportPacket>(0x56, EntityTeleportPacket::class) {
|
||||
override fun EntityTeleportPacket.encode(dst: ByteBuf) {
|
||||
dst.writeVarInt(entityID)
|
||||
dst.writeDouble(position.x)
|
||||
dst.writeDouble(position.y)
|
||||
dst.writeDouble(position.z)
|
||||
dst.writeByte(position.yawIn256Steps.toInt())
|
||||
dst.writeByte(position.pitchIn256Steps.toInt())
|
||||
dst.writeBoolean(onGround)
|
||||
}
|
||||
}
|
|
@ -18,6 +18,10 @@ object PlayProtocol : Protocol(
|
|||
DeclareRecipesPacketCodec,
|
||||
DestroyEntitiesPacketCodec,
|
||||
DisconnectPacketCodec,
|
||||
EntityOrientationPacketCodec,
|
||||
EntityRelativeMovePacketCodec,
|
||||
EntityRelativeMoveWithOrientationPacketCodec,
|
||||
EntityTeleportPacketCodec,
|
||||
IncomingKeepAlivePacketCodec,
|
||||
IncomingPlayerPositionPacketCodec,
|
||||
IncomingPluginMessagePacketCodec,
|
||||
|
|
|
@ -21,7 +21,7 @@ object SpawnLivingEntityPacketCodec : OutgoingPacketCodec<SpawnLivingEntityPacke
|
|||
dst.writeDouble(position.z)
|
||||
dst.writeByte(position.yawIn256Steps.toInt())
|
||||
dst.writeByte(position.pitchIn256Steps.toInt())
|
||||
dst.writeByte(((headPitch / 360) * 256).toInt())
|
||||
dst.writeByte(0) // Head pitch; I do not know what this does
|
||||
dst.writeShort((velocity.x * 8000).toInt().toShort().toInt())
|
||||
dst.writeShort((velocity.y * 8000).toInt().toShort().toInt())
|
||||
dst.writeShort((velocity.z * 8000).toInt().toShort().toInt())
|
||||
|
|
|
@ -8,5 +8,4 @@ package space.uranos.net.packet.play
|
|||
import space.uranos.net.packet.OutgoingPacket
|
||||
import space.uranos.world.VoxelLocation
|
||||
|
||||
// TODO: Remove "Set" prefix for all packet names
|
||||
data class CompassTargetPacket(val target: VoxelLocation) : OutgoingPacket()
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
/*
|
||||
* Copyright 2020-2021 Moritz Ruth and Uranos contributors
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file
|
||||
*/
|
||||
|
||||
package space.uranos.net.packet.play
|
||||
|
||||
import space.uranos.net.packet.OutgoingPacket
|
||||
|
||||
data class EntityOrientationPacket(
|
||||
val entityID: Int,
|
||||
val yaw: UByte,
|
||||
val pitch: UByte,
|
||||
val onGround: Boolean
|
||||
) : OutgoingPacket()
|
|
@ -0,0 +1,16 @@
|
|||
/*
|
||||
* Copyright 2020-2021 Moritz Ruth and Uranos contributors
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file
|
||||
*/
|
||||
|
||||
package space.uranos.net.packet.play
|
||||
|
||||
import space.uranos.net.packet.OutgoingPacket
|
||||
|
||||
data class EntityRelativeMovePacket(
|
||||
val entityID: Int,
|
||||
val deltaX: Short,
|
||||
val deltaY: Short,
|
||||
val deltaZ: Short,
|
||||
val onGround: Boolean
|
||||
) : OutgoingPacket()
|
|
@ -0,0 +1,24 @@
|
|||
/*
|
||||
* Copyright 2020-2021 Moritz Ruth and Uranos contributors
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file
|
||||
*/
|
||||
|
||||
package space.uranos.net.packet.play
|
||||
|
||||
import space.uranos.net.packet.OutgoingPacket
|
||||
|
||||
data class EntityRelativeMoveWithOrientationPacket(
|
||||
val entityID: Int,
|
||||
val deltaX: Short,
|
||||
val deltaY: Short,
|
||||
val deltaZ: Short,
|
||||
/**
|
||||
* Absolute value.
|
||||
*/
|
||||
val yaw: UByte,
|
||||
/**
|
||||
* Absolute value.
|
||||
*/
|
||||
val pitch: UByte,
|
||||
val onGround: Boolean
|
||||
) : OutgoingPacket()
|
|
@ -0,0 +1,15 @@
|
|||
/*
|
||||
* 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.OutgoingPacket
|
||||
|
||||
data class EntityTeleportPacket(
|
||||
val entityID: Int,
|
||||
val position: Position,
|
||||
val onGround: Boolean
|
||||
) : OutgoingPacket()
|
|
@ -19,7 +19,6 @@ data class SpawnLivingEntityPacket(
|
|||
val uuid: UUID,
|
||||
val type: EntityType<*>,
|
||||
val position: Position,
|
||||
val headPitch: Float,
|
||||
/**
|
||||
* Velocity in blocks per tick
|
||||
*/
|
||||
|
|
|
@ -0,0 +1,49 @@
|
|||
/*
|
||||
* 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.util
|
||||
|
||||
import space.uranos.Position
|
||||
import space.uranos.abs
|
||||
import space.uranos.net.packet.OutgoingPacket
|
||||
import space.uranos.net.packet.play.EntityOrientationPacket
|
||||
import space.uranos.net.packet.play.EntityRelativeMovePacket
|
||||
import space.uranos.net.packet.play.EntityRelativeMoveWithOrientationPacket
|
||||
import space.uranos.net.packet.play.EntityTeleportPacket
|
||||
|
||||
fun createEntityMovementPacket(entityID: Int, oldPosition: Position, newPosition: Position): OutgoingPacket? {
|
||||
val delta = abs(newPosition.toVector() - oldPosition.toVector())
|
||||
val orientationChanged = oldPosition.yaw != newPosition.yaw || oldPosition.pitch != newPosition.pitch
|
||||
val onGround = true // TODO: Find out what onGround does
|
||||
|
||||
return if (delta.x + delta.y + delta.z == 0.0) {
|
||||
if (orientationChanged) EntityOrientationPacket(
|
||||
entityID,
|
||||
newPosition.yawIn256Steps,
|
||||
newPosition.pitchIn256Steps,
|
||||
onGround
|
||||
) else null
|
||||
} else if (delta.x > 8 || delta.y > 8 || delta.z > 8) {
|
||||
EntityTeleportPacket(entityID, newPosition, onGround)
|
||||
} else {
|
||||
if (orientationChanged) EntityRelativeMoveWithOrientationPacket(
|
||||
entityID,
|
||||
getDeltaShort(delta.x),
|
||||
getDeltaShort(delta.y),
|
||||
getDeltaShort(delta.z),
|
||||
newPosition.yawIn256Steps,
|
||||
newPosition.pitchIn256Steps,
|
||||
onGround
|
||||
) else EntityRelativeMovePacket(
|
||||
entityID,
|
||||
getDeltaShort(delta.x),
|
||||
getDeltaShort(delta.y),
|
||||
getDeltaShort(delta.z),
|
||||
onGround
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getDeltaShort(delta: Double): Short = (delta * 32 * 128).toInt().toShort()
|
|
@ -17,7 +17,6 @@ fun Entity.createSpawnPacket(): OutgoingPacket = when (this) {
|
|||
uuid,
|
||||
type,
|
||||
position,
|
||||
headPitch,
|
||||
velocity
|
||||
)
|
||||
is ObjectEntity -> SpawnObjectEntityPacket(
|
|
@ -13,6 +13,7 @@ import space.uranos.entity.event.ViewingChangedEvent
|
|||
import space.uranos.player.Player
|
||||
import space.uranos.util.TickSynchronizationContainer
|
||||
import space.uranos.util.WatchableSet
|
||||
import space.uranos.util.createEntityMovementPacket
|
||||
import space.uranos.util.memoized
|
||||
import space.uranos.world.Chunk
|
||||
import space.uranos.world.VoxelLocation
|
||||
|
@ -31,6 +32,11 @@ sealed class UranosEntity(server: UranosServer) : Entity {
|
|||
override val uuid: UUID = UUID.randomUUID()
|
||||
|
||||
override val viewers: MutableSet<Player> = object : WatchableSet<Player>(Collections.newSetFromMap(WeakHashMap())) {
|
||||
override fun beforeAdd(element: Player) {
|
||||
if ((this@UranosEntity as? UranosPlayerEntity)?.let { it.player == element } == true)
|
||||
throw IllegalArgumentException("A player cannot be a viewer of it's own entity")
|
||||
}
|
||||
|
||||
override fun onAdd(element: Player) {
|
||||
Uranos.scope.launch { Uranos.eventBus.emit(ViewingChangedEvent(this@UranosEntity, element, true)) }
|
||||
}
|
||||
|
@ -70,10 +76,16 @@ sealed class UranosEntity(server: UranosServer) : Entity {
|
|||
|
||||
abstract class UranosLivingEntity(server: UranosServer) : UranosEntity(server), LivingEntity {
|
||||
override var velocity: Vector = Vector.ZERO
|
||||
override var headPitch: Float = 0f
|
||||
|
||||
private var lastSentPosition: Position = Position.ZERO
|
||||
override var position: Position by container.ifChanged(Position.ZERO) { value ->
|
||||
// TODO: Broadcast to players
|
||||
if (viewers.isNotEmpty()) createEntityMovementPacket(numericID, lastSentPosition, value)?.let {
|
||||
for (viewer in viewers) {
|
||||
viewer.session.sendNextTick(it)
|
||||
}
|
||||
}
|
||||
|
||||
lastSentPosition = value
|
||||
}
|
||||
|
||||
override val chunkKey: Chunk.Key by memoized({ position }) { Chunk.Key.from(position) }
|
||||
|
@ -82,8 +94,13 @@ abstract class UranosLivingEntity(server: UranosServer) : UranosEntity(server),
|
|||
abstract class UranosObjectEntity(server: UranosServer) : UranosEntity(server), ObjectEntity {
|
||||
override var velocity: Vector = Vector.ZERO
|
||||
|
||||
private var lastSentPosition: Position = Position.ZERO
|
||||
override var position: Position by container.ifChanged(Position.ZERO) { value ->
|
||||
// TODO: Broadcast to players
|
||||
createEntityMovementPacket(numericID, lastSentPosition, value)?.let {
|
||||
for (viewer in viewers) {
|
||||
viewer.session.sendNextTick(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override val chunkKey: Chunk.Key by memoized({ position }) { Chunk.Key.from(position) }
|
||||
|
|
|
@ -62,11 +62,10 @@ class UranosPlayer(
|
|||
|
||||
override val entity: PlayerEntity = session.server.createPlayerEntity(this).also {
|
||||
it.position = position
|
||||
it.headPitch = headPitch
|
||||
}
|
||||
|
||||
suspend fun spawnInitially(world: World) {
|
||||
session.server.entities.forEach { if (it.visibleToNewPlayers) it.viewers.add(this) }
|
||||
session.server.entities.forEach { if (it.visibleToNewPlayers && it != entity) it.viewers.add(this) }
|
||||
entity.setWorld(world)
|
||||
updateCurrentlyViewedChunks()
|
||||
sendChunksAndLight()
|
||||
|
|
Reference in a new issue