Sync entities when joining or when viewers change
This commit is contained in:
parent
1b98b19f16
commit
6c6b9c74f4
31 changed files with 371 additions and 145 deletions
|
@ -16,6 +16,8 @@ repositories {
|
||||||
allprojects {
|
allprojects {
|
||||||
tasks.withType<KotlinCompile> {
|
tasks.withType<KotlinCompile> {
|
||||||
kotlinOptions.jvmTarget = "14"
|
kotlinOptions.jvmTarget = "14"
|
||||||
|
kotlinOptions.languageVersion = "1.4"
|
||||||
|
|
||||||
kotlinOptions.freeCompilerArgs += "-progressive"
|
kotlinOptions.freeCompilerArgs += "-progressive"
|
||||||
kotlinOptions.freeCompilerArgs += "-Xopt-in=kotlin.ExperimentalStdlibApi"
|
kotlinOptions.freeCompilerArgs += "-Xopt-in=kotlin.ExperimentalStdlibApi"
|
||||||
kotlinOptions.freeCompilerArgs += "-Xopt-in=kotlin.ExperimentalUnsignedTypes"
|
kotlinOptions.freeCompilerArgs += "-Xopt-in=kotlin.ExperimentalUnsignedTypes"
|
||||||
|
|
|
@ -15,7 +15,6 @@ const val ITEM_PACKAGE = "$BASE_PACKAGE.item"
|
||||||
const val TAG_PACKAGE = "$BASE_PACKAGE.tag"
|
const val TAG_PACKAGE = "$BASE_PACKAGE.tag"
|
||||||
val MATERIAL_TYPE = ClassName(BLOCK_PACKAGE, "Material")
|
val MATERIAL_TYPE = ClassName(BLOCK_PACKAGE, "Material")
|
||||||
val BLOCK_TYPE = ClassName(BLOCK_PACKAGE, "Block")
|
val BLOCK_TYPE = ClassName(BLOCK_PACKAGE, "Block")
|
||||||
val ENTITY_TYPE = ClassName(ENTITY_PACKAGE, "Entity")
|
|
||||||
val ITEM_TYPE_TYPE = ClassName(ITEM_PACKAGE, "ItemType")
|
val ITEM_TYPE_TYPE = ClassName(ITEM_PACKAGE, "ItemType")
|
||||||
val ENTITY_TYPE_TYPE = ClassName(ENTITY_PACKAGE, "EntityType")
|
val ENTITY_TYPE_TYPE = ClassName(ENTITY_PACKAGE, "EntityType")
|
||||||
val TAG_TYPE = ClassName(TAG_PACKAGE, "Tag")
|
val TAG_TYPE = ClassName(TAG_PACKAGE, "Tag")
|
||||||
|
|
|
@ -73,33 +73,27 @@ class EntitiesGenerator(
|
||||||
val name = CaseFormat.LOWER_UNDERSCORE.to(CaseFormat.UPPER_CAMEL, entity.get("name").toString())!! +
|
val name = CaseFormat.LOWER_UNDERSCORE.to(CaseFormat.UPPER_CAMEL, entity.get("name").toString())!! +
|
||||||
"Entity"
|
"Entity"
|
||||||
|
|
||||||
val filePathRelativeToSourceRoot = "./${ENTITY_PACKAGE.replace(".", "/")}/$name.kt"
|
if (name == "PaintingEntity") continue
|
||||||
if (sourcesDir.resolve(filePathRelativeToSourceRoot).exists()) continue
|
|
||||||
|
|
||||||
val type = TypeSpec.classBuilder(name)
|
val path = "./${ENTITY_PACKAGE.replace(".", "/")}/$name.kt"
|
||||||
.superclass(ENTITY_TYPE)
|
if (sourcesDir.resolve(path).exists()) continue
|
||||||
.addModifiers(KModifier.OPEN)
|
|
||||||
.addProperty(
|
|
||||||
PropertySpec.builder("type", ENTITY_TYPE_TYPE, KModifier.OVERRIDE, KModifier.FINAL)
|
|
||||||
.initializer("Type")
|
|
||||||
.build()
|
|
||||||
)
|
|
||||||
.addType(
|
|
||||||
TypeSpec.companionObjectBuilder("Type")
|
|
||||||
.superclass(ClassName(ENTITY_PACKAGE, name + "Type"))
|
|
||||||
.build()
|
|
||||||
)
|
|
||||||
.build()
|
|
||||||
|
|
||||||
FileSpec.builder(ENTITY_PACKAGE, name)
|
outputDir.resolve(path).writeText(
|
||||||
.addType(type)
|
"""
|
||||||
.build()
|
package $ENTITY_PACKAGE
|
||||||
.writeTo(outputDir)
|
|
||||||
|
// open class AreaEffectCloudEntity : Entity() {
|
||||||
|
// final override val type: EntityType = Type
|
||||||
|
//
|
||||||
|
// companion object Type : AreaEffectCloudEntityType()
|
||||||
|
// }
|
||||||
|
""".trimIndent()
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun generateEntityTypeList(entities: List<JsonAny>) {
|
private fun generateEntityTypeList(entities: List<JsonAny>) {
|
||||||
val names = entities
|
val names = entities.asSequence()
|
||||||
.map { it.get("name").toString() }
|
.map { it.get("name").toString() }
|
||||||
.map { CaseFormat.LOWER_UNDERSCORE.to(CaseFormat.UPPER_CAMEL, it) + "Entity" }
|
.map { CaseFormat.LOWER_UNDERSCORE.to(CaseFormat.UPPER_CAMEL, it) + "Entity" }
|
||||||
|
|
||||||
|
@ -107,7 +101,12 @@ class EntitiesGenerator(
|
||||||
"ENTITY_TYPES",
|
"ENTITY_TYPES",
|
||||||
List::class.asTypeName().parameterizedBy(ENTITY_TYPE_TYPE)
|
List::class.asTypeName().parameterizedBy(ENTITY_TYPE_TYPE)
|
||||||
)
|
)
|
||||||
.initializer("listOf(\n${names.joinToString(",\n")}\n)")
|
.initializer("listOf(\n${
|
||||||
|
names.joinToString(",\n") {
|
||||||
|
val path = "./${ENTITY_PACKAGE.replace(".", "/")}/$it.kt"
|
||||||
|
if (sourcesDir.resolve(path).exists()) it else "object : ${it}Type() {}"
|
||||||
|
}
|
||||||
|
}\n)")
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
FileSpec.builder(ENTITY_PACKAGE, "EntityTypes")
|
FileSpec.builder(ENTITY_PACKAGE, "EntityTypes")
|
||||||
|
|
|
@ -5,14 +5,26 @@
|
||||||
|
|
||||||
package space.uranos.entity
|
package space.uranos.entity
|
||||||
|
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.sync.Mutex
|
import kotlinx.coroutines.sync.Mutex
|
||||||
import kotlinx.coroutines.sync.withLock
|
import kotlinx.coroutines.sync.withLock
|
||||||
import space.uranos.Uranos
|
import space.uranos.*
|
||||||
|
import space.uranos.entity.event.ViewingChangedEvent
|
||||||
|
import space.uranos.event.EventBusWrapper
|
||||||
|
import space.uranos.player.Player
|
||||||
import space.uranos.util.TickSynchronizationContainer
|
import space.uranos.util.TickSynchronizationContainer
|
||||||
|
import space.uranos.util.WatchableSet
|
||||||
|
import space.uranos.util.memoized
|
||||||
|
import space.uranos.world.Chunk
|
||||||
|
import space.uranos.world.VoxelLocation
|
||||||
import space.uranos.world.World
|
import space.uranos.world.World
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
import kotlin.math.max
|
||||||
|
|
||||||
abstract class Entity internal constructor() {
|
private typealias Vector = space.uranos.Vector
|
||||||
|
|
||||||
|
sealed class Entity {
|
||||||
|
internal var eventBusWrapper: EventBusWrapper<*>? = null
|
||||||
protected val container = TickSynchronizationContainer()
|
protected val container = TickSynchronizationContainer()
|
||||||
|
|
||||||
@Deprecated(
|
@Deprecated(
|
||||||
|
@ -25,7 +37,8 @@ abstract class Entity internal constructor() {
|
||||||
container.tick()
|
container.tick()
|
||||||
}
|
}
|
||||||
|
|
||||||
open fun onTick() {}
|
protected open fun onTick() {}
|
||||||
|
protected open fun onWorldSet() {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The UUID of this entity.
|
* The UUID of this entity.
|
||||||
|
@ -38,6 +51,25 @@ abstract class Entity internal constructor() {
|
||||||
open val uuid: UUID = UUID.randomUUID()
|
open val uuid: UUID = UUID.randomUUID()
|
||||||
|
|
||||||
abstract val type: EntityType
|
abstract val type: EntityType
|
||||||
|
abstract val chunkKey: Chunk.Key
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Players that can see this entity.
|
||||||
|
*/
|
||||||
|
val viewers: MutableSet<Player> = object : WatchableSet<Player>(Collections.newSetFromMap(WeakHashMap())) {
|
||||||
|
override fun onAdd(element: Player) {
|
||||||
|
Uranos.scope.launch { Uranos.eventBus.emit(ViewingChangedEvent(this@Entity, element, true)) }
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onRemove(element: Player) {
|
||||||
|
Uranos.scope.launch { Uranos.eventBus.emit(ViewingChangedEvent(this@Entity, element, false)) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If players should be added to [viewers] when they join.
|
||||||
|
*/
|
||||||
|
var visibleToNewPlayers: Boolean = true
|
||||||
|
|
||||||
private val worldMutex = Mutex()
|
private val worldMutex = Mutex()
|
||||||
var world: World? = null; protected set
|
var world: World? = null; protected set
|
||||||
|
@ -53,6 +85,8 @@ abstract class Entity internal constructor() {
|
||||||
this.world = world
|
this.world = world
|
||||||
world?.internalEntities?.add(this)
|
world?.internalEntities?.add(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onWorldSet()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -64,6 +98,48 @@ abstract class Entity internal constructor() {
|
||||||
* An integer unique to this entity which will not be persisted, for example when the entity is serialized.
|
* An integer unique to this entity which will not be persisted, for example when the entity is serialized.
|
||||||
*/
|
*/
|
||||||
@Suppress("LeakingThis")
|
@Suppress("LeakingThis")
|
||||||
@Deprecated("This is an internal value that you usually should not use.", ReplaceWith("uuid"))
|
|
||||||
val numericID: Int = Uranos.registerEntity(this)
|
val numericID: Int = Uranos.registerEntity(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
abstract class LivingEntity(position: Position) : Entity(), Mobile {
|
||||||
|
abstract var headPitch: Float // TODO: This should probably be headYaw, but wiki.vg says headPitch. And it is only used in the SpawnLivingEntity packet
|
||||||
|
override var velocity: Vector = Vector.ZERO
|
||||||
|
|
||||||
|
override var position: Position by container.ifChanged(position) { value ->
|
||||||
|
// TODO: Broadcast to players
|
||||||
|
}
|
||||||
|
|
||||||
|
override val chunkKey: Chunk.Key by memoized({ position }) { Chunk.Key.from(position) }
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class ObjectEntity(position: Position) : Entity(), Mobile {
|
||||||
|
override var position: Position by container.ifChanged(position) { value ->
|
||||||
|
// TODO: Broadcast to players
|
||||||
|
}
|
||||||
|
|
||||||
|
override var velocity: Vector = Vector.ZERO
|
||||||
|
}
|
||||||
|
|
||||||
|
open class PaintingEntity(
|
||||||
|
val topLeftLocation: VoxelLocation,
|
||||||
|
val direction: CardinalDirection,
|
||||||
|
val motive: PaintingMotive
|
||||||
|
) : Entity() {
|
||||||
|
override val type: EntityType = Type
|
||||||
|
override val chunkKey: Chunk.Key get() = Chunk.Key.from(topLeftLocation)
|
||||||
|
|
||||||
|
val centerLocation = topLeftLocation.copy(
|
||||||
|
x = max(0, motive.width / 2) + topLeftLocation.x,
|
||||||
|
z = motive.height / 2 + topLeftLocation.z
|
||||||
|
)
|
||||||
|
|
||||||
|
companion object Type : PaintingEntityType()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
fun <T : Entity> T.events(): EventBusWrapper<T> {
|
||||||
|
val wrapper = eventBusWrapper
|
||||||
|
|
||||||
|
return if (wrapper == null) EventBusWrapper<T>(this).also { eventBusWrapper = it }
|
||||||
|
else wrapper as EventBusWrapper<T>
|
||||||
|
}
|
||||||
|
|
|
@ -6,9 +6,11 @@
|
||||||
package space.uranos.entity
|
package space.uranos.entity
|
||||||
|
|
||||||
import space.uranos.Position
|
import space.uranos.Position
|
||||||
|
import space.uranos.world.Chunk
|
||||||
|
|
||||||
open class ItemEntity(override var position: Position) : ObjectEntity() {
|
open class ItemEntity(position: Position) : ObjectEntity(position) {
|
||||||
final override val type: EntityType = Type
|
final override val type: EntityType = Type
|
||||||
|
override val chunkKey: Chunk.Key get() = Chunk.Key.from(position)
|
||||||
|
|
||||||
companion object Type : ItemEntityType()
|
companion object Type : ItemEntityType()
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,18 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2020-2021 Moritz Ruth and Uranos contributors
|
|
||||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file
|
|
||||||
*/
|
|
||||||
|
|
||||||
package space.uranos.entity
|
|
||||||
|
|
||||||
import space.uranos.Position
|
|
||||||
import space.uranos.Vector
|
|
||||||
|
|
||||||
abstract class LivingEntity(position: Position) : Entity(), Mobile {
|
|
||||||
abstract var headPitch: Float // TODO: This should probably be headYaw, but wiki.vg says headPitch. And it is only used in the SpawnLivingEntity packet
|
|
||||||
abstract override var velocity: Vector // TODO: Move the entity every tick
|
|
||||||
|
|
||||||
override var position: Position by container.ifChanged(position) { value ->
|
|
||||||
// TODO: Broadcast to players
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -14,5 +14,5 @@ interface Mobile {
|
||||||
/**
|
/**
|
||||||
* The velocity in blocks per tick.
|
* The velocity in blocks per tick.
|
||||||
*/
|
*/
|
||||||
var velocity: Vector
|
var velocity: Vector // TODO: Move the entity every tick
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,15 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2020-2021 Moritz Ruth and Uranos contributors
|
|
||||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file
|
|
||||||
*/
|
|
||||||
|
|
||||||
package space.uranos.entity
|
|
||||||
|
|
||||||
import space.uranos.Position
|
|
||||||
import space.uranos.Vector
|
|
||||||
|
|
||||||
abstract class ObjectEntity : Entity(), Mobile {
|
|
||||||
abstract override var position: Position
|
|
||||||
|
|
||||||
final override var velocity: Vector = Vector.ZERO
|
|
||||||
}
|
|
|
@ -1,20 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2020-2021 Moritz Ruth and Uranos contributors
|
|
||||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file
|
|
||||||
*/
|
|
||||||
|
|
||||||
package space.uranos.entity
|
|
||||||
|
|
||||||
import space.uranos.CardinalDirection
|
|
||||||
import space.uranos.PaintingMotive
|
|
||||||
import space.uranos.world.VoxelLocation
|
|
||||||
|
|
||||||
class PaintingEntity(
|
|
||||||
val topLeftLocation: VoxelLocation,
|
|
||||||
val direction: CardinalDirection,
|
|
||||||
val motive: PaintingMotive
|
|
||||||
) : Entity() {
|
|
||||||
override val type: EntityType = Type
|
|
||||||
|
|
||||||
companion object Type : PaintingEntityType()
|
|
||||||
}
|
|
|
@ -6,7 +6,6 @@
|
||||||
package space.uranos.entity
|
package space.uranos.entity
|
||||||
|
|
||||||
import space.uranos.Position
|
import space.uranos.Position
|
||||||
import space.uranos.Vector
|
|
||||||
import space.uranos.player.Player
|
import space.uranos.player.Player
|
||||||
import space.uranos.world.World
|
import space.uranos.world.World
|
||||||
|
|
||||||
|
@ -21,7 +20,6 @@ open class PlayerEntity(
|
||||||
override var headPitch: Float = 0f
|
override var headPitch: Float = 0f
|
||||||
) : LivingEntity(position) {
|
) : LivingEntity(position) {
|
||||||
final override val type: EntityType = Type
|
final override val type: EntityType = Type
|
||||||
override var velocity: Vector = Vector.ZERO
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Because [world] is never `null` for player entities, you can use this property instead of writing `world!!`.
|
* Because [world] is never `null` for player entities, you can use this property instead of writing `world!!`.
|
||||||
|
|
|
@ -0,0 +1,11 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2020-2021 Moritz Ruth and Uranos contributors
|
||||||
|
* Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file
|
||||||
|
*/
|
||||||
|
|
||||||
|
package space.uranos.entity.event
|
||||||
|
|
||||||
|
import space.uranos.entity.Entity
|
||||||
|
import space.uranos.event.TargetedEvent
|
||||||
|
|
||||||
|
abstract class EntityEvent : TargetedEvent<Entity>()
|
|
@ -0,0 +1,11 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2020-2021 Moritz Ruth and Uranos contributors
|
||||||
|
* Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file
|
||||||
|
*/
|
||||||
|
|
||||||
|
package space.uranos.entity.event
|
||||||
|
|
||||||
|
import space.uranos.entity.Entity
|
||||||
|
import space.uranos.player.Player
|
||||||
|
|
||||||
|
data class ViewingChangedEvent(override val target: Entity, val player: Player, val viewing: Boolean) : EntityEvent()
|
|
@ -73,17 +73,17 @@ abstract class Server {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set of all existing [Entity] instances.
|
* Set of all existing [Entity] instances.
|
||||||
*
|
|
||||||
* This is not public because the instances may not be fully initialized as they are added.
|
|
||||||
*/
|
*/
|
||||||
protected val entities: MutableSet<Entity> = Collections.newSetFromMap(WeakHashMap())
|
private val internalEntities: MutableSet<Entity> = Collections.newSetFromMap(WeakHashMap())
|
||||||
private val nextEntityID = AtomicInteger()
|
val entities: Set<Entity> = internalEntities
|
||||||
|
|
||||||
|
private val nextEntityID = AtomicInteger(1)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the UID for [entity].
|
* Returns the UID for [entity].
|
||||||
*/
|
*/
|
||||||
internal fun registerEntity(entity: Entity): Int {
|
internal fun registerEntity(entity: Entity): Int {
|
||||||
entities.add(entity)
|
internalEntities.add(entity)
|
||||||
return nextEntityID.getAndIncrement()
|
return nextEntityID.getAndIncrement()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,29 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2020-2021 Moritz Ruth and Uranos contributors
|
||||||
|
* Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file
|
||||||
|
*/
|
||||||
|
|
||||||
|
package space.uranos.util
|
||||||
|
|
||||||
|
import kotlin.reflect.KProperty
|
||||||
|
|
||||||
|
class MemoizedDelegate<T>(private val dependingGetter: () -> Any?, private val initializer: () -> T) {
|
||||||
|
private object UNINITIALIZED
|
||||||
|
|
||||||
|
private var lastDependingValue: Any? = null
|
||||||
|
private var value: Any? = UNINITIALIZED
|
||||||
|
|
||||||
|
operator fun getValue(thisRef: Any?, property: KProperty<*>): T {
|
||||||
|
val currentDependingValue = dependingGetter()
|
||||||
|
|
||||||
|
if (value == UNINITIALIZED || (lastDependingValue != currentDependingValue)) {
|
||||||
|
value = initializer()
|
||||||
|
lastDependingValue = currentDependingValue
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
return value as T
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun <T> memoized(dependingGetter: () -> Any?, initializer: () -> T) = MemoizedDelegate(dependingGetter, initializer)
|
73
uranos-api/src/main/kotlin/space/uranos/util/WatchableSet.kt
Normal file
73
uranos-api/src/main/kotlin/space/uranos/util/WatchableSet.kt
Normal file
|
@ -0,0 +1,73 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2020-2021 Moritz Ruth and Uranos contributors
|
||||||
|
* Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file
|
||||||
|
*/
|
||||||
|
|
||||||
|
package space.uranos.util
|
||||||
|
|
||||||
|
import java.util.*
|
||||||
|
import java.util.function.Predicate
|
||||||
|
import java.util.stream.Stream
|
||||||
|
|
||||||
|
abstract class WatchableSet<T>(private val backingSet: MutableSet<T>) : MutableSet<T> by backingSet {
|
||||||
|
abstract fun onAdd(element: T)
|
||||||
|
abstract fun onRemove(element: T)
|
||||||
|
|
||||||
|
override fun add(element: T): Boolean {
|
||||||
|
val added = backingSet.add(element)
|
||||||
|
if (added) onAdd(element)
|
||||||
|
return added
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun remove(element: T): Boolean {
|
||||||
|
val removed = backingSet.remove(element)
|
||||||
|
if (removed) onRemove(element)
|
||||||
|
return removed
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun addAll(elements: Collection<T>): Boolean = elements.any { add(it) }
|
||||||
|
|
||||||
|
override fun clear() {
|
||||||
|
backingSet.forEach { remove(it) }
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun removeAll(elements: Collection<T>): Boolean =
|
||||||
|
if (elements.size > backingSet.size) backingSet.toSet().any { if (elements.contains(it)) remove(it) else false }
|
||||||
|
else elements.any { remove(it) }
|
||||||
|
|
||||||
|
override fun retainAll(elements: Collection<T>): Boolean =
|
||||||
|
if (elements.size > backingSet.size) backingSet.toSet().any { if (elements.contains(it)) remove(it) else false }
|
||||||
|
else elements.any { remove(it) }
|
||||||
|
|
||||||
|
override fun iterator(): MutableIterator<T> {
|
||||||
|
val iterator = backingSet.iterator()
|
||||||
|
var current: T? = null
|
||||||
|
|
||||||
|
return object : MutableIterator<T> {
|
||||||
|
override fun hasNext(): Boolean = iterator.hasNext()
|
||||||
|
override fun next(): T = iterator.next().also { current = it }
|
||||||
|
override fun remove() {
|
||||||
|
iterator.remove()
|
||||||
|
current?.let { onRemove(it) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun removeIf(filter: Predicate<in T>): Boolean {
|
||||||
|
val iterator = iterator()
|
||||||
|
var modified = false
|
||||||
|
|
||||||
|
for (item in iterator) {
|
||||||
|
if (filter.test(item)) {
|
||||||
|
modified = true
|
||||||
|
iterator.remove()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return modified
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun parallelStream(): Stream<T> = throw UnsupportedOperationException("Not implemented for WatchableSet")
|
||||||
|
override fun stream(): Stream<T> = throw UnsupportedOperationException("Not implemented for WatchableSet")
|
||||||
|
override fun spliterator(): Spliterator<T> = throw UnsupportedOperationException("Not implemented for WatchableSet")
|
||||||
|
}
|
|
@ -7,6 +7,7 @@ package space.uranos.world
|
||||||
|
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.SupervisorJob
|
import kotlinx.coroutines.SupervisorJob
|
||||||
|
import space.uranos.Position
|
||||||
import space.uranos.Uranos
|
import space.uranos.Uranos
|
||||||
import space.uranos.player.Player
|
import space.uranos.player.Player
|
||||||
import kotlin.math.floor
|
import kotlin.math.floor
|
||||||
|
@ -26,6 +27,11 @@ abstract class Chunk(
|
||||||
floor(location.x.toFloat() / LENGTH).toInt(),
|
floor(location.x.toFloat() / LENGTH).toInt(),
|
||||||
floor(location.z.toFloat() / LENGTH).toInt()
|
floor(location.z.toFloat() / LENGTH).toInt()
|
||||||
)
|
)
|
||||||
|
|
||||||
|
fun from(location: Position) = Key(
|
||||||
|
floor(location.x.toFloat() / LENGTH).toInt(),
|
||||||
|
floor(location.z.toFloat() / LENGTH).toInt()
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun translateWorldToChunk(x: Int, z: Int) = Pair(Math.floorMod(x, LENGTH), Math.floorMod(z, LENGTH))
|
fun translateWorldToChunk(x: Int, z: Int) = Pair(Math.floorMod(x, LENGTH), Math.floorMod(z, LENGTH))
|
||||||
|
|
|
@ -11,7 +11,7 @@ import space.uranos.entity.Entity
|
||||||
import space.uranos.util.newSingleThreadDispatcher
|
import space.uranos.util.newSingleThreadDispatcher
|
||||||
import space.uranos.util.untilPossiblyLower
|
import space.uranos.util.untilPossiblyLower
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import java.util.concurrent.CopyOnWriteArraySet
|
import kotlin.collections.HashSet
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A Minecraft world.
|
* A Minecraft world.
|
||||||
|
@ -47,7 +47,7 @@ abstract class World(val uuid: UUID) {
|
||||||
/**
|
/**
|
||||||
* All entities in this world.
|
* All entities in this world.
|
||||||
*/
|
*/
|
||||||
internal val internalEntities = CopyOnWriteArraySet<Entity>()
|
internal val internalEntities = HashSet<Entity>()
|
||||||
|
|
||||||
val entities get() = internalEntities.toList()
|
val entities get() = internalEntities.toList()
|
||||||
|
|
||||||
|
@ -102,6 +102,9 @@ abstract class World(val uuid: UUID) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
suspend inline operator fun <T> invoke(noinline block: suspend CoroutineScope.() -> T): T =
|
||||||
|
withContext(dispatcher, block)
|
||||||
|
|
||||||
suspend fun destroy() {
|
suspend fun destroy() {
|
||||||
// TODO: Move or kick players
|
// TODO: Move or kick players
|
||||||
scope.cancel()
|
scope.cancel()
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2020-2021 Moritz Ruth and Uranos contributors
|
||||||
|
* Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file
|
||||||
|
*/
|
||||||
|
|
||||||
|
package space.uranos.net.packet.play
|
||||||
|
|
||||||
|
import io.netty.buffer.ByteBuf
|
||||||
|
import space.uranos.net.MinecraftProtocolDataTypes.writeVarInt
|
||||||
|
import space.uranos.net.packet.OutgoingPacketCodec
|
||||||
|
|
||||||
|
object DestroyEntitiesPacketCodec :
|
||||||
|
OutgoingPacketCodec<DestroyEntitiesPacket>(0x36, DestroyEntitiesPacket::class) {
|
||||||
|
override fun DestroyEntitiesPacket.encode(dst: ByteBuf) {
|
||||||
|
dst.writeVarInt(entityIDs.size)
|
||||||
|
entityIDs.forEach { dst.writeVarInt(it) }
|
||||||
|
}
|
||||||
|
}
|
|
@ -16,6 +16,7 @@ object PlayProtocol : Protocol(
|
||||||
CompassTargetPacketCodec,
|
CompassTargetPacketCodec,
|
||||||
DeclareCommandsPacketCodec,
|
DeclareCommandsPacketCodec,
|
||||||
DeclareRecipesPacketCodec,
|
DeclareRecipesPacketCodec,
|
||||||
|
DestroyEntitiesPacketCodec,
|
||||||
DisconnectPacketCodec,
|
DisconnectPacketCodec,
|
||||||
IncomingKeepAlivePacketCodec,
|
IncomingKeepAlivePacketCodec,
|
||||||
IncomingPlayerPositionPacketCodec,
|
IncomingPlayerPositionPacketCodec,
|
||||||
|
|
|
@ -6,7 +6,6 @@
|
||||||
package space.uranos.net.packet.play
|
package space.uranos.net.packet.play
|
||||||
|
|
||||||
import io.netty.buffer.ByteBuf
|
import io.netty.buffer.ByteBuf
|
||||||
import space.uranos.entity.LivingEntity
|
|
||||||
import space.uranos.net.MinecraftProtocolDataTypes.writeUUID
|
import space.uranos.net.MinecraftProtocolDataTypes.writeUUID
|
||||||
import space.uranos.net.MinecraftProtocolDataTypes.writeVarInt
|
import space.uranos.net.MinecraftProtocolDataTypes.writeVarInt
|
||||||
import space.uranos.net.packet.OutgoingPacketCodec
|
import space.uranos.net.packet.OutgoingPacketCodec
|
||||||
|
@ -27,14 +26,4 @@ object SpawnLivingEntityPacketCodec : OutgoingPacketCodec<SpawnLivingEntityPacke
|
||||||
dst.writeShort((velocity.y * 8000).toInt().toShort().toInt())
|
dst.writeShort((velocity.y * 8000).toInt().toShort().toInt())
|
||||||
dst.writeShort((velocity.z * 8000).toInt().toShort().toInt())
|
dst.writeShort((velocity.z * 8000).toInt().toShort().toInt())
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getPacketFromEntity(entity: LivingEntity) = SpawnLivingEntityPacket(
|
|
||||||
@Suppress("DEPRECATION")
|
|
||||||
entity.numericID,
|
|
||||||
entity.uuid,
|
|
||||||
entity.type,
|
|
||||||
entity.position,
|
|
||||||
entity.headPitch,
|
|
||||||
entity.velocity
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,8 +6,6 @@
|
||||||
package space.uranos.net.packet.play
|
package space.uranos.net.packet.play
|
||||||
|
|
||||||
import io.netty.buffer.ByteBuf
|
import io.netty.buffer.ByteBuf
|
||||||
import space.uranos.entity.ItemEntity
|
|
||||||
import space.uranos.entity.ObjectEntity
|
|
||||||
import space.uranos.net.MinecraftProtocolDataTypes.writeUUID
|
import space.uranos.net.MinecraftProtocolDataTypes.writeUUID
|
||||||
import space.uranos.net.MinecraftProtocolDataTypes.writeVarInt
|
import space.uranos.net.MinecraftProtocolDataTypes.writeVarInt
|
||||||
import space.uranos.net.packet.OutgoingPacketCodec
|
import space.uranos.net.packet.OutgoingPacketCodec
|
||||||
|
@ -28,20 +26,4 @@ object SpawnObjectEntityPacketCodec : OutgoingPacketCodec<SpawnObjectEntityPacke
|
||||||
dst.writeShort((velocity.y * 8000).toInt().toShort().toInt())
|
dst.writeShort((velocity.y * 8000).toInt().toShort().toInt())
|
||||||
dst.writeShort((velocity.z * 8000).toInt().toShort().toInt())
|
dst.writeShort((velocity.z * 8000).toInt().toShort().toInt())
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getPacketFromEntity(entity: ObjectEntity) = SpawnObjectEntityPacket(
|
|
||||||
@Suppress("DEPRECATION")
|
|
||||||
entity.numericID,
|
|
||||||
entity.uuid,
|
|
||||||
entity.type,
|
|
||||||
entity.position,
|
|
||||||
getDataForEntity(entity),
|
|
||||||
entity.velocity
|
|
||||||
)
|
|
||||||
|
|
||||||
fun getDataForEntity(entity: ObjectEntity): Int = when(entity) {
|
|
||||||
is ItemEntity -> 1
|
|
||||||
// TODO: Add remaining
|
|
||||||
else -> throw IllegalArgumentException("Unknown entity type")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,14 +7,10 @@ package space.uranos.net.packet.play
|
||||||
|
|
||||||
import io.netty.buffer.ByteBuf
|
import io.netty.buffer.ByteBuf
|
||||||
import space.uranos.CardinalDirection
|
import space.uranos.CardinalDirection
|
||||||
import space.uranos.PaintingMotive
|
|
||||||
import space.uranos.entity.PaintingEntity
|
|
||||||
import space.uranos.net.MinecraftProtocolDataTypes.writeUUID
|
import space.uranos.net.MinecraftProtocolDataTypes.writeUUID
|
||||||
import space.uranos.net.MinecraftProtocolDataTypes.writeVarInt
|
import space.uranos.net.MinecraftProtocolDataTypes.writeVarInt
|
||||||
import space.uranos.net.MinecraftProtocolDataTypes.writeVoxelLocation
|
import space.uranos.net.MinecraftProtocolDataTypes.writeVoxelLocation
|
||||||
import space.uranos.net.packet.OutgoingPacketCodec
|
import space.uranos.net.packet.OutgoingPacketCodec
|
||||||
import space.uranos.world.VoxelLocation
|
|
||||||
import kotlin.math.max
|
|
||||||
|
|
||||||
object SpawnPaintingPacketCodec : OutgoingPacketCodec<SpawnPaintingPacket>(0x03, SpawnPaintingPacket::class) {
|
object SpawnPaintingPacketCodec : OutgoingPacketCodec<SpawnPaintingPacket>(0x03, SpawnPaintingPacket::class) {
|
||||||
override fun SpawnPaintingPacket.encode(dst: ByteBuf) {
|
override fun SpawnPaintingPacket.encode(dst: ByteBuf) {
|
||||||
|
@ -29,19 +25,4 @@ object SpawnPaintingPacketCodec : OutgoingPacketCodec<SpawnPaintingPacket>(0x03,
|
||||||
CardinalDirection.EAST -> 3
|
CardinalDirection.EAST -> 3
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getPacketFromEntity(entity: PaintingEntity) = SpawnPaintingPacket(
|
|
||||||
@Suppress("DEPRECATION")
|
|
||||||
entity.numericID,
|
|
||||||
entity.uuid,
|
|
||||||
entity.motive,
|
|
||||||
getCenterLocation(entity.topLeftLocation, entity.motive),
|
|
||||||
entity.direction
|
|
||||||
)
|
|
||||||
|
|
||||||
private fun getCenterLocation(topLeftLocation: VoxelLocation, motive: PaintingMotive): VoxelLocation =
|
|
||||||
topLeftLocation.copy(
|
|
||||||
x = max(0, motive.width / 2) + topLeftLocation.x,
|
|
||||||
z = motive.height / 2 + topLeftLocation.z
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,44 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2020-2021 Moritz Ruth and Uranos contributors
|
||||||
|
* Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file
|
||||||
|
*/
|
||||||
|
|
||||||
|
package space.uranos.util
|
||||||
|
|
||||||
|
import space.uranos.entity.*
|
||||||
|
import space.uranos.net.packet.OutgoingPacket
|
||||||
|
import space.uranos.net.packet.play.SpawnLivingEntityPacket
|
||||||
|
import space.uranos.net.packet.play.SpawnObjectEntityPacket
|
||||||
|
import space.uranos.net.packet.play.SpawnPaintingPacket
|
||||||
|
|
||||||
|
fun Entity.createSpawnPacket(): OutgoingPacket = when (this) {
|
||||||
|
is LivingEntity -> SpawnLivingEntityPacket(
|
||||||
|
numericID,
|
||||||
|
uuid,
|
||||||
|
type,
|
||||||
|
position,
|
||||||
|
headPitch,
|
||||||
|
velocity
|
||||||
|
)
|
||||||
|
is ObjectEntity -> SpawnObjectEntityPacket(
|
||||||
|
numericID,
|
||||||
|
uuid,
|
||||||
|
type,
|
||||||
|
position,
|
||||||
|
getDataValue(),
|
||||||
|
velocity
|
||||||
|
)
|
||||||
|
is PaintingEntity -> SpawnPaintingPacket(
|
||||||
|
numericID,
|
||||||
|
uuid,
|
||||||
|
motive,
|
||||||
|
centerLocation,
|
||||||
|
direction
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun ObjectEntity.getDataValue(): Int = when (this) {
|
||||||
|
is ItemEntity -> 1
|
||||||
|
// TODO: Add remaining
|
||||||
|
else -> throw IllegalArgumentException("Unknown entity type")
|
||||||
|
}
|
|
@ -0,0 +1,15 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2020-2021 Moritz Ruth and Uranos contributors
|
||||||
|
* Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file
|
||||||
|
*/
|
||||||
|
|
||||||
|
package space.uranos.net.packet.play
|
||||||
|
|
||||||
|
import space.uranos.net.packet.Mergeable
|
||||||
|
import space.uranos.net.packet.OutgoingPacket
|
||||||
|
|
||||||
|
class DestroyEntitiesPacket(val entityIDs: Array<Int>) : OutgoingPacket(), Mergeable {
|
||||||
|
override fun mergeWith(otherPacket: OutgoingPacket): OutgoingPacket? {
|
||||||
|
return (otherPacket as? DestroyEntitiesPacket)?.let { DestroyEntitiesPacket(it.entityIDs + otherPacket.entityIDs) }
|
||||||
|
}
|
||||||
|
}
|
|
@ -11,17 +11,21 @@ import com.sksamuel.hoplite.ConfigSource
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
import space.uranos.command.Command
|
import space.uranos.command.Command
|
||||||
import space.uranos.config.UranosConfig
|
import space.uranos.config.UranosConfig
|
||||||
|
import space.uranos.entity.event.ViewingChangedEvent
|
||||||
|
import space.uranos.event.EventHandlerPosition
|
||||||
import space.uranos.event.UranosEventBus
|
import space.uranos.event.UranosEventBus
|
||||||
import space.uranos.event.UranosEventHandlerPositionManager
|
import space.uranos.event.UranosEventHandlerPositionManager
|
||||||
import space.uranos.logging.Logger
|
import space.uranos.logging.Logger
|
||||||
import space.uranos.logging.UranosLoggingOutputProvider
|
import space.uranos.logging.UranosLoggingOutputProvider
|
||||||
import space.uranos.net.UranosSocketServer
|
import space.uranos.net.UranosSocketServer
|
||||||
|
import space.uranos.net.packet.play.DestroyEntitiesPacket
|
||||||
import space.uranos.net.packet.play.PlayerInfoPacket
|
import space.uranos.net.packet.play.PlayerInfoPacket
|
||||||
import space.uranos.player.UranosPlayer
|
import space.uranos.player.UranosPlayer
|
||||||
import space.uranos.plugin.UranosPluginManager
|
import space.uranos.plugin.UranosPluginManager
|
||||||
import space.uranos.recipe.Recipe
|
import space.uranos.recipe.Recipe
|
||||||
import space.uranos.server.Server
|
import space.uranos.server.Server
|
||||||
import space.uranos.util.EncryptionUtils
|
import space.uranos.util.EncryptionUtils
|
||||||
|
import space.uranos.util.createSpawnPacket
|
||||||
import space.uranos.util.msToTicks
|
import space.uranos.util.msToTicks
|
||||||
import space.uranos.util.runInServerThread
|
import space.uranos.util.runInServerThread
|
||||||
import space.uranos.world.BiomeRegistry
|
import space.uranos.world.BiomeRegistry
|
||||||
|
@ -106,6 +110,7 @@ class UranosServer internal constructor() : Server() {
|
||||||
logger info "Listening on ${config.host}:${config.port}"
|
logger info "Listening on ${config.host}:${config.port}"
|
||||||
|
|
||||||
scheduler.start()
|
scheduler.start()
|
||||||
|
registerListeners()
|
||||||
startTicking()
|
startTicking()
|
||||||
startPingSync()
|
startPingSync()
|
||||||
}
|
}
|
||||||
|
@ -135,6 +140,13 @@ class UranosServer internal constructor() : Server() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun registerListeners() {
|
||||||
|
eventBus.on<ViewingChangedEvent>(EventHandlerPosition.LAST) { event ->
|
||||||
|
if (event.viewing) event.player.session.sendNextTick(event.target.createSpawnPacket())
|
||||||
|
else event.player.session.sendNextTick(DestroyEntitiesPacket(arrayOf(event.target.numericID)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
val VERSION = UranosServer::class.java.`package`.implementationVersion ?: "development"
|
val VERSION = UranosServer::class.java.`package`.implementationVersion ?: "development"
|
||||||
val VERSION_WITH_V = if (VERSION == "development") VERSION else "v$VERSION"
|
val VERSION_WITH_V = if (VERSION == "development") VERSION else "v$VERSION"
|
||||||
|
|
|
@ -7,6 +7,7 @@ package space.uranos.net
|
||||||
|
|
||||||
import io.netty.buffer.ByteBuf
|
import io.netty.buffer.ByteBuf
|
||||||
import io.netty.util.ReferenceCountUtil
|
import io.netty.util.ReferenceCountUtil
|
||||||
|
import kotlinx.coroutines.CancellationException
|
||||||
import kotlinx.coroutines.channels.Channel
|
import kotlinx.coroutines.channels.Channel
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import space.uranos.event.ifNotCancelled
|
import space.uranos.event.ifNotCancelled
|
||||||
|
@ -39,7 +40,17 @@ class PacketsAdapter(val session: UranosSession) {
|
||||||
if (session.server.config.logging.shouldLog(packet)) session.logger.trace { "Packet received: $packet" }
|
if (session.server.config.logging.shouldLog(packet)) session.logger.trace { "Packet received: $packet" }
|
||||||
|
|
||||||
session.server.eventBus.emit(PacketReceivedEvent(session, packet)).ifNotCancelled {
|
session.server.eventBus.emit(PacketReceivedEvent(session, packet)).ifNotCancelled {
|
||||||
|
try {
|
||||||
SessionPacketReceivedEventHandler.handle(session, packet)
|
SessionPacketReceivedEventHandler.handle(session, packet)
|
||||||
|
} catch (e: CancellationException) {
|
||||||
|
throw e
|
||||||
|
} catch (e: Exception) {
|
||||||
|
session.logger.error(
|
||||||
|
"An error occurred while handling a packet " +
|
||||||
|
"(${packet::class.simpleName!!.removeSuffix("Packet")})",
|
||||||
|
e
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -47,6 +47,8 @@ class UranosSession(val channel: io.netty.channel.Channel, val server: UranosSer
|
||||||
field = value
|
field = value
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val earlyPlayer get() = (state as? State.WithPlayer)?.player
|
||||||
|
|
||||||
override var state: State = State.WaitingForHandshake
|
override var state: State = State.WaitingForHandshake
|
||||||
|
|
||||||
override val currentProtocol: Protocol?
|
override val currentProtocol: Protocol?
|
||||||
|
@ -112,7 +114,7 @@ class UranosSession(val channel: io.netty.channel.Channel, val server: UranosSer
|
||||||
lateinit var keepAliveDisconnectJob: Job
|
lateinit var keepAliveDisconnectJob: Job
|
||||||
|
|
||||||
fun scheduleKeepAlivePacket(isFirst: Boolean = false) {
|
fun scheduleKeepAlivePacket(isFirst: Boolean = false) {
|
||||||
scope.launch {
|
scope.launch { // TODO: Fix random disconnects (maybe some response packets are skipped?)
|
||||||
if (!isFirst) {
|
if (!isFirst) {
|
||||||
val timeSinceLastPacket = (System.currentTimeMillis() - lastKeepAlivePacketTimestamp).toInt()
|
val timeSinceLastPacket = (System.currentTimeMillis() - lastKeepAlivePacketTimestamp).toInt()
|
||||||
delay(KEEP_ALIVE_PACKET_INTERVAL.toLong() - timeSinceLastPacket)
|
delay(KEEP_ALIVE_PACKET_INTERVAL.toLong() - timeSinceLastPacket)
|
||||||
|
|
|
@ -10,6 +10,6 @@ import space.uranos.net.UranosSession
|
||||||
|
|
||||||
object IncomingPlayerPositionPacketHandler : PacketReceivedEventHandler<IncomingPlayerPositionPacket>() {
|
object IncomingPlayerPositionPacketHandler : PacketReceivedEventHandler<IncomingPlayerPositionPacket>() {
|
||||||
override suspend fun handle(session: UranosSession, packet: IncomingPlayerPositionPacket) {
|
override suspend fun handle(session: UranosSession, packet: IncomingPlayerPositionPacket) {
|
||||||
session.player!!.entity.position = packet.position
|
session.earlyPlayer?.let { it.entity.position = packet.position } ?: error("Player not yet initialized")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,7 +10,7 @@ import space.uranos.net.UranosSession
|
||||||
|
|
||||||
object PlayerLocationPacketHandler : PacketReceivedEventHandler<PlayerLocationPacket>() {
|
object PlayerLocationPacketHandler : PacketReceivedEventHandler<PlayerLocationPacket>() {
|
||||||
override suspend fun handle(session: UranosSession, packet: PlayerLocationPacket) {
|
override suspend fun handle(session: UranosSession, packet: PlayerLocationPacket) {
|
||||||
val player = session.player!!
|
val player = session.earlyPlayer ?: error("Player not yet initialized")
|
||||||
player.entity.position = player.entity.position.copy(
|
player.entity.position = player.entity.position.copy(
|
||||||
x = packet.location.x,
|
x = packet.location.x,
|
||||||
y = packet.location.y,
|
y = packet.location.y,
|
||||||
|
|
|
@ -10,7 +10,7 @@ import space.uranos.net.UranosSession
|
||||||
|
|
||||||
object PlayerOrientationPacketHandler : PacketReceivedEventHandler<PlayerOrientationPacket>() {
|
object PlayerOrientationPacketHandler : PacketReceivedEventHandler<PlayerOrientationPacket>() {
|
||||||
override suspend fun handle(session: UranosSession, packet: PlayerOrientationPacket) {
|
override suspend fun handle(session: UranosSession, packet: PlayerOrientationPacket) {
|
||||||
val player = session.player!!
|
session.earlyPlayer?.entity?.let { it.position = it.position.copy(yaw = packet.yaw, pitch = packet.pitch) }
|
||||||
player.entity.position = player.entity.position.copy(yaw = packet.yaw, pitch = packet.pitch)
|
?: error("Player not yet initialized")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
package space.uranos.player
|
package space.uranos.player
|
||||||
|
|
||||||
import space.uranos.Position
|
import space.uranos.Position
|
||||||
|
import space.uranos.Uranos
|
||||||
import space.uranos.chat.TextComponent
|
import space.uranos.chat.TextComponent
|
||||||
import space.uranos.entity.PlayerEntity
|
import space.uranos.entity.PlayerEntity
|
||||||
import space.uranos.net.UranosSession
|
import space.uranos.net.UranosSession
|
||||||
|
@ -15,6 +16,7 @@ import space.uranos.net.packet.play.PlayerInfoPacket
|
||||||
import space.uranos.net.packet.play.SelectedHotbarSlotPacket
|
import space.uranos.net.packet.play.SelectedHotbarSlotPacket
|
||||||
import space.uranos.util.TickSynchronizationContainer
|
import space.uranos.util.TickSynchronizationContainer
|
||||||
import space.uranos.util.clampArgument
|
import space.uranos.util.clampArgument
|
||||||
|
import space.uranos.util.createSpawnPacket
|
||||||
import space.uranos.world.Chunk
|
import space.uranos.world.Chunk
|
||||||
import space.uranos.world.VoxelLocation
|
import space.uranos.world.VoxelLocation
|
||||||
import space.uranos.world.World
|
import space.uranos.world.World
|
||||||
|
@ -62,9 +64,11 @@ class UranosPlayer(
|
||||||
override val entity: PlayerEntity = PlayerEntity(position, this, headPitch)
|
override val entity: PlayerEntity = PlayerEntity(position, this, headPitch)
|
||||||
|
|
||||||
suspend fun spawnInitially(world: World) {
|
suspend fun spawnInitially(world: World) {
|
||||||
|
Uranos.entities.forEach { if (it.visibleToNewPlayers) it.viewers.add(this) }
|
||||||
entity.setWorld(world)
|
entity.setWorld(world)
|
||||||
updateCurrentlyViewedChunks()
|
updateCurrentlyViewedChunks()
|
||||||
sendChunksAndLight()
|
sendChunksAndLight()
|
||||||
|
sendEntitiesInViewedChunks()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -89,4 +93,15 @@ class UranosPlayer(
|
||||||
chunks.forEach { session.send(ChunkLightDataPacket(it.key, it.getLightData(this))) }
|
chunks.forEach { session.send(ChunkLightDataPacket(it.key, it.getLightData(this))) }
|
||||||
chunks.forEach { session.send(ChunkDataPacket(it.key, it.getData(this))) }
|
chunks.forEach { session.send(ChunkDataPacket(it.key, it.getData(this))) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private suspend fun sendEntitiesInViewedChunks() {
|
||||||
|
val world = entity.safeWorld
|
||||||
|
val entities = world { world.entities.toList() }
|
||||||
|
|
||||||
|
entities
|
||||||
|
.asSequence()
|
||||||
|
.filter { it != entity && it.viewers.contains(this) }
|
||||||
|
.filter { entity -> currentlyViewedChunks.any { it.key == entity.chunkKey } }
|
||||||
|
.forEach { session.sendNextTick(it.createSpawnPacket()) }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Reference in a new issue