Implement entity metadata synchronization
This commit is contained in:
parent
0b1ba947a1
commit
b51ed1c291
48 changed files with 459 additions and 179 deletions
6
.idea/inspectionProfiles/Project_Default.xml
generated
Normal file
6
.idea/inspectionProfiles/Project_Default.xml
generated
Normal file
|
@ -0,0 +1,6 @@
|
|||
<component name="InspectionProjectProfileManager">
|
||||
<profile version="1.0">
|
||||
<option name="myName" value="Project Default" />
|
||||
<inspection_tool class="LeakingThis" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
</profile>
|
||||
</component>
|
|
@ -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
|
||||
|
|
|
@ -49,8 +49,8 @@ class MinecraftDataSourcesPlugin : Plugin<Project> {
|
|||
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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
) {
|
||||
|
|
|
@ -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
|
||||
) {
|
||||
|
|
|
@ -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<RideableMinecartEntity>()
|
||||
val entity = Uranos.create<CowEntity>()
|
||||
entity.position = Position(0.0, 4.0, 0.0)
|
||||
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 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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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 =
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
package space.uranos.entity
|
||||
|
||||
interface Ageable: Entity {
|
||||
var isBaby: Boolean
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
package space.uranos.entity
|
||||
|
||||
interface BatEntity : LivingEntity, HasMovableHead {
|
||||
companion object Type : BatEntityType()
|
||||
|
||||
var hanging: Boolean
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
package space.uranos.entity
|
||||
|
||||
interface CowEntity : LivingEntity, Ageable {
|
||||
companion object Type : CowEntityType()
|
||||
}
|
|
@ -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<Entity, EventBusWrapper<Entity>>() {
|
||||
override fun load(key: Entity): EventBusWrapper<Entity> = EventBusWrapper(key)
|
||||
})
|
||||
private val eventBusWrapperCache = createWeakKeysLoadingCache<Entity, EventBusWrapper<Entity>> { EventBusWrapper(it) }
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
val <T : Entity> T.events
|
||||
|
|
|
@ -15,11 +15,11 @@ interface EntityType<T : Entity> {
|
|||
*/
|
||||
val all: Collection<EntityType<*>> = ENTITY_TYPES
|
||||
|
||||
val byID: Map<String, EntityType<*>> = all.map { it.id to it }.toMap()
|
||||
val byID: Map<String, EntityType<*>> = all.associateBy { it.id }
|
||||
fun byID(id: String) = byID[id]
|
||||
|
||||
private val byInterfaceTypeMap: Map<KClass<out Entity>, EntityType<*>> =
|
||||
ENTITY_TYPES.map { it.interfaceType to it }.toMap()
|
||||
ENTITY_TYPES.associateBy { it.interfaceType }
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
fun <T : Entity> byInterfaceType(interfaceType: KClass<T>): EntityType<T> =
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
package space.uranos.entity
|
||||
|
||||
interface LivingEntity : Entity, Mobile {
|
||||
// potion effects
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
package space.uranos.entity
|
||||
|
||||
interface ObjectEntity : Entity, Mobile {
|
||||
|
||||
}
|
||||
interface ObjectEntity : Entity, Mobile
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
package space.uranos.entity.metadata
|
||||
|
||||
enum class Pose {
|
||||
STANDING,
|
||||
FLYING,
|
||||
LYING,
|
||||
SWIMMING,
|
||||
SPIN_ATTACKING,
|
||||
SNEAKING,
|
||||
DYING
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
package space.uranos.entity.metadata
|
||||
|
||||
data class ThreeAxisRotation(val x: Float, val y: Float, val z: Float)
|
|
@ -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<IncomingPacketCodec<*>>().map { it.id to it }.toMap()
|
||||
private val codecsByPacketType = codecs.associateBy { it.dataType }
|
||||
val incomingPacketCodecsByID = codecs.filterIsInstance<IncomingPacketCodec<*>>().associateBy { it.id }
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
fun <T : Packet> getCodecByType(type: KClass<T>): PacketCodec<T> = codecsByPacketType[type] as PacketCodec<T>?
|
||||
|
|
|
@ -5,5 +5,5 @@ object TagRegistry {
|
|||
val tagsByType: Map<Tag.Type, List<Tag>> = tags.groupBy { it.type }
|
||||
|
||||
val tagsByNameByType: Map<Tag.Type, Map<String, Tag>> =
|
||||
tagsByType.mapValues { (_, tags) -> tags.map { it.name to it }.toMap() }
|
||||
tagsByType.mapValues { (_, tags) -> tags.associateBy { it.name } }
|
||||
}
|
||||
|
|
|
@ -20,7 +20,7 @@ class TickSynchronizationContainer {
|
|||
onChange: ((value: T) -> Unit)? = null,
|
||||
onTick: suspend (value: T) -> Unit
|
||||
): Delegate<T> =
|
||||
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 <T> invoke(
|
||||
initialValue: T,
|
||||
|
|
|
@ -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 <K, V> createWeakKeysLoadingCache(create: (key: K) -> V): LoadingCache<K, V> {
|
||||
return CacheBuilder.newBuilder().weakKeys().build(object : CacheLoader<K, V>() {
|
||||
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 <K, V> createWeakValuesLoadingCache(create: (key: K) -> V): LoadingCache<K, V> {
|
||||
return CacheBuilder.newBuilder().weakValues().build(object : CacheLoader<K, V>() {
|
||||
override fun load(key: K): V = create(key)
|
||||
})
|
||||
}
|
|
@ -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 <K, V> createWeakValuesLoadingCache(loader: (key: K) -> V): LoadingCache<K, V> {
|
||||
return CacheBuilder
|
||||
.newBuilder()
|
||||
.weakValues()
|
||||
.build(object : CacheLoader<K, V>() {
|
||||
override fun load(key: K): V = loader(key)
|
||||
})
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
/**
|
||||
|
|
|
@ -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<T : Block> internal constructor(
|
||||
blockClass: KClass<T>,
|
||||
val id: Int,
|
||||
|
|
|
@ -17,9 +17,9 @@ interface Material<T : Block> {
|
|||
* 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 <T : Block> byClass(blockClass: KClass<T>): Material<T>? = byClass[blockClass] as Material<T>?
|
||||
|
|
|
@ -42,7 +42,7 @@ abstract class NBTType<T : Any> internal constructor(val typeClass: KClass<*>, v
|
|||
)
|
||||
}
|
||||
|
||||
val byID: Map<Byte, NBTType<*>> by lazy { all.map { it.id to it }.toMap() }
|
||||
val byID: Map<Byte, NBTType<*>> by lazy { all.associateBy { it.id } }
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
fun <T : Any> of(value: T): NBTType<T>? = all.find { it.typeClass.isInstance(value) } as NBTType<T>?
|
||||
|
|
|
@ -14,18 +14,18 @@ object EntityMetadataPacketCodec : OutgoingPacketCodec<EntityMetadataPacket>(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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -20,7 +20,10 @@ object JoinGamePacketCodec : OutgoingPacketCodec<JoinGamePacket>(0x24, JoinGameP
|
|||
dst.writeVarInt(1)
|
||||
dst.writeString("uranos:world")
|
||||
|
||||
val dimensionsByID = Uranos.dimensionRegistry.items.values.map { dimension ->
|
||||
val dimensionsByID = // Not known what this does
|
||||
|
||||
// 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
|
||||
|
@ -41,7 +44,7 @@ object JoinGamePacketCodec : OutgoingPacketCodec<JoinGamePacket>(0x24, JoinGameP
|
|||
"respawn_anchor_works" setAsByte false
|
||||
"piglin_safe" setAsByte false
|
||||
}
|
||||
}.toMap()
|
||||
}
|
||||
|
||||
// TODO: Cache this
|
||||
val dimensions = buildNBT {
|
||||
|
|
|
@ -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<MetadataEntry<*>>
|
||||
val metadata: Collection<Entry<*>>
|
||||
) : OutgoingPacket() {
|
||||
sealed class MetadataEntry<T>(val typeID: kotlin.Int) {
|
||||
sealed class Entry<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)
|
||||
data class Byte(override val index: UByte, override val value: kotlin.Byte) : Entry<kotlin.Byte>(0)
|
||||
data class Int(override val index: UByte, override val value: kotlin.Int) : Entry<kotlin.Int>(1)
|
||||
data class Float(override val index: UByte, override val value: kotlin.Float) : Entry<kotlin.Float>(2)
|
||||
data class String(override val index: UByte, override val value: kotlin.String) : Entry<kotlin.String>(3)
|
||||
data class Chat(override val index: UByte, override val value: ChatComponent) : Entry<ChatComponent>(4)
|
||||
data class OptionalChatComponent(override val index: UByte, override val value: ChatComponent?) : Entry<ChatComponent?>(5)
|
||||
// data class ItemStack(override val index: UByte, override val value: ItemStack) : Entry<ItemStack>(6)
|
||||
data class Boolean(override val index: UByte, override val value: kotlin.Boolean) : Entry<kotlin.Boolean>(7)
|
||||
data class Rotation(override val index: UByte, override val value: ThreeAxisRotation) : Entry<ThreeAxisRotation>(8)
|
||||
data class Position(override val index: UByte, override val value: space.uranos.entity.Position) : Entry<space.uranos.entity.Position>(9)
|
||||
data class OptionalPosition(override val index: UByte, override val value: space.uranos.entity.Position?) : Entry<space.uranos.entity.Position?>(10)
|
||||
data class Direction(override val index: UByte, override val value: space.uranos.Direction) : Entry<space.uranos.Direction>(11)
|
||||
data class OptionalUUID(override val index: UByte, override val value: UUID?) : Entry<UUID?>(12)
|
||||
data class OptionalBlockID(override val index: UByte, override val value: kotlin.Int) : Entry<kotlin.Int>(13)
|
||||
data class NBT(override val index: UByte, override val value: ByteBuf) : Entry<ByteBuf>(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<kotlin.Int?>(17)
|
||||
data class Pose(override val index: UByte, override val value: space.uranos.entity.metadata.Pose) : Entry<space.uranos.entity.metadata.Pose>(18)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -60,7 +60,7 @@ data class PlayerInfoPacket(val action: Action<*>) : OutgoingPacket(), Mergeable
|
|||
data class UpdateDisplayName(override val entries: Map<UUID, TextComponent?>) : Action<TextComponent?>()
|
||||
|
||||
data class RemovePlayer(override val entries: Map<UUID, Unit>) : Action<Unit>() {
|
||||
constructor(entries: List<UUID>) : this(entries.map { it to Unit }.toMap())
|
||||
constructor(entries: List<UUID>) : this(entries.associateWith {})
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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 <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
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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<KProperty<*>, 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<KProperty1<Entity, *>>).forEach {
|
||||
val value = it.get(entity)
|
||||
|
||||
if (!changed) changed = value != oldValues[it]
|
||||
|
||||
oldValues[it] = value
|
||||
}
|
||||
|
||||
fun tick() {
|
||||
return if (changed) field.createMetadataPacketEntry(index, entity) else null
|
||||
}
|
||||
|
||||
fun tick(newViewers: Collection<Player>) {
|
||||
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<KProperty1<Entity, *>>).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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<UByte, Field>) {
|
||||
sealed class Field(val dependingProperties: Array<KProperty1<out Entity, *>>) {
|
||||
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<T>(property: KProperty1<out Entity, T>) : Field(arrayOf(property)) {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
val property: KProperty1<Entity, T>
|
||||
get() = dependingProperties[0] as KProperty1<Entity, T>
|
||||
}
|
||||
|
||||
abstract class Required<T : Any>(property: KProperty1<out Entity, T>) : Simple<T>(property)
|
||||
abstract class Optional<T : Any>(property: KProperty1<out Entity, T?>) : Simple<T?>(property)
|
||||
|
||||
class RequiredInt(property: KProperty1<out Entity, Int>) : Required<Int>(property) {
|
||||
override fun createMetadataPacketEntry(index: UByte, entity: Entity) = EntityMetadataPacket.Entry.Int(index, property.get(entity))
|
||||
}
|
||||
|
||||
class RequiredBoolean(property: KProperty1<out Entity, Boolean>) : Required<Boolean>(property) {
|
||||
override fun createMetadataPacketEntry(index: UByte, entity: Entity) = EntityMetadataPacket.Entry.Boolean(index, property.get(entity))
|
||||
}
|
||||
|
||||
class RequiredFloat(property: KProperty1<out Entity, Float>) : Required<Float>(property) {
|
||||
override fun createMetadataPacketEntry(index: UByte, entity: Entity) = EntityMetadataPacket.Entry.Float(index, property.get(entity))
|
||||
}
|
||||
|
||||
class OptionalChatComponent(property: KProperty1<out Entity, ChatComponent?>) : Optional<ChatComponent>(property) {
|
||||
override fun createMetadataPacketEntry(index: UByte, entity: Entity) = EntityMetadataPacket.Entry.OptionalChatComponent(index, property.get(entity))
|
||||
}
|
||||
|
||||
class Bits(properties: Array<KProperty1<out Entity, Boolean>>, private val static: Array<Boolean?>) : 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<KProperty1<Entity, *>>)[nonStaticIndex - 1].get(entity) as Boolean
|
||||
} else staticValue
|
||||
}).toByte())
|
||||
}
|
||||
}
|
||||
|
||||
class Computed(dependingProperties: Array<KProperty1<out Entity, *>>, 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<UByte, Field>) {
|
||||
@JvmName("requiredInt")
|
||||
fun required(index: UByte, property: KProperty1<out Entity, Int>) {
|
||||
fields[index] = Field.RequiredInt(property)
|
||||
}
|
||||
|
||||
@JvmName("requiredBoolean")
|
||||
fun required(index: UByte, property: KProperty1<out Entity, Boolean>) {
|
||||
fields[index] = Field.RequiredBoolean(property)
|
||||
}
|
||||
|
||||
@JvmName("requiredFloat")
|
||||
fun required(index: UByte, property: KProperty1<out Entity, Float>) {
|
||||
fields[index] = Field.RequiredFloat(property)
|
||||
}
|
||||
|
||||
@JvmName("optionalChatComponent")
|
||||
fun optional(index: UByte, property: KProperty1<out Entity, ChatComponent?>) {
|
||||
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<KProperty1<out Entity, Boolean>>()
|
||||
val static = mutableListOf<Boolean?>()
|
||||
BitsContext(properties, static).init()
|
||||
fields[index] = Field.Bits(properties.toTypedArray(), static.toTypedArray())
|
||||
}
|
||||
|
||||
@BuilderMarker
|
||||
class BitsContext(private val properties: MutableList<KProperty1<out Entity, Boolean>>, private val static: MutableList<Boolean?>) {
|
||||
operator fun KProperty1<out Entity, Boolean>.unaryPlus() {
|
||||
properties.add(this)
|
||||
static.add(null)
|
||||
}
|
||||
|
||||
operator fun Boolean.unaryPlus() {
|
||||
static.add(this)
|
||||
}
|
||||
}
|
||||
|
||||
fun computed(
|
||||
index: UByte,
|
||||
dependingProperties: Array<KProperty1<out Entity, *>>,
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
|
@ -180,7 +180,8 @@ 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 {
|
||||
session.sendNow(PlayerInfoPacket(PlayerInfoPacket.Action.AddPlayer(
|
||||
(session.server.players + player).associate {
|
||||
it.uuid to PlayerInfoPacket.Action.AddPlayer.Data(
|
||||
it.name,
|
||||
it.gameMode,
|
||||
|
@ -188,12 +189,13 @@ class LoginAndJoinProcedure(val session: UranosSession) {
|
|||
it.playerListName,
|
||||
emptyMap() // TODO: Load skin
|
||||
)
|
||||
}.toMap())))
|
||||
}
|
||||
)))
|
||||
|
||||
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 }
|
||||
)
|
||||
)
|
||||
)
|
||||
|
|
|
@ -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])
|
||||
|
|
|
@ -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
|
||||
|
|
Reference in a new issue