From 0b1ba947a1f8f4efd5be52ac38bc9a68a2b84db4 Mon Sep 17 00:00:00 2001 From: Moritz Ruth Date: Sun, 7 Mar 2021 15:09:04 +0100 Subject: [PATCH] Add entity metadata packet and object entity spawning --- .idea/misc.xml | 5 ++ .../space/uranos/testplugin/TestPlugin.kt | 22 ++++-- .../main/kotlin/space/uranos/entity/Entity.kt | 13 +++- .../space/uranos/entity/MinecartEntity.kt | 27 +++++++ .../uranos/entity/RideableMinecartEntity.kt | 5 ++ .../packet/play/EntityMetadataPacketCodec.kt | 34 +++++++++ .../packet/play/EntityVelocityPacketCodec.kt | 14 ++++ .../uranos/net/packet/play/PlayProtocol.kt | 2 + .../play/PlayerOrientationPacketCodec.kt | 2 +- .../play/SpawnLivingEntityPacketCodec.kt | 6 +- .../play/SpawnObjectEntityPacketCodec.kt | 6 +- .../net/packet/play/EntityMetadataPacket.kt | 21 ++++++ .../net/packet/play/EntityVelocityPacket.kt | 10 +++ .../packet/play/SpawnLivingEntityPacket.kt | 10 +-- .../packet/play/SpawnObjectEntityPacket.kt | 7 +- .../kotlin/space/uranos/util/SpawnEntity.kt | 72 ++++++++++--------- .../main/kotlin/space/uranos/util/Vector.kt | 9 +++ .../space/uranos/CreateEntityInstance.kt | 16 +++++ .../main/kotlin/space/uranos/UranosServer.kt | 22 ++---- .../entity/EntityMetadataSynchronizer.kt | 11 +++ .../space/uranos/entity/UranosEntity.kt | 43 ++++++----- .../entity/{impl => }/UranosMinecartEntity.kt | 10 +-- .../impl/UranosRideableMinecartEntity.kt | 11 +++ .../space/uranos/net/LoginAndJoinProcedure.kt | 1 + .../kotlin/space/uranos/net/PacketsAdapter.kt | 1 + .../kotlin/space/uranos/net/UranosSession.kt | 1 + 26 files changed, 288 insertions(+), 93 deletions(-) create mode 100644 uranos-api/src/main/kotlin/space/uranos/entity/MinecartEntity.kt create mode 100644 uranos-api/src/main/kotlin/space/uranos/entity/RideableMinecartEntity.kt create mode 100644 uranos-packet-codecs/src/main/kotlin/space/uranos/net/packet/play/EntityMetadataPacketCodec.kt create mode 100644 uranos-packet-codecs/src/main/kotlin/space/uranos/net/packet/play/EntityVelocityPacketCodec.kt create mode 100644 uranos-packets/src/main/kotlin/space/uranos/net/packet/play/EntityMetadataPacket.kt create mode 100644 uranos-packets/src/main/kotlin/space/uranos/net/packet/play/EntityVelocityPacket.kt create mode 100644 uranos-packets/src/main/kotlin/space/uranos/util/Vector.kt create mode 100644 uranos-server/src/main/kotlin/space/uranos/CreateEntityInstance.kt create mode 100644 uranos-server/src/main/kotlin/space/uranos/entity/EntityMetadataSynchronizer.kt rename uranos-server/src/main/kotlin/space/uranos/entity/{impl => }/UranosMinecartEntity.kt (57%) create mode 100644 uranos-server/src/main/kotlin/space/uranos/entity/impl/UranosRideableMinecartEntity.kt diff --git a/.idea/misc.xml b/.idea/misc.xml index 6f45ef7..e72ac25 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -1,5 +1,10 @@ + + + + + diff --git a/test-plugin/src/main/kotlin/space/uranos/testplugin/TestPlugin.kt b/test-plugin/src/main/kotlin/space/uranos/testplugin/TestPlugin.kt index 74f8339..62361af 100644 --- a/test-plugin/src/main/kotlin/space/uranos/testplugin/TestPlugin.kt +++ b/test-plugin/src/main/kotlin/space/uranos/testplugin/TestPlugin.kt @@ -3,12 +3,14 @@ package space.uranos.testplugin import space.uranos.Uranos import space.uranos.chat.ChatColor import space.uranos.chat.TextComponent -import space.uranos.entity.MinecartEntity import space.uranos.entity.Position +import space.uranos.entity.RideableMinecartEntity import space.uranos.net.ServerListInfo import space.uranos.net.event.ServerListInfoRequestEvent import space.uranos.net.event.SessionAfterLoginEvent +import space.uranos.net.packet.play.EntityMetadataPacket import space.uranos.player.GameMode +import space.uranos.player.event.PlayerReadyEvent import space.uranos.plugin.Plugin import space.uranos.testplugin.anvil.AnvilWorld 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() + val entity = Uranos.create() entity.position = Position(0.0, 4.0, 0.0) -// entity.setWorld(world) + entity.setWorld(world) + + Uranos.eventBus.on { + 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 y = -90f diff --git a/uranos-api/src/main/kotlin/space/uranos/entity/Entity.kt b/uranos-api/src/main/kotlin/space/uranos/entity/Entity.kt index 26389ea..5492500 100644 --- a/uranos-api/src/main/kotlin/space/uranos/entity/Entity.kt +++ b/uranos-api/src/main/kotlin/space/uranos/entity/Entity.kt @@ -25,7 +25,7 @@ interface Entity { 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 @@ -38,6 +38,17 @@ interface Entity { * If players should be added to [viewers] when they enter [world]. */ 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 } /** diff --git a/uranos-api/src/main/kotlin/space/uranos/entity/MinecartEntity.kt b/uranos-api/src/main/kotlin/space/uranos/entity/MinecartEntity.kt new file mode 100644 index 0000000..378a069 --- /dev/null +++ b/uranos-api/src/main/kotlin/space/uranos/entity/MinecartEntity.kt @@ -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 +} diff --git a/uranos-api/src/main/kotlin/space/uranos/entity/RideableMinecartEntity.kt b/uranos-api/src/main/kotlin/space/uranos/entity/RideableMinecartEntity.kt new file mode 100644 index 0000000..33bf224 --- /dev/null +++ b/uranos-api/src/main/kotlin/space/uranos/entity/RideableMinecartEntity.kt @@ -0,0 +1,5 @@ +package space.uranos.entity + +interface RideableMinecartEntity : MinecartEntity { + companion object Type : MinecartEntityType() +} diff --git a/uranos-packet-codecs/src/main/kotlin/space/uranos/net/packet/play/EntityMetadataPacketCodec.kt b/uranos-packet-codecs/src/main/kotlin/space/uranos/net/packet/play/EntityMetadataPacketCodec.kt new file mode 100644 index 0000000..6df3cc1 --- /dev/null +++ b/uranos-packet-codecs/src/main/kotlin/space/uranos/net/packet/play/EntityMetadataPacketCodec.kt @@ -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(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) + } +} diff --git a/uranos-packet-codecs/src/main/kotlin/space/uranos/net/packet/play/EntityVelocityPacketCodec.kt b/uranos-packet-codecs/src/main/kotlin/space/uranos/net/packet/play/EntityVelocityPacketCodec.kt new file mode 100644 index 0000000..286a48f --- /dev/null +++ b/uranos-packet-codecs/src/main/kotlin/space/uranos/net/packet/play/EntityVelocityPacketCodec.kt @@ -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(0x46, EntityVelocityPacket::class) { + override fun EntityVelocityPacket.encode(dst: ByteBuf) { + dst.writeVarInt(entityID) + dst.writeShort(x.toInt()) + dst.writeShort(y.toInt()) + dst.writeShort(z.toInt()) + } +} diff --git a/uranos-packet-codecs/src/main/kotlin/space/uranos/net/packet/play/PlayProtocol.kt b/uranos-packet-codecs/src/main/kotlin/space/uranos/net/packet/play/PlayProtocol.kt index 244a6ae..0e8010e 100644 --- a/uranos-packet-codecs/src/main/kotlin/space/uranos/net/packet/play/PlayProtocol.kt +++ b/uranos-packet-codecs/src/main/kotlin/space/uranos/net/packet/play/PlayProtocol.kt @@ -14,10 +14,12 @@ object PlayProtocol : Protocol( DestroyEntitiesPacketCodec, DisconnectPacketCodec, EntityHeadYawPacketCodec, + EntityMetadataPacketCodec, EntityOrientationPacketCodec, EntityRelativeMovePacketCodec, EntityRelativeMoveWithOrientationPacketCodec, EntityTeleportPacketCodec, + EntityVelocityPacketCodec, IncomingKeepAlivePacketCodec, IncomingPlayerPositionPacketCodec, IncomingPluginMessagePacketCodec, diff --git a/uranos-packet-codecs/src/main/kotlin/space/uranos/net/packet/play/PlayerOrientationPacketCodec.kt b/uranos-packet-codecs/src/main/kotlin/space/uranos/net/packet/play/PlayerOrientationPacketCodec.kt index 04189c0..5a40e60 100644 --- a/uranos-packet-codecs/src/main/kotlin/space/uranos/net/packet/play/PlayerOrientationPacketCodec.kt +++ b/uranos-packet-codecs/src/main/kotlin/space/uranos/net/packet/play/PlayerOrientationPacketCodec.kt @@ -7,7 +7,7 @@ import space.uranos.util.numbers.floorMod object PlayerOrientationPacketCodec : IncomingPacketCodec(0x14, PlayerOrientationPacket::class) { 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.readBoolean() ) diff --git a/uranos-packet-codecs/src/main/kotlin/space/uranos/net/packet/play/SpawnLivingEntityPacketCodec.kt b/uranos-packet-codecs/src/main/kotlin/space/uranos/net/packet/play/SpawnLivingEntityPacketCodec.kt index ba0beaf..1ca66bc 100644 --- a/uranos-packet-codecs/src/main/kotlin/space/uranos/net/packet/play/SpawnLivingEntityPacketCodec.kt +++ b/uranos-packet-codecs/src/main/kotlin/space/uranos/net/packet/play/SpawnLivingEntityPacketCodec.kt @@ -20,8 +20,8 @@ object SpawnLivingEntityPacketCodec : OutgoingPacketCodec> +) : OutgoingPacket() { + sealed class MetadataEntry(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(0) + data class Int(override val index: UByte, override val value: kotlin.Int) : MetadataEntry(1) + data class Float(override val index: UByte, override val value: kotlin.Float) : MetadataEntry(2) + data class String(override val index: UByte, override val value: kotlin.String) : MetadataEntry(3) + data class OptChat(override val index: UByte, override val value: ChatComponent?) : MetadataEntry(5) + data class Boolean(override val index: UByte, override val value: kotlin.Boolean) : MetadataEntry(7) + } +} diff --git a/uranos-packets/src/main/kotlin/space/uranos/net/packet/play/EntityVelocityPacket.kt b/uranos-packets/src/main/kotlin/space/uranos/net/packet/play/EntityVelocityPacket.kt new file mode 100644 index 0000000..27581e5 --- /dev/null +++ b/uranos-packets/src/main/kotlin/space/uranos/net/packet/play/EntityVelocityPacket.kt @@ -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() diff --git a/uranos-packets/src/main/kotlin/space/uranos/net/packet/play/SpawnLivingEntityPacket.kt b/uranos-packets/src/main/kotlin/space/uranos/net/packet/play/SpawnLivingEntityPacket.kt index 2ef4f57..6cb149d 100644 --- a/uranos-packets/src/main/kotlin/space/uranos/net/packet/play/SpawnLivingEntityPacket.kt +++ b/uranos-packets/src/main/kotlin/space/uranos/net/packet/play/SpawnLivingEntityPacket.kt @@ -1,6 +1,5 @@ package space.uranos.net.packet.play -import space.uranos.Vector import space.uranos.entity.EntityType import space.uranos.entity.Position import space.uranos.net.packet.OutgoingPacket @@ -8,6 +7,8 @@ import java.util.UUID /** * Sent to spawn **living** entities. + * + * Velocity is measured in 1/8000 blocks per tick. */ data class SpawnLivingEntityPacket( val entityID: Int, @@ -17,8 +18,7 @@ data class SpawnLivingEntityPacket( val yaw: Float, val pitch: Float, val headYaw: Float, - /** - * Velocity in blocks per tick - */ - val velocity: Vector + val velocityX: Short, + val velocityY: Short, + val velocityZ: Short ) : OutgoingPacket() diff --git a/uranos-packets/src/main/kotlin/space/uranos/net/packet/play/SpawnObjectEntityPacket.kt b/uranos-packets/src/main/kotlin/space/uranos/net/packet/play/SpawnObjectEntityPacket.kt index 58f121e..a00f2fc 100644 --- a/uranos-packets/src/main/kotlin/space/uranos/net/packet/play/SpawnObjectEntityPacket.kt +++ b/uranos-packets/src/main/kotlin/space/uranos/net/packet/play/SpawnObjectEntityPacket.kt @@ -1,11 +1,12 @@ package space.uranos.net.packet.play -import space.uranos.Vector import space.uranos.net.packet.OutgoingPacket import java.util.UUID /** * Sent to spawn object entities. + * + * Velocity is measured in 1/8000 blocks per tick. */ data class SpawnObjectEntityPacket( val entityID: Int, @@ -17,5 +18,7 @@ data class SpawnObjectEntityPacket( val yaw: UByte, val pitch: UByte, val data: Int, - val velocity: Vector + val velocityX: Short, + val velocityY: Short, + val velocityZ: Short ) : OutgoingPacket() diff --git a/uranos-packets/src/main/kotlin/space/uranos/util/SpawnEntity.kt b/uranos-packets/src/main/kotlin/space/uranos/util/SpawnEntity.kt index 1dd4dab..4e5a68c 100644 --- a/uranos-packets/src/main/kotlin/space/uranos/util/SpawnEntity.kt +++ b/uranos-packets/src/main/kotlin/space/uranos/util/SpawnEntity.kt @@ -8,37 +8,45 @@ import space.uranos.net.packet.play.SpawnPaintingPacket import space.uranos.util.numbers.mapToUByte fun Entity.createSpawnPacket(): OutgoingPacket = when (this) { - is LivingEntity -> if (this is HasMovableHead) SpawnLivingEntityPacket( - numericID, - uuid, - type, - position, - headYaw, - headPitch, - headYaw, - velocity - ) else SpawnLivingEntityPacket( - numericID, - uuid, - type, - position, - if (this is YawRotatable) yaw else 0f, - if (this is PitchRotatable) pitch else 0f, - 0f, - velocity - ) - is ObjectEntity -> SpawnObjectEntityPacket( - numericID, - uuid, - type.numericID, - position.x, - position.y, - position.z, - if (this is YawRotatable) yaw.mapToUByte(360f) else 0u, - if (this is PitchRotatable) pitch.mapToUByte(360f) else 0u, - getDataValue(), - velocity - ) + is LivingEntity -> { + val (velX, velY, velZ) = velocity.getAsVelocityPacketValues() + + if (this is HasMovableHead) SpawnLivingEntityPacket( + numericID, + uuid, + type, + position, + headYaw, + headPitch, + headYaw, + velX, velY, velZ + ) else SpawnLivingEntityPacket( + numericID, + uuid, + type, + position, + if (this is YawRotatable) yaw else 0f, + if (this is PitchRotatable) pitch else 0f, + 0f, + velX, velY, velZ + ) + } + is ObjectEntity -> { + val (velX, velY, velZ) = velocity.getAsVelocityPacketValues() + + SpawnObjectEntityPacket( + numericID, + uuid, + type.numericID, + position.x, + position.y, + position.z, + if (this is YawRotatable) yaw.mapToUByte(360f) else 0u, + if (this is PitchRotatable) pitch.mapToUByte(360f) else 0u, + getDataValue(), + velX, velY, velZ + ) + } is PaintingEntity -> SpawnPaintingPacket( numericID, uuid, @@ -51,7 +59,7 @@ fun Entity.createSpawnPacket(): OutgoingPacket = when (this) { fun ObjectEntity.getDataValue(): Int = when (this) { is ItemEntity -> 1 - is MinecartEntity -> 2 + is RideableMinecartEntity -> 0 // TODO: Add remaining else -> throw IllegalArgumentException("Unknown entity type") } diff --git a/uranos-packets/src/main/kotlin/space/uranos/util/Vector.kt b/uranos-packets/src/main/kotlin/space/uranos/util/Vector.kt new file mode 100644 index 0000000..c389585 --- /dev/null +++ b/uranos-packets/src/main/kotlin/space/uranos/util/Vector.kt @@ -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() +) diff --git a/uranos-server/src/main/kotlin/space/uranos/CreateEntityInstance.kt b/uranos-server/src/main/kotlin/space/uranos/CreateEntityInstance.kt new file mode 100644 index 0000000..13b974a --- /dev/null +++ b/uranos-server/src/main/kotlin/space/uranos/CreateEntityInstance.kt @@ -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 createEntityInstance(server: UranosServer, type: EntityType): 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 diff --git a/uranos-server/src/main/kotlin/space/uranos/UranosServer.kt b/uranos-server/src/main/kotlin/space/uranos/UranosServer.kt index d8eaa34..c4a55e0 100644 --- a/uranos-server/src/main/kotlin/space/uranos/UranosServer.kt +++ b/uranos-server/src/main/kotlin/space/uranos/UranosServer.kt @@ -5,8 +5,10 @@ import com.sksamuel.hoplite.ConfigLoader import com.sksamuel.hoplite.ConfigSource import kotlinx.coroutines.runBlocking import space.uranos.config.UranosConfig -import space.uranos.entity.* -import space.uranos.entity.impl.* +import space.uranos.entity.Entity +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.UranosEventHandlerPositionManager import space.uranos.logging.Logger @@ -79,20 +81,8 @@ class UranosServer internal constructor() : Server() { private val internalEntities = HashSet() override val entities: Set = internalEntities - override fun create(type: EntityType): T { - val entity: UranosEntity = when (type) { - 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 - } + override fun create(type: EntityType): T = + createEntityInstance(this, type).also { internalEntities.add(it as UranosEntity) } fun createPlayerEntity(player: UranosPlayer) = UranosPlayerEntity(this, player).also { internalEntities.add(it) } diff --git a/uranos-server/src/main/kotlin/space/uranos/entity/EntityMetadataSynchronizer.kt b/uranos-server/src/main/kotlin/space/uranos/entity/EntityMetadataSynchronizer.kt new file mode 100644 index 0000000..896be5e --- /dev/null +++ b/uranos-server/src/main/kotlin/space/uranos/entity/EntityMetadataSynchronizer.kt @@ -0,0 +1,11 @@ +package space.uranos.entity + +class EntityMetadataSynchronizer(val entity: UranosEntity) { + init { +// println(entity::class.allSuperclasses) + } + + fun tick() { + + } +} diff --git a/uranos-server/src/main/kotlin/space/uranos/entity/UranosEntity.kt b/uranos-server/src/main/kotlin/space/uranos/entity/UranosEntity.kt index 8b3daf9..a0480ea 100644 --- a/uranos-server/src/main/kotlin/space/uranos/entity/UranosEntity.kt +++ b/uranos-server/src/main/kotlin/space/uranos/entity/UranosEntity.kt @@ -23,8 +23,6 @@ import java.util.UUID import java.util.WeakHashMap sealed class UranosEntity(server: UranosServer) : Entity { - override fun belongsToChunk(key: Chunk.Key): Boolean = key == chunkKey - override val numericID: Int = server.claimEntityID() 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 protected val container = TickSynchronizationContainer() - protected fun sendSpawnAndDestroyPackets() { + private fun sendSpawnAndDestroyPackets() { 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) } } } - protected fun finishTick() { + @Suppress("LeakingThis") + private val metadataSynchronizer = EntityMetadataSynchronizer(this) + + open suspend fun tick() { + container.tick() + metadataSynchronizer.tick() + + sendSpawnAndDestroyPackets() addedViewers.clear() removedViewers.clear() } @@ -106,8 +116,6 @@ abstract class UranosNotHasMovableHeadLivingEntity(server: UranosServer) : Urano private var lastSentPitch: Float = 0f final override suspend fun tick() { - container.tick() - val viewersWithoutAdded = viewers.subtract(addedViewers) if (viewersWithoutAdded.isNotEmpty()) { val packet = createMovementPacket() @@ -119,8 +127,7 @@ abstract class UranosNotHasMovableHeadLivingEntity(server: UranosServer) : Urano if (this is PitchRotatable) lastSentPitch = pitch if (this is YawRotatable) lastSentYaw = yaw - sendSpawnAndDestroyPackets() - finishTick() + super.tick() } private fun createMovementPacket(): OutgoingPacket? = createNotHasMovableHeadMovementPacket(position, lastSentPosition, lastSentYaw, lastSentPitch) @@ -138,8 +145,6 @@ abstract class UranosHasMovableHeadLivingEntity(server: UranosServer) : UranosLi } final override suspend fun tick() { - container.tick() - val viewersWithoutAdded = viewers.subtract(addedViewers) if (viewersWithoutAdded.isNotEmpty()) { val packets = createMovementPackets() @@ -152,8 +157,7 @@ abstract class UranosHasMovableHeadLivingEntity(server: UranosServer) : UranosLi oldHeadPitch = headPitch oldHeadYaw = headYaw - sendSpawnAndDestroyPackets() - finishTick() + super.tick() } private var oldPosition: Position = Position.ZERO @@ -216,8 +220,6 @@ abstract class UranosObjectEntity(server: UranosServer) : UranosEntity(server), private var lastSentPitch: Float = 0f final override suspend fun tick() { - container.tick() - val viewersWithoutAdded = viewers.subtract(addedViewers) if (viewersWithoutAdded.isNotEmpty()) { val packet = createMovementPacket() @@ -228,9 +230,9 @@ abstract class UranosObjectEntity(server: UranosServer) : UranosEntity(server), if (this is PitchRotatable) lastSentPitch = pitch if (this is YawRotatable) lastSentYaw = yaw + lastSentPosition = position - sendSpawnAndDestroyPackets() - finishTick() + super.tick() } private fun createMovementPacket(): OutgoingPacket? = createNotHasMovableHeadMovementPacket(position, lastSentPosition, lastSentYaw, lastSentPitch) @@ -247,8 +249,6 @@ class UranosPaintingEntity( private var lastSentTopLeftLocation: VoxelLocation = topLeftLocation override suspend fun tick() { - container.tick() - if (lastSentTopLeftLocation != topLeftLocation) { val viewersWithoutAdded = viewers.subtract(addedViewers) if (viewersWithoutAdded.isNotEmpty()) { @@ -269,8 +269,7 @@ class UranosPaintingEntity( lastSentTopLeftLocation = topLeftLocation - sendSpawnAndDestroyPackets() - finishTick() + super.tick() } } diff --git a/uranos-server/src/main/kotlin/space/uranos/entity/impl/UranosMinecartEntity.kt b/uranos-server/src/main/kotlin/space/uranos/entity/UranosMinecartEntity.kt similarity index 57% rename from uranos-server/src/main/kotlin/space/uranos/entity/impl/UranosMinecartEntity.kt rename to uranos-server/src/main/kotlin/space/uranos/entity/UranosMinecartEntity.kt index 7ceb116..12d7313 100644 --- a/uranos-server/src/main/kotlin/space/uranos/entity/impl/UranosMinecartEntity.kt +++ b/uranos-server/src/main/kotlin/space/uranos/entity/UranosMinecartEntity.kt @@ -1,12 +1,10 @@ -package space.uranos.entity.impl +package space.uranos.entity 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.validateYaw -class UranosMinecartEntity(server: UranosServer) : UranosObjectEntity(server), MinecartEntity { +abstract class UranosMinecartEntity(server: UranosServer) : UranosObjectEntity(server), MinecartEntity { override var yaw: Float = 0f set(value) { validateYaw(value); field = value @@ -16,4 +14,8 @@ class UranosMinecartEntity(server: UranosServer) : UranosObjectEntity(server), M set(value) { validatePitch(value); field = value } + + override val shakingPower: Int = 0 + override val shakingDirection: Int = 1 + override val shakingMultiplier: Float = 0f } diff --git a/uranos-server/src/main/kotlin/space/uranos/entity/impl/UranosRideableMinecartEntity.kt b/uranos-server/src/main/kotlin/space/uranos/entity/impl/UranosRideableMinecartEntity.kt new file mode 100644 index 0000000..151ce2a --- /dev/null +++ b/uranos-server/src/main/kotlin/space/uranos/entity/impl/UranosRideableMinecartEntity.kt @@ -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 +} diff --git a/uranos-server/src/main/kotlin/space/uranos/net/LoginAndJoinProcedure.kt b/uranos-server/src/main/kotlin/space/uranos/net/LoginAndJoinProcedure.kt index daa6654..0f0a17b 100644 --- a/uranos-server/src/main/kotlin/space/uranos/net/LoginAndJoinProcedure.kt +++ b/uranos-server/src/main/kotlin/space/uranos/net/LoginAndJoinProcedure.kt @@ -68,6 +68,7 @@ class LoginAndJoinProcedure(val session: UranosSession) { val result = AuthenticationHelper.authenticate(hashString, state.username) session.sendNow(SetCompressionPacket(session.server.config.packetCompressionThreshold)) + // TODO: Handle disconnect errors session.enableCompressionCodec() session.sendNow(LoginSuccessPacket(result.uuid, result.username)) diff --git a/uranos-server/src/main/kotlin/space/uranos/net/PacketsAdapter.kt b/uranos-server/src/main/kotlin/space/uranos/net/PacketsAdapter.kt index 1c95bbc..2cbbd37 100644 --- a/uranos-server/src/main/kotlin/space/uranos/net/PacketsAdapter.kt +++ b/uranos-server/src/main/kotlin/space/uranos/net/PacketsAdapter.kt @@ -18,6 +18,7 @@ class PacketsAdapter(val session: UranosSession) { private val packetsForNextTick = ArrayList() suspend fun tick() { + // TODO: Fix ConcurrentModificationException packetsForNextTick.forEach { send(it) } packetsForNextTick.clear() } diff --git a/uranos-server/src/main/kotlin/space/uranos/net/UranosSession.kt b/uranos-server/src/main/kotlin/space/uranos/net/UranosSession.kt index e0d939a..02528c8 100644 --- a/uranos-server/src/main/kotlin/space/uranos/net/UranosSession.kt +++ b/uranos-server/src/main/kotlin/space/uranos/net/UranosSession.kt @@ -125,6 +125,7 @@ class UranosSession(val channel: io.netty.channel.Channel, val server: UranosSer if (!expected && currentProtocol != HandshakingProtocol && currentProtocol != StatusProtocol) logger trace "The client disconnected unexpectedly" + // TODO: Remove the player entity and send PlayerInfo packet packetsAdapter.stopProcessingIncomingPackets() coroutineContext.cancel(DisconnectedCancellationException()) state = State.Disconnected