From b51ed1c291e74cd8e56acfd51824a959cb4a41d9 Mon Sep 17 00:00:00 2001 From: Moritz Ruth Date: Wed, 7 Apr 2021 14:12:13 +0200 Subject: [PATCH] Implement entity metadata synchronization --- .idea/inspectionProfiles/Project_Default.xml | 6 + README.md | 4 +- .../uranos/mdsp/MinecraftDataSourcesPlugin.kt | 4 +- .../mdsp/generator/FluidIDMapGenerator.kt | 1 - .../mdsp/generator/ItemTypeEnumGenerator.kt | 1 - .../space/uranos/testplugin/TestPlugin.kt | 36 +--- .../uranos/testplugin/anvil/AnvilWorld.kt | 1 - .../kotlin/space/uranos/CardinalDirection.kt | 4 +- .../kotlin/space/uranos/chat/ChatColor.kt | 2 +- .../kotlin/space/uranos/entity/Ageable.kt | 5 + .../kotlin/space/uranos/entity/BatEntity.kt | 7 + .../kotlin/space/uranos/entity/CowEntity.kt | 5 + .../main/kotlin/space/uranos/entity/Entity.kt | 18 +- .../kotlin/space/uranos/entity/EntityType.kt | 4 +- .../space/uranos/entity/LivingEntity.kt | 1 + .../space/uranos/entity/MinecartEntity.kt | 10 +- .../space/uranos/entity/ObjectEntity.kt | 4 +- .../space/uranos/entity/PaintingEntity.kt | 2 +- .../space/uranos/entity/metadata/Pose.kt | 11 ++ .../entity/metadata/ThreeAxisRotation.kt | 3 + .../space/uranos/net/packet/Protocol.kt | 4 +- .../kotlin/space/uranos/tag/TagRegistry.kt | 2 +- .../util/TickSynchronizationContainer.kt | 2 +- .../space/uranos/util/collections/Caches.kt | 27 +++ .../CreateWeakValuesLoadingCache.kt | 19 --- .../kotlin/space/uranos/util/numbers/Bits.kt | 7 + .../main/kotlin/space/uranos/world/Chunk.kt | 2 - .../space/uranos/world/block/BlockCodec.kt | 2 + .../space/uranos/world/block/Material.kt | 4 +- .../main/kotlin/space/uranos/nbt/NBTType.kt | 2 +- .../packet/play/EntityMetadataPacketCodec.kt | 12 +- .../net/packet/play/JoinGamePacketCodec.kt | 41 ++--- .../net/packet/play/EntityMetadataPacket.kt | 32 +++- .../net/packet/play/PlayerInfoPacket.kt | 2 +- .../space/uranos/CreateEntityInstance.kt | 2 - .../main/kotlin/space/uranos/UranosServer.kt | 2 +- .../entity/EntityMetadataSynchronizer.kt | 55 ++++++- .../space/uranos/entity/UranosEntity.kt | 25 +-- .../uranos/entity/UranosMinecartEntity.kt | 6 +- .../uranos/entity/impl/UranosBatEntity.kt | 16 +- .../uranos/entity/impl/UranosCowEntity.kt | 15 +- .../uranos/entity/impl/UranosCreeperEntity.kt | 7 - .../uranos/entity/impl/UranosPlayerEntity.kt | 10 ++ .../impl/UranosRideableMinecartEntity.kt | 31 +++- .../metadata/EntityMetadataFieldsTable.kt | 155 ++++++++++++++++++ .../space/uranos/net/LoginAndJoinProcedure.kt | 22 +-- .../kotlin/space/uranos/net/PacketsAdapter.kt | 1 + .../kotlin/space/uranos/net/UranosSession.kt | 4 +- 48 files changed, 459 insertions(+), 179 deletions(-) create mode 100644 .idea/inspectionProfiles/Project_Default.xml create mode 100644 uranos-api/src/main/kotlin/space/uranos/entity/Ageable.kt create mode 100644 uranos-api/src/main/kotlin/space/uranos/entity/BatEntity.kt create mode 100644 uranos-api/src/main/kotlin/space/uranos/entity/CowEntity.kt create mode 100644 uranos-api/src/main/kotlin/space/uranos/entity/metadata/Pose.kt create mode 100644 uranos-api/src/main/kotlin/space/uranos/entity/metadata/ThreeAxisRotation.kt create mode 100644 uranos-api/src/main/kotlin/space/uranos/util/collections/Caches.kt delete mode 100644 uranos-api/src/main/kotlin/space/uranos/util/collections/CreateWeakValuesLoadingCache.kt delete mode 100644 uranos-server/src/main/kotlin/space/uranos/entity/impl/UranosCreeperEntity.kt create mode 100644 uranos-server/src/main/kotlin/space/uranos/entity/metadata/EntityMetadataFieldsTable.kt diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 0000000..28305ea --- /dev/null +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,6 @@ + + + + diff --git a/README.md b/README.md index 46de3e1..298f8cb 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ Uranos is a game server implementing the Minecraft protocol. That means you can use the official Minecraft client to join Uranos servers. -Its goal is to be a modern alternative to Bukkit, SpigotMC and Paper. It is primarily intended to make creating custom games inside of Minecraft easier than it +Its goal is to be a modern alternative to Bukkit, Spigot and Paper. It is primarily intended to make creating custom games inside of Minecraft easier than it is possible with the existing alternatives, but it can also be used for something like a lobby/hub server. The most important thing for Uranos is @@ -10,7 +10,6 @@ The most important thing for Uranos is ## Milestones -- Players can see entities - Players can see other players - Players can send and receive chat messages - Players can see Titles @@ -24,6 +23,7 @@ The most important thing for Uranos is - Command framework + permissions - Commands can be sent from the console - Players can be teleported between worlds +- All entities are implemented with all metadata - Entity AI framework - Scoreboards + Teams - Crafting diff --git a/buildSrc/src/main/kotlin/space/uranos/mdsp/MinecraftDataSourcesPlugin.kt b/buildSrc/src/main/kotlin/space/uranos/mdsp/MinecraftDataSourcesPlugin.kt index 6321095..6feb9ec 100644 --- a/buildSrc/src/main/kotlin/space/uranos/mdsp/MinecraftDataSourcesPlugin.kt +++ b/buildSrc/src/main/kotlin/space/uranos/mdsp/MinecraftDataSourcesPlugin.kt @@ -49,8 +49,8 @@ class MinecraftDataSourcesPlugin : Plugin { BlocksAndMaterialGenerator(workingDir, outputDir, sourcesDir).generate() TagsGenerator(workingDir, outputDir).generate() EntitiesGenerator(workingDir, outputDir, sourcesDir).generate() - FluidIDMapGenerator(workingDir, outputDir, registries).generate() - ItemTypeEnumGenerator(workingDir, outputDir, registries).generate() + FluidIDMapGenerator(outputDir, registries).generate() + ItemTypeEnumGenerator(outputDir, registries).generate() } } } diff --git a/buildSrc/src/main/kotlin/space/uranos/mdsp/generator/FluidIDMapGenerator.kt b/buildSrc/src/main/kotlin/space/uranos/mdsp/generator/FluidIDMapGenerator.kt index 8d0e4ea..e13ad62 100644 --- a/buildSrc/src/main/kotlin/space/uranos/mdsp/generator/FluidIDMapGenerator.kt +++ b/buildSrc/src/main/kotlin/space/uranos/mdsp/generator/FluidIDMapGenerator.kt @@ -7,7 +7,6 @@ import space.uranos.mdsp.JsonAny import java.io.File class FluidIDMapGenerator( - private val workingDir: File, private val outputDir: File, private val registries: JsonAny ) { diff --git a/buildSrc/src/main/kotlin/space/uranos/mdsp/generator/ItemTypeEnumGenerator.kt b/buildSrc/src/main/kotlin/space/uranos/mdsp/generator/ItemTypeEnumGenerator.kt index 957499d..57bfbcd 100644 --- a/buildSrc/src/main/kotlin/space/uranos/mdsp/generator/ItemTypeEnumGenerator.kt +++ b/buildSrc/src/main/kotlin/space/uranos/mdsp/generator/ItemTypeEnumGenerator.kt @@ -8,7 +8,6 @@ import space.uranos.mdsp.util.ConstructorPropertiesHelper import java.io.File class ItemTypeEnumGenerator( - private val workingDir: File, private val outputDir: File, private val registries: JsonAny ) { 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 62361af..b97e8c1 100644 --- a/test-plugin/src/main/kotlin/space/uranos/testplugin/TestPlugin.kt +++ b/test-plugin/src/main/kotlin/space/uranos/testplugin/TestPlugin.kt @@ -3,25 +3,22 @@ package space.uranos.testplugin import space.uranos.Uranos import space.uranos.chat.ChatColor import space.uranos.chat.TextComponent +import space.uranos.entity.CowEntity 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 -import space.uranos.util.runInServerThread import space.uranos.util.secondsToTicks import space.uranos.world.* import space.uranos.world.block.GreenWoolBlock import space.uranos.world.block.RedWoolBlock class TestPlugin : Plugin("Test", "1.0.0") { - fun enableOptifineFix() { + private fun enableOptifineFix() { Uranos.biomeRegistry.register( Biome( "minecraft:swamp", @@ -106,35 +103,12 @@ class TestPlugin : Plugin("Test", "1.0.0") { } } - val entity = Uranos.create() + val entity = Uranos.create() entity.position = Position(0.0, 4.0, 0.0) 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 - Uranos.scheduler.executeRepeating(1) { - runInServerThread { - entity.yaw = x - entity.pitch = y - } - - x += 5f - y += 5f - if (x == 360f) x = 0f - if (y == 90f) y = -90f + Uranos.scheduler.executeRepeating(20) { + entity.isBaby = !entity.isBaby } } } diff --git a/test-plugin/src/main/kotlin/space/uranos/testplugin/anvil/AnvilWorld.kt b/test-plugin/src/main/kotlin/space/uranos/testplugin/anvil/AnvilWorld.kt index f227b3a..b6ff46e 100644 --- a/test-plugin/src/main/kotlin/space/uranos/testplugin/anvil/AnvilWorld.kt +++ b/test-plugin/src/main/kotlin/space/uranos/testplugin/anvil/AnvilWorld.kt @@ -9,7 +9,6 @@ import space.uranos.util.newSingleThreadDispatcher import space.uranos.world.Chunk import space.uranos.world.Dimension import space.uranos.world.World -import java.util.* class AnvilWorld( override val dimension: Dimension, diff --git a/uranos-api/src/main/kotlin/space/uranos/CardinalDirection.kt b/uranos-api/src/main/kotlin/space/uranos/CardinalDirection.kt index 6221950..ac9aca3 100644 --- a/uranos-api/src/main/kotlin/space/uranos/CardinalDirection.kt +++ b/uranos-api/src/main/kotlin/space/uranos/CardinalDirection.kt @@ -1,10 +1,8 @@ package space.uranos -enum class CardinalDirection(private val direction: Direction) { +enum class CardinalDirection(val direction: Direction) { NORTH(Direction.NORTH), EAST(Direction.EAST), SOUTH(Direction.SOUTH), WEST(Direction.WEST); - - fun asDirection() = direction } diff --git a/uranos-api/src/main/kotlin/space/uranos/chat/ChatColor.kt b/uranos-api/src/main/kotlin/space/uranos/chat/ChatColor.kt index abc0d2d..3090df2 100644 --- a/uranos-api/src/main/kotlin/space/uranos/chat/ChatColor.kt +++ b/uranos-api/src/main/kotlin/space/uranos/chat/ChatColor.kt @@ -47,7 +47,7 @@ sealed class ChatColor(val stringRepresentation: String) { PINK, YELLOW, WHITE - ).map { it.stringRepresentation to it }.toMap() + ).associateBy { it.stringRepresentation } } fun fromString(value: String): ChatColor = diff --git a/uranos-api/src/main/kotlin/space/uranos/entity/Ageable.kt b/uranos-api/src/main/kotlin/space/uranos/entity/Ageable.kt new file mode 100644 index 0000000..7abaaf3 --- /dev/null +++ b/uranos-api/src/main/kotlin/space/uranos/entity/Ageable.kt @@ -0,0 +1,5 @@ +package space.uranos.entity + +interface Ageable: Entity { + var isBaby: Boolean +} diff --git a/uranos-api/src/main/kotlin/space/uranos/entity/BatEntity.kt b/uranos-api/src/main/kotlin/space/uranos/entity/BatEntity.kt new file mode 100644 index 0000000..36884c1 --- /dev/null +++ b/uranos-api/src/main/kotlin/space/uranos/entity/BatEntity.kt @@ -0,0 +1,7 @@ +package space.uranos.entity + +interface BatEntity : LivingEntity, HasMovableHead { + companion object Type : BatEntityType() + + var hanging: Boolean +} diff --git a/uranos-api/src/main/kotlin/space/uranos/entity/CowEntity.kt b/uranos-api/src/main/kotlin/space/uranos/entity/CowEntity.kt new file mode 100644 index 0000000..fd877af --- /dev/null +++ b/uranos-api/src/main/kotlin/space/uranos/entity/CowEntity.kt @@ -0,0 +1,5 @@ +package space.uranos.entity + +interface CowEntity : LivingEntity, Ageable { + companion object Type : CowEntityType() +} 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 5492500..3b16203 100644 --- a/uranos-api/src/main/kotlin/space/uranos/entity/Entity.kt +++ b/uranos-api/src/main/kotlin/space/uranos/entity/Entity.kt @@ -1,9 +1,8 @@ package space.uranos.entity -import com.google.common.cache.CacheBuilder -import com.google.common.cache.CacheLoader import space.uranos.event.EventBusWrapper import space.uranos.player.Player +import space.uranos.util.collections.createWeakKeysLoadingCache import space.uranos.world.Chunk import space.uranos.world.World import java.util.UUID @@ -39,16 +38,9 @@ interface Entity { */ var visibleToNewPlayers: Boolean - var glowing: Boolean // even experience orbs can glow + var glowing: Boolean // It seems like every entity can glow var invisible: Boolean - - /** - * Whether this entity does not produce any sounds. - */ - var silent: Boolean - - var ignoreGravity: Boolean } /** @@ -56,11 +48,7 @@ interface Entity { */ fun Entity.getWorldOrFail() = world ?: throw IllegalStateException("This entity has not been spawned") -private val eventBusWrapperCache = CacheBuilder.newBuilder() - .weakKeys() - .build(object : CacheLoader>() { - override fun load(key: Entity): EventBusWrapper = EventBusWrapper(key) - }) +private val eventBusWrapperCache = createWeakKeysLoadingCache> { EventBusWrapper(it) } @Suppress("UNCHECKED_CAST") val T.events diff --git a/uranos-api/src/main/kotlin/space/uranos/entity/EntityType.kt b/uranos-api/src/main/kotlin/space/uranos/entity/EntityType.kt index 36c8a07..e52300f 100644 --- a/uranos-api/src/main/kotlin/space/uranos/entity/EntityType.kt +++ b/uranos-api/src/main/kotlin/space/uranos/entity/EntityType.kt @@ -15,11 +15,11 @@ interface EntityType { */ val all: Collection> = ENTITY_TYPES - val byID: Map> = all.map { it.id to it }.toMap() + val byID: Map> = all.associateBy { it.id } fun byID(id: String) = byID[id] private val byInterfaceTypeMap: Map, EntityType<*>> = - ENTITY_TYPES.map { it.interfaceType to it }.toMap() + ENTITY_TYPES.associateBy { it.interfaceType } @Suppress("UNCHECKED_CAST") fun byInterfaceType(interfaceType: KClass): EntityType = diff --git a/uranos-api/src/main/kotlin/space/uranos/entity/LivingEntity.kt b/uranos-api/src/main/kotlin/space/uranos/entity/LivingEntity.kt index 754a32b..31a09ce 100644 --- a/uranos-api/src/main/kotlin/space/uranos/entity/LivingEntity.kt +++ b/uranos-api/src/main/kotlin/space/uranos/entity/LivingEntity.kt @@ -1,4 +1,5 @@ package space.uranos.entity interface LivingEntity : Entity, Mobile { + // potion effects } diff --git a/uranos-api/src/main/kotlin/space/uranos/entity/MinecartEntity.kt b/uranos-api/src/main/kotlin/space/uranos/entity/MinecartEntity.kt index 378a069..320c72c 100644 --- a/uranos-api/src/main/kotlin/space/uranos/entity/MinecartEntity.kt +++ b/uranos-api/src/main/kotlin/space/uranos/entity/MinecartEntity.kt @@ -6,22 +6,22 @@ interface MinecartEntity: ObjectEntity, YawRotatable, PitchRotatable { /** * Default is `0`. */ - val shakingPower: Int + var shakingPower: Int /** * Default is `1`. */ - val shakingDirection: Int + var shakingDirection: Int /** * Default is `0.0`. */ - val shakingMultiplier: Float + var shakingMultiplier: Float - val customBlock: Block? + var customBlock: Block? /** * The Y offset of the block inside the minecart, measured in 16ths of a block. */ - val blockOffset: Int + var blockOffset: Int } diff --git a/uranos-api/src/main/kotlin/space/uranos/entity/ObjectEntity.kt b/uranos-api/src/main/kotlin/space/uranos/entity/ObjectEntity.kt index e8e6303..56bf092 100644 --- a/uranos-api/src/main/kotlin/space/uranos/entity/ObjectEntity.kt +++ b/uranos-api/src/main/kotlin/space/uranos/entity/ObjectEntity.kt @@ -1,5 +1,3 @@ package space.uranos.entity -interface ObjectEntity : Entity, Mobile { - -} +interface ObjectEntity : Entity, Mobile diff --git a/uranos-api/src/main/kotlin/space/uranos/entity/PaintingEntity.kt b/uranos-api/src/main/kotlin/space/uranos/entity/PaintingEntity.kt index 0b6e9b1..cb4d6e0 100644 --- a/uranos-api/src/main/kotlin/space/uranos/entity/PaintingEntity.kt +++ b/uranos-api/src/main/kotlin/space/uranos/entity/PaintingEntity.kt @@ -10,7 +10,7 @@ interface PaintingEntity : Entity { val facing: CardinalDirection val motive: PaintingMotive - companion object Type : AreaEffectCloudEntityType() + companion object Type : PaintingEntityType() } fun PaintingEntity.calculateCenterLocation() = topLeftLocation.copy( diff --git a/uranos-api/src/main/kotlin/space/uranos/entity/metadata/Pose.kt b/uranos-api/src/main/kotlin/space/uranos/entity/metadata/Pose.kt new file mode 100644 index 0000000..ba2ddcd --- /dev/null +++ b/uranos-api/src/main/kotlin/space/uranos/entity/metadata/Pose.kt @@ -0,0 +1,11 @@ +package space.uranos.entity.metadata + +enum class Pose { + STANDING, + FLYING, + LYING, + SWIMMING, + SPIN_ATTACKING, + SNEAKING, + DYING +} diff --git a/uranos-api/src/main/kotlin/space/uranos/entity/metadata/ThreeAxisRotation.kt b/uranos-api/src/main/kotlin/space/uranos/entity/metadata/ThreeAxisRotation.kt new file mode 100644 index 0000000..1df47d8 --- /dev/null +++ b/uranos-api/src/main/kotlin/space/uranos/entity/metadata/ThreeAxisRotation.kt @@ -0,0 +1,3 @@ +package space.uranos.entity.metadata + +data class ThreeAxisRotation(val x: Float, val y: Float, val z: Float) diff --git a/uranos-api/src/main/kotlin/space/uranos/net/packet/Protocol.kt b/uranos-api/src/main/kotlin/space/uranos/net/packet/Protocol.kt index 8ec1dc7..f892da0 100644 --- a/uranos-api/src/main/kotlin/space/uranos/net/packet/Protocol.kt +++ b/uranos-api/src/main/kotlin/space/uranos/net/packet/Protocol.kt @@ -4,8 +4,8 @@ import kotlin.reflect.KClass abstract class Protocol constructor(val name: String, vararg codecs: PacketCodec<*>) { val codecs = codecs.toSet() - private val codecsByPacketType = codecs.map { it.dataType to it }.toMap() - val incomingPacketCodecsByID = codecs.filterIsInstance>().map { it.id to it }.toMap() + private val codecsByPacketType = codecs.associateBy { it.dataType } + val incomingPacketCodecsByID = codecs.filterIsInstance>().associateBy { it.id } @Suppress("UNCHECKED_CAST") fun getCodecByType(type: KClass): PacketCodec = codecsByPacketType[type] as PacketCodec? diff --git a/uranos-api/src/main/kotlin/space/uranos/tag/TagRegistry.kt b/uranos-api/src/main/kotlin/space/uranos/tag/TagRegistry.kt index 5d0df67..ba52667 100644 --- a/uranos-api/src/main/kotlin/space/uranos/tag/TagRegistry.kt +++ b/uranos-api/src/main/kotlin/space/uranos/tag/TagRegistry.kt @@ -5,5 +5,5 @@ object TagRegistry { val tagsByType: Map> = tags.groupBy { it.type } val tagsByNameByType: Map> = - tagsByType.mapValues { (_, tags) -> tags.map { it.name to it }.toMap() } + tagsByType.mapValues { (_, tags) -> tags.associateBy { it.name } } } diff --git a/uranos-api/src/main/kotlin/space/uranos/util/TickSynchronizationContainer.kt b/uranos-api/src/main/kotlin/space/uranos/util/TickSynchronizationContainer.kt index 947fa55..ef9047a 100644 --- a/uranos-api/src/main/kotlin/space/uranos/util/TickSynchronizationContainer.kt +++ b/uranos-api/src/main/kotlin/space/uranos/util/TickSynchronizationContainer.kt @@ -20,7 +20,7 @@ class TickSynchronizationContainer { onChange: ((value: T) -> Unit)? = null, onTick: suspend (value: T) -> Unit ): Delegate = - Delegate(initialValue, onChange, { value, changed -> if (changed) onTick(value) }).also { delegates.add(it) } + Delegate(initialValue, onChange) { value, changed -> if (changed) onTick(value) }.also { delegates.add(it) } operator fun invoke( initialValue: T, diff --git a/uranos-api/src/main/kotlin/space/uranos/util/collections/Caches.kt b/uranos-api/src/main/kotlin/space/uranos/util/collections/Caches.kt new file mode 100644 index 0000000..0bf05de --- /dev/null +++ b/uranos-api/src/main/kotlin/space/uranos/util/collections/Caches.kt @@ -0,0 +1,27 @@ +package space.uranos.util.collections + +import com.google.common.cache.CacheBuilder +import com.google.common.cache.CacheLoader +import com.google.common.cache.LoadingCache + +/** + * Creates a loading cache with weak **keys**. + * + * @param create The function invoked to create a new value. + */ +fun createWeakKeysLoadingCache(create: (key: K) -> V): LoadingCache { + return CacheBuilder.newBuilder().weakKeys().build(object : CacheLoader() { + override fun load(key: K): V = create(key) + }) +} + +/** + * Creates a loading cache with weak **values**. + * + * @param create The function invoked to create a new value. + */ +fun createWeakValuesLoadingCache(create: (key: K) -> V): LoadingCache { + return CacheBuilder.newBuilder().weakValues().build(object : CacheLoader() { + override fun load(key: K): V = create(key) + }) +} diff --git a/uranos-api/src/main/kotlin/space/uranos/util/collections/CreateWeakValuesLoadingCache.kt b/uranos-api/src/main/kotlin/space/uranos/util/collections/CreateWeakValuesLoadingCache.kt deleted file mode 100644 index eb4e2d7..0000000 --- a/uranos-api/src/main/kotlin/space/uranos/util/collections/CreateWeakValuesLoadingCache.kt +++ /dev/null @@ -1,19 +0,0 @@ -package space.uranos.util.collections - -import com.google.common.cache.CacheBuilder -import com.google.common.cache.CacheLoader -import com.google.common.cache.LoadingCache - -/** - * Creates a LoadingCache with weak values. - * - * @param loader The function invoked every time a value is not yet in the cache. - */ -fun createWeakValuesLoadingCache(loader: (key: K) -> V): LoadingCache { - return CacheBuilder - .newBuilder() - .weakValues() - .build(object : CacheLoader() { - override fun load(key: K): V = loader(key) - }) -} diff --git a/uranos-api/src/main/kotlin/space/uranos/util/numbers/Bits.kt b/uranos-api/src/main/kotlin/space/uranos/util/numbers/Bits.kt index cc108b6..6571016 100644 --- a/uranos-api/src/main/kotlin/space/uranos/util/numbers/Bits.kt +++ b/uranos-api/src/main/kotlin/space/uranos/util/numbers/Bits.kt @@ -35,3 +35,10 @@ fun bitmask(vararg values: Boolean): Int { values.forEachIndexed { index, value -> mask = mask.setBit(index, value) } return mask } + +@JvmName("bitmaskFromArray") +fun bitmask(values: BooleanArray): Int { + var mask = 0 + values.forEachIndexed { index, value -> mask = mask.setBit(index, value) } + return mask +} diff --git a/uranos-api/src/main/kotlin/space/uranos/world/Chunk.kt b/uranos-api/src/main/kotlin/space/uranos/world/Chunk.kt index 28cd82d..6874176 100644 --- a/uranos-api/src/main/kotlin/space/uranos/world/Chunk.kt +++ b/uranos-api/src/main/kotlin/space/uranos/world/Chunk.kt @@ -11,8 +11,6 @@ abstract class Chunk( val world: World, val key: Key ) { - private val identifier = "Chunk(${key.x}-${key.z})" - data class Key(val x: Int, val z: Int) { companion object { /** diff --git a/uranos-api/src/main/kotlin/space/uranos/world/block/BlockCodec.kt b/uranos-api/src/main/kotlin/space/uranos/world/block/BlockCodec.kt index 387109b..1ed013b 100644 --- a/uranos-api/src/main/kotlin/space/uranos/world/block/BlockCodec.kt +++ b/uranos-api/src/main/kotlin/space/uranos/world/block/BlockCodec.kt @@ -10,6 +10,8 @@ import kotlin.reflect.full.isSubtypeOf import kotlin.reflect.full.starProjectedType import kotlin.reflect.jvm.jvmErasure +// TODO: Move this class and Attribute to uranos-packet-codecs + class BlockCodec internal constructor( blockClass: KClass, val id: Int, diff --git a/uranos-api/src/main/kotlin/space/uranos/world/block/Material.kt b/uranos-api/src/main/kotlin/space/uranos/world/block/Material.kt index 7015d2d..5cb729d 100644 --- a/uranos-api/src/main/kotlin/space/uranos/world/block/Material.kt +++ b/uranos-api/src/main/kotlin/space/uranos/world/block/Material.kt @@ -17,9 +17,9 @@ interface Material { * All materials, sorted by their numeric ID in ascending order. */ val all = GENERATED_BLOCKS - val byID = GENERATED_BLOCKS.map { it.id to it }.toMap() + val byID = GENERATED_BLOCKS.associateBy { it.id } - private val byClass = GENERATED_BLOCKS.map { it.blockClass to it }.toMap() + private val byClass = GENERATED_BLOCKS.associateBy { it.blockClass } @Suppress("UNCHECKED_CAST") fun byClass(blockClass: KClass): Material? = byClass[blockClass] as Material? diff --git a/uranos-nbt/src/main/kotlin/space/uranos/nbt/NBTType.kt b/uranos-nbt/src/main/kotlin/space/uranos/nbt/NBTType.kt index 10231f3..9338039 100644 --- a/uranos-nbt/src/main/kotlin/space/uranos/nbt/NBTType.kt +++ b/uranos-nbt/src/main/kotlin/space/uranos/nbt/NBTType.kt @@ -42,7 +42,7 @@ abstract class NBTType internal constructor(val typeClass: KClass<*>, v ) } - val byID: Map> by lazy { all.map { it.id to it }.toMap() } + val byID: Map> by lazy { all.associateBy { it.id } } @Suppress("UNCHECKED_CAST") fun of(value: T): NBTType? = all.find { it.typeClass.isInstance(value) } as NBTType? 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 index 6df3cc1..cb2c3b4 100644 --- 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 @@ -14,18 +14,18 @@ object EntityMetadataPacketCodec : OutgoingPacketCodec(0x4 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 -> { + is EntityMetadataPacket.Entry.Byte -> dst.writeByte(entry.value.toInt()) + is EntityMetadataPacket.Entry.Int -> dst.writeVarInt(entry.value) + is EntityMetadataPacket.Entry.Float -> dst.writeFloat(entry.value) + is EntityMetadataPacket.Entry.String -> dst.writeString(entry.value) + is EntityMetadataPacket.Entry.OptionalChatComponent -> { if (entry.value == null) dst.writeBoolean(false) else { dst.writeBoolean(true) dst.writeString(entry.value!!.toJson()) } } - is EntityMetadataPacket.MetadataEntry.Boolean -> dst.writeBoolean(entry.value) + is EntityMetadataPacket.Entry.Boolean -> dst.writeBoolean(entry.value) } } diff --git a/uranos-packet-codecs/src/main/kotlin/space/uranos/net/packet/play/JoinGamePacketCodec.kt b/uranos-packet-codecs/src/main/kotlin/space/uranos/net/packet/play/JoinGamePacketCodec.kt index 01380cc..cddb6a2 100644 --- a/uranos-packet-codecs/src/main/kotlin/space/uranos/net/packet/play/JoinGamePacketCodec.kt +++ b/uranos-packet-codecs/src/main/kotlin/space/uranos/net/packet/play/JoinGamePacketCodec.kt @@ -20,28 +20,31 @@ object JoinGamePacketCodec : OutgoingPacketCodec(0x24, JoinGameP dst.writeVarInt(1) dst.writeString("uranos:world") - val dimensionsByID = Uranos.dimensionRegistry.items.values.map { dimension -> - dimension.id to buildNBT { - "natural" setAsByte !dimension.compassesSpinRandomly - "ambient_light" set dimension.ambientLight - "has_skylight" setAsByte dimension.hasSkylight + val dimensionsByID = // Not known what this does - // Not known what this does - "effects" set "minecraft:overworld" + // These values do not actually change something client-sided + Uranos.dimensionRegistry.items.values.associate { dimension -> + dimension.id to buildNBT { + "natural" setAsByte !dimension.compassesSpinRandomly + "ambient_light" set dimension.ambientLight + "has_skylight" setAsByte dimension.hasSkylight - // These values do not actually change something client-sided - "ultrawarm" setAsByte false - "has_ceiling" setAsByte false - "has_raids" setAsByte true - "logical_height" set 255 - "coordinate_scale" set 1.toFloat() - "bed_works" setAsByte true - "fixed_light" setAsByte false - "infiniburn" set "" - "respawn_anchor_works" setAsByte false - "piglin_safe" setAsByte false + // Not known what this does + "effects" set "minecraft:overworld" + + // These values do not actually change something client-sided + "ultrawarm" setAsByte false + "has_ceiling" setAsByte false + "has_raids" setAsByte true + "logical_height" set 255 + "coordinate_scale" set 1.toFloat() + "bed_works" setAsByte true + "fixed_light" setAsByte false + "infiniburn" set "" + "respawn_anchor_works" setAsByte false + "piglin_safe" setAsByte false + } } - }.toMap() // TODO: Cache this val dimensions = buildNBT { diff --git a/uranos-packets/src/main/kotlin/space/uranos/net/packet/play/EntityMetadataPacket.kt b/uranos-packets/src/main/kotlin/space/uranos/net/packet/play/EntityMetadataPacket.kt index 6afbeaa..fdbab19 100644 --- a/uranos-packets/src/main/kotlin/space/uranos/net/packet/play/EntityMetadataPacket.kt +++ b/uranos-packets/src/main/kotlin/space/uranos/net/packet/play/EntityMetadataPacket.kt @@ -1,21 +1,37 @@ package space.uranos.net.packet.play +import io.netty.buffer.ByteBuf import space.uranos.chat.ChatComponent +import space.uranos.entity.metadata.ThreeAxisRotation import space.uranos.net.packet.OutgoingPacket +import java.util.UUID data class EntityMetadataPacket( val entityID: Int, - val metadata: Iterable> + val metadata: Collection> ) : OutgoingPacket() { - sealed class MetadataEntry(val typeID: kotlin.Int) { + sealed class Entry(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) + data class Byte(override val index: UByte, override val value: kotlin.Byte) : Entry(0) + data class Int(override val index: UByte, override val value: kotlin.Int) : Entry(1) + data class Float(override val index: UByte, override val value: kotlin.Float) : Entry(2) + data class String(override val index: UByte, override val value: kotlin.String) : Entry(3) + data class Chat(override val index: UByte, override val value: ChatComponent) : Entry(4) + data class OptionalChatComponent(override val index: UByte, override val value: ChatComponent?) : Entry(5) +// data class ItemStack(override val index: UByte, override val value: ItemStack) : Entry(6) + data class Boolean(override val index: UByte, override val value: kotlin.Boolean) : Entry(7) + data class Rotation(override val index: UByte, override val value: ThreeAxisRotation) : Entry(8) + data class Position(override val index: UByte, override val value: space.uranos.entity.Position) : Entry(9) + data class OptionalPosition(override val index: UByte, override val value: space.uranos.entity.Position?) : Entry(10) + data class Direction(override val index: UByte, override val value: space.uranos.Direction) : Entry(11) + data class OptionalUUID(override val index: UByte, override val value: UUID?) : Entry(12) + data class OptionalBlockID(override val index: UByte, override val value: kotlin.Int) : Entry(13) + data class NBT(override val index: UByte, override val value: ByteBuf) : Entry(14) +// data class Particle(override val index: UByte, override val value: ?) : Entry(15) +// data class VillagerData(override val index: UByte, override val value: ?) : Entry(16) + data class OptionalInt(override val index: UByte, override val value: kotlin.Int?) : Entry(17) + data class Pose(override val index: UByte, override val value: space.uranos.entity.metadata.Pose) : Entry(18) } } diff --git a/uranos-packets/src/main/kotlin/space/uranos/net/packet/play/PlayerInfoPacket.kt b/uranos-packets/src/main/kotlin/space/uranos/net/packet/play/PlayerInfoPacket.kt index 9110612..18f00c0 100644 --- a/uranos-packets/src/main/kotlin/space/uranos/net/packet/play/PlayerInfoPacket.kt +++ b/uranos-packets/src/main/kotlin/space/uranos/net/packet/play/PlayerInfoPacket.kt @@ -60,7 +60,7 @@ data class PlayerInfoPacket(val action: Action<*>) : OutgoingPacket(), Mergeable data class UpdateDisplayName(override val entries: Map) : Action() data class RemovePlayer(override val entries: Map) : Action() { - constructor(entries: List) : this(entries.map { it to Unit }.toMap()) + constructor(entries: List) : this(entries.associateWith {}) } } diff --git a/uranos-server/src/main/kotlin/space/uranos/CreateEntityInstance.kt b/uranos-server/src/main/kotlin/space/uranos/CreateEntityInstance.kt index 13b974a..b7308d5 100644 --- a/uranos-server/src/main/kotlin/space/uranos/CreateEntityInstance.kt +++ b/uranos-server/src/main/kotlin/space/uranos/CreateEntityInstance.kt @@ -3,14 +3,12 @@ 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 c4a55e0..006c2a9 100644 --- a/uranos-server/src/main/kotlin/space/uranos/UranosServer.kt +++ b/uranos-server/src/main/kotlin/space/uranos/UranosServer.kt @@ -131,7 +131,7 @@ class UranosServer internal constructor() : Server() { // Not in UranosSession.tick because this simplifies reusing the packet and the ping is not a critical information scheduler.executeRepeating(msToTicks(config.pingUpdateInterval.toMillis()), 0) { val packet = PlayerInfoPacket( - PlayerInfoPacket.Action.UpdateLatency(players.map { it.uuid to it.session.ping }.toMap()) + PlayerInfoPacket.Action.UpdateLatency(players.associate { it.uuid to it.session.ping }) ) players.forEach { diff --git a/uranos-server/src/main/kotlin/space/uranos/entity/EntityMetadataSynchronizer.kt b/uranos-server/src/main/kotlin/space/uranos/entity/EntityMetadataSynchronizer.kt index 896be5e..688a7ed 100644 --- a/uranos-server/src/main/kotlin/space/uranos/entity/EntityMetadataSynchronizer.kt +++ b/uranos-server/src/main/kotlin/space/uranos/entity/EntityMetadataSynchronizer.kt @@ -1,11 +1,58 @@ package space.uranos.entity -class EntityMetadataSynchronizer(val entity: UranosEntity) { - init { -// println(entity::class.allSuperclasses) +import space.uranos.entity.metadata.EntityMetadataFieldsTable +import space.uranos.net.packet.play.EntityMetadataPacket +import space.uranos.player.Player +import kotlin.reflect.KProperty +import kotlin.reflect.KProperty1 + +class EntityMetadataSynchronizer(val entity: Entity, private val table: EntityMetadataFieldsTable) { + private var oldValues = mutableMapOf, Any?>() + + private fun createFullPacket() = EntityMetadataPacket(entity.numericID, table.fields.map { it.value.createMetadataPacketEntry(it.key, entity) }) + + // Does two things so that only one iteration is needed. + private fun createEntryIfChangedAndUpdateOldValue(index: UByte, field: EntityMetadataFieldsTable.Field): EntityMetadataPacket.Entry<*>? { + var changed = false + + @Suppress("UNCHECKED_CAST") + (field.dependingProperties as Array>).forEach { + val value = it.get(entity) + + if (!changed) changed = value != oldValues[it] + + oldValues[it] = value + } + + return if (changed) field.createMetadataPacketEntry(index, entity) else null } - fun tick() { + fun tick(newViewers: Collection) { + if (entity.viewers.isEmpty()) return + if (oldValues.isEmpty()) { + // First invocation with at least one viewer + + createFullPacket().let { packet -> entity.viewers.forEach { it.session.send(packet) } } + + for (field in table.fields.values) { + @Suppress("UNCHECKED_CAST") + (field.dependingProperties as Array>).forEach { + oldValues[it] = it.get(entity) + } + } + } else { + if (newViewers.isNotEmpty()) createFullPacket().let { packet -> newViewers.forEach { it.session.send(packet) } } + + val entries = table.fields.mapNotNull { createEntryIfChangedAndUpdateOldValue(it.key, it.value) } + + if (entries.isNotEmpty()) { + val packet = EntityMetadataPacket(entity.numericID, entries) + + for (viewer in entity.viewers) { + if (!newViewers.contains(viewer)) viewer.session.send(packet) + } + } + } } } 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 a0480ea..8d4364b 100644 --- a/uranos-server/src/main/kotlin/space/uranos/entity/UranosEntity.kt +++ b/uranos-server/src/main/kotlin/space/uranos/entity/UranosEntity.kt @@ -9,8 +9,9 @@ import space.uranos.entity.impl.UranosPlayerEntity import space.uranos.net.packet.OutgoingPacket import space.uranos.net.packet.play.* import space.uranos.player.Player -import space.uranos.util.* import space.uranos.util.collections.WatchableSet +import space.uranos.util.createSpawnPacket +import space.uranos.util.memoized import space.uranos.util.numbers.mapToUByte import space.uranos.util.numbers.validatePitch import space.uranos.util.numbers.validateYaw @@ -64,30 +65,23 @@ sealed class UranosEntity(server: UranosServer) : Entity { } 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 val chunkKey: Chunk.Key - - protected val container = TickSynchronizationContainer() + abstract val metadataSynchronizer: EntityMetadataSynchronizer? 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) } } } - @Suppress("LeakingThis") - private val metadataSynchronizer = EntityMetadataSynchronizer(this) - open suspend fun tick() { - container.tick() - metadataSynchronizer.tick() - sendSpawnAndDestroyPackets() + metadataSynchronizer?.tick(addedViewers) + addedViewers.clear() removedViewers.clear() } @@ -107,14 +101,12 @@ sealed class UranosLivingEntity(server: UranosServer) : UranosEntity(server), Li abstract class UranosNotHasMovableHeadLivingEntity(server: UranosServer) : UranosLivingEntity(server) { override var velocity: Vector = Vector.ZERO - override var position: Position = Position.ZERO - - override val chunkKey: Chunk.Key by memoized({ position }) { Chunk.Key.from(position) } private var lastSentPosition: Position = Position.ZERO private var lastSentYaw: Float = 0f private var lastSentPitch: Float = 0f + @Suppress("DuplicatedCode") final override suspend fun tick() { val viewersWithoutAdded = viewers.subtract(addedViewers) if (viewersWithoutAdded.isNotEmpty()) { @@ -212,13 +204,13 @@ abstract class UranosHasMovableHeadLivingEntity(server: UranosServer) : UranosLi abstract class UranosObjectEntity(server: UranosServer) : UranosEntity(server), ObjectEntity { override var velocity: Vector = Vector.ZERO override var position: Position = Position.ZERO - override val chunkKey: Chunk.Key by memoized({ position }) { Chunk.Key.from(position) } private var lastSentPosition: Position = Position.ZERO private var lastSentYaw: Float = 0f private var lastSentPitch: Float = 0f + @Suppress("DuplicatedCode") final override suspend fun tick() { val viewersWithoutAdded = viewers.subtract(addedViewers) if (viewersWithoutAdded.isNotEmpty()) { @@ -246,6 +238,8 @@ class UranosPaintingEntity( ) : UranosEntity(server), PaintingEntity { override val chunkKey: Chunk.Key get() = Chunk.Key.from(topLeftLocation) + override val metadataSynchronizer: EntityMetadataSynchronizer? = null + private var lastSentTopLeftLocation: VoxelLocation = topLeftLocation override suspend fun tick() { @@ -268,7 +262,6 @@ class UranosPaintingEntity( } lastSentTopLeftLocation = topLeftLocation - super.tick() } } diff --git a/uranos-server/src/main/kotlin/space/uranos/entity/UranosMinecartEntity.kt b/uranos-server/src/main/kotlin/space/uranos/entity/UranosMinecartEntity.kt index 12d7313..c9b4c81 100644 --- a/uranos-server/src/main/kotlin/space/uranos/entity/UranosMinecartEntity.kt +++ b/uranos-server/src/main/kotlin/space/uranos/entity/UranosMinecartEntity.kt @@ -15,7 +15,7 @@ abstract class UranosMinecartEntity(server: UranosServer) : UranosObjectEntity(s validatePitch(value); field = value } - override val shakingPower: Int = 0 - override val shakingDirection: Int = 1 - override val shakingMultiplier: Float = 0f + override var shakingPower: Int = 0 + override var shakingDirection: Int = 1 + override var shakingMultiplier: Float = 0f } diff --git a/uranos-server/src/main/kotlin/space/uranos/entity/impl/UranosBatEntity.kt b/uranos-server/src/main/kotlin/space/uranos/entity/impl/UranosBatEntity.kt index 2f4360e..a75b402 100644 --- a/uranos-server/src/main/kotlin/space/uranos/entity/impl/UranosBatEntity.kt +++ b/uranos-server/src/main/kotlin/space/uranos/entity/impl/UranosBatEntity.kt @@ -2,6 +2,20 @@ package space.uranos.entity.impl import space.uranos.UranosServer import space.uranos.entity.BatEntity +import space.uranos.entity.EntityMetadataSynchronizer import space.uranos.entity.UranosHasMovableHeadLivingEntity +import space.uranos.entity.metadata.EntityMetadataFieldsTable -class UranosBatEntity(server: UranosServer) : UranosHasMovableHeadLivingEntity(server), BatEntity +class UranosBatEntity(server: UranosServer) : UranosHasMovableHeadLivingEntity(server), BatEntity { + override var hanging: Boolean = false + + override val metadataSynchronizer: EntityMetadataSynchronizer = EntityMetadataSynchronizer(this, entityMetadataFieldsTable) + + companion object { + val entityMetadataFieldsTable = EntityMetadataFieldsTable.DEFAULT.extend { + bits(15u) { + + BatEntity::hanging + } + } + } +} diff --git a/uranos-server/src/main/kotlin/space/uranos/entity/impl/UranosCowEntity.kt b/uranos-server/src/main/kotlin/space/uranos/entity/impl/UranosCowEntity.kt index 8366ae5..cdca305 100644 --- a/uranos-server/src/main/kotlin/space/uranos/entity/impl/UranosCowEntity.kt +++ b/uranos-server/src/main/kotlin/space/uranos/entity/impl/UranosCowEntity.kt @@ -1,7 +1,20 @@ package space.uranos.entity.impl import space.uranos.UranosServer +import space.uranos.entity.Ageable import space.uranos.entity.CowEntity +import space.uranos.entity.EntityMetadataSynchronizer import space.uranos.entity.UranosHasMovableHeadLivingEntity +import space.uranos.entity.metadata.EntityMetadataFieldsTable -class UranosCowEntity(server: UranosServer) : UranosHasMovableHeadLivingEntity(server), CowEntity +class UranosCowEntity(server: UranosServer) : UranosHasMovableHeadLivingEntity(server), CowEntity { + override var isBaby = false + + override val metadataSynchronizer: EntityMetadataSynchronizer = EntityMetadataSynchronizer(this, entityMetadataFieldsTable) + + companion object { + val entityMetadataFieldsTable = EntityMetadataFieldsTable.DEFAULT.extend { + required(15u, Ageable::isBaby) + } + } +} diff --git a/uranos-server/src/main/kotlin/space/uranos/entity/impl/UranosCreeperEntity.kt b/uranos-server/src/main/kotlin/space/uranos/entity/impl/UranosCreeperEntity.kt deleted file mode 100644 index 8e391ea..0000000 --- a/uranos-server/src/main/kotlin/space/uranos/entity/impl/UranosCreeperEntity.kt +++ /dev/null @@ -1,7 +0,0 @@ -package space.uranos.entity.impl - -import space.uranos.UranosServer -import space.uranos.entity.CreeperEntity -import space.uranos.entity.UranosHasMovableHeadLivingEntity - -class UranosCreeperEntity(server: UranosServer) : UranosHasMovableHeadLivingEntity(server), CreeperEntity diff --git a/uranos-server/src/main/kotlin/space/uranos/entity/impl/UranosPlayerEntity.kt b/uranos-server/src/main/kotlin/space/uranos/entity/impl/UranosPlayerEntity.kt index 3541faa..218d0a8 100644 --- a/uranos-server/src/main/kotlin/space/uranos/entity/impl/UranosPlayerEntity.kt +++ b/uranos-server/src/main/kotlin/space/uranos/entity/impl/UranosPlayerEntity.kt @@ -1,8 +1,10 @@ package space.uranos.entity.impl import space.uranos.UranosServer +import space.uranos.entity.EntityMetadataSynchronizer import space.uranos.entity.PlayerEntity import space.uranos.entity.UranosHasMovableHeadLivingEntity +import space.uranos.entity.metadata.EntityMetadataFieldsTable import space.uranos.player.Player import java.util.UUID @@ -11,4 +13,12 @@ class UranosPlayerEntity( override val player: Player ) : UranosHasMovableHeadLivingEntity(server), PlayerEntity { override val uuid: UUID = player.uuid + + override val metadataSynchronizer: EntityMetadataSynchronizer = EntityMetadataSynchronizer(this, entityMetadataFieldsTable) + + companion object { + val entityMetadataFieldsTable = EntityMetadataFieldsTable.DEFAULT.extend { + // 16 and 17 are sent externally because they depend on player.settings + } + } } 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 index 151ce2a..d82d213 100644 --- a/uranos-server/src/main/kotlin/space/uranos/entity/impl/UranosRideableMinecartEntity.kt +++ b/uranos-server/src/main/kotlin/space/uranos/entity/impl/UranosRideableMinecartEntity.kt @@ -1,11 +1,38 @@ package space.uranos.entity.impl import space.uranos.UranosServer +import space.uranos.entity.EntityMetadataSynchronizer +import space.uranos.entity.MinecartEntity import space.uranos.entity.RideableMinecartEntity import space.uranos.entity.UranosMinecartEntity +import space.uranos.entity.metadata.EntityMetadataFieldsTable +import space.uranos.net.packet.play.EntityMetadataPacket import space.uranos.world.block.Block +import space.uranos.world.block.material class UranosRideableMinecartEntity(server: UranosServer) : UranosMinecartEntity(server), RideableMinecartEntity { - override val customBlock: Block? = null - override val blockOffset: Int = 6 + override var customBlock: Block? = null + override var blockOffset: Int = 6 + + override val metadataSynchronizer: EntityMetadataSynchronizer = EntityMetadataSynchronizer(this, entityMetadataFieldsTable) + + companion object { + val entityMetadataFieldsTable = EntityMetadataFieldsTable.DEFAULT.extend { + required(7u, MinecartEntity::shakingPower) + required(8u, MinecartEntity::shakingDirection) + required(9u, MinecartEntity::shakingMultiplier) + + computed(10u, arrayOf(MinecartEntity::customBlock)) { index, entity -> + val block = (entity as UranosRideableMinecartEntity).customBlock + EntityMetadataPacket.Entry.Int(index, block?.material()?.codec?.getStateID(block) ?: 0) + } + + required(11u, MinecartEntity::blockOffset) + + computed(12u, arrayOf(MinecartEntity::customBlock)) { index, entity -> + val block = (entity as UranosRideableMinecartEntity).customBlock + EntityMetadataPacket.Entry.Boolean(index, block != null) + } + } + } } diff --git a/uranos-server/src/main/kotlin/space/uranos/entity/metadata/EntityMetadataFieldsTable.kt b/uranos-server/src/main/kotlin/space/uranos/entity/metadata/EntityMetadataFieldsTable.kt new file mode 100644 index 0000000..d23b46b --- /dev/null +++ b/uranos-server/src/main/kotlin/space/uranos/entity/metadata/EntityMetadataFieldsTable.kt @@ -0,0 +1,155 @@ +package space.uranos.entity.metadata + +import space.uranos.chat.ChatComponent +import space.uranos.entity.Entity +import space.uranos.net.packet.play.EntityMetadataPacket +import space.uranos.util.numbers.bitmask +import kotlin.reflect.KProperty1 + +class EntityMetadataFieldsTable private constructor(val fields: Map) { + sealed class Field(val dependingProperties: Array>) { + abstract fun createMetadataPacketEntry(index: UByte, entity: Entity): EntityMetadataPacket.Entry<*> + + class StaticBoolean(val value: Boolean) : Field(emptyArray()) { + override fun createMetadataPacketEntry(index: UByte, entity: Entity) = EntityMetadataPacket.Entry.Boolean(index, value) + } + + class StaticInt(val value: Int) : Field(emptyArray()) { + override fun createMetadataPacketEntry(index: UByte, entity: Entity) = EntityMetadataPacket.Entry.Int(index, value) + } + + abstract class Simple(property: KProperty1) : Field(arrayOf(property)) { + @Suppress("UNCHECKED_CAST") + val property: KProperty1 + get() = dependingProperties[0] as KProperty1 + } + + abstract class Required(property: KProperty1) : Simple(property) + abstract class Optional(property: KProperty1) : Simple(property) + + class RequiredInt(property: KProperty1) : Required(property) { + override fun createMetadataPacketEntry(index: UByte, entity: Entity) = EntityMetadataPacket.Entry.Int(index, property.get(entity)) + } + + class RequiredBoolean(property: KProperty1) : Required(property) { + override fun createMetadataPacketEntry(index: UByte, entity: Entity) = EntityMetadataPacket.Entry.Boolean(index, property.get(entity)) + } + + class RequiredFloat(property: KProperty1) : Required(property) { + override fun createMetadataPacketEntry(index: UByte, entity: Entity) = EntityMetadataPacket.Entry.Float(index, property.get(entity)) + } + + class OptionalChatComponent(property: KProperty1) : Optional(property) { + override fun createMetadataPacketEntry(index: UByte, entity: Entity) = EntityMetadataPacket.Entry.OptionalChatComponent(index, property.get(entity)) + } + + class Bits(properties: Array>, private val static: Array) : Field(arrayOf(*properties)) { + override fun createMetadataPacketEntry(index: UByte, entity: Entity): EntityMetadataPacket.Entry.Byte { + var nonStaticIndex = 0 + + return EntityMetadataPacket.Entry.Byte(index, bitmask(BooleanArray(static.size) { i -> + val staticValue = static[i] + + if (staticValue == null) { + nonStaticIndex++ + + @Suppress("UNCHECKED_CAST") + (dependingProperties as Array>)[nonStaticIndex - 1].get(entity) as Boolean + } else staticValue + }).toByte()) + } + } + + class Computed(dependingProperties: Array>, val getEntry: (index: UByte, entity: Entity) -> EntityMetadataPacket.Entry<*>) : + Field(dependingProperties) { + override fun createMetadataPacketEntry(index: UByte, entity: Entity) = getEntry(index, entity) + } + } + + fun extend(init: BuilderContext.() -> Unit) = + EntityMetadataFieldsTable(fields.plus(buildMap { BuilderContext(this).init() })) + + @BuilderMarker + @Suppress("INAPPLICABLE_JVM_NAME") + class BuilderContext(private val fields: MutableMap) { + @JvmName("requiredInt") + fun required(index: UByte, property: KProperty1) { + fields[index] = Field.RequiredInt(property) + } + + @JvmName("requiredBoolean") + fun required(index: UByte, property: KProperty1) { + fields[index] = Field.RequiredBoolean(property) + } + + @JvmName("requiredFloat") + fun required(index: UByte, property: KProperty1) { + fields[index] = Field.RequiredFloat(property) + } + + @JvmName("optionalChatComponent") + fun optional(index: UByte, property: KProperty1) { + fields[index] = Field.OptionalChatComponent(property) + } + + fun static(index: UByte, value: Boolean) { + fields[index] = Field.StaticBoolean(value) + } + + fun static(index: UByte, value: Int) { + fields[index] = Field.StaticInt(value) + } + + fun bits(index: UByte, init: BitsContext.() -> Unit) { + val properties = mutableListOf>() + val static = mutableListOf() + BitsContext(properties, static).init() + fields[index] = Field.Bits(properties.toTypedArray(), static.toTypedArray()) + } + + @BuilderMarker + class BitsContext(private val properties: MutableList>, private val static: MutableList) { + operator fun KProperty1.unaryPlus() { + properties.add(this) + static.add(null) + } + + operator fun Boolean.unaryPlus() { + static.add(this) + } + } + + fun computed( + index: UByte, + dependingProperties: Array>, + getEntry: (index: UByte, entity: Entity) -> EntityMetadataPacket.Entry<*> + ) { + fields[index] = Field.Computed(dependingProperties, getEntry) + } + } + + @DslMarker + private annotation class BuilderMarker + + companion object { + val DEFAULT = EntityMetadataFieldsTable(emptyMap()).extend { + bits(0u) { + +false // on fire + +false // crouching + +false // unused + +false // sprinting + +false // swimming + +Entity::invisible + +Entity::glowing + +false // flying with an elytra + } + + static(1u, 0) // ticks until drowning + static(2u, false) // custom name + static(3u, false) // custom name visible + static(4u, false) // silent + static(5u, false) // ignores gravity + static(6u, 0) // pose + } + } +} 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 0f0a17b..3ece942 100644 --- a/uranos-server/src/main/kotlin/space/uranos/net/LoginAndJoinProcedure.kt +++ b/uranos-server/src/main/kotlin/space/uranos/net/LoginAndJoinProcedure.kt @@ -180,20 +180,22 @@ class LoginAndJoinProcedure(val session: UranosSession) { session.sendNow(OutgoingPlayerPositionPacket(state.position.x, state.position.y, state.position.z, state.headYaw, state.headPitch)) - session.sendNow(PlayerInfoPacket(PlayerInfoPacket.Action.AddPlayer((session.server.players + player).map { - it.uuid to PlayerInfoPacket.Action.AddPlayer.Data( - it.name, - it.gameMode, - it.session.ping, - it.playerListName, - emptyMap() // TODO: Load skin - ) - }.toMap()))) + session.sendNow(PlayerInfoPacket(PlayerInfoPacket.Action.AddPlayer( + (session.server.players + player).associate { + it.uuid to PlayerInfoPacket.Action.AddPlayer.Data( + it.name, + it.gameMode, + it.session.ping, + it.playerListName, + emptyMap() // TODO: Load skin + ) + } + ))) session.sendNow( PlayerInfoPacket( PlayerInfoPacket.Action.UpdateLatency( - session.server.players.map { it.uuid to it.session.ping }.toMap() + session.server.players.associate { it.uuid to it.session.ping } ) ) ) 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 2cbbd37..841b0f1 100644 --- a/uranos-server/src/main/kotlin/space/uranos/net/PacketsAdapter.kt +++ b/uranos-server/src/main/kotlin/space/uranos/net/PacketsAdapter.kt @@ -51,6 +51,7 @@ class PacketsAdapter(val session: UranosSession) { } fun sendNextTick(packet: OutgoingPacket) { + // TODO: Allow disabling merging in the config if (packet is Mergeable) { for (i in packetsForNextTick.indices.reversed()) { val merged = packet.mergeWith(packetsForNextTick[i]) 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 02528c8..c84dbe2 100644 --- a/uranos-server/src/main/kotlin/space/uranos/net/UranosSession.kt +++ b/uranos-server/src/main/kotlin/space/uranos/net/UranosSession.kt @@ -1,13 +1,13 @@ package space.uranos.net import io.netty.buffer.ByteBuf -import kotlinx.coroutines.* +import kotlinx.coroutines.cancel +import kotlinx.coroutines.launch import space.uranos.Uranos import space.uranos.UranosServer import space.uranos.chat.ChatColor import space.uranos.chat.ChatComponent import space.uranos.chat.TextComponent -import space.uranos.event.* import space.uranos.logging.Logger import space.uranos.net.packet.OutgoingPacket import space.uranos.net.packet.Protocol