diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml
new file mode 100644
index 0000000..a55e7a1
--- /dev/null
+++ b/.idea/codeStyles/codeStyleConfig.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/README.md b/README.md
index 4945e5f..f3b0ea3 100644
--- a/README.md
+++ b/README.md
@@ -20,6 +20,12 @@
- Scoreboards + Teams
- Crafting
+## Development
+
+Because IntelliJ sometimes removes needed imports for types which have the same name as types in `java.util` when
+running the `Optimize Imports` action, you need to disable automatic wildcard imports for this package
+(Editor / Code Style / Kotlin / Imports / Packages to Use Import With '*').
+
## Conventions
### KDoc
diff --git a/buildSrc/src/main/kotlin/space/uranos/mdsp/generator/Constants.kt b/buildSrc/src/main/kotlin/space/uranos/mdsp/generator/Constants.kt
index c823318..e38940a 100644
--- a/buildSrc/src/main/kotlin/space/uranos/mdsp/generator/Constants.kt
+++ b/buildSrc/src/main/kotlin/space/uranos/mdsp/generator/Constants.kt
@@ -16,7 +16,7 @@ const val TAG_PACKAGE = "$BASE_PACKAGE.tag"
val MATERIAL_TYPE = ClassName(BLOCK_PACKAGE, "Material")
val BLOCK_TYPE = ClassName(BLOCK_PACKAGE, "Block")
val ITEM_TYPE_TYPE = ClassName(ITEM_PACKAGE, "ItemType")
+val ENTITY_TYPE = ClassName(ENTITY_PACKAGE, "Entity")
val ENTITY_TYPE_TYPE = ClassName(ENTITY_PACKAGE, "EntityType")
val TAG_TYPE = ClassName(TAG_PACKAGE, "Tag")
val TAG_TYPE_TYPE = TAG_TYPE.nestedClass("Type")
-
diff --git a/buildSrc/src/main/kotlin/space/uranos/mdsp/generator/EntitiesGenerator.kt b/buildSrc/src/main/kotlin/space/uranos/mdsp/generator/EntitiesGenerator.kt
index 5fe71f8..ac33bcd 100644
--- a/buildSrc/src/main/kotlin/space/uranos/mdsp/generator/EntitiesGenerator.kt
+++ b/buildSrc/src/main/kotlin/space/uranos/mdsp/generator/EntitiesGenerator.kt
@@ -11,6 +11,7 @@ import com.squareup.kotlinpoet.*
import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy
import space.uranos.mdsp.JsonAny
import java.io.File
+import kotlin.reflect.KClass
class EntitiesGenerator(
private val workingDir: File,
@@ -28,13 +29,28 @@ class EntitiesGenerator(
private fun generateEntityTypes(types: List) {
for (entity in types) {
- val name =
- CaseFormat.LOWER_UNDERSCORE.to(CaseFormat.UPPER_CAMEL, entity.get("name").toString())!! + "EntityType"
+ val entityClassName =
+ CaseFormat.LOWER_UNDERSCORE.to(CaseFormat.UPPER_CAMEL, entity.get("name").toString())!! +
+ "Entity"
+
+ val entityClass = ClassName(ENTITY_PACKAGE, entityClassName)
+
+ val name = entityClassName + "Type"
val type = TypeSpec.classBuilder(name)
.addModifiers(KModifier.ABSTRACT)
.primaryConstructor(FunSpec.constructorBuilder().addModifiers(KModifier.INTERNAL).build())
- .addSuperinterface(ENTITY_TYPE_TYPE)
+ .addSuperinterface(ENTITY_TYPE_TYPE.parameterizedBy(entityClass))
+ .addProperty(
+ PropertySpec
+ .builder(
+ "interfaceType",
+ KClass::class.asTypeName().parameterizedBy(entityClass),
+ KModifier.OVERRIDE
+ )
+ .initializer("%T::class", entityClass)
+ .build()
+ )
.addProperty(
PropertySpec
.builder("numericID", Int::class, KModifier.OVERRIDE)
@@ -78,17 +94,19 @@ class EntitiesGenerator(
val path = "./${ENTITY_PACKAGE.replace(".", "/")}/$name.kt"
if (sourcesDir.resolve(path).exists()) continue
- outputDir.resolve(path).writeText(
- """
- package $ENTITY_PACKAGE
-
- // open class AreaEffectCloudEntity : Entity() {
- // final override val type: EntityType = Type
- //
- // companion object Type : AreaEffectCloudEntityType()
- // }
- """.trimIndent()
- )
+ val type = TypeSpec.interfaceBuilder(name)
+ .addSuperinterface(ENTITY_TYPE)
+ .addType(
+ TypeSpec.companionObjectBuilder("Type")
+ .superclass(ClassName(ENTITY_PACKAGE, name + "Type"))
+ .build()
+ )
+ .build()
+
+ FileSpec.builder(ENTITY_PACKAGE, name)
+ .addType(type)
+ .build()
+ .writeTo(outputDir)
}
}
@@ -97,20 +115,32 @@ class EntitiesGenerator(
.map { it.get("name").toString() }
.map { CaseFormat.LOWER_UNDERSCORE.to(CaseFormat.UPPER_CAMEL, it) + "Entity" }
- val property = PropertySpec.builder(
+ val listProperty = PropertySpec.builder(
"ENTITY_TYPES",
- List::class.asTypeName().parameterizedBy(ENTITY_TYPE_TYPE)
+ List::class.asTypeName().parameterizedBy(ENTITY_TYPE_TYPE.parameterizedBy(STAR))
)
- .initializer("listOf(\n${
- names.joinToString(",\n") {
- val path = "./${ENTITY_PACKAGE.replace(".", "/")}/$it.kt"
- if (sourcesDir.resolve(path).exists()) it else "object : ${it}Type() {}"
- }
- }\n)")
+ .initializer("listOf(\n${names.joinToString(",\n")}\n)")
+ .build()
+
+ val typeVariableName = TypeVariableName("T", ENTITY_TYPE)
+
+ val typeProperty = PropertySpec.builder("type", ENTITY_TYPE_TYPE.parameterizedBy(typeVariableName))
+ .addTypeVariable(typeVariableName)
+ .receiver(typeVariableName)
+ .addAnnotation(AnnotationSpec.builder(Suppress::class).addMember(""""UNCHECKED_CAST"""").build())
+ .getter(
+ FunSpec.getterBuilder()
+ .addStatement(
+ "return when(this) {\n${names.joinToString("\n") { "is $it -> $it" }.prependIndent(" ")}\n" +
+ " else -> error(\"Unknown entity type\")\n} as EntityType"
+ )
+ .build()
+ )
.build()
FileSpec.builder(ENTITY_PACKAGE, "EntityTypes")
- .addProperty(property)
+ .addProperty(typeProperty)
+ .addProperty(listProperty)
.build()
.writeTo(outputDir)
}
diff --git a/test-plugin/src/main/kotlin/space/uranos/testplugin/TestPlugin.kt b/test-plugin/src/main/kotlin/space/uranos/testplugin/TestPlugin.kt
index 2d1ea46..78aa0f7 100644
--- a/test-plugin/src/main/kotlin/space/uranos/testplugin/TestPlugin.kt
+++ b/test-plugin/src/main/kotlin/space/uranos/testplugin/TestPlugin.kt
@@ -18,10 +18,7 @@ import space.uranos.plugin.Plugin
import space.uranos.testplugin.anvil.AnvilWorld
import space.uranos.util.RGBColor
import space.uranos.util.secondsToTicks
-import space.uranos.world.Biome
-import space.uranos.world.Chunk
-import space.uranos.world.Dimension
-import space.uranos.world.VoxelLocation
+import space.uranos.world.*
import space.uranos.world.block.GreenWoolBlock
import space.uranos.world.block.RedWoolBlock
@@ -49,20 +46,22 @@ class TestPlugin: Plugin("Test", "1.0.0") {
Uranos.dimensionRegistry.register(dimension)
Uranos.biomeRegistry.register(biome)
- val world = AnvilWorld(dimension, true)
+ val world = AnvilWorld(dimension, true, null)
+ Uranos.worldRegistry.register(world.internals)
+
world.getVoxelsInCuboid(VoxelLocation.of(16, 0, 16), VoxelLocation.of(-16, 0, -16)).forEach {
it.block = if (it.location.x % 16 == 0 || it.location.z % 16 == 0) GreenWoolBlock() else RedWoolBlock()
}
world.getVoxelsInSphere(VoxelLocation.of(20, 50, 20), 40.0).forEach { it.block = RedWoolBlock() }
-
world.getChunk(Chunk.Key(0, 0)).setBiome(0, 0, 0, biome)
Uranos.eventBus.on { event ->
event.response = ServerListInfo("1.16.4", 754, TextComponent of "Test", 10, 0, emptyList())
}
- val entity = CowEntity(Position(0.0, 10.0, 0.0, 0f, 0f), 0f)
+ val entity = Uranos.create()
+ entity.position = Position(0.0, 10.0, 0.0, 0f, 0f)
entity.setWorld(world)
Uranos.eventBus.on { event ->
diff --git a/test-plugin/src/main/kotlin/space/uranos/testplugin/anvil/AnvilWorld.kt b/test-plugin/src/main/kotlin/space/uranos/testplugin/anvil/AnvilWorld.kt
index 18d967f..f3493fc 100644
--- a/test-plugin/src/main/kotlin/space/uranos/testplugin/anvil/AnvilWorld.kt
+++ b/test-plugin/src/main/kotlin/space/uranos/testplugin/anvil/AnvilWorld.kt
@@ -8,6 +8,9 @@ package space.uranos.testplugin.anvil
import com.google.common.cache.CacheBuilder
import com.google.common.cache.CacheLoader
import com.google.common.cache.LoadingCache
+import kotlinx.coroutines.CoroutineDispatcher
+import space.uranos.entity.Entity
+import space.uranos.util.newSingleThreadDispatcher
import space.uranos.world.Chunk
import space.uranos.world.Dimension
import space.uranos.world.World
@@ -15,8 +18,10 @@ import java.util.*
class AnvilWorld(
override val dimension: Dimension,
- override val isFlat: Boolean
-) : World(UUID.randomUUID()) {
+ override val isFlat: Boolean,
+ override val seed: Long?,
+) : World {
+ override val dispatcher: CoroutineDispatcher = newSingleThreadDispatcher("AnvilWorld")
override val loadedChunks: Map get() = chunks.asMap().filter { (_, chunk) -> chunk.loaded }
private val chunks: LoadingCache = CacheBuilder.newBuilder()
@@ -27,4 +32,18 @@ class AnvilWorld(
})
override fun getChunk(key: Chunk.Key): AnvilChunk = chunks.get(key)
+
+ override val entities = mutableSetOf()
+
+ val internals = object : World.Internals {
+ override val world: World = this@AnvilWorld
+
+ override fun addEntity(entity: Entity) {
+ entities.add(entity)
+ }
+
+ override fun removeEntity(entity: Entity) {
+ entities.remove(entity)
+ }
+ }
}
diff --git a/uranos-api/src/main/kotlin/space/uranos/Registry.kt b/uranos-api/src/main/kotlin/space/uranos/Registry.kt
index 7adcac4..8bc57bb 100644
--- a/uranos-api/src/main/kotlin/space/uranos/Registry.kt
+++ b/uranos-api/src/main/kotlin/space/uranos/Registry.kt
@@ -5,10 +5,8 @@
package space.uranos
-import java.util.concurrent.ConcurrentHashMap
-
open class Registry {
- protected val internalItems = ConcurrentHashMap()
+ protected val internalItems = HashMap()
val items: Map = internalItems
/**
diff --git a/uranos-api/src/main/kotlin/space/uranos/entity/CowEntity.kt b/uranos-api/src/main/kotlin/space/uranos/entity/CowEntity.kt
deleted file mode 100644
index 62a089e..0000000
--- a/uranos-api/src/main/kotlin/space/uranos/entity/CowEntity.kt
+++ /dev/null
@@ -1,16 +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
-
-open class CowEntity(position: Position, override var headPitch: Float) : LivingEntity(position) {
- final override val type: EntityType = Type
- override var velocity: Vector = Vector.ZERO
-
- companion object Type : CowEntityType()
-}
diff --git a/uranos-api/src/main/kotlin/space/uranos/entity/Entity.kt b/uranos-api/src/main/kotlin/space/uranos/entity/Entity.kt
index bacceae..b5c53ca 100644
--- a/uranos-api/src/main/kotlin/space/uranos/entity/Entity.kt
+++ b/uranos-api/src/main/kotlin/space/uranos/entity/Entity.kt
@@ -5,41 +5,15 @@
package space.uranos.entity
-import kotlinx.coroutines.launch
-import kotlinx.coroutines.sync.Mutex
-import kotlinx.coroutines.sync.withLock
-import space.uranos.*
-import space.uranos.entity.event.ViewingChangedEvent
+import com.google.common.cache.CacheBuilder
+import com.google.common.cache.CacheLoader
import space.uranos.event.EventBusWrapper
import space.uranos.player.Player
-import space.uranos.util.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 java.util.*
-import kotlin.math.max
-
-private typealias Vector = space.uranos.Vector
-
-sealed class Entity {
- internal var eventBusWrapper: EventBusWrapper<*>? = null
- protected val container = TickSynchronizationContainer()
-
- @Deprecated(
- "This function should only be called by the server.",
- ReplaceWith(""),
- DeprecationLevel.ERROR
- )
- suspend fun tick() {
- onTick()
- container.tick()
- }
-
- protected open fun onTick() {}
- protected open fun onWorldSet() {}
+import java.util.UUID
+interface Entity {
/**
* The UUID of this entity.
*
@@ -48,98 +22,40 @@ sealed class Entity {
*
* Otherwise, it is usually randomly generated.
*/
- open val uuid: UUID = UUID.randomUUID()
-
- abstract val type: EntityType
- abstract val chunkKey: Chunk.Key
-
- /**
- * Players that can see this entity.
- */
- val viewers: MutableSet = object : WatchableSet(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()
- var world: World? = null; protected set
-
- suspend fun setWorld(world: World?) {
- if (world == null && this is PlayerEntity)
- throw IllegalArgumentException("You cannot set the world of a PlayerEntity to null")
-
- if (world == this.world) return
-
- worldMutex.withLock {
- this.world?.internalEntities?.remove(this)
- this.world = world
- world?.internalEntities?.add(this)
- }
-
- onWorldSet()
- }
-
- /**
- * Returns [world] if it is not null, otherwise throws [IllegalStateException].
- */
- fun getWorldOrFail() = world ?: throw IllegalStateException("This entity has not been spawned")
+ val uuid: UUID
/**
* An integer unique to this entity which will not be persisted, for example when the entity is serialized.
*/
- @Suppress("LeakingThis")
- val numericID: Int = Uranos.registerEntity(this)
+ val numericID: Int
+
+ /**
+ * Players that can see this entity.
+ */
+ val viewers: MutableSet
+
+ fun belongsToChunk(key: Chunk.Key): Boolean
+
+ val world: World?
+ suspend fun setWorld(world: World?)
+
+ /**
+ * If players should be added to [viewers] when they join.
+ */
+ var visibleToNewPlayers: Boolean
}
-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
+/**
+ * Returns [Entity.world] if it is not null, otherwise throws [IllegalStateException].
+ */
+fun Entity.getWorldOrFail() = world ?: throw IllegalStateException("This entity has not been spawned")
- 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()
-}
+private val eventBusWrapperCache = CacheBuilder.newBuilder()
+ .weakKeys()
+ .build(object : CacheLoader>() {
+ override fun load(key: Entity): EventBusWrapper = EventBusWrapper(key)
+ })
@Suppress("UNCHECKED_CAST")
-fun T.events(): EventBusWrapper {
- val wrapper = eventBusWrapper
-
- return if (wrapper == null) EventBusWrapper(this).also { eventBusWrapper = it }
- else wrapper as EventBusWrapper
-}
+val T.events
+ get() = eventBusWrapperCache.get(this) as EventBusWrapper
diff --git a/uranos-api/src/main/kotlin/space/uranos/entity/EntityType.kt b/uranos-api/src/main/kotlin/space/uranos/entity/EntityType.kt
index 2069b2e..4e6cb76 100644
--- a/uranos-api/src/main/kotlin/space/uranos/entity/EntityType.kt
+++ b/uranos-api/src/main/kotlin/space/uranos/entity/EntityType.kt
@@ -7,7 +7,8 @@ package space.uranos.entity
import kotlin.reflect.KClass
-interface EntityType {
+interface EntityType {
+ val interfaceType: KClass
val numericID: Int
val id: String
val width: Float
@@ -17,7 +18,16 @@ interface EntityType {
/**
* All entity types, sorted by their numeric ID in ascending order.
*/
- val all = ENTITY_TYPES
- val byID = ENTITY_TYPES.map { it.id to it }.toMap()
+ val all: Collection> = ENTITY_TYPES
+
+ val byID: Map> = all.map { it.id to it }.toMap()
+ fun byID(id: String) = byID[id]
+
+ private val byInterfaceTypeMap: Map, EntityType<*>> =
+ ENTITY_TYPES.map { it.interfaceType to it }.toMap()
+
+ @Suppress("UNCHECKED_CAST")
+ fun byInterfaceType(interfaceType: KClass): EntityType =
+ byInterfaceTypeMap[interfaceType] as EntityType
}
}
diff --git a/uranos-api/src/main/kotlin/space/uranos/entity/ItemEntity.kt b/uranos-api/src/main/kotlin/space/uranos/entity/ItemEntity.kt
deleted file mode 100644
index 49c7657..0000000
--- a/uranos-api/src/main/kotlin/space/uranos/entity/ItemEntity.kt
+++ /dev/null
@@ -1,16 +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.world.Chunk
-
-open class ItemEntity(position: Position) : ObjectEntity(position) {
- final override val type: EntityType = Type
- override val chunkKey: Chunk.Key get() = Chunk.Key.from(position)
-
- companion object Type : ItemEntityType()
-}
diff --git a/uranos-api/src/main/kotlin/space/uranos/entity/LivingEntity.kt b/uranos-api/src/main/kotlin/space/uranos/entity/LivingEntity.kt
new file mode 100644
index 0000000..c952a6e
--- /dev/null
+++ b/uranos-api/src/main/kotlin/space/uranos/entity/LivingEntity.kt
@@ -0,0 +1,12 @@
+/*
+ * 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
+
+interface LivingEntity : Entity, Mobile {
+ // TODO: This should probably be headYaw, but wiki.vg says headPitch.
+ // And it is only used in the SpawnLivingEntity packet
+ var headPitch: Float
+}
diff --git a/uranos-api/src/main/kotlin/space/uranos/CalledFromWrongThread.kt b/uranos-api/src/main/kotlin/space/uranos/entity/ObjectEntity.kt
similarity index 66%
rename from uranos-api/src/main/kotlin/space/uranos/CalledFromWrongThread.kt
rename to uranos-api/src/main/kotlin/space/uranos/entity/ObjectEntity.kt
index 80498dd..7a7f274 100644
--- a/uranos-api/src/main/kotlin/space/uranos/CalledFromWrongThread.kt
+++ b/uranos-api/src/main/kotlin/space/uranos/entity/ObjectEntity.kt
@@ -3,6 +3,8 @@
* Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file
*/
-package space.uranos
+package space.uranos.entity
-class CalledFromWrongThread(message: String) : Exception(message)
+interface ObjectEntity : Entity, Mobile {
+
+}
diff --git a/uranos-api/src/main/kotlin/space/uranos/entity/PaintingEntity.kt b/uranos-api/src/main/kotlin/space/uranos/entity/PaintingEntity.kt
new file mode 100644
index 0000000..529686c
--- /dev/null
+++ b/uranos-api/src/main/kotlin/space/uranos/entity/PaintingEntity.kt
@@ -0,0 +1,25 @@
+/*
+ * 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
+import kotlin.math.max
+
+interface PaintingEntity : Entity {
+ val topLeftLocation: VoxelLocation
+ val facing: CardinalDirection
+ val motive: PaintingMotive
+
+ companion object Type : AreaEffectCloudEntityType()
+}
+
+val PaintingEntity.centerLocation
+ get() = topLeftLocation.copy(
+ x = max(0, motive.width / 2) + topLeftLocation.x,
+ z = motive.height / 2 + topLeftLocation.z
+ )
diff --git a/uranos-api/src/main/kotlin/space/uranos/entity/PlayerEntity.kt b/uranos-api/src/main/kotlin/space/uranos/entity/PlayerEntity.kt
index 5dc410f..ccb5e5b 100644
--- a/uranos-api/src/main/kotlin/space/uranos/entity/PlayerEntity.kt
+++ b/uranos-api/src/main/kotlin/space/uranos/entity/PlayerEntity.kt
@@ -5,26 +5,16 @@
package space.uranos.entity
-import space.uranos.Position
import space.uranos.player.Player
import space.uranos.world.World
-open class PlayerEntity(
- position: Position,
- /**
- * The player to which this entity belongs.
- *
- * **Not every player entity belongs to a player.**
- */
- open val player: Player? = null,
- override var headPitch: Float = 0f
-) : LivingEntity(position) {
- final override val type: EntityType = Type
+interface PlayerEntity : LivingEntity {
+ val player: Player
- /**
- * Because [world] is never `null` for player entities, you can use this property instead of writing `world!!`.
- */
- val safeWorld: World get() = world!!
-
- companion object Type : PlayerEntityType()
+ companion object Type : PlayerEntityType()
}
+
+/**
+ * As player entities are always in a world, you can use this property instead of [getWorldOrFail].
+ */
+val PlayerEntity.safeWorld: World get() = getWorldOrFail()
diff --git a/uranos-api/src/main/kotlin/space/uranos/server/Server.kt b/uranos-api/src/main/kotlin/space/uranos/server/Server.kt
index 2b00c8a..302df07 100644
--- a/uranos-api/src/main/kotlin/space/uranos/server/Server.kt
+++ b/uranos-api/src/main/kotlin/space/uranos/server/Server.kt
@@ -12,6 +12,7 @@ import space.uranos.Registry
import space.uranos.Scheduler
import space.uranos.command.Command
import space.uranos.entity.Entity
+import space.uranos.entity.EntityType
import space.uranos.event.EventBus
import space.uranos.event.EventHandlerPositionManager
import space.uranos.logging.Logger
@@ -23,9 +24,8 @@ import space.uranos.recipe.Recipe
import space.uranos.util.newSingleThreadDispatcher
import space.uranos.world.BiomeRegistry
import space.uranos.world.Dimension
+import space.uranos.world.WorldRegistry
import java.io.File
-import java.util.*
-import java.util.concurrent.atomic.AtomicInteger
abstract class Server {
abstract val eventBus: EventBus
@@ -55,10 +55,11 @@ abstract class Server {
abstract val serverDirectory: File
val cacheDirectory: File get() = serverDirectory.resolve("cache")
- abstract val dimensionRegistry: Registry
- abstract val recipeRegistry: Registry
- abstract val commandRegistry: Registry
- abstract val biomeRegistry: BiomeRegistry
+ abstract val worldRegistry: WorldRegistry
+ val dimensionRegistry: Registry = Registry()
+ val recipeRegistry: Registry = Registry()
+ val commandRegistry: Registry = Registry()
+ val biomeRegistry: BiomeRegistry = BiomeRegistry()
abstract val loggingOutputProvider: LoggingOutputProvider
abstract val scheduler: Scheduler
@@ -66,27 +67,24 @@ abstract class Server {
abstract val developmentMode: Boolean
abstract val minimumLogLevel: Logger.Level
+ /**
+ * Set of all existing [Entity] instances.
+ */
+ abstract val entities: Set
+
+ /**
+ * Create a new entity of [type].
+ *
+ * @throws IllegalArgumentException When [type] is [PlayerEntity][space.uranos.entity.PlayerEntity].
+ */
+ abstract fun create(type: EntityType): T
+ inline fun create(): T = create(EntityType.byInterfaceType(T::class))
+
/**
* Initiates shutting down the server.
*/
abstract fun shutdown()
- /**
- * Set of all existing [Entity] instances.
- */
- private val internalEntities: MutableSet = Collections.newSetFromMap(WeakHashMap())
- val entities: Set = internalEntities
-
- private val nextEntityID = AtomicInteger(1)
-
- /**
- * Returns the UID for [entity].
- */
- internal fun registerEntity(entity: Entity): Int {
- internalEntities.add(entity)
- return nextEntityID.getAndIncrement()
- }
-
companion object {
const val TICKS_PER_SECOND = 20
}
diff --git a/uranos-api/src/main/kotlin/space/uranos/world/Chunk.kt b/uranos-api/src/main/kotlin/space/uranos/world/Chunk.kt
index 34df482..8254db7 100644
--- a/uranos-api/src/main/kotlin/space/uranos/world/Chunk.kt
+++ b/uranos-api/src/main/kotlin/space/uranos/world/Chunk.kt
@@ -16,7 +16,7 @@ abstract class Chunk(
val world: World,
val key: Key
) {
- private val identifier = "Chunk(${world.uuid}/${key.x}-${key.z})"
+ private val identifier = "Chunk(${key.x}-${key.z})"
data class Key(val x: Int, val z: Int) {
companion object {
diff --git a/uranos-api/src/main/kotlin/space/uranos/world/World.kt b/uranos-api/src/main/kotlin/space/uranos/world/World.kt
index 68744c6..5dfe6e0 100644
--- a/uranos-api/src/main/kotlin/space/uranos/world/World.kt
+++ b/uranos-api/src/main/kotlin/space/uranos/world/World.kt
@@ -5,35 +5,26 @@
package space.uranos.world
-import kotlinx.coroutines.*
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.withContext
import space.uranos.Vector
import space.uranos.entity.Entity
-import space.uranos.util.newSingleThreadDispatcher
import space.uranos.util.untilPossiblyLower
-import java.util.*
-import kotlin.collections.HashSet
-/**
- * A Minecraft world.
- */
-abstract class World(val uuid: UUID) {
- private val identifier = "World($uuid)"
+interface World {
+ val dispatcher: CoroutineDispatcher
+ val dimension: Dimension
+ val isFlat: Boolean
+ val entities: Collection
+ val loadedChunks: Map
- private val internalDispatcher = newSingleThreadDispatcher(identifier)
+ interface Internals {
+ val world: World
- /**
- * A coroutine dispatcher that is confined to the server thread.
- */
- val dispatcher: CoroutineDispatcher = internalDispatcher
-
- /**
- * A coroutine scope confined to [dispatcher] that is cancelled when the world is destroyed.
- */
- val scope = CoroutineScope(SupervisorJob() + dispatcher)
-
- abstract val dimension: Dimension
- abstract val loadedChunks: Map
- abstract val isFlat: Boolean
+ fun addEntity(entity: Entity)
+ fun removeEntity(entity: Entity)
+ }
/**
* This can be any value.
@@ -42,77 +33,60 @@ abstract class World(val uuid: UUID) {
* (and it seems not to use it in any way).
* A random value will be used if this is null.
*/
- open val seed: Long? = null
+ val seed: Long?
- /**
- * All entities in this world.
- */
- internal val internalEntities = HashSet()
+ fun getChunk(key: Chunk.Key): Chunk
+}
- val entities get() = internalEntities.toList()
+/**
+ * Returns the chunk containing the voxel at [location].
+ */
+fun World.getChunk(location: VoxelLocation): Chunk = getChunk(Chunk.Key.from(location))
- abstract fun getChunk(key: Chunk.Key): Chunk
+/**
+ * Returns the voxel at [location].
+ */
+fun World.getVoxel(location: VoxelLocation): Voxel = getChunk(location).getVoxel(location)
- /**
- * Returns the chunk containing the voxel at [location].
- */
- fun getChunk(location: VoxelLocation): Chunk = getChunk(Chunk.Key.from(location))
+/**
+ * Returns all voxels in the cuboid with the corner points [cornerA] and [cornerB].
+ *
+ * @param hollow Whether the cuboid is hollow
+ */
+fun World.getVoxelsInCuboid(cornerA: VoxelLocation, cornerB: VoxelLocation, hollow: Boolean = false): List {
+ fun getList(a: Int, b: Int) =
+ if (hollow) listOf(a, b).distinct()
+ else (a untilPossiblyLower b).toList()
- /**
- * Returns the voxel at [location].
- */
- fun getVoxel(location: VoxelLocation): Voxel = getChunk(location).getVoxel(location)
-
- /**
- * Returns all voxels in the cuboid with the corner points [cornerA] and [cornerB].
- *
- * @param hollow Whether the cuboid is hollow
- */
- fun getVoxelsInCuboid(cornerA: VoxelLocation, cornerB: VoxelLocation, hollow: Boolean = false): List {
- fun getList(a: Int, b: Int) =
- if (hollow) listOf(a, b).distinct()
- else (a untilPossiblyLower b).toList()
-
- return buildList {
- for (x in getList(cornerA.x, cornerB.x)) {
- for (y in getList(cornerA.y.toInt(), cornerB.y.toInt())) {
- for (z in getList(cornerA.z, cornerB.z)) {
- add(getVoxel(VoxelLocation(x, y.toUByte(), z)))
- }
+ return buildList {
+ for (x in getList(cornerA.x, cornerB.x)) {
+ for (y in getList(cornerA.y.toInt(), cornerB.y.toInt())) {
+ for (z in getList(cornerA.z, cornerB.z)) {
+ add(getVoxel(VoxelLocation(x, y.toUByte(), z)))
}
}
}
}
+}
- /**
- * Returns all voxels in a sphere with the specified [center] and [radius].
- */
- fun getVoxelsInSphere(
- center: VoxelLocation,
- radius: Double,
- wallWidth: Int = Int.MAX_VALUE
- ): List {
- val centerVector = center.asVector()
- return getVoxelsInCuboid(
- centerVector.plus(radius, radius, radius).toVoxelLocation(),
- centerVector.minus(radius, radius, radius).toVoxelLocation()
- ).filter {
- val distance = Vector.distanceBetween(it.location.asVector(), centerVector)
- distance < radius && distance > radius - 0.5 - wallWidth
- }
- }
- suspend inline operator fun invoke(noinline block: suspend CoroutineScope.() -> T): T =
- withContext(dispatcher, block)
-
- suspend fun destroy() {
- // TODO: Move or kick players
- scope.cancel()
-
- coroutineScope {
- loadedChunks.values.forEach { launch { (it as? Chunk.Unloadable)?.unload() } }
- }
-
- internalDispatcher.close()
+/**
+ * Returns all voxels in a sphere with the specified [center] and [radius].
+ */
+fun World.getVoxelsInSphere(
+ center: VoxelLocation,
+ radius: Double,
+ wallWidth: Int = Int.MAX_VALUE
+): List {
+ val centerVector = center.asVector()
+ return getVoxelsInCuboid(
+ centerVector.plus(radius, radius, radius).toVoxelLocation(),
+ centerVector.minus(radius, radius, radius).toVoxelLocation()
+ ).filter {
+ val distance = Vector.distanceBetween(it.location.asVector(), centerVector)
+ distance < radius && distance > radius - 0.5 - wallWidth
}
}
+
+suspend inline fun runIn(world: World, noinline block: suspend CoroutineScope.() -> T): T =
+ withContext(world.dispatcher, block)
diff --git a/uranos-api/src/main/kotlin/space/uranos/world/WorldRegistry.kt b/uranos-api/src/main/kotlin/space/uranos/world/WorldRegistry.kt
new file mode 100644
index 0000000..9cad049
--- /dev/null
+++ b/uranos-api/src/main/kotlin/space/uranos/world/WorldRegistry.kt
@@ -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.world
+
+open class WorldRegistry {
+ protected val internalItems = HashMap()
+ val items: Collection get() = internalItems.keys
+
+ fun register(internals: World.Internals) {
+ internalItems[internals.world] = internals
+ }
+}
diff --git a/uranos-packet-codecs/src/main/kotlin/space/uranos/util/SpawnEntity.kt b/uranos-packet-codecs/src/main/kotlin/space/uranos/util/SpawnEntity.kt
index fdc4505..f54e72a 100644
--- a/uranos-packet-codecs/src/main/kotlin/space/uranos/util/SpawnEntity.kt
+++ b/uranos-packet-codecs/src/main/kotlin/space/uranos/util/SpawnEntity.kt
@@ -33,8 +33,9 @@ fun Entity.createSpawnPacket(): OutgoingPacket = when (this) {
uuid,
motive,
centerLocation,
- direction
+ facing
)
+ else -> throw IllegalArgumentException("Unknown entity type")
}
fun ObjectEntity.getDataValue(): Int = when (this) {
diff --git a/uranos-packets/src/main/kotlin/space/uranos/net/packet/play/SpawnLivingEntityPacket.kt b/uranos-packets/src/main/kotlin/space/uranos/net/packet/play/SpawnLivingEntityPacket.kt
index 6d135d2..cc6c690 100644
--- a/uranos-packets/src/main/kotlin/space/uranos/net/packet/play/SpawnLivingEntityPacket.kt
+++ b/uranos-packets/src/main/kotlin/space/uranos/net/packet/play/SpawnLivingEntityPacket.kt
@@ -9,9 +9,7 @@ import space.uranos.Position
import space.uranos.Vector
import space.uranos.entity.EntityType
import space.uranos.net.packet.OutgoingPacket
-import space.uranos.world.Chunk
-import space.uranos.world.ChunkData
-import java.util.*
+import java.util.UUID
/**
* Sent to spawn **living** entities.
@@ -19,7 +17,7 @@ import java.util.*
data class SpawnLivingEntityPacket(
val entityID: Int,
val uuid: UUID,
- val type: EntityType,
+ val type: EntityType<*>,
val position: Position,
val headPitch: Float,
/**
diff --git a/uranos-packets/src/main/kotlin/space/uranos/net/packet/play/SpawnObjectEntityPacket.kt b/uranos-packets/src/main/kotlin/space/uranos/net/packet/play/SpawnObjectEntityPacket.kt
index 9296490..a299e39 100644
--- a/uranos-packets/src/main/kotlin/space/uranos/net/packet/play/SpawnObjectEntityPacket.kt
+++ b/uranos-packets/src/main/kotlin/space/uranos/net/packet/play/SpawnObjectEntityPacket.kt
@@ -9,9 +9,7 @@ import space.uranos.Position
import space.uranos.Vector
import space.uranos.entity.EntityType
import space.uranos.net.packet.OutgoingPacket
-import space.uranos.world.Chunk
-import space.uranos.world.ChunkData
-import java.util.*
+import java.util.UUID
/**
* Sent to spawn object entities.
@@ -19,7 +17,7 @@ import java.util.*
data class SpawnObjectEntityPacket(
val entityID: Int,
val uuid: UUID,
- val type: EntityType,
+ val type: EntityType<*>,
val position: Position,
val data: Int,
val velocity: Vector
diff --git a/uranos-server/src/main/kotlin/space/uranos/UranosServer.kt b/uranos-server/src/main/kotlin/space/uranos/UranosServer.kt
index 78bae50..0530105 100644
--- a/uranos-server/src/main/kotlin/space/uranos/UranosServer.kt
+++ b/uranos-server/src/main/kotlin/space/uranos/UranosServer.kt
@@ -9,8 +9,8 @@ import com.sksamuel.hoplite.ConfigFilePropertySource
import com.sksamuel.hoplite.ConfigLoader
import com.sksamuel.hoplite.ConfigSource
import kotlinx.coroutines.runBlocking
-import space.uranos.command.Command
import space.uranos.config.UranosConfig
+import space.uranos.entity.*
import space.uranos.entity.event.ViewingChangedEvent
import space.uranos.event.EventHandlerPosition
import space.uranos.event.UranosEventBus
@@ -22,16 +22,15 @@ import space.uranos.net.packet.play.DestroyEntitiesPacket
import space.uranos.net.packet.play.PlayerInfoPacket
import space.uranos.player.UranosPlayer
import space.uranos.plugin.UranosPluginManager
-import space.uranos.recipe.Recipe
import space.uranos.server.Server
import space.uranos.util.EncryptionUtils
import space.uranos.util.createSpawnPacket
import space.uranos.util.msToTicks
import space.uranos.util.runInServerThread
-import space.uranos.world.BiomeRegistry
-import space.uranos.world.Dimension
+import space.uranos.world.UranosWorldRegistry
import java.io.File
import java.security.KeyPair
+import java.util.concurrent.atomic.AtomicInteger
import kotlin.system.exitProcess
// TODO: Consider using DI because this improves testability
@@ -60,11 +59,6 @@ class UranosServer internal constructor() : Server() {
else it
}
- override val dimensionRegistry = Registry()
- override val recipeRegistry = Registry()
- override val commandRegistry = Registry()
- override val biomeRegistry = BiomeRegistry()
-
override val loggingOutputProvider = UranosLoggingOutputProvider
override val scheduler = UranosScheduler()
@@ -88,12 +82,35 @@ class UranosServer internal constructor() : Server() {
override val eventBus = UranosEventBus(config.logging.events)
override val eventHandlerPositions = UranosEventHandlerPositionManager()
+ override val worldRegistry = UranosWorldRegistry()
+
+ private val internalEntities = HashSet()
+ override val entities: Set = internalEntities
+
+ override fun create(type: EntityType): T {
+ val entity: UranosEntity = when (type) {
+ CowEntity -> UranosCowEntity(this)
+ else -> throw IllegalArgumentException("Entities of this type cannot be created with this function")
+ }
+
+ internalEntities.add(entity)
+
+ @Suppress("UNCHECKED_CAST")
+ return entity as T
+ }
+
+ fun createPlayerEntity(player: UranosPlayer) =
+ UranosPlayerEntity(this, player).also { internalEntities.add(it) }
+
override fun shutdown() {
runBlocking {
scheduler.shutdown()
}
}
+ private val nextEntityID = AtomicInteger(1)
+ fun claimEntityID() = nextEntityID.getAndIncrement()
+
private fun failInitialization(t: Throwable): Nothing {
logger.error("Server initialization failed:", t)
exitProcess(1)
@@ -119,12 +136,7 @@ class UranosServer internal constructor() : Server() {
scheduler.executeRepeating(1, 0) {
runInServerThread {
players.forEach { it.container.tick() }
-
- entities.forEach {
- @Suppress("DEPRECATION_ERROR")
- it.tick()
- }
-
+ internalEntities.forEach { it.tick() }
sessions.forEach { it.packetsAdapter.tick() }
}
}
@@ -142,6 +154,8 @@ class UranosServer internal constructor() : Server() {
private fun registerListeners() {
eventBus.on(EventHandlerPosition.LAST) { event ->
+ if (event.target == event.player.entity) return@on
+
if (event.viewing) event.player.session.sendNextTick(event.target.createSpawnPacket())
else event.player.session.sendNextTick(DestroyEntitiesPacket(arrayOf(event.target.numericID)))
}
diff --git a/uranos-server/src/main/kotlin/space/uranos/entity/UranosCowEntity.kt b/uranos-server/src/main/kotlin/space/uranos/entity/UranosCowEntity.kt
new file mode 100644
index 0000000..ed57538
--- /dev/null
+++ b/uranos-server/src/main/kotlin/space/uranos/entity/UranosCowEntity.kt
@@ -0,0 +1,10 @@
+/*
+ * 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.UranosServer
+
+class UranosCowEntity(server: UranosServer) : UranosLivingEntity(server), CowEntity
diff --git a/uranos-server/src/main/kotlin/space/uranos/entity/UranosEntity.kt b/uranos-server/src/main/kotlin/space/uranos/entity/UranosEntity.kt
new file mode 100644
index 0000000..03d3a6a
--- /dev/null
+++ b/uranos-server/src/main/kotlin/space/uranos/entity/UranosEntity.kt
@@ -0,0 +1,99 @@
+/*
+ * 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 kotlinx.coroutines.launch
+import kotlinx.coroutines.sync.Mutex
+import kotlinx.coroutines.sync.withLock
+import space.uranos.*
+import space.uranos.entity.event.ViewingChangedEvent
+import space.uranos.player.Player
+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.getInternalsIfRegistered
+import java.util.Collections
+import java.util.UUID
+import java.util.WeakHashMap
+
+sealed class UranosEntity(server: UranosServer) : Entity {
+ abstract val chunkKey: Chunk.Key
+
+ override fun belongsToChunk(key: Chunk.Key): Boolean = key == chunkKey
+
+ override val numericID: Int = server.claimEntityID()
+ override val uuid: UUID = UUID.randomUUID()
+
+ override val viewers: MutableSet = object : WatchableSet(Collections.newSetFromMap(WeakHashMap())) {
+ override fun onAdd(element: Player) {
+ Uranos.scope.launch { Uranos.eventBus.emit(ViewingChangedEvent(this@UranosEntity, element, true)) }
+ }
+
+ override fun onRemove(element: Player) {
+ Uranos.scope.launch { Uranos.eventBus.emit(ViewingChangedEvent(this@UranosEntity, element, false)) }
+ }
+ }
+
+ /**
+ * If players should be added to [viewers] when they join.
+ */
+ override var visibleToNewPlayers: Boolean = true
+
+ private val worldMutex = Mutex()
+ final override var world: World? = null; private set
+
+ protected val container = TickSynchronizationContainer()
+
+ override suspend fun setWorld(world: World?) {
+ if (world == null && this is PlayerEntity)
+ throw IllegalArgumentException("You cannot set the world of a PlayerEntity to null")
+
+ if (world == this.world) return
+
+ worldMutex.withLock {
+ this.world?.getInternalsIfRegistered()?.removeEntity(this)
+ this.world = world
+ world?.getInternalsIfRegistered()?.addEntity(this)
+ }
+ }
+
+ suspend fun tick() {
+ container.tick()
+ }
+}
+
+abstract class UranosLivingEntity(server: UranosServer) : UranosEntity(server), LivingEntity {
+ override var velocity: Vector = Vector.ZERO
+ override var headPitch: Float = 0f
+
+ override var position: Position by container.ifChanged(Position.ZERO) { value ->
+ // TODO: Broadcast to players
+ }
+
+ override val chunkKey: Chunk.Key by memoized({ position }) { Chunk.Key.from(position) }
+}
+
+abstract class UranosObjectEntity(server: UranosServer) : UranosEntity(server), ObjectEntity {
+ override var velocity: Vector = Vector.ZERO
+
+ override var position: Position by container.ifChanged(Position.ZERO) { value ->
+ // TODO: Broadcast to players
+ }
+
+ override val chunkKey: Chunk.Key by memoized({ position }) { Chunk.Key.from(position) }
+}
+
+class UranosPaintingEntity(
+ server: UranosServer,
+ override val topLeftLocation: VoxelLocation,
+ override val facing: CardinalDirection,
+ override val motive: PaintingMotive
+) : UranosEntity(server), PaintingEntity {
+ override val chunkKey: Chunk.Key get() = Chunk.Key.from(topLeftLocation)
+}
diff --git a/uranos-server/src/main/kotlin/space/uranos/entity/UranosPlayerEntity.kt b/uranos-server/src/main/kotlin/space/uranos/entity/UranosPlayerEntity.kt
new file mode 100644
index 0000000..cecaa40
--- /dev/null
+++ b/uranos-server/src/main/kotlin/space/uranos/entity/UranosPlayerEntity.kt
@@ -0,0 +1,17 @@
+/*
+ * 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.UranosServer
+import space.uranos.player.Player
+import java.util.UUID
+
+class UranosPlayerEntity(
+ server: UranosServer,
+ override val player: Player
+) : UranosLivingEntity(server), PlayerEntity {
+ override val uuid: UUID = player.uuid
+}
diff --git a/uranos-server/src/main/kotlin/space/uranos/player/UranosPlayer.kt b/uranos-server/src/main/kotlin/space/uranos/player/UranosPlayer.kt
index 4896615..732c6d1 100644
--- a/uranos-server/src/main/kotlin/space/uranos/player/UranosPlayer.kt
+++ b/uranos-server/src/main/kotlin/space/uranos/player/UranosPlayer.kt
@@ -6,9 +6,9 @@
package space.uranos.player
import space.uranos.Position
-import space.uranos.Uranos
import space.uranos.chat.TextComponent
import space.uranos.entity.PlayerEntity
+import space.uranos.entity.safeWorld
import space.uranos.net.UranosSession
import space.uranos.net.packet.play.ChunkDataPacket
import space.uranos.net.packet.play.ChunkLightDataPacket
@@ -16,11 +16,10 @@ import space.uranos.net.packet.play.PlayerInfoPacket
import space.uranos.net.packet.play.SelectedHotbarSlotPacket
import space.uranos.util.TickSynchronizationContainer
import space.uranos.util.clampArgument
-import space.uranos.util.createSpawnPacket
import space.uranos.world.Chunk
import space.uranos.world.VoxelLocation
import space.uranos.world.World
-import java.util.*
+import java.util.UUID
import kotlin.math.abs
class UranosPlayer(
@@ -61,14 +60,16 @@ class UranosPlayer(
override var currentlyViewedChunks = emptyList()
- override val entity: PlayerEntity = PlayerEntity(position, this, headPitch)
+ override val entity: PlayerEntity = session.server.createPlayerEntity(this).also {
+ it.position = position
+ it.headPitch = headPitch
+ }
suspend fun spawnInitially(world: World) {
- Uranos.entities.forEach { if (it.visibleToNewPlayers) it.viewers.add(this) }
+ session.server.entities.forEach { if (it.visibleToNewPlayers) it.viewers.add(this) }
entity.setWorld(world)
updateCurrentlyViewedChunks()
sendChunksAndLight()
- sendEntitiesInViewedChunks()
}
/**
@@ -93,15 +94,4 @@ class UranosPlayer(
chunks.forEach { session.send(ChunkLightDataPacket(it.key, it.getLightData(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()) }
- }
}
diff --git a/uranos-server/src/main/kotlin/space/uranos/world/GetInternalsIfRegistered.kt b/uranos-server/src/main/kotlin/space/uranos/world/GetInternalsIfRegistered.kt
new file mode 100644
index 0000000..651774c
--- /dev/null
+++ b/uranos-server/src/main/kotlin/space/uranos/world/GetInternalsIfRegistered.kt
@@ -0,0 +1,12 @@
+/*
+ * 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.world
+
+import space.uranos.Uranos
+import space.uranos.UranosServer
+
+fun World.getInternalsIfRegistered(): World.Internals =
+ (Uranos as UranosServer).worldRegistry.internals[this] ?: throw IllegalStateException("World not registered")
diff --git a/uranos-server/src/main/kotlin/space/uranos/world/UranosWorldRegistry.kt b/uranos-server/src/main/kotlin/space/uranos/world/UranosWorldRegistry.kt
new file mode 100644
index 0000000..bc63c88
--- /dev/null
+++ b/uranos-server/src/main/kotlin/space/uranos/world/UranosWorldRegistry.kt
@@ -0,0 +1,10 @@
+/*
+ * 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.world
+
+class UranosWorldRegistry : WorldRegistry() {
+ val internals = internalItems
+}