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.
|
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.
|
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
|
The most important thing for Uranos is
|
||||||
|
@ -10,7 +10,6 @@ The most important thing for Uranos is
|
||||||
|
|
||||||
## Milestones
|
## Milestones
|
||||||
|
|
||||||
- Players can see entities
|
|
||||||
- Players can see other players
|
- Players can see other players
|
||||||
- Players can send and receive chat messages
|
- Players can send and receive chat messages
|
||||||
- Players can see Titles
|
- Players can see Titles
|
||||||
|
@ -24,6 +23,7 @@ The most important thing for Uranos is
|
||||||
- Command framework + permissions
|
- Command framework + permissions
|
||||||
- Commands can be sent from the console
|
- Commands can be sent from the console
|
||||||
- Players can be teleported between worlds
|
- Players can be teleported between worlds
|
||||||
|
- All entities are implemented with all metadata
|
||||||
- Entity AI framework
|
- Entity AI framework
|
||||||
- Scoreboards + Teams
|
- Scoreboards + Teams
|
||||||
- Crafting
|
- Crafting
|
||||||
|
|
|
@ -49,8 +49,8 @@ class MinecraftDataSourcesPlugin : Plugin<Project> {
|
||||||
BlocksAndMaterialGenerator(workingDir, outputDir, sourcesDir).generate()
|
BlocksAndMaterialGenerator(workingDir, outputDir, sourcesDir).generate()
|
||||||
TagsGenerator(workingDir, outputDir).generate()
|
TagsGenerator(workingDir, outputDir).generate()
|
||||||
EntitiesGenerator(workingDir, outputDir, sourcesDir).generate()
|
EntitiesGenerator(workingDir, outputDir, sourcesDir).generate()
|
||||||
FluidIDMapGenerator(workingDir, outputDir, registries).generate()
|
FluidIDMapGenerator(outputDir, registries).generate()
|
||||||
ItemTypeEnumGenerator(workingDir, outputDir, registries).generate()
|
ItemTypeEnumGenerator(outputDir, registries).generate()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,7 +7,6 @@ import space.uranos.mdsp.JsonAny
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
class FluidIDMapGenerator(
|
class FluidIDMapGenerator(
|
||||||
private val workingDir: File,
|
|
||||||
private val outputDir: File,
|
private val outputDir: File,
|
||||||
private val registries: JsonAny
|
private val registries: JsonAny
|
||||||
) {
|
) {
|
||||||
|
|
|
@ -8,7 +8,6 @@ import space.uranos.mdsp.util.ConstructorPropertiesHelper
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
class ItemTypeEnumGenerator(
|
class ItemTypeEnumGenerator(
|
||||||
private val workingDir: File,
|
|
||||||
private val outputDir: File,
|
private val outputDir: File,
|
||||||
private val registries: JsonAny
|
private val registries: JsonAny
|
||||||
) {
|
) {
|
||||||
|
|
|
@ -3,25 +3,22 @@ package space.uranos.testplugin
|
||||||
import space.uranos.Uranos
|
import space.uranos.Uranos
|
||||||
import space.uranos.chat.ChatColor
|
import space.uranos.chat.ChatColor
|
||||||
import space.uranos.chat.TextComponent
|
import space.uranos.chat.TextComponent
|
||||||
|
import space.uranos.entity.CowEntity
|
||||||
import space.uranos.entity.Position
|
import space.uranos.entity.Position
|
||||||
import space.uranos.entity.RideableMinecartEntity
|
|
||||||
import space.uranos.net.ServerListInfo
|
import space.uranos.net.ServerListInfo
|
||||||
import space.uranos.net.event.ServerListInfoRequestEvent
|
import space.uranos.net.event.ServerListInfoRequestEvent
|
||||||
import space.uranos.net.event.SessionAfterLoginEvent
|
import space.uranos.net.event.SessionAfterLoginEvent
|
||||||
import space.uranos.net.packet.play.EntityMetadataPacket
|
|
||||||
import space.uranos.player.GameMode
|
import space.uranos.player.GameMode
|
||||||
import space.uranos.player.event.PlayerReadyEvent
|
|
||||||
import space.uranos.plugin.Plugin
|
import space.uranos.plugin.Plugin
|
||||||
import space.uranos.testplugin.anvil.AnvilWorld
|
import space.uranos.testplugin.anvil.AnvilWorld
|
||||||
import space.uranos.util.RGBColor
|
import space.uranos.util.RGBColor
|
||||||
import space.uranos.util.runInServerThread
|
|
||||||
import space.uranos.util.secondsToTicks
|
import space.uranos.util.secondsToTicks
|
||||||
import space.uranos.world.*
|
import space.uranos.world.*
|
||||||
import space.uranos.world.block.GreenWoolBlock
|
import space.uranos.world.block.GreenWoolBlock
|
||||||
import space.uranos.world.block.RedWoolBlock
|
import space.uranos.world.block.RedWoolBlock
|
||||||
|
|
||||||
class TestPlugin : Plugin("Test", "1.0.0") {
|
class TestPlugin : Plugin("Test", "1.0.0") {
|
||||||
fun enableOptifineFix() {
|
private fun enableOptifineFix() {
|
||||||
Uranos.biomeRegistry.register(
|
Uranos.biomeRegistry.register(
|
||||||
Biome(
|
Biome(
|
||||||
"minecraft:swamp",
|
"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.position = Position(0.0, 4.0, 0.0)
|
||||||
entity.setWorld(world)
|
entity.setWorld(world)
|
||||||
|
|
||||||
Uranos.eventBus.on<PlayerReadyEvent> {
|
Uranos.scheduler.executeRepeating(20) {
|
||||||
it.player.session.send(EntityMetadataPacket(entity.numericID, listOf(
|
entity.isBaby = !entity.isBaby
|
||||||
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
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,7 +9,6 @@ import space.uranos.util.newSingleThreadDispatcher
|
||||||
import space.uranos.world.Chunk
|
import space.uranos.world.Chunk
|
||||||
import space.uranos.world.Dimension
|
import space.uranos.world.Dimension
|
||||||
import space.uranos.world.World
|
import space.uranos.world.World
|
||||||
import java.util.*
|
|
||||||
|
|
||||||
class AnvilWorld(
|
class AnvilWorld(
|
||||||
override val dimension: Dimension,
|
override val dimension: Dimension,
|
||||||
|
|
|
@ -1,10 +1,8 @@
|
||||||
package space.uranos
|
package space.uranos
|
||||||
|
|
||||||
enum class CardinalDirection(private val direction: Direction) {
|
enum class CardinalDirection(val direction: Direction) {
|
||||||
NORTH(Direction.NORTH),
|
NORTH(Direction.NORTH),
|
||||||
EAST(Direction.EAST),
|
EAST(Direction.EAST),
|
||||||
SOUTH(Direction.SOUTH),
|
SOUTH(Direction.SOUTH),
|
||||||
WEST(Direction.WEST);
|
WEST(Direction.WEST);
|
||||||
|
|
||||||
fun asDirection() = direction
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -47,7 +47,7 @@ sealed class ChatColor(val stringRepresentation: String) {
|
||||||
PINK,
|
PINK,
|
||||||
YELLOW,
|
YELLOW,
|
||||||
WHITE
|
WHITE
|
||||||
).map { it.stringRepresentation to it }.toMap()
|
).associateBy { it.stringRepresentation }
|
||||||
}
|
}
|
||||||
|
|
||||||
fun fromString(value: String): ChatColor =
|
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
|
package space.uranos.entity
|
||||||
|
|
||||||
import com.google.common.cache.CacheBuilder
|
|
||||||
import com.google.common.cache.CacheLoader
|
|
||||||
import space.uranos.event.EventBusWrapper
|
import space.uranos.event.EventBusWrapper
|
||||||
import space.uranos.player.Player
|
import space.uranos.player.Player
|
||||||
|
import space.uranos.util.collections.createWeakKeysLoadingCache
|
||||||
import space.uranos.world.Chunk
|
import space.uranos.world.Chunk
|
||||||
import space.uranos.world.World
|
import space.uranos.world.World
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
|
@ -39,16 +38,9 @@ interface Entity {
|
||||||
*/
|
*/
|
||||||
var visibleToNewPlayers: Boolean
|
var visibleToNewPlayers: Boolean
|
||||||
|
|
||||||
var glowing: Boolean // even experience orbs can glow
|
var glowing: Boolean // It seems like every entity can glow
|
||||||
|
|
||||||
var invisible: Boolean
|
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")
|
fun Entity.getWorldOrFail() = world ?: throw IllegalStateException("This entity has not been spawned")
|
||||||
|
|
||||||
private val eventBusWrapperCache = CacheBuilder.newBuilder()
|
private val eventBusWrapperCache = createWeakKeysLoadingCache<Entity, EventBusWrapper<Entity>> { EventBusWrapper(it) }
|
||||||
.weakKeys()
|
|
||||||
.build(object : CacheLoader<Entity, EventBusWrapper<Entity>>() {
|
|
||||||
override fun load(key: Entity): EventBusWrapper<Entity> = EventBusWrapper(key)
|
|
||||||
})
|
|
||||||
|
|
||||||
@Suppress("UNCHECKED_CAST")
|
@Suppress("UNCHECKED_CAST")
|
||||||
val <T : Entity> T.events
|
val <T : Entity> T.events
|
||||||
|
|
|
@ -15,11 +15,11 @@ interface EntityType<T : Entity> {
|
||||||
*/
|
*/
|
||||||
val all: Collection<EntityType<*>> = ENTITY_TYPES
|
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]
|
fun byID(id: String) = byID[id]
|
||||||
|
|
||||||
private val byInterfaceTypeMap: Map<KClass<out Entity>, EntityType<*>> =
|
private val byInterfaceTypeMap: Map<KClass<out Entity>, EntityType<*>> =
|
||||||
ENTITY_TYPES.map { it.interfaceType to it }.toMap()
|
ENTITY_TYPES.associateBy { it.interfaceType }
|
||||||
|
|
||||||
@Suppress("UNCHECKED_CAST")
|
@Suppress("UNCHECKED_CAST")
|
||||||
fun <T : Entity> byInterfaceType(interfaceType: KClass<T>): EntityType<T> =
|
fun <T : Entity> byInterfaceType(interfaceType: KClass<T>): EntityType<T> =
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
package space.uranos.entity
|
package space.uranos.entity
|
||||||
|
|
||||||
interface LivingEntity : Entity, Mobile {
|
interface LivingEntity : Entity, Mobile {
|
||||||
|
// potion effects
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,22 +6,22 @@ interface MinecartEntity: ObjectEntity, YawRotatable, PitchRotatable {
|
||||||
/**
|
/**
|
||||||
* Default is `0`.
|
* Default is `0`.
|
||||||
*/
|
*/
|
||||||
val shakingPower: Int
|
var shakingPower: Int
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Default is `1`.
|
* Default is `1`.
|
||||||
*/
|
*/
|
||||||
val shakingDirection: Int
|
var shakingDirection: Int
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Default is `0.0`.
|
* 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.
|
* 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
|
package space.uranos.entity
|
||||||
|
|
||||||
interface ObjectEntity : Entity, Mobile {
|
interface ObjectEntity : Entity, Mobile
|
||||||
|
|
||||||
}
|
|
||||||
|
|
|
@ -10,7 +10,7 @@ interface PaintingEntity : Entity {
|
||||||
val facing: CardinalDirection
|
val facing: CardinalDirection
|
||||||
val motive: PaintingMotive
|
val motive: PaintingMotive
|
||||||
|
|
||||||
companion object Type : AreaEffectCloudEntityType()
|
companion object Type : PaintingEntityType()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun PaintingEntity.calculateCenterLocation() = topLeftLocation.copy(
|
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<*>) {
|
abstract class Protocol constructor(val name: String, vararg codecs: PacketCodec<*>) {
|
||||||
val codecs = codecs.toSet()
|
val codecs = codecs.toSet()
|
||||||
private val codecsByPacketType = codecs.map { it.dataType to it }.toMap()
|
private val codecsByPacketType = codecs.associateBy { it.dataType }
|
||||||
val incomingPacketCodecsByID = codecs.filterIsInstance<IncomingPacketCodec<*>>().map { it.id to it }.toMap()
|
val incomingPacketCodecsByID = codecs.filterIsInstance<IncomingPacketCodec<*>>().associateBy { it.id }
|
||||||
|
|
||||||
@Suppress("UNCHECKED_CAST")
|
@Suppress("UNCHECKED_CAST")
|
||||||
fun <T : Packet> getCodecByType(type: KClass<T>): PacketCodec<T> = codecsByPacketType[type] as PacketCodec<T>?
|
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 tagsByType: Map<Tag.Type, List<Tag>> = tags.groupBy { it.type }
|
||||||
|
|
||||||
val tagsByNameByType: Map<Tag.Type, Map<String, Tag>> =
|
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,
|
onChange: ((value: T) -> Unit)? = null,
|
||||||
onTick: suspend (value: T) -> Unit
|
onTick: suspend (value: T) -> Unit
|
||||||
): Delegate<T> =
|
): 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(
|
operator fun <T> invoke(
|
||||||
initialValue: T,
|
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) }
|
values.forEachIndexed { index, value -> mask = mask.setBit(index, value) }
|
||||||
return mask
|
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 world: World,
|
||||||
val key: Key
|
val key: Key
|
||||||
) {
|
) {
|
||||||
private val identifier = "Chunk(${key.x}-${key.z})"
|
|
||||||
|
|
||||||
data class Key(val x: Int, val z: Int) {
|
data class Key(val x: Int, val z: Int) {
|
||||||
companion object {
|
companion object {
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -10,6 +10,8 @@ import kotlin.reflect.full.isSubtypeOf
|
||||||
import kotlin.reflect.full.starProjectedType
|
import kotlin.reflect.full.starProjectedType
|
||||||
import kotlin.reflect.jvm.jvmErasure
|
import kotlin.reflect.jvm.jvmErasure
|
||||||
|
|
||||||
|
// TODO: Move this class and Attribute to uranos-packet-codecs
|
||||||
|
|
||||||
class BlockCodec<T : Block> internal constructor(
|
class BlockCodec<T : Block> internal constructor(
|
||||||
blockClass: KClass<T>,
|
blockClass: KClass<T>,
|
||||||
val id: Int,
|
val id: Int,
|
||||||
|
|
|
@ -17,9 +17,9 @@ interface Material<T : Block> {
|
||||||
* All materials, sorted by their numeric ID in ascending order.
|
* All materials, sorted by their numeric ID in ascending order.
|
||||||
*/
|
*/
|
||||||
val all = GENERATED_BLOCKS
|
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")
|
@Suppress("UNCHECKED_CAST")
|
||||||
fun <T : Block> byClass(blockClass: KClass<T>): Material<T>? = byClass[blockClass] as Material<T>?
|
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")
|
@Suppress("UNCHECKED_CAST")
|
||||||
fun <T : Any> of(value: T): NBTType<T>? = all.find { it.typeClass.isInstance(value) } as NBTType<T>?
|
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)
|
dst.writeVarInt(entry.typeID)
|
||||||
|
|
||||||
when(entry) {
|
when(entry) {
|
||||||
is EntityMetadataPacket.MetadataEntry.Byte -> dst.writeByte(entry.value.toInt())
|
is EntityMetadataPacket.Entry.Byte -> dst.writeByte(entry.value.toInt())
|
||||||
is EntityMetadataPacket.MetadataEntry.Int -> dst.writeVarInt(entry.value)
|
is EntityMetadataPacket.Entry.Int -> dst.writeVarInt(entry.value)
|
||||||
is EntityMetadataPacket.MetadataEntry.Float -> dst.writeFloat(entry.value)
|
is EntityMetadataPacket.Entry.Float -> dst.writeFloat(entry.value)
|
||||||
is EntityMetadataPacket.MetadataEntry.String -> dst.writeString(entry.value)
|
is EntityMetadataPacket.Entry.String -> dst.writeString(entry.value)
|
||||||
is EntityMetadataPacket.MetadataEntry.OptChat -> {
|
is EntityMetadataPacket.Entry.OptionalChatComponent -> {
|
||||||
if (entry.value == null) dst.writeBoolean(false)
|
if (entry.value == null) dst.writeBoolean(false)
|
||||||
else {
|
else {
|
||||||
dst.writeBoolean(true)
|
dst.writeBoolean(true)
|
||||||
dst.writeString(entry.value!!.toJson())
|
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.writeVarInt(1)
|
||||||
dst.writeString("uranos:world")
|
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 {
|
dimension.id to buildNBT {
|
||||||
"natural" setAsByte !dimension.compassesSpinRandomly
|
"natural" setAsByte !dimension.compassesSpinRandomly
|
||||||
"ambient_light" set dimension.ambientLight
|
"ambient_light" set dimension.ambientLight
|
||||||
|
@ -41,7 +44,7 @@ object JoinGamePacketCodec : OutgoingPacketCodec<JoinGamePacket>(0x24, JoinGameP
|
||||||
"respawn_anchor_works" setAsByte false
|
"respawn_anchor_works" setAsByte false
|
||||||
"piglin_safe" setAsByte false
|
"piglin_safe" setAsByte false
|
||||||
}
|
}
|
||||||
}.toMap()
|
}
|
||||||
|
|
||||||
// TODO: Cache this
|
// TODO: Cache this
|
||||||
val dimensions = buildNBT {
|
val dimensions = buildNBT {
|
||||||
|
|
|
@ -1,21 +1,37 @@
|
||||||
package space.uranos.net.packet.play
|
package space.uranos.net.packet.play
|
||||||
|
|
||||||
|
import io.netty.buffer.ByteBuf
|
||||||
import space.uranos.chat.ChatComponent
|
import space.uranos.chat.ChatComponent
|
||||||
|
import space.uranos.entity.metadata.ThreeAxisRotation
|
||||||
import space.uranos.net.packet.OutgoingPacket
|
import space.uranos.net.packet.OutgoingPacket
|
||||||
|
import java.util.UUID
|
||||||
|
|
||||||
data class EntityMetadataPacket(
|
data class EntityMetadataPacket(
|
||||||
val entityID: Int,
|
val entityID: Int,
|
||||||
val metadata: Iterable<MetadataEntry<*>>
|
val metadata: Collection<Entry<*>>
|
||||||
) : OutgoingPacket() {
|
) : OutgoingPacket() {
|
||||||
sealed class MetadataEntry<T>(val typeID: kotlin.Int) {
|
sealed class Entry<T>(val typeID: kotlin.Int) {
|
||||||
abstract val index: UByte
|
abstract val index: UByte
|
||||||
abstract val value: T
|
abstract val value: T
|
||||||
|
|
||||||
data class Byte(override val index: UByte, override val value: kotlin.Byte) : MetadataEntry<kotlin.Byte>(0)
|
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) : MetadataEntry<kotlin.Int>(1)
|
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) : MetadataEntry<kotlin.Float>(2)
|
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) : MetadataEntry<kotlin.String>(3)
|
data class String(override val index: UByte, override val value: kotlin.String) : Entry<kotlin.String>(3)
|
||||||
data class OptChat(override val index: UByte, override val value: ChatComponent?) : MetadataEntry<ChatComponent?>(5)
|
data class Chat(override val index: UByte, override val value: ChatComponent) : Entry<ChatComponent>(4)
|
||||||
data class Boolean(override val index: UByte, override val value: kotlin.Boolean) : MetadataEntry<kotlin.Boolean>(7)
|
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 UpdateDisplayName(override val entries: Map<UUID, TextComponent?>) : Action<TextComponent?>()
|
||||||
|
|
||||||
data class RemovePlayer(override val entries: Map<UUID, Unit>) : Action<Unit>() {
|
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.*
|
||||||
import space.uranos.entity.impl.UranosBatEntity
|
import space.uranos.entity.impl.UranosBatEntity
|
||||||
import space.uranos.entity.impl.UranosCowEntity
|
import space.uranos.entity.impl.UranosCowEntity
|
||||||
import space.uranos.entity.impl.UranosCreeperEntity
|
|
||||||
import space.uranos.entity.impl.UranosRideableMinecartEntity
|
import space.uranos.entity.impl.UranosRideableMinecartEntity
|
||||||
|
|
||||||
@Suppress("UNCHECKED_CAST")
|
@Suppress("UNCHECKED_CAST")
|
||||||
fun <T : Entity> createEntityInstance(server: UranosServer, type: EntityType<T>): T = when (type) {
|
fun <T : Entity> createEntityInstance(server: UranosServer, type: EntityType<T>): T = when (type) {
|
||||||
CowEntity -> UranosCowEntity(server)
|
CowEntity -> UranosCowEntity(server)
|
||||||
BatEntity -> UranosBatEntity(server)
|
BatEntity -> UranosBatEntity(server)
|
||||||
CreeperEntity -> UranosCreeperEntity(server)
|
|
||||||
RideableMinecartEntity -> UranosRideableMinecartEntity(server)
|
RideableMinecartEntity -> UranosRideableMinecartEntity(server)
|
||||||
else -> throw IllegalArgumentException("Entities of this type cannot be created with this function")
|
else -> throw IllegalArgumentException("Entities of this type cannot be created with this function")
|
||||||
} as T
|
} 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
|
// 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) {
|
scheduler.executeRepeating(msToTicks(config.pingUpdateInterval.toMillis()), 0) {
|
||||||
val packet = PlayerInfoPacket(
|
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 {
|
players.forEach {
|
||||||
|
|
|
@ -1,11 +1,58 @@
|
||||||
package space.uranos.entity
|
package space.uranos.entity
|
||||||
|
|
||||||
class EntityMetadataSynchronizer(val entity: UranosEntity) {
|
import space.uranos.entity.metadata.EntityMetadataFieldsTable
|
||||||
init {
|
import space.uranos.net.packet.play.EntityMetadataPacket
|
||||||
// println(entity::class.allSuperclasses)
|
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.OutgoingPacket
|
||||||
import space.uranos.net.packet.play.*
|
import space.uranos.net.packet.play.*
|
||||||
import space.uranos.player.Player
|
import space.uranos.player.Player
|
||||||
import space.uranos.util.*
|
|
||||||
import space.uranos.util.collections.WatchableSet
|
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.mapToUByte
|
||||||
import space.uranos.util.numbers.validatePitch
|
import space.uranos.util.numbers.validatePitch
|
||||||
import space.uranos.util.numbers.validateYaw
|
import space.uranos.util.numbers.validateYaw
|
||||||
|
@ -64,30 +65,23 @@ sealed class UranosEntity(server: UranosServer) : Entity {
|
||||||
}
|
}
|
||||||
|
|
||||||
override var glowing: Boolean = false
|
override var glowing: Boolean = false
|
||||||
override var ignoreGravity: Boolean = false
|
|
||||||
override var invisible: Boolean = false
|
override var invisible: Boolean = false
|
||||||
override var silent: Boolean = false
|
|
||||||
|
|
||||||
override fun toString(): String = "Entity($uuid)"
|
override fun toString(): String = "Entity($uuid)"
|
||||||
override fun belongsToChunk(key: Chunk.Key): Boolean = key == chunkKey
|
override fun belongsToChunk(key: Chunk.Key): Boolean = key == chunkKey
|
||||||
|
|
||||||
abstract val chunkKey: Chunk.Key
|
abstract val chunkKey: Chunk.Key
|
||||||
|
abstract val metadataSynchronizer: EntityMetadataSynchronizer?
|
||||||
protected val container = TickSynchronizationContainer()
|
|
||||||
|
|
||||||
private fun sendSpawnAndDestroyPackets() {
|
private fun sendSpawnAndDestroyPackets() {
|
||||||
if (addedViewers.isNotEmpty()) createSpawnPacket().let { packet -> addedViewers.forEach { it.session.send(packet) } }
|
if (addedViewers.isNotEmpty()) createSpawnPacket().let { packet -> addedViewers.forEach { it.session.send(packet) } }
|
||||||
if (removedViewers.isNotEmpty()) DestroyEntitiesPacket(arrayOf(numericID)).let { packet -> removedViewers.forEach { it.session.send(packet) } }
|
if (removedViewers.isNotEmpty()) DestroyEntitiesPacket(arrayOf(numericID)).let { packet -> removedViewers.forEach { it.session.send(packet) } }
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("LeakingThis")
|
|
||||||
private val metadataSynchronizer = EntityMetadataSynchronizer(this)
|
|
||||||
|
|
||||||
open suspend fun tick() {
|
open suspend fun tick() {
|
||||||
container.tick()
|
|
||||||
metadataSynchronizer.tick()
|
|
||||||
|
|
||||||
sendSpawnAndDestroyPackets()
|
sendSpawnAndDestroyPackets()
|
||||||
|
metadataSynchronizer?.tick(addedViewers)
|
||||||
|
|
||||||
addedViewers.clear()
|
addedViewers.clear()
|
||||||
removedViewers.clear()
|
removedViewers.clear()
|
||||||
}
|
}
|
||||||
|
@ -107,14 +101,12 @@ sealed class UranosLivingEntity(server: UranosServer) : UranosEntity(server), Li
|
||||||
|
|
||||||
abstract class UranosNotHasMovableHeadLivingEntity(server: UranosServer) : UranosLivingEntity(server) {
|
abstract class UranosNotHasMovableHeadLivingEntity(server: UranosServer) : UranosLivingEntity(server) {
|
||||||
override var velocity: Vector = Vector.ZERO
|
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 lastSentPosition: Position = Position.ZERO
|
||||||
private var lastSentYaw: Float = 0f
|
private var lastSentYaw: Float = 0f
|
||||||
private var lastSentPitch: Float = 0f
|
private var lastSentPitch: Float = 0f
|
||||||
|
|
||||||
|
@Suppress("DuplicatedCode")
|
||||||
final override suspend fun tick() {
|
final override suspend fun tick() {
|
||||||
val viewersWithoutAdded = viewers.subtract(addedViewers)
|
val viewersWithoutAdded = viewers.subtract(addedViewers)
|
||||||
if (viewersWithoutAdded.isNotEmpty()) {
|
if (viewersWithoutAdded.isNotEmpty()) {
|
||||||
|
@ -212,13 +204,13 @@ abstract class UranosHasMovableHeadLivingEntity(server: UranosServer) : UranosLi
|
||||||
abstract class UranosObjectEntity(server: UranosServer) : UranosEntity(server), ObjectEntity {
|
abstract class UranosObjectEntity(server: UranosServer) : UranosEntity(server), ObjectEntity {
|
||||||
override var velocity: Vector = Vector.ZERO
|
override var velocity: Vector = Vector.ZERO
|
||||||
override var position: Position = Position.ZERO
|
override var position: Position = Position.ZERO
|
||||||
|
|
||||||
override val chunkKey: Chunk.Key by memoized({ position }) { Chunk.Key.from(position) }
|
override val chunkKey: Chunk.Key by memoized({ position }) { Chunk.Key.from(position) }
|
||||||
|
|
||||||
private var lastSentPosition: Position = Position.ZERO
|
private var lastSentPosition: Position = Position.ZERO
|
||||||
private var lastSentYaw: Float = 0f
|
private var lastSentYaw: Float = 0f
|
||||||
private var lastSentPitch: Float = 0f
|
private var lastSentPitch: Float = 0f
|
||||||
|
|
||||||
|
@Suppress("DuplicatedCode")
|
||||||
final override suspend fun tick() {
|
final override suspend fun tick() {
|
||||||
val viewersWithoutAdded = viewers.subtract(addedViewers)
|
val viewersWithoutAdded = viewers.subtract(addedViewers)
|
||||||
if (viewersWithoutAdded.isNotEmpty()) {
|
if (viewersWithoutAdded.isNotEmpty()) {
|
||||||
|
@ -246,6 +238,8 @@ class UranosPaintingEntity(
|
||||||
) : UranosEntity(server), PaintingEntity {
|
) : UranosEntity(server), PaintingEntity {
|
||||||
override val chunkKey: Chunk.Key get() = Chunk.Key.from(topLeftLocation)
|
override val chunkKey: Chunk.Key get() = Chunk.Key.from(topLeftLocation)
|
||||||
|
|
||||||
|
override val metadataSynchronizer: EntityMetadataSynchronizer? = null
|
||||||
|
|
||||||
private var lastSentTopLeftLocation: VoxelLocation = topLeftLocation
|
private var lastSentTopLeftLocation: VoxelLocation = topLeftLocation
|
||||||
|
|
||||||
override suspend fun tick() {
|
override suspend fun tick() {
|
||||||
|
@ -268,7 +262,6 @@ class UranosPaintingEntity(
|
||||||
}
|
}
|
||||||
|
|
||||||
lastSentTopLeftLocation = topLeftLocation
|
lastSentTopLeftLocation = topLeftLocation
|
||||||
|
|
||||||
super.tick()
|
super.tick()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,7 +15,7 @@ abstract class UranosMinecartEntity(server: UranosServer) : UranosObjectEntity(s
|
||||||
validatePitch(value); field = value
|
validatePitch(value); field = value
|
||||||
}
|
}
|
||||||
|
|
||||||
override val shakingPower: Int = 0
|
override var shakingPower: Int = 0
|
||||||
override val shakingDirection: Int = 1
|
override var shakingDirection: Int = 1
|
||||||
override val shakingMultiplier: Float = 0f
|
override var shakingMultiplier: Float = 0f
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,20 @@ package space.uranos.entity.impl
|
||||||
|
|
||||||
import space.uranos.UranosServer
|
import space.uranos.UranosServer
|
||||||
import space.uranos.entity.BatEntity
|
import space.uranos.entity.BatEntity
|
||||||
|
import space.uranos.entity.EntityMetadataSynchronizer
|
||||||
import space.uranos.entity.UranosHasMovableHeadLivingEntity
|
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
|
package space.uranos.entity.impl
|
||||||
|
|
||||||
import space.uranos.UranosServer
|
import space.uranos.UranosServer
|
||||||
|
import space.uranos.entity.Ageable
|
||||||
import space.uranos.entity.CowEntity
|
import space.uranos.entity.CowEntity
|
||||||
|
import space.uranos.entity.EntityMetadataSynchronizer
|
||||||
import space.uranos.entity.UranosHasMovableHeadLivingEntity
|
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
|
package space.uranos.entity.impl
|
||||||
|
|
||||||
import space.uranos.UranosServer
|
import space.uranos.UranosServer
|
||||||
|
import space.uranos.entity.EntityMetadataSynchronizer
|
||||||
import space.uranos.entity.PlayerEntity
|
import space.uranos.entity.PlayerEntity
|
||||||
import space.uranos.entity.UranosHasMovableHeadLivingEntity
|
import space.uranos.entity.UranosHasMovableHeadLivingEntity
|
||||||
|
import space.uranos.entity.metadata.EntityMetadataFieldsTable
|
||||||
import space.uranos.player.Player
|
import space.uranos.player.Player
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
|
|
||||||
|
@ -11,4 +13,12 @@ class UranosPlayerEntity(
|
||||||
override val player: Player
|
override val player: Player
|
||||||
) : UranosHasMovableHeadLivingEntity(server), PlayerEntity {
|
) : UranosHasMovableHeadLivingEntity(server), PlayerEntity {
|
||||||
override val uuid: UUID = player.uuid
|
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
|
package space.uranos.entity.impl
|
||||||
|
|
||||||
import space.uranos.UranosServer
|
import space.uranos.UranosServer
|
||||||
|
import space.uranos.entity.EntityMetadataSynchronizer
|
||||||
|
import space.uranos.entity.MinecartEntity
|
||||||
import space.uranos.entity.RideableMinecartEntity
|
import space.uranos.entity.RideableMinecartEntity
|
||||||
import space.uranos.entity.UranosMinecartEntity
|
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.Block
|
||||||
|
import space.uranos.world.block.material
|
||||||
|
|
||||||
class UranosRideableMinecartEntity(server: UranosServer) : UranosMinecartEntity(server), RideableMinecartEntity {
|
class UranosRideableMinecartEntity(server: UranosServer) : UranosMinecartEntity(server), RideableMinecartEntity {
|
||||||
override val customBlock: Block? = null
|
override var customBlock: Block? = null
|
||||||
override val blockOffset: Int = 6
|
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(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.uuid to PlayerInfoPacket.Action.AddPlayer.Data(
|
||||||
it.name,
|
it.name,
|
||||||
it.gameMode,
|
it.gameMode,
|
||||||
|
@ -188,12 +189,13 @@ class LoginAndJoinProcedure(val session: UranosSession) {
|
||||||
it.playerListName,
|
it.playerListName,
|
||||||
emptyMap() // TODO: Load skin
|
emptyMap() // TODO: Load skin
|
||||||
)
|
)
|
||||||
}.toMap())))
|
}
|
||||||
|
)))
|
||||||
|
|
||||||
session.sendNow(
|
session.sendNow(
|
||||||
PlayerInfoPacket(
|
PlayerInfoPacket(
|
||||||
PlayerInfoPacket.Action.UpdateLatency(
|
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) {
|
fun sendNextTick(packet: OutgoingPacket) {
|
||||||
|
// TODO: Allow disabling merging in the config
|
||||||
if (packet is Mergeable) {
|
if (packet is Mergeable) {
|
||||||
for (i in packetsForNextTick.indices.reversed()) {
|
for (i in packetsForNextTick.indices.reversed()) {
|
||||||
val merged = packet.mergeWith(packetsForNextTick[i])
|
val merged = packet.mergeWith(packetsForNextTick[i])
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
package space.uranos.net
|
package space.uranos.net
|
||||||
|
|
||||||
import io.netty.buffer.ByteBuf
|
import io.netty.buffer.ByteBuf
|
||||||
import kotlinx.coroutines.*
|
import kotlinx.coroutines.cancel
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
import space.uranos.Uranos
|
import space.uranos.Uranos
|
||||||
import space.uranos.UranosServer
|
import space.uranos.UranosServer
|
||||||
import space.uranos.chat.ChatColor
|
import space.uranos.chat.ChatColor
|
||||||
import space.uranos.chat.ChatComponent
|
import space.uranos.chat.ChatComponent
|
||||||
import space.uranos.chat.TextComponent
|
import space.uranos.chat.TextComponent
|
||||||
import space.uranos.event.*
|
|
||||||
import space.uranos.logging.Logger
|
import space.uranos.logging.Logger
|
||||||
import space.uranos.net.packet.OutgoingPacket
|
import space.uranos.net.packet.OutgoingPacket
|
||||||
import space.uranos.net.packet.Protocol
|
import space.uranos.net.packet.Protocol
|
||||||
|
|
Reference in a new issue