Archived
1
0
Fork 0

Add entity metadata packet and object entity spawning

This commit is contained in:
Moritz Ruth 2021-03-07 15:09:04 +01:00
parent c959478978
commit 0b1ba947a1
No known key found for this signature in database
GPG key ID: AFD57E23E753841B
26 changed files with 288 additions and 93 deletions

5
.idea/misc.xml generated
View file

@ -1,5 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<project version="4"> <project version="4">
<component name="EntryPointsManager">
<list size="1">
<item index="0" class="java.lang.String" itemvalue="com.squareup.moshi.FromJson" />
</list>
</component>
<component name="ExternalStorageConfigurationManager" enabled="true" /> <component name="ExternalStorageConfigurationManager" enabled="true" />
<component name="ProjectRootManager" version="2" languageLevel="JDK_14" default="true" project-jdk-name="14" project-jdk-type="JavaSDK" /> <component name="ProjectRootManager" version="2" languageLevel="JDK_14" default="true" project-jdk-name="14" project-jdk-type="JavaSDK" />
<component name="SuppressKotlinCodeStyleNotification"> <component name="SuppressKotlinCodeStyleNotification">

View file

@ -3,12 +3,14 @@ package space.uranos.testplugin
import space.uranos.Uranos import space.uranos.Uranos
import space.uranos.chat.ChatColor import space.uranos.chat.ChatColor
import space.uranos.chat.TextComponent import space.uranos.chat.TextComponent
import space.uranos.entity.MinecartEntity
import space.uranos.entity.Position import space.uranos.entity.Position
import space.uranos.entity.RideableMinecartEntity
import space.uranos.net.ServerListInfo import space.uranos.net.ServerListInfo
import space.uranos.net.event.ServerListInfoRequestEvent import space.uranos.net.event.ServerListInfoRequestEvent
import space.uranos.net.event.SessionAfterLoginEvent import space.uranos.net.event.SessionAfterLoginEvent
import space.uranos.net.packet.play.EntityMetadataPacket
import space.uranos.player.GameMode import space.uranos.player.GameMode
import space.uranos.player.event.PlayerReadyEvent
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
@ -104,10 +106,22 @@ class TestPlugin : Plugin("Test", "1.0.0") {
} }
} }
// Not showing up yet because no metadata is sent val entity = Uranos.create<RideableMinecartEntity>()
val entity = Uranos.create<MinecartEntity>()
entity.position = Position(0.0, 4.0, 0.0) entity.position = Position(0.0, 4.0, 0.0)
// entity.setWorld(world) entity.setWorld(world)
Uranos.eventBus.on<PlayerReadyEvent> {
it.player.session.send(EntityMetadataPacket(entity.numericID, listOf(
EntityMetadataPacket.MetadataEntry.Byte(0u, 0x00),
EntityMetadataPacket.MetadataEntry.Int(1u, 0x00),
EntityMetadataPacket.MetadataEntry.OptChat(2u, null),
EntityMetadataPacket.MetadataEntry.Boolean(3u, false),
EntityMetadataPacket.MetadataEntry.Boolean(4u, false),
EntityMetadataPacket.MetadataEntry.Boolean(5u, false),
EntityMetadataPacket.MetadataEntry.Int(6u, 0),
EntityMetadataPacket.MetadataEntry.Boolean(12u, true)
)))
}
var x = 0f var x = 0f
var y = -90f var y = -90f

View file

@ -25,7 +25,7 @@ interface Entity {
val numericID: Int val numericID: Int
/** /**
* Players that can see this entity. * Players that can see this entity if it is in their view distance.
*/ */
val viewers: MutableSet<Player> val viewers: MutableSet<Player>
@ -38,6 +38,17 @@ interface Entity {
* If players should be added to [viewers] when they enter [world]. * If players should be added to [viewers] when they enter [world].
*/ */
var visibleToNewPlayers: Boolean var visibleToNewPlayers: Boolean
var glowing: Boolean // even experience orbs can glow
var invisible: Boolean
/**
* Whether this entity does not produce any sounds.
*/
var silent: Boolean
var ignoreGravity: Boolean
} }
/** /**

View file

@ -0,0 +1,27 @@
package space.uranos.entity
import space.uranos.world.block.Block
interface MinecartEntity: ObjectEntity, YawRotatable, PitchRotatable {
/**
* Default is `0`.
*/
val shakingPower: Int
/**
* Default is `1`.
*/
val shakingDirection: Int
/**
* Default is `0.0`.
*/
val shakingMultiplier: Float
val customBlock: Block?
/**
* The Y offset of the block inside the minecart, measured in 16ths of a block.
*/
val blockOffset: Int
}

