Archived
1
0
Fork 0

Sync entity positions

This commit is contained in:
Moritz Ruth 2021-01-11 21:55:03 +01:00
parent 792536ae7a
commit 3767d66065
No known key found for this signature in database
GPG key ID: AFD57E23E753841B
19 changed files with 245 additions and 16 deletions

View file

@ -17,6 +17,7 @@ import space.uranos.player.GameMode
import space.uranos.plugin.Plugin import space.uranos.plugin.Plugin
import space.uranos.testplugin.anvil.AnvilWorld import space.uranos.testplugin.anvil.AnvilWorld
import space.uranos.util.RGBColor import space.uranos.util.RGBColor
import space.uranos.util.runInServerThread
import space.uranos.util.secondsToTicks import space.uranos.util.secondsToTicks
import space.uranos.world.* import space.uranos.world.*
import space.uranos.world.block.GreenWoolBlock import space.uranos.world.block.GreenWoolBlock
@ -61,7 +62,7 @@ class TestPlugin: Plugin("Test", "1.0.0") {
} }
val entity = Uranos.create<CowEntity>() 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) entity.setWorld(world)
Uranos.eventBus.on<SessionAfterLoginEvent> { event -> 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
}
} }
} }

View file

@ -5,8 +5,4 @@
package space.uranos.entity package space.uranos.entity
interface LivingEntity : Entity, Mobile { 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
}

View file

@ -5,15 +5,17 @@
package space.uranos.util package space.uranos.util
import java.util.* import java.util.Spliterator
import java.util.function.Predicate import java.util.function.Predicate
import java.util.stream.Stream import java.util.stream.Stream
abstract class WatchableSet<T>(private val backingSet: MutableSet<T>) : MutableSet<T> by backingSet { abstract class WatchableSet<T>(private val backingSet: MutableSet<T>) : MutableSet<T> by backingSet {
abstract fun onAdd(element: T) abstract fun onAdd(element: T)
abstract fun onRemove(element: T) abstract fun onRemove(element: T)
abstract fun beforeAdd(element: T)
override fun add(element: T): Boolean { override fun add(element: T): Boolean {
beforeAdd(element)
val added = backingSet.add(element) val added = backingSet.add(element)
if (added) onAdd(element) if (added) onAdd(element)
return added return added

View file

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

View file

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

View file

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

View file

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

View file

@ -18,6 +18,10 @@ object PlayProtocol : Protocol(
DeclareRecipesPacketCodec, DeclareRecipesPacketCodec,
DestroyEntitiesPacketCodec, DestroyEntitiesPacketCodec,
DisconnectPacketCodec, DisconnectPacketCodec,
EntityOrientationPacketCodec,
EntityRelativeMovePacketCodec,
EntityRelativeMoveWithOrientationPacketCodec,
EntityTeleportPacketCodec,
IncomingKeepAlivePacketCodec, IncomingKeepAlivePacketCodec,
IncomingPlayerPositionPacketCodec, IncomingPlayerPositionPacketCodec,
IncomingPluginMessagePacketCodec, IncomingPluginMessagePacketCodec,

View file

@ -21,7 +21,7 @@ object SpawnLivingEntityPacketCodec : OutgoingPacketCodec<SpawnLivingEntityPacke
dst.writeDouble(position.z) dst.writeDouble(position.z)
dst.writeByte(position.yawIn256Steps.toInt()) dst.writeByte(position.yawIn256Steps.toInt())
dst.writeByte(position.pitchIn256Steps.toInt()) dst.writeByte(position.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.x * 8000).toInt().toShort().toInt())
dst.writeShort((velocity.y * 8000).toInt().toShort().toInt()) dst.writeShort((velocity.y * 8000).toInt().toShort().toInt())
dst.writeShort((velocity.z * 8000).toInt().toShort().toInt()) dst.writeShort((velocity.z * 8000).toInt().toShort().toInt())

View file

@ -8,5 +8,4 @@ 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
// TODO: Remove "Set" prefix for all packet names
data class CompassTargetPacket(val target: VoxelLocation) : OutgoingPacket() data class CompassTargetPacket(val target: VoxelLocation) : OutgoingPacket()

View file

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

View file

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

View file

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

View file

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

View file

@ -19,7 +19,6 @@ data class SpawnLivingEntityPacket(
val uuid: UUID, val uuid: UUID,
val type: EntityType<*>, val type: EntityType<*>,
val position: Position, val position: Position,
val headPitch: Float,
/** /**
* Velocity in blocks per tick * Velocity in blocks per tick
*/ */

View file

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

View file

@ -17,7 +17,6 @@ fun Entity.createSpawnPacket(): OutgoingPacket = when (this) {
uuid, uuid,
type, type,
position, position,
headPitch,
velocity velocity
) )
is ObjectEntity -> SpawnObjectEntityPacket( is ObjectEntity -> SpawnObjectEntityPacket(

View file

@ -13,6 +13,7 @@ import space.uranos.entity.event.ViewingChangedEvent
import space.uranos.player.Player import space.uranos.player.Player
import space.uranos.util.TickSynchronizationContainer import space.uranos.util.TickSynchronizationContainer
import space.uranos.util.WatchableSet import space.uranos.util.WatchableSet
import space.uranos.util.createEntityMovementPacket
import space.uranos.util.memoized import space.uranos.util.memoized
import space.uranos.world.Chunk import space.uranos.world.Chunk
import space.uranos.world.VoxelLocation import space.uranos.world.VoxelLocation
@ -31,6 +32,11 @@ sealed class UranosEntity(server: UranosServer) : Entity {
override val uuid: UUID = UUID.randomUUID() override val uuid: UUID = UUID.randomUUID()
override val viewers: MutableSet<Player> = object : WatchableSet<Player>(Collections.newSetFromMap(WeakHashMap())) { 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) { override fun onAdd(element: Player) {
Uranos.scope.launch { Uranos.eventBus.emit(ViewingChangedEvent(this@UranosEntity, element, true)) } 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 { abstract class UranosLivingEntity(server: UranosServer) : UranosEntity(server), LivingEntity {
override var velocity: Vector = Vector.ZERO 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 -> 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) } 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 { abstract class UranosObjectEntity(server: UranosServer) : UranosEntity(server), ObjectEntity {
override var velocity: Vector = Vector.ZERO override var velocity: Vector = Vector.ZERO
private var lastSentPosition: Position = Position.ZERO
override var position: Position by container.ifChanged(Position.ZERO) { value -> 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) } override val chunkKey: Chunk.Key by memoized({ position }) { Chunk.Key.from(position) }

View file

@ -62,11 +62,10 @@ class UranosPlayer(
override val entity: PlayerEntity = session.server.createPlayerEntity(this).also { override val entity: PlayerEntity = session.server.createPlayerEntity(this).also {
it.position = position it.position = position
it.headPitch = headPitch
} }
suspend fun spawnInitially(world: World) { 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) entity.setWorld(world)
updateCurrentlyViewedChunks() updateCurrentlyViewedChunks()
sendChunksAndLight() sendChunksAndLight()