View file

@ -0,0 +1,5 @@
package space.uranos.entity
interface RideableMinecartEntity : MinecartEntity {
companion object Type : MinecartEntityType()
}

View file

@ -0,0 +1,34 @@
package space.uranos.net.packet.play
import io.netty.buffer.ByteBuf
import space.uranos.net.MinecraftProtocolDataTypes.writeString
import space.uranos.net.MinecraftProtocolDataTypes.writeVarInt
import space.uranos.net.packet.OutgoingPacketCodec
object EntityMetadataPacketCodec : OutgoingPacketCodec<EntityMetadataPacket>(0x44, EntityMetadataPacket::class) {
override fun EntityMetadataPacket.encode(dst: ByteBuf) {
dst.writeVarInt(entityID)
for (entry in metadata) {
dst.writeByte(entry.index.toInt())
dst.writeVarInt(entry.typeID)
when(entry) {
is EntityMetadataPacket.MetadataEntry.Byte -> dst.writeByte(entry.value.toInt())
is EntityMetadataPacket.MetadataEntry.Int -> dst.writeVarInt(entry.value)
is EntityMetadataPacket.MetadataEntry.Float -> dst.writeFloat(entry.value)
is EntityMetadataPacket.MetadataEntry.String -> dst.writeString(entry.value)
is EntityMetadataPacket.MetadataEntry.OptChat -> {
if (entry.value == null) dst.writeBoolean(false)
else {
dst.writeBoolean(true)
dst.writeString(entry.value!!.toJson())
}
}
is EntityMetadataPacket.MetadataEntry.Boolean -> dst.writeBoolean(entry.value)
}
}
dst.writeByte(0xff)
}
}

View file

@ -0,0 +1,14 @@
package space.uranos.net.packet.play
import io.netty.buffer.ByteBuf
import space.uranos.net.MinecraftProtocolDataTypes.writeVarInt
import space.uranos.net.packet.OutgoingPacketCodec
object EntityVelocityPacketCodec : OutgoingPacketCodec<EntityVelocityPacket>(0x46, EntityVelocityPacket::class) {
override fun EntityVelocityPacket.encode(dst: ByteBuf) {
dst.writeVarInt(entityID)
dst.writeShort(x.toInt())
dst.writeShort(y.toInt())
dst.writeShort(z.toInt())
}
}

View file

@ -14,10 +14,12 @@ object PlayProtocol : Protocol(
DestroyEntitiesPacketCodec, DestroyEntitiesPacketCodec,
DisconnectPacketCodec, DisconnectPacketCodec,
EntityHeadYawPacketCodec, EntityHeadYawPacketCodec,
EntityMetadataPacketCodec,
EntityOrientationPacketCodec, EntityOrientationPacketCodec,
EntityRelativeMovePacketCodec, EntityRelativeMovePacketCodec,
EntityRelativeMoveWithOrientationPacketCodec, EntityRelativeMoveWithOrientationPacketCodec,
EntityTeleportPacketCodec, EntityTeleportPacketCodec,
EntityVelocityPacketCodec,
IncomingKeepAlivePacketCodec, IncomingKeepAlivePacketCodec,
IncomingPlayerPositionPacketCodec, IncomingPlayerPositionPacketCodec,
IncomingPluginMessagePacketCodec, IncomingPluginMessagePacketCodec,

View file

@ -7,7 +7,7 @@ import space.uranos.util.numbers.floorMod
object PlayerOrientationPacketCodec : object PlayerOrientationPacketCodec :
IncomingPacketCodec<PlayerOrientationPacket>(0x14, PlayerOrientationPacket::class) { IncomingPacketCodec<PlayerOrientationPacket>(0x14, PlayerOrientationPacket::class) {
override fun decode(msg: ByteBuf): PlayerOrientationPacket = PlayerOrientationPacket( override fun decode(msg: ByteBuf): PlayerOrientationPacket = PlayerOrientationPacket(
floorMod(msg.readFloat(), 360f), floorMod(msg.readFloat(), 360f), // TODO: Ensure it is never 360 (should be 0 then)
msg.readFloat(), msg.readFloat(),
msg.readBoolean() msg.readBoolean()
) )

View file

@ -20,8 +20,8 @@ object SpawnLivingEntityPacketCodec : OutgoingPacketCodec<SpawnLivingEntityPacke
// This is named "head pitch" on wiki.vg, but it is actually head yaw. // This is named "head pitch" on wiki.vg, but it is actually head yaw.
dst.writeByte(headYaw.mapToUByte(360f).toInt()) dst.writeByte(headYaw.mapToUByte(360f).toInt())
dst.writeShort((velocity.x * 8000).toInt().toShort().toInt()) dst.writeShort(velocityX.toInt())
dst.writeShort((velocity.y * 8000).toInt().toShort().toInt()) dst.writeShort(velocityY.toInt())
dst.writeShort((velocity.z * 8000).toInt().toShort().toInt()) dst.writeShort(velocityZ.toInt())
} }
} }

View file

@ -16,8 +16,8 @@ object SpawnObjectEntityPacketCodec : OutgoingPacketCodec<SpawnObjectEntityPacke
dst.writeByte(pitch.toInt()) dst.writeByte(pitch.toInt())
dst.writeByte(yaw.toInt()) dst.writeByte(yaw.toInt())
dst.writeInt(data) dst.writeInt(data)
dst.writeShort((velocity.x * 8000).toInt().toShort().toInt()) dst.writeShort(velocityX.toInt())
dst.writeShort((velocity.y * 8000).toInt().toShort().toInt()) dst.writeShort(velocityY.toInt())
dst.writeShort((velocity.z * 8000).toInt().toShort().toInt()) dst.writeShort(velocityZ.toInt())
} }
} }

View file

@ -0,0 +1,21 @@
package space.uranos.net.packet.play
import space.uranos.chat.ChatComponent
import space.uranos.net.packet.OutgoingPacket
data class EntityMetadataPacket(
val entityID: Int,
val metadata: Iterable<MetadataEntry<*>>
) : OutgoingPacket() {
sealed class MetadataEntry<T>(val typeID: kotlin.Int) {
abstract val index: UByte
abstract val value: T
data class Byte(override val index: UByte, override val value: kotlin.Byte) : MetadataEntry<kotlin.Byte>(0)
data class Int(override val index: UByte, override val value: kotlin.Int) : MetadataEntry<kotlin.Int>(1)
data class Float(override val index: UByte, override val value: kotlin.Float) : MetadataEntry<kotlin.Float>(2)
data class String(override val index: UByte, override val value: kotlin.String) : MetadataEntry<kotlin.String>(3)
data class OptChat(override val index: UByte, override val value: ChatComponent?) : MetadataEntry<ChatComponent?>(5)
data class Boolean(override val index: UByte, override val value: kotlin.Boolean) : MetadataEntry<kotlin.Boolean>(7)
}
}

View file

@ -0,0 +1,10 @@
package space.uranos.net.packet.play
import space.uranos.net.packet.OutgoingPacket
data class EntityVelocityPacket(
val entityID: Int,
val x: Short,
val y: Short,
val z: Short
) : OutgoingPacket()

View file

@ -1,6 +1,5 @@
package space.uranos.net.packet.play package space.uranos.net.packet.play
import space.uranos.Vector
import space.uranos.entity.EntityType import space.uranos.entity.EntityType
import space.uranos.entity.Position import space.uranos.entity.Position
import space.uranos.net.packet.OutgoingPacket import space.uranos.net.packet.OutgoingPacket
@ -8,6 +7,8 @@ import java.util.UUID
/** /**
* Sent to spawn **living** entities. * Sent to spawn **living** entities.
*
* Velocity is measured in 1/8000 blocks per tick.
*/ */
data class SpawnLivingEntityPacket( data class SpawnLivingEntityPacket(
val entityID: Int, val entityID: Int,
@ -17,8 +18,7 @@ data class SpawnLivingEntityPacket(
val yaw: Float, val yaw: Float,
val pitch: Float, val pitch: Float,
val headYaw: Float, val headYaw: Float,
/** val velocityX: Short,
* Velocity in blocks per tick val velocityY: Short,
*/ val velocityZ: Short
val velocity: Vector
) : OutgoingPacket() ) : OutgoingPacket()

View file

@ -1,11 +1,12 @@
package space.uranos.net.packet.play package space.uranos.net.packet.play
import space.uranos.Vector
import space.uranos.net.packet.OutgoingPacket import space.uranos.net.packet.OutgoingPacket
import java.util.UUID import java.util.UUID
/** /**
* Sent to spawn object entities. * Sent to spawn object entities.
*
* Velocity is measured in 1/8000 blocks per tick.
*/ */
data class SpawnObjectEntityPacket( data class SpawnObjectEntityPacket(
val entityID: Int, val entityID: Int,
@ -17,5 +18,7 @@ data class SpawnObjectEntityPacket(
val yaw: UByte, val yaw: UByte,
val pitch: UByte, val pitch: UByte,
val data: Int, val data: Int,
val velocity: Vector val velocityX: Short,
val velocityY: Short,
val velocityZ: Short
) : OutgoingPacket() ) : OutgoingPacket()

View file

@ -8,7 +8,10 @@ import space.uranos.net.packet.play.SpawnPaintingPacket
import space.uranos.util.numbers.mapToUByte import space.uranos.util.numbers.mapToUByte
fun Entity.createSpawnPacket(): OutgoingPacket = when (this) { fun Entity.createSpawnPacket(): OutgoingPacket = when (this) {
is LivingEntity -> if (this is HasMovableHead) SpawnLivingEntityPacket( is LivingEntity -> {
val (velX, velY, velZ) = velocity.getAsVelocityPacketValues()
if (this is HasMovableHead) SpawnLivingEntityPacket(
numericID, numericID,
uuid, uuid,
type, type,
@ -16,7 +19,7 @@ fun Entity.createSpawnPacket(): OutgoingPacket = when (this) {
headYaw, headYaw,
headPitch, headPitch,
headYaw, headYaw,
velocity velX, velY, velZ
) else SpawnLivingEntityPacket( ) else SpawnLivingEntityPacket(
numericID, numericID,
uuid, uuid,
@ -25,9 +28,13 @@ fun Entity.createSpawnPacket(): OutgoingPacket = when (this) {
if (this is YawRotatable) yaw else 0f, if (this is YawRotatable) yaw else 0f,
if (this is PitchRotatable) pitch else 0f, if (this is PitchRotatable) pitch else 0f,
0f, 0f,
velocity velX, velY, velZ
) )
is ObjectEntity -> SpawnObjectEntityPacket( }
is ObjectEntity -> {
val (velX, velY, velZ) = velocity.getAsVelocityPacketValues()
SpawnObjectEntityPacket(
numericID, numericID,
uuid, uuid,
type.numericID, type.numericID,
@ -37,8 +44,9 @@ fun Entity.createSpawnPacket(): OutgoingPacket = when (this) {
if (this is YawRotatable) yaw.mapToUByte(360f) else 0u, if (this is YawRotatable) yaw.mapToUByte(360f) else 0u,
if (this is PitchRotatable) pitch.mapToUByte(360f) else 0u, if (this is PitchRotatable) pitch.mapToUByte(360f) else 0u,
getDataValue(), getDataValue(),
velocity velX, velY, velZ
) )
}
is PaintingEntity -> SpawnPaintingPacket( is PaintingEntity -> SpawnPaintingPacket(
numericID, numericID,
uuid, uuid,
@ -51,7 +59,7 @@ fun Entity.createSpawnPacket(): OutgoingPacket = when (this) {
fun ObjectEntity.getDataValue(): Int = when (this) { fun ObjectEntity.getDataValue(): Int = when (this) {
is ItemEntity -> 1 is ItemEntity -> 1
is MinecartEntity -> 2 is RideableMinecartEntity -> 0
// TODO: Add remaining // TODO: Add remaining
else -> throw IllegalArgumentException("Unknown entity type") else -> throw IllegalArgumentException("Unknown entity type")
} }

View file

@ -0,0 +1,9 @@
package space.uranos.util
import space.uranos.Vector
fun Vector.getAsVelocityPacketValues() = Triple(
(x * 8000).toInt().toShort(),
(y * 8000).toInt().toShort(),
(z * 8000).toInt().toShort()
)

View file

@ -0,0 +1,16 @@
package space.uranos
import space.uranos.entity.*
import space.uranos.entity.impl.UranosBatEntity
import space.uranos.entity.impl.UranosCowEntity
import space.uranos.entity.impl.UranosCreeperEntity
import space.uranos.entity.impl.UranosRideableMinecartEntity
@Suppress("UNCHECKED_CAST")
fun <T : Entity> createEntityInstance(server: UranosServer, type: EntityType<T>): T = when (type) {
CowEntity -> UranosCowEntity(server)
BatEntity -> UranosBatEntity(server)
CreeperEntity -> UranosCreeperEntity(server)
RideableMinecartEntity -> UranosRideableMinecartEntity(server)
else -> throw IllegalArgumentException("Entities of this type cannot be created with this function")
} as T

View file

@ -5,8 +5,10 @@ import com.sksamuel.hoplite.ConfigLoader
import com.sksamuel.hoplite.ConfigSource import com.sksamuel.hoplite.ConfigSource
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import space.uranos.config.UranosConfig import space.uranos.config.UranosConfig
import space.uranos.entity.* import space.uranos.entity.Entity
import space.uranos.entity.impl.* import space.uranos.entity.EntityType
import space.uranos.entity.UranosEntity
import space.uranos.entity.impl.UranosPlayerEntity
import space.uranos.event.UranosEventBus import space.uranos.event.UranosEventBus
import space.uranos.event.UranosEventHandlerPositionManager import space.uranos.event.UranosEventHandlerPositionManager
import space.uranos.logging.Logger import space.uranos.logging.Logger
@ -79,20 +81,8 @@ class UranosServer internal constructor() : Server() {
private val internalEntities = HashSet<UranosEntity>() private val internalEntities = HashSet<UranosEntity>()
override val entities: Set<Entity> = internalEntities override val entities: Set<Entity> = internalEntities
override fun <T : Entity> create(type: EntityType<T>): T { override fun <T : Entity> create(type: EntityType<T>): T =
val entity: UranosEntity = when (type) { createEntityInstance(this, type).also { internalEntities.add(it as UranosEntity) }
CowEntity -> UranosCowEntity(this)
BatEntity -> UranosBatEntity(this)
CreeperEntity -> UranosCreeperEntity(this)
MinecartEntity -> UranosMinecartEntity(this)
else -> throw IllegalArgumentException("Entities of this type cannot be created with this function")
}
internalEntities.add(entity)
@Suppress("UNCHECKED_CAST")
return entity as T
}
fun createPlayerEntity(player: UranosPlayer) = fun createPlayerEntity(player: UranosPlayer) =
UranosPlayerEntity(this, player).also { internalEntities.add(it) } UranosPlayerEntity(this, player).also { internalEntities.add(it) }

View file

@ -0,0 +1,11 @@
package space.uranos.entity
class EntityMetadataSynchronizer(val entity: UranosEntity) {
init {
// println(entity::class.allSuperclasses)
}
fun tick() {
}
}

View file

@ -23,8 +23,6 @@ import java.util.UUID
import java.util.WeakHashMap import java.util.WeakHashMap
sealed class UranosEntity(server: UranosServer) : Entity { sealed class UranosEntity(server: UranosServer) : Entity {
override fun belongsToChunk(key: Chunk.Key): Boolean = key == chunkKey
override val numericID: Int = server.claimEntityID() override val numericID: Int = server.claimEntityID()
override val uuid: UUID = UUID.randomUUID() override val uuid: UUID = UUID.randomUUID()
@ -65,19 +63,31 @@ sealed class UranosEntity(server: UranosServer) : Entity {
} }
} }
override fun toString(): String = "Entity($uuid)" override var glowing: Boolean = false
override var ignoreGravity: Boolean = false
override var invisible: Boolean = false
override var silent: Boolean = false
override fun toString(): String = "Entity($uuid)"
override fun belongsToChunk(key: Chunk.Key): Boolean = key == chunkKey
abstract suspend fun tick()
abstract val chunkKey: Chunk.Key abstract val chunkKey: Chunk.Key
protected val container = TickSynchronizationContainer() protected val container = TickSynchronizationContainer()
protected fun sendSpawnAndDestroyPackets() { private fun sendSpawnAndDestroyPackets() {
if (addedViewers.isNotEmpty()) createSpawnPacket().let { packet -> addedViewers.forEach { it.session.send(packet) } } if (addedViewers.isNotEmpty()) createSpawnPacket().let { packet -> addedViewers.forEach { it.session.send(packet) } }
if (removedViewers.isNotEmpty()) DestroyEntitiesPacket(arrayOf(numericID)).let { packet -> removedViewers.forEach { it.session.send(packet) } } if (removedViewers.isNotEmpty()) DestroyEntitiesPacket(arrayOf(numericID)).let { packet -> removedViewers.forEach { it.session.send(packet) } }
} }
protected fun finishTick() { @Suppress("LeakingThis")
private val metadataSynchronizer = EntityMetadataSynchronizer(this)
open suspend fun tick() {
container.tick()
metadataSynchronizer.tick()
sendSpawnAndDestroyPackets()
addedViewers.clear() addedViewers.clear()
removedViewers.clear() removedViewers.clear()
} }
@ -106,8 +116,6 @@ abstract class UranosNotHasMovableHeadLivingEntity(server: UranosServer) : Urano
private var lastSentPitch: Float = 0f private var lastSentPitch: Float = 0f
final override suspend fun tick() { final override suspend fun tick() {
container.tick()
val viewersWithoutAdded = viewers.subtract(addedViewers) val viewersWithoutAdded = viewers.subtract(addedViewers)
if (viewersWithoutAdded.isNotEmpty()) { if (viewersWithoutAdded.isNotEmpty()) {
val packet = createMovementPacket() val packet = createMovementPacket()
@ -119,8 +127,7 @@ abstract class UranosNotHasMovableHeadLivingEntity(server: UranosServer) : Urano
if (this is PitchRotatable) lastSentPitch = pitch if (this is PitchRotatable) lastSentPitch = pitch
if (this is YawRotatable) lastSentYaw = yaw if (this is YawRotatable) lastSentYaw = yaw
sendSpawnAndDestroyPackets() super.tick()
finishTick()
} }
private fun createMovementPacket(): OutgoingPacket? = createNotHasMovableHeadMovementPacket(position, lastSentPosition, lastSentYaw, lastSentPitch) private fun createMovementPacket(): OutgoingPacket? = createNotHasMovableHeadMovementPacket(position, lastSentPosition, lastSentYaw, lastSentPitch)
@ -138,8 +145,6 @@ abstract class UranosHasMovableHeadLivingEntity(server: UranosServer) : UranosLi
} }
final override suspend fun tick() { final override suspend fun tick() {
container.tick()
val viewersWithoutAdded = viewers.subtract(addedViewers) val viewersWithoutAdded = viewers.subtract(addedViewers)
if (viewersWithoutAdded.isNotEmpty()) { if (viewersWithoutAdded.isNotEmpty()) {
val packets = createMovementPackets() val packets = createMovementPackets()
@ -152,8 +157,7 @@ abstract class UranosHasMovableHeadLivingEntity(server: UranosServer) : UranosLi
oldHeadPitch = headPitch oldHeadPitch = headPitch
oldHeadYaw = headYaw oldHeadYaw = headYaw
sendSpawnAndDestroyPackets() super.tick()
finishTick()
} }
private var oldPosition: Position = Position.ZERO private var oldPosition: Position = Position.ZERO
@ -216,8 +220,6 @@ abstract class UranosObjectEntity(server: UranosServer) : UranosEntity(server),
private var lastSentPitch: Float = 0f private var lastSentPitch: Float = 0f
final override suspend fun tick() { final override suspend fun tick() {
container.tick()
val viewersWithoutAdded = viewers.subtract(addedViewers) val viewersWithoutAdded = viewers.subtract(addedViewers)
if (viewersWithoutAdded.isNotEmpty()) { if (viewersWithoutAdded.isNotEmpty()) {
val packet = createMovementPacket() val packet = createMovementPacket()
@ -228,9 +230,9 @@ abstract class UranosObjectEntity(server: UranosServer) : UranosEntity(server),
if (this is PitchRotatable) lastSentPitch = pitch if (this is PitchRotatable) lastSentPitch = pitch
if (this is YawRotatable) lastSentYaw = yaw if (this is YawRotatable) lastSentYaw = yaw
lastSentPosition = position
sendSpawnAndDestroyPackets() super.tick()
finishTick()
} }
private fun createMovementPacket(): OutgoingPacket? = createNotHasMovableHeadMovementPacket(position, lastSentPosition, lastSentYaw, lastSentPitch) private fun createMovementPacket(): OutgoingPacket? = createNotHasMovableHeadMovementPacket(position, lastSentPosition, lastSentYaw, lastSentPitch)
@ -247,8 +249,6 @@ class UranosPaintingEntity(
private var lastSentTopLeftLocation: VoxelLocation = topLeftLocation private var lastSentTopLeftLocation: VoxelLocation = topLeftLocation
override suspend fun tick() { override suspend fun tick() {
container.tick()
if (lastSentTopLeftLocation != topLeftLocation) { if (lastSentTopLeftLocation != topLeftLocation) {
val viewersWithoutAdded = viewers.subtract(addedViewers) val viewersWithoutAdded = viewers.subtract(addedViewers)
if (viewersWithoutAdded.isNotEmpty()) { if (viewersWithoutAdded.isNotEmpty()) {
@ -269,8 +269,7 @@ class UranosPaintingEntity(
lastSentTopLeftLocation = topLeftLocation lastSentTopLeftLocation = topLeftLocation
sendSpawnAndDestroyPackets() super.tick()
finishTick()
} }
} }

View file

@ -1,12 +1,10 @@
package space.uranos.entity.impl package space.uranos.entity
import space.uranos.UranosServer import space.uranos.UranosServer
import space.uranos.entity.MinecartEntity
import space.uranos.entity.UranosObjectEntity
import space.uranos.util.numbers.validatePitch import space.uranos.util.numbers.validatePitch
import space.uranos.util.numbers.validateYaw import space.uranos.util.numbers.validateYaw
class UranosMinecartEntity(server: UranosServer) : UranosObjectEntity(server), MinecartEntity { abstract class UranosMinecartEntity(server: UranosServer) : UranosObjectEntity(server), MinecartEntity {
override var yaw: Float = 0f override var yaw: Float = 0f
set(value) { set(value) {
validateYaw(value); field = value validateYaw(value); field = value
@ -16,4 +14,8 @@ class UranosMinecartEntity(server: UranosServer) : UranosObjectEntity(server), M
set(value) { set(value) {
validatePitch(value); field = value validatePitch(value); field = value
} }
override val shakingPower: Int = 0
override val shakingDirection: Int = 1
override val shakingMultiplier: Float = 0f
} }

View file

@ -0,0 +1,11 @@
package space.uranos.entity.impl
import space.uranos.UranosServer
import space.uranos.entity.RideableMinecartEntity
import space.uranos.entity.UranosMinecartEntity
import space.uranos.world.block.Block
class UranosRideableMinecartEntity(server: UranosServer) : UranosMinecartEntity(server), RideableMinecartEntity {
override val customBlock: Block? = null
override val blockOffset: Int = 6
}

View file

@ -68,6 +68,7 @@ class LoginAndJoinProcedure(val session: UranosSession) {
val result = AuthenticationHelper.authenticate(hashString, state.username) val result = AuthenticationHelper.authenticate(hashString, state.username)
session.sendNow(SetCompressionPacket(session.server.config.packetCompressionThreshold)) session.sendNow(SetCompressionPacket(session.server.config.packetCompressionThreshold))
// TODO: Handle disconnect errors
session.enableCompressionCodec() session.enableCompressionCodec()
session.sendNow(LoginSuccessPacket(result.uuid, result.username)) session.sendNow(LoginSuccessPacket(result.uuid, result.username))

View file

@ -18,6 +18,7 @@ class PacketsAdapter(val session: UranosSession) {
private val packetsForNextTick = ArrayList<OutgoingPacket>() private val packetsForNextTick = ArrayList<OutgoingPacket>()
suspend fun tick() { suspend fun tick() {
// TODO: Fix ConcurrentModificationException
packetsForNextTick.forEach { send(it) } packetsForNextTick.forEach { send(it) }
packetsForNextTick.clear() packetsForNextTick.clear()
} }

View file

@ -125,6 +125,7 @@ class UranosSession(val channel: io.netty.channel.Channel, val server: UranosSer
if (!expected && currentProtocol != HandshakingProtocol && currentProtocol != StatusProtocol) if (!expected && currentProtocol != HandshakingProtocol && currentProtocol != StatusProtocol)
logger trace "The client disconnected unexpectedly" logger trace "The client disconnected unexpectedly"
// TODO: Remove the player entity and send PlayerInfo packet
packetsAdapter.stopProcessingIncomingPackets() packetsAdapter.stopProcessingIncomingPackets()
coroutineContext.cancel(DisconnectedCancellationException()) coroutineContext.cancel(DisconnectedCancellationException())
state = State.Disconnected state = State.Disconnected